diff --git a/.jcheck/conf b/.jcheck/conf index 60881e74d2a..c2c2e856524 100644 --- a/.jcheck/conf +++ b/.jcheck/conf @@ -1,15 +1,11 @@ [general] -project=jdk +project=valhalla jbs=JDK version=26 [checks] -error=author,committer,reviewers,merge,issues,executable,symlink,message,hg-tag,whitespace,problemlists,copyright -warning=issuestitle,binary - -[repository] -tags=(?:jdk-(?:[1-9]([0-9]*)(?:\.(?:0|[1-9][0-9]*)){0,4})(?:\+(?:(?:[0-9]+))|(?:-ga)))|(?:jdk[4-9](?:u\d{1,3})?-(?:(?:b\d{2,3})|(?:ga)))|(?:hs\d\d(?:\.\d{1,2})?-b\d\d) -branches=.* +error=author,committer,executable,symlink,whitespace +warning=issuestitle [census] version=0 @@ -19,19 +15,9 @@ domain=openjdk.org files=.*\.cpp|.*\.hpp|.*\.c|.*\.h|.*\.java|.*\.cc|.*\.hh|.*\.m|.*\.mm|.*\.S|.*\.md|.*\.properties|.*\.gmk|.*\.m4|.*\.ac|Makefile ignore-tabs=.*\.gmk|Makefile -[checks "merge"] -message=Merge - -[checks "reviewers"] -reviewers=1 -ignore=duke - [checks "committer"] role=committer -[checks "issues"] -pattern=^([124-8][0-9]{6}): (\S.*)$ - [checks "problemlists"] dirs=test/jdk|test/langtools|test/lib-test|test/hotspot/jtreg|test/jaxp diff --git a/make/CompileJavaModules.gmk b/make/CompileJavaModules.gmk index 54d063a7a71..8741dba1868 100644 --- a/make/CompileJavaModules.gmk +++ b/make/CompileJavaModules.gmk @@ -30,6 +30,8 @@ include MakeFileStart.gmk include JavaCompilation.gmk include Modules.gmk +include CopyFiles.gmk + ################################################################################ # If this is an imported module that has prebuilt classes, only compile # module-info.java. @@ -97,7 +99,6 @@ endif ################################################################################ # Setup the main compilation - $(eval $(call SetupJavaCompilation, $(MODULE), \ SMALL_JAVA := false, \ MODULE := $(MODULE), \ @@ -126,6 +127,51 @@ $(eval $(call SetupJavaCompilation, $(MODULE), \ TARGETS += $($(MODULE)) +################################################################################ +# Setup compilation for value classes in the module +# TBD: When $(DOCLINT) was included there was an NPE in JavacTypes.getOverriddenMethods + +# Directory and file name suffix for jar file containing value classes +VALUECLASSES_STR := valueclasses + +ifneq ($(COMPILER), bootjdk) + MODULE_VALUECLASS_SRC_DIRS := $(call FindModuleValueClassSrcDirs, $(MODULE)) + MODULE_VALUECLASS_SOURCEPATH := $(call GetModuleValueClassSrcPath) + + ifneq ($(MODULE_VALUECLASS_SRC_DIRS),) + $(eval $(call SetupJavaCompilation, $(MODULE)-$(VALUECLASSES_STR), \ + SMALL_JAVA := false, \ + MODULE := $(MODULE), \ + SRC := $(wildcard $(MODULE_VALUECLASS_SRC_DIRS)), \ + INCLUDES := $(JDK_USER_DEFINED_FILTER), \ + FAIL_NO_SRC := $(FAIL_NO_SRC), \ + BIN := $(SUPPORT_OUTPUTDIR)/$(VALUECLASSES_STR)/, \ + JAR := $(JDK_OUTPUTDIR)/lib/$(VALUECLASSES_STR)/$(MODULE)-$(VALUECLASSES_STR).jar, \ + HEADERS := $(SUPPORT_OUTPUTDIR)/headers, \ + DISABLED_WARNINGS := $(DISABLED_WARNINGS_java) preview, \ + EXCLUDES := $(EXCLUDES), \ + EXCLUDE_FILES := $(EXCLUDE_FILES) \ + KEEP_ALL_TRANSLATIONS := $(KEEP_ALL_TRANSLATIONS), \ + DEPENDS := $($(MODULE)), \ + JAVAC_FLAGS := \ + $(JAVAC_FLAGS) \ + --module-source-path $(MODULE_VALUECLASS_SOURCEPATH) \ + --module-path $(JDK_OUTPUTDIR)/modules \ + --system none \ + --enable-preview -source $(JDK_SOURCE_TARGET_VERSION), \ + )) + + TARGETS += $($(MODULE)-$(VALUECLASSES_STR)) + + $(eval $(call SetupCopyFiles, $(MODULE)-copy-valueclass-jar, \ + FILES := $(JDK_OUTPUTDIR)/lib/$(VALUECLASSES_STR)/$(MODULE)-$(VALUECLASSES_STR).jar, \ + DEST := $(SUPPORT_OUTPUTDIR)/modules_libs/$(MODULE)/$(VALUECLASSES_STR), \ + )) + + TARGETS += $($(MODULE)-copy-valueclass-jar) + endif +endif + # Declare dependencies between java compilations of different modules. # Since the other modules are declared in different invocations of this file, # use the macro to find the correct target file to depend on. diff --git a/make/Docs.gmk b/make/Docs.gmk index 8b20572cb03..667f9e9fda9 100644 --- a/make/Docs.gmk +++ b/make/Docs.gmk @@ -93,9 +93,11 @@ JAVADOC_DISABLED_DOCLINT_WARNINGS := missing JAVADOC_DISABLED_DOCLINT_PACKAGES := org.w3c.* javax.smartcardio # The initial set of options for javadoc -JAVADOC_OPTIONS := -use -keywords -notimestamp \ +JAVADOC_OPTIONS := -XDignore.symbol.file=true -use -keywords -notimestamp \ -serialwarn -encoding utf-8 -docencoding utf-8 -breakiterator \ -splitIndex --system none -javafx --expand-requires transitive \ + -XDenableValueTypes \ + --enable-preview -source $(JDK_SOURCE_TARGET_VERSION) \ --override-methods=summary # The reference options must stay stable to allow for comparisons across the diff --git a/make/Images.gmk b/make/Images.gmk index 34d81081d29..8d5b066a72e 100644 --- a/make/Images.gmk +++ b/make/Images.gmk @@ -138,6 +138,7 @@ CDS_DUMP_FLAGS = -Xmx128M -Xms128M # # Param1 - VM variant (e.g., server, client, zero, ...) # Param2 - _nocoops, _coh, _nocoops_coh, or empty +# Param3 - _valhalla, or empty define CreateCDSArchive $1_$2_COOPS_OPTION := $(if $(findstring _nocoops, $2),-XX:-UseCompressedOops) # enable and also explicitly disable coh as needed. @@ -145,16 +146,16 @@ define CreateCDSArchive $1_$2_COH_OPTION := -XX:+UnlockExperimentalVMOptions \ $(if $(findstring _coh, $2),-XX:+UseCompactObjectHeaders,-XX:-UseCompactObjectHeaders) endif - $1_$2_DUMP_EXTRA_ARG := $$($1_$2_COOPS_OPTION) $$($1_$2_COH_OPTION) - $1_$2_DUMP_TYPE := $(if $(findstring _nocoops, $2),-NOCOOPS,)$(if $(findstring _coh, $2),-COH,) + $1_$2_$3_DUMP_EXTRA_ARG := $$($1_$2_COOPS_OPTION) $$($1_$2_COH_OPTION) $(if $(findstring _valhalla, $3), --enable-preview,) + $1_$2_$3_DUMP_TYPE := $(if $(findstring _nocoops, $2),-NOCOOPS,)$(if $(findstring _coh, $2),-COH,)$(if $(findstring _valhalla, $3), -VALHALLA,) # Only G1 supports dumping the shared heap, so explicitly use G1 if the JVM supports it. - $1_$2_CDS_DUMP_FLAGS := $(CDS_DUMP_FLAGS) $(if $(filter g1gc, $(JVM_FEATURES_$1)), -XX:+UseG1GC) + $1_$2_$3_CDS_DUMP_FLAGS := $(CDS_DUMP_FLAGS) $(if $(filter g1gc, $(JVM_FEATURES_$1)), -XX:+UseG1GC) ifeq ($(OPENJDK_TARGET_OS), windows) - $1_$2_CDS_ARCHIVE := bin/$1/classes$2.jsa + $1_$2_$3_CDS_ARCHIVE := bin/$1/classes$2$3.jsa else - $1_$2_CDS_ARCHIVE := lib/$1/classes$2.jsa + $1_$2_$3_CDS_ARCHIVE := lib/$1/classes$2$3.jsa endif ifneq ($(COMPARE_BUILD), ) @@ -165,41 +166,43 @@ define CreateCDSArchive $1_$2_CDS_DUMP_FLAGS += -Xlog:aot+map*=trace:file=$$(JDK_IMAGE_DIR)/$$($1_$2_CDS_ARCHIVE).cdsmap:none:filesize=0 endif - $$(eval $$(call SetupExecute, $1_$2_gen_cds_archive_jdk, \ - WARN := Creating CDS$$($1_$2_DUMP_TYPE) archive for jdk image for $1, \ - INFO := Using CDS flags for $1: $$($1_$2_CDS_DUMP_FLAGS), \ + $$(eval $$(call SetupExecute, $1_$2_$3_gen_cds_archive_jdk, \ + WARN := Creating CDS$$($1_$2_$3_DUMP_TYPE) archive for jdk image for $1, \ + INFO := Using CDS flags for $1: $$($1_$2_$3_CDS_DUMP_FLAGS), \ DEPS := $$(jlink_jdk), \ - OUTPUT_FILE := $$(JDK_IMAGE_DIR)/$$($1_$2_CDS_ARCHIVE), \ + OUTPUT_FILE := $$(JDK_IMAGE_DIR)/$$($1_$2_$3_CDS_ARCHIVE), \ SUPPORT_DIR := $$(JDK_IMAGE_SUPPORT_DIR), \ COMMAND := $$(FIXPATH) $$(JDK_IMAGE_DIR)/bin/java -Xshare:dump \ - -XX:SharedArchiveFile=$$(JDK_IMAGE_DIR)/$$($1_$2_CDS_ARCHIVE) \ - -$1 $$($1_$2_DUMP_EXTRA_ARG) $$($1_$2_CDS_DUMP_FLAGS) $$(LOG_INFO), \ + -XX:SharedArchiveFile=$$(JDK_IMAGE_DIR)/$$($1_$2_$3_CDS_ARCHIVE) \ + -$1 $$($1_$2_$3_DUMP_EXTRA_ARG) $$($1_$2_$3_CDS_DUMP_FLAGS) $$(LOG_INFO), \ )) - JDK_TARGETS += $$($1_$2_gen_cds_archive_jdk) + JDK_TARGETS += $$($1_$2_$3_gen_cds_archive_jdk) - $$(eval $$(call SetupExecute, $1_$2_gen_cds_archive_jre, \ - WARN := Creating CDS$$($1_$2_DUMP_TYPE) archive for jre image for $1, \ - INFO := Using CDS flags for $1: $$($1_$2_CDS_DUMP_FLAGS), \ + $$(eval $$(call SetupExecute, $1_$2_$3_gen_cds_archive_jre, \ + WARN := Creating CDS$$($1_$2_$3_DUMP_TYPE) archive for jre image for $1, \ + INFO := Using CDS flags for $1: $$($1_$2_$3_CDS_DUMP_FLAGS), \ DEPS := $$(jlink_jre), \ - OUTPUT_FILE := $$(JRE_IMAGE_DIR)/$$($1_$2_CDS_ARCHIVE), \ + OUTPUT_FILE := $$(JRE_IMAGE_DIR)/$$($1_$2_$3_CDS_ARCHIVE), \ SUPPORT_DIR := $$(JRE_IMAGE_SUPPORT_DIR), \ COMMAND := $$(FIXPATH) $$(JRE_IMAGE_DIR)/bin/java -Xshare:dump \ - -XX:SharedArchiveFile=$$(JRE_IMAGE_DIR)/$$($1_$2_CDS_ARCHIVE) \ - -$1 $$($1_$2_DUMP_EXTRA_ARG) $$($1_$2_CDS_DUMP_FLAGS) $$(LOG_INFO), \ + -XX:SharedArchiveFile=$$(JRE_IMAGE_DIR)/$$($1_$2_$3_CDS_ARCHIVE) \ + -$1 $$($1_$2_$3_DUMP_EXTRA_ARG) $$($1_$2_$3_CDS_DUMP_FLAGS) $$(LOG_INFO), \ )) - JRE_TARGETS += $$($1_$2_gen_cds_archive_jre) + JRE_TARGETS += $$($1_$2_$3_gen_cds_archive_jre) endef ifeq ($(BUILD_CDS_ARCHIVE), true) $(foreach v, $(JVM_VARIANTS), \ - $(eval $(call CreateCDSArchive,$v,)) \ + $(eval $(call CreateCDSArchive,$v,,)) \ + $(eval $(call CreateCDSArchive,$v,,_valhalla)) \ ) ifeq ($(call isTargetCpuBits, 64), true) $(foreach v, $(JVM_VARIANTS), \ - $(eval $(call CreateCDSArchive,$v,_nocoops)) \ + $(eval $(call CreateCDSArchive,$v,_nocoops,)) \ + $(eval $(call CreateCDSArchive,$v,_nocoops,_valhalla)) \ ) ifeq ($(BUILD_CDS_ARCHIVE_COH), true) $(foreach v, $(JVM_VARIANTS), \ diff --git a/make/RunTestsPrebuiltSpec.gmk b/make/RunTestsPrebuiltSpec.gmk index e9fd901c9e1..28466a1eed5 100644 --- a/make/RunTestsPrebuiltSpec.gmk +++ b/make/RunTestsPrebuiltSpec.gmk @@ -63,7 +63,7 @@ TEST_JOBS ?= 0 # Use hard-coded values for java flags (one size, fits all!) JAVA_FLAGS := -Duser.language=en -Duser.country=US -JAVA_FLAGS_BIG := -Xms64M -Xmx2048M +JAVA_FLAGS_BIG := -Xms64M -Xmx3200M JAVA_FLAGS_SMALL := -XX:+UseSerialGC -Xms32M -Xmx512M -XX:TieredStopAtLevel=1 BUILDJDK_JAVA_FLAGS_SMALL := -Xms32M -Xmx512M -XX:TieredStopAtLevel=1 BUILD_JAVA_FLAGS := $(JAVA_FLAGS_BIG) diff --git a/make/autoconf/boot-jdk.m4 b/make/autoconf/boot-jdk.m4 index 4ba1f9f2089..d51996af23b 100644 --- a/make/autoconf/boot-jdk.m4 +++ b/make/autoconf/boot-jdk.m4 @@ -470,7 +470,7 @@ AC_DEFUN_ONCE([BOOTJDK_SETUP_BOOT_JDK_ARGUMENTS], # Maximum amount of heap memory. JVM_HEAP_LIMIT_32="768" # Running a 64 bit JVM allows for and requires a bigger heap - JVM_HEAP_LIMIT_64="2048" + JVM_HEAP_LIMIT_64="3200" JVM_HEAP_LIMIT_GLOBAL=`expr $MEMORY_SIZE / 2` if test "$JVM_HEAP_LIMIT_GLOBAL" -lt "$JVM_HEAP_LIMIT_32"; then JVM_HEAP_LIMIT_32=$JVM_HEAP_LIMIT_GLOBAL diff --git a/make/autoconf/hotspot.m4 b/make/autoconf/hotspot.m4 index 6dc46d17aa3..3bff41b6321 100644 --- a/make/autoconf/hotspot.m4 +++ b/make/autoconf/hotspot.m4 @@ -26,6 +26,9 @@ # All valid JVM variants VALID_JVM_VARIANTS="server client minimal core zero custom" +# Valhalla temporarily disabled +VALHALLA_TEMP=false + ################################################################################ # Check if the specified JVM variant should be built. To be used in shell if # constructs, like this: diff --git a/make/autoconf/jdk-options.m4 b/make/autoconf/jdk-options.m4 index d4299078abf..75f9c272725 100644 --- a/make/autoconf/jdk-options.m4 +++ b/make/autoconf/jdk-options.m4 @@ -749,9 +749,11 @@ AC_DEFUN([JDKOPT_ENABLE_DISABLE_CDS_ARCHIVE], # # Enable or disable the default CDS archive generation for Compact Object Headers # +# Default disabled within Valhalla until support added (JDK-8348568) +# AC_DEFUN([JDKOPT_ENABLE_DISABLE_CDS_ARCHIVE_COH], [ - UTIL_ARG_ENABLE(NAME: cds-archive-coh, DEFAULT: auto, RESULT: BUILD_CDS_ARCHIVE_COH, + UTIL_ARG_ENABLE(NAME: cds-archive-coh, DEFAULT: false, RESULT: BUILD_CDS_ARCHIVE_COH, DESC: [enable generation of default CDS archives for compact object headers (requires --enable-cds-archive)], DEFAULT_DESC: [auto], CHECKING_MSG: [if default CDS archives for compact object headers should be generated], diff --git a/make/autoconf/lib-tests.m4 b/make/autoconf/lib-tests.m4 index 9eb5ee5a046..d2a4fcbb191 100644 --- a/make/autoconf/lib-tests.m4 +++ b/make/autoconf/lib-tests.m4 @@ -28,7 +28,7 @@ ################################################################################ # Minimum supported versions -JTREG_MINIMUM_VERSION=7.5.2 +JTREG_MINIMUM_VERSION=7.5.1 GTEST_MINIMUM_VERSION=1.14.0 ################################################################################ diff --git a/make/common/Modules.gmk b/make/common/Modules.gmk index 2880504676a..2566f09889f 100644 --- a/make/common/Modules.gmk +++ b/make/common/Modules.gmk @@ -75,6 +75,10 @@ GENERATED_SRC_DIRS += \ $(SUPPORT_OUTPUTDIR)/gensrc \ # +GENERATED_VALUE_CLASS_SUBDIRS += \ + $(SUPPORT_OUTPUTDIR)/gensrc-valueclasses \ + # + TOP_SRC_DIRS += \ $(TOPDIR)/src \ # @@ -144,6 +148,12 @@ FindModuleSrcDirs = \ $(addsuffix /$(strip $1), $(GENERATED_SRC_DIRS) $(IMPORT_MODULES_SRC)) \ $(foreach sub, $(SRC_SUBDIRS), $(addsuffix /$(strip $1)/$(sub), $(TOP_SRC_DIRS))))) +# Find value class source dirs for a particular module (only generated) +# $1 - Module to find source dirs for +FindModuleValueClassSrcDirs = \ + $(strip $(wildcard \ + $(addsuffix /$(strip $1), $(GENERATED_VALUE_CLASS_SUBDIRS)))) + # Find all specs dirs for a particular module # $1 - Module to find specs dirs for FindModuleSpecsDirs = \ @@ -166,6 +176,12 @@ GetModuleSrcPath = \ $(addsuffix /*, $(GENERATED_SRC_DIRS) $(IMPORT_MODULES_SRC)) \ $(foreach sub, $(SRC_SUBDIRS), $(addsuffix /*/$(sub), $(TOP_SRC_DIRS)))) +# Construct the complete module source path for value classes +GetModuleValueClassSrcPath = \ + $(call PathList, \ + $(addsuffix /*, $(GENERATED_VALUE_CLASS_SUBDIRS) $(GENERATED_SRC_DIRS) $(IMPORT_MODULES_SRC)) \ + $(foreach sub, $(SRC_SUBDIRS), $(addsuffix /*/$(sub), $(TOP_SRC_DIRS)))) + ################################################################################ # Extract module dependencies from module-info.java files, both normal # dependencies ("requires"), and indirect exports ("requires transitive"). diff --git a/make/conf/github-actions.conf b/make/conf/github-actions.conf index d2b6cd23128..27845ffbd7a 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.5.2+1 +JTREG_VERSION=7.5.1+1 LINUX_X64_BOOT_JDK_EXT=tar.gz LINUX_X64_BOOT_JDK_URL=https://download.java.net/java/GA/jdk24/1f9ff9062db4449d8ca828c504ffae90/36/GPL/openjdk-24_linux-x64_bin.tar.gz diff --git a/make/conf/jib-profiles.js b/make/conf/jib-profiles.js index d4877604a90..d7ad1d09258 100644 --- a/make/conf/jib-profiles.js +++ b/make/conf/jib-profiles.js @@ -1174,9 +1174,9 @@ var getJibProfilesDependencies = function (input, common) { jtreg: { server: "jpg", product: "jtreg", - version: "7.5.2", + version: "7.5.1", build_number: "1", - file: "bundles/jtreg-7.5.2+1.zip", + file: "bundles/jtreg-7.5.1+1.zip", environment_name: "JT_HOME", environment_path: input.get("jtreg", "home_path") + "/bin", configure_args: "--with-jtreg=" + input.get("jtreg", "home_path"), @@ -1476,6 +1476,7 @@ var versionArgs = function(input, common) { + common.build_number + "." + ciBuildNumber); } } else { + args = concat(args, "--with-version-pre=" + version_numbers.get("DEFAULT_PROMOTED_VERSION_PRE")); args = concat(args, "--with-version-opt=" + common.build_id); } var sourceDate diff --git a/make/conf/version-numbers.conf b/make/conf/version-numbers.conf index 38d6e42dff9..013ca8327fb 100644 --- a/make/conf/version-numbers.conf +++ b/make/conf/version-numbers.conf @@ -39,4 +39,4 @@ DEFAULT_VERSION_CLASSFILE_MINOR=0 DEFAULT_VERSION_DOCS_API_SINCE=11 DEFAULT_ACCEPTABLE_BOOT_VERSIONS="24 25 26" DEFAULT_JDK_SOURCE_TARGET_VERSION=26 -DEFAULT_PROMOTED_VERSION_PRE=ea +DEFAULT_PROMOTED_VERSION_PRE=jep401ea2 diff --git a/make/hotspot/lib/JvmFeatures.gmk b/make/hotspot/lib/JvmFeatures.gmk index 0fd1c752174..4b4c68e441d 100644 --- a/make/hotspot/lib/JvmFeatures.gmk +++ b/make/hotspot/lib/JvmFeatures.gmk @@ -51,10 +51,9 @@ endif ifeq ($(call check-jvm-feature, zero), true) JVM_EXCLUDES += opto libadt JVM_EXCLUDE_PATTERNS += c1_ c1/ c2_ runtime_ /c2/ - JVM_EXCLUDE_FILES += templateInterpreter.cpp \ - templateInterpreterGenerator.cpp bcEscapeAnalyzer.cpp ciTypeFlow.cpp - JVM_CFLAGS_FEATURES += -DZERO \ - -DZERO_LIBARCH='"$(OPENJDK_TARGET_CPU_LEGACY_LIB)"' $(LIBFFI_CFLAGS) + JVM_EXCLUDE_FILES += templateInterpreter.cpp templateInterpreterGenerator.cpp \ + bcEscapeAnalyzer.cpp ciTypeFlow.cpp macroAssembler_common.cpp + JVM_CFLAGS_FEATURES += -DZERO -DZERO_LIBARCH='"$(OPENJDK_TARGET_CPU_LEGACY_LIB)"' $(LIBFFI_CFLAGS) JVM_LIBS_FEATURES += $(LIBFFI_LIBS) ifeq ($(ENABLE_LIBFFI_BUNDLING), true) JVM_LDFLAGS_FEATURES += $(call SET_EXECUTABLE_ORIGIN,/..) diff --git a/make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java b/make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java index fe5938ce0e3..2bf6b76ed4b 100644 --- a/make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java +++ b/make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java @@ -306,6 +306,10 @@ public void createSymbols(String ctDescriptionFileExtra, String ctDescriptionFil "Ljdk/internal/ValueBased;"; private static final String VALUE_BASED_ANNOTATION_INTERNAL = "Ljdk/internal/ValueBased+Annotation;"; + private static final String MIGRATED_VALUE_CLASS_ANNOTATION = + "Ljdk/internal/MigratedValueClass;"; + private static final String MIGRATED_VALUE_CLASS_ANNOTATION_INTERNAL = + "Ljdk/internal/MigratedValueClass+Annotation;"; private static final String REQUIRES_IDENTITY_ANNOTATION = "Ljdk/internal/RequiresIdentity;"; private static final String REQUIRES_IDENTITY_ANNOTATION_INTERNAL = @@ -316,6 +320,7 @@ public void createSymbols(String ctDescriptionFileExtra, String ctDescriptionFil PREVIEW_FEATURE_ANNOTATION_OLD, PREVIEW_FEATURE_ANNOTATION_NEW, VALUE_BASED_ANNOTATION, + MIGRATED_VALUE_CLASS_ANNOTATION, RESTRICTED_ANNOTATION, REQUIRES_IDENTITY_ANNOTATION)); @@ -1034,6 +1039,12 @@ private Annotation createAnnotation(AnnotationDescription desc) { annotationType = VALUE_BASED_ANNOTATION_INTERNAL; } + if (MIGRATED_VALUE_CLASS_ANNOTATION.equals(annotationType)) { + //the non-public MigratedValueClass annotation will not be available in ct.sym, + //replace with purely synthetic javac-internal annotation: + annotationType = MIGRATED_VALUE_CLASS_ANNOTATION_INTERNAL; + } + if (REQUIRES_IDENTITY_ANNOTATION.equals(annotationType)) { //the non-public RequiresIdentity annotation will not be available in ct.sym, //replace with purely synthetic javac-internal annotation: diff --git a/make/modules/java.base/Gensrc.gmk b/make/modules/java.base/Gensrc.gmk index e4a019ed584..856ac61e3ba 100644 --- a/make/modules/java.base/Gensrc.gmk +++ b/make/modules/java.base/Gensrc.gmk @@ -36,6 +36,7 @@ include gensrc/GensrcMisc.gmk include gensrc/GensrcModuleLoaderMap.gmk include gensrc/GensrcRegex.gmk include gensrc/GensrcScopedMemoryAccess.gmk +include gensrc/GensrcValueClasses.gmk include gensrc/GensrcVarHandles.gmk ################################################################################ diff --git a/make/modules/java.base/Java.gmk b/make/modules/java.base/Java.gmk index fc091377456..7af2cf38370 100644 --- a/make/modules/java.base/Java.gmk +++ b/make/modules/java.base/Java.gmk @@ -29,7 +29,7 @@ # new warning is added to javac, it can be temporarily added to the # disabled warnings list. # -# DISABLED_WARNINGS_java += +DISABLED_WARNINGS_java += initialization DOCLINT += -Xdoclint:all/protected \ '-Xdoclint/package:java.*,javax.*' diff --git a/make/modules/java.base/gensrc/GensrcValueClasses.gmk b/make/modules/java.base/gensrc/GensrcValueClasses.gmk new file mode 100644 index 00000000000..6a5b6864b67 --- /dev/null +++ b/make/modules/java.base/gensrc/GensrcValueClasses.gmk @@ -0,0 +1,76 @@ +# +# 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. 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. +# + +################################################################################ +# Generate the value class replacements for selected java.base source files + +java.base-VALUE_CLASS-REPLACEMENTS := \ + java/lang/Byte.java \ + java/lang/Short.java \ + java/lang/Integer.java \ + java/lang/Long.java \ + java/lang/Float.java \ + java/lang/Double.java \ + java/lang/Boolean.java \ + java/lang/Character.java \ + java/lang/Number.java \ + java/lang/Record.java \ + java/util/Optional.java \ + java/util/OptionalInt.java \ + java/util/OptionalLong.java \ + java/util/OptionalDouble.java \ + java/time/LocalDate.java \ + java/time/LocalDateTime.java \ + java/time/LocalTime.java \ + java/time/Duration.java \ + java/time/Instant.java \ + java/time/MonthDay.java \ + java/time/ZonedDateTime.java \ + java/time/OffsetDateTime.java \ + java/time/OffsetTime.java \ + java/time/YearMonth.java \ + java/time/Year.java \ + java/time/Period.java \ + java/time/chrono/ChronoLocalDateImpl.java \ + java/time/chrono/MinguoDate.java \ + java/time/chrono/HijrahDate.java \ + java/time/chrono/JapaneseDate.java \ + java/time/chrono/ThaiBuddhistDate.java \ + # + +java.base-VALUE-CLASS-FILES := \ + $(foreach f, $(java.base-VALUE_CLASS-REPLACEMENTS), $(addprefix $(TOPDIR)/src/java.base/share/classes/, $(f))) + +$(eval $(call SetupTextFileProcessing, JAVA_BASE_VALUECLASS_REPLACEMENTS, \ + SOURCE_FILES := $(java.base-VALUE-CLASS-FILES), \ + SOURCE_BASE_DIR := $(TOPDIR)/src/java.base/share/classes, \ + OUTPUT_DIR := $(SUPPORT_OUTPUTDIR)/gensrc-valueclasses/java.base/, \ + REPLACEMENTS := \ + public final class => public final value class ; \ + public abstract class => public abstract value class ; \ + abstract class ChronoLocalDateImpl => abstract value class ChronoLocalDateImpl, \ +)) + +TARGETS += $(JAVA_BASE_VALUECLASS_REPLACEMENTS) diff --git a/make/modules/java.base/gensrc/GensrcVarHandles.gmk b/make/modules/java.base/gensrc/GensrcVarHandles.gmk index 899f827462c..dafd15b1bca 100644 --- a/make/modules/java.base/gensrc/GensrcVarHandles.gmk +++ b/make/modules/java.base/gensrc/GensrcVarHandles.gmk @@ -39,33 +39,75 @@ VARHANDLES_SRC_DIR := $(MODULE_SRC)/share/classes/java/lang/invoke # Param 2 - Type with first letter capitalized define GenerateVarHandle - $1_Type := $2 + $1_InputType := $2 - $1_FILENAME := $(VARHANDLES_GENSRC_DIR)/VarHandle$$($1_Type)s.java + $1_FILENAME := $(VARHANDLES_GENSRC_DIR)/VarHandle$$($1_InputType)s.java $1_ARGS += -KCAS - ifneq ($$(findstring $$($1_Type), Byte Short Char Int Long Float Double), ) + ifneq ($$(findstring $$($1_InputType), Byte Short Char Int Long Float Double), ) $1_ARGS += -KAtomicAdd + $1_ARGS += -KNonPlainAccess + $1_ARGS += -KStatic + $1_ARGS += -KArray + endif + + ifneq ($$(findstring $$($1_InputType), Byte Short Char Int Long), ) + $1_ARGS += -KBitwise endif - ifneq ($$(findstring $$($1_Type), Boolean Byte Short Char Int Long), ) + ifeq ($$($1_InputType), Boolean) $1_ARGS += -KBitwise + $1_ARGS += -KNonPlainAccess + $1_ARGS += -KStatic + $1_ARGS += -KArray endif - ifneq ($$(findstring $$($1_Type), Byte Short Char), ) + ifneq ($$(findstring $$($1_InputType), Byte Short Char), ) $1_ARGS += -KShorterThanInt endif + ifeq ($$($1_InputType), Reference) + $1_ARGS += -KReference + $1_ARGS += -KNonPlainAccess + $1_ARGS += -KStatic + endif + + ifeq ($$($1_InputType), NonAtomicReference) + $1_ARGS += -KReference + $1_ARGS += -KStatic + $1_Type := Reference + endif + + ifeq ($$($1_InputType), FlatValue) + $1_ARGS += -KFlatValue + $1_ARGS += -KNonPlainAccess + endif + + ifeq ($$($1_InputType), NonAtomicFlatValue) + $1_ARGS += -KFlatValue + endif + $$($1_FILENAME): $(VARHANDLES_SRC_DIR)/X-VarHandle.java.template $(BUILD_TOOLS_JDK) - ifeq ($$($1_Type), Reference) + ifeq ($$($1_InputType), Reference) + $$(eval $1_type := Object) + $$(eval $1_Type := Reference) + else ifeq ($$($1_InputType), NonAtomicReference) + $$(eval $1_type := Object) + $$(eval $1_Type := Reference) + else ifeq ($$($1_InputType), FlatValue) + $$(eval $1_type := Object) + $$(eval $1_Type := FlatValue) + else ifeq ($$($1_InputType), NonAtomicFlatValue) $$(eval $1_type := Object) + $$(eval $1_Type := FlatValue) else - $$(eval $1_type := $$$$(shell $(TR) '[:upper:]' '[:lower:]' <<< $$$$($1_Type))) + $$(eval $1_type := $$$$(shell $(TR) '[:upper:]' '[:lower:]' <<< $$$$($1_InputType))) + $$(eval $1_Type := $$$$($1_InputType)) endif $$(call MakeDir, $$(@D)) $(RM) $$@ - $(TOOL_SPP) -nel -K$$($1_type) -Dtype=$$($1_type) -DType=$$($1_Type) \ + $(TOOL_SPP) -nel -K$$($1_type) -Dtype=$$($1_type) -DType=$$($1_Type) -DInputType=$$($1_InputType) \ $$($1_ARGS) -i$$< -o$$@ GENSRC_VARHANDLES += $$($1_FILENAME) @@ -284,7 +326,7 @@ endef ################################################################################ # List the types to generate source for, with capitalized first letter -VARHANDLES_TYPES := Boolean Byte Short Char Int Long Float Double Reference +VARHANDLES_TYPES := Boolean Byte Short Char Int Long Float Double Reference FlatValue NonAtomicReference NonAtomicFlatValue $(foreach t, $(VARHANDLES_TYPES), \ $(eval $(call GenerateVarHandle,VAR_HANDLE_$t,$t))) diff --git a/make/test/BuildMicrobenchmark.gmk b/make/test/BuildMicrobenchmark.gmk index 4946263ef4e..6c415ea10cb 100644 --- a/make/test/BuildMicrobenchmark.gmk +++ b/make/test/BuildMicrobenchmark.gmk @@ -84,7 +84,7 @@ $(eval $(call SetupJavaCompilation, BUILD_JDK_MICROBENCHMARK, \ CLASSPATH := $(JMH_COMPILE_JARS), \ CREATE_API_DIGEST := true, \ DISABLED_WARNINGS := restricted this-escape processing rawtypes removal cast \ - serial preview dangling-doc-comments, \ + serial preview unchecked deprecation dangling-doc-comments, \ SRC := $(MICROBENCHMARK_SRC), \ BIN := $(MICROBENCHMARK_CLASSES), \ JAVAC_FLAGS := \ @@ -95,7 +95,9 @@ $(eval $(call SetupJavaCompilation, BUILD_JDK_MICROBENCHMARK, \ --add-exports java.base/jdk.internal.jimage=ALL-UNNAMED \ --add-exports java.base/jdk.internal.misc=ALL-UNNAMED \ --add-exports java.base/jdk.internal.util=ALL-UNNAMED \ + --add-exports java.base/jdk.internal.value=ALL-UNNAMED \ --add-exports java.base/jdk.internal.vm=ALL-UNNAMED \ + --add-exports java.base/jdk.internal.vm.annotation=ALL-UNNAMED \ --add-exports java.base/sun.invoke.util=ALL-UNNAMED \ --add-exports java.base/sun.security.util=ALL-UNNAMED \ --add-exports java.base/sun.security.util.math=ALL-UNNAMED \ diff --git a/make/test/BuildTestLib.gmk b/make/test/BuildTestLib.gmk index dc5e0a9bd64..d3f3661a7c1 100644 --- a/make/test/BuildTestLib.gmk +++ b/make/test/BuildTestLib.gmk @@ -47,7 +47,6 @@ $(eval $(call SetupJavaCompilation, BUILD_WB_JAR, \ BIN := $(TEST_LIB_SUPPORT)/wb_classes, \ JAR := $(TEST_LIB_SUPPORT)/wb.jar, \ DISABLED_WARNINGS := deprecation removal preview, \ - JAVAC_FLAGS := --enable-preview, \ )) TARGETS += $(BUILD_WB_JAR) @@ -59,10 +58,11 @@ endif $(eval $(call SetupJavaCompilation, BUILD_TEST_LIB_JAR, \ TARGET_RELEASE := $(TARGET_RELEASE_NEWJDK_UPGRADED), \ SRC := $(TEST_LIB_SOURCE_DIR), \ - EXCLUDES := $(BUILD_TEST_LIB_JAR_EXCLUDES), \ + EXCLUDES := $(BUILD_TEST_LIB_JAR_EXCLUDES) org, \ BIN := $(TEST_LIB_SUPPORT)/test-lib_classes, \ HEADERS := $(TEST_LIB_SUPPORT)/test-lib_headers, \ JAR := $(TEST_LIB_SUPPORT)/test-lib.jar, \ + DISABLED_WARNINGS := try deprecation rawtypes unchecked serial cast removal preview restricted varargs dangling-doc-comments, \ JAVAC_FLAGS := --add-exports java.base/sun.security.util=ALL-UNNAMED \ --add-exports java.base/jdk.internal.classfile=ALL-UNNAMED \ --add-exports java.base/jdk.internal.classfile.attribute=ALL-UNNAMED \ @@ -73,6 +73,12 @@ $(eval $(call SetupJavaCompilation, BUILD_TEST_LIB_JAR, \ --add-exports java.base/sun.security.provider.certpath=ALL-UNNAMED \ --add-exports java.base/sun.security.tools.keytool=ALL-UNNAMED \ --add-exports java.base/sun.security.x509=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ + --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \ --enable-preview, \ )) diff --git a/src/README b/src/README new file mode 100644 index 00000000000..71ea93e97de --- /dev/null +++ b/src/README @@ -0,0 +1,51 @@ + +*** Project Valhalla "L-World Value Types" Early Access *** + +Prototype Version: LW2 + +This is an "early access" build, from "Project Valhalla" [1], aimed at testing a +"Value Types" implementation within the JVM, where-by value types may +interoperate with existing type descriptors. Otherwise known as "L-World Value +Types" (L-World) [2]. + +Intended for a number of expert users with active interest in the +Valhalla project, and is provided as a convenience to users needing to build +from source code (branch "lworld") [3]. + +Warning, not only is this build based on an incomplete version of "JDK 12", +there has been less than comprehensive testing carried out. + +* System Limitations + +- platforms: x64 Linux, x64 Mac OS X, Windows +- no support for atomic fields containing value types (work-in-progress for an + update) +- no AOT, CDS, redefineclasses +- -Xint and C2 only, no C1, no tiered-compilation +- interpreter is not optimized, focus is on JIT optimization + +* EA disclaimer + +- EA release functionality might not be released in a GA binary. Any EA + functionality might be changed or removed +- EA builds do not imply that the feature is being considered to any scheduled + OpenJDK release +- The list of supported platforms at GA might be different than the platforms + supported during EA +- These binaries have not been tested to the same level to which Oracle would + test a GA binary. It has been produced for gathering feedback. Use for any + other purpose is at your own risk +- These binaries might be missing security vulnerabilities fixes available in GA + binaries or in other OpenJDK projects +- Oracle does not provide support for these binaries + +* Filing Bugs + +Send email to valhalla-dev@openjdk.java.net + +Current known issues [4] + +[1] https://wiki.openjdk.java.net/display/valhalla/Main +[2] https://wiki.openjdk.java.net/display/valhalla/L-World+Value+Types +[3] http://hg.openjdk.java.net/valhalla/valhalla/ +[4] https://bugs.openjdk.java.net/issues/?filter=35615 diff --git a/src/hotspot/cpu/aarch64/aarch64.ad b/src/hotspot/cpu/aarch64/aarch64.ad index 33466453b76..cf5cd6294c1 100644 --- a/src/hotspot/cpu/aarch64/aarch64.ad +++ b/src/hotspot/cpu/aarch64/aarch64.ad @@ -1694,6 +1694,9 @@ int MachCallRuntimeNode::ret_addr_offset() { CodeBlob *cb = CodeCache::find_blob(_entry_point); if (cb) { return 1 * NativeInstruction::instruction_size; + } else if (_entry_point == nullptr) { + // See CallLeafNoFPIndirect + return 1 * NativeInstruction::instruction_size; } else { return 6 * NativeInstruction::instruction_size; } @@ -1802,49 +1805,15 @@ void MachPrologNode::format(PhaseRegAlloc *ra_, outputStream *st) const { void MachPrologNode::emit(C2_MacroAssembler *masm, PhaseRegAlloc *ra_) const { Compile* C = ra_->C; - // n.b. frame size includes space for return pc and rfp - const int framesize = C->output()->frame_size_in_bytes(); - - if (C->clinit_barrier_on_entry()) { - assert(!C->method()->holder()->is_not_initialized(), "initialization should have been started"); - - Label L_skip_barrier; - __ mov_metadata(rscratch2, C->method()->holder()->constant_encoding()); - __ clinit_barrier(rscratch2, rscratch1, &L_skip_barrier); - __ far_jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); - __ bind(L_skip_barrier); - } + __ verified_entry(C, 0); - if (C->max_vector_size() > 0) { - __ reinitialize_ptrue(); + if (C->stub_function() == nullptr) { + __ entry_barrier(); } - int bangsize = C->output()->bang_size_in_bytes(); - if (C->output()->need_stack_bang(bangsize)) - __ generate_stack_overflow_check(bangsize); - - __ build_frame(framesize); - - if (C->stub_function() == nullptr) { - BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); - // Dummy labels for just measuring the code size - Label dummy_slow_path; - Label dummy_continuation; - Label dummy_guard; - Label* slow_path = &dummy_slow_path; - Label* continuation = &dummy_continuation; - Label* guard = &dummy_guard; - if (!Compile::current()->output()->in_scratch_emit_size()) { - // Use real labels from actual stub when not emitting code for the purpose of measuring its size - C2EntryBarrierStub* stub = new (Compile::current()->comp_arena()) C2EntryBarrierStub(); - Compile::current()->output()->add_stub(stub); - slow_path = &stub->entry(); - continuation = &stub->continuation(); - guard = &stub->guard(); - } - // In the C2 code, we move the non-hot part of nmethod entry barriers out-of-line to a stub. - bs->nmethod_entry_barrier(masm, slow_path, continuation, guard); + if (!Compile::current()->output()->in_scratch_emit_size()) { + __ bind(*_verified_entry); } if (VerifyStackAtCalls) { @@ -1861,12 +1830,6 @@ void MachPrologNode::emit(C2_MacroAssembler *masm, PhaseRegAlloc *ra_) const { } } -uint MachPrologNode::size(PhaseRegAlloc* ra_) const -{ - return MachNode::size(ra_); // too many variables; just compute it - // the hard way -} - int MachPrologNode::reloc() const { return 0; @@ -1909,7 +1872,7 @@ void MachEpilogNode::emit(C2_MacroAssembler *masm, PhaseRegAlloc *ra_) const { Compile* C = ra_->C; int framesize = C->output()->frame_slots() << LogBytesPerInt; - __ remove_frame(framesize); + __ remove_frame(framesize, C->needs_stack_repair()); if (StackReservedPages > 0 && C->has_reserved_stack_access()) { __ reserved_stack_check(); @@ -1928,11 +1891,6 @@ void MachEpilogNode::emit(C2_MacroAssembler *masm, PhaseRegAlloc *ra_) const { } } -uint MachEpilogNode::size(PhaseRegAlloc *ra_) const { - // Variable size. Determine dynamically. - return MachNode::size(ra_); -} - int MachEpilogNode::reloc() const { // Return number of relocatable values contained in this instruction. return 1; // 1 for polling page. @@ -2228,8 +2186,46 @@ uint BoxLockNode::size(PhaseRegAlloc *ra_) const { } } -//============================================================================= +///============================================================================= +#ifndef PRODUCT +void MachVEPNode::format(PhaseRegAlloc* ra_, outputStream* st) const +{ + st->print_cr("# MachVEPNode"); + if (!_verified) { + st->print_cr("\t load_class"); + } else { + st->print_cr("\t unpack_inline_arg"); + } +} +#endif +void MachVEPNode::emit(C2_MacroAssembler *masm, PhaseRegAlloc* ra_) const +{ + if (!_verified) { + __ ic_check(1); + } else { + // TODO 8284443 Avoid creation of temporary frame + if (ra_->C->stub_function() == nullptr) { + __ verified_entry(ra_->C, 0); + __ entry_barrier(); + int framesize = ra_->C->output()->frame_slots() << LogBytesPerInt; + __ remove_frame(framesize, false); + } + // Unpack inline type args passed as oop and then jump to + // the verified entry point (skipping the unverified entry). + int sp_inc = __ unpack_inline_args(ra_->C, _receiver_only); + // Emit code for verified entry and save increment for stack repair on return + __ verified_entry(ra_->C, sp_inc); + if (Compile::current()->output()->in_scratch_emit_size()) { + Label dummy_verified_entry; + __ b(dummy_verified_entry); + } else { + __ b(*_verified_entry); + } + } +} + +//============================================================================= #ifndef PRODUCT void MachUEPNode::format(PhaseRegAlloc* ra_, outputStream* st) const { @@ -2252,11 +2248,6 @@ void MachUEPNode::emit(C2_MacroAssembler* masm, PhaseRegAlloc* ra_) const __ ic_check(InteriorEntryAlignment); } -uint MachUEPNode::size(PhaseRegAlloc* ra_) const -{ - return MachNode::size(ra_); -} - // REQUIRED EMIT CODE //============================================================================= @@ -3762,6 +3753,37 @@ encode %{ // Check that stack depth is unchanged: find majik cookie on stack __ call_Unimplemented(); } + if (tf()->returns_inline_type_as_fields() && !_method->is_method_handle_intrinsic() && _method->return_type()->is_loaded()) { + // The last return value is not set by the callee but used to pass the null marker to compiled code. + // Search for the corresponding projection, get the register and emit code that initialized it. + uint con = (tf()->range_cc()->cnt() - 1); + for (DUIterator_Fast imax, i = fast_outs(imax); i < imax; i++) { + ProjNode* proj = fast_out(i)->as_Proj(); + if (proj->_con == con) { + // Set null marker if r0 is non-null (a non-null value is returned buffered or scalarized) + OptoReg::Name optoReg = ra_->get_reg_first(proj); + VMReg reg = OptoReg::as_VMReg(optoReg, ra_->_framesize, OptoReg::reg2stack(ra_->_matcher._new_SP)); + Register toReg = reg->is_reg() ? reg->as_Register() : rscratch1; + __ cmp(r0, zr); + __ cset(toReg, Assembler::NE); + if (reg->is_stack()) { + int st_off = reg->reg2stack() * VMRegImpl::stack_slot_size; + __ str(toReg, Address(sp, st_off)); + } + break; + } + } + if (return_value_is_used()) { + // An inline type is returned as fields in multiple registers. + // R0 either contains an oop if the inline type is buffered or a pointer + // to the corresponding InlineKlass with the lowest bit set to 1. Zero r0 + // if the lowest bit is set to allow C2 to use the oop after null checking. + // r0 &= (r0 & 1) - 1 + __ andr(rscratch1, r0, 0x1); + __ sub(rscratch1, rscratch1, 0x1); + __ andr(r0, r0, rscratch1); + } + } %} enc_class aarch64_enc_java_to_runtime(method meth) %{ @@ -6946,7 +6968,7 @@ instruct loadConP(iRegPNoSp dst, immP con) ins_cost(INSN_COST * 4); format %{ - "mov $dst, $con\t# ptr\n\t" + "mov $dst, $con\t# ptr" %} ins_encode(aarch64_enc_mov_p(dst, con)); @@ -8139,6 +8161,36 @@ instruct castX2P(iRegPNoSp dst, iRegL src) %{ ins_pipe(ialu_reg); %} +instruct castI2N(iRegNNoSp dst, iRegI src) %{ + match(Set dst (CastI2N src)); + + ins_cost(INSN_COST); + format %{ "mov $dst, $src\t# int -> narrow ptr" %} + + ins_encode %{ + if ($dst$$reg != $src$$reg) { + __ mov(as_Register($dst$$reg), as_Register($src$$reg)); + } + %} + + ins_pipe(ialu_reg); +%} + +instruct castN2X(iRegLNoSp dst, iRegN src) %{ + match(Set dst (CastP2X src)); + + ins_cost(INSN_COST); + format %{ "mov $dst, $src\t# ptr -> long" %} + + ins_encode %{ + if ($dst$$reg != $src$$reg) { + __ mov(as_Register($dst$$reg), as_Register($src$$reg)); + } + %} + + ins_pipe(ialu_reg); +%} + instruct castP2X(iRegLNoSp dst, iRegP src) %{ match(Set dst (CastP2X src)); @@ -15092,9 +15144,9 @@ instruct MoveL2D_reg_reg(vRegD dst, iRegL src) %{ // ============================================================================ // clearing of an array -instruct clearArray_reg_reg(iRegL_R11 cnt, iRegP_R10 base, Universe dummy, rFlagsReg cr) +instruct clearArray_reg_reg_immL0(iRegL_R11 cnt, iRegP_R10 base, immL0 zero, Universe dummy, rFlagsReg cr) %{ - match(Set dummy (ClearArray cnt base)); + match(Set dummy (ClearArray (Binary cnt base) zero)); effect(USE_KILL cnt, USE_KILL base, KILL cr); ins_cost(4 * INSN_COST); @@ -15111,10 +15163,27 @@ instruct clearArray_reg_reg(iRegL_R11 cnt, iRegP_R10 base, Universe dummy, rFlag ins_pipe(pipe_class_memory); %} +instruct clearArray_reg_reg(iRegL_R11 cnt, iRegP_R10 base, iRegL val, Universe dummy, rFlagsReg cr) +%{ + predicate(((ClearArrayNode*)n)->word_copy_only()); + match(Set dummy (ClearArray (Binary cnt base) val)); + effect(USE_KILL cnt, USE_KILL base, KILL cr); + + ins_cost(4 * INSN_COST); + format %{ "ClearArray $cnt, $base, $val" %} + + ins_encode %{ + __ fill_words($base$$Register, $cnt$$Register, $val$$Register); + %} + + ins_pipe(pipe_class_memory); +%} + instruct clearArray_imm_reg(immL cnt, iRegP_R10 base, iRegL_R11 temp, Universe dummy, rFlagsReg cr) %{ predicate((uint64_t)n->in(2)->get_long() - < (uint64_t)(BlockZeroingLowLimit >> LogBytesPerWord)); + < (uint64_t)(BlockZeroingLowLimit >> LogBytesPerWord) + && !((ClearArrayNode*)n)->word_copy_only()); match(Set dummy (ClearArray cnt base)); effect(TEMP temp, USE_KILL base, KILL cr); @@ -16431,8 +16500,28 @@ instruct CallLeafDirectVector(method meth) // Call Runtime Instruction +// entry point is null, target holds the address to call +instruct CallLeafNoFPIndirect(iRegP target) +%{ + predicate(n->as_Call()->entry_point() == nullptr); + + match(CallLeafNoFP target); + + ins_cost(CALL_COST); + + format %{ "CALL, runtime leaf nofp indirect $target" %} + + ins_encode %{ + __ blr($target$$Register); + %} + + ins_pipe(pipe_class_call); +%} + instruct CallLeafNoFPDirect(method meth) %{ + predicate(n->as_Call()->entry_point() != nullptr); + match(CallLeafNoFP); effect(USE meth); diff --git a/src/hotspot/cpu/aarch64/c1_CodeStubs_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_CodeStubs_aarch64.cpp index 954e4abee14..d9b17261de0 100644 --- a/src/hotspot/cpu/aarch64/c1_CodeStubs_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_CodeStubs_aarch64.cpp @@ -119,6 +119,72 @@ void DivByZeroStub::emit_code(LIR_Assembler* ce) { #endif } +// Implementation of LoadFlattenedArrayStub + +LoadFlattenedArrayStub::LoadFlattenedArrayStub(LIR_Opr array, LIR_Opr index, LIR_Opr result, CodeEmitInfo* info) { + _array = array; + _index = index; + _result = result; + _scratch_reg = FrameMap::r0_oop_opr; + _info = new CodeEmitInfo(info); +} + +void LoadFlattenedArrayStub::emit_code(LIR_Assembler* ce) { + assert(__ rsp_offset() == 0, "frame size should be fixed"); + __ bind(_entry); + ce->store_parameter(_array->as_register(), 1); + ce->store_parameter(_index->as_register(), 0); + __ far_call(RuntimeAddress(Runtime1::entry_for(StubId::c1_load_flat_array_id))); + ce->add_call_info_here(_info); + ce->verify_oop_map(_info); + if (_result->as_register() != r0) { + __ mov(_result->as_register(), r0); + } + __ b(_continuation); +} + + +// Implementation of StoreFlattenedArrayStub + +StoreFlattenedArrayStub::StoreFlattenedArrayStub(LIR_Opr array, LIR_Opr index, LIR_Opr value, CodeEmitInfo* info) { + _array = array; + _index = index; + _value = value; + _scratch_reg = FrameMap::r0_oop_opr; + _info = new CodeEmitInfo(info); +} + + +void StoreFlattenedArrayStub::emit_code(LIR_Assembler* ce) { + assert(__ rsp_offset() == 0, "frame size should be fixed"); + __ bind(_entry); + ce->store_parameter(_array->as_register(), 2); + ce->store_parameter(_index->as_register(), 1); + ce->store_parameter(_value->as_register(), 0); + __ far_call(RuntimeAddress(Runtime1::entry_for(StubId::c1_store_flat_array_id))); + ce->add_call_info_here(_info); + ce->verify_oop_map(_info); + __ b(_continuation); +} + +// Implementation of SubstitutabilityCheckStub +SubstitutabilityCheckStub::SubstitutabilityCheckStub(LIR_Opr left, LIR_Opr right, CodeEmitInfo* info) { + _left = left; + _right = right; + _scratch_reg = FrameMap::r0_oop_opr; + _info = new CodeEmitInfo(info); +} + +void SubstitutabilityCheckStub::emit_code(LIR_Assembler* ce) { + assert(__ rsp_offset() == 0, "frame size should be fixed"); + __ bind(_entry); + ce->store_parameter(_left->as_register(), 1); + ce->store_parameter(_right->as_register(), 0); + __ far_call(RuntimeAddress(Runtime1::entry_for(StubId::c1_substitutability_check_id))); + ce->add_call_info_here(_info); + ce->verify_oop_map(_info); + __ b(_continuation); +} // Implementation of NewInstanceStub @@ -135,8 +201,6 @@ NewInstanceStub::NewInstanceStub(LIR_Opr klass_reg, LIR_Opr result, ciInstanceKl _stub_id = stub_id; } - - void NewInstanceStub::emit_code(LIR_Assembler* ce) { assert(__ rsp_offset() == 0, "frame size should be fixed"); __ bind(_entry); @@ -176,11 +240,13 @@ void NewTypeArrayStub::emit_code(LIR_Assembler* ce) { // Implementation of NewObjectArrayStub -NewObjectArrayStub::NewObjectArrayStub(LIR_Opr klass_reg, LIR_Opr length, LIR_Opr result, CodeEmitInfo* info) { +NewObjectArrayStub::NewObjectArrayStub(LIR_Opr klass_reg, LIR_Opr length, LIR_Opr result, + CodeEmitInfo* info, bool is_null_free) { _klass_reg = klass_reg; _result = result; _length = length; _info = new CodeEmitInfo(info); + _is_null_free = is_null_free; } @@ -189,7 +255,13 @@ void NewObjectArrayStub::emit_code(LIR_Assembler* ce) { __ bind(_entry); assert(_length->as_register() == r19, "length must in r19,"); assert(_klass_reg->as_register() == r3, "klass_reg must in r3"); - __ far_call(RuntimeAddress(Runtime1::entry_for(StubId::c1_new_object_array_id))); + + if (_is_null_free) { + __ far_call(RuntimeAddress(Runtime1::entry_for(StubId::c1_new_null_free_array_id))); + } else { + __ far_call(RuntimeAddress(Runtime1::entry_for(StubId::c1_new_object_array_id))); + } + ce->add_call_info_here(_info); ce->verify_oop_map(_info); assert(_result->as_register() == r0, "result must in r0"); @@ -199,6 +271,16 @@ void NewObjectArrayStub::emit_code(LIR_Assembler* ce) { void MonitorEnterStub::emit_code(LIR_Assembler* ce) { assert(__ rsp_offset() == 0, "frame size should be fixed"); __ bind(_entry); + if (_throw_ie_stub != nullptr) { + // When we come here, _obj_reg has already been checked to be non-null. + __ ldr(rscratch1, Address(_obj_reg->as_register(), oopDesc::mark_offset_in_bytes())); + __ mov(rscratch2, markWord::inline_type_pattern); + __ andr(rscratch1, rscratch1, rscratch2); + + __ cmp(rscratch1, rscratch2); + __ br(Assembler::EQ, *_throw_ie_stub->entry()); + } + ce->store_parameter(_obj_reg->as_register(), 1); ce->store_parameter(_lock_reg->as_register(), 0); StubId enter_id; diff --git a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp index e9bb2350b5b..7dca3bcc45c 100644 --- a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp @@ -32,12 +32,14 @@ #include "c1/c1_Runtime1.hpp" #include "c1/c1_ValueStack.hpp" #include "ci/ciArrayKlass.hpp" +#include "ci/ciInlineKlass.hpp" #include "ci/ciInstance.hpp" #include "code/compiledIC.hpp" #include "gc/shared/collectedHeap.hpp" #include "gc/shared/gc_globals.hpp" #include "nativeInst_aarch64.hpp" #include "oops/objArrayKlass.hpp" +#include "oops/oop.inline.hpp" #include "runtime/frame.inline.hpp" #include "runtime/sharedRuntime.hpp" #include "runtime/stubRoutines.hpp" @@ -426,7 +428,7 @@ int LIR_Assembler::emit_unwind_handler() { // remove the activation and dispatch to the unwind handler __ block_comment("remove_frame and dispatch to the unwind handler"); - __ remove_frame(initial_frame_size_in_bytes()); + __ remove_frame(initial_frame_size_in_bytes(), needs_stack_repair()); __ far_jump(RuntimeAddress(Runtime1::entry_for(StubId::c1_unwind_exception_id))); // Emit the slow path assembly @@ -470,8 +472,50 @@ void LIR_Assembler::add_debug_info_for_branch(address adr, CodeEmitInfo* info) { void LIR_Assembler::return_op(LIR_Opr result, C1SafepointPollStub* code_stub) { assert(result->is_illegal() || !result->is_single_cpu() || result->as_register() == r0, "word returns are in r0,"); + if (InlineTypeReturnedAsFields) { + // Check if we are returning an non-null inline type and load its fields into registers + ciType* return_type = compilation()->method()->return_type(); + if (return_type->is_inlinetype()) { + ciInlineKlass* vk = return_type->as_inline_klass(); + if (vk->can_be_returned_as_fields()) { + address unpack_handler = vk->unpack_handler(); + assert(unpack_handler != nullptr, "must be"); + __ far_call(RuntimeAddress(unpack_handler)); + } + } else if (return_type->is_instance_klass() && (!return_type->is_loaded() || StressCallingConvention)) { + Label skip; + Label not_null; + __ cbnz(r0, not_null); + // Returned value is null, zero all return registers because they may belong to oop fields + __ mov(j_rarg1, zr); + __ mov(j_rarg2, zr); + __ mov(j_rarg3, zr); + __ mov(j_rarg4, zr); + __ mov(j_rarg5, zr); + __ mov(j_rarg6, zr); + __ mov(j_rarg7, zr); + __ b(skip); + __ bind(not_null); + + // Check if we are returning an non-null inline type and load its fields into registers + __ test_oop_is_not_inline_type(r0, rscratch2, skip, /* can_be_null= */ false); + + // Load fields from a buffered value with an inline class specific handler + __ load_klass(rscratch1 /*dst*/, r0 /*src*/); + __ ldr(rscratch1, Address(rscratch1, InstanceKlass::adr_inlineklass_fixed_block_offset())); + __ ldr(rscratch1, Address(rscratch1, InlineKlass::unpack_handler_offset())); + // Unpack handler can be null if inline type is not scalarizable in returns + __ cbz(rscratch1, skip); + __ blr(rscratch1); + + __ bind(skip); + } + // At this point, r0 points to the value object (for interpreter or C1 caller). + // The fields of the object are copied into registers (for C2 caller). + } + // Pop the stack before the safepoint code - __ remove_frame(initial_frame_size_in_bytes()); + __ remove_frame(initial_frame_size_in_bytes(), needs_stack_repair()); if (StackReservedPages > 0 && compilation()->has_reserved_stack_access()) { __ reserved_stack_check(); @@ -483,6 +527,10 @@ void LIR_Assembler::return_op(LIR_Opr result, C1SafepointPollStub* code_stub) { __ ret(lr); } +int LIR_Assembler::store_inline_type_fields_to_buf(ciInlineKlass* vk) { + return (__ store_inline_type_fields_to_buf(vk, false)); +} + int LIR_Assembler::safepoint_poll(LIR_Opr tmp, CodeEmitInfo* info) { guarantee(info != nullptr, "Shouldn't be null"); __ get_polling_page(rscratch1, relocInfo::poll_type); @@ -529,10 +577,10 @@ void LIR_Assembler::const2reg(LIR_Opr src, LIR_Opr dest, LIR_PatchCode patch_cod } case T_OBJECT: { - if (patch_code == lir_patch_none) { - jobject2reg(c->as_jobject(), dest->as_register()); - } else { + if (patch_code != lir_patch_none) { jobject2reg_with_patching(dest->as_register(), info); + } else { + jobject2reg(c->as_jobject(), dest->as_register()); } break; } @@ -642,6 +690,8 @@ void LIR_Assembler::const2mem(LIR_Opr src, LIR_Opr dest, BasicType type, CodeEmi break; case T_OBJECT: case T_ARRAY: + // Non-null case is not handled on aarch64 but handled on x86 + // FIXME: do we need to add it here? assert(c->as_jobject() == nullptr, "should be"); if (UseCompressedOops && !wide) { insn = &Assembler::strw; @@ -989,6 +1039,20 @@ void LIR_Assembler::mem2reg(LIR_Opr src, LIR_Opr dest, BasicType type, LIR_Patch } } +void LIR_Assembler::move(LIR_Opr src, LIR_Opr dst) { + assert(dst->is_cpu_register(), "must be"); + assert(dst->type() == src->type(), "must be"); + + if (src->is_cpu_register()) { + reg2reg(src, dst); + } else if (src->is_stack()) { + stack2reg(src, dst, dst->type()); + } else if (src->is_constant()) { + const2reg(src, dst, lir_patch_none, nullptr); + } else { + ShouldNotReachHere(); + } +} int LIR_Assembler::array_element_size(BasicType type) const { int elem_size = type2aelembytes(type); @@ -1180,7 +1244,7 @@ void LIR_Assembler::emit_alloc_array(LIR_OpAllocArray* op) { Register len = op->len()->as_register(); __ uxtw(len, len); - if (UseSlowPath || + if (UseSlowPath || op->always_slow_path() || (!UseFastNewObjectArray && is_reference_type(op->type())) || (!UseFastNewTypeArray && !is_reference_type(op->type()))) { __ b(*op->stub()->entry()); @@ -1292,32 +1356,34 @@ void LIR_Assembler::emit_typecheck_helper(LIR_OpTypeCheck *op, Label* success, L assert_different_registers(obj, k_RInfo, klass_RInfo); - if (should_profile) { - Register mdo = klass_RInfo; - __ mov_metadata(mdo, md->constant_encoding()); - Label not_null; - __ cbnz(obj, not_null); - // Object is null; update MDO and exit - Address data_addr - = __ form_address(rscratch2, mdo, - md->byte_offset_of_slot(data, DataLayout::flags_offset()), - 0); - __ ldrb(rscratch1, data_addr); - __ orr(rscratch1, rscratch1, BitData::null_seen_byte_constant()); - __ strb(rscratch1, data_addr); - __ b(*obj_is_null); - __ bind(not_null); - - Label update_done; - Register recv = k_RInfo; - __ load_klass(recv, obj); - type_profile_helper(mdo, md, data, recv, &update_done); - Address counter_addr(mdo, md->byte_offset_of_slot(data, CounterData::count_offset())); - __ addptr(counter_addr, DataLayout::counter_increment); + if (op->need_null_check()) { + if (should_profile) { + Register mdo = klass_RInfo; + __ mov_metadata(mdo, md->constant_encoding()); + Label not_null; + __ cbnz(obj, not_null); + // Object is null; update MDO and exit + Address data_addr + = __ form_address(rscratch2, mdo, + md->byte_offset_of_slot(data, DataLayout::flags_offset()), + 0); + __ ldrb(rscratch1, data_addr); + __ orr(rscratch1, rscratch1, BitData::null_seen_byte_constant()); + __ strb(rscratch1, data_addr); + __ b(*obj_is_null); + __ bind(not_null); - __ bind(update_done); - } else { - __ cbz(obj, *obj_is_null); + Label update_done; + Register recv = k_RInfo; + __ load_klass(recv, obj); + type_profile_helper(mdo, md, data, recv, &update_done); + Address counter_addr(mdo, md->byte_offset_of_slot(data, CounterData::count_offset())); + __ addptr(counter_addr, DataLayout::counter_increment); + + __ bind(update_done); + } else { + __ cbz(obj, *obj_is_null); + } } if (!k->is_loaded()) { @@ -1475,6 +1541,112 @@ void LIR_Assembler::emit_opTypeCheck(LIR_OpTypeCheck* op) { } } +void LIR_Assembler::emit_opFlattenedArrayCheck(LIR_OpFlattenedArrayCheck* op) { + // We are loading/storing from/to an array that *may* be a flat array (the + // declared type is Object[], abstract[], interface[] or VT.ref[]). + // If this array is a flat array, take the slow path. + __ test_flat_array_oop(op->array()->as_register(), op->tmp()->as_register(), *op->stub()->entry()); + if (!op->value()->is_illegal()) { + // The array is not a flat array, but it might be null-free. If we are storing + // a null into a null-free array, take the slow path (which will throw NPE). + Label skip; + __ cbnz(op->value()->as_register(), skip); + __ test_null_free_array_oop(op->array()->as_register(), op->tmp()->as_register(), *op->stub()->entry()); + __ bind(skip); + } +} + +void LIR_Assembler::emit_opNullFreeArrayCheck(LIR_OpNullFreeArrayCheck* op) { + // We are storing into an array that *may* be null-free (the declared type is + // Object[], abstract[], interface[] or VT.ref[]). + Label test_mark_word; + Register tmp = op->tmp()->as_register(); + __ ldr(tmp, Address(op->array()->as_register(), oopDesc::mark_offset_in_bytes())); + __ tst(tmp, markWord::unlocked_value); + __ br(Assembler::NE, test_mark_word); + __ load_prototype_header(tmp, op->array()->as_register()); + __ bind(test_mark_word); + __ tst(tmp, markWord::null_free_array_bit_in_place); +} + +void LIR_Assembler::emit_opSubstitutabilityCheck(LIR_OpSubstitutabilityCheck* op) { + Label L_oops_equal; + Label L_oops_not_equal; + Label L_end; + + Register left = op->left()->as_register(); + Register right = op->right()->as_register(); + + __ cmp(left, right); + __ br(Assembler::EQ, L_oops_equal); + + // (1) Null check -- if one of the operands is null, the other must not be null (because + // the two references are not equal), so they are not substitutable, + // FIXME: do null check only if the operand is nullable + { + __ cbz(left, L_oops_not_equal); + __ cbz(right, L_oops_not_equal); + } + + ciKlass* left_klass = op->left_klass(); + ciKlass* right_klass = op->right_klass(); + + // (2) Inline type check -- if either of the operands is not a inline type, + // they are not substitutable. We do this only if we are not sure that the + // operands are inline type + if ((left_klass == nullptr || right_klass == nullptr) ||// The klass is still unloaded, or came from a Phi node. + !left_klass->is_inlinetype() || !right_klass->is_inlinetype()) { + Register tmp1 = op->tmp1()->as_register(); + __ mov(tmp1, markWord::inline_type_pattern); + __ ldr(rscratch1, Address(left, oopDesc::mark_offset_in_bytes())); + __ andr(tmp1, tmp1, rscratch1); + __ ldr(rscratch1, Address(right, oopDesc::mark_offset_in_bytes())); + __ andr(tmp1, tmp1, rscratch1); + __ cmp(tmp1, (u1)markWord::inline_type_pattern); + __ br(Assembler::NE, L_oops_not_equal); + } + + // (3) Same klass check: if the operands are of different klasses, they are not substitutable. + if (left_klass != nullptr && left_klass->is_inlinetype() && left_klass == right_klass) { + // No need to load klass -- the operands are statically known to be the same inline klass. + __ b(*op->stub()->entry()); + } else { + Register left_klass_op = op->left_klass_op()->as_register(); + Register right_klass_op = op->right_klass_op()->as_register(); + + if (UseCompressedClassPointers) { + __ ldrw(left_klass_op, Address(left, oopDesc::klass_offset_in_bytes())); + __ ldrw(right_klass_op, Address(right, oopDesc::klass_offset_in_bytes())); + __ cmpw(left_klass_op, right_klass_op); + } else { + __ ldr(left_klass_op, Address(left, oopDesc::klass_offset_in_bytes())); + __ ldr(right_klass_op, Address(right, oopDesc::klass_offset_in_bytes())); + __ cmp(left_klass_op, right_klass_op); + } + + __ br(Assembler::EQ, *op->stub()->entry()); // same klass -> do slow check + // fall through to L_oops_not_equal + } + + __ bind(L_oops_not_equal); + move(op->not_equal_result(), op->result_opr()); + __ b(L_end); + + __ bind(L_oops_equal); + move(op->equal_result(), op->result_opr()); + __ b(L_end); + + // We've returned from the stub. R0 contains 0x0 IFF the two + // operands are not substitutable. (Don't compare against 0x1 in case the + // C compiler is naughty) + __ bind(*op->stub()->continuation()); + __ cbz(r0, L_oops_not_equal); // (call_stub() == 0x0) -> not_equal + move(op->equal_result(), op->result_opr()); // (call_stub() != 0x0) -> equal + // fall-through + __ bind(L_end); +} + + void LIR_Assembler::casw(Register addr, Register newval, Register cmpval) { __ cmpxchg(addr, cmpval, newval, Assembler::word, /* acquire*/ true, /* release*/ true, /* weak*/ false, rscratch1); __ cset(rscratch1, Assembler::NE); @@ -1988,7 +2160,7 @@ void LIR_Assembler::call(LIR_OpJavaCall* op, relocInfo::relocType rtype) { bailout("trampoline stub overflow"); return; } - add_call_info(code_offset(), op->info()); + add_call_info(code_offset(), op->info(), op->maybe_return_as_fields()); __ post_call_nop(); } @@ -1999,7 +2171,7 @@ void LIR_Assembler::ic_call(LIR_OpJavaCall* op) { bailout("trampoline stub overflow"); return; } - add_call_info(code_offset(), op->info()); + add_call_info(code_offset(), op->info(), op->maybe_return_as_fields()); __ post_call_nop(); } @@ -2162,6 +2334,18 @@ void LIR_Assembler::store_parameter(jobject o, int offset_from_rsp_in_words) { __ str(rscratch1, Address(sp, offset_from_rsp_in_bytes)); } +void LIR_Assembler::arraycopy_inlinetype_check(Register obj, Register tmp, CodeStub* slow_path, bool is_dest, bool null_check) { + if (null_check) { + __ cbz(obj, *slow_path->entry()); + } + if (is_dest) { + __ test_null_free_array_oop(obj, tmp, *slow_path->entry()); + // TODO 8350865 Flat no longer implies null-free, so we need to check for flat dest. Can we do better here? + __ test_flat_array_oop(obj, tmp, *slow_path->entry()); + } else { + __ test_flat_array_oop(obj, tmp, *slow_path->entry()); + } +} // This code replaces a call to arraycopy; no exception may // be thrown in this code, they must be thrown in the System.arraycopy @@ -2180,6 +2364,12 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { BasicType basic_type = default_type != nullptr ? default_type->element_type()->basic_type() : T_ILLEGAL; if (is_reference_type(basic_type)) basic_type = T_OBJECT; + if (flags & LIR_OpArrayCopy::always_slow_path) { + __ b(*stub->entry()); + __ bind(*stub->continuation()); + return; + } + // if we don't know anything, just go through the generic arraycopy if (default_type == nullptr // || basic_type == T_OBJECT ) { @@ -2233,6 +2423,14 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { return; } + // Handle inline type arrays + if (flags & LIR_OpArrayCopy::src_inlinetype_check) { + arraycopy_inlinetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check)); + } + if (flags & LIR_OpArrayCopy::dst_inlinetype_check) { + arraycopy_inlinetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check)); + } + assert(default_type != nullptr && default_type->is_array_klass() && default_type->is_loaded(), "must be true at this point"); int elem_size = type2aelembytes(basic_type); @@ -2781,6 +2979,26 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) { COMMENT("} emit_profile_type"); } +void LIR_Assembler::emit_profile_inline_type(LIR_OpProfileInlineType* op) { + Register obj = op->obj()->as_register(); + Register tmp = op->tmp()->as_pointer_register(); + bool not_null = op->not_null(); + int flag = op->flag(); + + Label not_inline_type; + if (!not_null) { + __ cbz(obj, not_inline_type); + } + + __ test_oop_is_not_inline_type(obj, tmp, not_inline_type); + + Address mdo_addr = as_Address(op->mdp()->as_address_ptr(), rscratch2); + __ ldrb(rscratch1, mdo_addr); + __ orr(rscratch1, rscratch1, flag); + __ strb(rscratch1, mdo_addr); + + __ bind(not_inline_type); +} void LIR_Assembler::align_backward_branch_target() { } @@ -2920,6 +3138,10 @@ void LIR_Assembler::get_thread(LIR_Opr result_reg) { __ mov(result_reg->as_register(), rthread); } +void LIR_Assembler::check_orig_pc() { + __ ldr(rscratch2, frame_map()->address_for_orig_pc_addr()); + __ cmp(rscratch2, (u1)NULL_WORD); +} void LIR_Assembler::peephole(LIR_List *lir) { #if 0 diff --git a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.hpp index 12b941fc4f7..a7172763dca 100644 --- a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.hpp @@ -74,6 +74,9 @@ friend class ArrayCopyStub; _deopt_handler_size = 7 * NativeInstruction::instruction_size }; + void arraycopy_inlinetype_check(Register obj, Register tmp, CodeStub* slow_path, bool is_dest, bool null_check); + void move(LIR_Opr src, LIR_Opr dst); + public: void store_parameter(Register r, int offset_from_esp_in_words); diff --git a/src/hotspot/cpu/aarch64/c1_LIRGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_LIRGenerator_aarch64.cpp index ad26d494b2d..bc69a8dcddb 100644 --- a/src/hotspot/cpu/aarch64/c1_LIRGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_LIRGenerator_aarch64.cpp @@ -32,6 +32,7 @@ #include "c1/c1_Runtime1.hpp" #include "c1/c1_ValueStack.hpp" #include "ci/ciArray.hpp" +#include "ci/ciInlineKlass.hpp" #include "ci/ciObjArrayKlass.hpp" #include "ci/ciTypeArrayKlass.hpp" #include "compiler/compilerDefinitions.inline.hpp" @@ -102,6 +103,12 @@ LIR_Opr LIRGenerator::rlock_byte(BasicType type) { } +void LIRGenerator::init_temps_for_substitutability_check(LIR_Opr& tmp1, LIR_Opr& tmp2) { + tmp1 = new_register(T_INT); + tmp2 = LIR_OprFact::illegalOpr; +} + + //--------- loading items into registers -------------------------------- @@ -323,11 +330,17 @@ void LIRGenerator::do_MonitorEnter(MonitorEnter* x) { if (x->needs_null_check()) { info_for_exception = state_for(x); } + + CodeStub* throw_ie_stub = + x->maybe_inlinetype() ? + new SimpleExceptionStub(StubId::c1_throw_identity_exception_id, obj.result(), state_for(x)) : + nullptr; + // this CodeEmitInfo must not have the xhandlers because here the // object is already locked (xhandlers expect object to be unlocked) CodeEmitInfo* info = state_for(x, x->state(), true); monitor_enter(obj.result(), lock, syncTempOpr(), scratch, - x->monitor_no(), info_for_exception, info); + x->monitor_no(), info_for_exception, info, throw_ie_stub); } @@ -1127,14 +1140,15 @@ void LIRGenerator::do_NewInstance(NewInstance* x) { tty->print_cr(" ###class not loaded at new bci %d", x->printable_bci()); } #endif - CodeEmitInfo* info = state_for(x, x->state()); + CodeEmitInfo* info = state_for(x, x->needs_state_before() ? x->state_before() : x->state()); LIR_Opr reg = result_register_for(x->type()); new_instance(reg, x->klass(), x->is_unresolved(), - FrameMap::r10_oop_opr, - FrameMap::r11_oop_opr, - FrameMap::r4_oop_opr, - LIR_OprFact::illegalOpr, - FrameMap::r3_metadata_opr, info); + !x->is_unresolved() && x->klass()->is_inlinetype(), + FrameMap::r10_oop_opr, + FrameMap::r11_oop_opr, + FrameMap::r4_oop_opr, + LIR_OprFact::illegalOpr, + FrameMap::r3_metadata_opr, info); LIR_Opr result = rlock_result(x); __ move(reg, result); } @@ -1190,13 +1204,19 @@ void LIRGenerator::do_NewObjectArray(NewObjectArray* x) { length.load_item_force(FrameMap::r19_opr); LIR_Opr len = length.result(); - CodeStub* slow_path = new NewObjectArrayStub(klass_reg, len, reg, info); - ciKlass* obj = (ciKlass*) ciObjArrayKlass::make(x->klass()); + ciKlass* obj = ciArrayKlass::make(x->klass(), false, true, true); + + // TODO 8265122 Implement a fast path for this + bool is_flat = obj->is_loaded() && obj->is_flat_array_klass(); + bool is_null_free = obj->is_loaded() && obj->as_array_klass()->is_elem_null_free(); + + CodeStub* slow_path = new NewObjectArrayStub(klass_reg, len, reg, info, is_null_free); if (obj == ciEnv::unloaded_ciobjarrayklass()) { BAILOUT("encountered unloaded_ciobjarrayklass due to out of memory error"); } + klass2reg_with_patching(klass_reg, obj, patching_info); - __ allocate_array(reg, len, tmp1, tmp2, tmp3, tmp4, T_OBJECT, klass_reg, slow_path); + __ allocate_array(reg, len, tmp1, tmp2, tmp3, tmp4, T_OBJECT, klass_reg, slow_path, true, is_null_free || is_flat); LIR_Opr result = rlock_result(x); __ move(reg, result); @@ -1290,10 +1310,13 @@ void LIRGenerator::do_CheckCast(CheckCast* x) { if (!x->klass()->is_loaded() || UseCompressedClassPointers) { tmp3 = new_register(objectType); } + + __ checkcast(reg, obj.result(), x->klass(), new_register(objectType), new_register(objectType), tmp3, x->direct_compare(), info_for_exception, patching_info, stub, - x->profiled_method(), x->profiled_bci()); + x->profiled_method(), x->profiled_bci(), x->is_null_free()); + } void LIRGenerator::do_InstanceOf(InstanceOf* x) { @@ -1376,7 +1399,12 @@ void LIRGenerator::do_If(If* x) { __ safepoint(LIR_OprFact::illegalOpr, state_for(x, x->state_before())); } - __ cmp(lir_cond(cond), left, right); + if (x->substitutability_check()) { + substitutability_check(x, *xin, *yin); + } else { + __ cmp(lir_cond(cond), left, right); + } + // Generate branch profiling. Profiling code doesn't kill flags. profile_branch(x, cond); move_to_phi(x->state()); diff --git a/src/hotspot/cpu/aarch64/c1_LIR_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_LIR_aarch64.cpp index 5d2890251d7..ccafc6c2542 100644 --- a/src/hotspot/cpu/aarch64/c1_LIR_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_LIR_aarch64.cpp @@ -49,5 +49,6 @@ void LIR_Address::verify() const { assert(index()->is_illegal() || index()->is_double_cpu() || index()->is_single_cpu(), "wrong index operand"); assert(base()->type() == T_ADDRESS || base()->type() == T_OBJECT || base()->type() == T_LONG || base()->type() == T_METADATA, "wrong type for addresses"); + assert(index()->is_illegal() || disp() == 0, "cannot set both index and displacement"); } #endif // PRODUCT diff --git a/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp index 8a79274b2ff..1d1a723958c 100644 --- a/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp @@ -27,6 +27,8 @@ #include "c1/c1_Runtime1.hpp" #include "gc/shared/barrierSetAssembler.hpp" #include "gc/shared/collectedHeap.hpp" +#include "gc/shared/barrierSet.hpp" +#include "gc/shared/barrierSetAssembler.hpp" #include "gc/shared/tlab_globals.hpp" #include "interpreter/interpreter.hpp" #include "oops/arrayOop.hpp" @@ -99,12 +101,21 @@ void C1_MacroAssembler::try_allocate(Register obj, Register var_size_in_bytes, i void C1_MacroAssembler::initialize_header(Register obj, Register klass, Register len, Register t1, Register t2) { assert_different_registers(obj, klass, len); - if (UseCompactObjectHeaders) { + if (UseCompactObjectHeaders || EnableValhalla) { + // COH: Markword contains class pointer which is only known at runtime. + // Valhalla: Could have value class which has a different prototype header to a normal object. + // In both cases, we need to fetch dynamically. ldr(t1, Address(klass, Klass::prototype_header_offset())); str(t1, Address(obj, oopDesc::mark_offset_in_bytes())); } else { + // Otherwise: Can use the statically computed prototype header which is the same for every object. mov(t1, checked_cast(markWord::prototype().value())); str(t1, Address(obj, oopDesc::mark_offset_in_bytes())); + } + + if (!UseCompactObjectHeaders) { + // COH: Markword already contains class pointer. Nothing else to do. + // Otherwise: Fetch klass pointer following the markword if (UseCompressedClassPointers) { // Take care not to kill klass encode_klass_not_null(t1, klass); strw(t1, Address(obj, oopDesc::klass_offset_in_bytes())); @@ -241,31 +252,107 @@ void C1_MacroAssembler::allocate_array(Register obj, Register len, Register t1, verify_oop(obj); } -void C1_MacroAssembler::build_frame(int framesize, int bang_size_in_bytes) { - assert(bang_size_in_bytes >= framesize, "stack bang size incorrect"); +void C1_MacroAssembler::build_frame_helper(int frame_size_in_bytes, int sp_offset_for_orig_pc, int sp_inc, bool reset_orig_pc, bool needs_stack_repair) { + MacroAssembler::build_frame(frame_size_in_bytes); + + if (needs_stack_repair) { + save_stack_increment(sp_inc, frame_size_in_bytes); + } + if (reset_orig_pc) { + // Zero orig_pc to detect deoptimization during buffering in the entry points + str(zr, Address(sp, sp_offset_for_orig_pc)); + } +} + +void C1_MacroAssembler::build_frame(int frame_size_in_bytes, int bang_size_in_bytes, int sp_offset_for_orig_pc, bool needs_stack_repair, bool has_scalarized_args, Label* verified_inline_entry_label) { // Make sure there is enough stack space for this method's activation. // Note that we do this before creating a frame. + assert(bang_size_in_bytes >= frame_size_in_bytes, "stack bang size incorrect"); generate_stack_overflow_check(bang_size_in_bytes); - MacroAssembler::build_frame(framesize); + + build_frame_helper(frame_size_in_bytes, sp_offset_for_orig_pc, 0, has_scalarized_args, needs_stack_repair); // Insert nmethod entry barrier into frame. BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); bs->nmethod_entry_barrier(this, nullptr /* slow_path */, nullptr /* continuation */, nullptr /* guard */); -} -void C1_MacroAssembler::remove_frame(int framesize) { - MacroAssembler::remove_frame(framesize); + if (verified_inline_entry_label != nullptr) { + // Jump here from the scalarized entry points that already created the frame. + bind(*verified_inline_entry_label); + } } - void C1_MacroAssembler::verified_entry(bool breakAtEntry) { // If we have to make this method not-entrant we'll overwrite its // first instruction with a jump. For this action to be legal we // must ensure that this first instruction is a B, BL, NOP, BKPT, // SVC, HVC, or SMC. Make it a NOP. nop(); + if (C1Breakpoint) brk(1); } +int C1_MacroAssembler::scalarized_entry(const CompiledEntrySignature* ces, int frame_size_in_bytes, int bang_size_in_bytes, int sp_offset_for_orig_pc, Label& verified_inline_entry_label, bool is_inline_ro_entry) { + assert(InlineTypePassFieldsAsArgs, "sanity"); + // Make sure there is enough stack space for this method's activation. + assert(bang_size_in_bytes >= frame_size_in_bytes, "stack bang size incorrect"); + generate_stack_overflow_check(bang_size_in_bytes); + + GrowableArray* sig = ces->sig(); + GrowableArray* sig_cc = is_inline_ro_entry ? ces->sig_cc_ro() : ces->sig_cc(); + VMRegPair* regs = ces->regs(); + VMRegPair* regs_cc = is_inline_ro_entry ? ces->regs_cc_ro() : ces->regs_cc(); + int args_on_stack = ces->args_on_stack(); + int args_on_stack_cc = is_inline_ro_entry ? ces->args_on_stack_cc_ro() : ces->args_on_stack_cc(); + + assert(sig->length() <= sig_cc->length(), "Zero-sized inline class not allowed!"); + BasicType* sig_bt = NEW_RESOURCE_ARRAY(BasicType, sig_cc->length()); + int args_passed = sig->length(); + int args_passed_cc = SigEntry::fill_sig_bt(sig_cc, sig_bt); + + // Create a temp frame so we can call into the runtime. It must be properly set up to accommodate GC. + build_frame_helper(frame_size_in_bytes, sp_offset_for_orig_pc, 0, true, ces->c1_needs_stack_repair()); + + // The runtime call might safepoint, make sure nmethod entry barrier is executed + BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); + // C1 code is not hot enough to micro optimize the nmethod entry barrier with an out-of-line stub + bs->nmethod_entry_barrier(this, nullptr /* slow_path */, nullptr /* continuation */, nullptr /* guard */); + + // FIXME -- call runtime only if we cannot in-line allocate all the incoming inline type args. + mov(r19, (intptr_t) ces->method()); + if (is_inline_ro_entry) { + far_call(RuntimeAddress(Runtime1::entry_for(StubId::c1_buffer_inline_args_no_receiver_id))); + } else { + far_call(RuntimeAddress(Runtime1::entry_for(StubId::c1_buffer_inline_args_id))); + } + int rt_call_offset = offset(); + + // The runtime call returns the new array in r20 instead of the usual r0 + // because r0 is also j_rarg7 which may be holding a live argument here. + Register val_array = r20; + + // Remove the temp frame + MacroAssembler::remove_frame(frame_size_in_bytes); + + // Check if we need to extend the stack for packing + int sp_inc = 0; + if (args_on_stack > args_on_stack_cc) { + sp_inc = extend_stack_for_inline_args(args_on_stack); + } + + shuffle_inline_args(true, is_inline_ro_entry, sig_cc, + args_passed_cc, args_on_stack_cc, regs_cc, // from + args_passed, args_on_stack, regs, // to + sp_inc, val_array); + + // Create the real frame. Below jump will then skip over the stack banging and frame + // setup code in the verified_inline_entry (which has a different real_frame_size). + build_frame_helper(frame_size_in_bytes, sp_offset_for_orig_pc, sp_inc, false, ces->c1_needs_stack_repair()); + + b(verified_inline_entry_label); + return rt_call_offset; +} + + void C1_MacroAssembler::load_parameter(int offset_in_words, Register reg) { // rfp, + 0: link // + 1: return address diff --git a/src/hotspot/cpu/aarch64/c1_Runtime1_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_Runtime1_aarch64.cpp index 350b6f68196..260cf5fc5da 100644 --- a/src/hotspot/cpu/aarch64/c1_Runtime1_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_Runtime1_aarch64.cpp @@ -685,7 +685,8 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { __ enter(); OopMap* map = save_live_registers(sasm); - int call_offset = __ call_RT(obj, noreg, CAST_FROM_FN_PTR(address, new_instance), klass); + int call_offset; + call_offset = __ call_RT(obj, noreg, CAST_FROM_FN_PTR(address, new_instance), klass); oop_maps = new OopMapSet(); oop_maps->add_gc_map(call_offset, map); restore_live_registers_except_r0(sasm); @@ -718,6 +719,7 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { case StubId::c1_new_type_array_id: case StubId::c1_new_object_array_id: + case StubId::c1_new_null_free_array_id: { Register length = r19; // Incoming Register klass = r3; // Incoming @@ -725,8 +727,10 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { if (id == StubId::c1_new_type_array_id) { __ set_info("new_type_array", dont_gc_arguments); - } else { + } else if (id == StubId::c1_new_object_array_id) { __ set_info("new_object_array", dont_gc_arguments); + } else { + __ set_info("new_null_free_array", dont_gc_arguments); } #ifdef ASSERT @@ -736,13 +740,28 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { Register t0 = obj; __ ldrw(t0, Address(klass, Klass::layout_helper_offset())); __ asrw(t0, t0, Klass::_lh_array_tag_shift); - int tag = ((id == StubId::c1_new_type_array_id) - ? Klass::_lh_array_tag_type_value - : Klass::_lh_array_tag_obj_value); - __ mov(rscratch1, tag); - __ cmpw(t0, rscratch1); - __ br(Assembler::EQ, ok); - __ stop("assert(is an array klass)"); + switch (id) { + case StubId::c1_new_type_array_id: + __ cmpw(t0, Klass::_lh_array_tag_type_value); + __ br(Assembler::EQ, ok); + __ stop("assert(is a type array klass)"); + break; + case StubId::c1_new_object_array_id: + __ cmpw(t0, Klass::_lh_array_tag_ref_value); // new "[Ljava/lang/Object;" + __ br(Assembler::EQ, ok); + __ cmpw(t0, Klass::_lh_array_tag_flat_value); // new "[LVT;" + __ br(Assembler::EQ, ok); + __ stop("assert(is an object or inline type array klass)"); + break; + case StubId::c1_new_null_free_array_id: + __ cmpw(t0, Klass::_lh_array_tag_flat_value); // the array can be a flat array. + __ br(Assembler::EQ, ok); + __ cmpw(t0, Klass::_lh_array_tag_ref_value); // the array cannot be a flat array (due to the InlineArrayElementMaxFlatSize, etc.) + __ br(Assembler::EQ, ok); + __ stop("assert(is an object or inline type array klass)"); + break; + default: ShouldNotReachHere(); + } __ should_not_reach_here(); __ bind(ok); } @@ -753,8 +772,11 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { int call_offset; if (id == StubId::c1_new_type_array_id) { call_offset = __ call_RT(obj, noreg, CAST_FROM_FN_PTR(address, new_type_array), klass, length); - } else { + } else if (id == StubId::c1_new_object_array_id) { call_offset = __ call_RT(obj, noreg, CAST_FROM_FN_PTR(address, new_object_array), klass, length); + } else { + assert(id == StubId::c1_new_null_free_array_id, "must be"); + call_offset = __ call_RT(obj, noreg, CAST_FROM_FN_PTR(address, new_null_free_array), klass, length); } oop_maps = new OopMapSet(); @@ -789,6 +811,93 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { } break; + case StubId::c1_buffer_inline_args_id: + case StubId::c1_buffer_inline_args_no_receiver_id: + { + const char* name = (id == StubId::c1_buffer_inline_args_id) ? + "buffer_inline_args" : "buffer_inline_args_no_receiver"; + StubFrame f(sasm, name, dont_gc_arguments); + OopMap* map = save_live_registers(sasm); + Register method = r19; // Incoming + address entry = (id == StubId::c1_buffer_inline_args_id) ? + CAST_FROM_FN_PTR(address, buffer_inline_args) : + CAST_FROM_FN_PTR(address, buffer_inline_args_no_receiver); + // This is called from a C1 method's scalarized entry point + // where r0-r7 may be holding live argument values so we can't + // return the result in r0 as the other stubs do. LR is used as + // a temporary below to avoid the result being clobbered by + // restore_live_registers. It's saved and restored by + // StubAssembler::prologue and epilogue anyway. + int call_offset = __ call_RT(lr, noreg, entry, method); + oop_maps = new OopMapSet(); + oop_maps->add_gc_map(call_offset, map); + restore_live_registers(sasm); + __ mov(r20, lr); + __ verify_oop(r20); // r20: an array of buffered value objects + } + break; + + case StubId::c1_load_flat_array_id: + { + StubFrame f(sasm, "load_flat_array", dont_gc_arguments); + OopMap* map = save_live_registers(sasm); + + // Called with store_parameter and not C abi + + f.load_argument(1, r0); // r0,: array + f.load_argument(0, r1); // r1,: index + int call_offset = __ call_RT(r0, noreg, CAST_FROM_FN_PTR(address, load_flat_array), r0, r1); + + // Ensure the stores that initialize the buffer are visible + // before any subsequent store that publishes this reference. + __ membar(Assembler::StoreStore); + + oop_maps = new OopMapSet(); + oop_maps->add_gc_map(call_offset, map); + restore_live_registers_except_r0(sasm); + + // r0: loaded element at array[index] + __ verify_oop(r0); + } + break; + + case StubId::c1_store_flat_array_id: + { + StubFrame f(sasm, "store_flat_array", dont_gc_arguments); + OopMap* map = save_live_registers(sasm, 4); + + // Called with store_parameter and not C abi + + f.load_argument(2, r0); // r0: array + f.load_argument(1, r1); // r1: index + f.load_argument(0, r2); // r2: value + int call_offset = __ call_RT(noreg, noreg, CAST_FROM_FN_PTR(address, store_flat_array), r0, r1, r2); + + oop_maps = new OopMapSet(); + oop_maps->add_gc_map(call_offset, map); + restore_live_registers_except_r0(sasm); + } + break; + + case StubId::c1_substitutability_check_id: + { + StubFrame f(sasm, "substitutability_check", dont_gc_arguments); + OopMap* map = save_live_registers(sasm); + + // Called with store_parameter and not C abi + + f.load_argument(1, r1); // r1,: left + f.load_argument(0, r2); // r2,: right + int call_offset = __ call_RT(noreg, noreg, CAST_FROM_FN_PTR(address, substitutability_check), r1, r2); + + oop_maps = new OopMapSet(); + oop_maps->add_gc_map(call_offset, map); + restore_live_registers_except_r0(sasm); + + // r0,: are the two operands substitutable + } + break; + case StubId::c1_register_finalizer_id: { __ set_info("register_finalizer", dont_gc_arguments); @@ -828,11 +937,23 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { break; case StubId::c1_throw_incompatible_class_change_error_id: - { StubFrame f(sasm, "throw_incompatible_class_cast_exception", dont_gc_arguments, does_not_return); + { StubFrame f(sasm, "throw_incompatible_class_change_error", dont_gc_arguments, does_not_return); oop_maps = generate_exception_throw(sasm, CAST_FROM_FN_PTR(address, throw_incompatible_class_change_error), false); } break; + case StubId::c1_throw_illegal_monitor_state_exception_id: + { StubFrame f(sasm, "throw_illegal_monitor_state_exception", dont_gc_arguments); + oop_maps = generate_exception_throw(sasm, CAST_FROM_FN_PTR(address, throw_illegal_monitor_state_exception), false); + } + break; + + case StubId::c1_throw_identity_exception_id: + { StubFrame f(sasm, "throw_identity_exception", dont_gc_arguments); + oop_maps = generate_exception_throw(sasm, CAST_FROM_FN_PTR(address, throw_identity_exception), true); + } + break; + case StubId::c1_slow_subtype_check_id: { // Typical calling sequence: @@ -1091,6 +1212,8 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { break; default: + // FIXME: For unhandled trap_id this code fails with assert during vm intialization + // rather than insert a call to unimplemented_entry { StubFrame f(sasm, "unimplemented entry", dont_gc_arguments, does_not_return); __ mov(r0, (int)id); __ call_RT(noreg, noreg, CAST_FROM_FN_PTR(address, unimplemented_entry), r0); @@ -1098,6 +1221,8 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { break; } } + + return oop_maps; } diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp index b1562c54f4e..ca560eececc 100644 --- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp @@ -46,6 +46,27 @@ typedef void (MacroAssembler::* chr_insn)(Register Rt, const Address &adr); +void C2_MacroAssembler::entry_barrier() { + BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); + // Dummy labels for just measuring the code size + Label dummy_slow_path; + Label dummy_continuation; + Label dummy_guard; + Label* slow_path = &dummy_slow_path; + Label* continuation = &dummy_continuation; + Label* guard = &dummy_guard; + if (!Compile::current()->output()->in_scratch_emit_size()) { + // Use real labels from actual stub when not emitting code for the purpose of measuring its size + C2EntryBarrierStub* stub = new (Compile::current()->comp_arena()) C2EntryBarrierStub(); + Compile::current()->output()->add_stub(stub); + slow_path = &stub->entry(); + continuation = &stub->continuation(); + guard = &stub->guard(); + } + // In the C2 code, we move the non-hot part of nmethod entry barriers out-of-line to a stub. + bs->nmethod_entry_barrier(this, slow_path, continuation, guard); +} + // jdk.internal.util.ArraysSupport.vectorizedHashCode address C2_MacroAssembler::arrays_hashcode(Register ary, Register cnt, Register result, FloatRegister vdata0, FloatRegister vdata1, diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp index 0403a27910f..145371340ce 100644 --- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp @@ -44,6 +44,8 @@ unsigned vector_length_in_bytes); public: + void entry_barrier(); + // jdk.internal.util.ArraysSupport.vectorizedHashCode address arrays_hashcode(Register ary, Register cnt, Register result, FloatRegister vdata0, FloatRegister vdata1, FloatRegister vdata2, FloatRegister vdata3, diff --git a/src/hotspot/cpu/aarch64/continuationFreezeThaw_aarch64.inline.hpp b/src/hotspot/cpu/aarch64/continuationFreezeThaw_aarch64.inline.hpp index 1fd59b81d9e..981189a7f6b 100644 --- a/src/hotspot/cpu/aarch64/continuationFreezeThaw_aarch64.inline.hpp +++ b/src/hotspot/cpu/aarch64/continuationFreezeThaw_aarch64.inline.hpp @@ -67,6 +67,12 @@ inline frame FreezeBase::sender(const frame& f) { int slot = 0; CodeBlob* sender_cb = CodeCache::find_blob_and_oopmap(sender_pc, slot); + + // Repair the sender sp if the frame has been extended + if (sender_cb->is_nmethod()) { + sender_sp = f.repair_sender_sp(sender_sp, link_addr); + } + return sender_cb != nullptr ? frame(sender_sp, sender_sp, *link_addr, sender_pc, sender_cb, slot == -1 ? nullptr : sender_cb->oop_map_for_slot(slot, sender_pc), @@ -75,7 +81,7 @@ inline frame FreezeBase::sender(const frame& f) { } template -frame FreezeBase::new_heap_frame(frame& f, frame& caller) { +frame FreezeBase::new_heap_frame(frame& f, frame& caller, int size_adjust) { assert(FKind::is_instance(f), ""); assert(!caller.is_interpreted_frame() || caller.unextended_sp() == (intptr_t*)caller.at(frame::interpreter_frame_last_sp_offset), ""); @@ -109,8 +115,8 @@ frame FreezeBase::new_heap_frame(frame& f, frame& caller) { fp = FKind::compiled ? *(intptr_t**)(f.sp() - frame::sender_sp_offset) : (intptr_t*)badAddressVal; int fsize = FKind::size(f); - sp = caller.unextended_sp() - fsize; - if (caller.is_interpreted_frame()) { + sp = caller.unextended_sp() - fsize - size_adjust; + if (caller.is_interpreted_frame() && size_adjust == 0) { // If the caller is interpreted, our stackargs are not supposed to overlap with it // so we make more room by moving sp down by argsize int argsize = FKind::stack_argsize(f); @@ -183,11 +189,12 @@ inline void FreezeBase::set_top_frame_metadata_pd(const frame& hf) { : (intptr_t)hf.fp(); } -inline void FreezeBase::patch_pd(frame& hf, const frame& caller) { +inline void FreezeBase::patch_pd(frame& hf, const frame& caller, bool is_bottom_frame) { if (caller.is_interpreted_frame()) { assert(!caller.is_empty(), ""); patch_callee_link_relative(caller, caller.fp()); - } else { + } else if (is_bottom_frame && caller.pc() != nullptr) { + assert(caller.is_compiled_frame(), ""); // If we're the bottom-most frame frozen in this freeze, the caller might have stayed frozen in the chunk, // and its oop-containing fp fixed. We've now just overwritten it, so we must patch it back to its value // as read from the chunk. @@ -223,7 +230,7 @@ inline frame ThawBase::new_entry_frame() { return frame(sp, sp, _cont.entryFP(), _cont.entryPC()); // TODO PERF: This finds code blob and computes deopt state } -template frame ThawBase::new_stack_frame(const frame& hf, frame& caller, bool bottom) { +template frame ThawBase::new_stack_frame(const frame& hf, frame& caller, bool bottom, int size_adjust) { assert(FKind::is_instance(hf), ""); // The values in the returned frame object will be written into the callee's stack in patch. @@ -251,24 +258,23 @@ template frame ThawBase::new_stack_frame(const frame& hf, frame& return f; } else { int fsize = FKind::size(hf); - intptr_t* frame_sp = caller.unextended_sp() - fsize; + intptr_t* frame_sp = caller.unextended_sp() - fsize - size_adjust; if (bottom || caller.is_interpreted_frame()) { - int argsize = FKind::stack_argsize(hf); - - fsize += argsize; - frame_sp -= argsize; - caller.set_sp(caller.sp() - argsize); - assert(caller.sp() == frame_sp + (fsize-argsize), ""); - + if (size_adjust == 0) { + int argsize = FKind::stack_argsize(hf); + frame_sp -= argsize; + } frame_sp = align(hf, frame_sp, caller, bottom); } + caller.set_sp(frame_sp + fsize); + assert(is_aligned(frame_sp, frame::frame_alignment), ""); assert(hf.cb() != nullptr, ""); assert(hf.oop_map() != nullptr, ""); intptr_t* fp; if (PreserveFramePointer) { // we need to recreate a "real" frame pointer, pointing into the stack - fp = frame_sp + FKind::size(hf) - frame::sender_sp_offset; + fp = frame_sp + fsize - frame::sender_sp_offset; } else { fp = FKind::stub || FKind::native ? frame_sp + fsize - frame::sender_sp_offset // fp always points to the address below the pushed return pc. We need correct address. @@ -283,16 +289,16 @@ inline intptr_t* ThawBase::align(const frame& hf, intptr_t* frame_sp, frame& cal if (((intptr_t)frame_sp & 0xf) != 0) { assert(caller.is_interpreted_frame() || (bottom && hf.compiled_frame_stack_argsize() % 2 != 0), ""); frame_sp--; - caller.set_sp(caller.sp() - 1); } assert(is_aligned(frame_sp, frame::frame_alignment), ""); #endif - return frame_sp; } inline void ThawBase::patch_pd(frame& f, const frame& caller) { - patch_callee_link(caller, caller.fp()); + if (caller.is_interpreted_frame() || PreserveFramePointer) { + patch_callee_link(caller, caller.fp()); + } } inline void ThawBase::patch_pd(frame& f, intptr_t* caller_sp) { diff --git a/src/hotspot/cpu/aarch64/continuationHelper_aarch64.inline.hpp b/src/hotspot/cpu/aarch64/continuationHelper_aarch64.inline.hpp index e39580369db..c74e1d5d7ab 100644 --- a/src/hotspot/cpu/aarch64/continuationHelper_aarch64.inline.hpp +++ b/src/hotspot/cpu/aarch64/continuationHelper_aarch64.inline.hpp @@ -141,8 +141,8 @@ inline address ContinuationHelper::Frame::real_pc(const frame& f) { return pauth_strip_pointer(*pc_addr); } -inline void ContinuationHelper::Frame::patch_pc(const frame& f, address pc) { - address* pc_addr = &(((address*) f.sp())[-1]); +inline void ContinuationHelper::Frame::patch_pc(const frame& f, address pc, bool callee_augmented) { + address* pc_addr = &(((address*) (callee_augmented ? f.unextended_sp() : f.sp()))[-1]); *pc_addr = pauth_sign_return_address(pc); } diff --git a/src/hotspot/cpu/aarch64/frame_aarch64.cpp b/src/hotspot/cpu/aarch64/frame_aarch64.cpp index aff50b9cf2f..cf8ebbebaba 100644 --- a/src/hotspot/cpu/aarch64/frame_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/frame_aarch64.cpp @@ -153,14 +153,17 @@ bool frame::safe_for_sender(JavaThread *thread) { if (!thread->is_in_full_stack_checked((address)sender_sp)) { return false; } - sender_unextended_sp = sender_sp; // Note: frame::sender_sp_offset is only valid for compiled frame - saved_fp = (intptr_t*) *(sender_sp - frame::sender_sp_offset); + intptr_t **saved_fp_addr = (intptr_t**) (sender_sp - frame::sender_sp_offset); + saved_fp = *saved_fp_addr; // Note: PAC authentication may fail in case broken frame is passed in. // Just strip it for now. sender_pc = pauth_strip_pointer((address) *(sender_sp - 1)); - } + // Repair the sender sp if this is a method with scalarized inline type args + sender_sp = repair_sender_sp(sender_sp, saved_fp_addr); + sender_unextended_sp = sender_sp; + } if (Continuation::is_return_barrier_entry(sender_pc)) { // sender_pc might be invalid so check that the frame // actually belongs to a Continuation. @@ -667,6 +670,9 @@ void frame::describe_pd(FrameValues& values, int frame_no) { } else { ret_pc_loc = real_fp() - return_addr_offset; fp_loc = real_fp() - sender_sp_offset; + if (cb()->is_nmethod() && cb()->as_nmethod_or_null()->needs_stack_repair()) { + values.describe(frame_no, fp_loc - 1, err_msg("fsize for #%d", frame_no), 1); + } } address ret_pc = *(address*)ret_pc_loc; values.describe(frame_no, ret_pc_loc, @@ -822,6 +828,44 @@ frame::frame(void* sp, void* fp, void* pc) { #endif +// Check for a method with scalarized inline type arguments that needs +// a stack repair and return the repaired sender stack pointer. +intptr_t* frame::repair_sender_sp(intptr_t* sender_sp, intptr_t** saved_fp_addr) const { + nmethod* nm = _cb->as_nmethod_or_null(); + if (nm != nullptr && nm->needs_stack_repair()) { + // The stack increment resides just below the saved FP on the stack and + // records the total frame size excluding the two words for saving FP and LR. + intptr_t* sp_inc_addr = (intptr_t*) (saved_fp_addr - 1); + assert(*sp_inc_addr % StackAlignmentInBytes == 0, "sp_inc not aligned"); + int real_frame_size = (*sp_inc_addr / wordSize) + 2; + assert(real_frame_size >= _cb->frame_size() && real_frame_size <= 1000000, "invalid frame size"); + sender_sp = unextended_sp() + real_frame_size; + } + return sender_sp; +} + +intptr_t* frame::repair_sender_sp(nmethod* nm, intptr_t* sp, intptr_t** saved_fp_addr) { + assert(nm != nullptr && nm->needs_stack_repair(), ""); + // The stack increment resides just below the saved FP on the stack and + // records the total frame size excluding the two words for saving FP and LR. + intptr_t* real_frame_size_addr = (intptr_t*) (saved_fp_addr - 1); + int real_frame_size = (*real_frame_size_addr / wordSize) + 2; + assert(real_frame_size >= nm->frame_size() && real_frame_size <= 1000000, "invalid frame size"); + return sp + real_frame_size; +} + +bool frame::was_augmented_on_entry(int& real_size) const { + assert(is_compiled_frame(), ""); + if (_cb->as_nmethod_or_null()->needs_stack_repair()) { + intptr_t* real_frame_size_addr = unextended_sp() + _cb->frame_size() - sender_sp_offset - 1; + log_trace(continuations)("real_frame_size is addr is " INTPTR_FORMAT, p2i(real_frame_size_addr)); + real_size = (*real_frame_size_addr / wordSize) + 2; + return real_size != _cb->frame_size(); + } + real_size = _cb->frame_size(); + return false; +} + void JavaFrameAnchor::make_walkable() { // last frame set? if (last_Java_sp() == nullptr) return; diff --git a/src/hotspot/cpu/aarch64/frame_aarch64.hpp b/src/hotspot/cpu/aarch64/frame_aarch64.hpp index da020b4234d..64dc087c1c6 100644 --- a/src/hotspot/cpu/aarch64/frame_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/frame_aarch64.hpp @@ -152,6 +152,14 @@ return (intptr_t*) addr_at(offset); } + public: + // Support for scalarized inline type calling convention + intptr_t* repair_sender_sp(intptr_t* sender_sp, intptr_t** saved_fp_addr) const; + static intptr_t* repair_sender_sp(nmethod* nm, intptr_t* sp, intptr_t** saved_fp_addr); + bool was_augmented_on_entry(int& real_size) const; + + private: + #ifdef ASSERT // Used in frame::sender_for_{interpreter,compiled}_frame static void verify_deopt_original_pc(nmethod* nm, intptr_t* unextended_sp); diff --git a/src/hotspot/cpu/aarch64/frame_aarch64.inline.hpp b/src/hotspot/cpu/aarch64/frame_aarch64.inline.hpp index 47ae93a4932..8c48a890df7 100644 --- a/src/hotspot/cpu/aarch64/frame_aarch64.inline.hpp +++ b/src/hotspot/cpu/aarch64/frame_aarch64.inline.hpp @@ -32,6 +32,9 @@ #include "interpreter/interpreter.hpp" #include "runtime/sharedRuntime.hpp" #include "pauth_aarch64.hpp" +#ifdef COMPILER1 +#include "c1/c1_Runtime1.hpp" +#endif // Inline functions for AArch64 frames: @@ -451,21 +454,51 @@ inline frame frame::sender_for_compiled_frame(RegisterMap* map) const { assert(_cb->frame_size() > 0, "must have non-zero frame size"); intptr_t* l_sender_sp = (!PreserveFramePointer || _sp_is_trusted) ? unextended_sp() + _cb->frame_size() : sender_sp(); +#ifdef ASSERT + address sender_pc_copy = pauth_strip_verifiable((address) *(l_sender_sp-1)); +#endif + assert(!_sp_is_trusted || l_sender_sp == real_fp(), ""); + intptr_t** saved_fp_addr = (intptr_t**) (l_sender_sp - frame::sender_sp_offset); + + // Repair the sender sp if the frame has been extended + l_sender_sp = repair_sender_sp(l_sender_sp, saved_fp_addr); + // The return_address is always the word on the stack. // For ROP protection, C1/C2 will have signed the sender_pc, // but there is no requirement to authenticate it here. address sender_pc = pauth_strip_verifiable((address) *(l_sender_sp - 1)); - intptr_t** saved_fp_addr = (intptr_t**) (l_sender_sp - frame::sender_sp_offset); +#ifdef ASSERT + if (sender_pc != sender_pc_copy) { + // When extending the stack in the callee method entry to make room for unpacking of value + // type args, we keep a copy of the sender pc at the expected location in the callee frame. + // If the sender pc is patched due to deoptimization, the copy is not consistent anymore. + nmethod* nm = CodeCache::find_blob(sender_pc)->as_nmethod(); + assert(sender_pc == nm->deopt_mh_handler_begin() || sender_pc == nm->deopt_handler_begin(), "unexpected sender pc"); + } +#endif if (map->update_map()) { // Tell GC to use argument oopmaps for some runtime stubs that need it. // For C1, the runtime stub might not have oop maps, so set this flag // outside of update_register_map. - if (!_cb->is_nmethod()) { // compiled frames do not use callee-saved registers - map->set_include_argument_oops(_cb->caller_must_gc_arguments(map->thread())); + bool c1_buffering = false; +#ifdef COMPILER1 + nmethod* nm = _cb->as_nmethod_or_null(); + if (nm != nullptr && nm->is_compiled_by_c1() && nm->method()->has_scalarized_args() && + pc() < nm->verified_inline_entry_point()) { + // TODO 8284443 Can't we do that by not passing 'dont_gc_arguments' in case 'StubId::c1_buffer_inline_args_id' in 'Runtime1::generate_code_for'? + // The VEP and VIEP(RO) of C1-compiled methods call buffer_inline_args_xxx + // before doing any argument shuffling, so we need to scan the oops + // as the caller passes them. + c1_buffering = true; + } +#endif + if (!_cb->is_nmethod() || c1_buffering) { // compiled frames do not use callee-saved registers + bool caller_args = _cb->caller_must_gc_arguments(map->thread()) || c1_buffering; + map->set_include_argument_oops(caller_args); if (oop_map() != nullptr) { _oop_map->update_register_map(this, map); } diff --git a/src/hotspot/cpu/aarch64/gc/g1/g1BarrierSetAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/g1/g1BarrierSetAssembler_aarch64.cpp index 42f3c4a015a..01ea2392e50 100644 --- a/src/hotspot/cpu/aarch64/gc/g1/g1BarrierSetAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/gc/g1/g1BarrierSetAssembler_aarch64.cpp @@ -99,6 +99,7 @@ void G1BarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssembler* mas static void generate_queue_test_and_insertion(MacroAssembler* masm, ByteSize index_offset, ByteSize buffer_offset, Label& runtime, const Register thread, const Register value, const Register temp1, const Register temp2) { + assert_different_registers(value, temp1, temp2); // Can we store a value in the given thread's buffer? // (The index field is typed as size_t.) __ ldr(temp1, Address(thread, in_bytes(index_offset))); // temp1 := *(index address) @@ -172,7 +173,19 @@ void G1BarrierSetAssembler::g1_write_barrier_pre(MacroAssembler* masm, __ bind(runtime); - __ push_call_clobbered_registers(); + // save the live input values + RegSet saved = RegSet::of(pre_val); + FloatRegSet fsaved; + + // Barriers might be emitted when converting between (scalarized) calling + // conventions for inline types. Save all argument registers before calling + // into the runtime. + + // TODO 8366717 This came with 8284161: Implementation of Virtual Threads (Preview) later in May 2022 + // Check if it's sufficient + //__ push_call_clobbered_registers(); + assert_different_registers(rscratch1, pre_val); // push_CPU_state trashes rscratch1 + __ push_CPU_state(true); // Calling the runtime using the regular call_VM_leaf mechanism generates // code (generated by InterpreterMacroAssember::call_VM_leaf_base) @@ -193,7 +206,7 @@ void G1BarrierSetAssembler::g1_write_barrier_pre(MacroAssembler* masm, __ call_VM_leaf(CAST_FROM_FN_PTR(address, G1BarrierSetRuntime::write_ref_field_pre_entry), pre_val, thread); } - __ pop_call_clobbered_registers(); + __ pop_CPU_state(true); __ bind(done); @@ -264,11 +277,24 @@ void G1BarrierSetAssembler::g1_write_barrier_post(MacroAssembler* masm, generate_post_barrier_slow_path(masm, thread, tmp1, tmp2, done, runtime); __ bind(runtime); + // save the live input values RegSet saved = RegSet::of(store_addr); - __ push(saved, sp); + FloatRegSet fsaved; + + // Barriers might be emitted when converting between (scalarized) calling + // conventions for inline types. Save all argument registers before calling + // into the runtime. + // TODO 8366717 Without this, r11 is corrupted below and it holds the array of pre-allocated value objects in the C2I adapter... + // Check if__ push_call_clobbered_registers() is sufficient + assert_different_registers(rscratch1, tmp1); // push_CPU_state trashes rscratch1 + __ enter(); + __ push_CPU_state(true); + __ call_VM_leaf(CAST_FROM_FN_PTR(address, G1BarrierSetRuntime::write_ref_field_post_entry), tmp1, thread); - __ pop(saved, sp); + + __ pop_CPU_state(true); + __ leave(); __ bind(done); } @@ -391,6 +417,16 @@ void G1BarrierSetAssembler::load_at(MacroAssembler* masm, DecoratorSet decorator void G1BarrierSetAssembler::oop_store_at(MacroAssembler* masm, DecoratorSet decorators, BasicType type, Address dst, Register val, Register tmp1, Register tmp2, Register tmp3) { + + bool in_heap = (decorators & IN_HEAP) != 0; + bool as_normal = (decorators & AS_NORMAL) != 0; + bool dest_uninitialized = (decorators & IS_DEST_UNINITIALIZED) != 0; + + bool needs_pre_barrier = as_normal && !dest_uninitialized; + bool needs_post_barrier = (val != noreg && in_heap); + + assert_different_registers(val, tmp1, tmp2, tmp3); + // flatten object address if needed if (dst.index() == noreg && dst.offset() == 0) { if (dst.base() != tmp3) { @@ -400,31 +436,38 @@ void G1BarrierSetAssembler::oop_store_at(MacroAssembler* masm, DecoratorSet deco __ lea(tmp3, dst); } - g1_write_barrier_pre(masm, - tmp3 /* obj */, - tmp2 /* pre_val */, - rthread /* thread */, - tmp1 /* tmp1 */, - rscratch2 /* tmp2 */, - val != noreg /* tosca_live */, - false /* expand_call */); + if (needs_pre_barrier) { + g1_write_barrier_pre(masm, + tmp3 /* obj */, + tmp2 /* pre_val */, + rthread /* thread */, + tmp1 /* tmp1 */, + rscratch2 /* tmp2 */, + val != noreg /* tosca_live */, + false /* expand_call */); + } if (val == noreg) { BarrierSetAssembler::store_at(masm, decorators, type, Address(tmp3, 0), noreg, noreg, noreg, noreg); } else { // G1 barrier needs uncompressed oop for region cross check. Register new_val = val; - if (UseCompressedOops) { - new_val = rscratch2; - __ mov(new_val, val); + if (needs_post_barrier) { + if (UseCompressedOops) { + new_val = rscratch2; + __ mov(new_val, val); + } } + BarrierSetAssembler::store_at(masm, decorators, type, Address(tmp3, 0), val, noreg, noreg, noreg); - g1_write_barrier_post(masm, - tmp3 /* store_adr */, - new_val /* new_val */, - rthread /* thread */, - tmp1 /* tmp1 */, - tmp2 /* tmp2 */); + if (needs_post_barrier) { + g1_write_barrier_post(masm, + tmp3 /* store_adr */, + new_val /* new_val */, + rthread /* thread */, + tmp1 /* tmp1 */, + tmp2 /* tmp2 */); + } } } diff --git a/src/hotspot/cpu/aarch64/gc/g1/g1_aarch64.ad b/src/hotspot/cpu/aarch64/gc/g1/g1_aarch64.ad index 081a67d6880..9fb041b53ff 100644 --- a/src/hotspot/cpu/aarch64/gc/g1/g1_aarch64.ad +++ b/src/hotspot/cpu/aarch64/gc/g1/g1_aarch64.ad @@ -61,18 +61,109 @@ static void write_barrier_post(MacroAssembler* masm, Register store_addr, Register new_val, Register tmp1, - Register tmp2) { + Register tmp2, + RegSet preserve = RegSet()) { if (!G1PostBarrierStubC2::needs_barrier(node)) { return; } Assembler::InlineSkippedInstructionsCounter skip_counter(masm); G1BarrierSetAssembler* g1_asm = static_cast(BarrierSet::barrier_set()->barrier_set_assembler()); G1PostBarrierStubC2* const stub = G1PostBarrierStubC2::create(node); + for (RegSetIterator reg = preserve.begin(); *reg != noreg; ++reg) { + stub->preserve(*reg); + } g1_asm->g1_write_barrier_post_c2(masm, store_addr, new_val, rthread, tmp1, tmp2, stub); } %} +// TODO 8350865 (same applies to g1StoreLSpecialTwoOops) +// - Can we use an unbound register for src? +// - Do no set/overwrite barrier data here, also handle G1C2BarrierPostNotNull +// - Is the zero-extend really required in all the places? +// - Move this into the .m4? +instruct g1StoreLSpecialOneOop(indirect mem, iRegL_R11 src, immI off, iRegPNoSp tmp1, iRegPNoSp tmp2, iRegPNoSp tmp3, iRegPNoSp tmp4, rFlagsReg cr) +%{ + predicate(UseG1GC); + match(Set mem (StoreLSpecial mem (Binary src off))); + effect(TEMP tmp1, TEMP tmp2, TEMP tmp3, TEMP tmp4, USE_KILL src, KILL cr); + ins_cost(INSN_COST); + format %{ "str $src, $mem\t# g1StoreLSpecialOneOop" %} + ins_encode %{ + ((MachNode*)this)->set_barrier_data(G1C2BarrierPre | G1C2BarrierPost); + + // Adjust address to point to narrow oop + __ add($tmp4$$Register, $mem$$Register, $off$$constant); + write_barrier_pre(masm, this, + $tmp4$$Register /* obj */, + $tmp1$$Register /* pre_val */, + $tmp2$$Register /* tmp1 */, + $tmp3$$Register /* tmp2 */, + RegSet::of($mem$$Register, $src$$Register, $tmp4$$Register) /* preserve */); + + __ str($src$$Register, $mem$$Register); + + // Shift long value to extract the narrow oop field value and zero-extend it + __ lsr($src$$Register, $src$$Register, $off$$constant << LogBitsPerByte); + __ ubfm($src$$Register, $src$$Register, 0, 31); + + write_barrier_post(masm, this, + $tmp4$$Register /* store_addr */, + $src$$Register /* new_val */, + $tmp2$$Register /* tmp1 */, + $tmp3$$Register /* tmp2 */); + %} + ins_pipe(istore_reg_mem); +%} + +instruct g1StoreLSpecialTwoOops(indirect mem, iRegL_R11 src, iRegPNoSp tmp1, iRegPNoSp tmp2, iRegPNoSp tmp3, iRegPNoSp tmp4, rFlagsReg cr) +%{ + predicate(UseG1GC); + match(Set mem (StoreLSpecial mem src)); + effect(TEMP tmp1, TEMP tmp2, TEMP tmp3, TEMP tmp4, USE_KILL src, KILL cr); + ins_cost(INSN_COST); + format %{ "str $src, $mem\t# g1StoreLSpecialTwoOops" %} + ins_encode %{ + ((MachNode*)this)->set_barrier_data(G1C2BarrierPre | G1C2BarrierPost); + + write_barrier_pre(masm, this, + $mem$$Register /* obj */, + $tmp1$$Register /* pre_val */, + $tmp2$$Register /* tmp1 */, + $tmp3$$Register /* tmp2 */, + RegSet::of($mem$$Register, $src$$Register) /* preserve */); + // Adjust address to point to the second narrow oop in the long value + __ add($tmp4$$Register, $mem$$Register, 4); + write_barrier_pre(masm, this, + $tmp4$$Register /* obj */, + $tmp1$$Register /* pre_val */, + $tmp2$$Register /* tmp1 */, + $tmp3$$Register /* tmp2 */, + RegSet::of($mem$$Register, $src$$Register, $tmp4$$Register) /* preserve */); + + __ str($src$$Register, $mem$$Register); + + // Zero-extend first narrow oop to long + __ ubfm($tmp1$$Register, $src$$Register, 0, 31); + + // Shift long value to extract the second narrow oop field value + __ lsr($src$$Register, $src$$Register, 32); + write_barrier_post(masm, this, + $mem$$Register /* store_addr */, + $tmp1$$Register /* new_val */, + $tmp2$$Register /* tmp1 */, + $tmp3$$Register /* tmp2 */, + RegSet::of($src$$Register, $tmp4$$Register) /* preserve */); + write_barrier_post(masm, this, + $tmp4$$Register /* store_addr */, + $src$$Register /* new_val */, + $tmp2$$Register /* tmp1 */, + $tmp3$$Register /* tmp2 */); + %} + ins_pipe(istore_reg_mem); +%} + + // BEGIN This section of the file is automatically generated. Do not edit -------------- // This section is generated from g1_aarch64.m4 diff --git a/src/hotspot/cpu/aarch64/gc/shared/barrierSetAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/shared/barrierSetAssembler_aarch64.cpp index 869e26d3359..9159a6dc0da 100644 --- a/src/hotspot/cpu/aarch64/gc/shared/barrierSetAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/gc/shared/barrierSetAssembler_aarch64.cpp @@ -26,6 +26,7 @@ #include "gc/shared/barrierSet.hpp" #include "gc/shared/barrierSetAssembler.hpp" #include "gc/shared/barrierSetNMethod.hpp" +#include "gc/shared/barrierSetRuntime.hpp" #include "gc/shared/collectedHeap.hpp" #include "interpreter/interp_masm.hpp" #include "memory/universe.hpp" @@ -49,6 +50,7 @@ void BarrierSetAssembler::load_at(MacroAssembler* masm, DecoratorSet decorators, bool in_heap = (decorators & IN_HEAP) != 0; bool in_native = (decorators & IN_NATIVE) != 0; bool is_not_null = (decorators & IS_NOT_NULL) != 0; + switch (type) { case T_OBJECT: case T_ARRAY: { @@ -86,22 +88,35 @@ void BarrierSetAssembler::store_at(MacroAssembler* masm, DecoratorSet decorators Address dst, Register val, Register tmp1, Register tmp2, Register tmp3) { bool in_heap = (decorators & IN_HEAP) != 0; bool in_native = (decorators & IN_NATIVE) != 0; + bool is_not_null = (decorators & IS_NOT_NULL) != 0; + switch (type) { case T_OBJECT: case T_ARRAY: { - val = val == noreg ? zr : val; if (in_heap) { - if (UseCompressedOops) { - assert(!dst.uses(val), "not enough registers"); - if (val != zr) { - __ encode_heap_oop(val); + if (val == noreg) { + assert(!is_not_null, "inconsistent access"); + if (UseCompressedOops) { + __ strw(zr, dst); + } else { + __ str(zr, dst); } - __ strw(val, dst); } else { - __ str(val, dst); + if (UseCompressedOops) { + assert(!dst.uses(val), "not enough registers"); + if (is_not_null) { + __ encode_heap_oop_not_null(val); + } else { + __ encode_heap_oop(val); + } + __ strw(val, dst); + } else { + __ str(val, dst); + } } } else { assert(in_native, "why else?"); + assert(val != noreg, "not supported"); __ str(val, dst); } break; @@ -122,6 +137,19 @@ void BarrierSetAssembler::store_at(MacroAssembler* masm, DecoratorSet decorators } } +void BarrierSetAssembler::flat_field_copy(MacroAssembler* masm, DecoratorSet decorators, + Register src, Register dst, Register inline_layout_info) { + // flat_field_copy implementation is fairly complex, and there are not any + // "short-cuts" to be made from asm. What there is, appears to have the same + // cost in C++, so just "call_VM_leaf" for now rather than maintain hundreds + // of hand-rolled instructions... + if (decorators & IS_DEST_UNINITIALIZED) { + __ call_VM_leaf(CAST_FROM_FN_PTR(address, BarrierSetRuntime::value_copy_is_dest_uninitialized), src, dst, inline_layout_info); + } else { + __ call_VM_leaf(CAST_FROM_FN_PTR(address, BarrierSetRuntime::value_copy), src, dst, inline_layout_info); + } +} + void BarrierSetAssembler::copy_load_at(MacroAssembler* masm, DecoratorSet decorators, BasicType type, diff --git a/src/hotspot/cpu/aarch64/gc/shared/barrierSetAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/gc/shared/barrierSetAssembler_aarch64.hpp index 0d6bfc98a72..a6982aace14 100644 --- a/src/hotspot/cpu/aarch64/gc/shared/barrierSetAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/gc/shared/barrierSetAssembler_aarch64.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -100,6 +100,9 @@ class BarrierSetAssembler: public CHeapObj { virtual void store_at(MacroAssembler* masm, DecoratorSet decorators, BasicType type, Address dst, Register val, Register tmp1, Register tmp2, Register tmp3); + virtual void flat_field_copy(MacroAssembler* masm, DecoratorSet decorators, + Register src, Register dst, Register inline_layout_info); + virtual void try_resolve_jobject_in_native(MacroAssembler* masm, Register jni_env, Register obj, Register tmp, Label& slowpath); diff --git a/src/hotspot/cpu/aarch64/gc/shared/barrierSetNMethod_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/shared/barrierSetNMethod_aarch64.cpp index c45611c882b..e5bfff75de9 100644 --- a/src/hotspot/cpu/aarch64/gc/shared/barrierSetNMethod_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/gc/shared/barrierSetNMethod_aarch64.cpp @@ -70,6 +70,7 @@ class NativeNMethodBarrier { int* _guard_addr; nmethod* _nm; +public: address instruction_address() const { return _instruction_address; } int *guard_addr() { @@ -81,10 +82,10 @@ class NativeNMethodBarrier { return (-entry_barrier_offset(nm)) - 4; } -public: - NativeNMethodBarrier(nmethod* nm): _nm(nm) { + NativeNMethodBarrier(nmethod* nm, address alt_entry_instruction_address = 0): _nm(nm) { #if INCLUDE_JVMCI if (nm->is_compiled_by_jvmci()) { + assert(alt_entry_instruction_address == 0, "invariant"); address pc = nm->code_begin() + nm->jvmci_nmethod_data()->nmethod_entry_patch_offset(); RelocIterator iter(nm, pc, pc + 4); guarantee(iter.next(), "missing relocs"); @@ -95,7 +96,8 @@ class NativeNMethodBarrier { } else #endif { - _instruction_address = nm->code_begin() + nm->frame_complete_offset() + entry_barrier_offset(nm); + _instruction_address = (alt_entry_instruction_address != 0) ? alt_entry_instruction_address : + nm->code_begin() + nm->frame_complete_offset() + entry_barrier_offset(nm); if (nm->is_compiled_by_c2()) { // With c2 compiled code, the guard is out-of-line in a stub // We find it using the RelocIterator. @@ -181,6 +183,31 @@ void BarrierSetNMethod::deoptimize(nmethod* nm, address* return_address_ptr) { new_frame->pc = SharedRuntime::get_handle_wrong_method_stub(); } +static void set_value(nmethod* nm, jint val) { + NativeNMethodBarrier cmp1 = NativeNMethodBarrier(nm); + cmp1.set_value(val); + + if (!nm->is_osr_method() && nm->method()->has_scalarized_args()) { + // nmethods with scalarized arguments have multiple entry points that each have an own nmethod entry barrier + assert(nm->verified_entry_point() != nm->verified_inline_entry_point(), "scalarized entry point not found"); + address method_body = nm->is_compiled_by_c1() ? nm->verified_inline_entry_point() : nm->verified_entry_point(); + address entry_point2 = nm->is_compiled_by_c1() ? nm->verified_entry_point() : nm->verified_inline_entry_point(); + + int barrier_offset = cmp1.instruction_address() - method_body; + NativeNMethodBarrier cmp2 = NativeNMethodBarrier(nm, entry_point2 + barrier_offset); + assert(cmp1.instruction_address() != cmp2.instruction_address(), "sanity"); + DEBUG_ONLY(cmp2.verify()); + cmp2.set_value(val); + + if (method_body != nm->verified_inline_ro_entry_point() && entry_point2 != nm->verified_inline_ro_entry_point()) { + NativeNMethodBarrier cmp3 = NativeNMethodBarrier(nm, nm->verified_inline_ro_entry_point() + barrier_offset); + assert(cmp1.instruction_address() != cmp3.instruction_address() && cmp2.instruction_address() != cmp3.instruction_address(), "sanity"); + DEBUG_ONLY(cmp3.verify()); + cmp3.set_value(val); + } + } +} + void BarrierSetNMethod::set_guard_value(nmethod* nm, int value) { if (!supports_entry_barrier(nm)) { return; @@ -197,8 +224,7 @@ void BarrierSetNMethod::set_guard_value(nmethod* nm, int value) { bs_asm->increment_patching_epoch(); } - NativeNMethodBarrier barrier(nm); - barrier.set_value(value); + set_value(nm, value); } int BarrierSetNMethod::guard_value(nmethod* nm) { diff --git a/src/hotspot/cpu/aarch64/gc/shared/cardTableBarrierSetAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/shared/cardTableBarrierSetAssembler_aarch64.cpp index ea36183c9de..7acb09be464 100644 --- a/src/hotspot/cpu/aarch64/gc/shared/cardTableBarrierSetAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/gc/shared/cardTableBarrierSetAssembler_aarch64.cpp @@ -84,11 +84,20 @@ void CardTableBarrierSetAssembler::oop_store_at(MacroAssembler* masm, DecoratorS bool precise = is_array || on_anonymous; bool needs_post_barrier = val != noreg && in_heap; + BarrierSetAssembler::store_at(masm, decorators, type, dst, val, noreg, noreg, noreg); if (needs_post_barrier) { // flatten object address if needed if (!precise || (dst.index() == noreg && dst.offset() == 0)) { - store_check(masm, dst.base(), dst); + if (tmp3 != noreg) { + // TODO 8366717 This change is from before the 'tmp3' arg was added to mainline, check if it's still needed. Same on x64. Also, this should be a __ lea + // Called by MacroAssembler::pack_inline_helper. We cannot corrupt the dst.base() register + __ mov(tmp3, dst.base()); + store_check(masm, tmp3, dst); + } else { + // It's OK to corrupt the dst.base() register. + store_check(masm, dst.base(), dst); + } } else { __ lea(tmp3, dst); store_check(masm, tmp3, dst); diff --git a/src/hotspot/cpu/aarch64/globals_aarch64.hpp b/src/hotspot/cpu/aarch64/globals_aarch64.hpp index 8e520314c8b..af0ccfc2442 100644 --- a/src/hotspot/cpu/aarch64/globals_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/globals_aarch64.hpp @@ -67,6 +67,9 @@ define_pd_global(bool, RewriteFrequentPairs, true); define_pd_global(bool, PreserveFramePointer, false); +define_pd_global(bool, InlineTypePassFieldsAsArgs, true); +define_pd_global(bool, InlineTypeReturnedAsFields, true); + define_pd_global(uintx, TypeProfileLevel, 111); define_pd_global(bool, CompactStrings, true); diff --git a/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp b/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp index 607912e6e49..fdb4467a667 100644 --- a/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp @@ -32,9 +32,11 @@ #include "interpreter/interpreterRuntime.hpp" #include "logging/log.hpp" #include "oops/arrayOop.hpp" +#include "oops/constMethodFlags.hpp" #include "oops/markWord.hpp" #include "oops/method.hpp" #include "oops/methodData.hpp" +#include "oops/inlineKlass.hpp" #include "oops/resolvedFieldEntry.hpp" #include "oops/resolvedIndyEntry.hpp" #include "oops/resolvedMethodEntry.hpp" @@ -208,6 +210,92 @@ void InterpreterMacroAssembler::get_method_counters(Register method, bind(has_counters); } +void InterpreterMacroAssembler::allocate_instance(Register klass, Register new_obj, + Register t1, Register t2, + bool clear_fields, Label& alloc_failed) { + MacroAssembler::allocate_instance(klass, new_obj, t1, t2, clear_fields, alloc_failed); + if (DTraceAllocProbes) { + // Trigger dtrace event for fastpath + push(atos); + call_VM_leaf(CAST_FROM_FN_PTR(address, static_cast(SharedRuntime::dtrace_object_alloc)), new_obj); + pop(atos); + } +} + +void InterpreterMacroAssembler::read_flat_field(Register entry, + Register field_index, Register field_offset, + Register temp, Register obj) { + Label failed_alloc, slow_path, done; + const Register src = field_offset; + const Register alloc_temp = r10; + const Register dst_temp = field_index; + const Register layout_info = temp; + assert_different_registers(obj, entry, field_index, field_offset, temp, alloc_temp, rscratch1); + + load_unsigned_byte(temp, Address(entry, in_bytes(ResolvedFieldEntry::flags_offset()))); + // If the field is nullable, jump to slow path + tbz(temp, ResolvedFieldEntry::is_null_free_inline_type_shift, slow_path); + + // Grab the inline field klass + ldr(rscratch1, Address(entry, in_bytes(ResolvedFieldEntry::field_holder_offset()))); + inline_layout_info(rscratch1, field_index, layout_info); + + const Register field_klass = dst_temp; + ldr(field_klass, Address(layout_info, in_bytes(InlineLayoutInfo::klass_offset()))); + + // allocate buffer + push(obj); // save holder + allocate_instance(field_klass, obj, alloc_temp, rscratch2, false, failed_alloc); + + // Have an oop instance buffer, copy into it + payload_address(obj, dst_temp, field_klass); // danger, uses rscratch1 + pop(alloc_temp); // restore holder + lea(src, Address(alloc_temp, field_offset)); + // call_VM_leaf, clobbers a few regs, save restore new obj + push(obj); + flat_field_copy(IS_DEST_UNINITIALIZED, src, dst_temp, layout_info); + pop(obj); + b(done); + + bind(failed_alloc); + pop(obj); + bind(slow_path); + call_VM(obj, CAST_FROM_FN_PTR(address, InterpreterRuntime::read_flat_field), + obj, entry); + + bind(done); + membar(Assembler::StoreStore); +} + +void InterpreterMacroAssembler::write_flat_field(Register entry, Register field_offset, + Register tmp1, Register tmp2, + Register obj) { + assert_different_registers(entry, field_offset, tmp1, tmp2, obj); + Label slow_path, done; + + load_unsigned_byte(tmp1, Address(entry, in_bytes(ResolvedFieldEntry::flags_offset()))); + test_field_is_not_null_free_inline_type(tmp1, noreg /* temp */, slow_path); + + null_check(r0); // FIXME JDK-8341120 + + add(obj, obj, field_offset); + + load_klass(tmp1, r0); + payload_address(r0, r0, tmp1); + + Register layout_info = field_offset; + load_unsigned_short(tmp1, Address(entry, in_bytes(ResolvedFieldEntry::field_index_offset()))); + ldr(tmp2, Address(entry, in_bytes(ResolvedFieldEntry::field_holder_offset()))); + inline_layout_info(tmp2, tmp1, layout_info); + + flat_field_copy(IN_HEAP, r0, obj, layout_info); + b(done); + + bind(slow_path); + call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::write_flat_field), obj, r0, entry); + bind(done); +} + // Load object from cpool->resolved_references(index) void InterpreterMacroAssembler::load_resolved_reference_at_index( Register result, Register index, Register tmp) { @@ -242,13 +330,16 @@ void InterpreterMacroAssembler::load_resolved_klass_at_offset( // Kills: // r2, r5 void InterpreterMacroAssembler::gen_subtype_check(Register Rsub_klass, - Label& ok_is_subtype) { + Label& ok_is_subtype, + bool profile) { assert(Rsub_klass != r0, "r0 holds superklass"); assert(Rsub_klass != r2, "r2 holds 2ndary super array length"); assert(Rsub_klass != r5, "r5 holds 2ndary super array scan ptr"); // Profile the not-null value's klass. - profile_typecheck(r2, Rsub_klass, r5); // blows r2, reloads r5 + if (profile) { + profile_typecheck(r2, Rsub_klass, r5); // blows r2, reloads r5 + } // Do the check. check_klass_subtype(Rsub_klass, r0, r2, ok_is_subtype); // blows r2 @@ -624,6 +715,7 @@ void InterpreterMacroAssembler::remove_activation(TosState state, // get sender esp ldr(rscratch2, Address(rfp, frame::interpreter_frame_sender_sp_offset * wordSize)); + if (StackReservedPages > 0) { // testing if reserved zone needs to be re-enabled Label no_reserved_zone_enabling; @@ -651,6 +743,50 @@ void InterpreterMacroAssembler::remove_activation(TosState state, bind(no_reserved_zone_enabling); } + if (state == atos && InlineTypeReturnedAsFields) { + Label skip; + Label not_null; + cbnz(r0, not_null); + // Returned value is null, zero all return registers because they may belong to oop fields + mov(j_rarg1, zr); + mov(j_rarg2, zr); + mov(j_rarg3, zr); + mov(j_rarg4, zr); + mov(j_rarg5, zr); + mov(j_rarg6, zr); + mov(j_rarg7, zr); + b(skip); + bind(not_null); + + // Check if we are returning an non-null inline type and load its fields into registers + test_oop_is_not_inline_type(r0, rscratch2, skip, /* can_be_null= */ false); + + // Load fields from a buffered value with an inline class specific handler + load_klass(rscratch1 /*dst*/, r0 /*src*/); + ldr(rscratch1, Address(rscratch1, InstanceKlass::adr_inlineklass_fixed_block_offset())); + ldr(rscratch1, Address(rscratch1, InlineKlass::unpack_handler_offset())); + // Unpack handler can be null if inline type is not scalarizable in returns + cbz(rscratch1, skip); + + blr(rscratch1); +#ifdef ASSERT + // TODO 8284443 Enable + if (StressCallingConvention && false) { + Label skip_stress; + ldr(rscratch1, Address(rfp, frame::interpreter_frame_method_offset * wordSize)); + ldrw(rscratch1, Address(rscratch1, Method::flags_offset())); + tstw(rscratch1, MethodFlags::has_scalarized_return_flag()); + br(Assembler::EQ, skip_stress); + load_klass(r0, r0); + orr(r0, r0, 1); + bind(skip_stress); + } +#endif + bind(skip); + // Check above kills sender esp in rscratch2. Reload it. + ldr(rscratch2, Address(rfp, frame::interpreter_frame_sender_sp_offset * wordSize)); + } + // remove frame anchor leave(); @@ -928,7 +1064,7 @@ void InterpreterMacroAssembler::profile_taken_branch(Register mdp) { } -void InterpreterMacroAssembler::profile_not_taken_branch(Register mdp) { +void InterpreterMacroAssembler::profile_not_taken_branch(Register mdp, bool acmp) { if (ProfileInterpreter) { Label profile_continue; @@ -940,7 +1076,7 @@ void InterpreterMacroAssembler::profile_not_taken_branch(Register mdp) { // The method data pointer needs to be updated to correspond to // the next bytecode - update_mdp_by_constant(mdp, in_bytes(BranchData::branch_data_size())); + update_mdp_by_constant(mdp, acmp ? in_bytes(ACmpData::acmp_data_size()) : in_bytes(BranchData::branch_data_size())); bind(profile_continue); } } @@ -1263,6 +1399,120 @@ void InterpreterMacroAssembler::profile_switch_case(Register index, } } +template void InterpreterMacroAssembler::profile_array_type(Register mdp, + Register array, + Register tmp) { + if (ProfileInterpreter) { + Label profile_continue; + + // If no method data exists, go to profile_continue. + test_method_data_pointer(mdp, profile_continue); + + mov(tmp, array); + profile_obj_type(tmp, Address(mdp, in_bytes(ArrayData::array_offset()))); + + Label not_flat; + test_non_flat_array_oop(array, tmp, not_flat); + + set_mdp_flag_at(mdp, ArrayData::flat_array_byte_constant()); + + bind(not_flat); + + Label not_null_free; + test_non_null_free_array_oop(array, tmp, not_null_free); + + set_mdp_flag_at(mdp, ArrayData::null_free_array_byte_constant()); + + bind(not_null_free); + + bind(profile_continue); + } +} + +template void InterpreterMacroAssembler::profile_array_type(Register mdp, + Register array, + Register tmp); +template void InterpreterMacroAssembler::profile_array_type(Register mdp, + Register array, + Register tmp); + +void InterpreterMacroAssembler::profile_multiple_element_types(Register mdp, Register element, Register tmp, const Register tmp2) { + if (ProfileInterpreter) { + Label profile_continue; + + // If no method data exists, go to profile_continue. + test_method_data_pointer(mdp, profile_continue); + + Label done, update; + cbnz(element, update); + set_mdp_flag_at(mdp, BitData::null_seen_byte_constant()); + b(done); + + bind(update); + load_klass(tmp, element); + + // Record the object type. + record_klass_in_profile(tmp, mdp, tmp2); + + bind(done); + + // The method data pointer needs to be updated. + update_mdp_by_constant(mdp, in_bytes(ArrayStoreData::array_store_data_size())); + + bind(profile_continue); + } +} + + +void InterpreterMacroAssembler::profile_element_type(Register mdp, + Register element, + Register tmp) { + if (ProfileInterpreter) { + Label profile_continue; + + // If no method data exists, go to profile_continue. + test_method_data_pointer(mdp, profile_continue); + + mov(tmp, element); + profile_obj_type(tmp, Address(mdp, in_bytes(ArrayLoadData::element_offset()))); + + // The method data pointer needs to be updated. + update_mdp_by_constant(mdp, in_bytes(ArrayLoadData::array_load_data_size())); + + bind(profile_continue); + } +} + +void InterpreterMacroAssembler::profile_acmp(Register mdp, + Register left, + Register right, + Register tmp) { + if (ProfileInterpreter) { + Label profile_continue; + + // If no method data exists, go to profile_continue. + test_method_data_pointer(mdp, profile_continue); + + mov(tmp, left); + profile_obj_type(tmp, Address(mdp, in_bytes(ACmpData::left_offset()))); + + Label left_not_inline_type; + test_oop_is_not_inline_type(left, tmp, left_not_inline_type); + set_mdp_flag_at(mdp, ACmpData::left_inline_type_byte_constant()); + bind(left_not_inline_type); + + mov(tmp, right); + profile_obj_type(tmp, Address(mdp, in_bytes(ACmpData::right_offset()))); + + Label right_not_inline_type; + test_oop_is_not_inline_type(right, tmp, right_not_inline_type); + set_mdp_flag_at(mdp, ACmpData::right_inline_type_byte_constant()); + bind(right_not_inline_type); + + bind(profile_continue); + } +} + void InterpreterMacroAssembler::_interp_verify_oop(Register reg, TosState state, const char* file, int line) { if (state == atos) { MacroAssembler::_verify_oop_checked(reg, "broken oop", file, line); @@ -1561,7 +1811,7 @@ void InterpreterMacroAssembler::profile_arguments_type(Register mdp, Register ca // argument. tmp is the number of cells left in the // CallTypeData/VirtualCallTypeData to reach its end. Non null // if there's a return to profile. - assert(ReturnTypeEntry::static_cell_count() < TypeStackSlotEntries::per_arg_count(), "can't move past ret type"); + assert(SingleTypeEntry::static_cell_count() < TypeStackSlotEntries::per_arg_count(), "can't move past ret type"); add(mdp, mdp, tmp, LSL, exact_log2(DataLayout::cell_size)); } str(mdp, Address(rfp, frame::interpreter_frame_mdp_offset * wordSize)); @@ -1607,7 +1857,7 @@ void InterpreterMacroAssembler::profile_return_type(Register mdp, Register ret, bind(do_profile); } - Address mdo_ret_addr(mdp, -in_bytes(ReturnTypeEntry::size())); + Address mdo_ret_addr(mdp, -in_bytes(SingleTypeEntry::size())); mov(tmp, ret); profile_obj_type(tmp, mdo_ret_addr); diff --git a/src/hotspot/cpu/aarch64/interp_masm_aarch64.hpp b/src/hotspot/cpu/aarch64/interp_masm_aarch64.hpp index e896a2a9430..05d5e516dc6 100644 --- a/src/hotspot/cpu/aarch64/interp_masm_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/interp_masm_aarch64.hpp @@ -144,6 +144,32 @@ class InterpreterMacroAssembler: public MacroAssembler { void get_cache_index_at_bcp(Register index, int bcp_offset, size_t index_size = sizeof(u2)); void get_method_counters(Register method, Register mcs, Label& skip); + // Kills t1 and t2, perserves klass, return allocation in new_obj + void allocate_instance(Register klass, Register new_obj, + Register t1, Register t2, + bool clear_fields, Label& alloc_failed); + + // Allocate instance in "obj" and read in the content of the inline field + // NOTES: + // - input holder object via "obj", which must be r0, + // will return new instance via the same reg + // - assumes holder_klass and valueKlass field klass have both been resolved + void read_flat_field(Register entry, + Register field_index, Register field_offset, + Register temp, Register obj); + + void write_flat_field(Register entry, Register field_offset, + Register tmp1, Register tmp2, + Register obj); + + // Allocate value buffer in "obj" and read in flat element at the given index + // NOTES: + // - Return via "obj" must be r0 + // - kills all given regs + void read_flat_element(Register array, Register index, + Register t1, Register t2, + Register obj = r0); + // load cpool->resolved_references(index); void load_resolved_reference_at_index(Register result, Register index, Register tmp = r5); @@ -188,7 +214,7 @@ class InterpreterMacroAssembler: public MacroAssembler { // Generate a subtype check: branch to ok_is_subtype if sub_klass is // a subtype of super_klass. - void gen_subtype_check( Register sub_klass, Label &ok_is_subtype ); + void gen_subtype_check( Register sub_klass, Label &ok_is_subtype, bool profile = true); // Dispatching void dispatch_prolog(TosState state, int step = 0); @@ -277,7 +303,7 @@ class InterpreterMacroAssembler: public MacroAssembler { void narrow(Register result); void profile_taken_branch(Register mdp); - void profile_not_taken_branch(Register mdp); + void profile_not_taken_branch(Register mdp, bool acmp = false); void profile_call(Register mdp); void profile_final_call(Register mdp); void profile_virtual_call(Register receiver, Register mdp, @@ -290,6 +316,10 @@ class InterpreterMacroAssembler: public MacroAssembler { void profile_switch_default(Register mdp); void profile_switch_case(Register index_in_scratch, Register mdp, Register scratch2); + template void profile_array_type(Register mdp, Register array, Register tmp); + void profile_multiple_element_types(Register mdp, Register element, Register tmp, Register tmp2); + void profile_element_type(Register mdp, Register element, Register tmp); + void profile_acmp(Register mdp, Register left, Register right, Register tmp); void profile_obj_type(Register obj, const Address& mdo_addr); void profile_arguments_type(Register mdp, Register callee, Register tmp, bool is_virtual); diff --git a/src/hotspot/cpu/aarch64/interpreterRT_aarch64.cpp b/src/hotspot/cpu/aarch64/interpreterRT_aarch64.cpp index d6310a2d326..5369f211381 100644 --- a/src/hotspot/cpu/aarch64/interpreterRT_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/interpreterRT_aarch64.cpp @@ -173,6 +173,10 @@ void InterpreterRuntime::SignatureHandlerGenerator::pass_object() { } } +void InterpreterRuntime::SignatureHandlerGenerator::pass_valuetype() { + pass_object(); +} + void InterpreterRuntime::SignatureHandlerGenerator::generate(uint64_t fingerprint) { // generate code to handle arguments iterate(fingerprint); @@ -257,6 +261,11 @@ class SlowSignatureHandler } } + virtual void pass_valuetype() { + // values are handled with oops, like objects + pass_object(); + } + virtual void pass_long() { intptr_t value = *double_slot_addr(); if (pass_gpr(value) < 0) { diff --git a/src/hotspot/cpu/aarch64/interpreterRT_aarch64.hpp b/src/hotspot/cpu/aarch64/interpreterRT_aarch64.hpp index 8f84ebbe114..e08291c5db6 100644 --- a/src/hotspot/cpu/aarch64/interpreterRT_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/interpreterRT_aarch64.hpp @@ -46,6 +46,7 @@ class SignatureHandlerGenerator: public NativeSignatureIterator { void pass_float(); void pass_double(); void pass_object(); + void pass_valuetype(); Register next_gpr(); FloatRegister next_fpr(); diff --git a/src/hotspot/cpu/aarch64/jniFastGetField_aarch64.cpp b/src/hotspot/cpu/aarch64/jniFastGetField_aarch64.cpp index 8bec45b4b47..cffdcf49429 100644 --- a/src/hotspot/cpu/aarch64/jniFastGetField_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/jniFastGetField_aarch64.cpp @@ -30,6 +30,7 @@ #include "prims/jniFastGetField.hpp" #include "prims/jvm_misc.hpp" #include "prims/jvmtiExport.hpp" +#include "runtime/jfieldIDWorkaround.hpp" #include "runtime/javaThread.inline.hpp" #include "runtime/safepoint.hpp" #include "runtime/threadWXSetters.inline.hpp" @@ -152,7 +153,7 @@ address JNI_FastGetField::generate_fast_get_int_field0(BasicType type) { BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); bs->try_resolve_jobject_in_native(masm, c_rarg0, robj, rscratch1, slow); - __ lsr(roffset, c_rarg2, 2); // offset + __ lsr(roffset, c_rarg2, jfieldIDWorkaround::offset_shift); // offset __ add(result, robj, roffset); assert(count < LIST_CAPACITY, "LIST_CAPACITY too small"); diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp index 3999beeec2b..394b5329a2c 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp @@ -26,6 +26,7 @@ #include "asm/assembler.hpp" #include "asm/assembler.inline.hpp" #include "ci/ciEnv.hpp" +#include "ci/ciInlineKlass.hpp" #include "code/compiledIC.hpp" #include "compiler/compileTask.hpp" #include "compiler/disassembler.hpp" @@ -47,15 +48,19 @@ #include "oops/compressedKlass.inline.hpp" #include "oops/compressedOops.inline.hpp" #include "oops/klass.inline.hpp" +#include "oops/resolvedFieldEntry.hpp" #include "runtime/continuation.hpp" +#include "runtime/globals.hpp" #include "runtime/icache.hpp" #include "runtime/interfaceSupport.inline.hpp" #include "runtime/javaThread.hpp" #include "runtime/jniHandles.inline.hpp" #include "runtime/sharedRuntime.hpp" +#include "runtime/signature_cc.hpp" #include "runtime/stubRoutines.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/powerOfTwo.hpp" +#include "vmreg_aarch64.inline.hpp" #ifdef COMPILER1 #include "c1/c1_LIRAssembler.hpp" #endif @@ -2066,7 +2071,11 @@ void MacroAssembler::clinit_barrier(Register klass, Register scratch, Label* L_f } void MacroAssembler::_verify_oop(Register reg, const char* s, const char* file, int line) { - if (!VerifyOops) return; + if (!VerifyOops || VerifyAdapterSharing) { + // Below address of the code string confuses VerifyAdapterSharing + // because it may differ between otherwise equivalent adapters. + return; + } // Pass register number to verify_oop_subroutine const char* b = nullptr; @@ -2099,7 +2108,11 @@ void MacroAssembler::_verify_oop(Register reg, const char* s, const char* file, } void MacroAssembler::_verify_oop_addr(Address addr, const char* s, const char* file, int line) { - if (!VerifyOops) return; + if (!VerifyOops || VerifyAdapterSharing) { + // Below address of the code string confuses VerifyAdapterSharing + // because it may differ between otherwise equivalent adapters. + return; + } const char* b = nullptr; { @@ -2197,6 +2210,10 @@ void MacroAssembler::call_VM_leaf(address entry_point, Register arg_0, call_VM_leaf_base(entry_point, 3); } +void MacroAssembler::super_call_VM_leaf(address entry_point) { + MacroAssembler::call_VM_leaf_base(entry_point, 1); +} + void MacroAssembler::super_call_VM_leaf(address entry_point, Register arg_0) { pass_arg0(this, arg_0); MacroAssembler::call_VM_leaf_base(entry_point, 1); @@ -2242,6 +2259,94 @@ void MacroAssembler::null_check(Register reg, int offset) { } } +void MacroAssembler::test_markword_is_inline_type(Register markword, Label& is_inline_type) { + assert_different_registers(markword, rscratch2); + mov(rscratch2, markWord::inline_type_mask_in_place); + andr(markword, markword, rscratch2); + mov(rscratch2, markWord::inline_type_pattern); + cmp(markword, rscratch2); + br(Assembler::EQ, is_inline_type); +} + +void MacroAssembler::test_oop_is_not_inline_type(Register object, Register tmp, Label& not_inline_type, bool can_be_null) { + assert_different_registers(tmp, rscratch1); + if (can_be_null) { + cbz(object, not_inline_type); + } + const int is_inline_type_mask = markWord::inline_type_pattern; + ldr(tmp, Address(object, oopDesc::mark_offset_in_bytes())); + mov(rscratch1, is_inline_type_mask); + andr(tmp, tmp, rscratch1); + cmp(tmp, rscratch1); + br(Assembler::NE, not_inline_type); +} + +void MacroAssembler::test_field_is_null_free_inline_type(Register flags, Register temp_reg, Label& is_null_free_inline_type) { + assert(temp_reg == noreg, "not needed"); // keep signature uniform with x86 + tbnz(flags, ResolvedFieldEntry::is_null_free_inline_type_shift, is_null_free_inline_type); +} + +void MacroAssembler::test_field_is_not_null_free_inline_type(Register flags, Register temp_reg, Label& not_null_free_inline_type) { + assert(temp_reg == noreg, "not needed"); // keep signature uniform with x86 + tbz(flags, ResolvedFieldEntry::is_null_free_inline_type_shift, not_null_free_inline_type); +} + +void MacroAssembler::test_field_is_flat(Register flags, Register temp_reg, Label& is_flat) { + assert(temp_reg == noreg, "not needed"); // keep signature uniform with x86 + tbnz(flags, ResolvedFieldEntry::is_flat_shift, is_flat); +} + +void MacroAssembler::test_field_has_null_marker(Register flags, Register temp_reg, Label& has_null_marker) { + assert(temp_reg == noreg, "not needed"); // keep signature uniform with x86 + tbnz(flags, ResolvedFieldEntry::has_null_marker_shift, has_null_marker); +} + +void MacroAssembler::test_oop_prototype_bit(Register oop, Register temp_reg, int32_t test_bit, bool jmp_set, Label& jmp_label) { + Label test_mark_word; + // load mark word + ldr(temp_reg, Address(oop, oopDesc::mark_offset_in_bytes())); + // check displaced + tst(temp_reg, markWord::unlocked_value); + br(Assembler::NE, test_mark_word); + // slow path use klass prototype + load_prototype_header(temp_reg, oop); + + bind(test_mark_word); + andr(temp_reg, temp_reg, test_bit); + if (jmp_set) { + cbnz(temp_reg, jmp_label); + } else { + cbz(temp_reg, jmp_label); + } +} + +void MacroAssembler::test_flat_array_oop(Register oop, Register temp_reg, Label& is_flat_array) { + test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, true, is_flat_array); +} + +void MacroAssembler::test_non_flat_array_oop(Register oop, Register temp_reg, + Label&is_non_flat_array) { + test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, false, is_non_flat_array); +} + +void MacroAssembler::test_null_free_array_oop(Register oop, Register temp_reg, Label& is_null_free_array) { + test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, true, is_null_free_array); +} + +void MacroAssembler::test_non_null_free_array_oop(Register oop, Register temp_reg, Label&is_non_null_free_array) { + test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, false, is_non_null_free_array); +} + +void MacroAssembler::test_flat_array_layout(Register lh, Label& is_flat_array) { + tst(lh, Klass::_lh_array_tag_flat_value_bit_inplace); + br(Assembler::NE, is_flat_array); +} + +void MacroAssembler::test_non_flat_array_layout(Register lh, Label& is_non_flat_array) { + tst(lh, Klass::_lh_array_tag_flat_value_bit_inplace); + br(Assembler::EQ, is_non_flat_array); +} + // MacroAssembler protected routines needed to implement // public methods @@ -5033,6 +5138,16 @@ void MacroAssembler::load_method_holder(Register holder, Register method) { ldr(holder, Address(holder, ConstantPool::pool_holder_offset())); // InstanceKlass* } +void MacroAssembler::load_metadata(Register dst, Register src) { + if (UseCompactObjectHeaders) { + load_narrow_klass_compact(dst, src); + } else if (UseCompressedClassPointers) { + ldrw(dst, Address(src, oopDesc::klass_offset_in_bytes())); + } else { + ldr(dst, Address(src, oopDesc::klass_offset_in_bytes())); + } +} + // Loads the obj's Klass* into dst. // Preserves all registers (incl src, rscratch1 and rscratch2). // Input: @@ -5143,6 +5258,11 @@ void MacroAssembler::cmp_klasses_from_objects(Register obj1, Register obj2, Regi } } +void MacroAssembler::load_prototype_header(Register dst, Register src) { + load_klass(dst, src); + ldr(dst, Address(dst, Klass::prototype_header_offset())); +} + void MacroAssembler::store_klass(Register dst, Register src) { // FIXME: Should this be a store release? concurrent gcs assumes // klass length is valid if klass field is not null. @@ -5544,6 +5664,46 @@ void MacroAssembler::access_store_at(BasicType type, DecoratorSet decorators, } } +void MacroAssembler::flat_field_copy(DecoratorSet decorators, Register src, Register dst, + Register inline_layout_info) { + BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); + bs->flat_field_copy(this, decorators, src, dst, inline_layout_info); +} + +void MacroAssembler::payload_offset(Register inline_klass, Register offset) { + ldr(offset, Address(inline_klass, InstanceKlass::adr_inlineklass_fixed_block_offset())); + ldrw(offset, Address(offset, InlineKlass::payload_offset_offset())); +} + +void MacroAssembler::payload_address(Register oop, Register data, Register inline_klass) { + // ((address) (void*) o) + vk->payload_offset(); + Register offset = (data == oop) ? rscratch1 : data; + payload_offset(inline_klass, offset); + if (data == oop) { + add(data, data, offset); + } else { + lea(data, Address(oop, offset)); + } +} + +void MacroAssembler::data_for_value_array_index(Register array, Register array_klass, + Register index, Register data) { + assert_different_registers(array, array_klass, index); + assert_different_registers(rscratch1, array, index); + + // array->base() + (index << Klass::layout_helper_log2_element_size(lh)); + ldrw(rscratch1, Address(array_klass, Klass::layout_helper_offset())); + + // Klass::layout_helper_log2_element_size(lh) + // (lh >> _lh_log2_element_size_shift) & _lh_log2_element_size_mask; + lsr(rscratch1, rscratch1, Klass::_lh_log2_element_size_shift); + andr(rscratch1, rscratch1, Klass::_lh_log2_element_size_mask); + lslv(index, index, rscratch1); + + add(data, array, index); + add(data, data, arrayOopDesc::base_offset_in_bytes(T_FLAT_ELEMENT)); +} + void MacroAssembler::load_heap_oop(Register dst, Address src, Register tmp1, Register tmp2, DecoratorSet decorators) { access_load_at(T_OBJECT, IN_HEAP | decorators, dst, src, tmp1, tmp2); @@ -5619,6 +5779,108 @@ Address MacroAssembler::constant_oop_address(jobject obj) { return Address((address)obj, oop_Relocation::spec(oop_index)); } +// Object / value buffer allocation... +void MacroAssembler::allocate_instance(Register klass, Register new_obj, + Register t1, Register t2, + bool clear_fields, Label& alloc_failed) +{ + Label done, initialize_header, initialize_object, slow_case, slow_case_no_pop; + Register layout_size = t1; + assert(new_obj == r0, "needs to be r0"); + assert_different_registers(klass, new_obj, t1, t2); + + // get instance_size in InstanceKlass (scaled to a count of bytes) + ldrw(layout_size, Address(klass, Klass::layout_helper_offset())); + // test to see if it is malformed in some way + tst(layout_size, Klass::_lh_instance_slow_path_bit); + br(Assembler::NE, slow_case_no_pop); + + // Allocate the instance: + // If TLAB is enabled: + // Try to allocate in the TLAB. + // If fails, go to the slow path. + // Initialize the allocation. + // Exit. + // + // Go to slow path. + + if (UseTLAB) { + push(klass); + tlab_allocate(new_obj, layout_size, 0, klass, t2, slow_case); + if (ZeroTLAB || (!clear_fields)) { + // the fields have been already cleared + b(initialize_header); + } else { + // initialize both the header and fields + b(initialize_object); + } + + if (clear_fields) { + // The object is initialized before the header. If the object size is + // zero, go directly to the header initialization. + bind(initialize_object); + int header_size = oopDesc::header_size() * HeapWordSize; + assert(is_aligned(header_size, BytesPerLong), "oop header size must be 8-byte-aligned"); + subs(layout_size, layout_size, header_size); + br(Assembler::EQ, initialize_header); + + // Initialize topmost object field, divide size by 8, check if odd and + // test if zero. + + #ifdef ASSERT + // make sure instance_size was multiple of 8 + Label L; + tst(layout_size, 7); + br(Assembler::EQ, L); + stop("object size is not multiple of 8 - adjust this code"); + bind(L); + // must be > 0, no extra check needed here + #endif + + lsr(layout_size, layout_size, LogBytesPerLong); + + // initialize remaining object fields: instance_size was a multiple of 8 + { + Label loop; + Register base = t2; + + bind(loop); + add(rscratch1, new_obj, layout_size, Assembler::LSL, LogBytesPerLong); + str(zr, Address(rscratch1, header_size - 1*oopSize)); + subs(layout_size, layout_size, 1); + br(Assembler::NE, loop); + } + } // clear_fields + + // initialize object header only. + bind(initialize_header); + pop(klass); + Register mark_word = t2; + if (UseCompactObjectHeaders || EnableValhalla) { + ldr(mark_word, Address(klass, Klass::prototype_header_offset())); + str(mark_word, Address(new_obj, oopDesc::mark_offset_in_bytes())); + } else { + mov(mark_word, (intptr_t)markWord::prototype().value()); + str(mark_word, Address(new_obj, oopDesc::mark_offset_in_bytes())); + } + if (!UseCompactObjectHeaders) { + store_klass_gap(new_obj, zr); // zero klass gap for compressed oops + mov(t2, klass); // preserve klass + store_klass(new_obj, t2); // src klass reg is potentially compressed + } + b(done); + } + + if (UseTLAB) { + bind(slow_case); + pop(klass); + } + bind(slow_case_no_pop); + b(alloc_failed); + + bind(done); +} + // Defines obj, preserves var_size_in_bytes, okay for t2 == var_size_in_bytes. void MacroAssembler::tlab_allocate(Register obj, Register var_size_in_bytes, @@ -5690,6 +5952,26 @@ void MacroAssembler::verify_tlab() { #endif } +void MacroAssembler::get_inline_type_field_klass(Register holder_klass, Register index, Register inline_klass) { + inline_layout_info(holder_klass, index, inline_klass); + ldr(inline_klass, Address(inline_klass, InlineLayoutInfo::klass_offset())); +} + +void MacroAssembler::inline_layout_info(Register holder_klass, Register index, Register layout_info) { + assert_different_registers(holder_klass, index, layout_info); + InlineLayoutInfo array[2]; + int size = (char*)&array[1] - (char*)&array[0]; // computing size of array elements + if (is_power_of_2(size)) { + lsl(index, index, log2i_exact(size)); // Scale index by power of 2 + } else { + mov(layout_info, size); + mul(index, index, layout_info); // Scale the index to be the entry index * array_element_size + } + ldr(layout_info, Address(holder_klass, InstanceKlass::inline_layout_info_array_offset())); + add(layout_info, layout_info, Array::base_offset_in_bytes()); + lea(layout_info, Address(layout_info, index)); +} + // Writes to stack successive pages until offset reached to check for // stack overflow + shadow pages. This clobbers tmp. void MacroAssembler::bang_stack_size(Register size, Register tmp) { @@ -5815,6 +6097,68 @@ void MacroAssembler::remove_frame(int framesize) { authenticate_return_address(); } +void MacroAssembler::remove_frame(int initial_framesize, bool needs_stack_repair) { + if (needs_stack_repair) { + // Remove the extension of the caller's frame used for inline type unpacking + // + // Right now the stack looks like this: + // + // | Arguments from caller | + // |---------------------------| <-- caller's SP + // | Saved LR #1 | + // | Saved FP #1 | + // |---------------------------| + // | Extension space for | + // | inline arg (un)packing | + // |---------------------------| <-- start of this method's frame + // | Saved LR #2 | + // | Saved FP #2 | + // |---------------------------| <-- FP + // | sp_inc | + // | method locals | + // |---------------------------| <-- SP + // + // There are two copies of FP and LR on the stack. They will be identical at + // first, but that can change. + // If the caller has been deoptimized, LR #1 will be patched to point at the + // deopt blob, and LR #2 will still point into the old method. + // If the saved FP (x29) was not used as the frame pointer, but to store an + // oop, the GC will be aware only of FP #2 as the spilled location of x29 and + // will fix only this one. + // + // When restoring, one must then load FP #2 into x29, and LR #1 into x30, + // while keeping in mind that from the scalarized entry point, there will be + // only one copy of each. + // + // The sp_inc stack slot holds the total size of the frame including the + // extension space minus two words for the saved FP and LR. That is how to + // find LR #1. FP #2 is always located just after sp_inc. + + int sp_inc_offset = initial_framesize - 3 * wordSize; // Immediately below saved LR and FP + + ldr(rscratch1, Address(sp, sp_inc_offset)); + ldr(rfp, Address(sp, sp_inc_offset + wordSize)); + add(sp, sp, rscratch1); + ldr(lr, Address(sp, wordSize)); + add(sp, sp, 2 * wordSize); + } else { + remove_frame(initial_framesize); + } +} + +void MacroAssembler::save_stack_increment(int sp_inc, int frame_size) { + int real_frame_size = frame_size + sp_inc; + assert(sp_inc == 0 || sp_inc > 2*wordSize, "invalid sp_inc value"); + assert(real_frame_size >= 2*wordSize, "frame size must include FP/LR space"); + assert((real_frame_size & (StackAlignmentInBytes-1)) == 0, "frame size not aligned"); + + int sp_inc_offset = frame_size - 3 * wordSize; // Immediately below saved LR and FP + + // Subtract two words for the saved FP and LR as these will be popped + // separately. See remove_frame above. + mov(rscratch1, real_frame_size - 2*wordSize); + str(rscratch1, Address(sp, sp_inc_offset)); +} // This method counts leading positive bytes (highest bit not set) in provided byte array address MacroAssembler::count_positives(Register ary1, Register len, Register result) { @@ -6732,6 +7076,454 @@ void MacroAssembler::get_thread(Register dst) { authenticate_return_address(); } +#ifdef COMPILER2 +// C2 compiled method's prolog code +// Moved here from aarch64.ad to support Valhalla code belows +void MacroAssembler::verified_entry(Compile* C, int sp_inc) { + if (C->clinit_barrier_on_entry()) { + assert(!C->method()->holder()->is_not_initialized(), "initialization should have been started"); + + Label L_skip_barrier; + + mov_metadata(rscratch2, C->method()->holder()->constant_encoding()); + clinit_barrier(rscratch2, rscratch1, &L_skip_barrier); + far_jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); + bind(L_skip_barrier); + } + + if (C->max_vector_size() > 0) { + reinitialize_ptrue(); + } + + int bangsize = C->output()->bang_size_in_bytes(); + if (C->output()->need_stack_bang(bangsize)) + generate_stack_overflow_check(bangsize); + + // n.b. frame size includes space for return pc and rfp + const long framesize = C->output()->frame_size_in_bytes(); + build_frame(framesize); + + if (C->needs_stack_repair()) { + save_stack_increment(sp_inc, framesize); + } + + if (VerifyStackAtCalls) { + Unimplemented(); + } +} +#endif // COMPILER2 + +int MacroAssembler::store_inline_type_fields_to_buf(ciInlineKlass* vk, bool from_interpreter) { + assert(InlineTypeReturnedAsFields, "Inline types should never be returned as fields"); + // An inline type might be returned. If fields are in registers we + // need to allocate an inline type instance and initialize it with + // the value of the fields. + Label skip; + // We only need a new buffered inline type if a new one is not returned + tbz(r0, 0, skip); + int call_offset = -1; + + // Be careful not to clobber r1-7 which hold returned fields + // Also do not use callee-saved registers as these may be live in the interpreter + Register tmp1 = r13, tmp2 = r14, klass = r15, r0_preserved = r12; + + // The following code is similar to allocate_instance but has some slight differences, + // e.g. object size is always not zero, sometimes it's constant; storing klass ptr after + // allocating is not necessary if vk != nullptr, etc. allocate_instance is not aware of these. + Label slow_case; + // 1. Try to allocate a new buffered inline instance either from TLAB or eden space + mov(r0_preserved, r0); // save r0 for slow_case since *_allocate may corrupt it when allocation failed + + if (vk != nullptr) { + // Called from C1, where the return type is statically known. + movptr(klass, (intptr_t)vk->get_InlineKlass()); + jint lh = vk->layout_helper(); + assert(lh != Klass::_lh_neutral_value, "inline class in return type must have been resolved"); + if (UseTLAB && !Klass::layout_helper_needs_slow_path(lh)) { + tlab_allocate(r0, noreg, lh, tmp1, tmp2, slow_case); + } else { + b(slow_case); + } + } else { + // Call from interpreter. R0 contains ((the InlineKlass* of the return type) | 0x01) + andr(klass, r0, -2); + if (UseTLAB) { + ldrw(tmp2, Address(klass, Klass::layout_helper_offset())); + tst(tmp2, Klass::_lh_instance_slow_path_bit); + br(Assembler::NE, slow_case); + tlab_allocate(r0, tmp2, 0, tmp1, tmp2, slow_case); + } else { + b(slow_case); + } + } + if (UseTLAB) { + // 2. Initialize buffered inline instance header + Register buffer_obj = r0; + if (UseCompactObjectHeaders) { + ldr(rscratch1, Address(klass, Klass::prototype_header_offset())); + str(rscratch1, Address(buffer_obj, oopDesc::mark_offset_in_bytes())); + } else { + mov(rscratch1, (intptr_t)markWord::inline_type_prototype().value()); + str(rscratch1, Address(buffer_obj, oopDesc::mark_offset_in_bytes())); + store_klass_gap(buffer_obj, zr); + if (vk == nullptr) { + // store_klass corrupts klass, so save it for later use (interpreter case only). + mov(tmp1, klass); + } + store_klass(buffer_obj, klass); + klass = tmp1; + } + // 3. Initialize its fields with an inline class specific handler + if (vk != nullptr) { + far_call(RuntimeAddress(vk->pack_handler())); // no need for call info as this will not safepoint. + } else { + ldr(tmp1, Address(klass, InstanceKlass::adr_inlineklass_fixed_block_offset())); + ldr(tmp1, Address(tmp1, InlineKlass::pack_handler_offset())); + blr(tmp1); + } + + membar(Assembler::StoreStore); + b(skip); + } else { + // Must have already branched to slow_case above. + DEBUG_ONLY(should_not_reach_here()); + } + bind(slow_case); + // We failed to allocate a new inline type, fall back to a runtime + // call. Some oop field may be live in some registers but we can't + // tell. That runtime call will take care of preserving them + // across a GC if there's one. + mov(r0, r0_preserved); + + if (from_interpreter) { + super_call_VM_leaf(StubRoutines::store_inline_type_fields_to_buf()); + } else { + far_call(RuntimeAddress(StubRoutines::store_inline_type_fields_to_buf())); + call_offset = offset(); + } + membar(Assembler::StoreStore); + + bind(skip); + return call_offset; +} + +// Move a value between registers/stack slots and update the reg_state +bool MacroAssembler::move_helper(VMReg from, VMReg to, BasicType bt, RegState reg_state[]) { + assert(from->is_valid() && to->is_valid(), "source and destination must be valid"); + if (reg_state[to->value()] == reg_written) { + return true; // Already written + } + + if (from != to && bt != T_VOID) { + if (reg_state[to->value()] == reg_readonly) { + return false; // Not yet writable + } + if (from->is_reg()) { + if (to->is_reg()) { + if (from->is_Register() && to->is_Register()) { + mov(to->as_Register(), from->as_Register()); + } else if (from->is_FloatRegister() && to->is_FloatRegister()) { + fmovd(to->as_FloatRegister(), from->as_FloatRegister()); + } else { + ShouldNotReachHere(); + } + } else { + int st_off = to->reg2stack() * VMRegImpl::stack_slot_size; + Address to_addr = Address(sp, st_off); + if (from->is_FloatRegister()) { + if (bt == T_DOUBLE) { + strd(from->as_FloatRegister(), to_addr); + } else { + assert(bt == T_FLOAT, "must be float"); + strs(from->as_FloatRegister(), to_addr); + } + } else { + str(from->as_Register(), to_addr); + } + } + } else { + Address from_addr = Address(sp, from->reg2stack() * VMRegImpl::stack_slot_size); + if (to->is_reg()) { + if (to->is_FloatRegister()) { + if (bt == T_DOUBLE) { + ldrd(to->as_FloatRegister(), from_addr); + } else { + assert(bt == T_FLOAT, "must be float"); + ldrs(to->as_FloatRegister(), from_addr); + } + } else { + ldr(to->as_Register(), from_addr); + } + } else { + int st_off = to->reg2stack() * VMRegImpl::stack_slot_size; + ldr(rscratch1, from_addr); + str(rscratch1, Address(sp, st_off)); + } + } + } + + // Update register states + reg_state[from->value()] = reg_writable; + reg_state[to->value()] = reg_written; + return true; +} + +// Calculate the extra stack space required for packing or unpacking inline +// args and adjust the stack pointer +int MacroAssembler::extend_stack_for_inline_args(int args_on_stack) { + int sp_inc = args_on_stack * VMRegImpl::stack_slot_size; + sp_inc = align_up(sp_inc, StackAlignmentInBytes); + assert(sp_inc > 0, "sanity"); + + // Save a copy of the FP and LR here for deoptimization patching and frame walking + stp(rfp, lr, Address(pre(sp, -2 * wordSize))); + + // Adjust the stack pointer. This will be repaired on return by MacroAssembler::remove_frame + if (sp_inc < (1 << 9)) { + sub(sp, sp, sp_inc); // Fits in an immediate + } else { + mov(rscratch1, sp_inc); + sub(sp, sp, rscratch1); + } + + return sp_inc + 2 * wordSize; // Account for the FP/LR space +} + +// Read all fields from an inline type oop and store the values in registers/stack slots +bool MacroAssembler::unpack_inline_helper(const GrowableArray* sig, int& sig_index, + VMReg from, int& from_index, VMRegPair* to, int to_count, int& to_index, + RegState reg_state[]) { + assert(sig->at(sig_index)._bt == T_VOID, "should be at end delimiter"); + assert(from->is_valid(), "source must be valid"); + bool progress = false; +#ifdef ASSERT + const int start_offset = offset(); +#endif + + Label L_null, L_notNull; + // Don't use r14 as tmp because it's used for spilling (see MacroAssembler::spill_reg_for) + // TODO 8366717 We need to make sure that r14 (and potentially other long-life regs) are kept live in slowpath runtime calls in GC barriers + Register tmp1 = r10; + Register tmp2 = r11; + Register fromReg = noreg; + ScalarizedInlineArgsStream stream(sig, sig_index, to, to_count, to_index, -1); + bool done = true; + bool mark_done = true; + VMReg toReg; + BasicType bt; + // Check if argument requires a null check + bool null_check = false; + VMReg nullCheckReg; + while (stream.next(nullCheckReg, bt)) { + if (sig->at(stream.sig_index())._offset == -1) { + null_check = true; + break; + } + } + stream.reset(sig_index, to_index); + while (stream.next(toReg, bt)) { + assert(toReg->is_valid(), "destination must be valid"); + int idx = (int)toReg->value(); + if (reg_state[idx] == reg_readonly) { + if (idx != from->value()) { + mark_done = false; + } + done = false; + continue; + } else if (reg_state[idx] == reg_written) { + continue; + } + assert(reg_state[idx] == reg_writable, "must be writable"); + reg_state[idx] = reg_written; + progress = true; + + if (fromReg == noreg) { + if (from->is_reg()) { + fromReg = from->as_Register(); + } else { + int st_off = from->reg2stack() * VMRegImpl::stack_slot_size; + ldr(tmp1, Address(sp, st_off)); + fromReg = tmp1; + } + if (null_check) { + // Nullable inline type argument, emit null check + cbz(fromReg, L_null); + } + } + int off = sig->at(stream.sig_index())._offset; + if (off == -1) { + assert(null_check, "Missing null check at"); + if (toReg->is_stack()) { + int st_off = toReg->reg2stack() * VMRegImpl::stack_slot_size; + mov(tmp2, 1); + str(tmp2, Address(sp, st_off)); + } else { + mov(toReg->as_Register(), 1); + } + continue; + } + assert(off > 0, "offset in object should be positive"); + Address fromAddr = Address(fromReg, off); + if (!toReg->is_FloatRegister()) { + Register dst = toReg->is_stack() ? tmp2 : toReg->as_Register(); + if (is_reference_type(bt)) { + load_heap_oop(dst, fromAddr, rscratch1, rscratch2); + } else { + bool is_signed = (bt != T_CHAR) && (bt != T_BOOLEAN); + load_sized_value(dst, fromAddr, type2aelembytes(bt), is_signed); + } + if (toReg->is_stack()) { + int st_off = toReg->reg2stack() * VMRegImpl::stack_slot_size; + str(dst, Address(sp, st_off)); + } + } else if (bt == T_DOUBLE) { + ldrd(toReg->as_FloatRegister(), fromAddr); + } else { + assert(bt == T_FLOAT, "must be float"); + ldrs(toReg->as_FloatRegister(), fromAddr); + } + } + if (progress && null_check) { + if (done) { + b(L_notNull); + bind(L_null); + // Set null marker to zero to signal that the argument is null. + // Also set all oop fields to zero to make the GC happy. + stream.reset(sig_index, to_index); + while (stream.next(toReg, bt)) { + if (sig->at(stream.sig_index())._offset == -1 || + bt == T_OBJECT || bt == T_ARRAY) { + if (toReg->is_stack()) { + int st_off = toReg->reg2stack() * VMRegImpl::stack_slot_size; + str(zr, Address(sp, st_off)); + } else { + mov(toReg->as_Register(), zr); + } + } + } + bind(L_notNull); + } else { + bind(L_null); + } + } + + // TODO 8366717 This is probably okay but looks fishy because stream is reset in the "Set null marker to zero" case just above. Same on x64. + sig_index = stream.sig_index(); + to_index = stream.regs_index(); + + if (mark_done && reg_state[from->value()] != reg_written) { + // This is okay because no one else will write to that slot + reg_state[from->value()] = reg_writable; + } + from_index--; + assert(progress || (start_offset == offset()), "should not emit code"); + return done; +} + +// Pack fields back into an inline type oop +bool MacroAssembler::pack_inline_helper(const GrowableArray* sig, int& sig_index, int vtarg_index, + VMRegPair* from, int from_count, int& from_index, VMReg to, + RegState reg_state[], Register val_array) { + assert(sig->at(sig_index)._bt == T_METADATA, "should be at delimiter"); + assert(to->is_valid(), "destination must be valid"); + + if (reg_state[to->value()] == reg_written) { + skip_unpacked_fields(sig, sig_index, from, from_count, from_index); + return true; // Already written + } + + // The GC barrier expanded by store_heap_oop below may call into the + // runtime so use callee-saved registers for any values that need to be + // preserved. The GC barrier assembler should take care of saving the + // Java argument registers. + // TODO 8284443 Isn't it an issue if below code uses r14 as tmp when it contains a spilled value? + // Be careful with r14 because it's used for spilling (see MacroAssembler::spill_reg_for). + Register val_obj_tmp = r21; + Register from_reg_tmp = r22; + Register tmp1 = r14; + Register tmp2 = r13; + Register tmp3 = r12; + Register val_obj = to->is_stack() ? val_obj_tmp : to->as_Register(); + + assert_different_registers(val_obj_tmp, from_reg_tmp, tmp1, tmp2, tmp3, val_array); + + if (reg_state[to->value()] == reg_readonly) { + if (!is_reg_in_unpacked_fields(sig, sig_index, to, from, from_count, from_index)) { + skip_unpacked_fields(sig, sig_index, from, from_count, from_index); + return false; // Not yet writable + } + val_obj = val_obj_tmp; + } + + int index = arrayOopDesc::base_offset_in_bytes(T_OBJECT) + vtarg_index * type2aelembytes(T_OBJECT); + load_heap_oop(val_obj, Address(val_array, index), tmp1, tmp2); + + ScalarizedInlineArgsStream stream(sig, sig_index, from, from_count, from_index); + VMReg fromReg; + BasicType bt; + Label L_null; + while (stream.next(fromReg, bt)) { + assert(fromReg->is_valid(), "source must be valid"); + reg_state[fromReg->value()] = reg_writable; + + int off = sig->at(stream.sig_index())._offset; + if (off == -1) { + // Nullable inline type argument, emit null check + Label L_notNull; + if (fromReg->is_stack()) { + int ld_off = fromReg->reg2stack() * VMRegImpl::stack_slot_size; + ldrb(tmp2, Address(sp, ld_off)); + cbnz(tmp2, L_notNull); + } else { + cbnz(fromReg->as_Register(), L_notNull); + } + mov(val_obj, 0); + b(L_null); + bind(L_notNull); + continue; + } + + assert(off > 0, "offset in object should be positive"); + size_t size_in_bytes = is_java_primitive(bt) ? type2aelembytes(bt) : wordSize; + + // Pack the scalarized field into the value object. + Address dst(val_obj, off); + if (!fromReg->is_FloatRegister()) { + Register src; + if (fromReg->is_stack()) { + src = from_reg_tmp; + int ld_off = fromReg->reg2stack() * VMRegImpl::stack_slot_size; + load_sized_value(src, Address(sp, ld_off), size_in_bytes, /* is_signed */ false); + } else { + src = fromReg->as_Register(); + } + assert_different_registers(dst.base(), src, tmp1, tmp2, tmp3, val_array); + if (is_reference_type(bt)) { + store_heap_oop(dst, src, tmp1, tmp2, tmp3, IN_HEAP | ACCESS_WRITE | IS_DEST_UNINITIALIZED); + } else { + store_sized_value(dst, src, size_in_bytes); + } + } else if (bt == T_DOUBLE) { + strd(fromReg->as_FloatRegister(), dst); + } else { + assert(bt == T_FLOAT, "must be float"); + strs(fromReg->as_FloatRegister(), dst); + } + } + bind(L_null); + sig_index = stream.sig_index(); + from_index = stream.regs_index(); + + assert(reg_state[to->value()] == reg_writable, "must have already been read"); + bool success = move_helper(val_obj->as_VMReg(), to, T_OBJECT, reg_state); + assert(success, "to register must be writeable"); + return true; +} + +VMReg MacroAssembler::spill_reg_for(VMReg reg) { + return (reg->is_FloatRegister()) ? v8->as_VMReg() : r14->as_VMReg(); +} + void MacroAssembler::cache_wb(Address line) { assert(line.getMode() == Address::base_plus_offset, "mode should be base_plus_offset"); assert(line.index() == noreg, "index should be noreg"); @@ -7142,6 +7934,10 @@ void MacroAssembler::lightweight_lock(Register basic_lock, Register obj, Registe // Try to lock. Transition lock bits 0b01 => 0b00 assert(oopDesc::mark_offset_in_bytes() == 0, "required to avoid lea"); orr(mark, mark, markWord::unlocked_value); + if (EnableValhalla) { + // Mask inline_type bit such that we go to the slow path if object is an inline type + andr(mark, mark, ~((int) markWord::inline_type_bit_in_place)); + } eor(t, mark, markWord::unlocked_value); cmpxchg(/*addr*/ obj, /*expected*/ mark, /*new*/ t, Assembler::xword, /*acquire*/ true, /*release*/ false, /*weak*/ false, noreg); diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp index fe2440fd3fd..30cbf884f8f 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp @@ -33,7 +33,12 @@ #include "oops/compressedOops.hpp" #include "oops/compressedKlass.hpp" #include "runtime/vm_version.hpp" +#include "utilities/macros.hpp" #include "utilities/powerOfTwo.hpp" +#include "runtime/signature.hpp" + + +class ciInlineKlass; class OopMap; @@ -676,6 +681,28 @@ class MacroAssembler: public Assembler { static bool needs_explicit_null_check(intptr_t offset); static bool uses_implicit_null_check(void* address); + // markWord tests, kills markWord reg + void test_markword_is_inline_type(Register markword, Label& is_inline_type); + + // inlineKlass queries, kills temp_reg + void test_oop_is_not_inline_type(Register object, Register tmp, Label& not_inline_type, bool can_be_null = true); + + void test_field_is_null_free_inline_type(Register flags, Register temp_reg, Label& is_null_free); + void test_field_is_not_null_free_inline_type(Register flags, Register temp_reg, Label& not_null_free); + void test_field_is_flat(Register flags, Register temp_reg, Label& is_flat); + void test_field_has_null_marker(Register flags, Register temp_reg, Label& has_null_marker); + + // Check oops for special arrays, i.e. flat arrays and/or null-free arrays + void test_oop_prototype_bit(Register oop, Register temp_reg, int32_t test_bit, bool jmp_set, Label& jmp_label); + void test_flat_array_oop(Register klass, Register temp_reg, Label& is_flat_array); + void test_non_flat_array_oop(Register oop, Register temp_reg, Label&is_non_flat_array); + void test_null_free_array_oop(Register oop, Register temp_reg, Label& is_null_free_array); + void test_non_null_free_array_oop(Register oop, Register temp_reg, Label&is_non_null_free_array); + + // Check array klass layout helper for flat or null-free arrays... + void test_flat_array_layout(Register lh, Label& is_flat_array); + void test_non_flat_array_layout(Register lh, Label& is_non_flat_array); + static address target_addr_for_insn(address insn_addr, unsigned insn); static address target_addr_for_insn_or_null(address insn_addr, unsigned insn); static address target_addr_for_insn(address insn_addr) { @@ -913,6 +940,8 @@ class MacroAssembler: public Assembler { void load_method_holder(Register holder, Register method); // oop manipulations + void load_metadata(Register dst, Register src); + void load_narrow_klass_compact(Register dst, Register src); void load_klass(Register dst, Register src); void store_klass(Register dst, Register src); @@ -929,6 +958,15 @@ class MacroAssembler: public Assembler { void access_store_at(BasicType type, DecoratorSet decorators, Address dst, Register val, Register tmp1, Register tmp2, Register tmp3); + void flat_field_copy(DecoratorSet decorators, Register src, Register dst, Register inline_layout_info); + + // inline type data payload offsets... + void payload_offset(Register inline_klass, Register offset); + void payload_address(Register oop, Register data, Register inline_klass); + // get data payload ptr a flat value array at index, kills rcx and index + void data_for_value_array_index(Register array, Register array_klass, + Register index, Register data); + void load_heap_oop(Register dst, Address src, Register tmp1, Register tmp2, DecoratorSet decorators = 0); @@ -942,6 +980,8 @@ class MacroAssembler: public Assembler { // stored using routines that take a jobject. void store_heap_oop_null(Address dst); + void load_prototype_header(Register dst, Register src); + void store_klass_gap(Register dst, Register src); // This dummy is to prevent a call to store_heap_oop from @@ -994,6 +1034,15 @@ class MacroAssembler: public Assembler { void java_round_float(Register dst, FloatRegister src, FloatRegister ftmp); // allocation + + // Object / value buffer allocation... + // Allocate instance of klass, assumes klass initialized by caller + // new_obj prefers to be rax + // Kills t1 and t2, perserves klass, return allocation in new_obj (rsi on LP64) + void allocate_instance(Register klass, Register new_obj, + Register t1, Register t2, + bool clear_fields, Label& alloc_failed); + void tlab_allocate( Register obj, // result: pointer to object after successful allocation Register var_size_in_bytes, // object size in bytes if unknown at compile time; invalid otherwise @@ -1004,6 +1053,11 @@ class MacroAssembler: public Assembler { ); void verify_tlab(); + // For field "index" within "klass", return inline_klass ... + void get_inline_type_field_klass(Register klass, Register index, Register inline_klass); + void inline_layout_info(Register holder_klass, Register index, Register layout_info); + + // interface method calling void lookup_interface_method(Register recv_klass, Register intf_klass, @@ -1469,6 +1523,24 @@ class MacroAssembler: public Assembler { void adrp(Register reg1, const Address &dest, uint64_t &byte_offset); + void verified_entry(Compile* C, int sp_inc); + + // Inline type specific methods + #include "asm/macroAssembler_common.hpp" + + int store_inline_type_fields_to_buf(ciInlineKlass* vk, bool from_interpreter = true); + bool move_helper(VMReg from, VMReg to, BasicType bt, RegState reg_state[]); + bool unpack_inline_helper(const GrowableArray* sig, int& sig_index, + VMReg from, int& from_index, VMRegPair* to, int to_count, int& to_index, + RegState reg_state[]); + bool pack_inline_helper(const GrowableArray* sig, int& sig_index, int vtarg_index, + VMRegPair* from, int from_count, int& from_index, VMReg to, + RegState reg_state[], Register val_array); + int extend_stack_for_inline_args(int args_on_stack); + void remove_frame(int initial_framesize, bool needs_stack_repair); + VMReg spill_reg_for(VMReg reg); + void save_stack_increment(int sp_inc, int frame_size); + void tableswitch(Register index, jint lowbound, jint highbound, Label &jumptable, Label &jumptable_end, int stride = 1) { adr(rscratch1, jumptable); @@ -1540,6 +1612,8 @@ class MacroAssembler: public Assembler { void string_equals(Register a1, Register a2, Register result, Register cnt1); void fill_words(Register base, Register cnt, Register value); + void fill_words(Register base, uint64_t cnt, Register value); + address zero_words(Register base, uint64_t cnt); address zero_words(Register ptr, Register cnt); void zero_dcache_blocks(Register base, Register cnt); diff --git a/src/hotspot/cpu/aarch64/methodHandles_aarch64.cpp b/src/hotspot/cpu/aarch64/methodHandles_aarch64.cpp index cdf67e3423f..4051cbc003b 100644 --- a/src/hotspot/cpu/aarch64/methodHandles_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/methodHandles_aarch64.cpp @@ -161,7 +161,11 @@ void MethodHandles::jump_from_method_handle(MacroAssembler* _masm, Register meth __ BIND(run_compiled_code); } - const ByteSize entry_offset = for_compiler_entry ? Method::from_compiled_offset() : + // The following jump might pass an inline type argument that was erased to Object as oop to a + // callee that expects inline type arguments to be passed as fields. We need to call the compiled + // value entry (_code->inline_entry_point() or _adapter->c2i_inline_entry()) which will take care + // of translating between the calling conventions. + const ByteSize entry_offset = for_compiler_entry ? Method::from_compiled_inline_offset() : Method::from_interpreted_offset(); __ ldr(rscratch1,Address(method, entry_offset)); __ br(rscratch1); diff --git a/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.cpp b/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.cpp index 748c3e8fb11..275bc1ef201 100644 --- a/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.cpp @@ -26,6 +26,7 @@ #include "asm/macroAssembler.hpp" #include "asm/macroAssembler.inline.hpp" +#include "classfile/symbolTable.hpp" #include "code/aotCodeCache.hpp" #include "code/codeCache.hpp" #include "code/compiledIC.hpp" @@ -359,6 +360,85 @@ int SharedRuntime::java_calling_convention(const BasicType *sig_bt, return stk_args; } + +const uint SharedRuntime::java_return_convention_max_int = Argument::n_int_register_parameters_j; +const uint SharedRuntime::java_return_convention_max_float = Argument::n_float_register_parameters_j; + +int SharedRuntime::java_return_convention(const BasicType *sig_bt, VMRegPair *regs, int total_args_passed) { + + // Create the mapping between argument positions and registers. + + static const Register INT_ArgReg[java_return_convention_max_int] = { + r0 /* j_rarg7 */, j_rarg6, j_rarg5, j_rarg4, j_rarg3, j_rarg2, j_rarg1, j_rarg0 + }; + + static const FloatRegister FP_ArgReg[java_return_convention_max_float] = { + j_farg0, j_farg1, j_farg2, j_farg3, j_farg4, j_farg5, j_farg6, j_farg7 + }; + + uint int_args = 0; + uint fp_args = 0; + + for (int i = 0; i < total_args_passed; i++) { + switch (sig_bt[i]) { + case T_BOOLEAN: + case T_CHAR: + case T_BYTE: + case T_SHORT: + case T_INT: + if (int_args < SharedRuntime::java_return_convention_max_int) { + regs[i].set1(INT_ArgReg[int_args]->as_VMReg()); + int_args ++; + } else { + return -1; + } + break; + case T_VOID: + // halves of T_LONG or T_DOUBLE + assert(i != 0 && (sig_bt[i - 1] == T_LONG || sig_bt[i - 1] == T_DOUBLE), "expecting half"); + regs[i].set_bad(); + break; + case T_LONG: + assert((i + 1) < total_args_passed && sig_bt[i + 1] == T_VOID, "expecting half"); + // fall through + case T_OBJECT: + case T_ARRAY: + case T_ADDRESS: + // Should T_METADATA be added to java_calling_convention as well ? + case T_METADATA: + if (int_args < SharedRuntime::java_return_convention_max_int) { + regs[i].set2(INT_ArgReg[int_args]->as_VMReg()); + int_args ++; + } else { + return -1; + } + break; + case T_FLOAT: + if (fp_args < SharedRuntime::java_return_convention_max_float) { + regs[i].set1(FP_ArgReg[fp_args]->as_VMReg()); + fp_args ++; + } else { + return -1; + } + break; + case T_DOUBLE: + assert((i + 1) < total_args_passed && sig_bt[i + 1] == T_VOID, "expecting half"); + if (fp_args < SharedRuntime::java_return_convention_max_float) { + regs[i].set2(FP_ArgReg[fp_args]->as_VMReg()); + fp_args ++; + } else { + return -1; + } + break; + default: + ShouldNotReachHere(); + break; + } + } + + return int_args + fp_args; +} + // Patch the callers callsite with entry to compiled code if it exists. static void patch_callers_callsite(MacroAssembler *masm) { Label L; @@ -393,12 +473,144 @@ static void patch_callers_callsite(MacroAssembler *masm) { __ bind(L); } +// For each inline type argument, sig includes the list of fields of +// the inline type. This utility function computes the number of +// arguments for the call if inline types are passed by reference (the +// calling convention the interpreter expects). +static int compute_total_args_passed_int(const GrowableArray* sig_extended) { + int total_args_passed = 0; + if (InlineTypePassFieldsAsArgs) { + for (int i = 0; i < sig_extended->length(); i++) { + BasicType bt = sig_extended->at(i)._bt; + if (bt == T_METADATA) { + // In sig_extended, an inline type argument starts with: + // T_METADATA, followed by the types of the fields of the + // inline type and T_VOID to mark the end of the value + // type. Inline types are flattened so, for instance, in the + // case of an inline type with an int field and an inline type + // field that itself has 2 fields, an int and a long: + // T_METADATA T_INT T_METADATA T_INT T_LONG T_VOID (second + // slot for the T_LONG) T_VOID (inner inline type) T_VOID + // (outer inline type) + total_args_passed++; + int vt = 1; + do { + i++; + BasicType bt = sig_extended->at(i)._bt; + BasicType prev_bt = sig_extended->at(i-1)._bt; + if (bt == T_METADATA) { + vt++; + } else if (bt == T_VOID && + prev_bt != T_LONG && + prev_bt != T_DOUBLE) { + vt--; + } + } while (vt != 0); + } else { + total_args_passed++; + } + } + } else { + total_args_passed = sig_extended->length(); + } + return total_args_passed; +} + + +static void gen_c2i_adapter_helper(MacroAssembler* masm, + BasicType bt, + BasicType prev_bt, + size_t size_in_bytes, + const VMRegPair& reg_pair, + const Address& to, + Register tmp1, + Register tmp2, + Register tmp3, + int extraspace, + bool is_oop) { + if (bt == T_VOID) { + assert(prev_bt == T_LONG || prev_bt == T_DOUBLE, "missing half"); + return; + } + + // Say 4 args: + // i st_off + // 0 32 T_LONG + // 1 24 T_VOID + // 2 16 T_OBJECT + // 3 8 T_BOOL + // - 0 return address + // + // However to make thing extra confusing. Because we can fit a Java long/double in + // a single slot on a 64 bt vm and it would be silly to break them up, the interpreter + // leaves one slot empty and only stores to a single slot. In this case the + // slot that is occupied is the T_VOID slot. See I said it was confusing. + + bool wide = (size_in_bytes == wordSize); + VMReg r_1 = reg_pair.first(); + VMReg r_2 = reg_pair.second(); + assert(r_2->is_valid() == wide, "invalid size"); + if (!r_1->is_valid()) { + assert(!r_2->is_valid(), ""); + return; + } + + if (!r_1->is_FloatRegister()) { + Register val = r25; + if (r_1->is_stack()) { + // memory to memory use r25 (scratch registers is used by store_heap_oop) + int ld_off = r_1->reg2stack() * VMRegImpl::stack_slot_size + extraspace; + __ load_sized_value(val, Address(sp, ld_off), size_in_bytes, /* is_signed */ false); + } else { + val = r_1->as_Register(); + } + assert_different_registers(to.base(), val, tmp1, tmp2, tmp3); + if (is_oop) { + __ store_heap_oop(to, val, tmp1, tmp2, tmp3, IN_HEAP | ACCESS_WRITE | IS_DEST_UNINITIALIZED); + } else { + __ store_sized_value(to, val, size_in_bytes); + } + } else { + if (wide) { + __ strd(r_1->as_FloatRegister(), to); + } else { + // only a float use just part of the slot + __ strs(r_1->as_FloatRegister(), to); + } + } +} + static void gen_c2i_adapter(MacroAssembler *masm, - int total_args_passed, - int comp_args_on_stack, - const BasicType *sig_bt, + const GrowableArray* sig_extended, const VMRegPair *regs, - Label& skip_fixup) { + bool requires_clinit_barrier, + address& c2i_no_clinit_check_entry, + Label& skip_fixup, + address start, + OopMapSet* oop_maps, + int& frame_complete, + int& frame_size_in_words, + bool alloc_inline_receiver) { + if (requires_clinit_barrier && VM_Version::supports_fast_class_init_checks()) { + Label L_skip_barrier; + + { // Bypass the barrier for non-static methods + __ ldrh(rscratch1, Address(rmethod, Method::access_flags_offset())); + __ andsw(zr, rscratch1, JVM_ACC_STATIC); + __ br(Assembler::EQ, L_skip_barrier); // non-static + } + + __ load_method_holder(rscratch2, rmethod); + __ clinit_barrier(rscratch2, rscratch1, &L_skip_barrier); + __ far_jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); + + __ bind(L_skip_barrier); + c2i_no_clinit_check_entry = __ pc(); + } + + BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); + bs->c2i_entry_barrier(masm); + // Before we get into the guts of the C2I adapter, see if we should be here // at all. We've come from compiled code and are attempting to jump to the // interpreter, which means the caller made a static call to get here @@ -408,114 +620,166 @@ static void gen_c2i_adapter(MacroAssembler *masm, __ bind(skip_fixup); - int words_pushed = 0; + // TODO 8366717 Is the comment about r13 correct? Isn't that r19_sender_sp? + // Name some registers to be used in the following code. We can use + // anything except r0-r7 which are arguments in the Java calling + // convention, rmethod (r12), and r13 which holds the outgoing sender + // SP for the interpreter. + // TODO 8366717 We need to make sure that buf_array, buf_oop (and potentially other long-life regs) are kept live in slowpath runtime calls in GC barriers + Register buf_array = r10; // Array of buffered inline types + Register buf_oop = r11; // Buffered inline type oop + Register tmp1 = r15; + Register tmp2 = r16; + Register tmp3 = r17; + + if (InlineTypePassFieldsAsArgs) { + // Is there an inline type argument? + bool has_inline_argument = false; + for (int i = 0; i < sig_extended->length() && !has_inline_argument; i++) { + has_inline_argument = (sig_extended->at(i)._bt == T_METADATA); + } + if (has_inline_argument) { + // There is at least an inline type argument: we're coming from + // compiled code so we have no buffers to back the inline types + // Allocate the buffers here with a runtime call. + // TODO 8366717 Do we need to save vectors here? They could be used as arg registers, right? Same on x64. + RegisterSaver reg_save(true /* save_vectors */); + OopMap* map = reg_save.save_live_registers(masm, 0, &frame_size_in_words); - // Since all args are passed on the stack, total_args_passed * - // Interpreter::stackElementSize is the space we need. + frame_complete = __ offset(); + address the_pc = __ pc(); - int extraspace = total_args_passed * Interpreter::stackElementSize; + Label retaddr; + __ set_last_Java_frame(sp, noreg, retaddr, rscratch1); - __ mov(r19_sender_sp, sp); + __ mov(c_rarg0, rthread); + __ mov(c_rarg1, rmethod); + __ mov(c_rarg2, (int64_t)alloc_inline_receiver); - // stack is aligned, keep it that way - extraspace = align_up(extraspace, 2*wordSize); + __ lea(rscratch1, RuntimeAddress(CAST_FROM_FN_PTR(address, SharedRuntime::allocate_inline_types))); + __ blr(rscratch1); + __ bind(retaddr); - if (extraspace) - __ sub(sp, sp, extraspace); + oop_maps->add_gc_map(__ pc() - start, map); + __ reset_last_Java_frame(false); - // Now write the args into the outgoing interpreter space - for (int i = 0; i < total_args_passed; i++) { - if (sig_bt[i] == T_VOID) { - assert(i > 0 && (sig_bt[i-1] == T_LONG || sig_bt[i-1] == T_DOUBLE), "missing half"); - continue; - } + reg_save.restore_live_registers(masm); - // offset to start parameters - int st_off = (total_args_passed - i - 1) * Interpreter::stackElementSize; - int next_off = st_off - Interpreter::stackElementSize; - - // Say 4 args: - // i st_off - // 0 32 T_LONG - // 1 24 T_VOID - // 2 16 T_OBJECT - // 3 8 T_BOOL - // - 0 return address - // - // However to make thing extra confusing. Because we can fit a Java long/double in - // a single slot on a 64 bt vm and it would be silly to break them up, the interpreter - // leaves one slot empty and only stores to a single slot. In this case the - // slot that is occupied is the T_VOID slot. See I said it was confusing. + Label no_exception; + __ ldr(rscratch1, Address(rthread, Thread::pending_exception_offset())); + __ cbz(rscratch1, no_exception); - VMReg r_1 = regs[i].first(); - VMReg r_2 = regs[i].second(); - if (!r_1->is_valid()) { - assert(!r_2->is_valid(), ""); - continue; + __ str(zr, Address(rthread, JavaThread::vm_result_oop_offset())); + __ ldr(r0, Address(rthread, Thread::pending_exception_offset())); + __ far_jump(RuntimeAddress(StubRoutines::forward_exception_entry())); + + __ bind(no_exception); + + // We get an array of objects from the runtime call + __ get_vm_result_oop(buf_array, rthread); + __ get_vm_result_metadata(rmethod, rthread); // TODO: required to keep the callee Method live? } - if (r_1->is_stack()) { - // memory to memory use rscratch1 - int ld_off = (r_1->reg2stack() * VMRegImpl::stack_slot_size - + extraspace - + words_pushed * wordSize); - if (!r_2->is_valid()) { - // sign extend?? - __ ldrw(rscratch1, Address(sp, ld_off)); - __ str(rscratch1, Address(sp, st_off)); + } - } else { + // Since all args are passed on the stack, total_args_passed * + // Interpreter::stackElementSize is the space we need. + + int total_args_passed = compute_total_args_passed_int(sig_extended); + int extraspace = total_args_passed * Interpreter::stackElementSize; + + // stack is aligned, keep it that way + extraspace = align_up(extraspace, StackAlignmentInBytes); + + // set senderSP value + __ mov(r19_sender_sp, sp); - __ ldr(rscratch1, Address(sp, ld_off)); + __ sub(sp, sp, extraspace); - // Two VMREgs|OptoRegs can be T_OBJECT, T_ADDRESS, T_DOUBLE, T_LONG - // T_DOUBLE and T_LONG use two slots in the interpreter - if ( sig_bt[i] == T_LONG || sig_bt[i] == T_DOUBLE) { - // ld_off == LSW, ld_off+wordSize == MSW - // st_off == MSW, next_off == LSW - __ str(rscratch1, Address(sp, next_off)); + // Now write the args into the outgoing interpreter space + + // next_arg_comp is the next argument from the compiler point of + // view (inline type fields are passed in registers/on the stack). In + // sig_extended, an inline type argument starts with: T_METADATA, + // followed by the types of the fields of the inline type and T_VOID + // to mark the end of the inline type. ignored counts the number of + // T_METADATA/T_VOID. next_vt_arg is the next inline type argument: + // used to get the buffer for that argument from the pool of buffers + // we allocated above and want to pass to the + // interpreter. next_arg_int is the next argument from the + // interpreter point of view (inline types are passed by reference). + for (int next_arg_comp = 0, ignored = 0, next_vt_arg = 0, next_arg_int = 0; + next_arg_comp < sig_extended->length(); next_arg_comp++) { + assert(ignored <= next_arg_comp, "shouldn't skip over more slots than there are arguments"); + assert(next_arg_int <= total_args_passed, "more arguments for the interpreter than expected?"); + BasicType bt = sig_extended->at(next_arg_comp)._bt; + int st_off = (total_args_passed - next_arg_int - 1) * Interpreter::stackElementSize; + if (!InlineTypePassFieldsAsArgs || bt != T_METADATA) { + int next_off = st_off - Interpreter::stackElementSize; + const int offset = (bt == T_LONG || bt == T_DOUBLE) ? next_off : st_off; + const VMRegPair reg_pair = regs[next_arg_comp-ignored]; + size_t size_in_bytes = reg_pair.second()->is_valid() ? 8 : 4; + gen_c2i_adapter_helper(masm, bt, next_arg_comp > 0 ? sig_extended->at(next_arg_comp-1)._bt : T_ILLEGAL, + size_in_bytes, reg_pair, Address(sp, offset), tmp1, tmp2, tmp3, extraspace, false); + next_arg_int++; #ifdef ASSERT - // Overwrite the unused slot with known junk - __ mov(rscratch1, (uint64_t)0xdeadffffdeadaaaaull); - __ str(rscratch1, Address(sp, st_off)); -#endif /* ASSERT */ - } else { - __ str(rscratch1, Address(sp, st_off)); - } + if (bt == T_LONG || bt == T_DOUBLE) { + // Overwrite the unused slot with known junk + __ mov(rscratch1, CONST64(0xdeadffffdeadaaaa)); + __ str(rscratch1, Address(sp, st_off)); } - } else if (r_1->is_Register()) { - Register r = r_1->as_Register(); - if (!r_2->is_valid()) { - // must be only an int (or less ) so move only 32bits to slot - // why not sign extend?? - __ str(r, Address(sp, st_off)); - } else { - // Two VMREgs|OptoRegs can be T_OBJECT, T_ADDRESS, T_DOUBLE, T_LONG - // T_DOUBLE and T_LONG use two slots in the interpreter - if ( sig_bt[i] == T_LONG || sig_bt[i] == T_DOUBLE) { - // jlong/double in gpr -#ifdef ASSERT - // Overwrite the unused slot with known junk - __ mov(rscratch1, (uint64_t)0xdeadffffdeadaaabull); - __ str(rscratch1, Address(sp, st_off)); #endif /* ASSERT */ - __ str(r, Address(sp, next_off)); + } else { + ignored++; + // get the buffer from the just allocated pool of buffers + int index = arrayOopDesc::base_offset_in_bytes(T_OBJECT) + next_vt_arg * type2aelembytes(T_OBJECT); + __ load_heap_oop(buf_oop, Address(buf_array, index), tmp1, tmp2); + next_vt_arg++; next_arg_int++; + int vt = 1; + // write fields we get from compiled code in registers/stack + // slots to the buffer: we know we are done with that inline type + // argument when we hit the T_VOID that acts as an end of inline + // type delimiter for this inline type. Inline types are flattened + // so we might encounter embedded inline types. Each entry in + // sig_extended contains a field offset in the buffer. + Label L_null; + do { + next_arg_comp++; + BasicType bt = sig_extended->at(next_arg_comp)._bt; + BasicType prev_bt = sig_extended->at(next_arg_comp - 1)._bt; + if (bt == T_METADATA) { + vt++; + ignored++; + } else if (bt == T_VOID && prev_bt != T_LONG && prev_bt != T_DOUBLE) { + vt--; + ignored++; } else { - __ str(r, Address(sp, st_off)); + int off = sig_extended->at(next_arg_comp)._offset; + if (off == -1) { + // Nullable inline type argument, emit null check + VMReg reg = regs[next_arg_comp-ignored].first(); + Label L_notNull; + if (reg->is_stack()) { + int ld_off = reg->reg2stack() * VMRegImpl::stack_slot_size + extraspace; + __ ldrb(tmp1, Address(sp, ld_off)); + __ cbnz(tmp1, L_notNull); + } else { + __ cbnz(reg->as_Register(), L_notNull); + } + __ str(zr, Address(sp, st_off)); + __ b(L_null); + __ bind(L_notNull); + continue; + } + assert(off > 0, "offset in object should be positive"); + size_t size_in_bytes = is_java_primitive(bt) ? type2aelembytes(bt) : wordSize; + bool is_oop = is_reference_type(bt); + gen_c2i_adapter_helper(masm, bt, next_arg_comp > 0 ? sig_extended->at(next_arg_comp-1)._bt : T_ILLEGAL, + size_in_bytes, regs[next_arg_comp-ignored], Address(buf_oop, off), tmp1, tmp2, tmp3, extraspace, is_oop); } - } - } else { - assert(r_1->is_FloatRegister(), ""); - if (!r_2->is_valid()) { - // only a float use just part of the slot - __ strs(r_1->as_FloatRegister(), Address(sp, st_off)); - } else { -#ifdef ASSERT - // Overwrite the unused slot with known junk - __ mov(rscratch1, (uint64_t)0xdeadffffdeadaaacull); - __ str(rscratch1, Address(sp, st_off)); -#endif /* ASSERT */ - __ strd(r_1->as_FloatRegister(), Address(sp, next_off)); - } + } while (vt != 0); + // pass the buffer to the interpreter + __ str(buf_oop, Address(sp, st_off)); + __ bind(L_null); } } @@ -525,12 +789,8 @@ static void gen_c2i_adapter(MacroAssembler *masm, __ br(rscratch1); } +void SharedRuntime::gen_i2c_adapter(MacroAssembler *masm, int comp_args_on_stack, const GrowableArray* sig, const VMRegPair *regs) { -void SharedRuntime::gen_i2c_adapter(MacroAssembler *masm, - int total_args_passed, - int comp_args_on_stack, - const BasicType *sig_bt, - const VMRegPair *regs) { // Note: r19_sender_sp contains the senderSP on entry. We must // preserve it since we may do a i2c -> c2i transition if we lose a @@ -559,15 +819,16 @@ void SharedRuntime::gen_i2c_adapter(MacroAssembler *masm, // caller, but with an uncorrected stack, causing delayed havoc. // Cut-out for having no stack args. - int comp_words_on_stack = align_up(comp_args_on_stack*VMRegImpl::stack_slot_size, wordSize)>>LogBytesPerWord; + int comp_words_on_stack = 0; if (comp_args_on_stack) { - __ sub(rscratch1, sp, comp_words_on_stack * wordSize); - __ andr(sp, rscratch1, -16); + comp_words_on_stack = align_up(comp_args_on_stack * VMRegImpl::stack_slot_size, wordSize) >> LogBytesPerWord; + __ sub(rscratch1, sp, comp_words_on_stack * wordSize); + __ andr(sp, rscratch1, -16); } // Will jump to the compiled code just as if compiled code was doing it. // Pre-load the register-jump target early, to schedule it better. - __ ldr(rscratch1, Address(rmethod, in_bytes(Method::from_compiled_offset()))); + __ ldr(rscratch1, Address(rmethod, in_bytes(Method::from_compiled_inline_offset()))); #if INCLUDE_JVMCI if (EnableJVMCI) { @@ -581,19 +842,21 @@ void SharedRuntime::gen_i2c_adapter(MacroAssembler *masm, } #endif // INCLUDE_JVMCI + int total_args_passed = sig->length(); + // Now generate the shuffle code. for (int i = 0; i < total_args_passed; i++) { - if (sig_bt[i] == T_VOID) { - assert(i > 0 && (sig_bt[i-1] == T_LONG || sig_bt[i-1] == T_DOUBLE), "missing half"); + BasicType bt = sig->at(i)._bt; + if (bt == T_VOID) { + assert(i > 0 && (sig->at(i - 1)._bt == T_LONG || sig->at(i - 1)._bt == T_DOUBLE), "missing half"); continue; } // Pick up 0, 1 or 2 words from SP+offset. + assert(!regs[i].second()->is_valid() || regs[i].first()->next() == regs[i].second(), "scrambled load targets?"); - assert(!regs[i].second()->is_valid() || regs[i].first()->next() == regs[i].second(), - "scrambled load targets?"); // Load in argument order going down. - int ld_off = (total_args_passed - i - 1)*Interpreter::stackElementSize; + int ld_off = (total_args_passed - i - 1) * Interpreter::stackElementSize; // Point to interpreter value (vs. tag) int next_off = ld_off - Interpreter::stackElementSize; // @@ -607,7 +870,7 @@ void SharedRuntime::gen_i2c_adapter(MacroAssembler *masm, } if (r_1->is_stack()) { // Convert stack slot to an SP offset (+ wordSize to account for return address ) - int st_off = regs[i].first()->reg2stack()*VMRegImpl::stack_slot_size; + int st_off = regs[i].first()->reg2stack() * VMRegImpl::stack_slot_size; if (!r_2->is_valid()) { // sign extend??? __ ldrsw(rscratch2, Address(esp, ld_off)); @@ -624,39 +887,38 @@ void SharedRuntime::gen_i2c_adapter(MacroAssembler *masm, // are accessed as negative so LSW is at LOW address // ld_off is MSW so get LSW - const int offset = (sig_bt[i]==T_LONG||sig_bt[i]==T_DOUBLE)? - next_off : ld_off; + const int offset = (bt == T_LONG || bt == T_DOUBLE) ? next_off : ld_off; __ ldr(rscratch2, Address(esp, offset)); // st_off is LSW (i.e. reg.first()) - __ str(rscratch2, Address(sp, st_off)); - } - } else if (r_1->is_Register()) { // Register argument - Register r = r_1->as_Register(); - if (r_2->is_valid()) { - // - // We are using two VMRegs. This can be either T_OBJECT, - // T_ADDRESS, T_LONG, or T_DOUBLE the interpreter allocates - // two slots but only uses one for thr T_LONG or T_DOUBLE case - // So we must adjust where to pick up the data to match the - // interpreter. + __ str(rscratch2, Address(sp, st_off)); + } + } else if (r_1->is_Register()) { // Register argument + Register r = r_1->as_Register(); + if (r_2->is_valid()) { + // + // We are using two VMRegs. This can be either T_OBJECT, + // T_ADDRESS, T_LONG, or T_DOUBLE the interpreter allocates + // two slots but only uses one for thr T_LONG or T_DOUBLE case + // So we must adjust where to pick up the data to match the + // interpreter. + + const int offset = (bt == T_LONG || bt == T_DOUBLE) ? next_off : ld_off; + + // this can be a misaligned move + __ ldr(r, Address(esp, offset)); + } else { + // sign extend and use a full word? + __ ldrw(r, Address(esp, ld_off)); + } + } else { + if (!r_2->is_valid()) { + __ ldrs(r_1->as_FloatRegister(), Address(esp, ld_off)); + } else { + __ ldrd(r_1->as_FloatRegister(), Address(esp, next_off)); + } + } + } - const int offset = (sig_bt[i]==T_LONG||sig_bt[i]==T_DOUBLE)? - next_off : ld_off; - - // this can be a misaligned move - __ ldr(r, Address(esp, offset)); - } else { - // sign extend and use a full word? - __ ldrw(r, Address(esp, ld_off)); - } - } else { - if (!r_2->is_valid()) { - __ ldrs(r_1->as_FloatRegister(), Address(esp, ld_off)); - } else { - __ ldrd(r_1->as_FloatRegister(), Address(esp, next_off)); - } - } - } __ mov(rscratch2, rscratch1); __ push_cont_fastpath(rthread); // Set JavaThread::_cont_fastpath to the sp of the oldest interpreted frame we know about; kills rscratch1 @@ -673,27 +935,36 @@ void SharedRuntime::gen_i2c_adapter(MacroAssembler *masm, // and the vm will find there should this case occur. __ str(rmethod, Address(rthread, JavaThread::callee_target_offset())); - __ br(rscratch1); } +static void gen_inline_cache_check(MacroAssembler *masm, Label& skip_fixup) { + Register data = rscratch2; + __ ic_check(1 /* end_alignment */); + __ ldr(rmethod, Address(data, CompiledICData::speculated_method_offset())); + + // Method might have been compiled since the call site was patched to + // interpreted; if that is the case treat it as a miss so we can get + // the call site corrected. + __ ldr(rscratch1, Address(rmethod, in_bytes(Method::code_offset()))); + __ cbz(rscratch1, skip_fixup); + __ far_jump(RuntimeAddress(SharedRuntime::get_ic_miss_stub())); +} + // --------------------------------------------------------------- -void SharedRuntime::generate_i2c2i_adapters(MacroAssembler *masm, - int total_args_passed, +void SharedRuntime::generate_i2c2i_adapters(MacroAssembler* masm, int comp_args_on_stack, - const BasicType *sig_bt, - const VMRegPair *regs, - AdapterHandlerEntry* handler) { + const GrowableArray* sig, + const VMRegPair* regs, + const GrowableArray* sig_cc, + const VMRegPair* regs_cc, + const GrowableArray* sig_cc_ro, + const VMRegPair* regs_cc_ro, + AdapterHandlerEntry* handler, + AdapterBlob*& new_adapter, + bool allocate_code_blob) { address i2c_entry = __ pc(); - - gen_i2c_adapter(masm, total_args_passed, comp_args_on_stack, sig_bt, regs); - - address c2i_unverified_entry = __ pc(); - Label skip_fixup; - - Register data = rscratch2; - Register receiver = j_rarg0; - Register tmp = r10; // A call-clobbered register not used for arg passing + gen_i2c_adapter(masm, comp_args_on_stack, sig, regs); // ------------------------------------------------------------------------- // Generate a C2I adapter. On entry we know rmethod holds the Method* during calls @@ -704,48 +975,61 @@ void SharedRuntime::generate_i2c2i_adapters(MacroAssembler *masm, // On exit from the interpreter, the interpreter will restore our SP (lest the // compiled code, which relies solely on SP and not FP, get sick). - { - __ block_comment("c2i_unverified_entry {"); - // Method might have been compiled since the call site was patched to - // interpreted; if that is the case treat it as a miss so we can get - // the call site corrected. - __ ic_check(1 /* end_alignment */); - __ ldr(rmethod, Address(data, CompiledICData::speculated_method_offset())); + address c2i_unverified_entry = __ pc(); + address c2i_unverified_inline_entry = __ pc(); + Label skip_fixup; - __ ldr(rscratch1, Address(rmethod, in_bytes(Method::code_offset()))); - __ cbz(rscratch1, skip_fixup); - __ far_jump(RuntimeAddress(SharedRuntime::get_ic_miss_stub())); - __ block_comment("} c2i_unverified_entry"); - } + gen_inline_cache_check(masm, skip_fixup); - address c2i_entry = __ pc(); + OopMapSet* oop_maps = new OopMapSet(); + int frame_complete = CodeOffsets::frame_never_safe; + int frame_size_in_words = 0; - // Class initialization barrier for static methods + // Scalarized c2i adapter with non-scalarized receiver (i.e., don't pack receiver) address c2i_no_clinit_check_entry = nullptr; - if (VM_Version::supports_fast_class_init_checks()) { - Label L_skip_barrier; - - { // Bypass the barrier for non-static methods - __ ldrh(rscratch1, Address(rmethod, Method::access_flags_offset())); - __ andsw(zr, rscratch1, JVM_ACC_STATIC); - __ br(Assembler::EQ, L_skip_barrier); // non-static - } - - __ load_method_holder(rscratch2, rmethod); - __ clinit_barrier(rscratch2, rscratch1, &L_skip_barrier); - __ far_jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); - - __ bind(L_skip_barrier); - c2i_no_clinit_check_entry = __ pc(); - } - - BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); - bs->c2i_entry_barrier(masm); - - gen_c2i_adapter(masm, total_args_passed, comp_args_on_stack, sig_bt, regs, skip_fixup); - - handler->set_entry_points(i2c_entry, c2i_entry, c2i_unverified_entry, c2i_no_clinit_check_entry); - return; + address c2i_inline_ro_entry = __ pc(); + if (regs_cc != regs_cc_ro) { + // No class init barrier needed because method is guaranteed to be non-static + gen_c2i_adapter(masm, sig_cc_ro, regs_cc_ro, /* requires_clinit_barrier = */ false, c2i_no_clinit_check_entry, + skip_fixup, i2c_entry, oop_maps, frame_complete, frame_size_in_words, /* alloc_inline_receiver = */ false); + skip_fixup.reset(); + } + + // Scalarized c2i adapter + address c2i_entry = __ pc(); + address c2i_inline_entry = __ pc(); + gen_c2i_adapter(masm, sig_cc, regs_cc, /* requires_clinit_barrier = */ true, c2i_no_clinit_check_entry, + skip_fixup, i2c_entry, oop_maps, frame_complete, frame_size_in_words, /* alloc_inline_receiver = */ true); + + // Non-scalarized c2i adapter + if (regs != regs_cc) { + c2i_unverified_inline_entry = __ pc(); + Label inline_entry_skip_fixup; + gen_inline_cache_check(masm, inline_entry_skip_fixup); + + c2i_inline_entry = __ pc(); + gen_c2i_adapter(masm, sig, regs, /* requires_clinit_barrier = */ true, c2i_no_clinit_check_entry, + inline_entry_skip_fixup, i2c_entry, oop_maps, frame_complete, frame_size_in_words, /* alloc_inline_receiver = */ false); + } + + // The c2i adapters might safepoint and trigger a GC. The caller must make sure that + // the GC knows about the location of oop argument locations passed to the c2i adapter. + if (allocate_code_blob) { + bool caller_must_gc_arguments = (regs != regs_cc); + int entry_offset[AdapterHandlerEntry::ENTRIES_COUNT]; + assert(AdapterHandlerEntry::ENTRIES_COUNT == 7, "sanity"); + entry_offset[0] = 0; // i2c_entry offset + entry_offset[1] = c2i_entry - i2c_entry; + entry_offset[2] = c2i_inline_entry - i2c_entry; + entry_offset[3] = c2i_inline_ro_entry - i2c_entry; + entry_offset[4] = c2i_unverified_entry - i2c_entry; + entry_offset[5] = c2i_unverified_inline_entry - i2c_entry; + entry_offset[6] = c2i_no_clinit_check_entry - i2c_entry; + new_adapter = AdapterBlob::create(masm->code(), entry_offset, frame_complete, frame_size_in_words, oop_maps, caller_must_gc_arguments); + } + + handler->set_entry_points(i2c_entry, c2i_entry, c2i_inline_entry, c2i_inline_ro_entry, c2i_unverified_entry, + c2i_unverified_inline_entry, c2i_no_clinit_check_entry); } static int c_calling_convention_priv(const BasicType *sig_bt, @@ -2756,6 +3040,149 @@ RuntimeStub* SharedRuntime::generate_resolve_blob(StubId id, address destination return rs_blob; } +BufferedInlineTypeBlob* SharedRuntime::generate_buffered_inline_type_adapter(const InlineKlass* vk) { + BufferBlob* buf = BufferBlob::create("inline types pack/unpack", 16 * K); + if (buf == nullptr) { + return nullptr; + } + CodeBuffer buffer(buf); + short buffer_locs[20]; + buffer.insts()->initialize_shared_locs((relocInfo*)buffer_locs, + sizeof(buffer_locs)/sizeof(relocInfo)); + + MacroAssembler _masm(&buffer); + MacroAssembler* masm = &_masm; + + const Array* sig_vk = vk->extended_sig(); + const Array* regs = vk->return_regs(); + + int pack_fields_jobject_off = __ offset(); + // Resolve pre-allocated buffer from JNI handle. + // We cannot do this in generate_call_stub() because it requires GC code to be initialized. + Register Rresult = r14; // See StubGenerator::generate_call_stub(). + __ ldr(r0, Address(Rresult)); + __ resolve_jobject(r0 /* value */, + rthread /* thread */, + r12 /* tmp */); + __ str(r0, Address(Rresult)); + + int pack_fields_off = __ offset(); + + int j = 1; + for (int i = 0; i < sig_vk->length(); i++) { + BasicType bt = sig_vk->at(i)._bt; + if (bt == T_METADATA) { + continue; + } + if (bt == T_VOID) { + if (sig_vk->at(i-1)._bt == T_LONG || + sig_vk->at(i-1)._bt == T_DOUBLE) { + j++; + } + continue; + } + int off = sig_vk->at(i)._offset; + VMRegPair pair = regs->at(j); + VMReg r_1 = pair.first(); + VMReg r_2 = pair.second(); + Address to(r0, off); + if (bt == T_FLOAT) { + __ strs(r_1->as_FloatRegister(), to); + } else if (bt == T_DOUBLE) { + __ strd(r_1->as_FloatRegister(), to); + } else { + Register val = r_1->as_Register(); + assert_different_registers(to.base(), val, r15, r16, r17); + if (is_reference_type(bt)) { + __ store_heap_oop(to, val, r15, r16, r17, IN_HEAP | ACCESS_WRITE | IS_DEST_UNINITIALIZED); + } else { + __ store_sized_value(to, r_1->as_Register(), type2aelembytes(bt)); + } + } + j++; + } + assert(j == regs->length(), "missed a field?"); + if (vk->has_nullable_atomic_layout()) { + // Zero the null marker (setting it to 1 would be better but would require an additional register) + __ strb(zr, Address(r0, vk->null_marker_offset())); + } + __ ret(lr); + + int unpack_fields_off = __ offset(); + + Label skip; + Label not_null; + __ cbnz(r0, not_null); + + // Return value is null. Zero oop registers to make the GC happy. + j = 1; + for (int i = 0; i < sig_vk->length(); i++) { + BasicType bt = sig_vk->at(i)._bt; + if (bt == T_METADATA) { + continue; + } + if (bt == T_VOID) { + if (sig_vk->at(i-1)._bt == T_LONG || + sig_vk->at(i-1)._bt == T_DOUBLE) { + j++; + } + continue; + } + if (bt == T_OBJECT || bt == T_ARRAY) { + VMRegPair pair = regs->at(j); + VMReg r_1 = pair.first(); + __ mov(r_1->as_Register(), zr); + } + j++; + } + __ b(skip); + __ bind(not_null); + + j = 1; + for (int i = 0; i < sig_vk->length(); i++) { + BasicType bt = sig_vk->at(i)._bt; + if (bt == T_METADATA) { + continue; + } + if (bt == T_VOID) { + if (sig_vk->at(i-1)._bt == T_LONG || + sig_vk->at(i-1)._bt == T_DOUBLE) { + j++; + } + continue; + } + int off = sig_vk->at(i)._offset; + assert(off > 0, "offset in object should be positive"); + VMRegPair pair = regs->at(j); + VMReg r_1 = pair.first(); + VMReg r_2 = pair.second(); + Address from(r0, off); + if (bt == T_FLOAT) { + __ ldrs(r_1->as_FloatRegister(), from); + } else if (bt == T_DOUBLE) { + __ ldrd(r_1->as_FloatRegister(), from); + } else if (bt == T_OBJECT || bt == T_ARRAY) { + assert_different_registers(r0, r_1->as_Register()); + __ load_heap_oop(r_1->as_Register(), from, rscratch1, rscratch2); + } else { + assert(is_java_primitive(bt), "unexpected basic type"); + assert_different_registers(r0, r_1->as_Register()); + size_t size_in_bytes = type2aelembytes(bt); + __ load_sized_value(r_1->as_Register(), from, size_in_bytes, bt != T_CHAR && bt != T_BOOLEAN); + } + j++; + } + assert(j == regs->length(), "missed a field?"); + + __ bind(skip); + + __ ret(lr); + + __ flush(); + + return BufferedInlineTypeBlob::create(&buffer, pack_fields_off, pack_fields_jobject_off, unpack_fields_off); +} + // Continuation point for throwing of implicit exceptions that are // not handled in the current activation. Fabricates an exception // oop and initiates normal exception dispatching in this diff --git a/src/hotspot/cpu/aarch64/stackChunkFrameStream_aarch64.inline.hpp b/src/hotspot/cpu/aarch64/stackChunkFrameStream_aarch64.inline.hpp index 8a221f13772..5776a8a75b5 100644 --- a/src/hotspot/cpu/aarch64/stackChunkFrameStream_aarch64.inline.hpp +++ b/src/hotspot/cpu/aarch64/stackChunkFrameStream_aarch64.inline.hpp @@ -35,8 +35,30 @@ template inline bool StackChunkFrameStream::is_in_frame(void* p0) const { assert(!is_done(), ""); intptr_t* p = (intptr_t*)p0; - int argsize = is_compiled() ? (_cb->as_nmethod()->num_stack_arg_slots() * VMRegImpl::stack_slot_size) >> LogBytesPerWord : 0; - int frame_size = _cb->frame_size() + argsize; + int frame_size = _cb->frame_size(); + if (is_compiled()) { + nmethod* nm = _cb->as_nmethod_or_null(); + if (nm->needs_stack_repair() && nm->is_compiled_by_c2()) { + frame f = to_frame(); + bool augmented = f.was_augmented_on_entry(frame_size); + if (!augmented) { + // Fix: C2 caller, so frame was not extended and thus the + // size read from the frame does not include the arguments. + // Ideally we have to count the arg size for the scalarized + // convention. For now we include the size of the caller frame + // which would at least be equal to that. + RegisterMap map(nullptr, + RegisterMap::UpdateMap::skip, + RegisterMap::ProcessFrames::skip, + RegisterMap::WalkContinuation::skip); + frame caller = to_frame().sender(&map); + assert(caller.is_compiled_frame() && caller.cb()->as_nmethod()->is_compiled_by_c2(), "needs stack repair but was not extended with c1/interpreter caller"); + frame_size += (caller.real_fp() - caller.sp()); + } + } else { + frame_size += _cb->as_nmethod()->num_stack_arg_slots() * VMRegImpl::stack_slot_size >> LogBytesPerWord; + } + } return p == sp() - frame::sender_sp_offset || ((p - unextended_sp()) >= 0 && (p - unextended_sp()) < frame_size); } #endif @@ -46,7 +68,13 @@ inline frame StackChunkFrameStream::to_frame() const { if (is_done()) { return frame(_sp, _sp, nullptr, nullptr, nullptr, nullptr, true); } else { - return frame(sp(), unextended_sp(), fp(), pc(), cb(), _oopmap, true); + frame f = frame(sp(), unextended_sp(), fp(), pc(), cb(), _oopmap, true); + // If caller tries to get the sender of this frame and PreserveFramePointer + // is set, fp() will be used which contains the old value at the time of + // freeze (fp is reconstructed again during thaw). Setting sp as trusted + // causes the sender code to use _unextended_sp instead (see sender_for_compiled_frame()). + f.set_sp_is_trusted(); + return f; } } @@ -54,7 +82,7 @@ template inline address StackChunkFrameStream::get_pc() const { assert(!is_done(), ""); // Just strip it for frames on the heap. - return pauth_strip_pointer(*(address*)(_sp - 1)); + return pauth_strip_pointer(*(address*)((_callee_augmented ? _unextended_sp : _sp) - 1)); } template diff --git a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp index fa7329f4942..3110c9388b8 100644 --- a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp @@ -328,20 +328,25 @@ class StubGenerator: public StubCodeGenerator { // T_OBJECT, T_LONG, T_FLOAT or T_DOUBLE is treated as T_INT) // n.b. this assumes Java returns an integral result in r0 // and a floating result in j_farg0 - __ ldr(j_rarg2, result); - Label is_long, is_float, is_double, exit; - __ ldr(j_rarg1, result_type); - __ cmp(j_rarg1, (u1)T_OBJECT); + // All of j_rargN may be used to return inline type fields so be careful + // not to clobber those. + // SharedRuntime::generate_buffered_inline_type_adapter() knows the register + // assignment of Rresult below. + Register Rresult = r14, Rresult_type = r15; + __ ldr(Rresult, result); + Label is_long, is_float, is_double, check_prim, exit; + __ ldr(Rresult_type, result_type); + __ cmp(Rresult_type, (u1)T_OBJECT); + __ br(Assembler::EQ, check_prim); + __ cmp(Rresult_type, (u1)T_LONG); __ br(Assembler::EQ, is_long); - __ cmp(j_rarg1, (u1)T_LONG); - __ br(Assembler::EQ, is_long); - __ cmp(j_rarg1, (u1)T_FLOAT); + __ cmp(Rresult_type, (u1)T_FLOAT); __ br(Assembler::EQ, is_float); - __ cmp(j_rarg1, (u1)T_DOUBLE); + __ cmp(Rresult_type, (u1)T_DOUBLE); __ br(Assembler::EQ, is_double); // handle T_INT case - __ strw(r0, Address(j_rarg2)); + __ strw(r0, Address(Rresult)); __ BIND(exit); @@ -393,17 +398,28 @@ class StubGenerator: public StubCodeGenerator { __ ret(lr); // handle return types different from T_INT + __ BIND(check_prim); + if (InlineTypeReturnedAsFields) { + // Check for scalarized return value + __ tbz(r0, 0, is_long); + // Load pack handler address + __ andr(rscratch1, r0, -2); + __ ldr(rscratch1, Address(rscratch1, InstanceKlass::adr_inlineklass_fixed_block_offset())); + __ ldr(rscratch1, Address(rscratch1, InlineKlass::pack_handler_jobject_offset())); + __ blr(rscratch1); + __ b(exit); + } __ BIND(is_long); - __ str(r0, Address(j_rarg2, 0)); + __ str(r0, Address(Rresult, 0)); __ br(Assembler::AL, exit); __ BIND(is_float); - __ strs(j_farg0, Address(j_rarg2, 0)); + __ strs(j_farg0, Address(Rresult, 0)); __ br(Assembler::AL, exit); __ BIND(is_double); - __ strd(j_farg0, Address(j_rarg2, 0)); + __ strd(j_farg0, Address(Rresult, 0)); __ br(Assembler::AL, exit); return start; @@ -2224,6 +2240,12 @@ class StubGenerator: public StubCodeGenerator { __ eor(rscratch2, rscratch2, scratch_src_klass); __ cbnz(rscratch2, L_failed); + // Check for flat inline type array -> return -1 + __ test_flat_array_oop(src, rscratch2, L_failed); + + // Check for null-free (non-flat) inline type array -> handle as object array + __ test_null_free_array_oop(src, rscratch2, L_objArray); + // if (!src->is_Array()) return -1; __ tbz(lh, 31, L_failed); // i.e. (lh >= 0) @@ -10461,6 +10483,30 @@ class StubGenerator: public StubCodeGenerator { } #endif // LINUX + static void save_return_registers(MacroAssembler* masm) { + if (InlineTypeReturnedAsFields) { + masm->push(RegSet::range(r0, r7), sp); + masm->sub(sp, sp, 4 * wordSize); + masm->st1(v0, v1, v2, v3, masm->T1D, Address(sp)); + masm->sub(sp, sp, 4 * wordSize); + masm->st1(v4, v5, v6, v7, masm->T1D, Address(sp)); + } else { + masm->fmovd(rscratch1, v0); + masm->stp(rscratch1, r0, Address(masm->pre(sp, -2 * wordSize))); + } + } + + static void restore_return_registers(MacroAssembler* masm) { + if (InlineTypeReturnedAsFields) { + masm->ld1(v4, v5, v6, v7, masm->T1D, Address(masm->post(sp, 4 * wordSize))); + masm->ld1(v0, v1, v2, v3, masm->T1D, Address(masm->post(sp, 4 * wordSize))); + masm->pop(RegSet::range(r0, r7), sp); + } else { + masm->ldp(rscratch1, r0, Address(masm->post(sp, 2 * wordSize))); + masm->fmovd(v0, rscratch1); + } + } + address generate_cont_thaw(Continuation::thaw_kind kind) { bool return_barrier = Continuation::is_thaw_return_barrier(kind); bool return_barrier_exception = Continuation::is_thaw_return_barrier_exception(kind); @@ -10475,8 +10521,7 @@ class StubGenerator: public StubCodeGenerator { if (return_barrier) { // preserve possible return value from a method returning to the return barrier - __ fmovd(rscratch1, v0); - __ stp(rscratch1, r0, Address(__ pre(sp, -2 * wordSize))); + save_return_registers(_masm); } __ movw(c_rarg1, (return_barrier ? 1 : 0)); @@ -10485,8 +10530,7 @@ class StubGenerator: public StubCodeGenerator { if (return_barrier) { // restore return value (no safepoint in the call to thaw, so even an oop return value should be OK) - __ ldp(rscratch1, r0, Address(__ post(sp, 2 * wordSize))); - __ fmovd(v0, rscratch1); + restore_return_registers(_masm); } assert_asm(_masm, (__ ldr(rscratch1, Address(rthread, JavaThread::cont_entry_offset())), __ cmp(sp, rscratch1)), Assembler::EQ, "incorrect sp"); @@ -10505,8 +10549,7 @@ class StubGenerator: public StubCodeGenerator { if (return_barrier) { // save original return value -- again - __ fmovd(rscratch1, v0); - __ stp(rscratch1, r0, Address(__ pre(sp, -2 * wordSize))); + save_return_registers(_masm); } // If we want, we can templatize thaw by kind, and have three different entries @@ -10517,8 +10560,7 @@ class StubGenerator: public StubCodeGenerator { if (return_barrier) { // restore return value (no safepoint in the call to thaw, so even an oop return value should be OK) - __ ldp(rscratch1, r0, Address(__ post(sp, 2 * wordSize))); - __ fmovd(v0, rscratch1); + restore_return_registers(_masm); } else { __ mov(r0, zr); // return 0 (success) from doYield } @@ -11655,6 +11697,134 @@ class StubGenerator: public StubCodeGenerator { // } }; + // Call here from the interpreter or compiled code to either load + // multiple returned values from the inline type instance being + // returned to registers or to store returned values to a newly + // allocated inline type instance. + address generate_return_value_stub(address destination, const char* name, bool has_res) { + // We need to save all registers the calling convention may use so + // the runtime calls read or update those registers. This needs to + // be in sync with SharedRuntime::java_return_convention(). + // n.b. aarch64 asserts that frame::arg_reg_save_area_bytes == 0 + enum layout { + j_rarg7_off = 0, j_rarg7_2, // j_rarg7 is r0 + j_rarg6_off, j_rarg6_2, + j_rarg5_off, j_rarg5_2, + j_rarg4_off, j_rarg4_2, + j_rarg3_off, j_rarg3_2, + j_rarg2_off, j_rarg2_2, + j_rarg1_off, j_rarg1_2, + j_rarg0_off, j_rarg0_2, + + j_farg7_off, j_farg7_2, + j_farg6_off, j_farg6_2, + j_farg5_off, j_farg5_2, + j_farg4_off, j_farg4_2, + j_farg3_off, j_farg3_2, + j_farg2_off, j_farg2_2, + j_farg1_off, j_farg1_2, + j_farg0_off, j_farg0_2, + + rfp_off, rfp_off2, + return_off, return_off2, + + framesize // inclusive of return address + }; + + CodeBuffer code(name, 512, 64); + MacroAssembler* masm = new MacroAssembler(&code); + + int frame_size_in_bytes = align_up(framesize*BytesPerInt, 16); + assert(frame_size_in_bytes == framesize*BytesPerInt, "misaligned"); + int frame_size_in_slots = frame_size_in_bytes / BytesPerInt; + int frame_size_in_words = frame_size_in_bytes / wordSize; + + OopMapSet* oop_maps = new OopMapSet(); + OopMap* map = new OopMap(frame_size_in_slots, 0); + + map->set_callee_saved(VMRegImpl::stack2reg(j_rarg7_off), j_rarg7->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_rarg6_off), j_rarg6->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_rarg5_off), j_rarg5->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_rarg4_off), j_rarg4->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_rarg3_off), j_rarg3->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_rarg2_off), j_rarg2->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_rarg1_off), j_rarg1->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_rarg0_off), j_rarg0->as_VMReg()); + + map->set_callee_saved(VMRegImpl::stack2reg(j_farg0_off), j_farg0->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_farg1_off), j_farg1->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_farg2_off), j_farg2->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_farg3_off), j_farg3->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_farg4_off), j_farg4->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_farg5_off), j_farg5->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_farg6_off), j_farg6->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_farg7_off), j_farg7->as_VMReg()); + + address start = __ pc(); + + __ enter(); // Save FP and LR before call + + __ stpd(j_farg1, j_farg0, Address(__ pre(sp, -2 * wordSize))); + __ stpd(j_farg3, j_farg2, Address(__ pre(sp, -2 * wordSize))); + __ stpd(j_farg5, j_farg4, Address(__ pre(sp, -2 * wordSize))); + __ stpd(j_farg7, j_farg6, Address(__ pre(sp, -2 * wordSize))); + + __ stp(j_rarg1, j_rarg0, Address(__ pre(sp, -2 * wordSize))); + __ stp(j_rarg3, j_rarg2, Address(__ pre(sp, -2 * wordSize))); + __ stp(j_rarg5, j_rarg4, Address(__ pre(sp, -2 * wordSize))); + __ stp(j_rarg7, j_rarg6, Address(__ pre(sp, -2 * wordSize))); + + int frame_complete = __ offset(); + + // Set up last_Java_sp and last_Java_fp + address the_pc = __ pc(); + __ set_last_Java_frame(sp, noreg, the_pc, rscratch1); + + // Call runtime + __ mov(c_rarg1, r0); + __ mov(c_rarg0, rthread); + + __ mov(rscratch1, destination); + __ blr(rscratch1); + + oop_maps->add_gc_map(the_pc - start, map); + + __ reset_last_Java_frame(false); + + __ ldp(j_rarg7, j_rarg6, Address(__ post(sp, 2 * wordSize))); + __ ldp(j_rarg5, j_rarg4, Address(__ post(sp, 2 * wordSize))); + __ ldp(j_rarg3, j_rarg2, Address(__ post(sp, 2 * wordSize))); + __ ldp(j_rarg1, j_rarg0, Address(__ post(sp, 2 * wordSize))); + + __ ldpd(j_farg7, j_farg6, Address(__ post(sp, 2 * wordSize))); + __ ldpd(j_farg5, j_farg4, Address(__ post(sp, 2 * wordSize))); + __ ldpd(j_farg3, j_farg2, Address(__ post(sp, 2 * wordSize))); + __ ldpd(j_farg1, j_farg0, Address(__ post(sp, 2 * wordSize))); + + __ leave(); + + // check for pending exceptions + Label pending; + __ ldr(rscratch1, Address(rthread, in_bytes(Thread::pending_exception_offset()))); + __ cbnz(rscratch1, pending); + + if (has_res) { + __ get_vm_result_oop(r0, rthread); + } + + __ ret(lr); + + __ bind(pending); + __ far_jump(RuntimeAddress(StubRoutines::forward_exception_entry())); + + // ------------- + // make sure all code is generated + masm->flush(); + + RuntimeStub* stub = RuntimeStub::new_runtime_stub(name, &code, frame_complete, frame_size_in_words, oop_maps, false); + return stub->entry_point(); + } + // Initialization void generate_preuniverse_stubs() { // preuniverse stubs are not needed for aarch64 @@ -11703,6 +11873,14 @@ class StubGenerator: public StubCodeGenerator { StubRoutines::_hf2f = generate_float16ToFloat(); StubRoutines::_f2hf = generate_floatToFloat16(); } + + if (InlineTypeReturnedAsFields) { + StubRoutines::_load_inline_type_fields_in_regs = + generate_return_value_stub(CAST_FROM_FN_PTR(address, SharedRuntime::load_inline_type_fields_in_regs), "load_inline_type_fields_in_regs", false); + StubRoutines::_store_inline_type_fields_to_buf = + generate_return_value_stub(CAST_FROM_FN_PTR(address, SharedRuntime::store_inline_type_fields_to_buf), "store_inline_type_fields_to_buf", true); + } + } void generate_continuation_stubs() { diff --git a/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp index c1eabed8ade..f4df3e757a0 100644 --- a/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp @@ -41,6 +41,7 @@ #include "oops/methodCounters.hpp" #include "oops/methodData.hpp" #include "oops/oop.inline.hpp" +#include "oops/inlineKlass.hpp" #include "oops/resolvedIndyEntry.hpp" #include "oops/resolvedMethodEntry.hpp" #include "prims/jvmtiExport.hpp" @@ -467,6 +468,11 @@ address TemplateInterpreterGenerator::generate_return_entry_for(TosState state, __ lea(esp, Address(rfp, rscratch1, Address::lsl(Interpreter::logStackElementSize))); // and null it as marker that esp is now tos until next java call __ str(zr, Address(rfp, frame::interpreter_frame_last_sp_offset * wordSize)); + + if (state == atos && InlineTypeReturnedAsFields) { + __ store_inline_type_fields_to_buf(nullptr, true); + } + __ restore_bcp(); __ restore_locals(); __ restore_constant_pool_cache(); @@ -1658,7 +1664,7 @@ address TemplateInterpreterGenerator::generate_native_entry(bool synchronized) { // // Generic interpreted method entry to (asm) interpreter // -address TemplateInterpreterGenerator::generate_normal_entry(bool synchronized) { +address TemplateInterpreterGenerator::generate_normal_entry(bool synchronized, bool object_init) { // determine code generation flags bool inc_counter = UseCompiler || CountCompiledCalls; @@ -1785,6 +1791,12 @@ address TemplateInterpreterGenerator::generate_normal_entry(bool synchronized) { #endif } + // Issue a StoreStore barrier on entry to Object_init if the + // class has strict field fields. Be lazy, always do it. + if (object_init) { + __ membar(MacroAssembler::StoreStore); + } + // start execution #ifdef ASSERT { diff --git a/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp b/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp index 2ccde98d98d..126421d7d99 100644 --- a/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp @@ -172,6 +172,7 @@ void TemplateTable::patch_bytecode(Bytecodes::Code bc, Register bc_reg, Label L_patch_done; switch (bc) { + case Bytecodes::_fast_vputfield: case Bytecodes::_fast_aputfield: case Bytecodes::_fast_bputfield: case Bytecodes::_fast_zputfield: @@ -753,10 +754,10 @@ void TemplateTable::index_check(Register array, Register index) } Label ok; __ br(Assembler::LO, ok); - // ??? convention: move array into r3 for exception message - __ mov(r3, array); - __ mov(rscratch1, Interpreter::_throw_ArrayIndexOutOfBoundsException_entry); - __ br(rscratch1); + // ??? convention: move array into r3 for exception message + __ mov(r3, array); + __ mov(rscratch1, Interpreter::_throw_ArrayIndexOutOfBoundsException_entry); + __ br(rscratch1); __ bind(ok); } @@ -816,11 +817,26 @@ void TemplateTable::aaload() // r0: array // r1: index index_check(r0, r1); // leaves index in r1, kills rscratch1 - __ add(r1, r1, arrayOopDesc::base_offset_in_bytes(T_OBJECT) >> LogBytesPerHeapOop); - do_oop_load(_masm, - Address(r0, r1, Address::uxtw(LogBytesPerHeapOop)), - r0, - IS_ARRAY); + __ profile_array_type(r2, r0, r4); + if (UseArrayFlattening) { + Label is_flat_array, done; + + __ test_flat_array_oop(r0, rscratch1 /*temp*/, is_flat_array); + __ add(r1, r1, arrayOopDesc::base_offset_in_bytes(T_OBJECT) >> LogBytesPerHeapOop); + do_oop_load(_masm, Address(r0, r1, Address::uxtw(LogBytesPerHeapOop)), r0, IS_ARRAY); + + __ b(done); + __ bind(is_flat_array); + __ call_VM(r0, CAST_FROM_FN_PTR(address, InterpreterRuntime::flat_array_load), r0, r1); + // Ensure the stores to copy the inline field contents are visible + // before any subsequent store that publishes this reference. + __ membar(Assembler::StoreStore); + __ bind(done); + } else { + __ add(r1, r1, arrayOopDesc::base_offset_in_bytes(T_OBJECT) >> LogBytesPerHeapOop); + do_oop_load(_masm, Address(r0, r1, Address::uxtw(LogBytesPerHeapOop)), r0, IS_ARRAY); + } + __ profile_element_type(r2, r0, r4); } void TemplateTable::baload() @@ -1107,32 +1123,45 @@ void TemplateTable::dastore() { } void TemplateTable::aastore() { - Label is_null, ok_is_subtype, done; + Label is_null, is_flat_array, ok_is_subtype, done; transition(vtos, vtos); // stack: ..., array, index, value __ ldr(r0, at_tos()); // value __ ldr(r2, at_tos_p1()); // index __ ldr(r3, at_tos_p2()); // array - Address element_address(r3, r4, Address::uxtw(LogBytesPerHeapOop)); - index_check(r3, r2); // kills r1 + + __ profile_array_type(r4, r3, r5); + __ profile_multiple_element_types(r4, r0, r5, r6); + __ add(r4, r2, arrayOopDesc::base_offset_in_bytes(T_OBJECT) >> LogBytesPerHeapOop); + Address element_address(r3, r4, Address::uxtw(LogBytesPerHeapOop)); + // Be careful not to clobber r4 below // do array store check - check for null value first __ cbz(r0, is_null); + // Move array class to r5 + __ load_klass(r5, r3); + + if (UseArrayFlattening) { + __ ldrw(r6, Address(r5, Klass::layout_helper_offset())); + __ test_flat_array_layout(r6, is_flat_array); + } + // Move subklass into r1 __ load_klass(r1, r0); - // Move superklass into r0 - __ load_klass(r0, r3); - __ ldr(r0, Address(r0, - ObjArrayKlass::element_klass_offset())); + + // Move array element superklass into r0 + __ ldr(r0, Address(r5, ObjArrayKlass::element_klass_offset())); // Compress array + index*oopSize + 12 into a single register. Frees r2. // Generate subtype check. Blows r2, r5 // Superklass in r0. Subklass in r1. - __ gen_subtype_check(r1, ok_is_subtype); + + // is "r1 <: r0" ? (value subclass <: array element superclass) + __ gen_subtype_check(r1, ok_is_subtype, false); // Come here on failure // object is at TOS @@ -1150,11 +1179,37 @@ void TemplateTable::aastore() { // Have a null in r0, r3=array, r2=index. Store null at ary[idx] __ bind(is_null); - __ profile_null_seen(r2); + if (EnableValhalla) { + Label is_null_into_value_array_npe, store_null; + + if (UseArrayFlattening) { + __ test_flat_array_oop(r3, rscratch1, is_flat_array); + } + + // No way to store null in a null-free array + __ test_null_free_array_oop(r3, rscratch1, is_null_into_value_array_npe); + __ b(store_null); + + __ bind(is_null_into_value_array_npe); + __ b(ExternalAddress(Interpreter::_throw_NullPointerException_entry)); + + __ bind(store_null); + } // Store a null // Clobbers: r10, r11, r3 do_oop_store(_masm, element_address, noreg, IS_ARRAY); + __ b(done); + + if (UseArrayFlattening) { + Label is_type_ok; + __ bind(is_flat_array); // Store non-null value to flat + + __ ldr(r0, at_tos()); // value + __ ldr(r3, at_tos_p1()); // index + __ ldr(r2, at_tos_p2()); // array + __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::flat_array_store), r0, r2, r3); + } // Pop stack arguments __ bind(done); @@ -1961,19 +2016,67 @@ void TemplateTable::if_nullcmp(Condition cc) __ profile_not_taken_branch(r0); } -void TemplateTable::if_acmp(Condition cc) -{ +void TemplateTable::if_acmp(Condition cc) { transition(atos, vtos); // assume branch is more often taken than not (loops use backward branches) - Label not_taken; + Label taken, not_taken; __ pop_ptr(r1); + + __ profile_acmp(r2, r1, r0, r4); + + Register is_inline_type_mask = rscratch1; + __ mov(is_inline_type_mask, markWord::inline_type_pattern); + + if (EnableValhalla) { + __ cmp(r1, r0); + __ br(Assembler::EQ, (cc == equal) ? taken : not_taken); + + // might be substitutable, test if either r0 or r1 is null + __ andr(r2, r0, r1); + __ cbz(r2, (cc == equal) ? not_taken : taken); + + // and both are values ? + __ ldr(r2, Address(r1, oopDesc::mark_offset_in_bytes())); + __ andr(r2, r2, is_inline_type_mask); + __ ldr(r4, Address(r0, oopDesc::mark_offset_in_bytes())); + __ andr(r4, r4, is_inline_type_mask); + __ andr(r2, r2, r4); + __ cmp(r2, is_inline_type_mask); + __ br(Assembler::NE, (cc == equal) ? not_taken : taken); + + // same value klass ? + __ load_metadata(r2, r1); + __ load_metadata(r4, r0); + __ cmp(r2, r4); + __ br(Assembler::NE, (cc == equal) ? not_taken : taken); + + // Know both are the same type, let's test for substitutability... + if (cc == equal) { + invoke_is_substitutable(r0, r1, taken, not_taken); + } else { + invoke_is_substitutable(r0, r1, not_taken, taken); + } + __ stop("Not reachable"); + } + __ cmpoop(r1, r0); __ br(j_not(cc), not_taken); + __ bind(taken); branch(false, false); __ bind(not_taken); - __ profile_not_taken_branch(r0); + __ profile_not_taken_branch(r0, true); +} + +void TemplateTable::invoke_is_substitutable(Register aobj, Register bobj, + Label& is_subst, Label& not_subst) { + + __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::is_substitutable), aobj, bobj); + // Restored... r0 answer, jmp to outcome... + __ cbz(r0, not_subst); + __ b(is_subst); } + void TemplateTable::ret() { transition(vtos, vtos); locals_index(r1); @@ -2207,7 +2310,8 @@ void TemplateTable::_return(TosState state) // Issue a StoreStore barrier after all stores but before return // from any constructor for any class with a final field. We don't // know if this is a finalizer, so we always do so. - if (_desc->bytecode() == Bytecodes::_return) + if (_desc->bytecode() == Bytecodes::_return + || _desc->bytecode() == Bytecodes::_return_register_finalizer) __ membar(MacroAssembler::StoreStore); if (_desc->bytecode() != Bytecodes::_return_register_finalizer) { @@ -2583,8 +2687,11 @@ void TemplateTable::pop_and_check_object(Register r) void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteControl rc) { - const Register cache = r4; + const Register cache = r2; const Register obj = r4; + const Register klass = r5; + const Register inline_klass = r7; + const Register field_index = r23; const Register index = r3; const Register tos_state = r3; const Register off = r19; @@ -2593,6 +2700,11 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr resolve_cache_and_index_for_field(byte_no, cache, index); jvmti_post_field_access(cache, index, is_static, false); + + // Valhalla extras + __ load_unsigned_short(field_index, Address(cache, in_bytes(ResolvedFieldEntry::field_index_offset()))); + __ ldr(klass, Address(cache, ResolvedFieldEntry::field_holder_offset())); + load_resolved_field_entry(obj, cache, tos_state, off, flags, is_static); if (!is_static) { @@ -2651,12 +2763,40 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr __ cmp(tos_state, (u1)atos); __ br(Assembler::NE, notObj); // atos - do_oop_load(_masm, field, r0, IN_HEAP); - __ push(atos); - if (rc == may_rewrite) { - patch_bytecode(Bytecodes::_fast_agetfield, bc, r1); + if (!EnableValhalla) { + do_oop_load(_masm, field, r0, IN_HEAP); + __ push(atos); + if (rc == may_rewrite) { + patch_bytecode(Bytecodes::_fast_agetfield, bc, r1); + } + __ b(Done); + } else { // Valhalla + if (is_static) { + __ load_heap_oop(r0, field, rscratch1, rscratch2); + __ push(atos); + __ b(Done); + } else { + Label is_flat, rewrite_inline; + __ test_field_is_flat(flags, noreg /*temp*/, is_flat); + __ load_heap_oop(r0, field, rscratch1, rscratch2); + __ push(atos); + if (rc == may_rewrite) { + patch_bytecode(Bytecodes::_fast_agetfield, bc, r1); + } + __ b(Done); + __ bind(is_flat); + // field is flat (null-free or nullable with a null-marker) + __ mov(r0, obj); + __ read_flat_field(cache, field_index, off, inline_klass /* temp */, r0); + __ verify_oop(r0); + __ push(atos); + __ bind(rewrite_inline); + if (rc == may_rewrite) { + patch_bytecode(Bytecodes::_fast_vgetfield, bc, r1); + } + __ b(Done); + } } - __ b(Done); __ bind(notObj); __ cmp(tos_state, (u1)itos); @@ -2817,19 +2957,18 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr const Register tos_state = r3; const Register obj = r2; const Register off = r19; - const Register flags = r0; + const Register flags = r6; const Register bc = r4; + const Register inline_klass = r5; resolve_cache_and_index_for_field(byte_no, cache, index); jvmti_post_field_mod(cache, index, is_static); load_resolved_field_entry(obj, cache, tos_state, off, flags, is_static); Label Done; - __ mov(r5, flags); - { Label notVolatile; - __ tbz(r5, ResolvedFieldEntry::is_volatile_shift, notVolatile); + __ tbz(flags, ResolvedFieldEntry::is_volatile_shift, notVolatile); __ membar(MacroAssembler::StoreStore | MacroAssembler::LoadStore); __ bind(notVolatile); } @@ -2878,15 +3017,55 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr // atos { - __ pop(atos); - if (!is_static) pop_and_check_object(obj); - // Store into the field - // Clobbers: r10, r11, r3 - do_oop_store(_masm, field, r0, IN_HEAP); - if (rc == may_rewrite) { - patch_bytecode(Bytecodes::_fast_aputfield, bc, r1, true, byte_no); - } - __ b(Done); + if (!EnableValhalla) { + __ pop(atos); + if (!is_static) pop_and_check_object(obj); + // Store into the field + // Clobbers: r10, r11, r3 + do_oop_store(_masm, field, r0, IN_HEAP); + if (rc == may_rewrite) { + patch_bytecode(Bytecodes::_fast_aputfield, bc, r1, true, byte_no); + } + __ b(Done); + } else { // Valhalla + __ pop(atos); + if (is_static) { + Label is_nullable; + __ test_field_is_not_null_free_inline_type(flags, noreg /* temp */, is_nullable); + __ null_check(r0); // FIXME JDK-8341120 + __ bind(is_nullable); + do_oop_store(_masm, field, r0, IN_HEAP); + __ b(Done); + } else { + Label null_free_reference, is_flat, rewrite_inline; + __ test_field_is_flat(flags, noreg /*temp*/, is_flat); + __ test_field_is_null_free_inline_type(flags, noreg /*temp*/, null_free_reference); + pop_and_check_object(obj); + // Store into the field + // Clobbers: r10, r11, r3 + do_oop_store(_masm, field, r0, IN_HEAP); + if (rc == may_rewrite) { + patch_bytecode(Bytecodes::_fast_aputfield, bc, r19, true, byte_no); + } + __ b(Done); + // Implementation of the inline type semantic + __ bind(null_free_reference); + __ null_check(r0); // FIXME JDK-8341120 + pop_and_check_object(obj); + // Store into the field + // Clobbers: r10, r11, r3 + do_oop_store(_masm, field, r0, IN_HEAP); + __ b(rewrite_inline); + __ bind(is_flat); + pop_and_check_object(r7); + __ write_flat_field(cache, off, r3, r6, r7); + __ bind(rewrite_inline); + if (rc == may_rewrite) { + patch_bytecode(Bytecodes::_fast_vputfield, bc, r19, true, byte_no); + } + __ b(Done); + } + } // Valhalla } __ bind(notObj); @@ -2991,7 +3170,7 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr { Label notVolatile; - __ tbz(r5, ResolvedFieldEntry::is_volatile_shift, notVolatile); + __ tbz(flags, ResolvedFieldEntry::is_volatile_shift, notVolatile); __ membar(MacroAssembler::StoreLoad | MacroAssembler::StoreStore); __ bind(notVolatile); } @@ -3025,6 +3204,7 @@ void TemplateTable::jvmti_post_fast_field_mod() { // to do it for every data type, we use the saved values as the // jvalue object. switch (bytecode()) { // load values into the jvalue object + case Bytecodes::_fast_vputfield: //fall through case Bytecodes::_fast_aputfield: __ push_ptr(r0); break; case Bytecodes::_fast_bputfield: // fall through case Bytecodes::_fast_zputfield: // fall through @@ -3051,6 +3231,7 @@ void TemplateTable::jvmti_post_fast_field_mod() { r19, c_rarg2, c_rarg3); switch (bytecode()) { // restore tos values + case Bytecodes::_fast_vputfield: //fall through case Bytecodes::_fast_aputfield: __ pop_ptr(r0); break; case Bytecodes::_fast_bputfield: // fall through case Bytecodes::_fast_zputfield: // fall through @@ -3097,6 +3278,22 @@ void TemplateTable::fast_storefield(TosState state) // access field switch (bytecode()) { + case Bytecodes::_fast_vputfield: + { + Label is_flat, has_null_marker, done; + __ test_field_is_flat(r5, noreg /* temp */, is_flat); + __ null_check(r0); + do_oop_store(_masm, field, r0, IN_HEAP); + __ b(done); + __ bind(is_flat); + __ load_field_entry(r4, r5); + // Re-shuffle registers because of VM calls calling convention + __ mov(r19, r1); + __ mov(r7, r2); + __ write_flat_field(r4, r19, r6, r8, r7); + __ bind(done); + } + break; case Bytecodes::_fast_aputfield: // Clobbers: r10, r11, r3 do_oop_store(_masm, field, r0, IN_HEAP); @@ -3190,6 +3387,15 @@ void TemplateTable::fast_accessfield(TosState state) // access field switch (bytecode()) { + case Bytecodes::_fast_vgetfield: + { + Register index = r4, tmp = r7; + // field is flat + __ load_unsigned_short(index, Address(r2, in_bytes(ResolvedFieldEntry::field_index_offset()))); + __ read_flat_field(r2, index, r1, tmp /* temp */, r0); + __ verify_oop(r0); + } + break; case Bytecodes::_fast_agetfield: do_oop_load(_masm, field, r0, IN_HEAP); __ verify_oop(r0); @@ -3608,69 +3814,8 @@ void TemplateTable::_new() { assert(VM_Version::supports_fast_class_init_checks(), "Optimization requires support for fast class initialization checks"); __ clinit_barrier(r4, rscratch1, nullptr /*L_fast_path*/, &slow_case); - // get instance_size in InstanceKlass (scaled to a count of bytes) - __ ldrw(r3, - Address(r4, - Klass::layout_helper_offset())); - // test to see if it is malformed in some way - __ tbnz(r3, exact_log2(Klass::_lh_instance_slow_path_bit), slow_case); - - // Allocate the instance: - // If TLAB is enabled: - // Try to allocate in the TLAB. - // If fails, go to the slow path. - // Initialize the allocation. - // Exit. - // - // Go to slow path. - - if (UseTLAB) { - __ tlab_allocate(r0, r3, 0, noreg, r1, slow_case); - - if (ZeroTLAB) { - // the fields have been already cleared - __ b(initialize_header); - } - - // The object is initialized before the header. If the object size is - // zero, go directly to the header initialization. - int header_size = oopDesc::header_size() * HeapWordSize; - assert(is_aligned(header_size, BytesPerLong), "oop header size must be 8-byte-aligned"); - __ sub(r3, r3, header_size); - __ cbz(r3, initialize_header); - - // Initialize object fields - { - __ add(r2, r0, header_size); - Label loop; - __ bind(loop); - __ str(zr, Address(__ post(r2, BytesPerLong))); - __ sub(r3, r3, BytesPerLong); - __ cbnz(r3, loop); - } - - // initialize object header only. - __ bind(initialize_header); - if (UseCompactObjectHeaders) { - __ ldr(rscratch1, Address(r4, Klass::prototype_header_offset())); - __ str(rscratch1, Address(r0, oopDesc::mark_offset_in_bytes())); - } else { - __ mov(rscratch1, (intptr_t)markWord::prototype().value()); - __ str(rscratch1, Address(r0, oopDesc::mark_offset_in_bytes())); - __ store_klass_gap(r0, zr); // zero klass gap for compressed oops - __ store_klass(r0, r4); // store klass last - } - - if (DTraceAllocProbes) { - // Trigger dtrace event for fastpath - __ push(atos); // save the return value - __ call_VM_leaf( - CAST_FROM_FN_PTR(address, static_cast(SharedRuntime::dtrace_object_alloc)), r0); - __ pop(atos); // restore the return value - - } - __ b(done); - } + __ allocate_instance(r4, r0, r3, r1, true, slow_case); + __ b(done); // slow case __ bind(slow_case); @@ -3754,14 +3899,14 @@ void TemplateTable::checkcast() __ bind(ok_is_subtype); __ mov(r0, r3); // Restore object in r3 + __ b(done); + __ bind(is_null); + // Collect counts on whether this test sees nulls a lot or not. if (ProfileInterpreter) { - __ b(done); - __ bind(is_null); __ profile_null_seen(r2); - } else { - __ bind(is_null); // same as 'done' } + __ bind(done); } @@ -3879,6 +4024,10 @@ void TemplateTable::monitorenter() // check for null object __ null_check(r0); + Label is_inline_type; + __ ldr(rscratch1, Address(r0, oopDesc::mark_offset_in_bytes())); + __ test_markword_is_inline_type(rscratch1, is_inline_type); + const Address monitor_block_top( rfp, frame::interpreter_frame_monitor_block_top_offset * wordSize); const Address monitor_block_bot( @@ -3980,6 +4129,11 @@ void TemplateTable::monitorenter() // The bcp has already been incremented. Just need to dispatch to // next instruction. __ dispatch_next(vtos); + + __ bind(is_inline_type); + __ call_VM(noreg, CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_identity_exception), r0); + __ should_not_reach_here(); } @@ -3990,6 +4144,18 @@ void TemplateTable::monitorexit() // check for null object __ null_check(r0); + const int is_inline_type_mask = markWord::inline_type_pattern; + Label has_identity; + __ ldr(rscratch1, Address(r0, oopDesc::mark_offset_in_bytes())); + __ mov(rscratch2, is_inline_type_mask); + __ andr(rscratch1, rscratch1, rscratch2); + __ cmp(rscratch1, rscratch2); + __ br(Assembler::NE, has_identity); + __ call_VM(noreg, CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_illegal_monitor_state_exception)); + __ should_not_reach_here(); + __ bind(has_identity); + const Address monitor_block_top( rfp, frame::interpreter_frame_monitor_block_top_offset * wordSize); const Address monitor_block_bot( diff --git a/src/hotspot/cpu/aarch64/templateTable_aarch64.hpp b/src/hotspot/cpu/aarch64/templateTable_aarch64.hpp index c51c111a6f8..1f82613f4af 100644 --- a/src/hotspot/cpu/aarch64/templateTable_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/templateTable_aarch64.hpp @@ -34,4 +34,6 @@ static void index_check(Register array, Register index); static void index_check_without_pop(Register array, Register index); + static void invoke_is_substitutable(Register aobj, Register bobj, Label& is_subst, Label& not_subst); + #endif // CPU_AARCH64_TEMPLATETABLE_AARCH64_HPP diff --git a/src/hotspot/cpu/aarch64/vtableStubs_aarch64.cpp b/src/hotspot/cpu/aarch64/vtableStubs_aarch64.cpp index 714904ab3df..30c8554795c 100644 --- a/src/hotspot/cpu/aarch64/vtableStubs_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/vtableStubs_aarch64.cpp @@ -47,10 +47,10 @@ extern "C" void bad_compiled_vtable_index(JavaThread* thread, oop receiver, int index); #endif -VtableStub* VtableStubs::create_vtable_stub(int vtable_index) { +VtableStub* VtableStubs::create_vtable_stub(int vtable_index, bool caller_is_c1) { // Read "A word on VtableStub sizing" in share/code/vtableStubs.hpp for details on stub sizing. const int stub_code_length = code_size_limit(true); - VtableStub* s = new(stub_code_length) VtableStub(true, vtable_index); + VtableStub* s = new(stub_code_length) VtableStub(true, vtable_index, caller_is_c1); // Can be null if there is no free space in the code cache. if (s == nullptr) { return nullptr; @@ -63,6 +63,10 @@ VtableStub* VtableStubs::create_vtable_stub(int vtable_index) { int slop_bytes = 0; int slop_delta = 0; +// No variance was detected in vtable stub sizes. Setting index_dependent_slop == 0 will unveil any deviation from this observation. + const int index_dependent_slop = 0; + ByteSize entry_offset = caller_is_c1 ? Method::from_compiled_inline_offset() : Method::from_compiled_inline_ro_offset(); + ResourceMark rm; CodeBuffer cb(s->entry_point(), stub_code_length); MacroAssembler* masm = new MacroAssembler(&cb); @@ -116,7 +120,7 @@ VtableStub* VtableStubs::create_vtable_stub(int vtable_index) { if (DebugVtables) { Label L; __ cbz(rmethod, L); - __ ldr(rscratch1, Address(rmethod, Method::from_compiled_offset())); + __ ldr(rscratch1, Address(rmethod, entry_offset)); __ cbnz(rscratch1, L); __ stop("Vtable entry is null"); __ bind(L); @@ -127,20 +131,21 @@ VtableStub* VtableStubs::create_vtable_stub(int vtable_index) { // rmethod: Method* // r2: receiver address ame_addr = __ pc(); - __ ldr(rscratch1, Address(rmethod, Method::from_compiled_offset())); + __ ldr(rscratch1, Address(rmethod, entry_offset)); __ br(rscratch1); masm->flush(); - bookkeeping(masm, tty, s, npe_addr, ame_addr, true, vtable_index, slop_bytes, 0); + slop_bytes += index_dependent_slop; // add'l slop for size variance due to large itable offsets + bookkeeping(masm, tty, s, npe_addr, ame_addr, true, vtable_index, slop_bytes, index_dependent_slop); return s; } -VtableStub* VtableStubs::create_itable_stub(int itable_index) { +VtableStub* VtableStubs::create_itable_stub(int itable_index, bool caller_is_c1) { // Read "A word on VtableStub sizing" in share/code/vtableStubs.hpp for details on stub sizing. const int stub_code_length = code_size_limit(false); - VtableStub* s = new(stub_code_length) VtableStub(false, itable_index); + VtableStub* s = new(stub_code_length) VtableStub(false, itable_index, caller_is_c1); // Can be null if there is no free space in the code cache. if (s == nullptr) { return nullptr; @@ -153,6 +158,10 @@ VtableStub* VtableStubs::create_itable_stub(int itable_index) { int slop_bytes = 0; int slop_delta = 0; + const int index_dependent_slop = (itable_index == 0) ? 4 : // code size change with transition from 8-bit to 32-bit constant (@index == 16). + (itable_index < 16) ? 3 : 0; // index == 0 generates even shorter code. + ByteSize entry_offset = caller_is_c1 ? Method::from_compiled_inline_offset() : Method::from_compiled_inline_ro_offset(); + ResourceMark rm; CodeBuffer cb(s->entry_point(), stub_code_length); MacroAssembler* masm = new MacroAssembler(&cb); @@ -207,7 +216,7 @@ VtableStub* VtableStubs::create_itable_stub(int itable_index) { if (DebugVtables) { Label L2; __ cbz(rmethod, L2); - __ ldr(rscratch1, Address(rmethod, Method::from_compiled_offset())); + __ ldr(rscratch1, Address(rmethod, entry_offset)); __ cbnz(rscratch1, L2); __ stop("compiler entrypoint is null"); __ bind(L2); @@ -217,7 +226,7 @@ VtableStub* VtableStubs::create_itable_stub(int itable_index) { // rmethod: Method* // j_rarg0: receiver address ame_addr = __ pc(); - __ ldr(rscratch1, Address(rmethod, Method::from_compiled_offset())); + __ ldr(rscratch1, Address(rmethod, entry_offset)); __ br(rscratch1); __ bind(L_no_such_interface); @@ -230,7 +239,8 @@ VtableStub* VtableStubs::create_itable_stub(int itable_index) { __ far_jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); masm->flush(); - bookkeeping(masm, tty, s, npe_addr, ame_addr, false, itable_index, slop_bytes, 0); + slop_bytes += index_dependent_slop; // add'l slop for size variance due to large itable offsets + bookkeeping(masm, tty, s, npe_addr, ame_addr, false, itable_index, slop_bytes, index_dependent_slop); return s; } diff --git a/src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp b/src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp index c3b91e8c76f..3e00cdd8427 100644 --- a/src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp +++ b/src/hotspot/cpu/arm/c1_LIRAssembler_arm.cpp @@ -2552,6 +2552,10 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) { fatal("Type profiling not implemented on this platform"); } +void LIR_Assembler::emit_profile_inline_type(LIR_OpProfileInlineType* op) { + Unimplemented(); +} + void LIR_Assembler::emit_delay(LIR_OpDelay*) { Unimplemented(); } diff --git a/src/hotspot/cpu/arm/continuationHelper_arm.inline.hpp b/src/hotspot/cpu/arm/continuationHelper_arm.inline.hpp index 4bcff7512e6..6d5faf7a83d 100644 --- a/src/hotspot/cpu/arm/continuationHelper_arm.inline.hpp +++ b/src/hotspot/cpu/arm/continuationHelper_arm.inline.hpp @@ -107,7 +107,7 @@ inline address ContinuationHelper::Frame::real_pc(const frame& f) { return nullptr; } -inline void ContinuationHelper::Frame::patch_pc(const frame& f, address pc) { +inline void ContinuationHelper::Frame::patch_pc(const frame& f, address pc, bool callee_augmented) { Unimplemented(); } diff --git a/src/hotspot/cpu/arm/templateInterpreterGenerator_arm.cpp b/src/hotspot/cpu/arm/templateInterpreterGenerator_arm.cpp index 8abefe39b2d..6ecdc29cf45 100644 --- a/src/hotspot/cpu/arm/templateInterpreterGenerator_arm.cpp +++ b/src/hotspot/cpu/arm/templateInterpreterGenerator_arm.cpp @@ -1139,7 +1139,7 @@ address TemplateInterpreterGenerator::generate_native_entry(bool synchronized) { // // Generic interpreted method entry to (asm) interpreter // -address TemplateInterpreterGenerator::generate_normal_entry(bool synchronized) { +address TemplateInterpreterGenerator::generate_normal_entry(bool synchronized, bool object_init) { // determine code generation flags bool inc_counter = UseCompiler || CountCompiledCalls; @@ -1254,6 +1254,12 @@ address TemplateInterpreterGenerator::generate_normal_entry(bool synchronized) { #endif } + // Issue a StoreStore barrier on entry to Object_init if the + // class has strict field fields. Be lazy, always do it. + if (object_init) { + __ membar(MacroAssembler::StoreStore, R1_tmp); + } + // start execution #ifdef ASSERT { Label L; diff --git a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp index 73509c22134..d8901f5139a 100644 --- a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp @@ -3130,6 +3130,9 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) { __ bind(Ldone); } +void LIR_Assembler::emit_profile_inline_type(LIR_OpProfileInlineType* op) { + Unimplemented(); +} void LIR_Assembler::emit_updatecrc32(LIR_OpUpdateCRC32* op) { assert(op->crc()->is_single_cpu(), "crc must be register"); diff --git a/src/hotspot/cpu/ppc/c1_Runtime1_ppc.cpp b/src/hotspot/cpu/ppc/c1_Runtime1_ppc.cpp index 09efa2c841b..78e41cf138c 100644 --- a/src/hotspot/cpu/ppc/c1_Runtime1_ppc.cpp +++ b/src/hotspot/cpu/ppc/c1_Runtime1_ppc.cpp @@ -442,7 +442,7 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { #ifdef ASSERT // Assert object type is really an array of the proper kind. { - int tag = (id == StubId::c1_new_type_array_id) ? Klass::_lh_array_tag_type_value : Klass::_lh_array_tag_obj_value; + int tag = (id == StubId::c1_new_type_array_id) ? Klass::_lh_array_tag_type_value : Klass::_lh_array_tag_ref_value; Label ok; __ lwz(R0, in_bytes(Klass::layout_helper_offset()), R4_ARG2); __ srawi(R0, R0, Klass::_lh_array_tag_shift); diff --git a/src/hotspot/cpu/ppc/continuationHelper_ppc.inline.hpp b/src/hotspot/cpu/ppc/continuationHelper_ppc.inline.hpp index d55bf0da3e3..2ab055f9177 100644 --- a/src/hotspot/cpu/ppc/continuationHelper_ppc.inline.hpp +++ b/src/hotspot/cpu/ppc/continuationHelper_ppc.inline.hpp @@ -110,7 +110,7 @@ inline address ContinuationHelper::Frame::real_pc(const frame& f) { return (address)f.own_abi()->lr; } -inline void ContinuationHelper::Frame::patch_pc(const frame& f, address pc) { +inline void ContinuationHelper::Frame::patch_pc(const frame& f, address pc, bool callee_augmented) { f.own_abi()->lr = (uint64_t)pc; } diff --git a/src/hotspot/cpu/ppc/globals_ppc.hpp b/src/hotspot/cpu/ppc/globals_ppc.hpp index 65334bf0389..f88b74b7d55 100644 --- a/src/hotspot/cpu/ppc/globals_ppc.hpp +++ b/src/hotspot/cpu/ppc/globals_ppc.hpp @@ -70,6 +70,9 @@ define_pd_global(bool, RewriteFrequentPairs, true); define_pd_global(bool, PreserveFramePointer, false); +define_pd_global(bool, InlineTypePassFieldsAsArgs, false); +define_pd_global(bool, InlineTypeReturnedAsFields, false); + define_pd_global(uintx, TypeProfileLevel, 111); define_pd_global(bool, CompactStrings, true); diff --git a/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp b/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp index 8df2cc5d273..1f6e8451154 100644 --- a/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp +++ b/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp @@ -1687,7 +1687,7 @@ void InterpreterMacroAssembler::profile_arguments_type(Register callee, // argument. tmp1 is the number of cells left in the // CallTypeData/VirtualCallTypeData to reach its end. Non null // if there's a return to profile. - assert(ReturnTypeEntry::static_cell_count() < TypeStackSlotEntries::per_arg_count(), + assert(SingleTypeEntry::static_cell_count() < TypeStackSlotEntries::per_arg_count(), "can't move past ret type"); sldi(tmp1, tmp1, exact_log2(DataLayout::cell_size)); add(R28_mdx, tmp1, R28_mdx); @@ -1728,7 +1728,7 @@ void InterpreterMacroAssembler::profile_return_type(Register ret, Register tmp1, bne(CR0, profile_continue); } - profile_obj_type(ret, R28_mdx, -in_bytes(ReturnTypeEntry::size()), tmp1, tmp2); + profile_obj_type(ret, R28_mdx, -in_bytes(SingleTypeEntry::size()), tmp1, tmp2); align(32, 12); bind(profile_continue); diff --git a/src/hotspot/cpu/ppc/templateInterpreterGenerator_ppc.cpp b/src/hotspot/cpu/ppc/templateInterpreterGenerator_ppc.cpp index 199b578a36f..57224bfc688 100644 --- a/src/hotspot/cpu/ppc/templateInterpreterGenerator_ppc.cpp +++ b/src/hotspot/cpu/ppc/templateInterpreterGenerator_ppc.cpp @@ -1706,7 +1706,7 @@ address TemplateInterpreterGenerator::generate_native_entry(bool synchronized) { // Generic interpreted method entry to (asm) interpreter. // -address TemplateInterpreterGenerator::generate_normal_entry(bool synchronized) { +address TemplateInterpreterGenerator::generate_normal_entry(bool synchronized, bool object_init) { bool inc_counter = UseCompiler || CountCompiledCalls; address entry = __ pc(); // Generate the code to allocate the interpreter stack frame. @@ -1801,6 +1801,13 @@ address TemplateInterpreterGenerator::generate_normal_entry(bool synchronized) { // JVMTI support __ notify_method_entry(); + // -------------------------------------------------------------------------- + // Issue a StoreStore barrier on entry to Object_init if the + // class has strict field fields. Be lazy, always do it. + if (object_init) { + __ membar(Assembler::StoreStore); + } + // -------------------------------------------------------------------------- // Start executing instructions. __ dispatch_next(vtos); diff --git a/src/hotspot/cpu/riscv/c1_Runtime1_riscv.cpp b/src/hotspot/cpu/riscv/c1_Runtime1_riscv.cpp index a06584e9411..90ad98c34d0 100644 --- a/src/hotspot/cpu/riscv/c1_Runtime1_riscv.cpp +++ b/src/hotspot/cpu/riscv/c1_Runtime1_riscv.cpp @@ -753,7 +753,7 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { Register tmp = obj; __ lwu(tmp, Address(klass, Klass::layout_helper_offset())); __ sraiw(tmp, tmp, Klass::_lh_array_tag_shift); - int tag = ((id == StubId::c1_new_type_array_id) ? Klass::_lh_array_tag_type_value : Klass::_lh_array_tag_obj_value); + int tag = ((id == StubId::c1_new_type_array_id) ? Klass::_lh_array_tag_type_value : Klass::_lh_array_tag_ref_value); __ mv(t0, tag); __ beq(t0, tmp, ok); __ stop("assert(is an array klass)"); diff --git a/src/hotspot/cpu/riscv/continuationHelper_riscv.inline.hpp b/src/hotspot/cpu/riscv/continuationHelper_riscv.inline.hpp index 424d56edf5a..28b3460232e 100644 --- a/src/hotspot/cpu/riscv/continuationHelper_riscv.inline.hpp +++ b/src/hotspot/cpu/riscv/continuationHelper_riscv.inline.hpp @@ -128,7 +128,7 @@ inline address ContinuationHelper::Frame::real_pc(const frame& f) { return *pc_addr; } -inline void ContinuationHelper::Frame::patch_pc(const frame& f, address pc) { +inline void ContinuationHelper::Frame::patch_pc(const frame& f, address pc, bool callee_augmented) { address* pc_addr = &(((address*) f.sp())[-1]); *pc_addr = pc; } diff --git a/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp b/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp index 61f4aa3e722..14775a969d2 100644 --- a/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp +++ b/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp @@ -1540,6 +1540,12 @@ address TemplateInterpreterGenerator::generate_normal_entry(bool synchronized) { #endif } + // Issue a StoreStore barrier on entry to Object_init if the + // class has strict field fields. Be lazy, always do it. + if (object_init) { + __ membar(MacroAssembler::StoreStore); + } + // start execution #ifdef ASSERT __ verify_frame_setup(); diff --git a/src/hotspot/cpu/riscv/templateTable_riscv.cpp b/src/hotspot/cpu/riscv/templateTable_riscv.cpp index 1011b8f1e7b..7fc38042d4e 100644 --- a/src/hotspot/cpu/riscv/templateTable_riscv.cpp +++ b/src/hotspot/cpu/riscv/templateTable_riscv.cpp @@ -2103,7 +2103,8 @@ void TemplateTable::_return(TosState state) { // Issue a StoreStore barrier after all stores but before return // from any constructor for any class with a final field. We don't // know if this is a finalizer, so we always do so. - if (_desc->bytecode() == Bytecodes::_return) { + if (_desc->bytecode() == Bytecodes::_return + || _desc->bytecode() == Bytecodes::_return_register_finalizer) { __ membar(MacroAssembler::StoreStore); } diff --git a/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp b/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp index 87dc8b9286d..5c0a362b1f0 100644 --- a/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp +++ b/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp @@ -3059,6 +3059,10 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) { } } +void LIR_Assembler::emit_profile_inline_type(LIR_OpProfileInlineType* op) { + Unimplemented(); +} + void LIR_Assembler::emit_updatecrc32(LIR_OpUpdateCRC32* op) { assert(op->crc()->is_single_cpu(), "crc must be register"); assert(op->val()->is_single_cpu(), "byte value must be register"); diff --git a/src/hotspot/cpu/s390/c1_Runtime1_s390.cpp b/src/hotspot/cpu/s390/c1_Runtime1_s390.cpp index e78b04fe911..01fe91928f4 100644 --- a/src/hotspot/cpu/s390/c1_Runtime1_s390.cpp +++ b/src/hotspot/cpu/s390/c1_Runtime1_s390.cpp @@ -396,8 +396,7 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { __ mem2reg_opt(t0, Address(klass, Klass::layout_helper_offset()), false); __ z_sra(t0, Klass::_lh_array_tag_shift); int tag = ((id == StubId::c1_new_type_array_id) - ? Klass::_lh_array_tag_type_value - : Klass::_lh_array_tag_obj_value); + ? Klass::_lh_array_tag_type_value : Klass::_lh_array_tag_ref_value); __ compare32_and_branch(t0, tag, Assembler::bcondEqual, ok); __ stop("assert(is an array klass)"); __ should_not_reach_here(); diff --git a/src/hotspot/cpu/s390/continuationHelper_s390.inline.hpp b/src/hotspot/cpu/s390/continuationHelper_s390.inline.hpp index fb7d998c458..b0e5c6805b4 100644 --- a/src/hotspot/cpu/s390/continuationHelper_s390.inline.hpp +++ b/src/hotspot/cpu/s390/continuationHelper_s390.inline.hpp @@ -107,7 +107,7 @@ inline address ContinuationHelper::Frame::real_pc(const frame& f) { return nullptr; } -inline void ContinuationHelper::Frame::patch_pc(const frame& f, address pc) { +inline void ContinuationHelper::Frame::patch_pc(const frame& f, address pc, bool callee_augmented) { Unimplemented(); } diff --git a/src/hotspot/cpu/s390/interp_masm_s390.cpp b/src/hotspot/cpu/s390/interp_masm_s390.cpp index aa3d4849062..e5776be2823 100644 --- a/src/hotspot/cpu/s390/interp_masm_s390.cpp +++ b/src/hotspot/cpu/s390/interp_masm_s390.cpp @@ -1644,7 +1644,7 @@ void InterpreterMacroAssembler::profile_arguments_type(Register mdp, Register ca // argument. Tmp is the number of cells left in the // CallTypeData/VirtualCallTypeData to reach its end. Non null // if there's a return to profile. - assert(ReturnTypeEntry::static_cell_count() < TypeStackSlotEntries::per_arg_count(), "can't move past ret type"); + assert(SingleTypeEntry::static_cell_count() < TypeStackSlotEntries::per_arg_count(), "can't move past ret type"); z_sllg(tmp, tmp, exact_log2(DataLayout::cell_size)); z_agr(mdp, tmp); } @@ -1693,7 +1693,7 @@ void InterpreterMacroAssembler::profile_return_type(Register mdp, Register ret, bind(do_profile); } - Address mdo_ret_addr(mdp, -in_bytes(ReturnTypeEntry::size())); + Address mdo_ret_addr(mdp, -in_bytes(SingleTypeEntry::size())); profile_obj_type(ret, mdo_ret_addr, tmp); bind(profile_continue); diff --git a/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp b/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp index 3f568572966..2e031932261 100644 --- a/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp +++ b/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp @@ -1797,6 +1797,12 @@ address TemplateInterpreterGenerator::generate_normal_entry(bool synchronized) { #endif // ASSERT } + // If object_init == true, we should insert a StoreStore barrier here to + // prevent strict fields initial default values from being observable. + // However, s390 is a TSO platform, so if `this` escapes, strict fields + // initialized values are guaranteed to be the ones observed, so the + // barrier can be elided. + // start execution #ifdef ASSERT diff --git a/src/hotspot/cpu/x86/c1_CodeStubs_x86.cpp b/src/hotspot/cpu/x86/c1_CodeStubs_x86.cpp index 2fd067a7749..153364b3c18 100644 --- a/src/hotspot/cpu/x86/c1_CodeStubs_x86.cpp +++ b/src/hotspot/cpu/x86/c1_CodeStubs_x86.cpp @@ -29,6 +29,7 @@ #include "c1/c1_Runtime1.hpp" #include "classfile/javaClasses.hpp" #include "nativeInst_x86.hpp" +#include "oops/objArrayKlass.hpp" #include "runtime/sharedRuntime.hpp" #include "utilities/align.hpp" #include "utilities/macros.hpp" @@ -115,6 +116,79 @@ void DivByZeroStub::emit_code(LIR_Assembler* ce) { } +// Implementation of LoadFlattenedArrayStub + +LoadFlattenedArrayStub::LoadFlattenedArrayStub(LIR_Opr array, LIR_Opr index, LIR_Opr result, CodeEmitInfo* info) { + _array = array; + _index = index; + _result = result; + // Tell the register allocator that the runtime call will scratch rax. + _scratch_reg = FrameMap::rax_oop_opr; + _info = new CodeEmitInfo(info); +} + +void LoadFlattenedArrayStub::emit_code(LIR_Assembler* ce) { + assert(__ rsp_offset() == 0, "frame size should be fixed"); + __ bind(_entry); + ce->store_parameter(_array->as_register(), 1); + ce->store_parameter(_index->as_register(), 0); + __ call(RuntimeAddress(Runtime1::entry_for(StubId::c1_load_flat_array_id))); + ce->add_call_info_here(_info); + ce->verify_oop_map(_info); + if (_result->as_register() != rax) { + __ movptr(_result->as_register(), rax); + } + __ jmp(_continuation); +} + + +// Implementation of StoreFlattenedArrayStub + +StoreFlattenedArrayStub::StoreFlattenedArrayStub(LIR_Opr array, LIR_Opr index, LIR_Opr value, CodeEmitInfo* info) { + _array = array; + _index = index; + _value = value; + // Tell the register allocator that the runtime call will scratch rax. + _scratch_reg = FrameMap::rax_oop_opr; + _info = new CodeEmitInfo(info); +} + + +void StoreFlattenedArrayStub::emit_code(LIR_Assembler* ce) { + assert(__ rsp_offset() == 0, "frame size should be fixed"); + __ bind(_entry); + ce->store_parameter(_array->as_register(), 2); + ce->store_parameter(_index->as_register(), 1); + ce->store_parameter(_value->as_register(), 0); + __ call(RuntimeAddress(Runtime1::entry_for(StubId::c1_store_flat_array_id))); + ce->add_call_info_here(_info); + ce->verify_oop_map(_info); + __ jmp(_continuation); +} + + +// Implementation of SubstitutabilityCheckStub + +SubstitutabilityCheckStub::SubstitutabilityCheckStub(LIR_Opr left, LIR_Opr right, CodeEmitInfo* info) { + _left = left; + _right = right; + // Tell the register allocator that the runtime call will scratch rax. + _scratch_reg = FrameMap::rax_oop_opr; + _info = new CodeEmitInfo(info); +} + +void SubstitutabilityCheckStub::emit_code(LIR_Assembler* ce) { + assert(__ rsp_offset() == 0, "frame size should be fixed"); + __ bind(_entry); + ce->store_parameter(_left->as_register(), 1); + ce->store_parameter(_right->as_register(), 0); + __ call(RuntimeAddress(Runtime1::entry_for(StubId::c1_substitutability_check_id))); + ce->add_call_info_here(_info); + ce->verify_oop_map(_info); + __ jmp(_continuation); +} + + // Implementation of NewInstanceStub NewInstanceStub::NewInstanceStub(LIR_Opr klass_reg, LIR_Opr result, ciInstanceKlass* klass, CodeEmitInfo* info, StubId stub_id) { @@ -167,11 +241,13 @@ void NewTypeArrayStub::emit_code(LIR_Assembler* ce) { // Implementation of NewObjectArrayStub -NewObjectArrayStub::NewObjectArrayStub(LIR_Opr klass_reg, LIR_Opr length, LIR_Opr result, CodeEmitInfo* info) { +NewObjectArrayStub::NewObjectArrayStub(LIR_Opr klass_reg, LIR_Opr length, LIR_Opr result, + CodeEmitInfo* info, bool is_null_free) { _klass_reg = klass_reg; _result = result; _length = length; _info = new CodeEmitInfo(info); + _is_null_free = is_null_free; } @@ -180,7 +256,11 @@ void NewObjectArrayStub::emit_code(LIR_Assembler* ce) { __ bind(_entry); assert(_length->as_register() == rbx, "length must in rbx,"); assert(_klass_reg->as_register() == rdx, "klass_reg must in rdx"); - __ call(RuntimeAddress(Runtime1::entry_for(StubId::c1_new_object_array_id))); + if (_is_null_free) { + __ call(RuntimeAddress(Runtime1::entry_for(StubId::c1_new_null_free_array_id))); + } else { + __ call(RuntimeAddress(Runtime1::entry_for(StubId::c1_new_object_array_id))); + } ce->add_call_info_here(_info); ce->verify_oop_map(_info); assert(_result->as_register() == rax, "result must in rax,"); @@ -190,6 +270,15 @@ void NewObjectArrayStub::emit_code(LIR_Assembler* ce) { void MonitorEnterStub::emit_code(LIR_Assembler* ce) { assert(__ rsp_offset() == 0, "frame size should be fixed"); __ bind(_entry); + if (_throw_ie_stub != nullptr) { + // When we come here, _obj_reg has already been checked to be non-null. + const int is_value_mask = markWord::inline_type_pattern; + Register mark = _scratch_reg->as_register(); + __ movptr(mark, Address(_obj_reg->as_register(), oopDesc::mark_offset_in_bytes())); + __ andptr(mark, is_value_mask); + __ cmpl(mark, is_value_mask); + __ jcc(Assembler::equal, *_throw_ie_stub->entry()); + } ce->store_parameter(_obj_reg->as_register(), 1); ce->store_parameter(_lock_reg->as_register(), 0); StubId enter_id; diff --git a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp index a30bbe08c55..429029e47d5 100644 --- a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp @@ -31,11 +31,13 @@ #include "c1/c1_Runtime1.hpp" #include "c1/c1_ValueStack.hpp" #include "ci/ciArrayKlass.hpp" +#include "ci/ciInlineKlass.hpp" #include "ci/ciInstance.hpp" #include "compiler/oopMap.hpp" #include "gc/shared/collectedHeap.hpp" #include "gc/shared/gc_globals.hpp" #include "nativeInst_x86.hpp" +#include "oops/oop.inline.hpp" #include "oops/objArrayKlass.hpp" #include "runtime/frame.inline.hpp" #include "runtime/safepointMechanism.hpp" @@ -428,7 +430,7 @@ int LIR_Assembler::emit_unwind_handler() { } // remove the activation and dispatch to the unwind handler - __ remove_frame(initial_frame_size_in_bytes()); + __ remove_frame(initial_frame_size_in_bytes(), needs_stack_repair()); __ jump(RuntimeAddress(Runtime1::entry_for(StubId::c1_unwind_exception_id))); // Emit the slow path assembly @@ -465,9 +467,53 @@ void LIR_Assembler::return_op(LIR_Opr result, C1SafepointPollStub* code_stub) { if (!result->is_illegal() && result->is_float_kind() && !result->is_xmm_register()) { assert(result->fpu() == 0, "result must already be on TOS"); } + if (InlineTypeReturnedAsFields) { + #ifndef _LP64 + Unimplemented(); + #endif + // Check if we are returning an non-null inline type and load its fields into registers + ciType* return_type = compilation()->method()->return_type(); + if (return_type->is_inlinetype()) { + ciInlineKlass* vk = return_type->as_inline_klass(); + if (vk->can_be_returned_as_fields()) { + address unpack_handler = vk->unpack_handler(); + assert(unpack_handler != nullptr, "must be"); + __ call(RuntimeAddress(unpack_handler)); + } + } else if (return_type->is_instance_klass() && (!return_type->is_loaded() || StressCallingConvention)) { + Label skip; + Label not_null; + __ testptr(rax, rax); + __ jcc(Assembler::notZero, not_null); + // Returned value is null, zero all return registers because they may belong to oop fields + __ xorq(j_rarg1, j_rarg1); + __ xorq(j_rarg2, j_rarg2); + __ xorq(j_rarg3, j_rarg3); + __ xorq(j_rarg4, j_rarg4); + __ xorq(j_rarg5, j_rarg5); + __ jmp(skip); + __ bind(not_null); + + // Check if we are returning an non-null inline type and load its fields into registers + __ test_oop_is_not_inline_type(rax, rscratch1, skip, /* can_be_null= */ false); + + // Load fields from a buffered value with an inline class specific handler + __ load_klass(rdi, rax, rscratch1); + __ movptr(rdi, Address(rdi, InstanceKlass::adr_inlineklass_fixed_block_offset())); + __ movptr(rdi, Address(rdi, InlineKlass::unpack_handler_offset())); + // Unpack handler can be null if inline type is not scalarizable in returns + __ testptr(rdi, rdi); + __ jcc(Assembler::zero, skip); + __ call(rdi); + + __ bind(skip); + } + // At this point, rax points to the value object (for interpreter or C1 caller). + // The fields of the object are copied into registers (for C2 caller). + } // Pop the stack before the safepoint code - __ remove_frame(initial_frame_size_in_bytes()); + __ remove_frame(initial_frame_size_in_bytes(), needs_stack_repair()); if (StackReservedPages > 0 && compilation()->has_reserved_stack_access()) { __ reserved_stack_check(); @@ -483,6 +529,10 @@ void LIR_Assembler::return_op(LIR_Opr result, C1SafepointPollStub* code_stub) { } +int LIR_Assembler::store_inline_type_fields_to_buf(ciInlineKlass* vk) { + return (__ store_inline_type_fields_to_buf(vk, false)); +} + int LIR_Assembler::safepoint_poll(LIR_Opr tmp, CodeEmitInfo* info) { guarantee(info != nullptr, "Shouldn't be null"); int offset = __ offset(); @@ -1221,7 +1271,7 @@ void LIR_Assembler::emit_alloc_array(LIR_OpAllocArray* op) { Register len = op->len()->as_register(); __ movslq(len, len); - if (UseSlowPath || + if (UseSlowPath || op->always_slow_path() || (!UseFastNewObjectArray && is_reference_type(op->type())) || (!UseFastNewTypeArray && !is_reference_type(op->type()))) { __ jmp(*op->stub()->entry()); @@ -1320,30 +1370,32 @@ void LIR_Assembler::emit_typecheck_helper(LIR_OpTypeCheck *op, Label* success, L assert_different_registers(obj, k_RInfo, klass_RInfo); - __ testptr(obj, obj); - if (op->should_profile()) { - Label not_null; - Register mdo = klass_RInfo; - __ mov_metadata(mdo, md->constant_encoding()); - __ jccb(Assembler::notEqual, not_null); - // Object is null; update MDO and exit - Address data_addr(mdo, md->byte_offset_of_slot(data, DataLayout::flags_offset())); - int header_bits = BitData::null_seen_byte_constant(); - __ orb(data_addr, header_bits); - __ jmp(*obj_is_null); - __ bind(not_null); - - Label update_done; - Register recv = k_RInfo; - __ load_klass(recv, obj, tmp_load_klass); - type_profile_helper(mdo, md, data, recv, &update_done); - - Address nonprofiled_receiver_count_addr(mdo, md->byte_offset_of_slot(data, CounterData::count_offset())); - __ addptr(nonprofiled_receiver_count_addr, DataLayout::counter_increment); - - __ bind(update_done); - } else { - __ jcc(Assembler::equal, *obj_is_null); + if (op->need_null_check()) { + __ testptr(obj, obj); + if (op->should_profile()) { + Label not_null; + Register mdo = klass_RInfo; + __ mov_metadata(mdo, md->constant_encoding()); + __ jccb(Assembler::notEqual, not_null); + // Object is null; update MDO and exit + Address data_addr(mdo, md->byte_offset_of_slot(data, DataLayout::flags_offset())); + int header_bits = BitData::null_seen_byte_constant(); + __ orb(data_addr, header_bits); + __ jmp(*obj_is_null); + __ bind(not_null); + + Label update_done; + Register recv = k_RInfo; + __ load_klass(recv, obj, tmp_load_klass); + type_profile_helper(mdo, md, data, recv, &update_done); + + Address nonprofiled_receiver_count_addr(mdo, md->byte_offset_of_slot(data, CounterData::count_offset())); + __ addptr(nonprofiled_receiver_count_addr, DataLayout::counter_increment); + + __ bind(update_done); + } else { + __ jcc(Assembler::equal, *obj_is_null); + } } if (!k->is_loaded()) { @@ -1354,6 +1406,7 @@ void LIR_Assembler::emit_typecheck_helper(LIR_OpTypeCheck *op, Label* success, L __ verify_oop(obj); if (op->fast_check()) { + // TODO 8366668 Is this correct? I don't think so. Probably we now always go to the slow path here. Same on AArch64. // get object class // not a safepoint as obj null check happens earlier if (UseCompressedClassPointers) { @@ -1513,6 +1566,109 @@ void LIR_Assembler::emit_opTypeCheck(LIR_OpTypeCheck* op) { } +void LIR_Assembler::emit_opFlattenedArrayCheck(LIR_OpFlattenedArrayCheck* op) { + // We are loading/storing from/to an array that *may* be a flat array (the + // declared type is Object[], abstract[], interface[] or VT.ref[]). + // If this array is a flat array, take the slow path. + __ test_flat_array_oop(op->array()->as_register(), op->tmp()->as_register(), *op->stub()->entry()); + if (!op->value()->is_illegal()) { + // TODO 8350865 This is also used for profiling code, right? And in that case we don't care about null but just want to know if the array is flat or not. + // The array is not a flat array, but it might be null-free. If we are storing + // a null into a null-free array, take the slow path (which will throw NPE). + Label skip; + __ cmpptr(op->value()->as_register(), NULL_WORD); + __ jcc(Assembler::notEqual, skip); + __ test_null_free_array_oop(op->array()->as_register(), op->tmp()->as_register(), *op->stub()->entry()); + __ bind(skip); + } +} + +void LIR_Assembler::emit_opNullFreeArrayCheck(LIR_OpNullFreeArrayCheck* op) { + // We are storing into an array that *may* be null-free (the declared type is + // Object[], abstract[], interface[] or VT.ref[]). + Label test_mark_word; + Register tmp = op->tmp()->as_register(); + __ movptr(tmp, Address(op->array()->as_register(), oopDesc::mark_offset_in_bytes())); + __ testl(tmp, markWord::unlocked_value); + __ jccb(Assembler::notZero, test_mark_word); + __ load_prototype_header(tmp, op->array()->as_register(), rscratch1); + __ bind(test_mark_word); + __ testl(tmp, markWord::null_free_array_bit_in_place); +} + +void LIR_Assembler::emit_opSubstitutabilityCheck(LIR_OpSubstitutabilityCheck* op) { + Label L_oops_equal; + Label L_oops_not_equal; + Label L_end; + + Register left = op->left()->as_register(); + Register right = op->right()->as_register(); + + __ cmpptr(left, right); + __ jcc(Assembler::equal, L_oops_equal); + + // (1) Null check -- if one of the operands is null, the other must not be null (because + // the two references are not equal), so they are not substitutable, + // FIXME: do null check only if the operand is nullable + __ testptr(left, right); + __ jcc(Assembler::zero, L_oops_not_equal); + + ciKlass* left_klass = op->left_klass(); + ciKlass* right_klass = op->right_klass(); + + // (2) Inline type check -- if either of the operands is not a inline type, + // they are not substitutable. We do this only if we are not sure that the + // operands are inline type + if ((left_klass == nullptr || right_klass == nullptr) ||// The klass is still unloaded, or came from a Phi node. + !left_klass->is_inlinetype() || !right_klass->is_inlinetype()) { + Register tmp1 = op->tmp1()->as_register(); + __ movptr(tmp1, (intptr_t)markWord::inline_type_pattern); + __ andptr(tmp1, Address(left, oopDesc::mark_offset_in_bytes())); + __ andptr(tmp1, Address(right, oopDesc::mark_offset_in_bytes())); + __ cmpptr(tmp1, (intptr_t)markWord::inline_type_pattern); + __ jcc(Assembler::notEqual, L_oops_not_equal); + } + + // (3) Same klass check: if the operands are of different klasses, they are not substitutable. + if (left_klass != nullptr && left_klass->is_inlinetype() && left_klass == right_klass) { + // No need to load klass -- the operands are statically known to be the same inline klass. + __ jmp(*op->stub()->entry()); + } else { + Register left_klass_op = op->left_klass_op()->as_register(); + Register right_klass_op = op->right_klass_op()->as_register(); + + if (UseCompressedClassPointers) { + __ movl(left_klass_op, Address(left, oopDesc::klass_offset_in_bytes())); + __ movl(right_klass_op, Address(right, oopDesc::klass_offset_in_bytes())); + __ cmpl(left_klass_op, right_klass_op); + } else { + __ movptr(left_klass_op, Address(left, oopDesc::klass_offset_in_bytes())); + __ movptr(right_klass_op, Address(right, oopDesc::klass_offset_in_bytes())); + __ cmpptr(left_klass_op, right_klass_op); + } + + __ jcc(Assembler::equal, *op->stub()->entry()); // same klass -> do slow check + // fall through to L_oops_not_equal + } + + __ bind(L_oops_not_equal); + move(op->not_equal_result(), op->result_opr()); + __ jmp(L_end); + + __ bind(L_oops_equal); + move(op->equal_result(), op->result_opr()); + __ jmp(L_end); + + // We've returned from the stub. RAX contains 0x0 IFF the two + // operands are not substitutable. (Don't compare against 0x1 in case the + // C compiler is naughty) + __ bind(*op->stub()->continuation()); + __ cmpl(rax, 0); + __ jcc(Assembler::equal, L_oops_not_equal); // (call_stub() == 0x0) -> not_equal + move(op->equal_result(), op->result_opr()); // (call_stub() != 0x0) -> equal + // fall-through + __ bind(L_end); +} void LIR_Assembler::emit_compare_and_swap(LIR_OpCompareAndSwap* op) { if (op->code() == lir_cas_int || op->code() == lir_cas_obj) { @@ -1558,6 +1714,21 @@ void LIR_Assembler::emit_compare_and_swap(LIR_OpCompareAndSwap* op) { } } +void LIR_Assembler::move(LIR_Opr src, LIR_Opr dst) { + assert(dst->is_cpu_register(), "must be"); + assert(dst->type() == src->type(), "must be"); + + if (src->is_cpu_register()) { + reg2reg(src, dst); + } else if (src->is_stack()) { + stack2reg(src, dst, dst->type()); + } else if (src->is_constant()) { + const2reg(src, dst, lir_patch_none, nullptr); + } else { + ShouldNotReachHere(); + } +} + void LIR_Assembler::cmove(LIR_Condition condition, LIR_Opr opr1, LIR_Opr opr2, LIR_Opr result, BasicType type, LIR_Opr cmp_opr1, LIR_Opr cmp_opr2) { assert(cmp_opr1 == LIR_OprFact::illegalOpr && cmp_opr2 == LIR_OprFact::illegalOpr, "unnecessary cmp oprs on x86"); @@ -2168,14 +2339,14 @@ void LIR_Assembler::call(LIR_OpJavaCall* op, relocInfo::relocType rtype) { assert((__ offset() + NativeCall::displacement_offset) % BytesPerWord == 0, "must be aligned"); __ call(AddressLiteral(op->addr(), rtype)); - add_call_info(code_offset(), op->info()); + add_call_info(code_offset(), op->info(), op->maybe_return_as_fields()); __ post_call_nop(); } void LIR_Assembler::ic_call(LIR_OpJavaCall* op) { __ ic_call(op->addr()); - add_call_info(code_offset(), op->info()); + add_call_info(code_offset(), op->info(), op->maybe_return_as_fields()); assert((__ offset() - NativeCall::instruction_size + NativeCall::displacement_offset) % BytesPerWord == 0, "must be aligned"); __ post_call_nop(); @@ -2342,6 +2513,21 @@ void LIR_Assembler::store_parameter(Metadata* m, int offset_from_rsp_in_words) { } +void LIR_Assembler::arraycopy_inlinetype_check(Register obj, Register tmp, CodeStub* slow_path, bool is_dest, bool null_check) { + if (null_check) { + __ testptr(obj, obj); + __ jcc(Assembler::zero, *slow_path->entry()); + } + if (is_dest) { + __ test_null_free_array_oop(obj, tmp, *slow_path->entry()); + // TODO 8350865 Flat no longer implies null-free, so we need to check for flat dest. Can we do better here? + __ test_flat_array_oop(obj, tmp, *slow_path->entry()); + } else { + __ test_flat_array_oop(obj, tmp, *slow_path->entry()); + } +} + + // This code replaces a call to arraycopy; no exception may // be thrown in this code, they must be thrown in the System.arraycopy // activation frame; we could save some checks if this would not be the case @@ -2361,6 +2547,12 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { BasicType basic_type = default_type != nullptr ? default_type->element_type()->basic_type() : T_ILLEGAL; if (is_reference_type(basic_type)) basic_type = T_OBJECT; + if (flags & LIR_OpArrayCopy::always_slow_path) { + __ jmp(*stub->entry()); + __ bind(*stub->continuation()); + return; + } + // if we don't know anything, just go through the generic arraycopy if (default_type == nullptr) { // save outgoing arguments on stack in case call to System.arraycopy is needed @@ -2437,6 +2629,14 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { return; } + // Handle inline type arrays + if (flags & LIR_OpArrayCopy::src_inlinetype_check) { + arraycopy_inlinetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check)); + } + if (flags & LIR_OpArrayCopy::dst_inlinetype_check) { + arraycopy_inlinetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check)); + } + assert(default_type != nullptr && default_type->is_array_klass() && default_type->is_loaded(), "must be true at this point"); int elem_size = type2aelembytes(basic_type); @@ -2663,6 +2863,7 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { // subtype which we can't check or src is the same array as dst // but not necessarily exactly of type default_type. Label known_ok, halt; + __ mov_metadata(tmp, default_type->constant_encoding()); if (UseCompressedClassPointers) { __ encode_klass_not_null(tmp, rscratch1); @@ -3001,6 +3202,26 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) { __ bind(next); } +void LIR_Assembler::emit_profile_inline_type(LIR_OpProfileInlineType* op) { + Register obj = op->obj()->as_register(); + Register tmp = op->tmp()->as_pointer_register(); + Address mdo_addr = as_Address(op->mdp()->as_address_ptr()); + bool not_null = op->not_null(); + int flag = op->flag(); + + Label not_inline_type; + if (!not_null) { + __ testptr(obj, obj); + __ jccb(Assembler::zero, not_inline_type); + } + + __ test_oop_is_not_inline_type(obj, tmp, not_inline_type); + + __ orb(mdo_addr, flag); + + __ bind(not_inline_type); +} + void LIR_Assembler::emit_delay(LIR_OpDelay*) { Unimplemented(); } @@ -3191,6 +3412,9 @@ void LIR_Assembler::get_thread(LIR_Opr result_reg) { __ mov(result_reg->as_register(), r15_thread); } +void LIR_Assembler::check_orig_pc() { + __ cmpptr(frame_map()->address_for_orig_pc_addr(), NULL_WORD); +} void LIR_Assembler::peephole(LIR_List*) { // do nothing for now diff --git a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.hpp b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.hpp index 8524dc90276..ba7e8bf8eda 100644 --- a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.hpp @@ -51,6 +51,9 @@ _deopt_handler_size = 17 }; + void arraycopy_inlinetype_check(Register obj, Register tmp, CodeStub* slow_path, bool is_dest, bool null_check); + void move(LIR_Opr src, LIR_Opr dst); + public: void store_parameter(Register r, int offset_from_esp_in_words); diff --git a/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp b/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp index 5459e8df229..42277d5d5b7 100644 --- a/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp +++ b/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp @@ -30,6 +30,7 @@ #include "c1/c1_Runtime1.hpp" #include "c1/c1_ValueStack.hpp" #include "ci/ciArray.hpp" +#include "ci/ciInlineKlass.hpp" #include "ci/ciObjArrayKlass.hpp" #include "ci/ciTypeArrayKlass.hpp" #include "gc/shared/c1/barrierSetC1.hpp" @@ -110,6 +111,19 @@ LIR_Opr LIRGenerator::rlock_byte(BasicType type) { } +void LIRGenerator::init_temps_for_substitutability_check(LIR_Opr& tmp1, LIR_Opr& tmp2) { + // We just need one 32-bit temp register for x86/x64, to check whether both + // oops have markWord::always_locked_pattern. See LIR_Assembler::emit_opSubstitutabilityCheck(). + // @temp = %r10d + // mov $0x405, %r10d + // and (%left), %r10d /* if need to check left */ + // and (%right), %r10d /* if need to check right */ + // cmp $0x405, $r10d + // jne L_oops_not_equal + tmp1 = new_register(T_INT); + tmp2 = LIR_OprFact::illegalOpr; +} + //--------- loading items into registers -------------------------------- @@ -281,17 +295,24 @@ void LIRGenerator::do_MonitorEnter(MonitorEnter* x) { // "lock" stores the address of the monitor stack slot, so this is not an oop LIR_Opr lock = new_register(T_INT); + // Need a scratch register for inline types on x86 + LIR_Opr scratch = new_register(T_ADDRESS); CodeEmitInfo* info_for_exception = nullptr; if (x->needs_null_check()) { info_for_exception = state_for(x); } + + CodeStub* throw_ie_stub = x->maybe_inlinetype() ? + new SimpleExceptionStub(StubId::c1_throw_identity_exception_id, + obj.result(), state_for(x)) + : nullptr; + // this CodeEmitInfo must not have the xhandlers because here the // object is already locked (xhandlers expect object to be unlocked) CodeEmitInfo* info = state_for(x, x->state(), true); - LIR_Opr tmp = new_register(T_ADDRESS); - monitor_enter(obj.result(), lock, syncTempOpr(), tmp, - x->monitor_no(), info_for_exception, info); + monitor_enter(obj.result(), lock, syncTempOpr(), scratch, + x->monitor_no(), info_for_exception, info, throw_ie_stub); } @@ -1129,19 +1150,19 @@ void LIRGenerator::do_Convert(Convert* x) { void LIRGenerator::do_NewInstance(NewInstance* x) { print_if_not_loaded(x); - CodeEmitInfo* info = state_for(x, x->state()); + CodeEmitInfo* info = state_for(x, x->needs_state_before() ? x->state_before() : x->state()); LIR_Opr reg = result_register_for(x->type()); new_instance(reg, x->klass(), x->is_unresolved(), - FrameMap::rcx_oop_opr, - FrameMap::rdi_oop_opr, - FrameMap::rsi_oop_opr, - LIR_OprFact::illegalOpr, - FrameMap::rdx_metadata_opr, info); + !x->is_unresolved() && x->klass()->is_inlinetype(), + FrameMap::rcx_oop_opr, + FrameMap::rdi_oop_opr, + FrameMap::rsi_oop_opr, + LIR_OprFact::illegalOpr, + FrameMap::rdx_metadata_opr, info); LIR_Opr result = rlock_result(x); __ move(reg, result); } - void LIRGenerator::do_NewTypeArray(NewTypeArray* x) { CodeEmitInfo* info = nullptr; if (x->state_before() != nullptr && x->state_before()->force_reexecute()) { @@ -1194,13 +1215,18 @@ void LIRGenerator::do_NewObjectArray(NewObjectArray* x) { length.load_item_force(FrameMap::rbx_opr); LIR_Opr len = length.result(); - CodeStub* slow_path = new NewObjectArrayStub(klass_reg, len, reg, info); - ciKlass* obj = (ciKlass*) ciObjArrayKlass::make(x->klass()); + ciKlass* obj = ciArrayKlass::make(x->klass(), false, true, true); + + // TODO 8265122 Implement a fast path for this + bool is_flat = obj->is_loaded() && obj->is_flat_array_klass(); + bool is_null_free = obj->is_loaded() && obj->as_array_klass()->is_elem_null_free(); + + CodeStub* slow_path = new NewObjectArrayStub(klass_reg, len, reg, info, is_null_free); if (obj == ciEnv::unloaded_ciobjarrayklass()) { BAILOUT("encountered unloaded_ciobjarrayklass due to out of memory error"); } klass2reg_with_patching(klass_reg, obj, patching_info); - __ allocate_array(reg, len, tmp1, tmp2, tmp3, tmp4, T_OBJECT, klass_reg, slow_path); + __ allocate_array(reg, len, tmp1, tmp2, tmp3, tmp4, T_OBJECT, klass_reg, slow_path, true, is_null_free || is_flat); LIR_Opr result = rlock_result(x); __ move(reg, result); @@ -1297,7 +1323,7 @@ void LIRGenerator::do_CheckCast(CheckCast* x) { __ checkcast(reg, obj.result(), x->klass(), new_register(objectType), new_register(objectType), tmp3, x->direct_compare(), info_for_exception, patching_info, stub, - x->profiled_method(), x->profiled_bci()); + x->profiled_method(), x->profiled_bci(), x->is_null_free()); } @@ -1353,7 +1379,7 @@ void LIRGenerator::do_If(If* x) { if (tag == longTag && yin->is_constant() && yin->get_jlong_constant() == 0 && (cond == If::eql || cond == If::neq)) { // inline long zero yin->dont_load_item(); - } else if (tag == longTag || tag == floatTag || tag == doubleTag) { + } else if (tag == longTag || tag == floatTag || tag == doubleTag || x->substitutability_check()) { // longs cannot handle constants at right side yin->load_item(); } else { @@ -1373,7 +1399,11 @@ void LIRGenerator::do_If(If* x) { __ safepoint(safepoint_poll_register(), state_for(x, x->state_before())); } - __ cmp(lir_cond(cond), left, right); + if (x->substitutability_check()) { + substitutability_check(x, *xin, *yin); + } else { + __ cmp(lir_cond(cond), left, right); + } // Generate branch profiling. Profiling code doesn't kill flags. profile_branch(x, cond); move_to_phi(x->state()); diff --git a/src/hotspot/cpu/x86/c1_MacroAssembler_x86.cpp b/src/hotspot/cpu/x86/c1_MacroAssembler_x86.cpp index 36efeafa940..fe5b3d8d425 100644 --- a/src/hotspot/cpu/x86/c1_MacroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/c1_MacroAssembler_x86.cpp @@ -34,6 +34,7 @@ #include "oops/arrayOop.hpp" #include "oops/markWord.hpp" #include "runtime/basicLock.hpp" +#include "runtime/frame.inline.hpp" #include "runtime/globals.hpp" #include "runtime/os.hpp" #include "runtime/sharedRuntime.hpp" @@ -82,17 +83,26 @@ void C1_MacroAssembler::try_allocate(Register obj, Register var_size_in_bytes, i void C1_MacroAssembler::initialize_header(Register obj, Register klass, Register len, Register t1, Register t2) { assert_different_registers(obj, klass, len, t1, t2); - if (UseCompactObjectHeaders) { + if (UseCompactObjectHeaders || EnableValhalla) { + // COH: Markword contains class pointer which is only known at runtime. + // Valhalla: Could have value class which has a different prototype header to a normal object. + // In both cases, we need to fetch dynamically. movptr(t1, Address(klass, Klass::prototype_header_offset())); movptr(Address(obj, oopDesc::mark_offset_in_bytes()), t1); - } else if (UseCompressedClassPointers) { // Take care not to kill klass - movptr(Address(obj, oopDesc::mark_offset_in_bytes()), checked_cast(markWord::prototype().value())); - movptr(t1, klass); - encode_klass_not_null(t1, rscratch1); - movl(Address(obj, oopDesc::klass_offset_in_bytes()), t1); } else { + // Otherwise: Can use the statically computed prototype header which is the same for every object. movptr(Address(obj, oopDesc::mark_offset_in_bytes()), checked_cast(markWord::prototype().value())); - movptr(Address(obj, oopDesc::klass_offset_in_bytes()), klass); + } + if (!UseCompactObjectHeaders) { + // COH: Markword already contains class pointer. Nothing else to do. + // Otherwise: Fetch klass pointer following the markword + if (UseCompressedClassPointers) { // Take care not to kill klass + movptr(t1, klass); + encode_klass_not_null(t1, rscratch1); + movl(Address(obj, oopDesc::klass_offset_in_bytes()), t1); + } else { + movptr(Address(obj, oopDesc::klass_offset_in_bytes()), klass); + } } if (len->is_valid()) { @@ -222,38 +232,109 @@ void C1_MacroAssembler::allocate_array(Register obj, Register len, Register t1, verify_oop(obj); } -void C1_MacroAssembler::build_frame(int frame_size_in_bytes, int bang_size_in_bytes) { - assert(bang_size_in_bytes >= frame_size_in_bytes, "stack bang size incorrect"); +void C1_MacroAssembler::build_frame_helper(int frame_size_in_bytes, int sp_offset_for_orig_pc, int sp_inc, bool reset_orig_pc, bool needs_stack_repair) { + push(rbp); + if (PreserveFramePointer) { + mov(rbp, rsp); + } + decrement(rsp, frame_size_in_bytes); + + if (needs_stack_repair) { + // Save stack increment (also account for fixed framesize and rbp) + assert((sp_inc & (StackAlignmentInBytes-1)) == 0, "stack increment not aligned"); + int real_frame_size = sp_inc + frame_size_in_bytes + wordSize; + movptr(Address(rsp, frame_size_in_bytes - wordSize), real_frame_size); + } + if (reset_orig_pc) { + // Zero orig_pc to detect deoptimization during buffering in the entry points + movptr(Address(rsp, sp_offset_for_orig_pc), 0); + } +} + +void C1_MacroAssembler::build_frame(int frame_size_in_bytes, int bang_size_in_bytes, int sp_offset_for_orig_pc, bool needs_stack_repair, bool has_scalarized_args, Label* verified_inline_entry_label) { // Make sure there is enough stack space for this method's activation. // Note that we do this before doing an enter(). This matches the // ordering of C2's stack overflow check / rsp decrement and allows // the SharedRuntime stack overflow handling to be consistent // between the two compilers. + assert(bang_size_in_bytes >= frame_size_in_bytes, "stack bang size incorrect"); generate_stack_overflow_check(bang_size_in_bytes); - push(rbp); - if (PreserveFramePointer) { - mov(rbp, rsp); - } - decrement(rsp, frame_size_in_bytes); // does not emit code for frame_size == 0 + build_frame_helper(frame_size_in_bytes, sp_offset_for_orig_pc, 0, has_scalarized_args, needs_stack_repair); BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); // C1 code is not hot enough to micro optimize the nmethod entry barrier with an out-of-line stub bs->nmethod_entry_barrier(this, nullptr /* slow_path */, nullptr /* continuation */); -} - -void C1_MacroAssembler::remove_frame(int frame_size_in_bytes) { - increment(rsp, frame_size_in_bytes); // Does not emit code for frame_size == 0 - pop(rbp); + if (verified_inline_entry_label != nullptr) { + // Jump here from the scalarized entry points that already created the frame. + bind(*verified_inline_entry_label); + } } - void C1_MacroAssembler::verified_entry(bool breakAtEntry) { if (breakAtEntry) int3(); // build frame } +int C1_MacroAssembler::scalarized_entry(const CompiledEntrySignature* ces, int frame_size_in_bytes, int bang_size_in_bytes, int sp_offset_for_orig_pc, Label& verified_inline_entry_label, bool is_inline_ro_entry) { + assert(InlineTypePassFieldsAsArgs, "sanity"); + // Make sure there is enough stack space for this method's activation. + assert(bang_size_in_bytes >= frame_size_in_bytes, "stack bang size incorrect"); + generate_stack_overflow_check(bang_size_in_bytes); + + GrowableArray* sig = ces->sig(); + GrowableArray* sig_cc = is_inline_ro_entry ? ces->sig_cc_ro() : ces->sig_cc(); + VMRegPair* regs = ces->regs(); + VMRegPair* regs_cc = is_inline_ro_entry ? ces->regs_cc_ro() : ces->regs_cc(); + int args_on_stack = ces->args_on_stack(); + int args_on_stack_cc = is_inline_ro_entry ? ces->args_on_stack_cc_ro() : ces->args_on_stack_cc(); + + assert(sig->length() <= sig_cc->length(), "Zero-sized inline class not allowed!"); + BasicType* sig_bt = NEW_RESOURCE_ARRAY(BasicType, sig_cc->length()); + int args_passed = sig->length(); + int args_passed_cc = SigEntry::fill_sig_bt(sig_cc, sig_bt); + + // Create a temp frame so we can call into the runtime. It must be properly set up to accommodate GC. + build_frame_helper(frame_size_in_bytes, sp_offset_for_orig_pc, 0, true, ces->c1_needs_stack_repair()); + + // The runtime call might safepoint, make sure nmethod entry barrier is executed + BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); + // C1 code is not hot enough to micro optimize the nmethod entry barrier with an out-of-line stub + bs->nmethod_entry_barrier(this, nullptr /* slow_path */, nullptr /* continuation */); + + // FIXME -- call runtime only if we cannot in-line allocate all the incoming inline type args. + movptr(rbx, (intptr_t)(ces->method())); + if (is_inline_ro_entry) { + call(RuntimeAddress(Runtime1::entry_for(StubId::c1_buffer_inline_args_no_receiver_id))); + } else { + call(RuntimeAddress(Runtime1::entry_for(StubId::c1_buffer_inline_args_id))); + } + int rt_call_offset = offset(); + + // Remove the temp frame + addptr(rsp, frame_size_in_bytes); + pop(rbp); + + // Check if we need to extend the stack for packing + int sp_inc = 0; + if (args_on_stack > args_on_stack_cc) { + sp_inc = extend_stack_for_inline_args(args_on_stack); + } + + shuffle_inline_args(true, is_inline_ro_entry, sig_cc, + args_passed_cc, args_on_stack_cc, regs_cc, // from + args_passed, args_on_stack, regs, // to + sp_inc, rax); + + // Create the real frame. Below jump will then skip over the stack banging and frame + // setup code in the verified_inline_entry (which has a different real_frame_size). + build_frame_helper(frame_size_in_bytes, sp_offset_for_orig_pc, sp_inc, false, ces->c1_needs_stack_repair()); + + jmp(verified_inline_entry_label); + return rt_call_offset; +} + void C1_MacroAssembler::load_parameter(int offset_in_words, Register reg) { // rbp, + 0: link // + 1: return address diff --git a/src/hotspot/cpu/x86/c1_Runtime1_x86.cpp b/src/hotspot/cpu/x86/c1_Runtime1_x86.cpp index 96439c71990..8db02d86c04 100644 --- a/src/hotspot/cpu/x86/c1_Runtime1_x86.cpp +++ b/src/hotspot/cpu/x86/c1_Runtime1_x86.cpp @@ -849,7 +849,8 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { __ enter(); OopMap* map = save_live_registers(sasm, 2); - int call_offset = __ call_RT(obj, noreg, CAST_FROM_FN_PTR(address, new_instance), klass); + int call_offset; + call_offset = __ call_RT(obj, noreg, CAST_FROM_FN_PTR(address, new_instance), klass); oop_maps = new OopMapSet(); oop_maps->add_gc_map(call_offset, map); restore_live_registers_except_rax(sasm); @@ -882,6 +883,7 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { case StubId::c1_new_type_array_id: case StubId::c1_new_object_array_id: + case StubId::c1_new_null_free_array_id: { Register length = rbx; // Incoming Register klass = rdx; // Incoming @@ -889,8 +891,10 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { if (id == StubId::c1_new_type_array_id) { __ set_info("new_type_array", dont_gc_arguments); - } else { + } else if (id == StubId::c1_new_object_array_id) { __ set_info("new_object_array", dont_gc_arguments); + } else { + __ set_info("new_null_free_array", dont_gc_arguments); } #ifdef ASSERT @@ -900,12 +904,28 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { Register t0 = obj; __ movl(t0, Address(klass, Klass::layout_helper_offset())); __ sarl(t0, Klass::_lh_array_tag_shift); - int tag = ((id == StubId::c1_new_type_array_id) - ? Klass::_lh_array_tag_type_value - : Klass::_lh_array_tag_obj_value); - __ cmpl(t0, tag); - __ jcc(Assembler::equal, ok); - __ stop("assert(is an array klass)"); + switch (id) { + case StubId::c1_new_type_array_id: + __ cmpl(t0, Klass::_lh_array_tag_type_value); + __ jcc(Assembler::equal, ok); + __ stop("assert(is a type array klass)"); + break; + case StubId::c1_new_object_array_id: + __ cmpl(t0, (Klass::_lh_array_tag_ref_value)); // new "[Ljava/lang/Object;" + __ jcc(Assembler::equal, ok); + __ cmpl(t0, Klass::_lh_array_tag_flat_value); // new "[LVT;" + __ jcc(Assembler::equal, ok); + __ stop("assert(is an object or inline type array klass)"); + break; + case StubId::c1_new_null_free_array_id: + __ cmpl(t0, Klass::_lh_array_tag_flat_value); // the array can be a flat array. + __ jcc(Assembler::equal, ok); + __ cmpl(t0, (Klass::_lh_array_tag_ref_value)); // the array cannot be a flat array (due to InlineArrayElementMaxFlatSize, etc) + __ jcc(Assembler::equal, ok); + __ stop("assert(is an object or inline type array klass)"); + break; + default: ShouldNotReachHere(); + } __ should_not_reach_here(); __ bind(ok); } @@ -916,8 +936,11 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { int call_offset; if (id == StubId::c1_new_type_array_id) { call_offset = __ call_RT(obj, noreg, CAST_FROM_FN_PTR(address, new_type_array), klass, length); - } else { + } else if (id == StubId::c1_new_object_array_id) { call_offset = __ call_RT(obj, noreg, CAST_FROM_FN_PTR(address, new_object_array), klass, length); + } else { + assert(id == StubId::c1_new_null_free_array_id, "must be"); + call_offset = __ call_RT(obj, noreg, CAST_FROM_FN_PTR(address, new_null_free_array), klass, length); } oop_maps = new OopMapSet(); @@ -949,6 +972,83 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { } break; + case StubId::c1_load_flat_array_id: + { + StubFrame f(sasm, "load_flat_array", dont_gc_arguments); + OopMap* map = save_live_registers(sasm, 3); + + // Called with store_parameter and not C abi + + f.load_argument(1, rax); // rax,: array + f.load_argument(0, rbx); // rbx,: index + int call_offset = __ call_RT(rax, noreg, CAST_FROM_FN_PTR(address, load_flat_array), rax, rbx); + + oop_maps = new OopMapSet(); + oop_maps->add_gc_map(call_offset, map); + restore_live_registers_except_rax(sasm); + + // rax,: loaded element at array[index] + __ verify_oop(rax); + } + break; + + case StubId::c1_store_flat_array_id: + { + StubFrame f(sasm, "store_flat_array", dont_gc_arguments); + OopMap* map = save_live_registers(sasm, 4); + + // Called with store_parameter and not C abi + + f.load_argument(2, rax); // rax,: array + f.load_argument(1, rbx); // rbx,: index + f.load_argument(0, rcx); // rcx,: value + int call_offset = __ call_RT(noreg, noreg, CAST_FROM_FN_PTR(address, store_flat_array), rax, rbx, rcx); + + oop_maps = new OopMapSet(); + oop_maps->add_gc_map(call_offset, map); + restore_live_registers_except_rax(sasm); + } + break; + + case StubId::c1_substitutability_check_id: + { + StubFrame f(sasm, "substitutability_check", dont_gc_arguments); + OopMap* map = save_live_registers(sasm, 3); + + // Called with store_parameter and not C abi + + f.load_argument(1, rax); // rax,: left + f.load_argument(0, rbx); // rbx,: right + int call_offset = __ call_RT(noreg, noreg, CAST_FROM_FN_PTR(address, substitutability_check), rax, rbx); + + oop_maps = new OopMapSet(); + oop_maps->add_gc_map(call_offset, map); + restore_live_registers_except_rax(sasm); + + // rax,: are the two operands substitutable + } + break; + + + case StubId::c1_buffer_inline_args_id: + case StubId::c1_buffer_inline_args_no_receiver_id: + { + const char* name = (id == StubId::c1_buffer_inline_args_id) ? + "buffer_inline_args" : "buffer_inline_args_no_receiver"; + StubFrame f(sasm, name, dont_gc_arguments); + OopMap* map = save_live_registers(sasm, 2); + Register method = rbx; + address entry = (id == StubId::c1_buffer_inline_args_id) ? + CAST_FROM_FN_PTR(address, buffer_inline_args) : + CAST_FROM_FN_PTR(address, buffer_inline_args_no_receiver); + int call_offset = __ call_RT(rax, noreg, entry, method); + oop_maps = new OopMapSet(); + oop_maps->add_gc_map(call_offset, map); + restore_live_registers_except_rax(sasm); + __ verify_oop(rax); // rax: an array of buffered value objects + } + break; + case StubId::c1_register_finalizer_id: { __ set_info("register_finalizer", dont_gc_arguments); @@ -1042,11 +1142,23 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { break; case StubId::c1_throw_incompatible_class_change_error_id: - { StubFrame f(sasm, "throw_incompatible_class_cast_exception", dont_gc_arguments); + { StubFrame f(sasm, "throw_incompatible_class_change_error", dont_gc_arguments); oop_maps = generate_exception_throw(sasm, CAST_FROM_FN_PTR(address, throw_incompatible_class_change_error), false); } break; + case StubId::c1_throw_illegal_monitor_state_exception_id: + { StubFrame f(sasm, "throw_illegal_monitor_state_exception", dont_gc_arguments); + oop_maps = generate_exception_throw(sasm, CAST_FROM_FN_PTR(address, throw_illegal_monitor_state_exception), false); + } + break; + + case StubId::c1_throw_identity_exception_id: + { StubFrame f(sasm, "throw_identity_exception", dont_gc_arguments); + oop_maps = generate_exception_throw(sasm, CAST_FROM_FN_PTR(address, throw_identity_exception), true); + } + break; + case StubId::c1_slow_subtype_check_id: { // Typical calling sequence: diff --git a/src/hotspot/cpu/x86/c1_globals_x86.hpp b/src/hotspot/cpu/x86/c1_globals_x86.hpp index be5c443a695..d5bc8098cbb 100644 --- a/src/hotspot/cpu/x86/c1_globals_x86.hpp +++ b/src/hotspot/cpu/x86/c1_globals_x86.hpp @@ -37,7 +37,7 @@ define_pd_global(bool, InlineIntrinsics, true ); define_pd_global(bool, PreferInterpreterNativeStubs, false); define_pd_global(bool, ProfileTraps, false); define_pd_global(bool, UseOnStackReplacement, true ); -define_pd_global(bool, TieredCompilation, false); +define_pd_global(bool, TieredCompilation, true); define_pd_global(intx, CompileThreshold, 1500 ); define_pd_global(intx, OnStackReplacePercentage, 933 ); diff --git a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp index 8c3f33e0aca..6790096c4d9 100644 --- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp @@ -49,7 +49,27 @@ #endif // C2 compiled method's prolog code. -void C2_MacroAssembler::verified_entry(int framesize, int stack_bang_size, bool fp_mode_24b, bool is_stub) { +void C2_MacroAssembler::verified_entry(Compile* C, int sp_inc) { + if (C->clinit_barrier_on_entry()) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); + assert(!C->method()->holder()->is_not_initialized(), "initialization should have been started"); + + Label L_skip_barrier; + Register klass = rscratch1; + + mov_metadata(klass, C->method()->holder()->constant_encoding()); + clinit_barrier(klass, &L_skip_barrier /*L_fast_path*/); + + jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); // slow path + + bind(L_skip_barrier); + } + + int framesize = C->output()->frame_size_in_bytes(); + int bangsize = C->output()->bang_size_in_bytes(); + bool fp_mode_24b = false; + int stack_bang_size = C->output()->need_stack_bang(bangsize) ? bangsize : 0; + assert(stack_bang_size >= framesize || stack_bang_size <= 0, "stack bang size incorrect"); assert((framesize & (StackAlignmentInBytes-1)) == 0, "frame size not aligned"); @@ -94,6 +114,12 @@ void C2_MacroAssembler::verified_entry(int framesize, int stack_bang_size, bool } } + if (C->needs_stack_repair()) { + // Save stack increment just below the saved rbp (also account for fixed framesize and rbp) + assert((sp_inc & (StackAlignmentInBytes-1)) == 0, "stack increment not aligned"); + movptr(Address(rsp, framesize - wordSize), sp_inc + framesize + wordSize); + } + if (VerifyStackAtCalls) { // Majik cookie to verify stack depth framesize -= wordSize; movptr(Address(rsp, framesize), (int32_t)0xbadb100d); @@ -112,23 +138,23 @@ void C2_MacroAssembler::verified_entry(int framesize, int stack_bang_size, bool bind(L); } #endif +} - if (!is_stub) { - BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); - // We put the non-hot code of the nmethod entry barrier out-of-line in a stub. - Label dummy_slow_path; - Label dummy_continuation; - Label* slow_path = &dummy_slow_path; - Label* continuation = &dummy_continuation; - if (!Compile::current()->output()->in_scratch_emit_size()) { - // Use real labels from actual stub when not emitting code for the purpose of measuring its size - C2EntryBarrierStub* stub = new (Compile::current()->comp_arena()) C2EntryBarrierStub(); - Compile::current()->output()->add_stub(stub); - slow_path = &stub->entry(); - continuation = &stub->continuation(); - } - bs->nmethod_entry_barrier(this, slow_path, continuation); +void C2_MacroAssembler::entry_barrier() { + BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); + // We put the non-hot code of the nmethod entry barrier out-of-line in a stub. + Label dummy_slow_path; + Label dummy_continuation; + Label* slow_path = &dummy_slow_path; + Label* continuation = &dummy_continuation; + if (!Compile::current()->output()->in_scratch_emit_size()) { + // Use real labels from actual stub when not emitting code for the purpose of measuring its size + C2EntryBarrierStub* stub = new (Compile::current()->comp_arena()) C2EntryBarrierStub(); + Compile::current()->output()->add_stub(stub); + slow_path = &stub->entry(); + continuation = &stub->continuation(); } + bs->nmethod_entry_barrier(this, slow_path, continuation); } inline Assembler::AvxVectorLen C2_MacroAssembler::vector_length_encoding(int vlen_in_bytes) { diff --git a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp index 950fcb75290..02200d49f0f 100644 --- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp @@ -29,8 +29,9 @@ public: // C2 compiled method's prolog code. - void verified_entry(int framesize, int stack_bang_size, bool fp_mode_24b, bool is_stub); + void verified_entry(Compile* C, int sp_inc = 0); + void entry_barrier(); Assembler::AvxVectorLen vector_length_encoding(int vlen_in_bytes); // Code used by cmpFastLock and cmpFastUnlock mach instructions in .ad file. diff --git a/src/hotspot/cpu/x86/continuationFreezeThaw_x86.inline.hpp b/src/hotspot/cpu/x86/continuationFreezeThaw_x86.inline.hpp index 126f4043cad..8d7aed3142c 100644 --- a/src/hotspot/cpu/x86/continuationFreezeThaw_x86.inline.hpp +++ b/src/hotspot/cpu/x86/continuationFreezeThaw_x86.inline.hpp @@ -65,6 +65,12 @@ inline frame FreezeBase::sender(const frame& f) { int slot = 0; CodeBlob* sender_cb = CodeCache::find_blob_and_oopmap(sender_pc, slot); + + // Repair the sender sp if the frame has been extended + if (sender_cb->is_nmethod()) { + sender_sp = f.repair_sender_sp(sender_sp, link_addr); + } + return sender_cb != nullptr ? frame(sender_sp, sender_sp, *link_addr, sender_pc, sender_cb, slot == -1 ? nullptr : sender_cb->oop_map_for_slot(slot, sender_pc), false) @@ -72,7 +78,7 @@ inline frame FreezeBase::sender(const frame& f) { } template -frame FreezeBase::new_heap_frame(frame& f, frame& caller) { +frame FreezeBase::new_heap_frame(frame& f, frame& caller, int size_adjust) { assert(FKind::is_instance(f), ""); assert(!caller.is_interpreted_frame() || caller.unextended_sp() == (intptr_t*)caller.at(frame::interpreter_frame_last_sp_offset), ""); @@ -106,8 +112,8 @@ frame FreezeBase::new_heap_frame(frame& f, frame& caller) { fp = FKind::compiled ? *(intptr_t**)(f.sp() - frame::sender_sp_offset) : (intptr_t*)badAddressVal; int fsize = FKind::size(f); - sp = caller.unextended_sp() - fsize; - if (caller.is_interpreted_frame()) { + sp = caller.unextended_sp() - fsize - size_adjust; + if (caller.is_interpreted_frame() && size_adjust == 0) { // If the caller is interpreted, our stackargs are not supposed to overlap with it // so we make more room by moving sp down by argsize int argsize = FKind::stack_argsize(f); @@ -174,11 +180,12 @@ inline void FreezeBase::set_top_frame_metadata_pd(const frame& hf) { assert(frame_pc == ContinuationHelper::Frame::real_pc(hf), ""); } -inline void FreezeBase::patch_pd(frame& hf, const frame& caller) { +inline void FreezeBase::patch_pd(frame& hf, const frame& caller, bool is_bottom_frame) { if (caller.is_interpreted_frame()) { assert(!caller.is_empty(), ""); patch_callee_link_relative(caller, caller.fp()); - } else { + } else if (is_bottom_frame && caller.pc() != nullptr) { + assert(caller.is_compiled_frame(), ""); // If we're the bottom-most frame frozen in this freeze, the caller might have stayed frozen in the chunk, // and its oop-containing fp fixed. We've now just overwritten it, so we must patch it back to its value // as read from the chunk. @@ -214,7 +221,7 @@ inline frame ThawBase::new_entry_frame() { return frame(sp, sp, _cont.entryFP(), _cont.entryPC()); // TODO PERF: This finds code blob and computes deopt state } -template frame ThawBase::new_stack_frame(const frame& hf, frame& caller, bool bottom) { +template frame ThawBase::new_stack_frame(const frame& hf, frame& caller, bool bottom, int size_adjust) { assert(FKind::is_instance(hf), ""); // The values in the returned frame object will be written into the callee's stack in patch. @@ -241,24 +248,23 @@ template frame ThawBase::new_stack_frame(const frame& hf, frame& return f; } else { int fsize = FKind::size(hf); - intptr_t* frame_sp = caller.unextended_sp() - fsize; + intptr_t* frame_sp = caller.unextended_sp() - fsize - size_adjust; if (bottom || caller.is_interpreted_frame()) { - int argsize = FKind::stack_argsize(hf); - - fsize += argsize; - frame_sp -= argsize; - caller.set_sp(caller.sp() - argsize); - assert(caller.sp() == frame_sp + (fsize-argsize), ""); - + if (size_adjust == 0) { + int argsize = FKind::stack_argsize(hf); + frame_sp -= argsize; + } frame_sp = align(hf, frame_sp, caller, bottom); } + caller.set_sp(frame_sp + fsize); + assert(is_aligned(frame_sp, frame::frame_alignment), ""); assert(hf.cb() != nullptr, ""); assert(hf.oop_map() != nullptr, ""); intptr_t* fp; if (PreserveFramePointer) { // we need to recreate a "real" frame pointer, pointing into the stack - fp = frame_sp + FKind::size(hf) - frame::sender_sp_offset; + fp = frame_sp + fsize - frame::sender_sp_offset; } else { fp = FKind::stub || FKind::native ? frame_sp + fsize - frame::sender_sp_offset // fp always points to the address below the pushed return pc. We need correct address. @@ -272,14 +278,15 @@ inline intptr_t* ThawBase::align(const frame& hf, intptr_t* frame_sp, frame& cal if (((intptr_t)frame_sp & 0xf) != 0) { assert(caller.is_interpreted_frame() || (bottom && hf.compiled_frame_stack_argsize() % 2 != 0), ""); frame_sp--; - caller.set_sp(caller.sp() - 1); } assert(is_aligned(frame_sp, frame::frame_alignment), ""); return frame_sp; } inline void ThawBase::patch_pd(frame& f, const frame& caller) { - patch_callee_link(caller, caller.fp()); + if (caller.is_interpreted_frame() || PreserveFramePointer) { + patch_callee_link(caller, caller.fp()); + } } inline void ThawBase::patch_pd(frame& f, intptr_t* caller_sp) { diff --git a/src/hotspot/cpu/x86/continuationHelper_x86.inline.hpp b/src/hotspot/cpu/x86/continuationHelper_x86.inline.hpp index 6d72e1b80e8..84a5a8f96fa 100644 --- a/src/hotspot/cpu/x86/continuationHelper_x86.inline.hpp +++ b/src/hotspot/cpu/x86/continuationHelper_x86.inline.hpp @@ -119,8 +119,8 @@ inline address ContinuationHelper::Frame::real_pc(const frame& f) { return *pc_addr; } -inline void ContinuationHelper::Frame::patch_pc(const frame& f, address pc) { - address* pc_addr = &(((address*) f.sp())[-1]); +inline void ContinuationHelper::Frame::patch_pc(const frame& f, address pc, bool callee_augmented) { + address* pc_addr = &(((address*) (callee_augmented ? f.unextended_sp() : f.sp()))[-1]); *pc_addr = pc; } diff --git a/src/hotspot/cpu/x86/frame_x86.cpp b/src/hotspot/cpu/x86/frame_x86.cpp index 46ffda93699..26420313d4f 100644 --- a/src/hotspot/cpu/x86/frame_x86.cpp +++ b/src/hotspot/cpu/x86/frame_x86.cpp @@ -145,13 +145,16 @@ bool frame::safe_for_sender(JavaThread *thread) { if (!thread->is_in_full_stack_checked((address)sender_sp)) { return false; } - sender_unextended_sp = sender_sp; // On Intel the return_address is always the word on the stack sender_pc = (address) *(sender_sp-1); // Note: frame::sender_sp_offset is only valid for compiled frame - saved_fp = (intptr_t*) *(sender_sp - frame::sender_sp_offset); - } + intptr_t** saved_fp_addr = (intptr_t**) (sender_sp - frame::sender_sp_offset); + saved_fp = *saved_fp_addr; + // Repair the sender sp if this is a method with scalarized inline type args + sender_sp = repair_sender_sp(sender_sp, saved_fp_addr); + sender_unextended_sp = sender_sp; + } if (Continuation::is_return_barrier_entry(sender_pc)) { // sender_pc might be invalid so check that the frame // actually belongs to a Continuation. @@ -673,6 +676,9 @@ void frame::describe_pd(FrameValues& values, int frame_no) { } else { ret_pc_loc = real_fp() - return_addr_offset; fp_loc = real_fp() - sender_sp_offset; + if (cb()->is_nmethod() && cb()->as_nmethod_or_null()->needs_stack_repair()) { + values.describe(frame_no, fp_loc - 1, err_msg("fsize for #%d", frame_no), 1); + } } address ret_pc = *(address*)ret_pc_loc; values.describe(frame_no, ret_pc_loc, @@ -696,6 +702,43 @@ frame::frame(void* sp, void* fp, void* pc) { #endif +// Check for a method with scalarized inline type arguments that needs +// a stack repair and return the repaired sender stack pointer. +intptr_t* frame::repair_sender_sp(intptr_t* sender_sp, intptr_t** saved_fp_addr) const { + nmethod* nm = _cb->as_nmethod_or_null(); + if (nm != nullptr && nm->needs_stack_repair()) { + // The stack increment resides just below the saved rbp on the stack + // and does not account for the return address. + intptr_t* real_frame_size_addr = (intptr_t*) (saved_fp_addr - 1); + int real_frame_size = ((*real_frame_size_addr) + wordSize) / wordSize; + assert(real_frame_size >= _cb->frame_size() && real_frame_size <= 1000000, "invalid frame size"); + sender_sp = unextended_sp() + real_frame_size; + } + return sender_sp; +} + +intptr_t* frame::repair_sender_sp(nmethod* nm, intptr_t* sp, intptr_t** saved_fp_addr) { + assert(nm != nullptr && nm->needs_stack_repair(), ""); + // The stack increment resides just below the saved rbp on the stack + // and does not account for the return address. + intptr_t* real_frame_size_addr = (intptr_t*) (saved_fp_addr - 1); + int real_frame_size = ((*real_frame_size_addr) + wordSize) / wordSize; + assert(real_frame_size >= nm->frame_size() && real_frame_size <= 1000000, "invalid frame size"); + return sp + real_frame_size; +} + +bool frame::was_augmented_on_entry(int& real_size) const { + assert(is_compiled_frame(), ""); + if (_cb->as_nmethod_or_null()->needs_stack_repair()) { + intptr_t* real_frame_size_addr = unextended_sp() + _cb->frame_size() - sender_sp_offset - 1; + log_trace(continuations)("real_frame_size is addr is " INTPTR_FORMAT, p2i(real_frame_size_addr)); + real_size = ((*real_frame_size_addr) + wordSize) / wordSize; + return real_size != _cb->frame_size(); + } + real_size = _cb->frame_size(); + return false; +} + void JavaFrameAnchor::make_walkable() { // last frame set? if (last_Java_sp() == nullptr) return; diff --git a/src/hotspot/cpu/x86/frame_x86.hpp b/src/hotspot/cpu/x86/frame_x86.hpp index f3034ee9263..c3cfc3c6e82 100644 --- a/src/hotspot/cpu/x86/frame_x86.hpp +++ b/src/hotspot/cpu/x86/frame_x86.hpp @@ -144,6 +144,13 @@ return (intptr_t*) addr_at(offset); } + public: + // Support for scalarized inline type calling convention + intptr_t* repair_sender_sp(intptr_t* sender_sp, intptr_t** saved_fp_addr) const; + static intptr_t* repair_sender_sp(nmethod* nm, intptr_t* sp, intptr_t** saved_fp_addr); + bool was_augmented_on_entry(int& real_size) const; + + private: #ifdef ASSERT // Used in frame::sender_for_{interpreter,compiled}_frame static void verify_deopt_original_pc(nmethod* nm, intptr_t* unextended_sp); diff --git a/src/hotspot/cpu/x86/frame_x86.inline.hpp b/src/hotspot/cpu/x86/frame_x86.inline.hpp index afc4ab8767b..82bff174408 100644 --- a/src/hotspot/cpu/x86/frame_x86.inline.hpp +++ b/src/hotspot/cpu/x86/frame_x86.inline.hpp @@ -32,6 +32,9 @@ #include "interpreter/interpreter.hpp" #include "runtime/sharedRuntime.hpp" #include "runtime/registerMap.hpp" +#ifdef COMPILER1 +#include "c1/c1_Runtime1.hpp" +#endif // Inline functions for Intel frames: @@ -432,20 +435,56 @@ inline frame frame::sender_for_compiled_frame(RegisterMap* map) const { intptr_t* sender_sp = unextended_sp() + _cb->frame_size(); assert(sender_sp == real_fp(), ""); - // On Intel the return_address is always the word on the stack - address sender_pc = (address) *(sender_sp-1); +#ifdef ASSERT + address sender_pc_copy = (address) *(sender_sp-1); +#endif // This is the saved value of EBP which may or may not really be an FP. // It is only an FP if the sender is an interpreter frame (or C1?). // saved_fp_addr should be correct even for a bottom thawed frame (with a return barrier) intptr_t** saved_fp_addr = (intptr_t**) (sender_sp - frame::sender_sp_offset); + // Repair the sender sp if the frame has been extended + sender_sp = repair_sender_sp(sender_sp, saved_fp_addr); + + // On Intel the return_address is always the word on the stack + address sender_pc = (address) *(sender_sp-1); + +#ifdef ASSERT + if (sender_pc != sender_pc_copy) { + // When extending the stack in the callee method entry to make room for unpacking of value + // type args, we keep a copy of the sender pc at the expected location in the callee frame. + // If the sender pc is patched due to deoptimization, the copy is not consistent anymore. + nmethod* nm = CodeCache::find_blob(sender_pc)->as_nmethod(); + assert(sender_pc == nm->deopt_mh_handler_begin() || sender_pc == nm->deopt_handler_begin(), "unexpected sender pc"); + } +#endif + if (map->update_map()) { // Tell GC to use argument oopmaps for some runtime stubs that need it. // For C1, the runtime stub might not have oop maps, so set this flag // outside of update_register_map. - if (!_cb->is_nmethod()) { // compiled frames do not use callee-saved registers - map->set_include_argument_oops(_cb->caller_must_gc_arguments(map->thread())); + bool c1_buffering = false; +#ifdef COMPILER1 + nmethod* nm = _cb->as_nmethod_or_null(); + if (nm != nullptr && nm->is_compiled_by_c1() && nm->method()->has_scalarized_args() && + pc() < nm->verified_inline_entry_point()) { + // TODO 8284443 Can't we do that by not passing 'dont_gc_arguments' in case 'StubId::c1_buffer_inline_args_id' in 'Runtime1::generate_code_for'? + // The VEP and VIEP(RO) of C1-compiled methods call buffer_inline_args_xxx + // before doing any argument shuffling, so we need to scan the oops + // as the caller passes them. + c1_buffering = true; +#ifdef ASSERT + NativeCall* call = nativeCall_before(pc()); + address dest = call->destination(); + assert(dest == Runtime1::entry_for(StubId::c1_buffer_inline_args_no_receiver_id) || + dest == Runtime1::entry_for(StubId::c1_buffer_inline_args_id), "unexpected safepoint in entry point"); +#endif + } +#endif + if (!_cb->is_nmethod() || c1_buffering) { // compiled frames do not use callee-saved registers + bool caller_args = _cb->caller_must_gc_arguments(map->thread()) || c1_buffering; + map->set_include_argument_oops(caller_args); if (oop_map() != nullptr) { _oop_map->update_register_map(this, map); } diff --git a/src/hotspot/cpu/x86/gc/g1/g1BarrierSetAssembler_x86.cpp b/src/hotspot/cpu/x86/gc/g1/g1BarrierSetAssembler_x86.cpp index c1920b52837..fe37e75f428 100644 --- a/src/hotspot/cpu/x86/gc/g1/g1BarrierSetAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/gc/g1/g1BarrierSetAssembler_x86.cpp @@ -202,8 +202,24 @@ void G1BarrierSetAssembler::g1_write_barrier_pre(MacroAssembler* masm, __ bind(runtime); - // Determine and save the live input values - __ push_call_clobbered_registers(); + if (EnableValhalla && InlineTypePassFieldsAsArgs) { + // Barriers might be emitted when converting between (scalarized) calling conventions for inline + // types. Save all argument registers before calling into the runtime. + // TODO 8366717: use push_set() (see JDK-8283327 push/pop_call_clobbered_registers & aarch64 ) + __ pusha(); + __ subptr(rsp, 64); + __ movdbl(Address(rsp, 0), j_farg0); + __ movdbl(Address(rsp, 8), j_farg1); + __ movdbl(Address(rsp, 16), j_farg2); + __ movdbl(Address(rsp, 24), j_farg3); + __ movdbl(Address(rsp, 32), j_farg4); + __ movdbl(Address(rsp, 40), j_farg5); + __ movdbl(Address(rsp, 48), j_farg6); + __ movdbl(Address(rsp, 56), j_farg7); + } else { + // Determine and save the live input values + __ push_call_clobbered_registers(); + } // Calling the runtime using the regular call_VM_leaf mechanism generates // code (generated by InterpreterMacroAssember::call_VM_leaf_base) @@ -230,7 +246,21 @@ void G1BarrierSetAssembler::g1_write_barrier_pre(MacroAssembler* masm, __ call_VM_leaf(CAST_FROM_FN_PTR(address, G1BarrierSetRuntime::write_ref_field_pre_entry), pre_val, thread); } - __ pop_call_clobbered_registers(); + if (EnableValhalla && InlineTypePassFieldsAsArgs) { + // Restore registers + __ movdbl(j_farg0, Address(rsp, 0)); + __ movdbl(j_farg1, Address(rsp, 8)); + __ movdbl(j_farg2, Address(rsp, 16)); + __ movdbl(j_farg3, Address(rsp, 24)); + __ movdbl(j_farg4, Address(rsp, 32)); + __ movdbl(j_farg5, Address(rsp, 40)); + __ movdbl(j_farg6, Address(rsp, 48)); + __ movdbl(j_farg7, Address(rsp, 56)); + __ addptr(rsp, 64); + __ popa(); + } else { + __ pop_call_clobbered_registers(); + } __ bind(done); } @@ -299,11 +329,33 @@ void G1BarrierSetAssembler::g1_write_barrier_post(MacroAssembler* masm, generate_post_barrier_slow_path(masm, thread, tmp, tmp2, done, runtime); __ bind(runtime); - // save the live input values - RegSet saved = RegSet::of(store_addr); - __ push_set(saved); + // Barriers might be emitted when converting between (scalarized) calling conventions for inline + // types. Save all argument registers before calling into the runtime. + // TODO 8366717: use push_set() (see JDK-8283327 push/pop_call_clobbered_registers & aarch64) + __ pusha(); + __ subptr(rsp, 64); + __ movdbl(Address(rsp, 0), j_farg0); + __ movdbl(Address(rsp, 8), j_farg1); + __ movdbl(Address(rsp, 16), j_farg2); + __ movdbl(Address(rsp, 24), j_farg3); + __ movdbl(Address(rsp, 32), j_farg4); + __ movdbl(Address(rsp, 40), j_farg5); + __ movdbl(Address(rsp, 48), j_farg6); + __ movdbl(Address(rsp, 56), j_farg7); + __ call_VM_leaf(CAST_FROM_FN_PTR(address, G1BarrierSetRuntime::write_ref_field_post_entry), tmp, thread); - __ pop_set(saved); + + // Restore registers + __ movdbl(j_farg0, Address(rsp, 0)); + __ movdbl(j_farg1, Address(rsp, 8)); + __ movdbl(j_farg2, Address(rsp, 16)); + __ movdbl(j_farg3, Address(rsp, 24)); + __ movdbl(j_farg4, Address(rsp, 32)); + __ movdbl(j_farg5, Address(rsp, 40)); + __ movdbl(j_farg6, Address(rsp, 48)); + __ movdbl(j_farg7, Address(rsp, 56)); + __ addptr(rsp, 64); + __ popa(); __ bind(done); } @@ -403,8 +455,9 @@ void G1BarrierSetAssembler::oop_store_at(MacroAssembler* masm, DecoratorSet deco Address dst, Register val, Register tmp1, Register tmp2, Register tmp3) { bool in_heap = (decorators & IN_HEAP) != 0; bool as_normal = (decorators & AS_NORMAL) != 0; + bool dest_uninitialized = (decorators & IS_DEST_UNINITIALIZED) != 0; - bool needs_pre_barrier = as_normal; + bool needs_pre_barrier = as_normal && !dest_uninitialized; bool needs_post_barrier = val != noreg && in_heap; // flatten object address if needed diff --git a/src/hotspot/cpu/x86/gc/g1/g1BarrierSetAssembler_x86.hpp b/src/hotspot/cpu/x86/gc/g1/g1BarrierSetAssembler_x86.hpp index 774e87b916c..e5f17de2421 100644 --- a/src/hotspot/cpu/x86/gc/g1/g1BarrierSetAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/gc/g1/g1BarrierSetAssembler_x86.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/hotspot/cpu/x86/gc/g1/g1_x86_64.ad b/src/hotspot/cpu/x86/gc/g1/g1_x86_64.ad index 819cd97696c..44043d18df4 100644 --- a/src/hotspot/cpu/x86/gc/g1/g1_x86_64.ad +++ b/src/hotspot/cpu/x86/gc/g1/g1_x86_64.ad @@ -60,13 +60,17 @@ static void write_barrier_post(MacroAssembler* masm, Register store_addr, Register new_val, Register tmp1, - Register tmp2) { + Register tmp2, + RegSet preserve = RegSet()) { if (!G1PostBarrierStubC2::needs_barrier(node)) { return; } Assembler::InlineSkippedInstructionsCounter skip_counter(masm); G1BarrierSetAssembler* g1_asm = static_cast(BarrierSet::barrier_set()->barrier_set_assembler()); G1PostBarrierStubC2* const stub = G1PostBarrierStubC2::create(node); + for (RegSetIterator reg = preserve.begin(); *reg != noreg; ++reg) { + stub->preserve(*reg); + } g1_asm->g1_write_barrier_post_c2(masm, store_addr, new_val, tmp1, tmp2, stub); } @@ -101,6 +105,91 @@ instruct g1StoreP(memory mem, any_RegP src, rRegP tmp1, rRegP tmp2, rRegP tmp3, ins_pipe(ialu_mem_reg); %} +// TODO 8350865 (same applies to g1StoreLSpecialTwoOops) +// - Can we use an unbound register for src? +// - Do no set/overwrite barrier data here, also handle G1C2BarrierPostNotNull +// - Is the zero-extend really required in all the places? +instruct g1StoreLSpecialOneOop(memory mem, rdx_RegL src, immI off, rRegP tmp1, rRegP tmp2, rRegP tmp3, rFlagsReg cr) +%{ + predicate(UseG1GC); + match(Set mem (StoreLSpecial mem (Binary src off))); + effect(TEMP tmp1, TEMP tmp2, TEMP tmp3, USE_KILL src, KILL cr); + format %{ "movq $mem, $src\t# g1StoreLSpecialOneOop" %} + ins_encode %{ + ((MachNode*)this)->set_barrier_data(G1C2BarrierPre | G1C2BarrierPost); + + __ lea($tmp1$$Register, $mem$$Address); + // Adjust address to point to narrow oop + __ addq($tmp1$$Register, $off$$constant); + write_barrier_pre(masm, this, + $tmp1$$Register /* obj */, + $tmp2$$Register /* pre_val */, + $tmp3$$Register /* tmp */, + RegSet::of($tmp1$$Register, $src$$Register) /* preserve */); + + __ movq(Address($tmp1$$Register, 0), $src$$Register); + + // Shift long value to extract the narrow oop field value and zero-extend it + __ shrq($src$$Register, $off$$constant << LogBitsPerByte); + __ movl($src$$Register, $src$$Register); + + write_barrier_post(masm, this, + $tmp1$$Register /* store_addr */, + $src$$Register /* new_val */, + $tmp3$$Register /* tmp1 */, + $tmp2$$Register /* tmp2 */); + %} + ins_pipe(ialu_mem_reg); +%} + +instruct g1StoreLSpecialTwoOops(memory mem, rdx_RegL src, rRegP tmp1, rRegP tmp2, rRegP tmp3, rRegP tmp4, rFlagsReg cr) +%{ + predicate(UseG1GC); + match(Set mem (StoreLSpecial mem src)); + effect(TEMP tmp1, TEMP tmp2, TEMP tmp3, TEMP tmp4, USE_KILL src, KILL cr); + format %{ "movq $mem, $src\t# g1StoreLSpecialTwoOops" %} + ins_encode %{ + ((MachNode*)this)->set_barrier_data(G1C2BarrierPre | G1C2BarrierPost); + + __ lea($tmp1$$Register, $mem$$Address); + write_barrier_pre(masm, this, + $tmp1$$Register /* obj */, + $tmp2$$Register /* pre_val */, + $tmp3$$Register /* tmp */, + RegSet::of($tmp1$$Register, $src$$Register) /* preserve */); + // Adjust address to point to the second narrow oop in the long value + __ addq($tmp1$$Register, 4); + write_barrier_pre(masm, this, + $tmp1$$Register /* obj */, + $tmp2$$Register /* pre_val */, + $tmp3$$Register /* tmp */, + RegSet::of($tmp1$$Register, $src$$Register) /* preserve */); + // Adjust address again to point to the first narrow oop in the long value + __ subq($tmp1$$Register, 4); + + __ movq(Address($tmp1$$Register, 0), $src$$Register); + + // Zero-extend first narrow oop to long + __ movl($tmp4$$Register, $src$$Register); + + // Shift long value to extract the second narrow oop field value + __ shrq($src$$Register, 32); + write_barrier_post(masm, this, + $tmp1$$Register /* store_addr */, + $tmp4$$Register /* new_val */, + $tmp3$$Register /* tmp1 */, + $tmp2$$Register /* tmp2 */, + RegSet::of($tmp1$$Register, $src$$Register) /* preserve */); + __ addq($tmp1$$Register, 4); + write_barrier_post(masm, this, + $tmp1$$Register /* store_addr */, + $src$$Register /* new_val */, + $tmp3$$Register /* tmp1 */, + $tmp2$$Register /* tmp2 */); + %} + ins_pipe(ialu_mem_reg); +%} + instruct g1StoreN(memory mem, rRegN src, rRegP tmp1, rRegP tmp2, rRegP tmp3, rFlagsReg cr) %{ predicate(UseG1GC && n->as_Store()->barrier_data() != 0); diff --git a/src/hotspot/cpu/x86/gc/shared/barrierSetAssembler_x86.cpp b/src/hotspot/cpu/x86/gc/shared/barrierSetAssembler_x86.cpp index 925444792ca..8eafa486ac0 100644 --- a/src/hotspot/cpu/x86/gc/shared/barrierSetAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/gc/shared/barrierSetAssembler_x86.cpp @@ -22,10 +22,12 @@ * */ +#include "asm/macroAssembler.inline.hpp" #include "classfile/classLoaderData.hpp" #include "gc/shared/barrierSet.hpp" #include "gc/shared/barrierSetAssembler.hpp" #include "gc/shared/barrierSetNMethod.hpp" +#include "gc/shared/barrierSetRuntime.hpp" #include "gc/shared/collectedHeap.hpp" #include "interpreter/interp_masm.hpp" #include "memory/universe.hpp" @@ -161,6 +163,19 @@ void BarrierSetAssembler::store_at(MacroAssembler* masm, DecoratorSet decorators } } +void BarrierSetAssembler::flat_field_copy(MacroAssembler* masm, DecoratorSet decorators, + Register src, Register dst, Register inline_layout_info) { + // flat_field_copy implementation is fairly complex, and there are not any + // "short-cuts" to be made from asm. What there is, appears to have the same + // cost in C++, so just "call_VM_leaf" for now rather than maintain hundreds + // of hand-rolled instructions... + if (decorators & IS_DEST_UNINITIALIZED) { + __ call_VM_leaf(CAST_FROM_FN_PTR(address, BarrierSetRuntime::value_copy_is_dest_uninitialized), src, dst, inline_layout_info); + } else { + __ call_VM_leaf(CAST_FROM_FN_PTR(address, BarrierSetRuntime::value_copy), src, dst, inline_layout_info); + } +} + void BarrierSetAssembler::copy_load_at(MacroAssembler* masm, DecoratorSet decorators, BasicType type, diff --git a/src/hotspot/cpu/x86/gc/shared/barrierSetAssembler_x86.hpp b/src/hotspot/cpu/x86/gc/shared/barrierSetAssembler_x86.hpp index fd52379d2e2..f39b35d8654 100644 --- a/src/hotspot/cpu/x86/gc/shared/barrierSetAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/gc/shared/barrierSetAssembler_x86.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -48,6 +48,9 @@ class BarrierSetAssembler: public CHeapObj { virtual void store_at(MacroAssembler* masm, DecoratorSet decorators, BasicType type, Address dst, Register val, Register tmp1, Register tmp2, Register tmp3); + virtual void flat_field_copy(MacroAssembler* masm, DecoratorSet decorators, + Register src, Register dst, Register inline_layout_info); + // The copy_[load/store]_at functions are used by arraycopy stubs. Be careful to only use // r10 (aka rscratch1) in a context where restore_arg_regs_using_thread has been used instead // of the looser setup_arg_regs. Currently this is done when using type T_OBJECT. diff --git a/src/hotspot/cpu/x86/gc/shared/barrierSetNMethod_x86.cpp b/src/hotspot/cpu/x86/gc/shared/barrierSetNMethod_x86.cpp index c27af4a29cd..9891d3e2b1d 100644 --- a/src/hotspot/cpu/x86/gc/shared/barrierSetNMethod_x86.cpp +++ b/src/hotspot/cpu/x86/gc/shared/barrierSetNMethod_x86.cpp @@ -159,13 +159,37 @@ static NativeNMethodCmpBarrier* native_nmethod_barrier(nmethod* nm) { return barrier; } +static void set_immediate(nmethod* nm, jint val) { + NativeNMethodCmpBarrier* cmp1 = native_nmethod_barrier(nm); + cmp1->set_immediate(val); + + if (!nm->is_osr_method() && nm->method()->has_scalarized_args()) { + // nmethods with scalarized arguments have multiple entry points that each have an own nmethod entry barrier + assert(nm->verified_entry_point() != nm->verified_inline_entry_point(), "scalarized entry point not found"); + address method_body = nm->is_compiled_by_c1() ? nm->verified_inline_entry_point() : nm->verified_entry_point(); + address entry_point2 = nm->is_compiled_by_c1() ? nm->verified_entry_point() : nm->verified_inline_entry_point(); + + int barrier_offset = reinterpret_cast
(cmp1) - method_body; + NativeNMethodCmpBarrier* cmp2 = reinterpret_cast(entry_point2 + barrier_offset); + assert(cmp1 != cmp2, "sanity"); + DEBUG_ONLY(cmp2->verify()); + cmp2->set_immediate(val); + + if (method_body != nm->verified_inline_ro_entry_point() && entry_point2 != nm->verified_inline_ro_entry_point()) { + NativeNMethodCmpBarrier* cmp3 = reinterpret_cast(nm->verified_inline_ro_entry_point() + barrier_offset); + assert(cmp1 != cmp3 && cmp2 != cmp3, "sanity"); + DEBUG_ONLY(cmp3->verify()); + cmp3->set_immediate(val); + } + } +} + void BarrierSetNMethod::set_guard_value(nmethod* nm, int value) { if (!supports_entry_barrier(nm)) { return; } - NativeNMethodCmpBarrier* cmp = native_nmethod_barrier(nm); - cmp->set_immediate(value); + set_immediate(nm, value); } int BarrierSetNMethod::guard_value(nmethod* nm) { diff --git a/src/hotspot/cpu/x86/gc/shared/cardTableBarrierSetAssembler_x86.cpp b/src/hotspot/cpu/x86/gc/shared/cardTableBarrierSetAssembler_x86.cpp index ba89b09e4dc..3a6d62fb491 100644 --- a/src/hotspot/cpu/x86/gc/shared/cardTableBarrierSetAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/gc/shared/cardTableBarrierSetAssembler_x86.cpp @@ -118,7 +118,6 @@ void CardTableBarrierSetAssembler::store_check(MacroAssembler* masm, Register ob void CardTableBarrierSetAssembler::oop_store_at(MacroAssembler* masm, DecoratorSet decorators, BasicType type, Address dst, Register val, Register tmp1, Register tmp2, Register tmp3) { bool in_heap = (decorators & IN_HEAP) != 0; - bool is_array = (decorators & IS_ARRAY) != 0; bool on_anonymous = (decorators & ON_UNKNOWN_OOP_REF) != 0; bool precise = is_array || on_anonymous; @@ -129,7 +128,14 @@ void CardTableBarrierSetAssembler::oop_store_at(MacroAssembler* masm, DecoratorS if (needs_post_barrier) { // flatten object address if needed if (!precise || (dst.index() == noreg && dst.disp() == 0)) { - store_check(masm, dst.base(), dst); + if (tmp3 != noreg) { + // Called by MacroAssembler::pack_inline_helper. We cannot corrupt the dst.base() register + __ movptr(tmp3, dst.base()); + store_check(masm, tmp3, dst); + } else { + // It's OK to corrupt the dst.base() register. + store_check(masm, dst.base(), dst); + } } else { __ lea(tmp1, dst); store_check(masm, tmp1, dst); diff --git a/src/hotspot/cpu/x86/gc/shared/cardTableBarrierSetAssembler_x86.hpp b/src/hotspot/cpu/x86/gc/shared/cardTableBarrierSetAssembler_x86.hpp index 4760b222977..8d3c4916c76 100644 --- a/src/hotspot/cpu/x86/gc/shared/cardTableBarrierSetAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/gc/shared/cardTableBarrierSetAssembler_x86.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/hotspot/cpu/x86/gc/shared/modRefBarrierSetAssembler_x86.hpp b/src/hotspot/cpu/x86/gc/shared/modRefBarrierSetAssembler_x86.hpp index c8b5043256a..ccc7f8316ee 100644 --- a/src/hotspot/cpu/x86/gc/shared/modRefBarrierSetAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/gc/shared/modRefBarrierSetAssembler_x86.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/hotspot/cpu/x86/globals_x86.hpp b/src/hotspot/cpu/x86/globals_x86.hpp index 103e22d0185..79a794c71e3 100644 --- a/src/hotspot/cpu/x86/globals_x86.hpp +++ b/src/hotspot/cpu/x86/globals_x86.hpp @@ -86,6 +86,9 @@ define_pd_global(bool, PreserveFramePointer, false); define_pd_global(intx, InitArrayShortSize, 8*BytesPerLong); +define_pd_global(bool, InlineTypePassFieldsAsArgs, LP64_ONLY(true) NOT_LP64(false)); +define_pd_global(bool, InlineTypeReturnedAsFields, LP64_ONLY(true) NOT_LP64(false)); + #define ARCH_FLAGS(develop, \ product, \ range, \ diff --git a/src/hotspot/cpu/x86/interp_masm_x86.cpp b/src/hotspot/cpu/x86/interp_masm_x86.cpp index a6b4efbe4f2..e1de2b9593e 100644 --- a/src/hotspot/cpu/x86/interp_masm_x86.cpp +++ b/src/hotspot/cpu/x86/interp_masm_x86.cpp @@ -28,9 +28,11 @@ #include "interpreter/interpreterRuntime.hpp" #include "logging/log.hpp" #include "oops/arrayOop.hpp" +#include "oops/constMethodFlags.hpp" #include "oops/markWord.hpp" #include "oops/methodData.hpp" #include "oops/method.hpp" +#include "oops/inlineKlass.hpp" #include "oops/resolvedFieldEntry.hpp" #include "oops/resolvedIndyEntry.hpp" #include "oops/resolvedMethodEntry.hpp" @@ -165,7 +167,7 @@ void InterpreterMacroAssembler::profile_arguments_type(Register mdp, Register ca // argument. tmp is the number of cells left in the // CallTypeData/VirtualCallTypeData to reach its end. Non null // if there's a return to profile. - assert(ReturnTypeEntry::static_cell_count() < TypeStackSlotEntries::per_arg_count(), "can't move past ret type"); + assert(SingleTypeEntry::static_cell_count() < TypeStackSlotEntries::per_arg_count(), "can't move past ret type"); shll(tmp, log2i_exact((int)DataLayout::cell_size)); addptr(mdp, tmp); } @@ -210,7 +212,7 @@ void InterpreterMacroAssembler::profile_return_type(Register mdp, Register ret, bind(do_profile); } - Address mdo_ret_addr(mdp, -in_bytes(ReturnTypeEntry::size())); + Address mdo_ret_addr(mdp, -in_bytes(SingleTypeEntry::size())); mov(tmp, ret); profile_obj_type(tmp, mdo_ret_addr); @@ -516,7 +518,8 @@ void InterpreterMacroAssembler::load_resolved_klass_at_index(Register klass, // Kills: // rcx, rdi void InterpreterMacroAssembler::gen_subtype_check(Register Rsub_klass, - Label& ok_is_subtype) { + Label& ok_is_subtype, + bool profile) { assert(Rsub_klass != rax, "rax holds superklass"); LP64_ONLY(assert(Rsub_klass != r14, "r14 holds locals");) LP64_ONLY(assert(Rsub_klass != r13, "r13 holds bcp");) @@ -524,7 +527,9 @@ void InterpreterMacroAssembler::gen_subtype_check(Register Rsub_klass, assert(Rsub_klass != rdi, "rdi holds 2ndary super array scan ptr"); // Profile the not-null value's klass. - profile_typecheck(rcx, Rsub_klass, rdi); // blows rcx, reloads rdi + if (profile) { + profile_typecheck(rcx, Rsub_klass, rdi); // blows rcx, reloads rdi + } // Do the check. check_klass_subtype(Rsub_klass, rax, rcx, ok_is_subtype); // blows rcx @@ -810,7 +815,7 @@ void InterpreterMacroAssembler::remove_activation(TosState state, movbool(rbx, do_not_unlock_if_synchronized); movbool(do_not_unlock_if_synchronized, false); // reset the flag - // get method access flags + // get method access flags movptr(rcx, Address(rbp, frame::interpreter_frame_method_offset * wordSize)); load_unsigned_short(rcx, Address(rcx, Method::access_flags_offset())); testl(rcx, JVM_ACC_SYNCHRONIZED); @@ -949,11 +954,9 @@ void InterpreterMacroAssembler::remove_activation(TosState state, notify_method_exit(state, SkipNotifyJVMTI); // preserve TOSCA } - // remove activation - // get sender sp - movptr(rbx, - Address(rbp, frame::interpreter_frame_sender_sp_offset * wordSize)); if (StackReservedPages > 0) { + movptr(rbx, + Address(rbp, frame::interpreter_frame_sender_sp_offset * wordSize)); // testing if reserved zone needs to be re-enabled Register rthread = r15_thread; Label no_reserved_zone_enabling; @@ -977,6 +980,58 @@ void InterpreterMacroAssembler::remove_activation(TosState state, bind(no_reserved_zone_enabling); } + // remove activation + // get sender sp + movptr(rbx, + Address(rbp, frame::interpreter_frame_sender_sp_offset * wordSize)); + + if (state == atos && InlineTypeReturnedAsFields) { + Label skip; + Label not_null; + testptr(rax, rax); + jcc(Assembler::notZero, not_null); + // Returned value is null, zero all return registers because they may belong to oop fields + xorq(j_rarg1, j_rarg1); + xorq(j_rarg2, j_rarg2); + xorq(j_rarg3, j_rarg3); + xorq(j_rarg4, j_rarg4); + xorq(j_rarg5, j_rarg5); + jmp(skip); + bind(not_null); + + // Check if we are returning an non-null inline type and load its fields into registers + test_oop_is_not_inline_type(rax, rscratch1, skip, /* can_be_null= */ false); + +#ifndef _LP64 + super_call_VM_leaf(StubRoutines::load_inline_type_fields_in_regs()); +#else + // Load fields from a buffered value with an inline class specific handler + load_klass(rdi, rax, rscratch1); + movptr(rdi, Address(rdi, InstanceKlass::adr_inlineklass_fixed_block_offset())); + movptr(rdi, Address(rdi, InlineKlass::unpack_handler_offset())); + // Unpack handler can be null if inline type is not scalarizable in returns + testptr(rdi, rdi); + jcc(Assembler::zero, skip); + call(rdi); +#endif +#ifdef ASSERT + // TODO 8284443 Enable + if (StressCallingConvention && false) { + Label skip_stress; + movptr(rscratch1, Address(rbp, frame::interpreter_frame_method_offset * wordSize)); + movl(rscratch1, Address(rscratch1, Method::flags_offset())); + testl(rcx, MethodFlags::has_scalarized_return_flag()); + jcc(Assembler::zero, skip_stress); + load_klass(rax, rax, rscratch1); + orptr(rax, 1); + bind(skip_stress); + } +#endif + // call above kills the value in rbx. Reload it. + movptr(rbx, Address(rbp, frame::interpreter_frame_sender_sp_offset * wordSize)); + bind(skip); + } + leave(); // remove frame anchor JFR_ONLY(leave_jfr_critical_section();) @@ -1013,6 +1068,95 @@ void InterpreterMacroAssembler::get_method_counters(Register method, bind(has_counters); } +void InterpreterMacroAssembler::allocate_instance(Register klass, Register new_obj, + Register t1, Register t2, + bool clear_fields, Label& alloc_failed) { + MacroAssembler::allocate_instance(klass, new_obj, t1, t2, clear_fields, alloc_failed); + if (DTraceAllocProbes) { + // Trigger dtrace event for fastpath + push(atos); + call_VM_leaf(CAST_FROM_FN_PTR(address, static_cast(SharedRuntime::dtrace_object_alloc)), new_obj); + pop(atos); + } +} + +void InterpreterMacroAssembler::read_flat_field(Register entry, Register tmp1, Register tmp2, Register obj) { + Label alloc_failed, slow_path, done; + const Register alloc_temp = LP64_ONLY(rscratch1) NOT_LP64(rsi); + const Register dst_temp = LP64_ONLY(rscratch2) NOT_LP64(rdi); + assert_different_registers(obj, entry, tmp1, tmp2, dst_temp, r8, r9); + + // If the field is nullable, jump to slow path + load_unsigned_byte(tmp1, Address(entry, in_bytes(ResolvedFieldEntry::flags_offset()))); + testl(tmp1, 1 << ResolvedFieldEntry::is_null_free_inline_type_shift); + jcc(Assembler::equal, slow_path); + + // Grap the inline field klass + const Register field_klass = tmp1; + load_unsigned_short(tmp2, Address(entry, in_bytes(ResolvedFieldEntry::field_index_offset()))); + + movptr(tmp1, Address(entry, ResolvedFieldEntry::field_holder_offset())); + get_inline_type_field_klass(tmp1, tmp2, field_klass); + + // allocate buffer + push(obj); // push object being read from + allocate_instance(field_klass, obj, alloc_temp, dst_temp, false, alloc_failed); + + // Have an oop instance buffer, copy into it + load_unsigned_short(r9, Address(entry, in_bytes(ResolvedFieldEntry::field_index_offset()))); + movptr(r8, Address(entry, in_bytes(ResolvedFieldEntry::field_holder_offset()))); + inline_layout_info(r8, r9, r8); // holder, index, info => InlineLayoutInfo into r8 + + payload_addr(obj, dst_temp, field_klass); + pop(alloc_temp); // restore object being read from + load_sized_value(tmp2, Address(entry, in_bytes(ResolvedFieldEntry::field_offset_offset())), sizeof(int), true /*is_signed*/); + lea(tmp2, Address(alloc_temp, tmp2)); + // call_VM_leaf, clobbers a few regs, save restore new obj + push(obj); + flat_field_copy(IS_DEST_UNINITIALIZED, tmp2, dst_temp, r8); + pop(obj); + jmp(done); + + bind(alloc_failed); + pop(obj); + bind(slow_path); + call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::read_flat_field), + obj, entry); + get_vm_result_oop(obj); + bind(done); +} + +void InterpreterMacroAssembler::write_flat_field(Register entry, Register tmp1, Register tmp2, + Register obj, Register off, Register value) { + assert_different_registers(entry, tmp1, tmp2, obj, off, value); + + Label slow_path, done; + + load_unsigned_byte(tmp2, Address(entry, in_bytes(ResolvedFieldEntry::flags_offset()))); + test_field_is_not_null_free_inline_type(tmp2, tmp1, slow_path); + + null_check(value); // FIXME JDK-8341120 + + lea(obj, Address(obj, off, Address::times_1)); + + load_klass(tmp2, value, tmp1); + payload_addr(value, value, tmp2); + + Register idx = tmp1; + load_unsigned_short(idx, Address(entry, in_bytes(ResolvedFieldEntry::field_index_offset()))); + movptr(tmp2, Address(entry, in_bytes(ResolvedFieldEntry::field_holder_offset()))); + + Register layout_info = off; + inline_layout_info(tmp2, idx, layout_info); + + flat_field_copy(IN_HEAP, value, obj, layout_info); + jmp(done); + + bind(slow_path); + call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::write_flat_field), obj, value, entry); + + bind(done); +} // Lock object // @@ -1265,7 +1409,7 @@ void InterpreterMacroAssembler::profile_taken_branch(Register mdp) { } -void InterpreterMacroAssembler::profile_not_taken_branch(Register mdp) { +void InterpreterMacroAssembler::profile_not_taken_branch(Register mdp, bool acmp) { if (ProfileInterpreter) { Label profile_continue; @@ -1277,7 +1421,7 @@ void InterpreterMacroAssembler::profile_not_taken_branch(Register mdp) { // The method data pointer needs to be updated to correspond to // the next bytecode - update_mdp_by_constant(mdp, in_bytes(BranchData::branch_data_size())); + update_mdp_by_constant(mdp, acmp ? in_bytes(ACmpData::acmp_data_size()): in_bytes(BranchData::branch_data_size())); bind(profile_continue); } } @@ -1340,7 +1484,7 @@ void InterpreterMacroAssembler::profile_virtual_call(Register receiver, } // Record the receiver type. - record_klass_in_profile(receiver, mdp, reg2, true); + record_klass_in_profile(receiver, mdp, reg2); bind(skip_receiver_profile); // The method data pointer needs to be updated to reflect the new target. @@ -1360,10 +1504,9 @@ void InterpreterMacroAssembler::profile_virtual_call(Register receiver, // function is recursive, to generate the required tree structured code. // It's the interpreter, so we are trading off code space for speed. // See below for example code. -void InterpreterMacroAssembler::record_klass_in_profile_helper( - Register receiver, Register mdp, - Register reg2, int start_row, - Label& done, bool is_virtual_call) { +void InterpreterMacroAssembler::record_klass_in_profile_helper(Register receiver, Register mdp, + Register reg2, int start_row, + Label& done) { if (TypeProfileWidth == 0) { increment_mdp_data_at(mdp, in_bytes(CounterData::count_offset())); } else { @@ -1467,13 +1610,11 @@ void InterpreterMacroAssembler::record_item_in_profile_helper(Register item, Reg // } // done: -void InterpreterMacroAssembler::record_klass_in_profile(Register receiver, - Register mdp, Register reg2, - bool is_virtual_call) { +void InterpreterMacroAssembler::record_klass_in_profile(Register receiver, Register mdp, Register reg2) { assert(ProfileInterpreter, "must be profiling"); Label done; - record_klass_in_profile_helper(receiver, mdp, reg2, 0, done, is_virtual_call); + record_klass_in_profile_helper(receiver, mdp, reg2, 0, done); bind (done); } @@ -1550,7 +1691,7 @@ void InterpreterMacroAssembler::profile_typecheck(Register mdp, Register klass, mdp_delta = in_bytes(VirtualCallData::virtual_call_data_size()); // Record the object type. - record_klass_in_profile(klass, mdp, reg2, false); + record_klass_in_profile(klass, mdp, reg2); } update_mdp_by_constant(mdp, mdp_delta); @@ -1610,6 +1751,120 @@ void InterpreterMacroAssembler::profile_switch_case(Register index, } } +template void InterpreterMacroAssembler::profile_array_type(Register mdp, + Register array, + Register tmp) { + if (ProfileInterpreter) { + Label profile_continue; + + // If no method data exists, go to profile_continue. + test_method_data_pointer(mdp, profile_continue); + + mov(tmp, array); + profile_obj_type(tmp, Address(mdp, in_bytes(ArrayData::array_offset()))); + + Label not_flat; + test_non_flat_array_oop(array, tmp, not_flat); + + set_mdp_flag_at(mdp, ArrayData::flat_array_byte_constant()); + + bind(not_flat); + + Label not_null_free; + test_non_null_free_array_oop(array, tmp, not_null_free); + + set_mdp_flag_at(mdp, ArrayData::null_free_array_byte_constant()); + + bind(not_null_free); + + bind(profile_continue); + } +} + +template void InterpreterMacroAssembler::profile_array_type(Register mdp, + Register array, + Register tmp); +template void InterpreterMacroAssembler::profile_array_type(Register mdp, + Register array, + Register tmp); + + +void InterpreterMacroAssembler::profile_multiple_element_types(Register mdp, Register element, Register tmp, const Register tmp2) { + if (ProfileInterpreter) { + Label profile_continue; + + // If no method data exists, go to profile_continue. + test_method_data_pointer(mdp, profile_continue); + + Label done, update; + testptr(element, element); + jccb(Assembler::notZero, update); + set_mdp_flag_at(mdp, BitData::null_seen_byte_constant()); + jmp(done); + + bind(update); + load_klass(tmp, element, rscratch1); + + // Record the object type. + record_klass_in_profile(tmp, mdp, tmp2); + + bind(done); + + // The method data pointer needs to be updated. + update_mdp_by_constant(mdp, in_bytes(ArrayStoreData::array_store_data_size())); + + bind(profile_continue); + } +} + +void InterpreterMacroAssembler::profile_element_type(Register mdp, + Register element, + Register tmp) { + if (ProfileInterpreter) { + Label profile_continue; + + // If no method data exists, go to profile_continue. + test_method_data_pointer(mdp, profile_continue); + + mov(tmp, element); + profile_obj_type(tmp, Address(mdp, in_bytes(ArrayLoadData::element_offset()))); + + // The method data pointer needs to be updated. + update_mdp_by_constant(mdp, in_bytes(ArrayLoadData::array_load_data_size())); + + bind(profile_continue); + } +} + +void InterpreterMacroAssembler::profile_acmp(Register mdp, + Register left, + Register right, + Register tmp) { + if (ProfileInterpreter) { + Label profile_continue; + + // If no method data exists, go to profile_continue. + test_method_data_pointer(mdp, profile_continue); + + mov(tmp, left); + profile_obj_type(tmp, Address(mdp, in_bytes(ACmpData::left_offset()))); + + Label left_not_inline_type; + test_oop_is_not_inline_type(left, tmp, left_not_inline_type); + set_mdp_flag_at(mdp, ACmpData::left_inline_type_byte_constant()); + bind(left_not_inline_type); + + mov(tmp, right); + profile_obj_type(tmp, Address(mdp, in_bytes(ACmpData::right_offset()))); + + Label right_not_inline_type; + test_oop_is_not_inline_type(right, tmp, right_not_inline_type); + set_mdp_flag_at(mdp, ACmpData::right_inline_type_byte_constant()); + bind(right_not_inline_type); + + bind(profile_continue); + } +} void InterpreterMacroAssembler::_interp_verify_oop(Register reg, TosState state, const char* file, int line) { diff --git a/src/hotspot/cpu/x86/interp_masm_x86.hpp b/src/hotspot/cpu/x86/interp_masm_x86.hpp index a36a697eebf..fa4d3d49dc3 100644 --- a/src/hotspot/cpu/x86/interp_masm_x86.hpp +++ b/src/hotspot/cpu/x86/interp_masm_x86.hpp @@ -162,7 +162,7 @@ class InterpreterMacroAssembler: public MacroAssembler { // Generate a subtype check: branch to ok_is_subtype if sub_klass is // a subtype of super_klass. - void gen_subtype_check( Register sub_klass, Label &ok_is_subtype ); + void gen_subtype_check(Register sub_klass, Label &ok_is_subtype, bool profile = true); // Dispatching void dispatch_prolog(TosState state, int step = 0); @@ -202,6 +202,23 @@ class InterpreterMacroAssembler: public MacroAssembler { bool notify_jvmdi = true); void get_method_counters(Register method, Register mcs, Label& skip); + // Kills t1 and t2, preserves klass, return allocation in new_obj + void allocate_instance(Register klass, Register new_obj, + Register t1, Register t2, + bool clear_fields, Label& alloc_failed); + + // Allocate instance in "obj" and read in the content of the inline field + // NOTES: + // - input holder object via "obj", which must be rax, + // will return new instance via the same reg + // - assumes holder_klass and valueKlass field klass have both been resolved + void read_flat_field(Register entry, + Register tmp1, Register tmp2, + Register obj = rax); + void write_flat_field(Register entry, + Register tmp1, Register tmp2, + Register obj, Register off, Register value); + // Object locking void lock_object (Register lock_reg); void unlock_object(Register lock_reg); @@ -221,11 +238,8 @@ class InterpreterMacroAssembler: public MacroAssembler { Register test_value_out, Label& not_equal_continue); - void record_klass_in_profile(Register receiver, Register mdp, - Register reg2, bool is_virtual_call); - void record_klass_in_profile_helper(Register receiver, Register mdp, - Register reg2, int start_row, - Label& done, bool is_virtual_call); + void record_klass_in_profile(Register receiver, Register mdp, Register reg2); + void record_klass_in_profile_helper(Register receiver, Register mdp, Register reg2, int start_row, Label &done); void record_item_in_profile_helper(Register item, Register mdp, Register reg2, int start_row, Label& done, int total_rows, OffsetFunction item_offset_fn, @@ -237,7 +251,7 @@ class InterpreterMacroAssembler: public MacroAssembler { void update_mdp_for_ret(Register return_bci); void profile_taken_branch(Register mdp); - void profile_not_taken_branch(Register mdp); + void profile_not_taken_branch(Register mdp, bool acmp = false); void profile_call(Register mdp); void profile_final_call(Register mdp); void profile_virtual_call(Register receiver, Register mdp, @@ -250,6 +264,11 @@ class InterpreterMacroAssembler: public MacroAssembler { void profile_switch_default(Register mdp); void profile_switch_case(Register index_in_scratch, Register mdp, Register scratch2); + template void profile_array_type(Register mdp, Register array, Register tmp); + + void profile_multiple_element_types(Register mdp, Register element, Register tmp, const Register tmp2); + void profile_element_type(Register mdp, Register element, Register tmp); + void profile_acmp(Register mdp, Register left, Register right, Register tmp); // Debugging // only if +VerifyOops && state == atos diff --git a/src/hotspot/cpu/x86/jniFastGetField_x86_64.cpp b/src/hotspot/cpu/x86/jniFastGetField_x86_64.cpp index 09ba4537854..2c4d34c7cd5 100644 --- a/src/hotspot/cpu/x86/jniFastGetField_x86_64.cpp +++ b/src/hotspot/cpu/x86/jniFastGetField_x86_64.cpp @@ -29,6 +29,7 @@ #include "memory/resourceArea.hpp" #include "prims/jniFastGetField.hpp" #include "prims/jvm_misc.hpp" +#include "runtime/jfieldIDWorkaround.hpp" #include "prims/jvmtiExport.hpp" #include "runtime/safepoint.hpp" @@ -80,7 +81,7 @@ address JNI_FastGetField::generate_fast_get_int_field0(BasicType type) { } __ mov (roffset, c_rarg2); - __ shrptr(roffset, 2); // offset + __ shrptr(roffset, jfieldIDWorkaround::offset_shift); // offset // Both robj and rtmp are clobbered by try_resolve_jobject_in_native. BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); @@ -182,7 +183,7 @@ address JNI_FastGetField::generate_fast_get_float_field0(BasicType type) { DEBUG_ONLY(__ movl(rtmp, 0xDEADC0DE);) __ mov (roffset, c_rarg2); - __ shrptr(roffset, 2); // offset + __ shrptr(roffset, jfieldIDWorkaround::offset_shift); // offset assert(count < LIST_CAPACITY, "LIST_CAPACITY too small"); speculative_load_pclist[count] = __ pc(); diff --git a/src/hotspot/cpu/x86/macroAssembler_x86.cpp b/src/hotspot/cpu/x86/macroAssembler_x86.cpp index 0b41d594194..66605e9e99b 100644 --- a/src/hotspot/cpu/x86/macroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/macroAssembler_x86.cpp @@ -28,6 +28,7 @@ #include "code/compiledIC.hpp" #include "compiler/compiler_globals.hpp" #include "compiler/disassembler.hpp" +#include "ci/ciInlineKlass.hpp" #include "crc32c.h" #include "gc/shared/barrierSet.hpp" #include "gc/shared/barrierSetAssembler.hpp" @@ -43,6 +44,7 @@ #include "oops/compressedKlass.inline.hpp" #include "oops/compressedOops.inline.hpp" #include "oops/klass.inline.hpp" +#include "oops/resolvedFieldEntry.hpp" #include "prims/methodHandles.hpp" #include "runtime/continuation.hpp" #include "runtime/interfaceSupport.inline.hpp" @@ -53,9 +55,14 @@ #include "runtime/safepoint.hpp" #include "runtime/safepointMechanism.hpp" #include "runtime/sharedRuntime.hpp" +#include "runtime/signature_cc.hpp" #include "runtime/stubRoutines.hpp" #include "utilities/checkedCast.hpp" #include "utilities/macros.hpp" +#include "vmreg_x86.inline.hpp" +#ifdef COMPILER2 +#include "opto/output.hpp" +#endif #ifdef PRODUCT #define BLOCK_COMMENT(str) /* nothing */ @@ -1303,6 +1310,10 @@ void MacroAssembler::call_VM_leaf(address entry_point, Register arg_0, Register call_VM_leaf(entry_point, 3); } +void MacroAssembler::super_call_VM_leaf(address entry_point) { + MacroAssembler::call_VM_leaf_base(entry_point, 1); +} + void MacroAssembler::super_call_VM_leaf(address entry_point, Register arg_0) { pass_arg0(this, arg_0); MacroAssembler::call_VM_leaf_base(entry_point, 1); @@ -2356,6 +2367,113 @@ void MacroAssembler::null_check(Register reg, int offset) { } } +void MacroAssembler::test_markword_is_inline_type(Register markword, Label& is_inline_type) { + andptr(markword, markWord::inline_type_mask_in_place); + cmpptr(markword, markWord::inline_type_pattern); + jcc(Assembler::equal, is_inline_type); +} + +void MacroAssembler::test_oop_is_not_inline_type(Register object, Register tmp, Label& not_inline_type, bool can_be_null) { + if (can_be_null) { + testptr(object, object); + jcc(Assembler::zero, not_inline_type); + } + const int is_inline_type_mask = markWord::inline_type_pattern; + movptr(tmp, Address(object, oopDesc::mark_offset_in_bytes())); + andptr(tmp, is_inline_type_mask); + cmpptr(tmp, is_inline_type_mask); + jcc(Assembler::notEqual, not_inline_type); +} + +void MacroAssembler::test_field_is_null_free_inline_type(Register flags, Register temp_reg, Label& is_null_free_inline_type) { + movl(temp_reg, flags); + testl(temp_reg, 1 << ResolvedFieldEntry::is_null_free_inline_type_shift); + jcc(Assembler::notEqual, is_null_free_inline_type); +} + +void MacroAssembler::test_field_is_not_null_free_inline_type(Register flags, Register temp_reg, Label& not_null_free_inline_type) { + movl(temp_reg, flags); + testl(temp_reg, 1 << ResolvedFieldEntry::is_null_free_inline_type_shift); + jcc(Assembler::equal, not_null_free_inline_type); +} + +void MacroAssembler::test_field_is_flat(Register flags, Register temp_reg, Label& is_flat) { + movl(temp_reg, flags); + testl(temp_reg, 1 << ResolvedFieldEntry::is_flat_shift); + jcc(Assembler::notEqual, is_flat); +} + +void MacroAssembler::test_field_has_null_marker(Register flags, Register temp_reg, Label& has_null_marker) { + movl(temp_reg, flags); + testl(temp_reg, 1 << ResolvedFieldEntry::has_null_marker_shift); + jcc(Assembler::notEqual, has_null_marker); +} + +void MacroAssembler::test_oop_prototype_bit(Register oop, Register temp_reg, int32_t test_bit, bool jmp_set, Label& jmp_label) { + Label test_mark_word; + // load mark word + movptr(temp_reg, Address(oop, oopDesc::mark_offset_in_bytes())); + // check displaced + testl(temp_reg, markWord::unlocked_value); + jccb(Assembler::notZero, test_mark_word); + // slow path use klass prototype + push(rscratch1); + load_prototype_header(temp_reg, oop, rscratch1); + pop(rscratch1); + + bind(test_mark_word); + testl(temp_reg, test_bit); + jcc((jmp_set) ? Assembler::notZero : Assembler::zero, jmp_label); +} + +void MacroAssembler::test_flat_array_oop(Register oop, Register temp_reg, + Label& is_flat_array) { +#ifdef _LP64 + test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, true, is_flat_array); +#else + load_klass(temp_reg, oop, noreg); + movl(temp_reg, Address(temp_reg, Klass::layout_helper_offset())); + test_flat_array_layout(temp_reg, is_flat_array); +#endif +} + +void MacroAssembler::test_non_flat_array_oop(Register oop, Register temp_reg, + Label& is_non_flat_array) { +#ifdef _LP64 + test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, false, is_non_flat_array); +#else + load_klass(temp_reg, oop, noreg); + movl(temp_reg, Address(temp_reg, Klass::layout_helper_offset())); + test_non_flat_array_layout(temp_reg, is_non_flat_array); +#endif +} + +void MacroAssembler::test_null_free_array_oop(Register oop, Register temp_reg, Label&is_null_free_array) { +#ifdef _LP64 + test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, true, is_null_free_array); +#else + Unimplemented(); +#endif +} + +void MacroAssembler::test_non_null_free_array_oop(Register oop, Register temp_reg, Label&is_non_null_free_array) { +#ifdef _LP64 + test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, false, is_non_null_free_array); +#else + Unimplemented(); +#endif +} + +void MacroAssembler::test_flat_array_layout(Register lh, Label& is_flat_array) { + testl(lh, Klass::_lh_array_tag_flat_value_bit_inplace); + jcc(Assembler::notZero, is_flat_array); +} + +void MacroAssembler::test_non_flat_array_layout(Register lh, Label& is_non_flat_array) { + testl(lh, Klass::_lh_array_tag_flat_value_bit_inplace); + jcc(Assembler::zero, is_non_flat_array); +} + void MacroAssembler::os_breakpoint() { // instead of directly emitting a breakpoint, call os:breakpoint for better debugability // (e.g., MSVC can't call ps() otherwise) @@ -3444,6 +3562,124 @@ void MacroAssembler::testptr(Register dst, Register src) { testq(dst, src); } +// Object / value buffer allocation... +// +// Kills klass and rsi on LP64 +void MacroAssembler::allocate_instance(Register klass, Register new_obj, + Register t1, Register t2, + bool clear_fields, Label& alloc_failed) +{ + Label done, initialize_header, initialize_object, slow_case, slow_case_no_pop; + Register layout_size = t1; + assert(new_obj == rax, "needs to be rax"); + assert_different_registers(klass, new_obj, t1, t2); + + // get instance_size in InstanceKlass (scaled to a count of bytes) + movl(layout_size, Address(klass, Klass::layout_helper_offset())); + // test to see if it is malformed in some way + testl(layout_size, Klass::_lh_instance_slow_path_bit); + jcc(Assembler::notZero, slow_case_no_pop); + + // Allocate the instance: + // If TLAB is enabled: + // Try to allocate in the TLAB. + // If fails, go to the slow path. + // Else If inline contiguous allocations are enabled: + // Try to allocate in eden. + // If fails due to heap end, go to slow path. + // + // If TLAB is enabled OR inline contiguous is enabled: + // Initialize the allocation. + // Exit. + // + // Go to slow path. + + push(klass); + if (UseTLAB) { + tlab_allocate(new_obj, layout_size, 0, klass, t2, slow_case); + if (ZeroTLAB || (!clear_fields)) { + // the fields have been already cleared + jmp(initialize_header); + } else { + // initialize both the header and fields + jmp(initialize_object); + } + } else { + jmp(slow_case); + } + + // If UseTLAB is true, the object is created above and there is an initialize need. + // Otherwise, skip and go to the slow path. + if (UseTLAB) { + if (clear_fields) { + // The object is initialized before the header. If the object size is + // zero, go directly to the header initialization. + bind(initialize_object); + if (UseCompactObjectHeaders) { + assert(is_aligned(oopDesc::base_offset_in_bytes(), BytesPerLong), "oop base offset must be 8-byte-aligned"); + decrement(layout_size, oopDesc::base_offset_in_bytes()); + } else { + decrement(layout_size, sizeof(oopDesc)); + } + jcc(Assembler::zero, initialize_header); + + // Initialize topmost object field, divide size by 8, check if odd and + // test if zero. + Register zero = klass; + xorl(zero, zero); // use zero reg to clear memory (shorter code) + shrl(layout_size, LogBytesPerLong); // divide by 2*oopSize and set carry flag if odd + + #ifdef ASSERT + // make sure instance_size was multiple of 8 + Label L; + // Ignore partial flag stall after shrl() since it is debug VM + jcc(Assembler::carryClear, L); + stop("object size is not multiple of 2 - adjust this code"); + bind(L); + // must be > 0, no extra check needed here + #endif + + // initialize remaining object fields: instance_size was a multiple of 8 + { + Label loop; + bind(loop); + int header_size_bytes = oopDesc::header_size() * HeapWordSize; + assert(is_aligned(header_size_bytes, BytesPerLong), "oop header size must be 8-byte-aligned"); + movptr(Address(new_obj, layout_size, Address::times_8, header_size_bytes - 1*oopSize), zero); + decrement(layout_size); + jcc(Assembler::notZero, loop); + } + } // clear_fields + + // initialize object header only. + bind(initialize_header); + if (UseCompactObjectHeaders || EnableValhalla) { + pop(klass); + Register mark_word = t2; + movptr(mark_word, Address(klass, Klass::prototype_header_offset())); + movptr(Address(new_obj, oopDesc::mark_offset_in_bytes ()), mark_word); + } else { + movptr(Address(new_obj, oopDesc::mark_offset_in_bytes()), + (intptr_t)markWord::prototype().value()); // header + pop(klass); // get saved klass back in the register. + } + if (!UseCompactObjectHeaders) { + xorl(rsi, rsi); // use zero reg to clear memory (shorter code) + store_klass_gap(new_obj, rsi); // zero klass gap for compressed oops + movptr(t2, klass); // preserve klass + store_klass(new_obj, t2, rscratch1); // src klass reg is potentially compressed + } + jmp(done); + } + + bind(slow_case); + pop(klass); + bind(slow_case_no_pop); + jmp(alloc_failed); + + bind(done); +} + // Defines obj, preserves var_size_in_bytes, okay for t2 == var_size_in_bytes. void MacroAssembler::tlab_allocate(Register obj, Register var_size_in_bytes, @@ -3647,6 +3883,33 @@ void MacroAssembler::zero_memory(Register address, Register length_in_bytes, int bind(done); } +void MacroAssembler::get_inline_type_field_klass(Register holder_klass, Register index, Register inline_klass) { + inline_layout_info(holder_klass, index, inline_klass); + movptr(inline_klass, Address(inline_klass, InlineLayoutInfo::klass_offset())); +} + +void MacroAssembler::inline_layout_info(Register holder_klass, Register index, Register layout_info) { + movptr(layout_info, Address(holder_klass, InstanceKlass::inline_layout_info_array_offset())); +#ifdef ASSERT + { + Label done; + cmpptr(layout_info, 0); + jcc(Assembler::notEqual, done); + stop("inline_layout_info_array is null"); + bind(done); + } +#endif + + InlineLayoutInfo array[2]; + int size = (char*)&array[1] - (char*)&array[0]; // computing size of array elements + if (is_power_of_2(size)) { + shll(index, log2i_exact(size)); // Scale index by power of 2 + } else { + imull(index, index, size); // Scale the index to be the entry index * array_element_size + } + lea(layout_info, Address(layout_info, index, Address::times_1, Array::base_offset_in_bytes())); +} + // Look up the method for a megamorphic invokeinterface call. // The target method is determined by . // The receiver klass is in recv_klass. @@ -4697,7 +4960,11 @@ void MacroAssembler::cmov32(Condition cc, Register dst, Register src) { } void MacroAssembler::_verify_oop(Register reg, const char* s, const char* file, int line) { - if (!VerifyOops) return; + if (!VerifyOops || VerifyAdapterSharing) { + // Below address of the code string confuses VerifyAdapterSharing + // because it may differ between otherwise equivalent adapters. + return; + } BLOCK_COMMENT("verify_oop {"); push(rscratch1); @@ -4756,7 +5023,11 @@ Address MacroAssembler::argument_address(RegisterOrConstant arg_slot, } void MacroAssembler::_verify_oop_addr(Address addr, const char* s, const char* file, int line) { - if (!VerifyOops) return; + if (!VerifyOops || VerifyAdapterSharing) { + // Below address of the code string confuses VerifyAdapterSharing + // because it may differ between otherwise equivalent adapters. + return; + } push(rscratch1); push(rax); // save rax, @@ -5150,6 +5421,16 @@ void MacroAssembler::load_method_holder(Register holder, Register method) { movptr(holder, Address(holder, ConstantPool::pool_holder_offset())); // InstanceKlass* } +void MacroAssembler::load_metadata(Register dst, Register src) { + if (UseCompactObjectHeaders) { + load_narrow_klass_compact(dst, src); + } else if (UseCompressedClassPointers) { + movl(dst, Address(src, oopDesc::klass_offset_in_bytes())); + } else { + movptr(dst, Address(src, oopDesc::klass_offset_in_bytes())); + } +} + void MacroAssembler::load_narrow_klass_compact(Register dst, Register src) { assert(UseCompactObjectHeaders, "expect compact object headers"); movq(dst, Address(src, oopDesc::mark_offset_in_bytes())); @@ -5171,6 +5452,11 @@ void MacroAssembler::load_klass(Register dst, Register src, Register tmp) { } } +void MacroAssembler::load_prototype_header(Register dst, Register src, Register tmp) { + load_klass(dst, src, tmp); + movptr(dst, Address(dst, Klass::prototype_header_offset())); +} + void MacroAssembler::store_klass(Register dst, Register src, Register tmp) { assert(!UseCompactObjectHeaders, "not with compact headers"); assert_different_registers(src, tmp); @@ -5236,6 +5522,46 @@ void MacroAssembler::access_store_at(BasicType type, DecoratorSet decorators, Ad } } +void MacroAssembler::flat_field_copy(DecoratorSet decorators, Register src, Register dst, + Register inline_layout_info) { + BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); + bs->flat_field_copy(this, decorators, src, dst, inline_layout_info); +} + +void MacroAssembler::payload_offset(Register inline_klass, Register offset) { + movptr(offset, Address(inline_klass, InstanceKlass::adr_inlineklass_fixed_block_offset())); + movl(offset, Address(offset, InlineKlass::payload_offset_offset())); +} + +void MacroAssembler::payload_addr(Register oop, Register data, Register inline_klass) { + // ((address) (void*) o) + vk->payload_offset(); + Register offset = (data == oop) ? rscratch1 : data; + payload_offset(inline_klass, offset); + if (data == oop) { + addptr(data, offset); + } else { + lea(data, Address(oop, offset)); + } +} + +void MacroAssembler::data_for_value_array_index(Register array, Register array_klass, + Register index, Register data) { + assert(index != rcx, "index needs to shift by rcx"); + assert_different_registers(array, array_klass, index); + assert_different_registers(rcx, array, index); + + // array->base() + (index << Klass::layout_helper_log2_element_size(lh)); + movl(rcx, Address(array_klass, Klass::layout_helper_offset())); + + // Klass::layout_helper_log2_element_size(lh) + // (lh >> _lh_log2_element_size_shift) & _lh_log2_element_size_mask; + shrl(rcx, Klass::_lh_log2_element_size_shift); + andl(rcx, Klass::_lh_log2_element_size_mask); + shlptr(index); // index << rcx + + lea(data, Address(array, index, Address::times_1, arrayOopDesc::base_offset_in_bytes(T_FLAT_ELEMENT))); +} + void MacroAssembler::load_heap_oop(Register dst, Address src, Register tmp1, DecoratorSet decorators) { access_load_at(T_OBJECT, IN_HEAP | decorators, dst, src, tmp1); } @@ -5595,17 +5921,20 @@ void MacroAssembler::reinit_heapbase() { #if COMPILER2_OR_JVMCI // clear memory of size 'cnt' qwords, starting at 'base' using XMM/YMM/ZMM registers -void MacroAssembler::xmm_clear_mem(Register base, Register cnt, Register rtmp, XMMRegister xtmp, KRegister mask) { +void MacroAssembler::xmm_clear_mem(Register base, Register cnt, Register val, XMMRegister xtmp, KRegister mask) { // cnt - number of qwords (8-byte words). // base - start address, qword aligned. Label L_zero_64_bytes, L_loop, L_sloop, L_tail, L_end; bool use64byteVector = (MaxVectorSize == 64) && (VM_Version::avx3_threshold() == 0); if (use64byteVector) { - vpxor(xtmp, xtmp, xtmp, AVX_512bit); + evpbroadcastq(xtmp, val, AVX_512bit); } else if (MaxVectorSize >= 32) { - vpxor(xtmp, xtmp, xtmp, AVX_256bit); + movdq(xtmp, val); + punpcklqdq(xtmp, xtmp); + vinserti128_high(xtmp, xtmp); } else { - pxor(xtmp, xtmp); + movdq(xtmp, val); + punpcklqdq(xtmp, xtmp); } jmp(L_zero_64_bytes); @@ -5628,7 +5957,7 @@ void MacroAssembler::xmm_clear_mem(Register base, Register cnt, Register rtmp, X if (use64byteVector) { addptr(cnt, 8); jccb(Assembler::equal, L_end); - fill64_masked(3, base, 0, xtmp, mask, cnt, rtmp, true); + fill64_masked(3, base, 0, xtmp, mask, cnt, val, true); jmp(L_end); } else { addptr(cnt, 4); @@ -5647,7 +5976,7 @@ void MacroAssembler::xmm_clear_mem(Register base, Register cnt, Register rtmp, X addptr(cnt, 4); jccb(Assembler::lessEqual, L_end); if (UseAVX > 2 && MaxVectorSize >= 32 && VM_Version::supports_avx512vl()) { - fill32_masked(3, base, 0, xtmp, mask, cnt, rtmp); + fill32_masked(3, base, 0, xtmp, mask, cnt, val); } else { decrement(cnt); @@ -5660,6 +5989,416 @@ void MacroAssembler::xmm_clear_mem(Register base, Register cnt, Register rtmp, X BIND(L_end); } +int MacroAssembler::store_inline_type_fields_to_buf(ciInlineKlass* vk, bool from_interpreter) { + assert(InlineTypeReturnedAsFields, "Inline types should never be returned as fields"); + // An inline type might be returned. If fields are in registers we + // need to allocate an inline type instance and initialize it with + // the value of the fields. + Label skip; + // We only need a new buffered inline type if a new one is not returned + testptr(rax, 1); + jcc(Assembler::zero, skip); + int call_offset = -1; + +#ifdef _LP64 + // The following code is similar to allocate_instance but has some slight differences, + // e.g. object size is always not zero, sometimes it's constant; storing klass ptr after + // allocating is not necessary if vk != nullptr, etc. allocate_instance is not aware of these. + Label slow_case; + // 1. Try to allocate a new buffered inline instance either from TLAB or eden space + mov(rscratch1, rax); // save rax for slow_case since *_allocate may corrupt it when allocation failed + if (vk != nullptr) { + // Called from C1, where the return type is statically known. + movptr(rbx, (intptr_t)vk->get_InlineKlass()); + jint lh = vk->layout_helper(); + assert(lh != Klass::_lh_neutral_value, "inline class in return type must have been resolved"); + if (UseTLAB && !Klass::layout_helper_needs_slow_path(lh)) { + tlab_allocate(rax, noreg, lh, r13, r14, slow_case); + } else { + jmp(slow_case); + } + } else { + // Call from interpreter. RAX contains ((the InlineKlass* of the return type) | 0x01) + mov(rbx, rax); + andptr(rbx, -2); + if (UseTLAB) { + movl(r14, Address(rbx, Klass::layout_helper_offset())); + testl(r14, Klass::_lh_instance_slow_path_bit); + jcc(Assembler::notZero, slow_case); + tlab_allocate(rax, r14, 0, r13, r14, slow_case); + } else { + jmp(slow_case); + } + } + if (UseTLAB) { + // 2. Initialize buffered inline instance header + Register buffer_obj = rax; + Register klass = rbx; + if (UseCompactObjectHeaders) { + Register mark_word = r13; + movptr(mark_word, Address(klass, Klass::prototype_header_offset())); + movptr(Address(buffer_obj, oopDesc::mark_offset_in_bytes()), mark_word); + } else { + movptr(Address(buffer_obj, oopDesc::mark_offset_in_bytes()), (intptr_t)markWord::inline_type_prototype().value()); + xorl(r13, r13); + store_klass_gap(buffer_obj, r13); + if (vk == nullptr) { + // store_klass corrupts rbx(klass), so save it in r13 for later use (interpreter case only). + mov(r13, klass); + } + store_klass(buffer_obj, klass, rscratch1); + klass = r13; + } + // 3. Initialize its fields with an inline class specific handler + if (vk != nullptr) { + call(RuntimeAddress(vk->pack_handler())); // no need for call info as this will not safepoint. + } else { + movptr(rbx, Address(klass, InstanceKlass::adr_inlineklass_fixed_block_offset())); + movptr(rbx, Address(rbx, InlineKlass::pack_handler_offset())); + call(rbx); + } + jmp(skip); + } + bind(slow_case); + // We failed to allocate a new inline type, fall back to a runtime + // call. Some oop field may be live in some registers but we can't + // tell. That runtime call will take care of preserving them + // across a GC if there's one. + mov(rax, rscratch1); +#endif + + if (from_interpreter) { + super_call_VM_leaf(StubRoutines::store_inline_type_fields_to_buf()); + } else { + call(RuntimeAddress(StubRoutines::store_inline_type_fields_to_buf())); + call_offset = offset(); + } + + bind(skip); + return call_offset; +} + +// Move a value between registers/stack slots and update the reg_state +bool MacroAssembler::move_helper(VMReg from, VMReg to, BasicType bt, RegState reg_state[]) { + assert(from->is_valid() && to->is_valid(), "source and destination must be valid"); + if (reg_state[to->value()] == reg_written) { + return true; // Already written + } + if (from != to && bt != T_VOID) { + if (reg_state[to->value()] == reg_readonly) { + return false; // Not yet writable + } + if (from->is_reg()) { + if (to->is_reg()) { + if (from->is_XMMRegister()) { + if (bt == T_DOUBLE) { + movdbl(to->as_XMMRegister(), from->as_XMMRegister()); + } else { + assert(bt == T_FLOAT, "must be float"); + movflt(to->as_XMMRegister(), from->as_XMMRegister()); + } + } else { + movq(to->as_Register(), from->as_Register()); + } + } else { + int st_off = to->reg2stack() * VMRegImpl::stack_slot_size + wordSize; + Address to_addr = Address(rsp, st_off); + if (from->is_XMMRegister()) { + if (bt == T_DOUBLE) { + movdbl(to_addr, from->as_XMMRegister()); + } else { + assert(bt == T_FLOAT, "must be float"); + movflt(to_addr, from->as_XMMRegister()); + } + } else { + movq(to_addr, from->as_Register()); + } + } + } else { + Address from_addr = Address(rsp, from->reg2stack() * VMRegImpl::stack_slot_size + wordSize); + if (to->is_reg()) { + if (to->is_XMMRegister()) { + if (bt == T_DOUBLE) { + movdbl(to->as_XMMRegister(), from_addr); + } else { + assert(bt == T_FLOAT, "must be float"); + movflt(to->as_XMMRegister(), from_addr); + } + } else { + movq(to->as_Register(), from_addr); + } + } else { + int st_off = to->reg2stack() * VMRegImpl::stack_slot_size + wordSize; + movq(r13, from_addr); + movq(Address(rsp, st_off), r13); + } + } + } + // Update register states + reg_state[from->value()] = reg_writable; + reg_state[to->value()] = reg_written; + return true; +} + +// Calculate the extra stack space required for packing or unpacking inline +// args and adjust the stack pointer +int MacroAssembler::extend_stack_for_inline_args(int args_on_stack) { + // Two additional slots to account for return address + int sp_inc = (args_on_stack + 2) * VMRegImpl::stack_slot_size; + sp_inc = align_up(sp_inc, StackAlignmentInBytes); + // Save the return address, adjust the stack (make sure it is properly + // 16-byte aligned) and copy the return address to the new top of the stack. + // The stack will be repaired on return (see MacroAssembler::remove_frame). + assert(sp_inc > 0, "sanity"); + pop(r13); + subptr(rsp, sp_inc); + push(r13); + return sp_inc; +} + +// Read all fields from an inline type buffer and store the field values in registers/stack slots. +bool MacroAssembler::unpack_inline_helper(const GrowableArray* sig, int& sig_index, + VMReg from, int& from_index, VMRegPair* to, int to_count, int& to_index, + RegState reg_state[]) { + assert(sig->at(sig_index)._bt == T_VOID, "should be at end delimiter"); + assert(from->is_valid(), "source must be valid"); + bool progress = false; +#ifdef ASSERT + const int start_offset = offset(); +#endif + + Label L_null, L_notNull; + // Don't use r14 as tmp because it's used for spilling (see MacroAssembler::spill_reg_for) + Register tmp1 = r10; + Register tmp2 = r13; + Register fromReg = noreg; + ScalarizedInlineArgsStream stream(sig, sig_index, to, to_count, to_index, -1); + bool done = true; + bool mark_done = true; + VMReg toReg; + BasicType bt; + // Check if argument requires a null check + bool null_check = false; + VMReg nullCheckReg; + while (stream.next(nullCheckReg, bt)) { + if (sig->at(stream.sig_index())._offset == -1) { + null_check = true; + break; + } + } + stream.reset(sig_index, to_index); + while (stream.next(toReg, bt)) { + assert(toReg->is_valid(), "destination must be valid"); + int idx = (int)toReg->value(); + if (reg_state[idx] == reg_readonly) { + if (idx != from->value()) { + mark_done = false; + } + done = false; + continue; + } else if (reg_state[idx] == reg_written) { + continue; + } + assert(reg_state[idx] == reg_writable, "must be writable"); + reg_state[idx] = reg_written; + progress = true; + + if (fromReg == noreg) { + if (from->is_reg()) { + fromReg = from->as_Register(); + } else { + int st_off = from->reg2stack() * VMRegImpl::stack_slot_size + wordSize; + movq(tmp1, Address(rsp, st_off)); + fromReg = tmp1; + } + if (null_check) { + // Nullable inline type argument, emit null check + testptr(fromReg, fromReg); + jcc(Assembler::zero, L_null); + } + } + int off = sig->at(stream.sig_index())._offset; + if (off == -1) { + assert(null_check, "Missing null check at"); + if (toReg->is_stack()) { + int st_off = toReg->reg2stack() * VMRegImpl::stack_slot_size + wordSize; + movq(Address(rsp, st_off), 1); + } else { + movq(toReg->as_Register(), 1); + } + continue; + } + assert(off > 0, "offset in object should be positive"); + Address fromAddr = Address(fromReg, off); + if (!toReg->is_XMMRegister()) { + Register dst = toReg->is_stack() ? tmp2 : toReg->as_Register(); + if (is_reference_type(bt)) { + load_heap_oop(dst, fromAddr); + } else { + bool is_signed = (bt != T_CHAR) && (bt != T_BOOLEAN); + load_sized_value(dst, fromAddr, type2aelembytes(bt), is_signed); + } + if (toReg->is_stack()) { + int st_off = toReg->reg2stack() * VMRegImpl::stack_slot_size + wordSize; + movq(Address(rsp, st_off), dst); + } + } else if (bt == T_DOUBLE) { + movdbl(toReg->as_XMMRegister(), fromAddr); + } else { + assert(bt == T_FLOAT, "must be float"); + movflt(toReg->as_XMMRegister(), fromAddr); + } + } + if (progress && null_check) { + if (done) { + jmp(L_notNull); + bind(L_null); + // Set null marker to zero to signal that the argument is null. + // Also set all oop fields to zero to make the GC happy. + stream.reset(sig_index, to_index); + while (stream.next(toReg, bt)) { + if (sig->at(stream.sig_index())._offset == -1 || + bt == T_OBJECT || bt == T_ARRAY) { + if (toReg->is_stack()) { + int st_off = toReg->reg2stack() * VMRegImpl::stack_slot_size + wordSize; + movq(Address(rsp, st_off), 0); + } else { + xorq(toReg->as_Register(), toReg->as_Register()); + } + } + } + bind(L_notNull); + } else { + bind(L_null); + } + } + + sig_index = stream.sig_index(); + to_index = stream.regs_index(); + + if (mark_done && reg_state[from->value()] != reg_written) { + // This is okay because no one else will write to that slot + reg_state[from->value()] = reg_writable; + } + from_index--; + assert(progress || (start_offset == offset()), "should not emit code"); + return done; +} + +bool MacroAssembler::pack_inline_helper(const GrowableArray* sig, int& sig_index, int vtarg_index, + VMRegPair* from, int from_count, int& from_index, VMReg to, + RegState reg_state[], Register val_array) { + assert(sig->at(sig_index)._bt == T_METADATA, "should be at delimiter"); + assert(to->is_valid(), "destination must be valid"); + + if (reg_state[to->value()] == reg_written) { + skip_unpacked_fields(sig, sig_index, from, from_count, from_index); + return true; // Already written + } + + // TODO 8284443 Isn't it an issue if below code uses r14 as tmp when it contains a spilled value? + // Be careful with r14 because it's used for spilling (see MacroAssembler::spill_reg_for). + Register val_obj_tmp = r11; + Register from_reg_tmp = r14; + Register tmp1 = r10; + Register tmp2 = r13; + Register tmp3 = rbx; + Register val_obj = to->is_stack() ? val_obj_tmp : to->as_Register(); + + assert_different_registers(val_obj_tmp, from_reg_tmp, tmp1, tmp2, tmp3, val_array); + + if (reg_state[to->value()] == reg_readonly) { + if (!is_reg_in_unpacked_fields(sig, sig_index, to, from, from_count, from_index)) { + skip_unpacked_fields(sig, sig_index, from, from_count, from_index); + return false; // Not yet writable + } + val_obj = val_obj_tmp; + } + + int index = arrayOopDesc::base_offset_in_bytes(T_OBJECT) + vtarg_index * type2aelembytes(T_OBJECT); + load_heap_oop(val_obj, Address(val_array, index)); + + ScalarizedInlineArgsStream stream(sig, sig_index, from, from_count, from_index); + VMReg fromReg; + BasicType bt; + Label L_null; + while (stream.next(fromReg, bt)) { + assert(fromReg->is_valid(), "source must be valid"); + reg_state[fromReg->value()] = reg_writable; + + int off = sig->at(stream.sig_index())._offset; + if (off == -1) { + // Nullable inline type argument, emit null check + Label L_notNull; + if (fromReg->is_stack()) { + int ld_off = fromReg->reg2stack() * VMRegImpl::stack_slot_size + wordSize; + testb(Address(rsp, ld_off), 1); + } else { + testb(fromReg->as_Register(), 1); + } + jcc(Assembler::notZero, L_notNull); + movptr(val_obj, 0); + jmp(L_null); + bind(L_notNull); + continue; + } + + assert(off > 0, "offset in object should be positive"); + size_t size_in_bytes = is_java_primitive(bt) ? type2aelembytes(bt) : wordSize; + + // Pack the scalarized field into the value object. + Address dst(val_obj, off); + if (!fromReg->is_XMMRegister()) { + Register src; + if (fromReg->is_stack()) { + src = from_reg_tmp; + int ld_off = fromReg->reg2stack() * VMRegImpl::stack_slot_size + wordSize; + load_sized_value(src, Address(rsp, ld_off), size_in_bytes, /* is_signed */ false); + } else { + src = fromReg->as_Register(); + } + assert_different_registers(dst.base(), src, tmp1, tmp2, tmp3, val_array); + if (is_reference_type(bt)) { + store_heap_oop(dst, src, tmp1, tmp2, tmp3, IN_HEAP | ACCESS_WRITE | IS_DEST_UNINITIALIZED); + } else { + store_sized_value(dst, src, size_in_bytes); + } + } else if (bt == T_DOUBLE) { + movdbl(dst, fromReg->as_XMMRegister()); + } else { + assert(bt == T_FLOAT, "must be float"); + movflt(dst, fromReg->as_XMMRegister()); + } + } + bind(L_null); + sig_index = stream.sig_index(); + from_index = stream.regs_index(); + + assert(reg_state[to->value()] == reg_writable, "must have already been read"); + bool success = move_helper(val_obj->as_VMReg(), to, T_OBJECT, reg_state); + assert(success, "to register must be writeable"); + return true; +} + +VMReg MacroAssembler::spill_reg_for(VMReg reg) { + return reg->is_XMMRegister() ? xmm8->as_VMReg() : r14->as_VMReg(); +} + +void MacroAssembler::remove_frame(int initial_framesize, bool needs_stack_repair) { + assert((initial_framesize & (StackAlignmentInBytes-1)) == 0, "frame size not aligned"); + if (needs_stack_repair) { + // TODO 8284443 Add a comment drawing the frame like in Aarch64's version of MacroAssembler::remove_frame + movq(rbp, Address(rsp, initial_framesize)); + // The stack increment resides just below the saved rbp + addq(rsp, Address(rsp, initial_framesize - wordSize)); + } else { + if (initial_framesize > 0) { + addq(rsp, initial_framesize); + } + pop(rbp); + } +} + // Clearing constant sized memory using YMM/ZMM registers. void MacroAssembler::clear_mem(Register base, int cnt, Register rtmp, XMMRegister xtmp, KRegister mask) { assert(UseAVX > 2 && VM_Version::supports_avx512vl(), ""); @@ -5749,21 +6488,18 @@ void MacroAssembler::clear_mem(Register base, int cnt, Register rtmp, XMMRegiste } } -void MacroAssembler::clear_mem(Register base, Register cnt, Register tmp, XMMRegister xtmp, - bool is_large, KRegister mask) { +void MacroAssembler::clear_mem(Register base, Register cnt, Register val, XMMRegister xtmp, + bool is_large, bool word_copy_only, KRegister mask) { // cnt - number of qwords (8-byte words). // base - start address, qword aligned. // is_large - if optimizers know cnt is larger than InitArrayShortSize assert(base==rdi, "base register must be edi for rep stos"); - assert(tmp==rax, "tmp register must be eax for rep stos"); + assert(val==rax, "val register must be eax for rep stos"); assert(cnt==rcx, "cnt register must be ecx for rep stos"); assert(InitArrayShortSize % BytesPerLong == 0, "InitArrayShortSize should be the multiple of BytesPerLong"); Label DONE; - if (!is_large || !UseXMMForObjInit) { - xorptr(tmp, tmp); - } if (!is_large) { Label LOOP, LONG; @@ -5775,7 +6511,7 @@ void MacroAssembler::clear_mem(Register base, Register cnt, Register tmp, XMMReg // Use individual pointer-sized stores for small counts: BIND(LOOP); - movptr(Address(base, cnt, Address::times_ptr), tmp); + movptr(Address(base, cnt, Address::times_ptr), val); decrement(cnt); jccb(Assembler::greaterEqual, LOOP); jmpb(DONE); @@ -5784,11 +6520,11 @@ void MacroAssembler::clear_mem(Register base, Register cnt, Register tmp, XMMReg } // Use longer rep-prefixed ops for non-small counts: - if (UseFastStosb) { + if (UseFastStosb && !word_copy_only) { shlptr(cnt, 3); // convert to number of bytes rep_stosb(); } else if (UseXMMForObjInit) { - xmm_clear_mem(base, cnt, tmp, xtmp, mask); + xmm_clear_mem(base, cnt, val, xtmp, mask); } else { rep_stos(); } @@ -9675,6 +10411,10 @@ void MacroAssembler::lightweight_lock(Register basic_lock, Register obj, Registe movptr(tmp, reg_rax); andptr(tmp, ~(int32_t)markWord::unlocked_value); orptr(reg_rax, markWord::unlocked_value); + if (EnableValhalla) { + // Mask inline_type bit such that we go to the slow path if object is an inline type + andptr(reg_rax, ~((int) markWord::inline_type_bit_in_place)); + } lock(); cmpxchgptr(tmp, Address(obj, oopDesc::mark_offset_in_bytes())); jcc(Assembler::notEqual, slow); diff --git a/src/hotspot/cpu/x86/macroAssembler_x86.hpp b/src/hotspot/cpu/x86/macroAssembler_x86.hpp index 1c0dbaaefbe..2d04032493d 100644 --- a/src/hotspot/cpu/x86/macroAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/macroAssembler_x86.hpp @@ -30,9 +30,12 @@ #include "code/vmreg.inline.hpp" #include "compiler/oopMap.hpp" #include "utilities/macros.hpp" +#include "runtime/signature.hpp" #include "runtime/vm_version.hpp" #include "utilities/checkedCast.hpp" +class ciInlineKlass; + // MacroAssembler extends Assembler by frequently used macros. // // Instructions for which a 'better' code sequence exists depending @@ -94,6 +97,28 @@ class MacroAssembler: public Assembler { static bool needs_explicit_null_check(intptr_t offset); static bool uses_implicit_null_check(void* address); + // markWord tests, kills markWord reg + void test_markword_is_inline_type(Register markword, Label& is_inline_type); + + // inlineKlass queries, kills temp_reg + void test_oop_is_not_inline_type(Register object, Register tmp, Label& not_inline_type, bool can_be_null = true); + + void test_field_is_null_free_inline_type(Register flags, Register temp_reg, Label& is_null_free); + void test_field_is_not_null_free_inline_type(Register flags, Register temp_reg, Label& not_null_free); + void test_field_is_flat(Register flags, Register temp_reg, Label& is_flat); + void test_field_has_null_marker(Register flags, Register temp_reg, Label& has_null_marker); + + // Check oops for special arrays, i.e. flat arrays and/or null-free arrays + void test_oop_prototype_bit(Register oop, Register temp_reg, int32_t test_bit, bool jmp_set, Label& jmp_label); + void test_flat_array_oop(Register oop, Register temp_reg, Label& is_flat_array); + void test_non_flat_array_oop(Register oop, Register temp_reg, Label& is_non_flat_array); + void test_null_free_array_oop(Register oop, Register temp_reg, Label& is_null_free_array); + void test_non_null_free_array_oop(Register oop, Register temp_reg, Label& is_non_null_free_array); + + // Check array klass layout helper for flat or null-free arrays... + void test_flat_array_layout(Register lh, Label& is_flat_array); + void test_non_flat_array_layout(Register lh, Label& is_non_flat_array); + // Required platform-specific helpers for Label::patch_instructions. // They _shadow_ the declarations in AbstractAssembler, which are undefined. void pd_patch_instruction(address branch, address target, const char* file, int line) { @@ -347,6 +372,9 @@ class MacroAssembler: public Assembler { void load_method_holder(Register holder, Register method); // oop manipulations + + // Load oopDesc._metadata without decode (useful for direct Klass* compare from oops) + void load_metadata(Register dst, Register src); void load_narrow_klass_compact(Register dst, Register src); void load_klass(Register dst, Register src, Register tmp); void store_klass(Register dst, Register src, Register tmp); @@ -364,6 +392,15 @@ class MacroAssembler: public Assembler { void access_store_at(BasicType type, DecoratorSet decorators, Address dst, Register val, Register tmp1, Register tmp2, Register tmp3); + void flat_field_copy(DecoratorSet decorators, Register src, Register dst, Register inline_layout_info); + + // inline type data payload offsets... + void payload_offset(Register inline_klass, Register offset); + void payload_addr(Register oop, Register data, Register inline_klass); + // get data payload ptr a flat value array at index, kills rcx and index + void data_for_value_array_index(Register array, Register array_klass, + Register index, Register data); + void load_heap_oop(Register dst, Address src, Register tmp1 = noreg, DecoratorSet decorators = 0); void load_heap_oop_not_null(Register dst, Address src, Register tmp1 = noreg, DecoratorSet decorators = 0); void store_heap_oop(Address dst, Register val, Register tmp1 = noreg, @@ -373,6 +410,8 @@ class MacroAssembler: public Assembler { // stored using routines that take a jobject. void store_heap_oop_null(Address dst); + void load_prototype_header(Register dst, Register src, Register tmp); + void store_klass_gap(Register dst, Register src); // This dummy is to prevent a call to store_heap_oop from @@ -510,6 +549,15 @@ class MacroAssembler: public Assembler { } // allocation + + // Object / value buffer allocation... + // Allocate instance of klass, assumes klass initialized by caller + // new_obj prefers to be rax + // Kills t1 and t2, perserves klass, return allocation in new_obj (rsi on LP64) + void allocate_instance(Register klass, Register new_obj, + Register t1, Register t2, + bool clear_fields, Label& alloc_failed); + void tlab_allocate( Register obj, // result: pointer to object after successful allocation Register var_size_in_bytes, // object size in bytes if unknown at compile time; invalid otherwise @@ -520,6 +568,11 @@ class MacroAssembler: public Assembler { ); void zero_memory(Register address, Register length_in_bytes, int offset_in_bytes, Register temp); + // For field "index" within "klass", return inline_klass ... + void get_inline_type_field_klass(Register klass, Register index, Register inline_klass); + + void inline_layout_info(Register klass, Register index, Register layout_info); + void population_count(Register dst, Register src, Register scratch1, Register scratch2); // interface method calling @@ -766,6 +819,7 @@ class MacroAssembler: public Assembler { void andptr(Register dst, int32_t src); void andptr(Register src1, Register src2) { andq(src1, src2); } + void andptr(Register dst, Address src) { andq(dst, src); } using Assembler::andq; void andq(Register dst, AddressLiteral src, Register rscratch = noreg); @@ -1904,9 +1958,24 @@ class MacroAssembler: public Assembler { public: + // Inline type specific methods + #include "asm/macroAssembler_common.hpp" + + int store_inline_type_fields_to_buf(ciInlineKlass* vk, bool from_interpreter = true); + bool move_helper(VMReg from, VMReg to, BasicType bt, RegState reg_state[]); + bool unpack_inline_helper(const GrowableArray* sig, int& sig_index, + VMReg from, int& from_index, VMRegPair* to, int to_count, int& to_index, + RegState reg_state[]); + bool pack_inline_helper(const GrowableArray* sig, int& sig_index, int vtarg_index, + VMRegPair* from, int from_count, int& from_index, VMReg to, + RegState reg_state[], Register val_array); + int extend_stack_for_inline_args(int args_on_stack); + void remove_frame(int initial_framesize, bool needs_stack_repair); + VMReg spill_reg_for(VMReg reg); + // clear memory of size 'cnt' qwords, starting at 'base'; // if 'is_large' is set, do not try to produce short loop - void clear_mem(Register base, Register cnt, Register rtmp, XMMRegister xtmp, bool is_large, KRegister mask=knoreg); + void clear_mem(Register base, Register cnt, Register val, XMMRegister xtmp, bool is_large, bool word_copy_only, KRegister mask=knoreg); // clear memory initialization sequence for constant size; void clear_mem(Register base, int cnt, Register rtmp, XMMRegister xtmp, KRegister mask=knoreg); diff --git a/src/hotspot/cpu/x86/methodHandles_x86.cpp b/src/hotspot/cpu/x86/methodHandles_x86.cpp index b921a157a52..0875e8c47a1 100644 --- a/src/hotspot/cpu/x86/methodHandles_x86.cpp +++ b/src/hotspot/cpu/x86/methodHandles_x86.cpp @@ -194,7 +194,11 @@ void MethodHandles::jump_from_method_handle(MacroAssembler* _masm, Register meth __ BIND(run_compiled_code); } - const ByteSize entry_offset = for_compiler_entry ? Method::from_compiled_offset() : + // The following jump might pass an inline type argument that was erased to Object as oop to a + // callee that expects inline type arguments to be passed as fields. We need to call the compiled + // value entry (_code->inline_entry_point() or _adapter->c2i_inline_entry()) which will take care + // of translating between the calling conventions. + const ByteSize entry_offset = for_compiler_entry ? Method::from_compiled_inline_offset() : Method::from_interpreted_offset(); __ jmp(Address(method, entry_offset)); diff --git a/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp b/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp index d60f535fefc..72cf505c1a4 100644 --- a/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp +++ b/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp @@ -27,6 +27,7 @@ #endif #include "asm/macroAssembler.hpp" #include "asm/macroAssembler.inline.hpp" +#include "classfile/symbolTable.hpp" #include "code/aotCodeCache.hpp" #include "code/compiledIC.hpp" #include "code/debugInfoRec.hpp" @@ -634,6 +635,87 @@ int SharedRuntime::java_calling_convention(const BasicType *sig_bt, return stk_args; } +// Same as java_calling_convention() but for multiple return +// values. There's no way to store them on the stack so if we don't +// have enough registers, multiple values can't be returned. +const uint SharedRuntime::java_return_convention_max_int = Argument::n_int_register_parameters_j+1; +const uint SharedRuntime::java_return_convention_max_float = Argument::n_float_register_parameters_j; +int SharedRuntime::java_return_convention(const BasicType *sig_bt, + VMRegPair *regs, + int total_args_passed) { + // Create the mapping between argument positions and + // registers. + static const Register INT_ArgReg[java_return_convention_max_int] = { + rax, j_rarg5, j_rarg4, j_rarg3, j_rarg2, j_rarg1, j_rarg0 + }; + static const XMMRegister FP_ArgReg[java_return_convention_max_float] = { + j_farg0, j_farg1, j_farg2, j_farg3, + j_farg4, j_farg5, j_farg6, j_farg7 + }; + + + uint int_args = 0; + uint fp_args = 0; + + for (int i = 0; i < total_args_passed; i++) { + switch (sig_bt[i]) { + case T_BOOLEAN: + case T_CHAR: + case T_BYTE: + case T_SHORT: + case T_INT: + if (int_args < Argument::n_int_register_parameters_j+1) { + regs[i].set1(INT_ArgReg[int_args]->as_VMReg()); + int_args++; + } else { + return -1; + } + break; + case T_VOID: + // halves of T_LONG or T_DOUBLE + assert(i != 0 && (sig_bt[i - 1] == T_LONG || sig_bt[i - 1] == T_DOUBLE), "expecting half"); + regs[i].set_bad(); + break; + case T_LONG: + assert(sig_bt[i + 1] == T_VOID, "expecting half"); + // fall through + case T_OBJECT: + case T_ARRAY: + case T_ADDRESS: + case T_METADATA: + if (int_args < Argument::n_int_register_parameters_j+1) { + regs[i].set2(INT_ArgReg[int_args]->as_VMReg()); + int_args++; + } else { + return -1; + } + break; + case T_FLOAT: + if (fp_args < Argument::n_float_register_parameters_j) { + regs[i].set1(FP_ArgReg[fp_args]->as_VMReg()); + fp_args++; + } else { + return -1; + } + break; + case T_DOUBLE: + assert(sig_bt[i + 1] == T_VOID, "expecting half"); + if (fp_args < Argument::n_float_register_parameters_j) { + regs[i].set2(FP_ArgReg[fp_args]->as_VMReg()); + fp_args++; + } else { + return -1; + } + break; + default: + ShouldNotReachHere(); + break; + } + } + + return int_args + fp_args; +} + // Patch the callers callsite with entry to compiled code if it exists. static void patch_callers_callsite(MacroAssembler *masm) { Label L; @@ -676,12 +758,147 @@ static void patch_callers_callsite(MacroAssembler *masm) { __ bind(L); } +// For each inline type argument, sig includes the list of fields of +// the inline type. This utility function computes the number of +// arguments for the call if inline types are passed by reference (the +// calling convention the interpreter expects). +static int compute_total_args_passed_int(const GrowableArray* sig_extended) { + int total_args_passed = 0; + if (InlineTypePassFieldsAsArgs) { + for (int i = 0; i < sig_extended->length(); i++) { + BasicType bt = sig_extended->at(i)._bt; + if (bt == T_METADATA) { + // In sig_extended, an inline type argument starts with: + // T_METADATA, followed by the types of the fields of the + // inline type and T_VOID to mark the end of the value + // type. Inline types are flattened so, for instance, in the + // case of an inline type with an int field and an inline type + // field that itself has 2 fields, an int and a long: + // T_METADATA T_INT T_METADATA T_INT T_LONG T_VOID (second + // slot for the T_LONG) T_VOID (inner inline type) T_VOID + // (outer inline type) + total_args_passed++; + int vt = 1; + do { + i++; + BasicType bt = sig_extended->at(i)._bt; + BasicType prev_bt = sig_extended->at(i-1)._bt; + if (bt == T_METADATA) { + vt++; + } else if (bt == T_VOID && + prev_bt != T_LONG && + prev_bt != T_DOUBLE) { + vt--; + } + } while (vt != 0); + } else { + total_args_passed++; + } + } + } else { + total_args_passed = sig_extended->length(); + } + return total_args_passed; +} + + +static void gen_c2i_adapter_helper(MacroAssembler* masm, + BasicType bt, + BasicType prev_bt, + size_t size_in_bytes, + const VMRegPair& reg_pair, + const Address& to, + int extraspace, + bool is_oop) { + if (bt == T_VOID) { + assert(prev_bt == T_LONG || prev_bt == T_DOUBLE, "missing half"); + return; + } + + // Say 4 args: + // i st_off + // 0 32 T_LONG + // 1 24 T_VOID + // 2 16 T_OBJECT + // 3 8 T_BOOL + // - 0 return address + // + // However to make thing extra confusing. Because we can fit a long/double in + // a single slot on a 64 bt vm and it would be silly to break them up, the interpreter + // leaves one slot empty and only stores to a single slot. In this case the + // slot that is occupied is the T_VOID slot. See I said it was confusing. + + bool wide = (size_in_bytes == wordSize); + VMReg r_1 = reg_pair.first(); + VMReg r_2 = reg_pair.second(); + assert(r_2->is_valid() == wide, "invalid size"); + if (!r_1->is_valid()) { + assert(!r_2->is_valid(), "must be invalid"); + return; + } + + if (!r_1->is_XMMRegister()) { + Register val = rax; + if (r_1->is_stack()) { + int ld_off = r_1->reg2stack() * VMRegImpl::stack_slot_size + extraspace; + __ load_sized_value(val, Address(rsp, ld_off), size_in_bytes, /* is_signed */ false); + } else { + val = r_1->as_Register(); + } + assert_different_registers(to.base(), val, rscratch1); + if (is_oop) { + __ push(r13); + __ push(rbx); + __ store_heap_oop(to, val, rscratch1, r13, rbx, IN_HEAP | ACCESS_WRITE | IS_DEST_UNINITIALIZED); + __ pop(rbx); + __ pop(r13); + } else { + __ store_sized_value(to, val, size_in_bytes); + } + } else { + if (wide) { + __ movdbl(to, r_1->as_XMMRegister()); + } else { + __ movflt(to, r_1->as_XMMRegister()); + } + } +} + static void gen_c2i_adapter(MacroAssembler *masm, - int total_args_passed, - int comp_args_on_stack, - const BasicType *sig_bt, + const GrowableArray* sig_extended, const VMRegPair *regs, - Label& skip_fixup) { + bool requires_clinit_barrier, + address& c2i_no_clinit_check_entry, + Label& skip_fixup, + address start, + OopMapSet* oop_maps, + int& frame_complete, + int& frame_size_in_words, + bool alloc_inline_receiver) { + if (requires_clinit_barrier && VM_Version::supports_fast_class_init_checks()) { + Label L_skip_barrier; + Register method = rbx; + + { // Bypass the barrier for non-static methods + Register flags = rscratch1; + __ load_unsigned_short(flags, Address(method, Method::access_flags_offset())); + __ testl(flags, JVM_ACC_STATIC); + __ jcc(Assembler::zero, L_skip_barrier); // non-static + } + + Register klass = rscratch1; + __ load_method_holder(klass, method); + __ clinit_barrier(klass, &L_skip_barrier /*L_fast_path*/); + + __ jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); // slow path + + __ bind(L_skip_barrier); + c2i_no_clinit_check_entry = __ pc(); + } + + BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); + bs->c2i_entry_barrier(masm); + // Before we get into the guts of the C2I adapter, see if we should be here // at all. We've come from compiled code and are attempting to jump to the // interpreter, which means the caller made a static call to get here @@ -691,9 +908,51 @@ static void gen_c2i_adapter(MacroAssembler *masm, __ bind(skip_fixup); + if (InlineTypePassFieldsAsArgs) { + // Is there an inline type argument? + bool has_inline_argument = false; + for (int i = 0; i < sig_extended->length() && !has_inline_argument; i++) { + has_inline_argument = (sig_extended->at(i)._bt == T_METADATA); + } + if (has_inline_argument) { + // There is at least an inline type argument: we're coming from + // compiled code so we have no buffers to back the inline types. + // Allocate the buffers here with a runtime call. + OopMap* map = RegisterSaver::save_live_registers(masm, 0, &frame_size_in_words, /*save_vectors*/ false); + + frame_complete = __ offset(); + + __ set_last_Java_frame(noreg, noreg, nullptr, rscratch1); + + __ mov(c_rarg0, r15_thread); + __ mov(c_rarg1, rbx); + __ mov64(c_rarg2, (int64_t)alloc_inline_receiver); + __ call(RuntimeAddress(CAST_FROM_FN_PTR(address, SharedRuntime::allocate_inline_types))); + + oop_maps->add_gc_map((int)(__ pc() - start), map); + __ reset_last_Java_frame(false); + + RegisterSaver::restore_live_registers(masm); + + Label no_exception; + __ cmpptr(Address(r15_thread, Thread::pending_exception_offset()), NULL_WORD); + __ jcc(Assembler::equal, no_exception); + + __ movptr(Address(r15_thread, JavaThread::vm_result_oop_offset()), NULL_WORD); + __ movptr(rax, Address(r15_thread, Thread::pending_exception_offset())); + __ jump(RuntimeAddress(StubRoutines::forward_exception_entry())); + + __ bind(no_exception); + + // We get an array of objects from the runtime call + __ get_vm_result_oop(rscratch2); // Use rscratch2 (r11) as temporary because rscratch1 (r10) is trashed by movptr() + __ get_vm_result_metadata(rbx); // TODO: required to keep the callee Method live? + } + } + // Since all args are passed on the stack, total_args_passed * // Interpreter::stackElementSize is the space we need. - + int total_args_passed = compute_total_args_passed_int(sig_extended); assert(total_args_passed >= 0, "total_args_passed is %d", total_args_passed); int extraspace = (total_args_passed * Interpreter::stackElementSize); @@ -728,96 +987,92 @@ static void gen_c2i_adapter(MacroAssembler *masm, #endif // Now write the args into the outgoing interpreter space - for (int i = 0; i < total_args_passed; i++) { - if (sig_bt[i] == T_VOID) { - assert(i > 0 && (sig_bt[i-1] == T_LONG || sig_bt[i-1] == T_DOUBLE), "missing half"); - continue; - } - // offset to start parameters - int st_off = (total_args_passed - i) * Interpreter::stackElementSize; - int next_off = st_off - Interpreter::stackElementSize; - - // Say 4 args: - // i st_off - // 0 32 T_LONG - // 1 24 T_VOID - // 2 16 T_OBJECT - // 3 8 T_BOOL - // - 0 return address - // - // However to make thing extra confusing. Because we can fit a long/double in - // a single slot on a 64 bt vm and it would be silly to break them up, the interpreter - // leaves one slot empty and only stores to a single slot. In this case the - // slot that is occupied is the T_VOID slot. See I said it was confusing. - - VMReg r_1 = regs[i].first(); - VMReg r_2 = regs[i].second(); - if (!r_1->is_valid()) { - assert(!r_2->is_valid(), ""); - continue; - } - if (r_1->is_stack()) { - // memory to memory use rax - int ld_off = r_1->reg2stack() * VMRegImpl::stack_slot_size + extraspace; - if (!r_2->is_valid()) { - // sign extend?? - __ movl(rax, Address(rsp, ld_off)); - __ movptr(Address(rsp, st_off), rax); - - } else { - - __ movq(rax, Address(rsp, ld_off)); - - // Two VMREgs|OptoRegs can be T_OBJECT, T_ADDRESS, T_DOUBLE, T_LONG - // T_DOUBLE and T_LONG use two slots in the interpreter - if ( sig_bt[i] == T_LONG || sig_bt[i] == T_DOUBLE) { - // ld_off == LSW, ld_off+wordSize == MSW - // st_off == MSW, next_off == LSW - __ movq(Address(rsp, next_off), rax); + // next_arg_comp is the next argument from the compiler point of + // view (inline type fields are passed in registers/on the stack). In + // sig_extended, an inline type argument starts with: T_METADATA, + // followed by the types of the fields of the inline type and T_VOID + // to mark the end of the inline type. ignored counts the number of + // T_METADATA/T_VOID. next_vt_arg is the next inline type argument: + // used to get the buffer for that argument from the pool of buffers + // we allocated above and want to pass to the + // interpreter. next_arg_int is the next argument from the + // interpreter point of view (inline types are passed by reference). + for (int next_arg_comp = 0, ignored = 0, next_vt_arg = 0, next_arg_int = 0; + next_arg_comp < sig_extended->length(); next_arg_comp++) { + assert(ignored <= next_arg_comp, "shouldn't skip over more slots than there are arguments"); + assert(next_arg_int <= total_args_passed, "more arguments for the interpreter than expected?"); + BasicType bt = sig_extended->at(next_arg_comp)._bt; + int st_off = (total_args_passed - next_arg_int) * Interpreter::stackElementSize; + if (!InlineTypePassFieldsAsArgs || bt != T_METADATA) { + int next_off = st_off - Interpreter::stackElementSize; + const int offset = (bt == T_LONG || bt == T_DOUBLE) ? next_off : st_off; + const VMRegPair reg_pair = regs[next_arg_comp-ignored]; + size_t size_in_bytes = reg_pair.second()->is_valid() ? 8 : 4; + gen_c2i_adapter_helper(masm, bt, next_arg_comp > 0 ? sig_extended->at(next_arg_comp-1)._bt : T_ILLEGAL, + size_in_bytes, reg_pair, Address(rsp, offset), extraspace, false); + next_arg_int++; #ifdef ASSERT - // Overwrite the unused slot with known junk - __ mov64(rax, CONST64(0xdeadffffdeadaaaa)); - __ movptr(Address(rsp, st_off), rax); -#endif /* ASSERT */ - } else { - __ movq(Address(rsp, st_off), rax); - } + if (bt == T_LONG || bt == T_DOUBLE) { + // Overwrite the unused slot with known junk + __ mov64(rax, CONST64(0xdeadffffdeadaaaa)); + __ movptr(Address(rsp, st_off), rax); } - } else if (r_1->is_Register()) { - Register r = r_1->as_Register(); - if (!r_2->is_valid()) { - // must be only an int (or less ) so move only 32bits to slot - // why not sign extend?? - __ movl(Address(rsp, st_off), r); - } else { - // Two VMREgs|OptoRegs can be T_OBJECT, T_ADDRESS, T_DOUBLE, T_LONG - // T_DOUBLE and T_LONG use two slots in the interpreter - if ( sig_bt[i] == T_LONG || sig_bt[i] == T_DOUBLE) { - // long/double in gpr -#ifdef ASSERT - // Overwrite the unused slot with known junk - __ mov64(rax, CONST64(0xdeadffffdeadaaab)); - __ movptr(Address(rsp, st_off), rax); #endif /* ASSERT */ - __ movq(Address(rsp, next_off), r); + } else { + ignored++; + // get the buffer from the just allocated pool of buffers + int index = arrayOopDesc::base_offset_in_bytes(T_OBJECT) + next_vt_arg * type2aelembytes(T_OBJECT); + __ load_heap_oop(r14, Address(rscratch2, index)); + next_vt_arg++; next_arg_int++; + int vt = 1; + // write fields we get from compiled code in registers/stack + // slots to the buffer: we know we are done with that inline type + // argument when we hit the T_VOID that acts as an end of inline + // type delimiter for this inline type. Inline types are flattened + // so we might encounter embedded inline types. Each entry in + // sig_extended contains a field offset in the buffer. + Label L_null; + do { + next_arg_comp++; + BasicType bt = sig_extended->at(next_arg_comp)._bt; + BasicType prev_bt = sig_extended->at(next_arg_comp-1)._bt; + if (bt == T_METADATA) { + vt++; + ignored++; + } else if (bt == T_VOID && + prev_bt != T_LONG && + prev_bt != T_DOUBLE) { + vt--; + ignored++; } else { - __ movptr(Address(rsp, st_off), r); + int off = sig_extended->at(next_arg_comp)._offset; + if (off == -1) { + // Nullable inline type argument, emit null check + VMReg reg = regs[next_arg_comp-ignored].first(); + Label L_notNull; + if (reg->is_stack()) { + int ld_off = reg->reg2stack() * VMRegImpl::stack_slot_size + extraspace; + __ testb(Address(rsp, ld_off), 1); + } else { + __ testb(reg->as_Register(), 1); + } + __ jcc(Assembler::notZero, L_notNull); + __ movptr(Address(rsp, st_off), 0); + __ jmp(L_null); + __ bind(L_notNull); + continue; + } + assert(off > 0, "offset in object should be positive"); + size_t size_in_bytes = is_java_primitive(bt) ? type2aelembytes(bt) : wordSize; + bool is_oop = is_reference_type(bt); + gen_c2i_adapter_helper(masm, bt, next_arg_comp > 0 ? sig_extended->at(next_arg_comp-1)._bt : T_ILLEGAL, + size_in_bytes, regs[next_arg_comp-ignored], Address(r14, off), extraspace, is_oop); } - } - } else { - assert(r_1->is_XMMRegister(), ""); - if (!r_2->is_valid()) { - // only a float use just part of the slot - __ movflt(Address(rsp, st_off), r_1->as_XMMRegister()); - } else { -#ifdef ASSERT - // Overwrite the unused slot with known junk - __ mov64(rax, CONST64(0xdeadffffdeadaaac)); - __ movptr(Address(rsp, st_off), rax); -#endif /* ASSERT */ - __ movdbl(Address(rsp, next_off), r_1->as_XMMRegister()); - } + } while (vt != 0); + // pass the buffer to the interpreter + __ movptr(Address(rsp, st_off), r14); + __ bind(L_null); } } @@ -827,9 +1082,8 @@ static void gen_c2i_adapter(MacroAssembler *masm, } void SharedRuntime::gen_i2c_adapter(MacroAssembler *masm, - int total_args_passed, int comp_args_on_stack, - const BasicType *sig_bt, + const GrowableArray* sig, const VMRegPair *regs) { // Note: r13 contains the senderSP on entry. We must preserve it since @@ -885,7 +1139,7 @@ void SharedRuntime::gen_i2c_adapter(MacroAssembler *masm, // Will jump to the compiled code just as if compiled code was doing it. // Pre-load the register-jump target early, to schedule it better. - __ movptr(r11, Address(rbx, in_bytes(Method::from_compiled_offset()))); + __ movptr(r11, Address(rbx, in_bytes(Method::from_compiled_inline_offset()))); #if INCLUDE_JVMCI if (EnableJVMCI) { @@ -899,13 +1153,17 @@ void SharedRuntime::gen_i2c_adapter(MacroAssembler *masm, } #endif // INCLUDE_JVMCI + int total_args_passed = sig->length(); + // Now generate the shuffle code. Pick up all register args and move the // rest through the floating point stack top. for (int i = 0; i < total_args_passed; i++) { - if (sig_bt[i] == T_VOID) { + BasicType bt = sig->at(i)._bt; + if (bt == T_VOID) { // Longs and doubles are passed in native word order, but misaligned // in the 32-bit build. - assert(i > 0 && (sig_bt[i-1] == T_LONG || sig_bt[i-1] == T_DOUBLE), "missing half"); + BasicType prev_bt = (i > 0) ? sig->at(i-1)._bt : T_ILLEGAL; + assert(i > 0 && (prev_bt == T_LONG || prev_bt == T_DOUBLE), "missing half"); continue; } @@ -947,7 +1205,7 @@ void SharedRuntime::gen_i2c_adapter(MacroAssembler *masm, // are accessed as negative so LSW is at LOW address // ld_off is MSW so get LSW - const int offset = (sig_bt[i]==T_LONG||sig_bt[i]==T_DOUBLE)? + const int offset = (bt==T_LONG||bt==T_DOUBLE)? next_off : ld_off; __ movq(r13, Address(saved_sp, offset)); // st_off is LSW (i.e. reg.first()) @@ -962,7 +1220,7 @@ void SharedRuntime::gen_i2c_adapter(MacroAssembler *masm, // the interpreter allocates two slots but only uses one for thr T_LONG or T_DOUBLE case // So we must adjust where to pick up the data to match the interpreter. - const int offset = (sig_bt[i]==T_LONG||sig_bt[i]==T_DOUBLE)? + const int offset = (bt==T_LONG||bt==T_DOUBLE)? next_off : ld_off; // this can be a misaligned move @@ -995,22 +1253,39 @@ void SharedRuntime::gen_i2c_adapter(MacroAssembler *masm, __ movptr(Address(r15_thread, JavaThread::callee_target_offset()), rbx); // put Method* where a c2i would expect should we end up there - // only needed because eof c2 resolve stubs return Method* as a result in + // only needed because of c2 resolve stubs return Method* as a result in // rax __ mov(rax, rbx); __ jmp(r11); } +static void gen_inline_cache_check(MacroAssembler *masm, Label& skip_fixup) { + Register data = rax; + __ ic_check(1 /* end_alignment */); + __ movptr(rbx, Address(data, CompiledICData::speculated_method_offset())); + + // Method might have been compiled since the call site was patched to + // interpreted if that is the case treat it as a miss so we can get + // the call site corrected. + __ cmpptr(Address(rbx, in_bytes(Method::code_offset())), NULL_WORD); + __ jcc(Assembler::equal, skip_fixup); + __ jump(RuntimeAddress(SharedRuntime::get_ic_miss_stub())); +} + // --------------------------------------------------------------- -void SharedRuntime::generate_i2c2i_adapters(MacroAssembler *masm, - int total_args_passed, +void SharedRuntime::generate_i2c2i_adapters(MacroAssembler* masm, int comp_args_on_stack, - const BasicType *sig_bt, - const VMRegPair *regs, - AdapterHandlerEntry* handler) { + const GrowableArray* sig, + const VMRegPair* regs, + const GrowableArray* sig_cc, + const VMRegPair* regs_cc, + const GrowableArray* sig_cc_ro, + const VMRegPair* regs_cc_ro, + AdapterHandlerEntry* handler, + AdapterBlob*& new_adapter, + bool allocate_code_blob) { address i2c_entry = __ pc(); - - gen_i2c_adapter(masm, total_args_passed, comp_args_on_stack, sig_bt, regs); + gen_i2c_adapter(masm, comp_args_on_stack, sig, regs); // ------------------------------------------------------------------------- // Generate a C2I adapter. On entry we know rbx holds the Method* during calls @@ -1021,56 +1296,62 @@ void SharedRuntime::generate_i2c2i_adapters(MacroAssembler *masm, // On exit from the interpreter, the interpreter will restore our SP (lest the // compiled code, which relies solely on SP and not RBP, get sick). - address c2i_unverified_entry = __ pc(); + address c2i_unverified_entry = __ pc(); + address c2i_unverified_inline_entry = __ pc(); Label skip_fixup; - Register data = rax; - Register receiver = j_rarg0; - Register temp = rbx; - - { - __ ic_check(1 /* end_alignment */); - __ movptr(rbx, Address(data, CompiledICData::speculated_method_offset())); - // Method might have been compiled since the call site was patched to - // interpreted if that is the case treat it as a miss so we can get - // the call site corrected. - __ cmpptr(Address(rbx, in_bytes(Method::code_offset())), NULL_WORD); - __ jcc(Assembler::equal, skip_fixup); - __ jump(RuntimeAddress(SharedRuntime::get_ic_miss_stub())); - } + gen_inline_cache_check(masm, skip_fixup); - address c2i_entry = __ pc(); + OopMapSet* oop_maps = new OopMapSet(); + int frame_complete = CodeOffsets::frame_never_safe; + int frame_size_in_words = 0; - // Class initialization barrier for static methods + // Scalarized c2i adapter with non-scalarized receiver (i.e., don't pack receiver) address c2i_no_clinit_check_entry = nullptr; - if (VM_Version::supports_fast_class_init_checks()) { - Label L_skip_barrier; - Register method = rbx; - - { // Bypass the barrier for non-static methods - Register flags = rscratch1; - __ load_unsigned_short(flags, Address(method, Method::access_flags_offset())); - __ testl(flags, JVM_ACC_STATIC); - __ jcc(Assembler::zero, L_skip_barrier); // non-static - } - - Register klass = rscratch1; - __ load_method_holder(klass, method); - __ clinit_barrier(klass, &L_skip_barrier /*L_fast_path*/); - - __ jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); // slow path - - __ bind(L_skip_barrier); - c2i_no_clinit_check_entry = __ pc(); - } - - BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); - bs->c2i_entry_barrier(masm); - - gen_c2i_adapter(masm, total_args_passed, comp_args_on_stack, sig_bt, regs, skip_fixup); - - handler->set_entry_points(i2c_entry, c2i_entry, c2i_unverified_entry, c2i_no_clinit_check_entry); - return; + address c2i_inline_ro_entry = __ pc(); + if (regs_cc != regs_cc_ro) { + // No class init barrier needed because method is guaranteed to be non-static + gen_c2i_adapter(masm, sig_cc_ro, regs_cc_ro, /* requires_clinit_barrier = */ false, c2i_no_clinit_check_entry, + skip_fixup, i2c_entry, oop_maps, frame_complete, frame_size_in_words, /* alloc_inline_receiver = */ false); + skip_fixup.reset(); + } + + // Scalarized c2i adapter + address c2i_entry = __ pc(); + address c2i_inline_entry = __ pc(); + gen_c2i_adapter(masm, sig_cc, regs_cc, /* requires_clinit_barrier = */ true, c2i_no_clinit_check_entry, + skip_fixup, i2c_entry, oop_maps, frame_complete, frame_size_in_words, /* alloc_inline_receiver = */ true); + + // Non-scalarized c2i adapter + if (regs != regs_cc) { + c2i_unverified_inline_entry = __ pc(); + Label inline_entry_skip_fixup; + gen_inline_cache_check(masm, inline_entry_skip_fixup); + + c2i_inline_entry = __ pc(); + gen_c2i_adapter(masm, sig, regs, /* requires_clinit_barrier = */ true, c2i_no_clinit_check_entry, + inline_entry_skip_fixup, i2c_entry, oop_maps, frame_complete, frame_size_in_words, /* alloc_inline_receiver = */ false); + } + + // The c2i adapters might safepoint and trigger a GC. The caller must make sure that + // the GC knows about the location of oop argument locations passed to the c2i adapter. + if (allocate_code_blob) { + bool caller_must_gc_arguments = (regs != regs_cc); + int entry_offset[AdapterHandlerEntry::ENTRIES_COUNT]; + assert(AdapterHandlerEntry::ENTRIES_COUNT == 7, "sanity"); + entry_offset[0] = 0; // i2c_entry offset + entry_offset[1] = c2i_entry - i2c_entry; + entry_offset[2] = c2i_inline_entry - i2c_entry; + entry_offset[3] = c2i_inline_ro_entry - i2c_entry; + entry_offset[4] = c2i_unverified_entry - i2c_entry; + entry_offset[5] = c2i_unverified_inline_entry - i2c_entry; + entry_offset[6] = c2i_no_clinit_check_entry - i2c_entry; + + new_adapter = AdapterBlob::create(masm->code(), entry_offset, frame_complete, frame_size_in_words, oop_maps, caller_must_gc_arguments); + } + + handler->set_entry_points(i2c_entry, c2i_entry, c2i_inline_entry, c2i_inline_ro_entry, c2i_unverified_entry, + c2i_unverified_inline_entry, c2i_no_clinit_check_entry); } int SharedRuntime::c_calling_convention(const BasicType *sig_bt, @@ -3558,6 +3839,147 @@ void SharedRuntime::montgomery_square(jint *a_ints, jint *n_ints, reverse_words(m, (julong *)m_ints, longwords); } +BufferedInlineTypeBlob* SharedRuntime::generate_buffered_inline_type_adapter(const InlineKlass* vk) { + BufferBlob* buf = BufferBlob::create("inline types pack/unpack", 16 * K); + if (buf == nullptr) { + return nullptr; + } + CodeBuffer buffer(buf); + short buffer_locs[20]; + buffer.insts()->initialize_shared_locs((relocInfo*)buffer_locs, + sizeof(buffer_locs)/sizeof(relocInfo)); + + MacroAssembler* masm = new MacroAssembler(&buffer); + + const Array* sig_vk = vk->extended_sig(); + const Array* regs = vk->return_regs(); + + int pack_fields_jobject_off = __ offset(); + // Resolve pre-allocated buffer from JNI handle. + // We cannot do this in generate_call_stub() because it requires GC code to be initialized. + __ movptr(rax, Address(r13, 0)); + __ resolve_jobject(rax /* value */, + r12 /* tmp */); + __ movptr(Address(r13, 0), rax); + + int pack_fields_off = __ offset(); + + int j = 1; + for (int i = 0; i < sig_vk->length(); i++) { + BasicType bt = sig_vk->at(i)._bt; + if (bt == T_METADATA) { + continue; + } + if (bt == T_VOID) { + if (sig_vk->at(i-1)._bt == T_LONG || + sig_vk->at(i-1)._bt == T_DOUBLE) { + j++; + } + continue; + } + int off = sig_vk->at(i)._offset; + assert(off > 0, "offset in object should be positive"); + VMRegPair pair = regs->at(j); + VMReg r_1 = pair.first(); + VMReg r_2 = pair.second(); + Address to(rax, off); + if (bt == T_FLOAT) { + __ movflt(to, r_1->as_XMMRegister()); + } else if (bt == T_DOUBLE) { + __ movdbl(to, r_1->as_XMMRegister()); + } else { + Register val = r_1->as_Register(); + assert_different_registers(to.base(), val, r14, r13, rbx, rscratch1); + if (is_reference_type(bt)) { + __ store_heap_oop(to, val, r14, r13, rbx, IN_HEAP | ACCESS_WRITE | IS_DEST_UNINITIALIZED); + } else { + __ store_sized_value(to, r_1->as_Register(), type2aelembytes(bt)); + } + } + j++; + } + assert(j == regs->length(), "missed a field?"); + if (vk->has_nullable_atomic_layout()) { + // Set the null marker + __ movb(Address(rax, vk->null_marker_offset()), 1); + } + __ ret(0); + + int unpack_fields_off = __ offset(); + + Label skip; + Label not_null; + __ testptr(rax, rax); + __ jcc(Assembler::notZero, not_null); + + // Return value is null. Zero oop registers to make the GC happy. + j = 1; + for (int i = 0; i < sig_vk->length(); i++) { + BasicType bt = sig_vk->at(i)._bt; + if (bt == T_METADATA) { + continue; + } + if (bt == T_VOID) { + if (sig_vk->at(i-1)._bt == T_LONG || + sig_vk->at(i-1)._bt == T_DOUBLE) { + j++; + } + continue; + } + if (bt == T_OBJECT || bt == T_ARRAY) { + VMRegPair pair = regs->at(j); + VMReg r_1 = pair.first(); + __ xorq(r_1->as_Register(), r_1->as_Register()); + } + j++; + } + __ jmp(skip); + __ bind(not_null); + + j = 1; + for (int i = 0; i < sig_vk->length(); i++) { + BasicType bt = sig_vk->at(i)._bt; + if (bt == T_METADATA) { + continue; + } + if (bt == T_VOID) { + if (sig_vk->at(i-1)._bt == T_LONG || + sig_vk->at(i-1)._bt == T_DOUBLE) { + j++; + } + continue; + } + int off = sig_vk->at(i)._offset; + assert(off > 0, "offset in object should be positive"); + VMRegPair pair = regs->at(j); + VMReg r_1 = pair.first(); + VMReg r_2 = pair.second(); + Address from(rax, off); + if (bt == T_FLOAT) { + __ movflt(r_1->as_XMMRegister(), from); + } else if (bt == T_DOUBLE) { + __ movdbl(r_1->as_XMMRegister(), from); + } else if (bt == T_OBJECT || bt == T_ARRAY) { + assert_different_registers(rax, r_1->as_Register()); + __ load_heap_oop(r_1->as_Register(), from); + } else { + assert(is_java_primitive(bt), "unexpected basic type"); + assert_different_registers(rax, r_1->as_Register()); + size_t size_in_bytes = type2aelembytes(bt); + __ load_sized_value(r_1->as_Register(), from, size_in_bytes, bt != T_CHAR && bt != T_BOOLEAN); + } + j++; + } + assert(j == regs->length(), "missed a field?"); + + __ bind(skip); + __ ret(0); + + __ flush(); + + return BufferedInlineTypeBlob::create(&buffer, pack_fields_off, pack_fields_jobject_off, unpack_fields_off); +} + #if INCLUDE_JFR // For c2: c_rarg0 is junk, call to runtime to write a checkpoint. @@ -3650,4 +4072,3 @@ RuntimeStub* SharedRuntime::generate_jfr_return_lease() { } #endif // INCLUDE_JFR - diff --git a/src/hotspot/cpu/x86/stackChunkFrameStream_x86.inline.hpp b/src/hotspot/cpu/x86/stackChunkFrameStream_x86.inline.hpp index 6289b903ab1..32fcd5d4037 100644 --- a/src/hotspot/cpu/x86/stackChunkFrameStream_x86.inline.hpp +++ b/src/hotspot/cpu/x86/stackChunkFrameStream_x86.inline.hpp @@ -34,8 +34,30 @@ template inline bool StackChunkFrameStream::is_in_frame(void* p0) const { assert(!is_done(), ""); intptr_t* p = (intptr_t*)p0; - int argsize = is_compiled() ? (_cb->as_nmethod()->num_stack_arg_slots() * VMRegImpl::stack_slot_size) >> LogBytesPerWord : 0; - int frame_size = _cb->frame_size() + argsize; + int frame_size = _cb->frame_size(); + if (is_compiled()) { + nmethod* nm = _cb->as_nmethod_or_null(); + if (nm->needs_stack_repair() && nm->is_compiled_by_c2()) { + frame f = to_frame(); + bool augmented = f.was_augmented_on_entry(frame_size); + if (!augmented) { + // Fix: C2 caller, so frame was not extended and thus the + // size read from the frame does not include the arguments. + // Ideally we have to count the arg size for the scalarized + // convention. For now we include the size of the caller frame + // which would at least be equal to that. + RegisterMap map(nullptr, + RegisterMap::UpdateMap::skip, + RegisterMap::ProcessFrames::skip, + RegisterMap::WalkContinuation::skip); + frame caller = to_frame().sender(&map); + assert(caller.is_compiled_frame() && caller.cb()->as_nmethod()->is_compiled_by_c2(), "needs stack repair but was not extended with c1/interpreter caller"); + frame_size += (caller.real_fp() - caller.sp()); + } + } else { + frame_size += _cb->as_nmethod()->num_stack_arg_slots() * VMRegImpl::stack_slot_size >> LogBytesPerWord; + } + } return p == sp() - frame::sender_sp_offset || ((p - unextended_sp()) >= 0 && (p - unextended_sp()) < frame_size); } #endif @@ -52,7 +74,7 @@ inline frame StackChunkFrameStream::to_frame() const { template inline address StackChunkFrameStream::get_pc() const { assert(!is_done(), ""); - return *(address*)(_sp - 1); + return *(address*)((_callee_augmented ? _unextended_sp : _sp) - 1); } template diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp index 6eb641daaf9..a5fc209d983 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp @@ -22,6 +22,7 @@ * */ +#include "asm/assembler.hpp" #include "asm/macroAssembler.hpp" #include "classfile/javaClasses.hpp" #include "classfile/vmIntrinsics.hpp" @@ -38,6 +39,8 @@ #include "runtime/javaThread.hpp" #include "runtime/sharedRuntime.hpp" #include "runtime/stubRoutines.hpp" +#include "utilities/macros.hpp" +#include "vmreg_x86.inline.hpp" #include "stubGenerator_x86_64.hpp" #ifdef COMPILER2 #include "opto/runtime.hpp" @@ -301,22 +304,22 @@ address StubGenerator::generate_call_stub(address& return_address) { // store result depending on type (everything that is not // T_OBJECT, T_LONG, T_FLOAT or T_DOUBLE is treated as T_INT) - __ movptr(c_rarg0, result); - Label is_long, is_float, is_double, exit; - __ movl(c_rarg1, result_type); - __ cmpl(c_rarg1, T_OBJECT); + __ movptr(r13, result); + Label is_long, is_float, is_double, check_prim, exit; + __ movl(rbx, result_type); + __ cmpl(rbx, T_OBJECT); + __ jcc(Assembler::equal, check_prim); + __ cmpl(rbx, T_LONG); __ jcc(Assembler::equal, is_long); - __ cmpl(c_rarg1, T_LONG); - __ jcc(Assembler::equal, is_long); - __ cmpl(c_rarg1, T_FLOAT); + __ cmpl(rbx, T_FLOAT); __ jcc(Assembler::equal, is_float); - __ cmpl(c_rarg1, T_DOUBLE); + __ cmpl(rbx, T_DOUBLE); __ jcc(Assembler::equal, is_double); #ifdef ASSERT // make sure the type is INT { Label L; - __ cmpl(c_rarg1, T_INT); + __ cmpl(rbx, T_INT); __ jcc(Assembler::equal, L); __ stop("StubRoutines::call_stub: unexpected result type"); __ bind(L); @@ -324,7 +327,7 @@ address StubGenerator::generate_call_stub(address& return_address) { #endif // handle T_INT case - __ movl(Address(c_rarg0, 0), rax); + __ movl(Address(r13, 0), rax); __ BIND(exit); @@ -382,16 +385,29 @@ address StubGenerator::generate_call_stub(address& return_address) { __ ret(0); // handle return types different from T_INT + __ BIND(check_prim); + if (InlineTypeReturnedAsFields) { + // Check for scalarized return value + __ testptr(rax, 1); + __ jcc(Assembler::zero, is_long); + // Load pack handler address + __ andptr(rax, -2); + __ movptr(rax, Address(rax, InstanceKlass::adr_inlineklass_fixed_block_offset())); + __ movptr(rbx, Address(rax, InlineKlass::pack_handler_jobject_offset())); + // Call pack handler to initialize the buffer + __ call(rbx); + __ jmp(exit); + } __ BIND(is_long); - __ movq(Address(c_rarg0, 0), rax); + __ movq(Address(r13, 0), rax); __ jmp(exit); __ BIND(is_float); - __ movflt(Address(c_rarg0, 0), xmm0); + __ movflt(Address(r13, 0), xmm0); __ jmp(exit); __ BIND(is_double); - __ movdbl(Address(c_rarg0, 0), xmm0); + __ movdbl(Address(r13, 0), xmm0); __ jmp(exit); return start; @@ -3765,6 +3781,67 @@ address StubGenerator::generate_floatToFloat16() { return start; } +static void save_return_registers(MacroAssembler* masm) { + masm->push_ppx(rax); + if (InlineTypeReturnedAsFields) { + masm->push(rdi); + masm->push(rsi); + masm->push(rdx); + masm->push(rcx); + masm->push(r8); + masm->push(r9); + } + masm->push_d(xmm0); + if (InlineTypeReturnedAsFields) { + masm->push_d(xmm1); + masm->push_d(xmm2); + masm->push_d(xmm3); + masm->push_d(xmm4); + masm->push_d(xmm5); + masm->push_d(xmm6); + masm->push_d(xmm7); + } +#ifdef ASSERT + masm->movq(rax, 0xBADC0FFE); + masm->movq(rdi, rax); + masm->movq(rsi, rax); + masm->movq(rdx, rax); + masm->movq(rcx, rax); + masm->movq(r8, rax); + masm->movq(r9, rax); + masm->movq(xmm0, rax); + masm->movq(xmm1, rax); + masm->movq(xmm2, rax); + masm->movq(xmm3, rax); + masm->movq(xmm4, rax); + masm->movq(xmm5, rax); + masm->movq(xmm6, rax); + masm->movq(xmm7, rax); +#endif +} + +static void restore_return_registers(MacroAssembler* masm) { + if (InlineTypeReturnedAsFields) { + masm->pop_d(xmm7); + masm->pop_d(xmm6); + masm->pop_d(xmm5); + masm->pop_d(xmm4); + masm->pop_d(xmm3); + masm->pop_d(xmm2); + masm->pop_d(xmm1); + } + masm->pop_d(xmm0); + if (InlineTypeReturnedAsFields) { + masm->pop(r9); + masm->pop(r8); + masm->pop(rcx); + masm->pop(rdx); + masm->pop(rsi); + masm->pop(rdi); + } + masm->pop_ppx(rax); +} + address StubGenerator::generate_cont_thaw(StubId stub_id) { if (!Continuations::enabled()) return nullptr; @@ -3816,8 +3893,7 @@ address StubGenerator::generate_cont_thaw(StubId stub_id) { if (return_barrier) { // Preserve possible return value from a method returning to the return barrier. - __ push_ppx(rax); - __ push_d(xmm0); + save_return_registers(_masm); } __ movptr(c_rarg0, r15_thread); @@ -3828,8 +3904,7 @@ address StubGenerator::generate_cont_thaw(StubId stub_id) { if (return_barrier) { // Restore return value from a method returning to the return barrier. // No safepoint in the call to thaw, so even an oop return value should be OK. - __ pop_d(xmm0); - __ pop_ppx(rax); + restore_return_registers(_masm); } #ifdef ASSERT @@ -3855,8 +3930,7 @@ address StubGenerator::generate_cont_thaw(StubId stub_id) { if (return_barrier) { // Preserve possible return value from a method returning to the return barrier. (Again.) - __ push_ppx(rax); - __ push_d(xmm0); + save_return_registers(_masm); } // If we want, we can templatize thaw by kind, and have three different entries. @@ -3868,8 +3942,7 @@ address StubGenerator::generate_cont_thaw(StubId stub_id) { if (return_barrier) { // Restore return value from a method returning to the return barrier. (Again.) // No safepoint in the call to thaw, so even an oop return value should be OK. - __ pop_d(xmm0); - __ pop_ppx(rax); + restore_return_registers(_masm); } else { // Return 0 (success) from doYield. __ xorptr(rax, rax); @@ -4076,6 +4149,16 @@ void StubGenerator::generate_initial_stubs() { StubRoutines::_forward_exception_entry = generate_forward_exception(); + // Generate these first because they are called from other stubs + if (InlineTypeReturnedAsFields) { + StubRoutines::_load_inline_type_fields_in_regs = + generate_return_value_stub(CAST_FROM_FN_PTR(address, SharedRuntime::load_inline_type_fields_in_regs), + "load_inline_type_fields_in_regs", false); + StubRoutines::_store_inline_type_fields_to_buf = + generate_return_value_stub(CAST_FROM_FN_PTR(address, SharedRuntime::store_inline_type_fields_to_buf), + "store_inline_type_fields_to_buf", true); + } + StubRoutines::_call_stub_entry = generate_call_stub(StubRoutines::_call_stub_return_address); @@ -4121,6 +4204,150 @@ void StubGenerator::generate_initial_stubs() { StubRoutines::_fmod = generate_libmFmod(); // from stubGenerator_x86_64_fmod.cpp } +// Call here from the interpreter or compiled code to either load +// multiple returned values from the inline type instance being +// returned to registers or to store returned values to a newly +// allocated inline type instance. +// Register is a class, but it would be assigned numerical value. +// "0" is assigned for xmm0. Thus we need to ignore -Wnonnull. +PRAGMA_DIAG_PUSH +PRAGMA_NONNULL_IGNORED +address StubGenerator::generate_return_value_stub(address destination, const char* name, bool has_res) { + // We need to save all registers the calling convention may use so + // the runtime calls read or update those registers. This needs to + // be in sync with SharedRuntime::java_return_convention(). + enum layout { + pad_off = frame::arg_reg_save_area_bytes/BytesPerInt, pad_off_2, + rax_off, rax_off_2, + j_rarg5_off, j_rarg5_2, + j_rarg4_off, j_rarg4_2, + j_rarg3_off, j_rarg3_2, + j_rarg2_off, j_rarg2_2, + j_rarg1_off, j_rarg1_2, + j_rarg0_off, j_rarg0_2, + j_farg0_off, j_farg0_2, + j_farg1_off, j_farg1_2, + j_farg2_off, j_farg2_2, + j_farg3_off, j_farg3_2, + j_farg4_off, j_farg4_2, + j_farg5_off, j_farg5_2, + j_farg6_off, j_farg6_2, + j_farg7_off, j_farg7_2, + rbp_off, rbp_off_2, + return_off, return_off_2, + + framesize + }; + + CodeBuffer buffer(name, 1000, 512); + MacroAssembler* _masm = new MacroAssembler(&buffer); + + int frame_size_in_bytes = align_up(framesize*BytesPerInt, 16); + assert(frame_size_in_bytes == framesize*BytesPerInt, "misaligned"); + int frame_size_in_slots = frame_size_in_bytes / BytesPerInt; + int frame_size_in_words = frame_size_in_bytes / wordSize; + + OopMapSet *oop_maps = new OopMapSet(); + OopMap* map = new OopMap(frame_size_in_slots, 0); + + map->set_callee_saved(VMRegImpl::stack2reg(rax_off), rax->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_rarg5_off), j_rarg5->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_rarg4_off), j_rarg4->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_rarg3_off), j_rarg3->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_rarg2_off), j_rarg2->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_rarg1_off), j_rarg1->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_rarg0_off), j_rarg0->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_farg0_off), j_farg0->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_farg1_off), j_farg1->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_farg2_off), j_farg2->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_farg3_off), j_farg3->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_farg4_off), j_farg4->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_farg5_off), j_farg5->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_farg6_off), j_farg6->as_VMReg()); + map->set_callee_saved(VMRegImpl::stack2reg(j_farg7_off), j_farg7->as_VMReg()); + + int start = __ offset(); + + __ subptr(rsp, frame_size_in_bytes - 8 /* return address*/); + + __ movptr(Address(rsp, rbp_off * BytesPerInt), rbp); + __ movdbl(Address(rsp, j_farg7_off * BytesPerInt), j_farg7); + __ movdbl(Address(rsp, j_farg6_off * BytesPerInt), j_farg6); + __ movdbl(Address(rsp, j_farg5_off * BytesPerInt), j_farg5); + __ movdbl(Address(rsp, j_farg4_off * BytesPerInt), j_farg4); + __ movdbl(Address(rsp, j_farg3_off * BytesPerInt), j_farg3); + __ movdbl(Address(rsp, j_farg2_off * BytesPerInt), j_farg2); + __ movdbl(Address(rsp, j_farg1_off * BytesPerInt), j_farg1); + __ movdbl(Address(rsp, j_farg0_off * BytesPerInt), j_farg0); + + __ movptr(Address(rsp, j_rarg0_off * BytesPerInt), j_rarg0); + __ movptr(Address(rsp, j_rarg1_off * BytesPerInt), j_rarg1); + __ movptr(Address(rsp, j_rarg2_off * BytesPerInt), j_rarg2); + __ movptr(Address(rsp, j_rarg3_off * BytesPerInt), j_rarg3); + __ movptr(Address(rsp, j_rarg4_off * BytesPerInt), j_rarg4); + __ movptr(Address(rsp, j_rarg5_off * BytesPerInt), j_rarg5); + __ movptr(Address(rsp, rax_off * BytesPerInt), rax); + + int frame_complete = __ offset(); + + __ set_last_Java_frame(noreg, noreg, nullptr, rscratch1); + + __ mov(c_rarg0, r15_thread); + __ mov(c_rarg1, rax); + + __ call(RuntimeAddress(destination)); + + // Set an oopmap for the call site. + + oop_maps->add_gc_map( __ offset() - start, map); + + // clear last_Java_sp + __ reset_last_Java_frame(false); + + __ movptr(rbp, Address(rsp, rbp_off * BytesPerInt)); + __ movdbl(j_farg7, Address(rsp, j_farg7_off * BytesPerInt)); + __ movdbl(j_farg6, Address(rsp, j_farg6_off * BytesPerInt)); + __ movdbl(j_farg5, Address(rsp, j_farg5_off * BytesPerInt)); + __ movdbl(j_farg4, Address(rsp, j_farg4_off * BytesPerInt)); + __ movdbl(j_farg3, Address(rsp, j_farg3_off * BytesPerInt)); + __ movdbl(j_farg2, Address(rsp, j_farg2_off * BytesPerInt)); + __ movdbl(j_farg1, Address(rsp, j_farg1_off * BytesPerInt)); + __ movdbl(j_farg0, Address(rsp, j_farg0_off * BytesPerInt)); + + __ movptr(j_rarg0, Address(rsp, j_rarg0_off * BytesPerInt)); + __ movptr(j_rarg1, Address(rsp, j_rarg1_off * BytesPerInt)); + __ movptr(j_rarg2, Address(rsp, j_rarg2_off * BytesPerInt)); + __ movptr(j_rarg3, Address(rsp, j_rarg3_off * BytesPerInt)); + __ movptr(j_rarg4, Address(rsp, j_rarg4_off * BytesPerInt)); + __ movptr(j_rarg5, Address(rsp, j_rarg5_off * BytesPerInt)); + __ movptr(rax, Address(rsp, rax_off * BytesPerInt)); + + __ addptr(rsp, frame_size_in_bytes-8); + + // check for pending exceptions + Label pending; + __ cmpptr(Address(r15_thread, Thread::pending_exception_offset()), (int32_t)NULL_WORD); + __ jcc(Assembler::notEqual, pending); + + if (has_res) { + __ get_vm_result_oop(rax); + } + + __ ret(0); + + __ bind(pending); + + __ movptr(rax, Address(r15_thread, Thread::pending_exception_offset())); + __ jump(RuntimeAddress(StubRoutines::forward_exception_entry())); + + // ------------- + // make sure all code is generated + _masm->flush(); + + RuntimeStub* stub = RuntimeStub::new_runtime_stub(name, &buffer, frame_complete, frame_size_in_words, oop_maps, false); + return stub->entry_point(); +} + void StubGenerator::generate_continuation_stubs() { // Continuation stubs: StubRoutines::_cont_thaw = generate_cont_thaw(); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64.hpp b/src/hotspot/cpu/x86/stubGenerator_x86_64.hpp index 44d13bbbf31..13bd659eff9 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64.hpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64.hpp @@ -626,6 +626,9 @@ class StubGenerator: public StubCodeGenerator { address generate_upcall_stub_exception_handler(); address generate_upcall_stub_load_target(); + // interpreter or compiled code marshalling registers to/from inline type instance + address generate_return_value_stub(address destination, const char* name, bool has_res); + // Specialized stub implementations for UseSecondarySupersTable. void generate_lookup_secondary_supers_table_stub(); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp index 743457f87af..15b00b5366b 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp @@ -3036,9 +3036,19 @@ address StubGenerator::generate_generic_copy(address byte_copy_entry, address sh __ cmpq(r10_src_klass, rax); __ jcc(Assembler::notEqual, L_failed); + // Check for flat inline type array -> return -1 + __ test_flat_array_oop(src, rax, L_failed); + + // Check for null-free (non-flat) inline type array -> handle as object array + __ test_null_free_array_oop(src, rax, L_objArray); + const Register rax_lh = rax; // layout helper __ movl(rax_lh, Address(r10_src_klass, lh_offset)); + // Check for flat inline type array -> return -1 + __ testl(rax_lh, Klass::_lh_array_tag_flat_value_bit_inplace); + __ jcc(Assembler::notZero, L_failed); + // if (!src->is_Array()) return -1; __ cmpl(rax_lh, Klass::_lh_neutral_value); __ jcc(Assembler::greaterEqual, L_failed); @@ -3048,8 +3058,10 @@ address StubGenerator::generate_generic_copy(address byte_copy_entry, address sh { BLOCK_COMMENT("assert primitive array {"); Label L; - __ cmpl(rax_lh, (Klass::_lh_array_tag_type_value << Klass::_lh_array_tag_shift)); - __ jcc(Assembler::greaterEqual, L); + __ movl(rklass_tmp, rax_lh); + __ sarl(rklass_tmp, Klass::_lh_array_tag_shift); + __ cmpl(rklass_tmp, Klass::_lh_array_tag_type_value); + __ jcc(Assembler::equal, L); __ stop("must be a primitive array"); __ bind(L); BLOCK_COMMENT("} assert primitive array done"); @@ -3157,9 +3169,21 @@ __ BIND(L_checkcast_copy); // live at this point: r10_src_klass, r11_length, rax (dst_klass) { // Before looking at dst.length, make sure dst is also an objArray. + // This check also fails for flat arrays which are not supported. __ cmpl(Address(rax, lh_offset), objArray_lh); __ jcc(Assembler::notEqual, L_failed); +#ifdef ASSERT + { + BLOCK_COMMENT("assert not null-free array {"); + Label L; + __ test_non_null_free_array_oop(dst, rklass_tmp, L); + __ stop("unexpected null-free array"); + __ bind(L); + BLOCK_COMMENT("} assert not null-free array"); + } +#endif + // It is safe to examine both src.length and dst.length. arraycopy_range_checks(src, src_pos, dst, dst_pos, r11_length, rax, L_failed); diff --git a/src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp b/src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp index 47ef0aef2bb..ffd2ab576bc 100644 --- a/src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp +++ b/src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp @@ -38,6 +38,7 @@ #include "oops/methodData.hpp" #include "oops/method.hpp" #include "oops/oop.inline.hpp" +#include "oops/inlineKlass.hpp" #include "oops/resolvedIndyEntry.hpp" #include "oops/resolvedMethodEntry.hpp" #include "prims/jvmtiExport.hpp" @@ -63,7 +64,7 @@ // if too small. // Run with +PrintInterpreter to get the VM to print out the size. // Max size with JVMTI -int TemplateInterpreter::InterpreterCodeSize = JVMCI_ONLY(268) NOT_JVMCI(256) * 1024; +int TemplateInterpreter::InterpreterCodeSize = JVMCI_ONLY(280) NOT_JVMCI(268) * 1024; // Global Register Names static const Register rbcp = r13; @@ -176,11 +177,15 @@ address TemplateInterpreterGenerator::generate_return_entry_for(TosState state, address entry = __ pc(); // Restore stack bottom in case i2c adjusted stack - __ movptr(rcx, Address(rbp, frame::interpreter_frame_last_sp_offset * wordSize)); - __ lea(rsp, Address(rbp, rcx, Address::times_ptr)); + __ movptr(rscratch1, Address(rbp, frame::interpreter_frame_last_sp_offset * wordSize)); + __ lea(rsp, Address(rbp, rscratch1, Address::times_ptr)); // and null it as marker that esp is now tos until next java call __ movptr(Address(rbp, frame::interpreter_frame_last_sp_offset * wordSize), NULL_WORD); + if (state == atos && InlineTypeReturnedAsFields) { + __ store_inline_type_fields_to_buf(nullptr); + } + __ restore_bcp(); __ restore_locals(); @@ -1229,7 +1234,7 @@ address TemplateInterpreterGenerator::generate_abstract_entry(void) { // // Generic interpreted method entry to (asm) interpreter // -address TemplateInterpreterGenerator::generate_normal_entry(bool synchronized) { +address TemplateInterpreterGenerator::generate_normal_entry(bool synchronized, bool object_init) { // determine code generation flags bool inc_counter = UseCompiler || CountCompiledCalls; @@ -1350,6 +1355,12 @@ address TemplateInterpreterGenerator::generate_normal_entry(bool synchronized) { #endif } + // If object_init == true, we should insert a StoreStore barrier here to + // prevent strict fields initial default values from being observable. + // However, x86 is a TSO platform, so if `this` escapes, strict fields + // initialized values are guaranteed to be the ones observed, so the + // barrier can be elided. + // start execution #ifdef ASSERT { diff --git a/src/hotspot/cpu/x86/templateTable_x86.cpp b/src/hotspot/cpu/x86/templateTable_x86.cpp index 82ca18d8a1f..bb7dc038d30 100644 --- a/src/hotspot/cpu/x86/templateTable_x86.cpp +++ b/src/hotspot/cpu/x86/templateTable_x86.cpp @@ -36,6 +36,7 @@ #include "oops/methodData.hpp" #include "oops/objArrayKlass.hpp" #include "oops/oop.inline.hpp" +#include "oops/inlineKlass.hpp" #include "oops/resolvedFieldEntry.hpp" #include "oops/resolvedIndyEntry.hpp" #include "oops/resolvedMethodEntry.hpp" @@ -167,6 +168,7 @@ void TemplateTable::patch_bytecode(Bytecodes::Code bc, Register bc_reg, Label L_patch_done; switch (bc) { + case Bytecodes::_fast_vputfield: case Bytecodes::_fast_aputfield: case Bytecodes::_fast_bputfield: case Bytecodes::_fast_zputfield: @@ -775,15 +777,34 @@ void TemplateTable::daload() { void TemplateTable::aaload() { transition(itos, atos); - // rax: index - // rdx: array - index_check(rdx, rax); // kills rbx - do_oop_load(_masm, - Address(rdx, rax, - UseCompressedOops ? Address::times_4 : Address::times_ptr, - arrayOopDesc::base_offset_in_bytes(T_OBJECT)), - rax, - IS_ARRAY); + Register array = rdx; + Register index = rax; + + index_check(array, index); // kills rbx + __ profile_array_type(rbx, array, rcx); + if (UseArrayFlattening) { + Label is_flat_array, done; + __ test_flat_array_oop(array, rbx, is_flat_array); + do_oop_load(_masm, + Address(array, index, + UseCompressedOops ? Address::times_4 : Address::times_ptr, + arrayOopDesc::base_offset_in_bytes(T_OBJECT)), + rax, + IS_ARRAY); + __ jmp(done); + __ bind(is_flat_array); + __ movptr(rcx, array); + call_VM(rax, CAST_FROM_FN_PTR(address, InterpreterRuntime::flat_array_load), rcx, index); + __ bind(done); + } else { + do_oop_load(_masm, + Address(array, index, + UseCompressedOops ? Address::times_4 : Address::times_ptr, + arrayOopDesc::base_offset_in_bytes(T_OBJECT)), + rax, + IS_ARRAY); + } + __ profile_element_type(rbx, rax, rcx); } void TemplateTable::baload() { @@ -1057,7 +1078,7 @@ void TemplateTable::dastore() { } void TemplateTable::aastore() { - Label is_null, ok_is_subtype, done; + Label is_null, is_flat_array, ok_is_subtype, done; transition(vtos, vtos); // stack: ..., array, index, value __ movptr(rax, at_tos()); // value @@ -1069,19 +1090,30 @@ void TemplateTable::aastore() { arrayOopDesc::base_offset_in_bytes(T_OBJECT)); index_check_without_pop(rdx, rcx); // kills rbx + + __ profile_array_type(rdi, rdx, rbx); + __ profile_multiple_element_types(rdi, rax, rbx, rcx); + __ testptr(rax, rax); __ jcc(Assembler::zero, is_null); + // Move array class to rdi + __ load_klass(rdi, rdx, rscratch1); + if (UseArrayFlattening) { + __ movl(rbx, Address(rdi, Klass::layout_helper_offset())); + __ test_flat_array_layout(rbx, is_flat_array); + } + // Move subklass into rbx __ load_klass(rbx, rax, rscratch1); - // Move superklass into rax - __ load_klass(rax, rdx, rscratch1); - __ movptr(rax, Address(rax, + // Move array element superklass into rax + __ movptr(rax, Address(rdi, ObjArrayKlass::element_klass_offset())); // Generate subtype check. Blows rcx, rdi // Superklass in rax. Subklass in rbx. - __ gen_subtype_check(rbx, ok_is_subtype); + // is "rbx <: rax" ? (value subclass <: array element superclass) + __ gen_subtype_check(rbx, ok_is_subtype, false); // Come here on failure // object is at TOS @@ -1099,11 +1131,39 @@ void TemplateTable::aastore() { // Have a null in rax, rdx=array, ecx=index. Store null at ary[idx] __ bind(is_null); - __ profile_null_seen(rbx); + if (EnableValhalla) { + Label write_null_to_null_free_array, store_null; + + // Move array class to rdi + __ load_klass(rdi, rdx, rscratch1); + if (UseArrayFlattening) { + __ movl(rbx, Address(rdi, Klass::layout_helper_offset())); + __ test_flat_array_layout(rbx, is_flat_array); + } + + // No way to store null in null-free array + __ test_null_free_array_oop(rdx, rbx, write_null_to_null_free_array); + __ jmp(store_null); + __ bind(write_null_to_null_free_array); + __ jump(RuntimeAddress(Interpreter::_throw_NullPointerException_entry)); + + __ bind(store_null); + } // Store a null do_oop_store(_masm, element_address, noreg, IS_ARRAY); + __ jmp(done); + + if (UseArrayFlattening) { + Label is_type_ok; + __ bind(is_flat_array); // Store non-null value to flat + __ movptr(rax, at_tos()); + __ movl(rcx, at_tos_p1()); // index + __ movptr(rdx, at_tos_p2()); // array + + call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::flat_array_store), rax, rdx, rcx); + } // Pop stack arguments __ bind(done); __ addptr(rsp, 3 * Interpreter::stackElementSize); @@ -1891,13 +1951,59 @@ void TemplateTable::if_nullcmp(Condition cc) { void TemplateTable::if_acmp(Condition cc) { transition(atos, vtos); // assume branch is more often taken than not (loops use backward branches) - Label not_taken; + Label taken, not_taken; __ pop_ptr(rdx); + + __ profile_acmp(rbx, rdx, rax, rcx); + + const int is_inline_type_mask = markWord::inline_type_pattern; + if (EnableValhalla) { + __ cmpoop(rdx, rax); + __ jcc(Assembler::equal, (cc == equal) ? taken : not_taken); + + // might be substitutable, test if either rax or rdx is null + __ testptr(rax, rax); + __ jcc(Assembler::zero, (cc == equal) ? not_taken : taken); + __ testptr(rdx, rdx); + __ jcc(Assembler::zero, (cc == equal) ? not_taken : taken); + + // and both are values ? + __ movptr(rbx, Address(rdx, oopDesc::mark_offset_in_bytes())); + __ andptr(rbx, Address(rax, oopDesc::mark_offset_in_bytes())); + __ andptr(rbx, is_inline_type_mask); + __ cmpptr(rbx, is_inline_type_mask); + __ jcc(Assembler::notEqual, (cc == equal) ? not_taken : taken); + + // same value klass ? + __ load_metadata(rbx, rdx); + __ load_metadata(rcx, rax); + __ cmpptr(rbx, rcx); + __ jcc(Assembler::notEqual, (cc == equal) ? not_taken : taken); + + // Know both are the same type, let's test for substitutability... + if (cc == equal) { + invoke_is_substitutable(rax, rdx, taken, not_taken); + } else { + invoke_is_substitutable(rax, rdx, not_taken, taken); + } + __ stop("Not reachable"); + } + __ cmpoop(rdx, rax); __ jcc(j_not(cc), not_taken); + __ bind(taken); branch(false, false); __ bind(not_taken); - __ profile_not_taken_branch(rax); + __ profile_not_taken_branch(rax, true); +} + +void TemplateTable::invoke_is_substitutable(Register aobj, Register bobj, + Label& is_subst, Label& not_subst) { + __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::is_substitutable), aobj, bobj); + // Restored...rax answer, jmp to outcome... + __ testl(rax, rax); + __ jcc(Assembler::zero, not_subst); + __ jmp(is_subst); } void TemplateTable::ret() { @@ -2151,7 +2257,8 @@ void TemplateTable::_return(TosState state) { if (state == itos) { __ narrow(rax); } - __ remove_activation(state, rbcp); + + __ remove_activation(state, rbcp, true, true, true); __ jmp(rbcp); } @@ -2511,7 +2618,7 @@ void TemplateTable::pop_and_check_object(Register r) { void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteControl rc) { transition(vtos, vtos); - const Register obj = c_rarg3; + const Register obj = r9; const Register cache = rcx; const Register index = rdx; const Register off = rbx; @@ -2523,11 +2630,9 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr jvmti_post_field_access(cache, index, is_static, false); load_resolved_field_entry(obj, cache, tos_state, off, flags, is_static); - if (!is_static) pop_and_check_object(obj); - const Address field(obj, off, Address::times_1, 0*wordSize); - Label Done, notByte, notBool, notInt, notShort, notChar, notLong, notFloat, notObj; + Label Done, notByte, notBool, notInt, notShort, notChar, notLong, notFloat, notObj, notInlineType; // Make sure we don't need to mask edx after the above shift assert(btos == 0, "change code, btos != 0"); @@ -2535,6 +2640,7 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr __ jcc(Assembler::notZero, notByte); // btos + if (!is_static) pop_and_check_object(obj); __ access_load_at(T_BYTE, IN_HEAP, rax, field, noreg); __ push(btos); // Rewrite bytecode to be faster @@ -2546,7 +2652,7 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr __ bind(notByte); __ cmpl(tos_state, ztos); __ jcc(Assembler::notEqual, notBool); - + if (!is_static) pop_and_check_object(obj); // ztos (same code as btos) __ access_load_at(T_BOOLEAN, IN_HEAP, rax, field, noreg); __ push(ztos); @@ -2561,14 +2667,47 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr __ cmpl(tos_state, atos); __ jcc(Assembler::notEqual, notObj); // atos - do_oop_load(_masm, field, rax); - __ push(atos); - if (!is_static && rc == may_rewrite) { - patch_bytecode(Bytecodes::_fast_agetfield, bc, rbx); + if (!EnableValhalla) { + if (!is_static) pop_and_check_object(obj); + do_oop_load(_masm, field, rax); + __ push(atos); + if (!is_static && rc == may_rewrite) { + patch_bytecode(Bytecodes::_fast_agetfield, bc, rbx); + } + __ jmp(Done); + } else { + if (is_static) { + __ load_heap_oop(rax, field); + __ push(atos); + __ jmp(Done); + } else { + Label is_flat, rewrite_inline; + __ test_field_is_flat(flags, rscratch1, is_flat); + pop_and_check_object(obj); + __ load_heap_oop(rax, field); + __ push(atos); + if (rc == may_rewrite) { + patch_bytecode(Bytecodes::_fast_agetfield, bc, rbx); + } + __ jmp(Done); + __ bind(is_flat); + // field is flat (null-free or nullable with a null-marker) + pop_and_check_object(rax); + __ read_flat_field(rcx, rdx, rbx, rax); + __ verify_oop(rax); + __ push(atos); + __ bind(rewrite_inline); + if (rc == may_rewrite) { + patch_bytecode(Bytecodes::_fast_vgetfield, bc, rbx); + } + __ jmp(Done); + } } - __ jmp(Done); __ bind(notObj); + + if (!is_static) pop_and_check_object(obj); + __ cmpl(tos_state, itos); __ jcc(Assembler::notEqual, notInt); // itos @@ -2668,7 +2807,6 @@ void TemplateTable::getstatic(int byte_no) { getfield_or_static(byte_no, true); } - // The registers cache and index expected to be set before call. // The function may destroy various registers, just not the cache and index registers. void TemplateTable::jvmti_post_field_mod(Register cache, Register index, bool is_static) { @@ -2730,7 +2868,7 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr const Register index = rdx; const Register tos_state = rdx; const Register off = rbx; - const Register flags = rax; + const Register flags = r9; resolve_cache_and_index_for_field(byte_no, cache, index); jvmti_post_field_mod(cache, index, is_static); @@ -2743,29 +2881,30 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr Label notVolatile, Done; // Check for volatile store - __ andl(flags, (1 << ResolvedFieldEntry::is_volatile_shift)); - __ testl(flags, flags); + __ movl(rscratch1, flags); + __ andl(rscratch1, (1 << ResolvedFieldEntry::is_volatile_shift)); + __ testl(rscratch1, rscratch1); __ jcc(Assembler::zero, notVolatile); - putfield_or_static_helper(byte_no, is_static, rc, obj, off, tos_state); + putfield_or_static_helper(byte_no, is_static, rc, obj, off, tos_state, flags); volatile_barrier(Assembler::Membar_mask_bits(Assembler::StoreLoad | Assembler::StoreStore)); __ jmp(Done); __ bind(notVolatile); - putfield_or_static_helper(byte_no, is_static, rc, obj, off, tos_state); + putfield_or_static_helper(byte_no, is_static, rc, obj, off, tos_state, flags); __ bind(Done); } void TemplateTable::putfield_or_static_helper(int byte_no, bool is_static, RewriteControl rc, - Register obj, Register off, Register tos_state) { + Register obj, Register off, Register tos_state, Register flags) { // field addresses const Address field(obj, off, Address::times_1, 0*wordSize); Label notByte, notBool, notInt, notShort, notChar, - notLong, notFloat, notObj; + notLong, notFloat, notObj, notInlineType; Label Done; const Register bc = c_rarg3; @@ -2806,14 +2945,51 @@ void TemplateTable::putfield_or_static_helper(int byte_no, bool is_static, Rewri // atos { - __ pop(atos); - if (!is_static) pop_and_check_object(obj); - // Store into the field - do_oop_store(_masm, field, rax); - if (!is_static && rc == may_rewrite) { - patch_bytecode(Bytecodes::_fast_aputfield, bc, rbx, true, byte_no); + if (!EnableValhalla) { + __ pop(atos); + if (!is_static) pop_and_check_object(obj); + // Store into the field + do_oop_store(_masm, field, rax); + if (!is_static && rc == may_rewrite) { + patch_bytecode(Bytecodes::_fast_aputfield, bc, rbx, true, byte_no); + } + __ jmp(Done); + } else { + __ pop(atos); + if (is_static) { + Label is_nullable; + __ test_field_is_not_null_free_inline_type(flags, rscratch1, is_nullable); + __ null_check(rax); // FIXME JDK-8341120 + __ bind(is_nullable); + do_oop_store(_masm, field, rax); + __ jmp(Done); + } else { + Label is_flat, null_free_reference, rewrite_inline; + __ test_field_is_flat(flags, rscratch1, is_flat); + __ test_field_is_null_free_inline_type(flags, rscratch1, null_free_reference); + pop_and_check_object(obj); + // Store into the field + do_oop_store(_masm, field, rax); + if (rc == may_rewrite) { + patch_bytecode(Bytecodes::_fast_aputfield, bc, rbx, true, byte_no); + } + __ jmp(Done); + __ bind(null_free_reference); + __ null_check(rax); // FIXME JDK-8341120 + pop_and_check_object(obj); + // Store into the field + do_oop_store(_masm, field, rax); + __ jmp(rewrite_inline); + __ bind(is_flat); + pop_and_check_object(rscratch2); + __ write_flat_field(rcx, r8, rscratch1, rscratch2, rbx, rax); + __ bind(rewrite_inline); + if (rc == may_rewrite) { + patch_bytecode(Bytecodes::_fast_vputfield, bc, rbx, true, byte_no); + } + __ jmp(Done); + } } - __ jmp(Done); } __ bind(notObj); @@ -2950,6 +3126,7 @@ void TemplateTable::jvmti_post_fast_field_mod() { // to do it for every data type, we use the saved values as the // jvalue object. switch (bytecode()) { // load values into the jvalue object + case Bytecodes::_fast_vputfield: //fall through case Bytecodes::_fast_aputfield: __ push_ptr(rax); break; case Bytecodes::_fast_bputfield: // fall through case Bytecodes::_fast_zputfield: // fall through @@ -2973,6 +3150,7 @@ void TemplateTable::jvmti_post_fast_field_mod() { __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::post_field_modification), rbx, c_rarg2, c_rarg3); switch (bytecode()) { // restore tos values + case Bytecodes::_fast_vputfield: // fall through case Bytecodes::_fast_aputfield: __ pop_ptr(rax); break; case Bytecodes::_fast_bputfield: // fall through case Bytecodes::_fast_zputfield: // fall through @@ -2991,18 +3169,15 @@ void TemplateTable::jvmti_post_fast_field_mod() { void TemplateTable::fast_storefield(TosState state) { transition(state, vtos); - Register cache = rcx; - Label notVolatile, Done; jvmti_post_fast_field_mod(); __ push(rax); __ load_field_entry(rcx, rax); - load_resolved_field_entry(noreg, cache, rax, rbx, rdx); - // RBX: field offset, RAX: TOS, RDX: flags - __ andl(rdx, (1 << ResolvedFieldEntry::is_volatile_shift)); + load_resolved_field_entry(noreg, rcx, rax, rbx, rdx); __ pop(rax); + // RBX: field offset, RCX: RAX: TOS, RDX: flags // Get object from stack pop_and_check_object(rcx); @@ -3011,26 +3186,47 @@ void TemplateTable::fast_storefield(TosState state) { const Address field(rcx, rbx, Address::times_1); // Check for volatile store - __ testl(rdx, rdx); + __ movl(rscratch2, rdx); // saving flags for is_flat test + __ andl(rscratch2, (1 << ResolvedFieldEntry::is_volatile_shift)); + __ testl(rscratch2, rscratch2); __ jcc(Assembler::zero, notVolatile); - fast_storefield_helper(field, rax); + fast_storefield_helper(field, rax, rdx); volatile_barrier(Assembler::Membar_mask_bits(Assembler::StoreLoad | Assembler::StoreStore)); __ jmp(Done); __ bind(notVolatile); - fast_storefield_helper(field, rax); + fast_storefield_helper(field, rax, rdx); __ bind(Done); } -void TemplateTable::fast_storefield_helper(Address field, Register rax) { +void TemplateTable::fast_storefield_helper(Address field, Register rax, Register flags) { + + // DANGER: 'field' argument depends on rcx and rbx // access field switch (bytecode()) { + case Bytecodes::_fast_vputfield: + { + // Field is either flat (nullable or not) or non-flat and null-free + Label is_flat, done; + __ test_field_is_flat(flags, rscratch1, is_flat); + __ null_check(rax); // FIXME JDK-8341120 + do_oop_store(_masm, field, rax); + __ jmp(done); + __ bind(is_flat); + __ load_field_entry(r8, r9); + __ movptr(rscratch2, rcx); // re-shuffle registers because of VM call calling convention + __ write_flat_field(r8, rscratch1, r9, rscratch2, rbx, rax); + __ bind(done); + } + break; case Bytecodes::_fast_aputfield: - do_oop_store(_masm, field, rax); + { + do_oop_store(_masm, field, rax); + } break; case Bytecodes::_fast_lputfield: __ access_store_at(T_LONG, IN_HEAP, field, noreg /* ltos */, noreg, noreg, noreg); @@ -3086,15 +3282,19 @@ void TemplateTable::fast_accessfield(TosState state) { // access constant pool cache __ load_field_entry(rcx, rbx); - __ load_sized_value(rbx, Address(rcx, in_bytes(ResolvedFieldEntry::field_offset_offset())), sizeof(int), true /*is_signed*/); + __ load_sized_value(rdx, Address(rcx, in_bytes(ResolvedFieldEntry::field_offset_offset())), sizeof(int), true /*is_signed*/); // rax: object __ verify_oop(rax); __ null_check(rax); - Address field(rax, rbx, Address::times_1); + Address field(rax, rdx, Address::times_1); // access field switch (bytecode()) { + case Bytecodes::_fast_vgetfield: + __ read_flat_field(rcx, rdx, rbx, rax); + __ verify_oop(rax); + break; case Bytecodes::_fast_agetfield: do_oop_load(_masm, field, rax); __ verify_oop(rax); @@ -3519,9 +3719,7 @@ void TemplateTable::_new() { transition(vtos, atos); __ get_unsigned_2_byte_index_at_bcp(rdx, 1); Label slow_case; - Label slow_case_no_pop; Label done; - Label initialize_header; __ get_cpool_and_tags(rcx, rax); @@ -3530,105 +3728,21 @@ void TemplateTable::_new() { // how Constant Pool is updated (see ConstantPool::klass_at_put) const int tags_offset = Array::base_offset_in_bytes(); __ cmpb(Address(rax, rdx, Address::times_1, tags_offset), JVM_CONSTANT_Class); - __ jcc(Assembler::notEqual, slow_case_no_pop); + __ jcc(Assembler::notEqual, slow_case); // get InstanceKlass __ load_resolved_klass_at_index(rcx, rcx, rdx); - __ push(rcx); // save the contexts of klass for initializing the header // make sure klass is initialized // init_state needs acquire, but x86 is TSO, and so we are already good. assert(VM_Version::supports_fast_class_init_checks(), "must support fast class initialization checks"); __ clinit_barrier(rcx, nullptr /*L_fast_path*/, &slow_case); - // get instance_size in InstanceKlass (scaled to a count of bytes) - __ movl(rdx, Address(rcx, Klass::layout_helper_offset())); - // test to see if it is malformed in some way - __ testl(rdx, Klass::_lh_instance_slow_path_bit); - __ jcc(Assembler::notZero, slow_case); - - // Allocate the instance: - // If TLAB is enabled: - // Try to allocate in the TLAB. - // If fails, go to the slow path. - // Initialize the allocation. - // Exit. - // - // Go to slow path. - - if (UseTLAB) { - __ tlab_allocate(rax, rdx, 0, rcx, rbx, slow_case); - if (ZeroTLAB) { - // the fields have been already cleared - __ jmp(initialize_header); - } - - // The object is initialized before the header. If the object size is - // zero, go directly to the header initialization. - if (UseCompactObjectHeaders) { - assert(is_aligned(oopDesc::base_offset_in_bytes(), BytesPerLong), "oop base offset must be 8-byte-aligned"); - __ decrement(rdx, oopDesc::base_offset_in_bytes()); - } else { - __ decrement(rdx, sizeof(oopDesc)); - } - __ jcc(Assembler::zero, initialize_header); - - // Initialize topmost object field, divide rdx by 8, check if odd and - // test if zero. - __ xorl(rcx, rcx); // use zero reg to clear memory (shorter code) - __ shrl(rdx, LogBytesPerLong); // divide by 2*oopSize and set carry flag if odd - - // rdx must have been multiple of 8 -#ifdef ASSERT - // make sure rdx was multiple of 8 - Label L; - // Ignore partial flag stall after shrl() since it is debug VM - __ jcc(Assembler::carryClear, L); - __ stop("object size is not multiple of 2 - adjust this code"); - __ bind(L); - // rdx must be > 0, no extra check needed here -#endif - - // initialize remaining object fields: rdx was a multiple of 8 - { Label loop; - __ bind(loop); - int header_size_bytes = oopDesc::header_size() * HeapWordSize; - assert(is_aligned(header_size_bytes, BytesPerLong), "oop header size must be 8-byte-aligned"); - __ movptr(Address(rax, rdx, Address::times_8, header_size_bytes - 1*oopSize), rcx); - __ decrement(rdx); - __ jcc(Assembler::notZero, loop); - } - - // initialize object header only. - __ bind(initialize_header); - if (UseCompactObjectHeaders) { - __ pop(rcx); // get saved klass back in the register. - __ movptr(rbx, Address(rcx, Klass::prototype_header_offset())); - __ movptr(Address(rax, oopDesc::mark_offset_in_bytes()), rbx); - } else { - __ movptr(Address(rax, oopDesc::mark_offset_in_bytes()), - (intptr_t)markWord::prototype().value()); // header - __ pop(rcx); // get saved klass back in the register. - __ xorl(rsi, rsi); // use zero reg to clear memory (shorter code) - __ store_klass_gap(rax, rsi); // zero klass gap for compressed oops - __ store_klass(rax, rcx, rscratch1); // klass - } - - if (DTraceAllocProbes) { - // Trigger dtrace event for fastpath - __ push(atos); - __ call_VM_leaf( - CAST_FROM_FN_PTR(address, static_cast(SharedRuntime::dtrace_object_alloc)), rax); - __ pop(atos); - } - - __ jmp(done); - } + __ allocate_instance(rcx, rax, rdx, rbx, true, slow_case); + __ jmp(done); // slow case __ bind(slow_case); - __ pop(rcx); // restore stack pointer to what it was when we came in. - __ bind(slow_case_no_pop); __ get_constant_pool(c_rarg1); __ get_unsigned_2_byte_index_at_bcp(c_rarg2, 1); @@ -3670,10 +3784,10 @@ void TemplateTable::checkcast() { __ get_cpool_and_tags(rcx, rdx); // rcx=cpool, rdx=tags array __ get_unsigned_2_byte_index_at_bcp(rbx, 1); // rbx=index // See if bytecode has already been quicked - __ cmpb(Address(rdx, rbx, - Address::times_1, - Array::base_offset_in_bytes()), - JVM_CONSTANT_Class); + __ movzbl(rdx, Address(rdx, rbx, + Address::times_1, + Array::base_offset_in_bytes())); + __ cmpl(rdx, JVM_CONSTANT_Class); __ jcc(Assembler::equal, quicked); __ push(atos); // save receiver for result, and for GC call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::quicken_io_cc)); @@ -3703,15 +3817,15 @@ void TemplateTable::checkcast() { // Come here on success __ bind(ok_is_subtype); __ mov(rax, rdx); // Restore object in rdx + __ jmp(done); + + __ bind(is_null); // Collect counts on whether this check-cast sees nulls a lot or not. if (ProfileInterpreter) { - __ jmp(done); - __ bind(is_null); __ profile_null_seen(rcx); - } else { - __ bind(is_null); // same as 'done' } + __ bind(done); } @@ -3725,10 +3839,10 @@ void TemplateTable::instanceof() { __ get_cpool_and_tags(rcx, rdx); // rcx=cpool, rdx=tags array __ get_unsigned_2_byte_index_at_bcp(rbx, 1); // rbx=index // See if bytecode has already been quicked - __ cmpb(Address(rdx, rbx, - Address::times_1, - Array::base_offset_in_bytes()), - JVM_CONSTANT_Class); + __ movzbl(rdx, Address(rdx, rbx, + Address::times_1, + Array::base_offset_in_bytes())); + __ cmpl(rdx, JVM_CONSTANT_Class); __ jcc(Assembler::equal, quicked); __ push(atos); // save receiver for result, and for GC @@ -3772,7 +3886,6 @@ void TemplateTable::instanceof() { // rax = 1: obj != nullptr and obj is an instanceof the specified klass } - //---------------------------------------------------------------------------------------------------- // Breakpoints void TemplateTable::_breakpoint() { @@ -3832,6 +3945,10 @@ void TemplateTable::monitorenter() { // check for null object __ null_check(rax); + Label is_inline_type; + __ movptr(rbx, Address(rax, oopDesc::mark_offset_in_bytes())); + __ test_markword_is_inline_type(rbx, is_inline_type); + const Address monitor_block_top( rbp, frame::interpreter_frame_monitor_block_top_offset * wordSize); const Address monitor_block_bot( @@ -3924,6 +4041,11 @@ void TemplateTable::monitorenter() { // The bcp has already been incremented. Just need to dispatch to // next instruction. __ dispatch_next(vtos); + + __ bind(is_inline_type); + __ call_VM(noreg, CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_identity_exception), rax); + __ should_not_reach_here(); } void TemplateTable::monitorexit() { @@ -3932,6 +4054,17 @@ void TemplateTable::monitorexit() { // check for null object __ null_check(rax); + const int is_inline_type_mask = markWord::inline_type_pattern; + Label has_identity; + __ movptr(rbx, Address(rax, oopDesc::mark_offset_in_bytes())); + __ andptr(rbx, is_inline_type_mask); + __ cmpl(rbx, is_inline_type_mask); + __ jcc(Assembler::notEqual, has_identity); + __ call_VM(noreg, CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_illegal_monitor_state_exception)); + __ should_not_reach_here(); + __ bind(has_identity); + const Address monitor_block_top( rbp, frame::interpreter_frame_monitor_block_top_offset * wordSize); const Address monitor_block_bot( diff --git a/src/hotspot/cpu/x86/templateTable_x86.hpp b/src/hotspot/cpu/x86/templateTable_x86.hpp index 15493d2f98a..3e4bab76945 100644 --- a/src/hotspot/cpu/x86/templateTable_x86.hpp +++ b/src/hotspot/cpu/x86/templateTable_x86.hpp @@ -37,7 +37,9 @@ static void index_check_without_pop(Register array, Register index); static void putfield_or_static_helper(int byte_no, bool is_static, RewriteControl rc, - Register obj, Register off, Register flags); - static void fast_storefield_helper(Address field, Register rax); + Register obj, Register off, Register tos_state, Register flags); + static void fast_storefield_helper(Address field, Register obj, Register flags); + + static void invoke_is_substitutable(Register aobj, Register bobj, Label& is_subst, Label& not_subst); #endif // CPU_X86_TEMPLATETABLE_X86_HPP diff --git a/src/hotspot/cpu/x86/vm_version_x86.cpp b/src/hotspot/cpu/x86/vm_version_x86.cpp index 1c9f8ed2e40..05b61cfa722 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.cpp +++ b/src/hotspot/cpu/x86/vm_version_x86.cpp @@ -1780,7 +1780,7 @@ void VM_Version::get_processor_features() { #endif // Use XMM/YMM MOVDQU instruction for Object Initialization - if (!UseFastStosb && UseUnalignedLoadStores) { + if (UseUnalignedLoadStores) { if (FLAG_IS_DEFAULT(UseXMMForObjInit)) { UseXMMForObjInit = true; } diff --git a/src/hotspot/cpu/x86/vtableStubs_x86_64.cpp b/src/hotspot/cpu/x86/vtableStubs_x86_64.cpp index b27755a243f..81929e78d58 100644 --- a/src/hotspot/cpu/x86/vtableStubs_x86_64.cpp +++ b/src/hotspot/cpu/x86/vtableStubs_x86_64.cpp @@ -44,11 +44,11 @@ extern "C" void bad_compiled_vtable_index(JavaThread* thread, oop receiver, int index); #endif -VtableStub* VtableStubs::create_vtable_stub(int vtable_index) { +VtableStub* VtableStubs::create_vtable_stub(int vtable_index, bool caller_is_c1) { // Read "A word on VtableStub sizing" in share/code/vtableStubs.hpp for details on stub sizing. const int stub_code_length = code_size_limit(true); - VtableStub* s = new(stub_code_length) VtableStub(true, vtable_index); - // Can be null if there is no free space in the code cache. + VtableStub* s = new(stub_code_length) VtableStub(true, vtable_index, caller_is_c1); + // Can be nullptr if there is no free space in the code cache. if (s == nullptr) { return nullptr; } @@ -61,6 +61,7 @@ VtableStub* VtableStubs::create_vtable_stub(int vtable_index) { int slop_delta = 0; // No variance was detected in vtable stub sizes. Setting index_dependent_slop == 0 will unveil any deviation from this observation. const int index_dependent_slop = 0; + ByteSize entry_offset = caller_is_c1 ? Method::from_compiled_inline_offset() : Method::from_compiled_inline_ro_offset(); ResourceMark rm; CodeBuffer cb(s->entry_point(), stub_code_length); @@ -117,7 +118,7 @@ VtableStub* VtableStubs::create_vtable_stub(int vtable_index) { Label L; __ cmpptr(method, NULL_WORD); __ jcc(Assembler::equal, L); - __ cmpptr(Address(method, Method::from_compiled_offset()), NULL_WORD); + __ cmpptr(Address(method, entry_offset), NULL_WORD); __ jcc(Assembler::notZero, L); __ stop("Vtable entry is null"); __ bind(L); @@ -128,7 +129,7 @@ VtableStub* VtableStubs::create_vtable_stub(int vtable_index) { // method (rbx): Method* // rcx: receiver address ame_addr = __ pc(); - __ jmp( Address(rbx, Method::from_compiled_offset())); + __ jmp( Address(rbx, entry_offset)); masm->flush(); slop_bytes += index_dependent_slop; // add'l slop for size variance due to large itable offsets @@ -138,11 +139,12 @@ VtableStub* VtableStubs::create_vtable_stub(int vtable_index) { } -VtableStub* VtableStubs::create_itable_stub(int itable_index) { +VtableStub* VtableStubs::create_itable_stub(int itable_index, bool caller_is_c1) { // Read "A word on VtableStub sizing" in share/code/vtableStubs.hpp for details on stub sizing. const int stub_code_length = code_size_limit(false); - VtableStub* s = new(stub_code_length) VtableStub(false, itable_index); - // Can be null if there is no free space in the code cache. + ByteSize entry_offset = caller_is_c1 ? Method::from_compiled_inline_offset() : Method::from_compiled_inline_ro_offset(); + VtableStub* s = new(stub_code_length) VtableStub(false, itable_index, caller_is_c1); + // Can be nullptr if there is no free space in the code cache. if (s == nullptr) { return nullptr; } @@ -209,7 +211,7 @@ VtableStub* VtableStubs::create_itable_stub(int itable_index) { // We expect we need index_dependent_slop extra bytes. Reason: // The emitted code in lookup_interface_method changes when itable_index exceeds 15. // For linux, a very narrow estimate would be 112, but Solaris requires some more space (130). - const ptrdiff_t estimate = 136; + const ptrdiff_t estimate = 144; const ptrdiff_t codesize = lookupSize + index_dependent_slop; slop_delta = (int)(estimate - codesize); slop_bytes += slop_delta; @@ -228,7 +230,7 @@ VtableStub* VtableStubs::create_itable_stub(int itable_index) { Label L2; __ cmpptr(method, NULL_WORD); __ jcc(Assembler::equal, L2); - __ cmpptr(Address(method, Method::from_compiled_offset()), NULL_WORD); + __ cmpptr(Address(method, entry_offset), NULL_WORD); __ jcc(Assembler::notZero, L2); __ stop("compiler entrypoint is null"); __ bind(L2); @@ -236,7 +238,7 @@ VtableStub* VtableStubs::create_itable_stub(int itable_index) { #endif // ASSERT address ame_addr = __ pc(); - __ jmp(Address(method, Method::from_compiled_offset())); + __ jmp(Address(method, entry_offset)); __ bind(L_no_such_interface); // Handle IncompatibleClassChangeError in itable stubs. diff --git a/src/hotspot/cpu/x86/x86.ad b/src/hotspot/cpu/x86/x86.ad index 2eb748e350c..7d5f0119d57 100644 --- a/src/hotspot/cpu/x86/x86.ad +++ b/src/hotspot/cpu/x86/x86.ad @@ -2721,6 +2721,39 @@ encode %{ __ int3(); __ bind(L); } + if (tf()->returns_inline_type_as_fields() && !_method->is_method_handle_intrinsic() && _method->return_type()->is_loaded()) { + // The last return value is not set by the callee but used to pass the null marker to compiled code. + // Search for the corresponding projection, get the register and emit code that initialized it. + uint con = (tf()->range_cc()->cnt() - 1); + for (DUIterator_Fast imax, i = fast_outs(imax); i < imax; i++) { + ProjNode* proj = fast_out(i)->as_Proj(); + if (proj->_con == con) { + // Set null marker if rax is non-null (a non-null value is returned buffered or scalarized) + OptoReg::Name optoReg = ra_->get_reg_first(proj); + VMReg reg = OptoReg::as_VMReg(optoReg, ra_->_framesize, OptoReg::reg2stack(ra_->_matcher._new_SP)); + Register toReg = reg->is_reg() ? reg->as_Register() : rscratch1; + __ testq(rax, rax); + __ setb(Assembler::notZero, toReg); + __ movzbl(toReg, toReg); + if (reg->is_stack()) { + int st_off = reg->reg2stack() * VMRegImpl::stack_slot_size; + __ movq(Address(rsp, st_off), toReg); + } + break; + } + } + if (return_value_is_used()) { + // An inline type is returned as fields in multiple registers. + // Rax either contains an oop if the inline type is buffered or a pointer + // to the corresponding InlineKlass with the lowest bit set to 1. Zero rax + // if the lowest bit is set to allow C2 to use the oop after null checking. + // rax &= (rax & 1) - 1 + __ movptr(rscratch1, rax); + __ andptr(rscratch1, 0x1); + __ subptr(rscratch1, 0x1); + __ andptr(rax, rscratch1); + } + } %} %} diff --git a/src/hotspot/cpu/x86/x86_64.ad b/src/hotspot/cpu/x86/x86_64.ad index 932dc9e1ca7..8368ecb66cc 100644 --- a/src/hotspot/cpu/x86/x86_64.ad +++ b/src/hotspot/cpu/x86/x86_64.ad @@ -615,12 +615,17 @@ int MachCallDynamicJavaNode::ret_addr_offset() } int MachCallRuntimeNode::ret_addr_offset() { + if (_entry_point == nullptr) { + // CallLeafNoFPInDirect + return 3; // callq (register) + } int offset = 13; // movq r10,#addr; callq (r10) if (this->ideal_Opcode() != Op_CallLeafVector) { offset += clear_avx_size(); } return offset; } + // // Compute padding required for nodes which need alignment // @@ -846,25 +851,15 @@ void MachPrologNode::format(PhaseRegAlloc* ra_, outputStream* st) const { void MachPrologNode::emit(C2_MacroAssembler *masm, PhaseRegAlloc *ra_) const { Compile* C = ra_->C; - int framesize = C->output()->frame_size_in_bytes(); - int bangsize = C->output()->bang_size_in_bytes(); - - if (C->clinit_barrier_on_entry()) { - assert(VM_Version::supports_fast_class_init_checks(), "sanity"); - assert(!C->method()->holder()->is_not_initialized(), "initialization should have been started"); - - Label L_skip_barrier; - Register klass = rscratch1; - - __ mov_metadata(klass, C->method()->holder()->constant_encoding()); - __ clinit_barrier(klass, &L_skip_barrier /*L_fast_path*/); - - __ jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); // slow path + __ verified_entry(C); - __ bind(L_skip_barrier); + if (ra_->C->stub_function() == nullptr) { + __ entry_barrier(); } - __ verified_entry(framesize, C->output()->need_stack_bang(bangsize)?bangsize:0, false, C->stub_function() != nullptr); + if (!Compile::current()->output()->in_scratch_emit_size()) { + __ bind(*_verified_entry); + } C->output()->set_frame_complete(__ offset()); @@ -876,12 +871,6 @@ void MachPrologNode::emit(C2_MacroAssembler *masm, PhaseRegAlloc *ra_) const { } } -uint MachPrologNode::size(PhaseRegAlloc* ra_) const -{ - return MachNode::size(ra_); // too many variables; just compute it - // the hard way -} - int MachPrologNode::reloc() const { return 0; // a large enough number @@ -928,19 +917,9 @@ void MachEpilogNode::emit(C2_MacroAssembler* masm, PhaseRegAlloc* ra_) const __ vzeroupper(); } - int framesize = C->output()->frame_size_in_bytes(); - assert((framesize & (StackAlignmentInBytes-1)) == 0, "frame size not aligned"); - // Remove word for return adr already pushed - // and RBP - framesize -= 2*wordSize; - - // Note that VerifyStackAtCalls' Majik cookie does not change the frame size popped here - - if (framesize) { - __ addq(rsp, framesize); - } - - __ popq(rbp); + // Subtract two words to account for return address and rbp + int initial_framesize = C->output()->frame_size_in_bytes() - 2*wordSize; + __ remove_frame(initial_framesize, C->needs_stack_repair()); if (StackReservedPages > 0 && C->has_reserved_stack_access()) { __ reserved_stack_check(); @@ -959,12 +938,6 @@ void MachEpilogNode::emit(C2_MacroAssembler* masm, PhaseRegAlloc* ra_) const } } -uint MachEpilogNode::size(PhaseRegAlloc* ra_) const -{ - return MachNode::size(ra_); // too many variables; just compute it - // the hard way -} - int MachEpilogNode::reloc() const { return 2; // a large enough number @@ -1566,6 +1539,49 @@ uint BoxLockNode::size(PhaseRegAlloc *ra_) const } } +//============================================================================= +#ifndef PRODUCT +void MachVEPNode::format(PhaseRegAlloc* ra_, outputStream* st) const +{ + st->print_cr("MachVEPNode"); +} +#endif + +void MachVEPNode::emit(C2_MacroAssembler* masm, PhaseRegAlloc* ra_) const +{ + CodeBuffer* cbuf = masm->code(); + uint insts_size = cbuf->insts_size(); + if (!_verified) { + __ ic_check(1); + } else { + // TODO 8284443 Avoid creation of temporary frame + if (ra_->C->stub_function() == nullptr) { + __ verified_entry(ra_->C, 0); + __ entry_barrier(); + int initial_framesize = ra_->C->output()->frame_size_in_bytes() - 2*wordSize; + __ remove_frame(initial_framesize, false); + } + // Unpack inline type args passed as oop and then jump to + // the verified entry point (skipping the unverified entry). + int sp_inc = __ unpack_inline_args(ra_->C, _receiver_only); + // Emit code for verified entry and save increment for stack repair on return + __ verified_entry(ra_->C, sp_inc); + if (Compile::current()->output()->in_scratch_emit_size()) { + Label dummy_verified_entry; + __ jmp(dummy_verified_entry); + } else { + __ jmp(*_verified_entry); + } + } + /* WARNING these NOPs are critical so that verified entry point is properly + 4 bytes aligned for patching by NativeJump::patch_verified_entry() */ + int nops_cnt = 4 - ((cbuf->insts_size() - insts_size) & 0x3); + nops_cnt &= 0x3; // Do not add nops if code is aligned. + if (nops_cnt > 0) { + __ nop(nops_cnt); + } +} + //============================================================================= #ifndef PRODUCT void MachUEPNode::format(PhaseRegAlloc* ra_, outputStream* st) const @@ -1586,13 +1602,6 @@ void MachUEPNode::emit(C2_MacroAssembler* masm, PhaseRegAlloc* ra_) const __ ic_check(InteriorEntryAlignment); } -uint MachUEPNode::size(PhaseRegAlloc* ra_) const -{ - return MachNode::size(ra_); // too many variables; just compute it - // the hard way -} - - //============================================================================= bool Matcher::supports_vector_calling_convention(void) { @@ -3067,6 +3076,22 @@ operand indPosIndexScaleOffset(any_RegP reg, immL32 off, rRegI idx, immI2 scale) %} %} +// Indirect Narrow Oop Operand +operand indCompressedOop(rRegN reg) %{ + predicate(UseCompressedOops && (CompressedOops::shift() == Address::times_8)); + constraint(ALLOC_IN_RC(ptr_reg)); + match(DecodeN reg); + + op_cost(10); + format %{"[R12 + $reg << 3] (compressed oop addressing)" %} + interface(MEMORY_INTER) %{ + base(0xc); // R12 + index($reg); + scale(0x3); + disp(0x0); + %} +%} + // Indirect Narrow Oop Plus Offset Operand // Note: x86 architecture doesn't support "scale * index + offset" without a base // we can't free r12 even with CompressedOops::base() == nullptr. @@ -3413,7 +3438,7 @@ operand cmpOpUCF2() %{ opclass memory(indirect, indOffset8, indOffset32, indIndexOffset, indIndex, indIndexScale, indPosIndexScale, indIndexScaleOffset, indPosIndexOffset, indPosIndexScaleOffset, - indCompressedOopOffset, + indCompressedOop, indCompressedOopOffset, indirectNarrow, indOffset8Narrow, indOffset32Narrow, indIndexOffsetNarrow, indIndexNarrow, indIndexScaleNarrow, indIndexScaleOffsetNarrow, indPosIndexOffsetNarrow, indPosIndexScaleOffsetNarrow); @@ -5998,6 +6023,32 @@ instruct castX2P(rRegP dst, rRegL src) ins_pipe(ialu_reg_reg); // XXX %} +instruct castI2N(rRegN dst, rRegI src) +%{ + match(Set dst (CastI2N src)); + + format %{ "movq $dst, $src\t# int -> narrow ptr" %} + ins_encode %{ + if ($dst$$reg != $src$$reg) { + __ movl($dst$$Register, $src$$Register); + } + %} + ins_pipe(ialu_reg_reg); // XXX +%} + +instruct castN2X(rRegL dst, rRegN src) +%{ + match(Set dst (CastP2X src)); + + format %{ "movq $dst, $src\t# ptr -> long" %} + ins_encode %{ + if ($dst$$reg != $src$$reg) { + __ movptr($dst$$Register, $src$$Register); + } + %} + ins_pipe(ialu_reg_reg); // XXX +%} + instruct castP2X(rRegL dst, rRegP src) %{ match(Set dst (CastP2X src)); @@ -12129,14 +12180,132 @@ instruct MoveL2D_reg_reg(regD dst, rRegL src) %{ ins_pipe( pipe_slow ); %} + // Fast clearing of an array // Small non-constant lenght ClearArray for non-AVX512 targets. -instruct rep_stos(rcx_RegL cnt, rdi_RegP base, regD tmp, rax_RegI zero, +instruct rep_stos(rcx_RegL cnt, rdi_RegP base, regD tmp, rax_RegL val, Universe dummy, rFlagsReg cr) %{ - predicate(!((ClearArrayNode*)n)->is_large() && (UseAVX <= 2)); - match(Set dummy (ClearArray cnt base)); - effect(USE_KILL cnt, USE_KILL base, TEMP tmp, KILL zero, KILL cr); + predicate(!((ClearArrayNode*)n)->is_large() && !((ClearArrayNode*)n)->word_copy_only() && (UseAVX <= 2)); + match(Set dummy (ClearArray (Binary cnt base) val)); + effect(USE_KILL cnt, USE_KILL base, TEMP tmp, USE_KILL val, KILL cr); + + format %{ $$template + $$emit$$"cmp InitArrayShortSize,rcx\n\t" + $$emit$$"jg LARGE\n\t" + $$emit$$"dec rcx\n\t" + $$emit$$"js DONE\t# Zero length\n\t" + $$emit$$"mov rax,(rdi,rcx,8)\t# LOOP\n\t" + $$emit$$"dec rcx\n\t" + $$emit$$"jge LOOP\n\t" + $$emit$$"jmp DONE\n\t" + $$emit$$"# LARGE:\n\t" + if (UseFastStosb) { + $$emit$$"shlq rcx,3\t# Convert doublewords to bytes\n\t" + $$emit$$"rep stosb\t# Store rax to *rdi++ while rcx--\n\t" + } else if (UseXMMForObjInit) { + $$emit$$"movdq $tmp, $val\n\t" + $$emit$$"punpcklqdq $tmp, $tmp\n\t" + $$emit$$"vinserti128_high $tmp, $tmp\n\t" + $$emit$$"jmpq L_zero_64_bytes\n\t" + $$emit$$"# L_loop:\t# 64-byte LOOP\n\t" + $$emit$$"vmovdqu $tmp,(rax)\n\t" + $$emit$$"vmovdqu $tmp,0x20(rax)\n\t" + $$emit$$"add 0x40,rax\n\t" + $$emit$$"# L_zero_64_bytes:\n\t" + $$emit$$"sub 0x8,rcx\n\t" + $$emit$$"jge L_loop\n\t" + $$emit$$"add 0x4,rcx\n\t" + $$emit$$"jl L_tail\n\t" + $$emit$$"vmovdqu $tmp,(rax)\n\t" + $$emit$$"add 0x20,rax\n\t" + $$emit$$"sub 0x4,rcx\n\t" + $$emit$$"# L_tail:\t# Clearing tail bytes\n\t" + $$emit$$"add 0x4,rcx\n\t" + $$emit$$"jle L_end\n\t" + $$emit$$"dec rcx\n\t" + $$emit$$"# L_sloop:\t# 8-byte short loop\n\t" + $$emit$$"vmovq xmm0,(rax)\n\t" + $$emit$$"add 0x8,rax\n\t" + $$emit$$"dec rcx\n\t" + $$emit$$"jge L_sloop\n\t" + $$emit$$"# L_end:\n\t" + } else { + $$emit$$"rep stosq\t# Store rax to *rdi++ while rcx--\n\t" + } + $$emit$$"# DONE" + %} + ins_encode %{ + __ clear_mem($base$$Register, $cnt$$Register, $val$$Register, + $tmp$$XMMRegister, false, false); + %} + ins_pipe(pipe_slow); +%} + +instruct rep_stos_word_copy(rcx_RegL cnt, rdi_RegP base, regD tmp, rax_RegL val, + Universe dummy, rFlagsReg cr) +%{ + predicate(!((ClearArrayNode*)n)->is_large() && ((ClearArrayNode*)n)->word_copy_only() && (UseAVX <= 2)); + match(Set dummy (ClearArray (Binary cnt base) val)); + effect(USE_KILL cnt, USE_KILL base, TEMP tmp, USE_KILL val, KILL cr); + + format %{ $$template + $$emit$$"cmp InitArrayShortSize,rcx\n\t" + $$emit$$"jg LARGE\n\t" + $$emit$$"dec rcx\n\t" + $$emit$$"js DONE\t# Zero length\n\t" + $$emit$$"mov rax,(rdi,rcx,8)\t# LOOP\n\t" + $$emit$$"dec rcx\n\t" + $$emit$$"jge LOOP\n\t" + $$emit$$"jmp DONE\n\t" + $$emit$$"# LARGE:\n\t" + if (UseXMMForObjInit) { + $$emit$$"movdq $tmp, $val\n\t" + $$emit$$"punpcklqdq $tmp, $tmp\n\t" + $$emit$$"vinserti128_high $tmp, $tmp\n\t" + $$emit$$"jmpq L_zero_64_bytes\n\t" + $$emit$$"# L_loop:\t# 64-byte LOOP\n\t" + $$emit$$"vmovdqu $tmp,(rax)\n\t" + $$emit$$"vmovdqu $tmp,0x20(rax)\n\t" + $$emit$$"add 0x40,rax\n\t" + $$emit$$"# L_zero_64_bytes:\n\t" + $$emit$$"sub 0x8,rcx\n\t" + $$emit$$"jge L_loop\n\t" + $$emit$$"add 0x4,rcx\n\t" + $$emit$$"jl L_tail\n\t" + $$emit$$"vmovdqu $tmp,(rax)\n\t" + $$emit$$"add 0x20,rax\n\t" + $$emit$$"sub 0x4,rcx\n\t" + $$emit$$"# L_tail:\t# Clearing tail bytes\n\t" + $$emit$$"add 0x4,rcx\n\t" + $$emit$$"jle L_end\n\t" + $$emit$$"dec rcx\n\t" + $$emit$$"# L_sloop:\t# 8-byte short loop\n\t" + $$emit$$"vmovq xmm0,(rax)\n\t" + $$emit$$"add 0x8,rax\n\t" + $$emit$$"dec rcx\n\t" + $$emit$$"jge L_sloop\n\t" + $$emit$$"# L_end:\n\t" + } else { + $$emit$$"rep stosq\t# Store rax to *rdi++ while rcx--\n\t" + } + $$emit$$"# DONE" + %} + ins_encode %{ + __ clear_mem($base$$Register, $cnt$$Register, $val$$Register, + $tmp$$XMMRegister, false, true); + %} + ins_pipe(pipe_slow); +%} + +// Small non-constant length ClearArray for AVX512 targets. +instruct rep_stos_evex(rcx_RegL cnt, rdi_RegP base, legRegD tmp, kReg ktmp, rax_RegL val, + Universe dummy, rFlagsReg cr) +%{ + predicate(!((ClearArrayNode*)n)->is_large() && !((ClearArrayNode*)n)->word_copy_only() && (UseAVX > 2)); + match(Set dummy (ClearArray (Binary cnt base) val)); + ins_cost(125); + effect(USE_KILL cnt, USE_KILL base, TEMP tmp, TEMP ktmp, USE_KILL val, KILL cr); format %{ $$template $$emit$$"xorq rax, rax\t# ClearArray:\n\t" @@ -12184,20 +12353,19 @@ instruct rep_stos(rcx_RegL cnt, rdi_RegP base, regD tmp, rax_RegI zero, $$emit$$"# DONE" %} ins_encode %{ - __ clear_mem($base$$Register, $cnt$$Register, $zero$$Register, - $tmp$$XMMRegister, false, knoreg); + __ clear_mem($base$$Register, $cnt$$Register, $val$$Register, + $tmp$$XMMRegister, false, false, $ktmp$$KRegister); %} ins_pipe(pipe_slow); %} -// Small non-constant length ClearArray for AVX512 targets. -instruct rep_stos_evex(rcx_RegL cnt, rdi_RegP base, legRegD tmp, kReg ktmp, rax_RegI zero, - Universe dummy, rFlagsReg cr) +instruct rep_stos_evex_word_copy(rcx_RegL cnt, rdi_RegP base, legRegD tmp, kReg ktmp, rax_RegL val, + Universe dummy, rFlagsReg cr) %{ - predicate(!((ClearArrayNode*)n)->is_large() && (UseAVX > 2)); - match(Set dummy (ClearArray cnt base)); + predicate(!((ClearArrayNode*)n)->is_large() && ((ClearArrayNode*)n)->word_copy_only() && (UseAVX > 2)); + match(Set dummy (ClearArray (Binary cnt base) val)); ins_cost(125); - effect(USE_KILL cnt, USE_KILL base, TEMP tmp, TEMP ktmp, KILL zero, KILL cr); + effect(USE_KILL cnt, USE_KILL base, TEMP tmp, TEMP ktmp, USE_KILL val, KILL cr); format %{ $$template $$emit$$"xorq rax, rax\t# ClearArray:\n\t" @@ -12245,19 +12413,115 @@ instruct rep_stos_evex(rcx_RegL cnt, rdi_RegP base, legRegD tmp, kReg ktmp, rax_ $$emit$$"# DONE" %} ins_encode %{ - __ clear_mem($base$$Register, $cnt$$Register, $zero$$Register, - $tmp$$XMMRegister, false, $ktmp$$KRegister); + __ clear_mem($base$$Register, $cnt$$Register, $val$$Register, + $tmp$$XMMRegister, false, true, $ktmp$$KRegister); %} ins_pipe(pipe_slow); %} // Large non-constant length ClearArray for non-AVX512 targets. -instruct rep_stos_large(rcx_RegL cnt, rdi_RegP base, regD tmp, rax_RegI zero, +instruct rep_stos_large(rcx_RegL cnt, rdi_RegP base, regD tmp, rax_RegL val, Universe dummy, rFlagsReg cr) %{ - predicate((UseAVX <=2) && ((ClearArrayNode*)n)->is_large()); - match(Set dummy (ClearArray cnt base)); - effect(USE_KILL cnt, USE_KILL base, TEMP tmp, KILL zero, KILL cr); + predicate(((ClearArrayNode*)n)->is_large() && !((ClearArrayNode*)n)->word_copy_only() && (UseAVX <= 2)); + match(Set dummy (ClearArray (Binary cnt base) val)); + effect(USE_KILL cnt, USE_KILL base, TEMP tmp, USE_KILL val, KILL cr); + + format %{ $$template + if (UseFastStosb) { + $$emit$$"shlq rcx,3\t# Convert doublewords to bytes\n\t" + $$emit$$"rep stosb\t# Store rax to *rdi++ while rcx--" + } else if (UseXMMForObjInit) { + $$emit$$"movdq $tmp, $val\n\t" + $$emit$$"punpcklqdq $tmp, $tmp\n\t" + $$emit$$"vinserti128_high $tmp, $tmp\n\t" + $$emit$$"jmpq L_zero_64_bytes\n\t" + $$emit$$"# L_loop:\t# 64-byte LOOP\n\t" + $$emit$$"vmovdqu $tmp,(rax)\n\t" + $$emit$$"vmovdqu $tmp,0x20(rax)\n\t" + $$emit$$"add 0x40,rax\n\t" + $$emit$$"# L_zero_64_bytes:\n\t" + $$emit$$"sub 0x8,rcx\n\t" + $$emit$$"jge L_loop\n\t" + $$emit$$"add 0x4,rcx\n\t" + $$emit$$"jl L_tail\n\t" + $$emit$$"vmovdqu $tmp,(rax)\n\t" + $$emit$$"add 0x20,rax\n\t" + $$emit$$"sub 0x4,rcx\n\t" + $$emit$$"# L_tail:\t# Clearing tail bytes\n\t" + $$emit$$"add 0x4,rcx\n\t" + $$emit$$"jle L_end\n\t" + $$emit$$"dec rcx\n\t" + $$emit$$"# L_sloop:\t# 8-byte short loop\n\t" + $$emit$$"vmovq xmm0,(rax)\n\t" + $$emit$$"add 0x8,rax\n\t" + $$emit$$"dec rcx\n\t" + $$emit$$"jge L_sloop\n\t" + $$emit$$"# L_end:\n\t" + } else { + $$emit$$"rep stosq\t# Store rax to *rdi++ while rcx--" + } + %} + ins_encode %{ + __ clear_mem($base$$Register, $cnt$$Register, $val$$Register, + $tmp$$XMMRegister, true, false); + %} + ins_pipe(pipe_slow); +%} + +instruct rep_stos_large_word_copy(rcx_RegL cnt, rdi_RegP base, regD tmp, rax_RegL val, + Universe dummy, rFlagsReg cr) +%{ + predicate(((ClearArrayNode*)n)->is_large() && ((ClearArrayNode*)n)->word_copy_only() && (UseAVX <= 2)); + match(Set dummy (ClearArray (Binary cnt base) val)); + effect(USE_KILL cnt, USE_KILL base, TEMP tmp, USE_KILL val, KILL cr); + + format %{ $$template + if (UseXMMForObjInit) { + $$emit$$"movdq $tmp, $val\n\t" + $$emit$$"punpcklqdq $tmp, $tmp\n\t" + $$emit$$"vinserti128_high $tmp, $tmp\n\t" + $$emit$$"jmpq L_zero_64_bytes\n\t" + $$emit$$"# L_loop:\t# 64-byte LOOP\n\t" + $$emit$$"vmovdqu $tmp,(rax)\n\t" + $$emit$$"vmovdqu $tmp,0x20(rax)\n\t" + $$emit$$"add 0x40,rax\n\t" + $$emit$$"# L_zero_64_bytes:\n\t" + $$emit$$"sub 0x8,rcx\n\t" + $$emit$$"jge L_loop\n\t" + $$emit$$"add 0x4,rcx\n\t" + $$emit$$"jl L_tail\n\t" + $$emit$$"vmovdqu $tmp,(rax)\n\t" + $$emit$$"add 0x20,rax\n\t" + $$emit$$"sub 0x4,rcx\n\t" + $$emit$$"# L_tail:\t# Clearing tail bytes\n\t" + $$emit$$"add 0x4,rcx\n\t" + $$emit$$"jle L_end\n\t" + $$emit$$"dec rcx\n\t" + $$emit$$"# L_sloop:\t# 8-byte short loop\n\t" + $$emit$$"vmovq xmm0,(rax)\n\t" + $$emit$$"add 0x8,rax\n\t" + $$emit$$"dec rcx\n\t" + $$emit$$"jge L_sloop\n\t" + $$emit$$"# L_end:\n\t" + } else { + $$emit$$"rep stosq\t# Store rax to *rdi++ while rcx--" + } + %} + ins_encode %{ + __ clear_mem($base$$Register, $cnt$$Register, $val$$Register, + $tmp$$XMMRegister, true, true); + %} + ins_pipe(pipe_slow); +%} + +// Large non-constant length ClearArray for AVX512 targets. +instruct rep_stos_large_evex(rcx_RegL cnt, rdi_RegP base, legRegD tmp, kReg ktmp, rax_RegL val, + Universe dummy, rFlagsReg cr) +%{ + predicate(((ClearArrayNode*)n)->is_large() && !((ClearArrayNode*)n)->word_copy_only() && (UseAVX > 2)); + match(Set dummy (ClearArray (Binary cnt base) val)); + effect(USE_KILL cnt, USE_KILL base, TEMP tmp, TEMP ktmp, USE_KILL val, KILL cr); format %{ $$template if (UseFastStosb) { @@ -12296,19 +12560,18 @@ instruct rep_stos_large(rcx_RegL cnt, rdi_RegP base, regD tmp, rax_RegI zero, } %} ins_encode %{ - __ clear_mem($base$$Register, $cnt$$Register, $zero$$Register, - $tmp$$XMMRegister, true, knoreg); + __ clear_mem($base$$Register, $cnt$$Register, $val$$Register, + $tmp$$XMMRegister, true, false, $ktmp$$KRegister); %} ins_pipe(pipe_slow); %} -// Large non-constant length ClearArray for AVX512 targets. -instruct rep_stos_large_evex(rcx_RegL cnt, rdi_RegP base, legRegD tmp, kReg ktmp, rax_RegI zero, - Universe dummy, rFlagsReg cr) +instruct rep_stos_large_evex_word_copy(rcx_RegL cnt, rdi_RegP base, legRegD tmp, kReg ktmp, rax_RegL val, + Universe dummy, rFlagsReg cr) %{ - predicate((UseAVX > 2) && ((ClearArrayNode*)n)->is_large()); - match(Set dummy (ClearArray cnt base)); - effect(USE_KILL cnt, USE_KILL base, TEMP tmp, TEMP ktmp, KILL zero, KILL cr); + predicate(((ClearArrayNode*)n)->is_large() && ((ClearArrayNode*)n)->word_copy_only() && (UseAVX > 2)); + match(Set dummy (ClearArray (Binary cnt base) val)); + effect(USE_KILL cnt, USE_KILL base, TEMP tmp, TEMP ktmp, USE_KILL val, KILL cr); format %{ $$template if (UseFastStosb) { @@ -12347,22 +12610,23 @@ instruct rep_stos_large_evex(rcx_RegL cnt, rdi_RegP base, legRegD tmp, kReg ktmp } %} ins_encode %{ - __ clear_mem($base$$Register, $cnt$$Register, $zero$$Register, - $tmp$$XMMRegister, true, $ktmp$$KRegister); + __ clear_mem($base$$Register, $cnt$$Register, $val$$Register, + $tmp$$XMMRegister, true, true, $ktmp$$KRegister); %} ins_pipe(pipe_slow); %} // Small constant length ClearArray for AVX512 targets. -instruct rep_stos_im(immL cnt, rRegP base, regD tmp, rRegI zero, kReg ktmp, Universe dummy, rFlagsReg cr) +instruct rep_stos_im(immL cnt, rRegP base, regD tmp, rax_RegL val, kReg ktmp, Universe dummy, rFlagsReg cr) %{ - predicate(!((ClearArrayNode*)n)->is_large() && (MaxVectorSize >= 32) && VM_Version::supports_avx512vl()); - match(Set dummy (ClearArray cnt base)); + predicate(!((ClearArrayNode*)n)->is_large() && !((ClearArrayNode*)n)->word_copy_only() && + ((MaxVectorSize >= 32) && VM_Version::supports_avx512vl())); + match(Set dummy (ClearArray (Binary cnt base) val)); ins_cost(100); - effect(TEMP tmp, TEMP zero, TEMP ktmp, KILL cr); + effect(TEMP tmp, USE_KILL val, TEMP ktmp, KILL cr); format %{ "clear_mem_imm $base , $cnt \n\t" %} ins_encode %{ - __ clear_mem($base$$Register, $cnt$$constant, $zero$$Register, $tmp$$XMMRegister, $ktmp$$KRegister); + __ clear_mem($base$$Register, $cnt$$constant, $val$$Register, $tmp$$XMMRegister, $ktmp$$KRegister); %} ins_pipe(pipe_slow); %} @@ -14208,8 +14472,24 @@ instruct CallLeafDirectVector(method meth) %} // Call runtime without safepoint +// entry point is null, target holds the address to call +instruct CallLeafNoFPInDirect(rRegP target) +%{ + predicate(n->as_Call()->entry_point() == nullptr); + match(CallLeafNoFP target); + + ins_cost(300); + format %{ "call_leaf_nofp,runtime indirect " %} + ins_encode %{ + __ call($target$$Register); + %} + + ins_pipe(pipe_slow); +%} + instruct CallLeafNoFPDirect(method meth) %{ + predicate(n->as_Call()->entry_point() != nullptr); match(CallLeafNoFP); effect(USE meth); diff --git a/src/hotspot/cpu/zero/continuationHelper_zero.inline.hpp b/src/hotspot/cpu/zero/continuationHelper_zero.inline.hpp index a1f782623b2..a20417904f0 100644 --- a/src/hotspot/cpu/zero/continuationHelper_zero.inline.hpp +++ b/src/hotspot/cpu/zero/continuationHelper_zero.inline.hpp @@ -105,7 +105,7 @@ inline address ContinuationHelper::Frame::real_pc(const frame& f) { return nullptr; } -inline void ContinuationHelper::Frame::patch_pc(const frame& f, address pc) { +inline void ContinuationHelper::Frame::patch_pc(const frame& f, address pc, bool callee_augmented) { Unimplemented(); } diff --git a/src/hotspot/cpu/zero/globals_zero.hpp b/src/hotspot/cpu/zero/globals_zero.hpp index 6b6c6ea983c..e3664f0b0eb 100644 --- a/src/hotspot/cpu/zero/globals_zero.hpp +++ b/src/hotspot/cpu/zero/globals_zero.hpp @@ -73,6 +73,9 @@ define_pd_global(uintx, TypeProfileLevel, 0); define_pd_global(bool, PreserveFramePointer, false); +define_pd_global(bool, InlineTypePassFieldsAsArgs, false); +define_pd_global(bool, InlineTypeReturnedAsFields, false); + define_pd_global(bool, CompactStrings, true); #define ARCH_FLAGS(develop, \ diff --git a/src/hotspot/cpu/zero/sharedRuntime_zero.cpp b/src/hotspot/cpu/zero/sharedRuntime_zero.cpp index b2e406c205b..9ce71c79589 100644 --- a/src/hotspot/cpu/zero/sharedRuntime_zero.cpp +++ b/src/hotspot/cpu/zero/sharedRuntime_zero.cpp @@ -50,14 +50,47 @@ int SharedRuntime::java_calling_convention(const BasicType *sig_bt, return 0; } +int SharedRuntime::java_return_convention(const BasicType *sig_bt, + VMRegPair *regs, + int total_args_passed) { + Unimplemented(); + return 0; +} + +BufferedInlineTypeBlob* SharedRuntime::generate_buffered_inline_type_adapter(const InlineKlass* vk) { + Unimplemented(); + return nullptr; +} + void SharedRuntime::generate_i2c2i_adapters(MacroAssembler *masm, - int total_args_passed, int comp_args_on_stack, - const BasicType *sig_bt, - const VMRegPair *regs, - AdapterHandlerEntry* handler) { + const GrowableArray * sig, + const VMRegPair* regs, + const GrowableArray * sig_cc, + const VMRegPair* regs_cc, + const GrowableArray * sig_cc_ro, + const VMRegPair* regs_cc_ro, + AdapterHandlerEntry* handler, + AdapterBlob*& new_adapter, + bool allocate_code_blob) { + if (allocate_code_blob) { + int entry_offset[AdapterHandlerEntry::ENTRIES_COUNT]; + assert(AdapterHandlerEntry::ENTRIES_COUNT == 7, "sanity"); + entry_offset[0] = 0; // i2c_entry offset + entry_offset[1] = -1; + entry_offset[2] = -1; + entry_offset[3] = -1; + entry_offset[4] = -1; + entry_offset[5] = -1; + entry_offset[6] = -1; + + new_adapter = AdapterBlob::create(masm->code(), entry_offset, 0, 0, nullptr); + } // foil any attempt to call the i2c, c2i or unverified c2i entries handler->set_entry_points(CAST_FROM_FN_PTR(address,zero_null_code_stub), + CAST_FROM_FN_PTR(address,zero_null_code_stub), + CAST_FROM_FN_PTR(address,zero_null_code_stub), + CAST_FROM_FN_PTR(address,zero_null_code_stub), CAST_FROM_FN_PTR(address,zero_null_code_stub), CAST_FROM_FN_PTR(address,zero_null_code_stub), nullptr); diff --git a/src/hotspot/cpu/zero/vtableStubs_zero.cpp b/src/hotspot/cpu/zero/vtableStubs_zero.cpp index 12819b484b2..ff559e9d269 100644 --- a/src/hotspot/cpu/zero/vtableStubs_zero.cpp +++ b/src/hotspot/cpu/zero/vtableStubs_zero.cpp @@ -26,12 +26,12 @@ #include "code/vtableStubs.hpp" #include "utilities/debug.hpp" -VtableStub* VtableStubs::create_vtable_stub(int vtable_index) { +VtableStub* VtableStubs::create_vtable_stub(int vtable_index, bool caller_is_c1) { ShouldNotCallThis(); return nullptr; } -VtableStub* VtableStubs::create_itable_stub(int vtable_index) { +VtableStub* VtableStubs::create_itable_stub(int vtable_index, bool caller_is_c1) { ShouldNotCallThis(); return nullptr; } diff --git a/src/hotspot/share/adlc/forms.cpp b/src/hotspot/share/adlc/forms.cpp index e2265f70ed9..ddcec91c4ab 100644 --- a/src/hotspot/share/adlc/forms.cpp +++ b/src/hotspot/share/adlc/forms.cpp @@ -282,6 +282,7 @@ Form::DataType Form::is_store_to_memory(const char *opType) const { if( strcmp(opType,"StoreF")==0) return Form::idealF; if( strcmp(opType,"StoreI")==0) return Form::idealI; if( strcmp(opType,"StoreL")==0) return Form::idealL; + if( strcmp(opType,"StoreLSpecial")==0) return Form::idealL; if( strcmp(opType,"StoreP")==0) return Form::idealP; if( strcmp(opType,"StoreN")==0) return Form::idealN; if( strcmp(opType,"StoreNKlass")==0) return Form::idealNKlass; diff --git a/src/hotspot/share/adlc/formssel.cpp b/src/hotspot/share/adlc/formssel.cpp index b938d5b7560..1d06ca97eb2 100644 --- a/src/hotspot/share/adlc/formssel.cpp +++ b/src/hotspot/share/adlc/formssel.cpp @@ -778,6 +778,7 @@ bool InstructForm::captures_bottom_type(FormDict &globals) const { !strcmp(_matrule->_rChild->_opType,"CastLL") || !strcmp(_matrule->_rChild->_opType,"CastVV") || !strcmp(_matrule->_rChild->_opType,"CastX2P") || // new result type + !strcmp(_matrule->_rChild->_opType,"CastI2N") || !strcmp(_matrule->_rChild->_opType,"DecodeN") || !strcmp(_matrule->_rChild->_opType,"EncodeP") || !strcmp(_matrule->_rChild->_opType,"DecodeNKlass") || @@ -808,7 +809,7 @@ bool InstructForm::captures_bottom_type(FormDict &globals) const { if (is_vector()) return true; if (is_mach_constant()) return true; - return false; + return false; } @@ -899,7 +900,8 @@ uint InstructForm::oper_input_base(FormDict &globals) { strcmp(_matrule->_opType,"TailJump" )==0 || strcmp(_matrule->_opType,"ForwardException")==0 || strcmp(_matrule->_opType,"SafePoint" )==0 || - strcmp(_matrule->_opType,"Halt" )==0 ) + strcmp(_matrule->_opType,"Halt" )==0 || + strcmp(_matrule->_opType,"CallLeafNoFP")==0) return AdlcVMDeps::Parms; // Skip the machine-state edges if( _matrule->_rChild && @@ -3643,7 +3645,7 @@ void MatchNode::forms_do(FormClosure *f) { int MatchNode::needs_ideal_memory_edge(FormDict &globals) const { static const char *needs_ideal_memory_list[] = { - "StoreI","StoreL","StoreP","StoreN","StoreNKlass","StoreD","StoreF" , + "StoreI","StoreL","StoreLSpecial","StoreP","StoreN","StoreNKlass","StoreD","StoreF" , "StoreB","StoreC","Store" ,"StoreFP", "LoadI", "LoadL", "LoadP" ,"LoadN", "LoadD" ,"LoadF" , "LoadB" , "LoadUB", "LoadUS" ,"LoadS" ,"Load" , diff --git a/src/hotspot/share/adlc/main.cpp b/src/hotspot/share/adlc/main.cpp index 4e8a96617e8..250820d2675 100644 --- a/src/hotspot/share/adlc/main.cpp +++ b/src/hotspot/share/adlc/main.cpp @@ -217,6 +217,7 @@ int main(int argc, char *argv[]) AD.addInclude(AD._CPP_file, "code/compiledIC.hpp"); AD.addInclude(AD._CPP_file, "code/nativeInst.hpp"); AD.addInclude(AD._CPP_file, "code/vmreg.inline.hpp"); + AD.addInclude(AD._CPP_file, "gc/shared/barrierSetAssembler.hpp"); AD.addInclude(AD._CPP_file, "gc/shared/collectedHeap.inline.hpp"); AD.addInclude(AD._CPP_file, "oops/compressedOops.hpp"); AD.addInclude(AD._CPP_file, "oops/markWord.hpp"); diff --git a/src/hotspot/share/asm/codeBuffer.hpp b/src/hotspot/share/asm/codeBuffer.hpp index 35bbd2f657f..38680c68256 100644 --- a/src/hotspot/share/asm/codeBuffer.hpp +++ b/src/hotspot/share/asm/codeBuffer.hpp @@ -53,6 +53,9 @@ class CodeOffsets: public StackObj { public: enum Entries { Entry, Verified_Entry, + Inline_Entry, + Verified_Inline_Entry, + Verified_Inline_Entry_RO, Frame_Complete, // Offset in the code where the frame setup is (for forte stackwalks) is complete OSR_Entry, Exceptions, // Offset where exception handler lives @@ -68,11 +71,15 @@ class CodeOffsets: public StackObj { private: int _values[max_Entries]; + void check(int e) const { assert(0 <= e && e < max_Entries, "must be"); } public: CodeOffsets() { _values[Entry ] = 0; _values[Verified_Entry] = 0; + _values[Inline_Entry ] = 0; + _values[Verified_Inline_Entry] = -1; + _values[Verified_Inline_Entry_RO] = -1; _values[Frame_Complete] = frame_never_safe; _values[OSR_Entry ] = 0; _values[Exceptions ] = -1; @@ -81,8 +88,8 @@ class CodeOffsets: public StackObj { _values[UnwindHandler ] = -1; } - int value(Entries e) { return _values[e]; } - void set_value(Entries e, int val) { _values[e] = val; } + int value(Entries e) const { check(e); return _values[e]; } + void set_value(Entries e, int val) { check(e); _values[e] = val; } }; // This class represents a stream of code and associated relocations. diff --git a/src/hotspot/share/asm/macroAssembler.hpp b/src/hotspot/share/asm/macroAssembler.hpp index 35265de35ef..742af7c0f8c 100644 --- a/src/hotspot/share/asm/macroAssembler.hpp +++ b/src/hotspot/share/asm/macroAssembler.hpp @@ -26,6 +26,7 @@ #define SHARE_ASM_MACROASSEMBLER_HPP #include "asm/assembler.hpp" +#include "utilities/growableArray.hpp" #include "utilities/macros.hpp" #include CPU_HEADER(macroAssembler) diff --git a/src/hotspot/share/asm/macroAssembler_common.cpp b/src/hotspot/share/asm/macroAssembler_common.cpp new file mode 100644 index 00000000000..f6a5906ada0 --- /dev/null +++ b/src/hotspot/share/asm/macroAssembler_common.cpp @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "asm/assembler.hpp" +#include "asm/assembler.inline.hpp" +#include "asm/macroAssembler.hpp" +#include "jvm.h" +#include "oops/inlineKlass.inline.hpp" +#include "runtime/sharedRuntime.hpp" +#include "runtime/signature_cc.hpp" +#ifdef COMPILER2 +#include "opto/compile.hpp" +#include "opto/node.hpp" +#endif + +void MacroAssembler::skip_unpacked_fields(const GrowableArray* sig, int& sig_index, VMRegPair* regs_from, int regs_from_count, int& from_index) { + ScalarizedInlineArgsStream stream(sig, sig_index, regs_from, regs_from_count, from_index); + VMReg reg; + BasicType bt; + while (stream.next(reg, bt)) {} + sig_index = stream.sig_index(); + from_index = stream.regs_index(); +} + +bool MacroAssembler::is_reg_in_unpacked_fields(const GrowableArray* sig, int sig_index, VMReg to, VMRegPair* regs_from, int regs_from_count, int from_index) { + ScalarizedInlineArgsStream stream(sig, sig_index, regs_from, regs_from_count, from_index); + VMReg reg; + BasicType bt; + while (stream.next(reg, bt)) { + if (reg == to) { + return true; + } + } + return false; +} + +void MacroAssembler::mark_reg_writable(const VMRegPair* regs, int num_regs, int reg_index, MacroAssembler::RegState* reg_state) { + assert(0 <= reg_index && reg_index < num_regs, "sanity"); + VMReg from_reg = regs[reg_index].first(); + if (from_reg->is_valid()) { + assert(from_reg->is_stack(), "reserved entries must be stack"); + reg_state[from_reg->value()] = MacroAssembler::reg_writable; + } +} + +MacroAssembler::RegState* MacroAssembler::init_reg_state(VMRegPair* regs, int num_regs, int sp_inc, int max_stack) { + int max_reg = VMRegImpl::stack2reg(max_stack)->value(); + MacroAssembler::RegState* reg_state = NEW_RESOURCE_ARRAY(MacroAssembler::RegState, max_reg); + + // Make all writable + for (int i = 0; i < max_reg; ++i) { + reg_state[i] = MacroAssembler::reg_writable; + } + // Set all source registers/stack slots to readonly to prevent accidental overwriting + for (int i = 0; i < num_regs; ++i) { + VMReg reg = regs[i].first(); + if (!reg->is_valid()) continue; + if (reg->is_stack()) { + // Update source stack location by adding stack increment + reg = VMRegImpl::stack2reg(reg->reg2stack() + sp_inc/VMRegImpl::stack_slot_size); + regs[i] = reg; + } + assert(reg->value() >= 0 && reg->value() < max_reg, "reg value out of bounds"); + reg_state[reg->value()] = MacroAssembler::reg_readonly; + } + return reg_state; +} + +#ifdef COMPILER2 +int MacroAssembler::unpack_inline_args(Compile* C, bool receiver_only) { + assert(C->has_scalarized_args(), "inline type argument scalarization is disabled"); + Method* method = C->method()->get_Method(); + const GrowableArray* sig = method->adapter()->get_sig_cc(); + assert(sig != nullptr, "must have scalarized signature"); + + // Get unscalarized calling convention + BasicType* sig_bt = NEW_RESOURCE_ARRAY(BasicType, 256); + int args_passed = 0; + if (!method->is_static()) { + sig_bt[args_passed++] = T_OBJECT; + } + if (!receiver_only) { + for (SignatureStream ss(method->signature()); !ss.at_return_type(); ss.next()) { + BasicType bt = ss.type(); + sig_bt[args_passed++] = bt; + if (type2size[bt] == 2) { + sig_bt[args_passed++] = T_VOID; + } + } + } else { + // Only unpack the receiver, all other arguments are already scalarized + InstanceKlass* holder = method->method_holder(); + int rec_len = (holder->is_inline_klass() && method->is_scalarized_arg(0)) ? InlineKlass::cast(holder)->extended_sig()->length() : 1; + // Copy scalarized signature but skip receiver and inline type delimiters + for (int i = 0; i < sig->length(); i++) { + if (SigEntry::skip_value_delimiters(sig, i) && rec_len <= 0) { + sig_bt[args_passed++] = sig->at(i)._bt; + } + rec_len--; + } + } + VMRegPair* regs = NEW_RESOURCE_ARRAY(VMRegPair, args_passed); + int args_on_stack = SharedRuntime::java_calling_convention(sig_bt, regs, args_passed); + + // Get scalarized calling convention + int args_passed_cc = SigEntry::fill_sig_bt(sig, sig_bt); + VMRegPair* regs_cc = NEW_RESOURCE_ARRAY(VMRegPair, sig->length()); + int args_on_stack_cc = SharedRuntime::java_calling_convention(sig_bt, regs_cc, args_passed_cc); + + // Check if we need to extend the stack for unpacking + int sp_inc = 0; + if (args_on_stack_cc > args_on_stack) { + sp_inc = extend_stack_for_inline_args(args_on_stack_cc); + } + shuffle_inline_args(false, receiver_only, sig, + args_passed, args_on_stack, regs, // from + args_passed_cc, args_on_stack_cc, regs_cc, // to + sp_inc, noreg); + return sp_inc; +} +#endif // COMPILER2 + +void MacroAssembler::shuffle_inline_args(bool is_packing, bool receiver_only, + const GrowableArray* sig, + int args_passed, int args_on_stack, VMRegPair* regs, + int args_passed_to, int args_on_stack_to, VMRegPair* regs_to, + int sp_inc, Register val_array) { + int max_stack = MAX2(args_on_stack + sp_inc/VMRegImpl::stack_slot_size, args_on_stack_to); + RegState* reg_state = init_reg_state(regs, args_passed, sp_inc, max_stack); + + // Emit code for packing/unpacking inline type arguments + // We try multiple times and eventually start spilling to resolve (circular) dependencies + bool done = (args_passed_to == 0); + for (int i = 0; i < 2*args_passed_to && !done; ++i) { + done = true; + bool spill = (i > args_passed_to); // Start spilling? + // Iterate over all arguments (when unpacking, do in reverse) + int step = is_packing ? 1 : -1; + int from_index = is_packing ? 0 : args_passed - 1; + int to_index = is_packing ? 0 : args_passed_to - 1; + int sig_index = is_packing ? 0 : sig->length() - 1; + int sig_index_end = is_packing ? sig->length() : -1; + int vtarg_index = 0; + for (; sig_index != sig_index_end; sig_index += step) { + assert(0 <= sig_index && sig_index < sig->length(), "index out of bounds"); + if (spill) { + // This call returns true IFF we should keep trying to spill in this round. + spill = shuffle_inline_args_spill(is_packing, sig, sig_index, regs, from_index, args_passed, + reg_state); + } + BasicType bt = sig->at(sig_index)._bt; + if (SigEntry::skip_value_delimiters(sig, sig_index)) { + VMReg from_reg = regs[from_index].first(); + if (from_reg->is_valid()) { + done &= move_helper(from_reg, regs_to[to_index].first(), bt, reg_state); + } else { + // halves of T_LONG or T_DOUBLE + assert(bt == T_VOID, "unexpected basic type"); + } + to_index += step; + from_index += step; + } else if (is_packing) { + assert(val_array != noreg, "must be"); + VMReg reg_to = regs_to[to_index].first(); + done &= pack_inline_helper(sig, sig_index, vtarg_index, + regs, args_passed, from_index, reg_to, + reg_state, val_array); + vtarg_index++; + to_index++; + } else if (!receiver_only || (from_index == 0 && bt == T_VOID)) { + VMReg from_reg = regs[from_index].first(); + done &= unpack_inline_helper(sig, sig_index, + from_reg, from_index, regs_to, args_passed_to, to_index, + reg_state); + if (from_index == -1 && sig_index != 0) { + // This can happen when we are confusing an empty inline type argument which is + // not counted in the scalarized signature for the receiver. Just ignore it. + assert(receiver_only, "sanity"); + from_index = 0; + } + } + } + } + guarantee(done, "Could not resolve circular dependency when shuffling inline type arguments"); +} + +bool MacroAssembler::shuffle_inline_args_spill(bool is_packing, const GrowableArray* sig, int sig_index, + VMRegPair* regs_from, int from_index, int regs_from_count, RegState* reg_state) { + VMReg reg; + if (!is_packing || SigEntry::skip_value_delimiters(sig, sig_index)) { + reg = regs_from[from_index].first(); + if (!reg->is_valid() || reg_state[reg->value()] != reg_readonly) { + // Spilling this won't break cycles + return true; + } + } else { + ScalarizedInlineArgsStream stream(sig, sig_index, regs_from, regs_from_count, from_index); + VMReg from_reg; + BasicType bt; + bool found = false; + while (stream.next(from_reg, bt)) { + reg = from_reg; + assert(from_reg->is_valid(), "must be"); + if (reg_state[from_reg->value()] == reg_readonly) { + found = true; + break; + } + } + if (!found) { + // Spilling fields in this inline type arg won't break cycles + return true; + } + } + + // Spill argument to be able to write the source and resolve circular dependencies + VMReg spill_reg = spill_reg_for(reg); + if (reg_state[spill_reg->value()] == reg_readonly) { + // We have already spilled (in previous round). The spilled register should be consumed by this round. + } else { + bool res = move_helper(reg, spill_reg, T_DOUBLE, reg_state); + assert(res, "Spilling should not fail"); + // Set spill_reg as new source and update state + reg = spill_reg; + regs_from[from_index].set1(reg); + reg_state[reg->value()] = reg_readonly; + } + + return false; // Do not spill again in this round +} diff --git a/src/hotspot/share/asm/macroAssembler_common.hpp b/src/hotspot/share/asm/macroAssembler_common.hpp new file mode 100644 index 00000000000..23f37501597 --- /dev/null +++ b/src/hotspot/share/asm/macroAssembler_common.hpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. + * 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_ASM_MACROASSEMBLER_COMMON_HPP +#define SHARE_ASM_MACROASSEMBLER_COMMON_HPP + +// These are part of the MacroAssembler class that are common for all CPUs + +// class MacroAssembler ... { + + enum RegState { + reg_readonly, + reg_writable, + reg_written + }; + + void skip_unpacked_fields(const GrowableArray* sig, int& sig_index, VMRegPair* regs_from, + int regs_from_count, int& from_index); + bool is_reg_in_unpacked_fields(const GrowableArray* sig, int sig_index, VMReg to, VMRegPair* regs_from, + int regs_from_count, int from_index); + void mark_reg_writable(const VMRegPair* regs, int num_regs, int reg_index, RegState* reg_state); + RegState* init_reg_state(VMRegPair* regs, int num_regs, int sp_inc, int max_stack); + int unpack_inline_args(Compile* C, bool receiver_only); + void shuffle_inline_args(bool is_packing, bool receiver_only, + const GrowableArray* sig, + int args_passed, int args_on_stack, VMRegPair* regs, + int args_passed_to, int args_on_stack_to, VMRegPair* regs_to, + int sp_inc, Register val_array); + bool shuffle_inline_args_spill(bool is_packing, const GrowableArray* sig, int sig_index, + VMRegPair* regs_from, int from_index, int regs_from_count, RegState* reg_state); +// }; + +#endif // SHARE_ASM_MACROASSEMBLER_COMMON_HPP diff --git a/src/hotspot/share/c1/c1_Canonicalizer.cpp b/src/hotspot/share/c1/c1_Canonicalizer.cpp index 573e1ac24d7..087a85b40d7 100644 --- a/src/hotspot/share/c1/c1_Canonicalizer.cpp +++ b/src/hotspot/share/c1/c1_Canonicalizer.cpp @@ -280,7 +280,7 @@ void Canonicalizer::do_LoadIndexed (LoadIndexed* x) { assert(array == nullptr || FoldStableValues, "not enabled"); // Constant fold loads from stable arrays. - if (!x->mismatched() && array != nullptr && index != nullptr) { + if (!x->should_profile() && !x->mismatched() && array != nullptr && index != nullptr) { jint idx = index->value(); if (idx < 0 || idx >= array->value()->length()) { // Leave the load as is. The range check will handle it. @@ -648,13 +648,14 @@ void Canonicalizer::do_CheckCast (CheckCast* x) { klass->as_instance_klass()->is_interface(); // Interface casts can't be statically optimized away since verifier doesn't // enforce interface types in bytecode. - if (!is_interface && klass->is_subtype_of(x->klass())) { + if (!is_interface && klass->is_subtype_of(x->klass()) && (!x->is_null_free() || obj->is_null_free())) { + assert(!x->klass()->is_inlinetype() || x->klass() == klass, "Inline klasses can't have subtypes"); set_canonical(obj); return; } } - // checkcast of null returns null - if (obj->as_Constant() && obj->type()->as_ObjectType()->constant_value()->is_null_object()) { + // checkcast of null returns null for non null-free klasses + if (!x->is_null_free() && obj->is_null_obj()) { set_canonical(obj); } } @@ -668,7 +669,7 @@ void Canonicalizer::do_InstanceOf (InstanceOf* x) { return; } // instanceof null returns false - if (obj->as_Constant() && obj->type()->as_ObjectType()->constant_value()->is_null_object()) { + if (obj->as_Constant() && obj->is_null_obj()) { set_constant(0); } } @@ -845,8 +846,9 @@ void Canonicalizer::do_UnsafePut (UnsafePut* x) {} void Canonicalizer::do_UnsafeGetAndSet(UnsafeGetAndSet* x) {} void Canonicalizer::do_ProfileCall (ProfileCall* x) {} void Canonicalizer::do_ProfileReturnType(ProfileReturnType* x) {} -void Canonicalizer::do_ProfileInvoke (ProfileInvoke* x) {} -void Canonicalizer::do_RuntimeCall (RuntimeCall* x) {} +void Canonicalizer::do_ProfileInvoke (ProfileInvoke* x) {} +void Canonicalizer::do_ProfileACmpTypes (ProfileACmpTypes* x) {} +void Canonicalizer::do_RuntimeCall (RuntimeCall* x) {} void Canonicalizer::do_RangeCheckPredicate(RangeCheckPredicate* x) {} #ifdef ASSERT void Canonicalizer::do_Assert (Assert* x) {} diff --git a/src/hotspot/share/c1/c1_Canonicalizer.hpp b/src/hotspot/share/c1/c1_Canonicalizer.hpp index f1c99d4996c..7e56947adfe 100644 --- a/src/hotspot/share/c1/c1_Canonicalizer.hpp +++ b/src/hotspot/share/c1/c1_Canonicalizer.hpp @@ -93,6 +93,7 @@ class Canonicalizer: InstructionVisitor { virtual void do_UnsafeGetAndSet(UnsafeGetAndSet* x); virtual void do_ProfileCall (ProfileCall* x); virtual void do_ProfileReturnType (ProfileReturnType* x); + virtual void do_ProfileACmpTypes(ProfileACmpTypes* x); virtual void do_ProfileInvoke (ProfileInvoke* x); virtual void do_RuntimeCall (RuntimeCall* x); virtual void do_MemBar (MemBar* x); diff --git a/src/hotspot/share/c1/c1_CodeStubs.hpp b/src/hotspot/share/c1/c1_CodeStubs.hpp index 5d1c51bdbbf..fc4e2024e4c 100644 --- a/src/hotspot/share/c1/c1_CodeStubs.hpp +++ b/src/hotspot/share/c1/c1_CodeStubs.hpp @@ -258,6 +258,83 @@ class ImplicitNullCheckStub: public CodeStub { }; +class LoadFlattenedArrayStub: public CodeStub { + private: + LIR_Opr _array; + LIR_Opr _index; + LIR_Opr _result; + LIR_Opr _scratch_reg; + CodeEmitInfo* _info; + + public: + LoadFlattenedArrayStub(LIR_Opr array, LIR_Opr index, LIR_Opr result, CodeEmitInfo* info); + virtual void emit_code(LIR_Assembler* e); + virtual CodeEmitInfo* info() const { return _info; } + virtual void visit(LIR_OpVisitState* visitor) { + visitor->do_slow_case(_info); + visitor->do_input(_array); + visitor->do_input(_index); + visitor->do_output(_result); + if (_scratch_reg != LIR_OprFact::illegalOpr) { + visitor->do_temp(_scratch_reg); + } + } + +#ifndef PRODUCT + virtual void print_name(outputStream* out) const { out->print("LoadFlattenedArrayStub"); } +#endif // PRODUCT +}; + + +class StoreFlattenedArrayStub: public CodeStub { + private: + LIR_Opr _array; + LIR_Opr _index; + LIR_Opr _value; + LIR_Opr _scratch_reg; + CodeEmitInfo* _info; + + public: + StoreFlattenedArrayStub(LIR_Opr array, LIR_Opr index, LIR_Opr value, CodeEmitInfo* info); + virtual void emit_code(LIR_Assembler* e); + virtual CodeEmitInfo* info() const { return _info; } + virtual void visit(LIR_OpVisitState* visitor) { + visitor->do_slow_case(_info); + visitor->do_input(_array); + visitor->do_input(_index); + visitor->do_input(_value); + if (_scratch_reg != LIR_OprFact::illegalOpr) { + visitor->do_temp(_scratch_reg); + } + } +#ifndef PRODUCT + virtual void print_name(outputStream* out) const { out->print("StoreFlattenedArrayStub"); } +#endif // PRODUCT +}; + +class SubstitutabilityCheckStub: public CodeStub { + private: + LIR_Opr _left; + LIR_Opr _right; + LIR_Opr _scratch_reg; + CodeEmitInfo* _info; + public: + SubstitutabilityCheckStub(LIR_Opr left, LIR_Opr right, CodeEmitInfo* info); + virtual void emit_code(LIR_Assembler* e); + virtual CodeEmitInfo* info() const { return _info; } + virtual void visit(LIR_OpVisitState* visitor) { + visitor->do_slow_case(_info); + visitor->do_input(_left); + visitor->do_input(_right); + if (_scratch_reg != LIR_OprFact::illegalOpr) { + visitor->do_temp(_scratch_reg); + } + } +#ifndef PRODUCT + virtual void print_name(outputStream* out) const { out->print("SubstitutabilityCheckStub"); } +#endif // PRODUCT +}; + class NewInstanceStub: public CodeStub { private: ciInstanceKlass* _klass; @@ -310,9 +387,9 @@ class NewObjectArrayStub: public CodeStub { LIR_Opr _length; LIR_Opr _result; CodeEmitInfo* _info; - + bool _is_null_free; public: - NewObjectArrayStub(LIR_Opr klass_reg, LIR_Opr length, LIR_Opr result, CodeEmitInfo* info); + NewObjectArrayStub(LIR_Opr klass_reg, LIR_Opr length, LIR_Opr result, CodeEmitInfo* info, bool is_null_free); virtual void emit_code(LIR_Assembler* e); virtual CodeEmitInfo* info() const { return _info; } virtual void visit(LIR_OpVisitState* visitor) { @@ -347,11 +424,19 @@ class MonitorAccessStub: public CodeStub { class MonitorEnterStub: public MonitorAccessStub { private: CodeEmitInfo* _info; + CodeStub* _throw_ie_stub; + LIR_Opr _scratch_reg; public: - MonitorEnterStub(LIR_Opr obj_reg, LIR_Opr lock_reg, CodeEmitInfo* info) + MonitorEnterStub(LIR_Opr obj_reg, LIR_Opr lock_reg, CodeEmitInfo* info, + CodeStub* throw_ie_stub = nullptr, LIR_Opr scratch_reg = LIR_OprFact::illegalOpr) : MonitorAccessStub(obj_reg, lock_reg) { _info = new CodeEmitInfo(info); + _scratch_reg = scratch_reg; + _throw_ie_stub = throw_ie_stub; + if (_throw_ie_stub != nullptr) { + assert(_scratch_reg != LIR_OprFact::illegalOpr, "must be"); + } FrameMap* f = Compilation::current()->frame_map(); f->update_reserved_argument_area_size(2 * BytesPerWord); } @@ -361,6 +446,9 @@ class MonitorEnterStub: public MonitorAccessStub { virtual void visit(LIR_OpVisitState* visitor) { visitor->do_input(_obj_reg); visitor->do_input(_lock_reg); + if (_scratch_reg != LIR_OprFact::illegalOpr) { + visitor->do_temp(_scratch_reg); + } visitor->do_slow_case(_info); } #ifndef PRODUCT diff --git a/src/hotspot/share/c1/c1_Compilation.cpp b/src/hotspot/share/c1/c1_Compilation.cpp index bb5ceb0106b..900de3b60d2 100644 --- a/src/hotspot/share/c1/c1_Compilation.cpp +++ b/src/hotspot/share/c1/c1_Compilation.cpp @@ -586,6 +586,7 @@ Compilation::Compilation(AbstractCompiler* compiler, ciEnv* env, ciMethod* metho , _code(buffer_blob) , _has_access_indexed(false) , _interpreter_frame_size(0) +, _compiled_entry_signature(method->get_Method()) , _immediate_oops_patched(0) , _current_instruction(nullptr) #ifndef PRODUCT @@ -603,9 +604,14 @@ Compilation::Compilation(AbstractCompiler* compiler, ciEnv* env, ciMethod* metho _cfg_printer_output = new CFGPrinterOutput(this); } #endif - CompilationMemoryStatisticMark cmsm(directive); + { + ResetNoHandleMark rnhm; // Huh? Required when doing class lookup of the Q-types + // TODO 8284443 Should only be computed once + _compiled_entry_signature.compute_calling_conventions(false); + } + compile_method(); if (bailed_out()) { _env->record_method_not_compilable(bailout_msg()); diff --git a/src/hotspot/share/c1/c1_Compilation.hpp b/src/hotspot/share/c1/c1_Compilation.hpp index 0c6b95e66c5..e7d9b5b8fef 100644 --- a/src/hotspot/share/c1/c1_Compilation.hpp +++ b/src/hotspot/share/c1/c1_Compilation.hpp @@ -32,6 +32,7 @@ #include "compiler/compilerDefinitions.inline.hpp" #include "compiler/compilerDirectives.hpp" #include "runtime/deoptimization.hpp" +#include "runtime/sharedRuntime.hpp" class CompilationFailureInfo; class CompilationResourceObj; @@ -95,6 +96,7 @@ class Compilation: public StackObj { CodeBuffer _code; bool _has_access_indexed; int _interpreter_frame_size; // Stack space needed in case of a deoptimization + CompiledEntrySignature _compiled_entry_signature; int _immediate_oops_patched; // compilation helpers @@ -257,6 +259,10 @@ class Compilation: public StackObj { return env()->comp_level() == CompLevel_full_profile && C1UpdateMethodData && MethodData::profile_return(); } + bool profile_array_accesses() { + return env()->comp_level() == CompLevel_full_profile && + C1UpdateMethodData; + } // will compilation make optimistic assumptions that might lead to // deoptimization and that the runtime will account for? @@ -282,6 +288,13 @@ class Compilation: public StackObj { int interpreter_frame_size() const { return _interpreter_frame_size; } + + const CompiledEntrySignature* compiled_entry_signature() const { + return &_compiled_entry_signature; + } + bool needs_stack_repair() const { + return compiled_entry_signature()->c1_needs_stack_repair(); + } }; diff --git a/src/hotspot/share/c1/c1_FrameMap.cpp b/src/hotspot/share/c1/c1_FrameMap.cpp index f42a9f7035b..a97c320bf96 100644 --- a/src/hotspot/share/c1/c1_FrameMap.cpp +++ b/src/hotspot/share/c1/c1_FrameMap.cpp @@ -186,14 +186,15 @@ FrameMap::FrameMap(ciMethod* method, int monitors, int reserved_argument_area_si } -bool FrameMap::finalize_frame(int nof_slots) { +bool FrameMap::finalize_frame(int nof_slots, bool needs_stack_repair) { assert(nof_slots >= 0, "must be positive"); assert(_num_spills == -1, "can only be set once"); _num_spills = nof_slots; assert(_framesize == -1, "should only be calculated once"); _framesize = align_up(in_bytes(sp_offset_for_monitor_base(0)) + _num_monitors * (int)sizeof(BasicObjectLock) + - (int)sizeof(intptr_t) + // offset of deopt orig pc + (int)sizeof(intptr_t) + // offset of deopt orig pc + (needs_stack_repair ? (int)sizeof(intptr_t) : 0) + // stack increment value frame_pad_in_bytes, StackAlignmentInBytes) / 4; int java_index = 0; diff --git a/src/hotspot/share/c1/c1_FrameMap.hpp b/src/hotspot/share/c1/c1_FrameMap.hpp index f10c4d3f226..e5adee681b3 100644 --- a/src/hotspot/share/c1/c1_FrameMap.hpp +++ b/src/hotspot/share/c1/c1_FrameMap.hpp @@ -184,7 +184,7 @@ class FrameMap : public CompilationResourceObj { } FrameMap(ciMethod* method, int monitors, int reserved_argument_area_size); - bool finalize_frame(int nof_slots); + bool finalize_frame(int nof_slots, bool needs_stack_repair); int reserved_argument_area_size () const { return _reserved_argument_area_size; } int framesize () const { assert(_framesize != -1, "hasn't been calculated"); return _framesize; } @@ -210,6 +210,9 @@ class FrameMap : public CompilationResourceObj { Address address_for_monitor_object(int monitor_index) const { return make_new_address(sp_offset_for_monitor_object(monitor_index)); } + Address address_for_orig_pc_addr() const { + return make_new_address(sp_offset_for_monitor_base(_num_monitors)); + } // Creates Location describing desired slot and returns it via pointer // to Location object. Returns true if the stack frame offset was legal diff --git a/src/hotspot/share/c1/c1_GraphBuilder.cpp b/src/hotspot/share/c1/c1_GraphBuilder.cpp index f910ecadc16..258e95a0fa3 100644 --- a/src/hotspot/share/c1/c1_GraphBuilder.cpp +++ b/src/hotspot/share/c1/c1_GraphBuilder.cpp @@ -29,6 +29,8 @@ #include "c1/c1_InstructionPrinter.hpp" #include "ci/ciCallSite.hpp" #include "ci/ciField.hpp" +#include "ci/ciFlatArrayKlass.hpp" +#include "ci/ciInlineKlass.hpp" #include "ci/ciKlass.hpp" #include "ci/ciMemberName.hpp" #include "ci/ciSymbols.hpp" @@ -1047,7 +1049,15 @@ void GraphBuilder::store_local(ValueStack* state, Value x, int index) { void GraphBuilder::load_indexed(BasicType type) { // In case of in block code motion in range check elimination - ValueStack* state_before = copy_state_indexed_access(); + ValueStack* state_before = nullptr; + int array_idx = state()->stack_size() - 2; + if (type == T_OBJECT && state()->stack_at(array_idx)->maybe_flat_array()) { + // Save the entire state and re-execute on deopt when accessing flat arrays + state_before = copy_state_before(); + state_before->set_should_reexecute(true); + } else { + state_before = copy_state_indexed_access(); + } compilation()->set_has_access_indexed(true); Value index = ipop(); Value array = apop(); @@ -1059,13 +1069,76 @@ void GraphBuilder::load_indexed(BasicType type) { (array->as_NewMultiArray() && array->as_NewMultiArray()->dims()->at(0)->type()->is_constant())) { length = append(new ArrayLength(array, state_before)); } - push(as_ValueType(type), append(new LoadIndexed(array, index, length, type, state_before))); + + bool need_membar = false; + LoadIndexed* load_indexed = nullptr; + Instruction* result = nullptr; + if (array->is_loaded_flat_array()) { + // TODO 8350865 This is currently dead code. Can we use set_null_free on the result here if the array is null-free? + ciType* array_type = array->declared_type(); + ciInlineKlass* elem_klass = array_type->as_flat_array_klass()->element_klass()->as_inline_klass(); + + bool can_delay_access = false; + ciBytecodeStream s(method()); + s.force_bci(bci()); + s.next(); + if (s.cur_bc() == Bytecodes::_getfield) { + bool will_link; + ciField* next_field = s.get_field(will_link); + bool next_needs_patching = !next_field->holder()->is_initialized() || + !next_field->will_link(method(), Bytecodes::_getfield) || + PatchALot; + can_delay_access = C1UseDelayedFlattenedFieldReads && !next_needs_patching; + } + if (can_delay_access) { + // potentially optimizable array access, storing information for delayed decision + LoadIndexed* li = new LoadIndexed(array, index, length, type, state_before); + DelayedLoadIndexed* dli = new DelayedLoadIndexed(li, state_before); + li->set_delayed(dli); + set_pending_load_indexed(dli); + return; // Nothing else to do for now + } else { + NewInstance* new_instance = new NewInstance(elem_klass, state_before, false, true); + _memory->new_instance(new_instance); + apush(append_split(new_instance)); + load_indexed = new LoadIndexed(array, index, length, type, state_before); + load_indexed->set_vt(new_instance); + // The LoadIndexed node will initialise this instance by copying from + // the flat field. Ensure these stores are visible before any + // subsequent store that publishes this reference. + need_membar = true; + } + } else { + load_indexed = new LoadIndexed(array, index, length, type, state_before); + if (profile_array_accesses() && is_reference_type(type)) { + compilation()->set_would_profile(true); + load_indexed->set_should_profile(true); + load_indexed->set_profiled_method(method()); + load_indexed->set_profiled_bci(bci()); + } + } + result = append(load_indexed); + if (need_membar) { + append(new MemBar(lir_membar_storestore)); + } + assert(!load_indexed->should_profile() || load_indexed == result, "should not be optimized out"); + if (!array->is_loaded_flat_array()) { + push(as_ValueType(type), result); + } } void GraphBuilder::store_indexed(BasicType type) { // In case of in block code motion in range check elimination - ValueStack* state_before = copy_state_indexed_access(); + ValueStack* state_before = nullptr; + int array_idx = state()->stack_size() - 3; + if (type == T_OBJECT && state()->stack_at(array_idx)->maybe_flat_array()) { + // Save the entire state and re-execute on deopt when accessing flat arrays + state_before = copy_state_before(); + state_before->set_should_reexecute(true); + } else { + state_before = copy_state_indexed_access(); + } compilation()->set_has_access_indexed(true); Value value = pop(as_ValueType(type)); Value index = ipop(); @@ -1090,32 +1163,28 @@ void GraphBuilder::store_indexed(BasicType type) { } else if (type == T_BYTE) { check_boolean = true; } - StoreIndexed* result = new StoreIndexed(array, index, length, type, value, state_before, check_boolean); - append(result); - _memory->store_value(value); - if (type == T_OBJECT && is_profiling()) { - // Note that we'd collect profile data in this method if we wanted it. + StoreIndexed* store_indexed = new StoreIndexed(array, index, length, type, value, state_before, check_boolean); + if (profile_array_accesses() && is_reference_type(type) && !array->is_loaded_flat_array()) { compilation()->set_would_profile(true); - - if (profile_checkcasts()) { - result->set_profiled_method(method()); - result->set_profiled_bci(bci()); - result->set_should_profile(true); - } + store_indexed->set_should_profile(true); + store_indexed->set_profiled_method(method()); + store_indexed->set_profiled_bci(bci()); } + Instruction* result = append(store_indexed); + assert(!store_indexed->should_profile() || store_indexed == result, "should not be optimized out"); + _memory->store_value(value); } - void GraphBuilder::stack_op(Bytecodes::Code code) { switch (code) { case Bytecodes::_pop: - { state()->raw_pop(); + { Value w = state()->raw_pop(); } break; case Bytecodes::_pop2: - { state()->raw_pop(); - state()->raw_pop(); + { Value w1 = state()->raw_pop(); + Value w2 = state()->raw_pop(); } break; case Bytecodes::_dup: @@ -1291,9 +1360,36 @@ void GraphBuilder::if_node(Value x, If::Condition cond, Value y, ValueStack* sta BlockBegin* tsux = block_at(stream()->get_dest()); BlockBegin* fsux = block_at(stream()->next_bci()); bool is_bb = tsux->bci() < stream()->cur_bci() || fsux->bci() < stream()->cur_bci(); + + bool subst_check = false; + if (EnableValhalla && (stream()->cur_bc() == Bytecodes::_if_acmpeq || stream()->cur_bc() == Bytecodes::_if_acmpne)) { + ValueType* left_vt = x->type(); + ValueType* right_vt = y->type(); + if (left_vt->is_object()) { + assert(right_vt->is_object(), "must be"); + ciKlass* left_klass = x->as_loaded_klass_or_null(); + ciKlass* right_klass = y->as_loaded_klass_or_null(); + + if (left_klass == nullptr || right_klass == nullptr) { + // The klass is still unloaded, or came from a Phi node. Go slow case; + subst_check = true; + } else if (left_klass->can_be_inline_klass() || right_klass->can_be_inline_klass()) { + // Either operand may be a value object, but we're not sure. Go slow case; + subst_check = true; + } else { + // No need to do substitutability check + } + } + } + if ((stream()->cur_bc() == Bytecodes::_if_acmpeq || stream()->cur_bc() == Bytecodes::_if_acmpne) && + is_profiling() && profile_branches()) { + compilation()->set_would_profile(true); + append(new ProfileACmpTypes(method(), bci(), x, y)); + } + // In case of loop invariant code motion or predicate insertion // before the body of a loop the state is needed - Instruction *i = append(new If(x, cond, false, y, tsux, fsux, (is_bb || compilation()->is_optimistic()) ? state_before : nullptr, is_bb)); + Instruction *i = append(new If(x, cond, false, y, tsux, fsux, (is_bb || compilation()->is_optimistic() || subst_check) ? state_before : nullptr, is_bb, subst_check)); assert(i->as_Goto() == nullptr || (i->as_Goto()->sux_at(0) == tsux && i->as_Goto()->is_safepoint() == (tsux->bci() < stream()->cur_bci())) || @@ -1548,7 +1644,7 @@ void GraphBuilder::method_return(Value x, bool ignore_return) { // The conditions for a memory barrier are described in Parse::do_exits(). bool need_mem_bar = false; - if (method()->name() == ciSymbols::object_initializer_name() && + if (method()->is_object_constructor() && (scope()->wrote_final() || scope()->wrote_stable() || (AlwaysSafeConstructors && scope()->wrote_fields()) || (support_IRIW_for_not_multiple_copy_atomic_cpu && scope()->wrote_volatile()))) { @@ -1699,16 +1795,40 @@ Value GraphBuilder::make_constant(ciConstant field_value, ciField* field) { } } +void GraphBuilder::copy_inline_content(ciInlineKlass* vk, Value src, int src_off, Value dest, int dest_off, ValueStack* state_before, ciField* enclosing_field) { + for (int i = 0; i < vk->nof_declared_nonstatic_fields(); i++) { + ciField* field = vk->declared_nonstatic_field_at(i); + int offset = field->offset_in_bytes() - vk->payload_offset(); + if (field->is_flat()) { + copy_inline_content(field->type()->as_inline_klass(), src, src_off + offset, dest, dest_off + offset, state_before, enclosing_field); + if (!field->is_null_free()) { + // Nullable, copy the null marker using Unsafe because null markers are no real fields + int null_marker_offset = field->null_marker_offset() - vk->payload_offset(); + Value offset = append(new Constant(new LongConstant(src_off + null_marker_offset))); + Value nm = append(new UnsafeGet(T_BOOLEAN, src, offset, false)); + offset = append(new Constant(new LongConstant(dest_off + null_marker_offset))); + append(new UnsafePut(T_BOOLEAN, dest, offset, nm, false)); + } + } else { + Value value = append(new LoadField(src, src_off + offset, field, false, state_before, false)); + StoreField* store = new StoreField(dest, dest_off + offset, field, value, false, state_before, false); + store->set_enclosing_field(enclosing_field); + append(store); + } + } +} + void GraphBuilder::access_field(Bytecodes::Code code) { bool will_link; ciField* field = stream()->get_field(will_link); ciInstanceKlass* holder = field->holder(); BasicType field_type = field->type()->basic_type(); ValueType* type = as_ValueType(field_type); + // call will_link again to determine if the field is valid. const bool needs_patching = !holder->is_loaded() || !field->will_link(method(), code) || - PatchALot; + (!field->is_flat() && PatchALot); ValueStack* state_before = nullptr; if (!holder->is_initialized() || needs_patching) { @@ -1740,7 +1860,7 @@ void GraphBuilder::access_field(Bytecodes::Code code) { } } - const int offset = !needs_patching ? field->offset_in_bytes() : -1; + int offset = !needs_patching ? field->offset_in_bytes() : -1; switch (code) { case Bytecodes::_getstatic: { // check for compile-time constants, i.e., initialized static final fields @@ -1757,8 +1877,9 @@ void GraphBuilder::access_field(Bytecodes::Code code) { if (state_before == nullptr) { state_before = copy_state_for_exception(); } - push(type, append(new LoadField(append(obj), offset, field, true, - state_before, needs_patching))); + LoadField* load_field = new LoadField(append(obj), offset, field, true, + state_before, needs_patching); + push(type, append(load_field)); } break; } @@ -1767,30 +1888,44 @@ void GraphBuilder::access_field(Bytecodes::Code code) { if (state_before == nullptr) { state_before = copy_state_for_exception(); } - if (field->type()->basic_type() == T_BOOLEAN) { + if (field_type == T_BOOLEAN) { Value mask = append(new Constant(new IntConstant(1))); val = append(new LogicOp(Bytecodes::_iand, val, mask)); } + if (field->is_null_free()) { + null_check(val); + } + if (field->is_null_free() && field->type()->is_loaded() && field->type()->as_inline_klass()->is_empty() && (!method()->is_class_initializer() || field->is_flat())) { + // Storing to a field of an empty, null-free inline type that is already initialized. Ignore. + break; + } append(new StoreField(append(obj), offset, field, val, true, state_before, needs_patching)); break; } case Bytecodes::_getfield: { // Check for compile-time constants, i.e., trusted final non-static fields. Value constant = nullptr; - obj = apop(); - ObjectType* obj_type = obj->type()->as_ObjectType(); - if (field->is_constant() && obj_type->is_constant() && !PatchALot) { - ciObject* const_oop = obj_type->constant_value(); - if (!const_oop->is_null_object() && const_oop->is_loaded()) { - ciConstant field_value = field->constant_value_of(const_oop); - if (field_value.is_valid()) { - constant = make_constant(field_value, field); - // For CallSite objects add a dependency for invalidation of the optimization. - if (field->is_call_site_target()) { - ciCallSite* call_site = const_oop->as_call_site(); - if (!call_site->is_fully_initialized_constant_call_site()) { - ciMethodHandle* target = field_value.as_object()->as_method_handle(); - dependency_recorder()->assert_call_site_target_value(call_site, target); + if (state_before == nullptr && field->is_flat()) { + // Save the entire state and re-execute on deopt when accessing flat fields + assert(Interpreter::bytecode_should_reexecute(code), "should reexecute"); + state_before = copy_state_before(); + } + if (!has_pending_field_access() && !has_pending_load_indexed()) { + obj = apop(); + ObjectType* obj_type = obj->type()->as_ObjectType(); + if (field->is_constant() && !field->is_flat() && obj_type->is_constant() && !PatchALot) { + ciObject* const_oop = obj_type->constant_value(); + if (!const_oop->is_null_object() && const_oop->is_loaded()) { + ciConstant field_value = field->constant_value_of(const_oop); + if (field_value.is_valid()) { + constant = make_constant(field_value, field); + // For CallSite objects add a dependency for invalidation of the optimization. + if (field->is_call_site_target()) { + ciCallSite* call_site = const_oop->as_call_site(); + if (!call_site->is_fully_initialized_constant_call_site()) { + ciMethodHandle* target = field_value.as_object()->as_method_handle(); + dependency_recorder()->assert_call_site_target_value(call_site, target); + } } } } @@ -1802,30 +1937,126 @@ void GraphBuilder::access_field(Bytecodes::Code code) { if (state_before == nullptr) { state_before = copy_state_for_exception(); } - LoadField* load = new LoadField(obj, offset, field, false, state_before, needs_patching); - Value replacement = !needs_patching ? _memory->load(load) : load; - if (replacement != load) { - assert(replacement->is_linked() || !replacement->can_be_linked(), "should already by linked"); - // Writing an (integer) value to a boolean, byte, char or short field includes an implicit narrowing - // conversion. Emit an explicit conversion here to get the correct field value after the write. - BasicType bt = field->type()->basic_type(); - switch (bt) { - case T_BOOLEAN: - case T_BYTE: - replacement = append(new Convert(Bytecodes::_i2b, replacement, as_ValueType(bt))); - break; - case T_CHAR: - replacement = append(new Convert(Bytecodes::_i2c, replacement, as_ValueType(bt))); - break; - case T_SHORT: - replacement = append(new Convert(Bytecodes::_i2s, replacement, as_ValueType(bt))); - break; - default: + if (!field->is_flat()) { + if (has_pending_field_access()) { + assert(!needs_patching, "Can't patch delayed field access"); + obj = pending_field_access()->obj(); + offset += pending_field_access()->offset() - field->holder()->as_inline_klass()->payload_offset(); + field = pending_field_access()->holder()->get_field_by_offset(offset, false); + assert(field != nullptr, "field not found"); + set_pending_field_access(nullptr); + } else if (has_pending_load_indexed()) { + assert(!needs_patching, "Can't patch delayed field access"); + pending_load_indexed()->update(field, offset - field->holder()->as_inline_klass()->payload_offset()); + LoadIndexed* li = pending_load_indexed()->load_instr(); + li->set_type(type); + push(type, append(li)); + set_pending_load_indexed(nullptr); break; } - push(type, replacement); + LoadField* load = new LoadField(obj, offset, field, false, state_before, needs_patching); + Value replacement = !needs_patching ? _memory->load(load) : load; + if (replacement != load) { + assert(replacement->is_linked() || !replacement->can_be_linked(), "should already by linked"); + // Writing an (integer) value to a boolean, byte, char or short field includes an implicit narrowing + // conversion. Emit an explicit conversion here to get the correct field value after the write. + switch (field_type) { + case T_BOOLEAN: + case T_BYTE: + replacement = append(new Convert(Bytecodes::_i2b, replacement, type)); + break; + case T_CHAR: + replacement = append(new Convert(Bytecodes::_i2c, replacement, type)); + break; + case T_SHORT: + replacement = append(new Convert(Bytecodes::_i2s, replacement, type)); + break; + default: + break; + } + push(type, replacement); + } else { + push(type, append(load)); + } } else { - push(type, append(load)); + // Flat field + assert(!needs_patching, "Can't patch flat inline type field access"); + ciInlineKlass* inline_klass = field->type()->as_inline_klass(); + bool is_naturally_atomic = inline_klass->nof_declared_nonstatic_fields() <= 1; + bool needs_atomic_access = !field->is_null_free() || (field->is_volatile() && !is_naturally_atomic); + if (needs_atomic_access) { + assert(!has_pending_field_access(), "Pending field accesses are not supported"); + LoadField* load = new LoadField(obj, offset, field, false, state_before, needs_patching); + push(type, append(load)); + } else { + assert(field->is_null_free(), "must be null-free"); + // Look at the next bytecode to check if we can delay the field access + bool can_delay_access = false; + ciBytecodeStream s(method()); + s.force_bci(bci()); + s.next(); + if (s.cur_bc() == Bytecodes::_getfield && !needs_patching) { + ciField* next_field = s.get_field(will_link); + bool next_needs_patching = !next_field->holder()->is_loaded() || + !next_field->will_link(method(), Bytecodes::_getfield) || + PatchALot; + // We can't update the offset for atomic accesses + bool next_needs_atomic_access = !next_field->is_null_free() || next_field->is_volatile(); + can_delay_access = C1UseDelayedFlattenedFieldReads && !next_needs_patching && !next_needs_atomic_access; + } + if (can_delay_access) { + if (has_pending_load_indexed()) { + pending_load_indexed()->update(field, offset - field->holder()->as_inline_klass()->payload_offset()); + } else if (has_pending_field_access()) { + pending_field_access()->inc_offset(offset - field->holder()->as_inline_klass()->payload_offset()); + } else { + null_check(obj); + DelayedFieldAccess* dfa = new DelayedFieldAccess(obj, field->holder(), field->offset_in_bytes(), state_before); + set_pending_field_access(dfa); + } + } else { + scope()->set_wrote_final(); + scope()->set_wrote_fields(); + bool need_membar = false; + if (has_pending_load_indexed()) { + assert(!needs_patching, "Can't patch delayed field access"); + pending_load_indexed()->update(field, offset - field->holder()->as_inline_klass()->payload_offset()); + NewInstance* vt = new NewInstance(inline_klass, pending_load_indexed()->state_before(), false, true); + _memory->new_instance(vt); + pending_load_indexed()->load_instr()->set_vt(vt); + apush(append_split(vt)); + append(pending_load_indexed()->load_instr()); + set_pending_load_indexed(nullptr); + need_membar = true; + } else { + if (has_pending_field_access()) { + state_before = pending_field_access()->state_before(); + } + NewInstance* new_instance = new NewInstance(inline_klass, state_before, false, true); + _memory->new_instance(new_instance); + apush(append_split(new_instance)); + if (has_pending_field_access()) { + copy_inline_content(inline_klass, pending_field_access()->obj(), + pending_field_access()->offset() + field->offset_in_bytes() - field->holder()->as_inline_klass()->payload_offset(), + new_instance, inline_klass->payload_offset(), state_before); + set_pending_field_access(nullptr); + } else { + if (field->type()->as_instance_klass()->is_initialized() && field->type()->as_inline_klass()->is_empty()) { + // Needs an explicit null check because below code does not perform any actual load if there are no fields + null_check(obj); + } + copy_inline_content(inline_klass, obj, field->offset_in_bytes(), new_instance, inline_klass->payload_offset(), state_before); + } + need_membar = true; + } + if (need_membar) { + // If we allocated a new instance ensure the stores to copy the + // field contents are visible before any subsequent store that + // publishes this reference. + append(new MemBar(lir_membar_storestore)); + } + } + } } } break; @@ -1836,14 +2067,39 @@ void GraphBuilder::access_field(Bytecodes::Code code) { if (state_before == nullptr) { state_before = copy_state_for_exception(); } - if (field->type()->basic_type() == T_BOOLEAN) { + if (field_type == T_BOOLEAN) { Value mask = append(new Constant(new IntConstant(1))); val = append(new LogicOp(Bytecodes::_iand, val, mask)); } - StoreField* store = new StoreField(obj, offset, field, val, false, state_before, needs_patching); - if (!needs_patching) store = _memory->store(store); - if (store != nullptr) { - append(store); + + if (field->is_null_free() && field->type()->is_loaded() && field->type()->as_inline_klass()->is_empty() && (!method()->is_object_constructor() || field->is_flat())) { + // Storing to a field of an empty, null-free inline type that is already initialized. Ignore. + null_check(obj); + null_check(val); + } else if (!field->is_flat()) { + if (field->is_null_free()) { + null_check(val); + } + StoreField* store = new StoreField(obj, offset, field, val, false, state_before, needs_patching); + if (!needs_patching) store = _memory->store(store); + if (store != nullptr) { + append(store); + } + } else { + // Flat field + assert(!needs_patching, "Can't patch flat inline type field access"); + ciInlineKlass* inline_klass = field->type()->as_inline_klass(); + bool is_naturally_atomic = inline_klass->nof_declared_nonstatic_fields() <= 1; + bool needs_atomic_access = !field->is_null_free() || (field->is_volatile() && !is_naturally_atomic); + if (needs_atomic_access) { + if (field->is_null_free()) { + null_check(val); + } + append(new StoreField(obj, offset, field, val, false, state_before, needs_patching)); + } else { + assert(field->is_null_free(), "must be null-free"); + copy_inline_content(inline_klass, val, inline_klass->payload_offset(), obj, offset, state_before, field); + } } break; } @@ -1853,7 +2109,6 @@ void GraphBuilder::access_field(Bytecodes::Code code) { } } - Dependencies* GraphBuilder::dependency_recorder() const { return compilation()->dependency_recorder(); } @@ -1969,7 +2224,7 @@ void GraphBuilder::invoke(Bytecodes::Code code) { if (bc_raw == Bytecodes::_invokeinterface) { receiver_constraint = holder; - } else if (bc_raw == Bytecodes::_invokespecial && !target->is_object_initializer() && calling_klass->is_interface()) { + } else if (bc_raw == Bytecodes::_invokespecial && !target->is_object_constructor() && calling_klass->is_interface()) { receiver_constraint = calling_klass; } @@ -2225,12 +2480,11 @@ void GraphBuilder::new_instance(int klass_index) { ValueStack* state_before = copy_state_exhandling(); ciKlass* klass = stream()->get_klass(); assert(klass->is_instance_klass(), "must be an instance klass"); - NewInstance* new_instance = new NewInstance(klass->as_instance_klass(), state_before, stream()->is_unresolved_klass()); + NewInstance* new_instance = new NewInstance(klass->as_instance_klass(), state_before, stream()->is_unresolved_klass(), false); _memory->new_instance(new_instance); apush(append_split(new_instance)); } - void GraphBuilder::new_type_array() { ValueStack* state_before = copy_state_exhandling(); apush(append_split(new NewTypeArray(ipop(), (BasicType)stream()->get_index(), state_before, true))); @@ -2303,9 +2557,28 @@ void GraphBuilder::instance_of(int klass_index) { void GraphBuilder::monitorenter(Value x, int bci) { + bool maybe_inlinetype = false; + if (bci == InvocationEntryBci) { + // Called by GraphBuilder::inline_sync_entry. +#ifdef ASSERT + ciType* obj_type = x->declared_type(); + assert(obj_type == nullptr || !obj_type->is_inlinetype(), "inline types cannot have synchronized methods"); +#endif + } else { + // We are compiling a monitorenter bytecode + if (EnableValhalla) { + ciType* obj_type = x->declared_type(); + if (obj_type == nullptr || obj_type->as_klass()->can_be_inline_klass()) { + // If we're (possibly) locking on an inline type, check for markWord::always_locked_pattern + // and throw IMSE. (obj_type is null for Phi nodes, so let's just be conservative). + maybe_inlinetype = true; + } + } + } + // save state before locking in case of deoptimization after a NullPointerException ValueStack* state_before = copy_state_for_exception_with_bci(bci); - append_with_bci(new MonitorEnter(x, state()->lock(x), state_before), bci); + append_with_bci(new MonitorEnter(x, state()->lock(x), state_before, maybe_inlinetype), bci); kill_all(); } @@ -2430,6 +2703,7 @@ void GraphBuilder::null_check(Value value) { } } } + if (value->is_null_free()) return; } append(new NullCheck(value, copy_state_for_exception())); } @@ -2455,7 +2729,9 @@ XHandlers* GraphBuilder::handle_exception(Instruction* instruction) { do { int cur_bci = cur_state->bci(); assert(cur_scope_data->scope() == cur_state->scope(), "scopes do not match"); - assert(cur_bci == SynchronizationEntryBCI || cur_bci == cur_scope_data->stream()->cur_bci(), "invalid bci"); + assert(cur_bci == SynchronizationEntryBCI || cur_bci == cur_scope_data->stream()->cur_bci() + || has_pending_field_access() || has_pending_load_indexed(), "invalid bci"); + // join with all potential exception handlers XHandlers* list = cur_scope_data->xhandlers(); @@ -3269,6 +3545,8 @@ GraphBuilder::GraphBuilder(Compilation* compilation, IRScope* scope) , _inline_bailout_msg(nullptr) , _instruction_count(0) , _osr_entry(nullptr) + , _pending_field_access(nullptr) + , _pending_load_indexed(nullptr) { int osr_bci = compilation->osr_bci(); @@ -4021,6 +4299,34 @@ bool GraphBuilder::try_inline_full(ciMethod* callee, bool holder_known, bool ign caller_state->truncate_stack(args_base); assert(callee_state->stack_size() == 0, "callee stack must be empty"); + // Check if we need a membar at the beginning of the java.lang.Object + // constructor to satisfy the memory model for strict fields. + if (EnableValhalla && method()->intrinsic_id() == vmIntrinsics::_Object_init) { + Value receiver = state()->local_at(0); + ciType* klass = receiver->exact_type(); + if (klass == nullptr) { + // No exact type, check if the declared type has no implementors and add a dependency + klass = receiver->declared_type(); + klass = compilation()->cha_exact_type(klass); + } + if (klass != nullptr && klass->is_instance_klass()) { + // Exact receiver type, check if there is a strict field + ciInstanceKlass* holder = klass->as_instance_klass(); + for (int i = 0; i < holder->nof_nonstatic_fields(); i++) { + ciField* field = holder->nonstatic_field_at(i); + if (field->is_strict()) { + // Found a strict field, a membar is needed + append(new MemBar(lir_membar_storestore)); + break; + } + } + } else if (klass == nullptr) { + // We can't statically determine the type of the receiver and therefore need + // to put a membar here because it could have a strict field. + append(new MemBar(lir_membar_storestore)); + } + } + Value lock = nullptr; BlockBegin* sync_handler = nullptr; diff --git a/src/hotspot/share/c1/c1_GraphBuilder.hpp b/src/hotspot/share/c1/c1_GraphBuilder.hpp index 6eaeda5adcf..37c4b228a78 100644 --- a/src/hotspot/share/c1/c1_GraphBuilder.hpp +++ b/src/hotspot/share/c1/c1_GraphBuilder.hpp @@ -35,6 +35,24 @@ class MemoryBuffer; +class DelayedFieldAccess : public CompilationResourceObj { +private: + Value _obj; + ciInstanceKlass* _holder; + int _offset; + ValueStack* _state_before; + +public: + DelayedFieldAccess(Value obj, ciInstanceKlass* holder, int offset, ValueStack* state_before) + : _obj(obj), _holder(holder) , _offset(offset), _state_before(state_before) { } + + Value obj() const { return _obj; } + ciInstanceKlass* holder() const { return _holder; } + int offset() const { return _offset; } + void inc_offset(int offset) { _offset += offset; } + ValueStack* state_before() const { return _state_before; } +}; + class GraphBuilder { friend class JfrResolution; private: @@ -192,6 +210,10 @@ class GraphBuilder { Instruction* _last; // the last instruction added bool _skip_block; // skip processing of the rest of this block + // support for optimization of accesses to flat fields and flat arrays + DelayedFieldAccess* _pending_field_access; + DelayedLoadIndexed* _pending_load_indexed; + // accessors ScopeData* scope_data() const { return _scope_data; } Compilation* compilation() const { return _compilation; } @@ -209,6 +231,12 @@ class GraphBuilder { Bytecodes::Code code() const { return stream()->cur_bc(); } int bci() const { return stream()->cur_bci(); } int next_bci() const { return stream()->next_bci(); } + bool has_pending_field_access() { return _pending_field_access != nullptr; } + DelayedFieldAccess* pending_field_access() { return _pending_field_access; } + void set_pending_field_access(DelayedFieldAccess* delayed) { _pending_field_access = delayed; } + bool has_pending_load_indexed() { return _pending_load_indexed != nullptr; } + DelayedLoadIndexed* pending_load_indexed() { return _pending_load_indexed; } + void set_pending_load_indexed(DelayedLoadIndexed* delayed) { _pending_load_indexed = delayed; } // unified bailout support void bailout(const char* msg) const { compilation()->bailout(msg); } @@ -267,6 +295,9 @@ class GraphBuilder { void new_multi_array(int dimensions); void throw_op(int bci); + // inline types + void copy_inline_content(ciInlineKlass* vk, Value src, int src_off, Value dest, int dest_off, ValueStack* state_before, ciField* encloding_field = nullptr); + // stack/code manipulation helpers Instruction* append_with_bci(Instruction* instr, int bci); Instruction* append(Instruction* instr); @@ -395,6 +426,7 @@ class GraphBuilder { bool profile_parameters() { return _compilation->profile_parameters(); } bool profile_arguments() { return _compilation->profile_arguments(); } bool profile_return() { return _compilation->profile_return(); } + bool profile_array_accesses(){ return _compilation->profile_array_accesses();} Values* args_list_for_profiling(ciMethod* target, int& start, bool may_have_receiver); Values* collect_args_for_profiling(Values* args, ciMethod* target, bool may_have_receiver); diff --git a/src/hotspot/share/c1/c1_IR.cpp b/src/hotspot/share/c1/c1_IR.cpp index ae8332116b3..39b5777ae8f 100644 --- a/src/hotspot/share/c1/c1_IR.cpp +++ b/src/hotspot/share/c1/c1_IR.cpp @@ -171,6 +171,9 @@ int IRScope::max_stack() const { bool IRScopeDebugInfo::should_reexecute() { + if (_should_reexecute) { + return true; + } ciMethod* cur_method = scope()->method(); int cur_bci = bci(); if (cur_method != nullptr && cur_bci != SynchronizationEntryBCI) { @@ -180,7 +183,6 @@ bool IRScopeDebugInfo::should_reexecute() { return false; } - // Implementation of CodeEmitInfo // Stack must be NON-null @@ -214,11 +216,11 @@ CodeEmitInfo::CodeEmitInfo(CodeEmitInfo* info, ValueStack* stack) } -void CodeEmitInfo::record_debug_info(DebugInformationRecorder* recorder, int pc_offset) { +void CodeEmitInfo::record_debug_info(DebugInformationRecorder* recorder, int pc_offset, bool maybe_return_as_fields) { // record the safepoint before recording the debug info for enclosing scopes recorder->add_safepoint(pc_offset, _oop_map->deep_copy()); bool reexecute = _force_reexecute || _scope_debug_info->should_reexecute(); - _scope_debug_info->record_debug_info(recorder, pc_offset, reexecute, _is_method_handle_invoke); + _scope_debug_info->record_debug_info(recorder, pc_offset, reexecute, _is_method_handle_invoke, maybe_return_as_fields); recorder->end_safepoint(pc_offset); } diff --git a/src/hotspot/share/c1/c1_IR.hpp b/src/hotspot/share/c1/c1_IR.hpp index a9a7a026390..aff708a7f33 100644 --- a/src/hotspot/share/c1/c1_IR.hpp +++ b/src/hotspot/share/c1/c1_IR.hpp @@ -208,6 +208,7 @@ class IRScopeDebugInfo: public CompilationResourceObj { GrowableArray* _expressions; GrowableArray* _monitors; IRScopeDebugInfo* _caller; + bool _should_reexecute; public: IRScopeDebugInfo(IRScope* scope, @@ -215,13 +216,15 @@ class IRScopeDebugInfo: public CompilationResourceObj { GrowableArray* locals, GrowableArray* expressions, GrowableArray* monitors, - IRScopeDebugInfo* caller): + IRScopeDebugInfo* caller, + bool should_reexecute): _scope(scope) , _bci(bci) , _locals(locals) , _expressions(expressions) , _monitors(monitors) - , _caller(caller) {} + , _caller(caller) + , _should_reexecute(should_reexecute) {} IRScope* scope() { return _scope; } @@ -234,7 +237,7 @@ class IRScopeDebugInfo: public CompilationResourceObj { //Whether we should reexecute this bytecode for deopt bool should_reexecute(); - void record_debug_info(DebugInformationRecorder* recorder, int pc_offset, bool reexecute, bool is_method_handle_invoke = false) { + void record_debug_info(DebugInformationRecorder* recorder, int pc_offset, bool reexecute, bool is_method_handle_invoke = false, bool maybe_return_as_fields = false) { if (caller() != nullptr) { // Order is significant: Must record caller first. caller()->record_debug_info(recorder, pc_offset, false/*reexecute*/); @@ -243,12 +246,17 @@ class IRScopeDebugInfo: public CompilationResourceObj { DebugToken* expvals = recorder->create_scope_values(expressions()); DebugToken* monvals = recorder->create_monitor_values(monitors()); // reexecute allowed only for the topmost frame - bool return_oop = false; // This flag will be ignored since it used only for C2 with escape analysis. + bool return_oop = false; + bool return_scalarized = false; + if (maybe_return_as_fields) { + return_oop = true; + return_scalarized = true; + } bool rethrow_exception = false; bool has_ea_local_in_scope = false; bool arg_escape = false; recorder->describe_scope(pc_offset, methodHandle(), scope()->method(), bci(), - reexecute, rethrow_exception, is_method_handle_invoke, return_oop, + reexecute, rethrow_exception, is_method_handle_invoke, return_oop, return_scalarized, has_ea_local_in_scope, arg_escape, locvals, expvals, monvals); } }; @@ -286,7 +294,7 @@ class CodeEmitInfo: public CompilationResourceObj { bool deoptimize_on_exception() const { return _deoptimize_on_exception; } void add_register_oop(LIR_Opr opr); - void record_debug_info(DebugInformationRecorder* recorder, int pc_offset); + void record_debug_info(DebugInformationRecorder* recorder, int pc_offset, bool maybe_return_as_fields = false); bool is_method_handle_invoke() const { return _is_method_handle_invoke; } void set_is_method_handle_invoke(bool x) { _is_method_handle_invoke = x; } diff --git a/src/hotspot/share/c1/c1_Instruction.cpp b/src/hotspot/share/c1/c1_Instruction.cpp index 3a7edef0088..8c7b199b8a2 100644 --- a/src/hotspot/share/c1/c1_Instruction.cpp +++ b/src/hotspot/share/c1/c1_Instruction.cpp @@ -26,6 +26,8 @@ #include "c1/c1_InstructionPrinter.hpp" #include "c1/c1_IR.hpp" #include "c1/c1_ValueStack.hpp" +#include "ci/ciFlatArrayKlass.hpp" +#include "ci/ciInlineKlass.hpp" #include "ci/ciObjArrayKlass.hpp" #include "ci/ciTypeArrayKlass.hpp" #include "utilities/bitMap.inline.hpp" @@ -105,13 +107,73 @@ void Instruction::state_values_do(ValueVisitor* f) { } ciType* Instruction::exact_type() const { - ciType* t = declared_type(); + ciType* t = declared_type(); if (t != nullptr && t->is_klass()) { return t->as_klass()->exact_klass(); } return nullptr; } +ciKlass* Instruction::as_loaded_klass_or_null() const { + ciType* type = declared_type(); + if (type != nullptr && type->is_klass()) { + ciKlass* klass = type->as_klass(); + if (klass->is_loaded()) { + return klass; + } + } + return nullptr; +} + +bool Instruction::is_loaded_flat_array() const { + if (UseArrayFlattening) { + ciType* type = declared_type(); + return type != nullptr && type->is_flat_array_klass(); + } + return false; +} + +bool Instruction::maybe_flat_array() { + if (UseArrayFlattening) { + ciType* type = declared_type(); + if (type != nullptr) { + if (type->is_obj_array_klass()) { + // Due to array covariance, the runtime type might be a flat array. + ciKlass* element_klass = type->as_obj_array_klass()->element_klass(); + if (element_klass->can_be_inline_klass() && (!element_klass->is_inlinetype() || element_klass->as_inline_klass()->maybe_flat_in_array())) { + return true; + } + } else if (type->is_flat_array_klass()) { + return true; + } else if (type->is_klass() && type->as_klass()->is_java_lang_Object()) { + // This can happen as a parameter to System.arraycopy() + return true; + } + } else { + // Type info gets lost during Phi merging (Phi, IfOp, etc), but we might be storing into a + // flat array, so we should do a runtime check. + return true; + } + } + return false; +} + +bool Instruction::maybe_null_free_array() { + ciType* type = declared_type(); + if (type != nullptr) { + if (type->is_obj_array_klass()) { + // Due to array covariance, the runtime type might be a null-free array. + if (type->as_obj_array_klass()->can_be_inline_array_klass()) { + return true; + } + } + } else { + // Type info gets lost during Phi merging (Phi, IfOp, etc), but we might be storing into a + // null-free array, so we should do a runtime check. + return true; + } + return false; +} #ifndef PRODUCT void Instruction::check_state(ValueStack* state) { @@ -172,7 +234,7 @@ ciType* Constant::exact_type() const { ciType* LoadIndexed::exact_type() const { ciType* array_type = array()->exact_type(); - if (array_type != nullptr) { + if (delayed() == nullptr && array_type != nullptr) { assert(array_type->is_array_klass(), "what else?"); ciArrayKlass* ak = (ciArrayKlass*)array_type; @@ -186,8 +248,10 @@ ciType* LoadIndexed::exact_type() const { return Instruction::exact_type(); } - ciType* LoadIndexed::declared_type() const { + if (delayed() != nullptr) { + return delayed()->field()->type(); + } ciType* array_type = array()->declared_type(); if (array_type == nullptr || !array_type->is_loaded()) { return nullptr; @@ -197,6 +261,20 @@ ciType* LoadIndexed::declared_type() const { return ak->element_type(); } +bool StoreIndexed::is_exact_flat_array_store() const { + if (array()->is_loaded_flat_array() && value()->as_Constant() == nullptr && value()->declared_type() != nullptr) { + ciKlass* element_klass = array()->declared_type()->as_flat_array_klass()->element_klass(); + ciKlass* actual_klass = value()->declared_type()->as_klass(); + + // The following check can fail with inlining: + // void test45_inline(Object[] oa, Object o, int index) { oa[index] = o; } + // void test45(MyValue1[] va, int index, MyValue2 v) { test45_inline(va, v, index); } + if (element_klass == actual_klass) { + return true; + } + } + return false; +} ciType* LoadField::declared_type() const { return field()->type(); @@ -208,7 +286,11 @@ ciType* NewTypeArray::exact_type() const { } ciType* NewObjectArray::exact_type() const { - return ciObjArrayKlass::make(klass()); + return ciArrayKlass::make(klass()); +} + +ciType* NewMultiArray::exact_type() const { + return _klass; } ciType* NewArray::declared_type() const { @@ -318,6 +400,32 @@ void BlockBegin::state_values_do(ValueVisitor* f) { } +StoreField::StoreField(Value obj, int offset, ciField* field, Value value, bool is_static, + ValueStack* state_before, bool needs_patching) + : AccessField(obj, offset, field, is_static, state_before, needs_patching) + , _value(value) + , _enclosing_field(nullptr) +{ +#ifdef ASSERT + AssertValues assert_value; + values_do(&assert_value); +#endif + pin(); +} + +StoreIndexed::StoreIndexed(Value array, Value index, Value length, BasicType elt_type, Value value, + ValueStack* state_before, bool check_boolean, bool mismatched) + : AccessIndexed(array, index, length, elt_type, state_before, mismatched) + , _value(value), _check_boolean(check_boolean) +{ +#ifdef ASSERT + AssertValues assert_value; + values_do(&assert_value); +#endif + pin(); +} + + // Implementation of Invoke @@ -344,7 +452,8 @@ Invoke::Invoke(Bytecodes::Code code, ValueType* result_type, Value recv, Values* _signature->append(as_BasicType(receiver()->type())); } for (int i = 0; i < number_of_arguments(); i++) { - ValueType* t = argument_at(i)->type(); + Value v = argument_at(i); + ValueType* t = v->type(); BasicType bt = as_BasicType(t); _signature->append(bt); } @@ -989,3 +1098,4 @@ void RangeCheckPredicate::check_state() { void ProfileInvoke::state_values_do(ValueVisitor* f) { if (state() != nullptr) state()->values_do(f); } + diff --git a/src/hotspot/share/c1/c1_Instruction.hpp b/src/hotspot/share/c1/c1_Instruction.hpp index 6b5838f6062..40f27060c75 100644 --- a/src/hotspot/share/c1/c1_Instruction.hpp +++ b/src/hotspot/share/c1/c1_Instruction.hpp @@ -74,6 +74,7 @@ class NewArray; class NewTypeArray; class NewObjectArray; class NewMultiArray; +class Deoptimize; class TypeCheck; class CheckCast; class InstanceOf; @@ -97,6 +98,7 @@ class UnsafePut; class UnsafeGetAndSet; class ProfileCall; class ProfileReturnType; +class ProfileACmpTypes; class ProfileInvoke; class RuntimeCall; class MemBar; @@ -191,6 +193,7 @@ class InstructionVisitor: public StackObj { virtual void do_UnsafeGetAndSet(UnsafeGetAndSet* x) = 0; virtual void do_ProfileCall (ProfileCall* x) = 0; virtual void do_ProfileReturnType (ProfileReturnType* x) = 0; + virtual void do_ProfileACmpTypes(ProfileACmpTypes* x) = 0; virtual void do_ProfileInvoke (ProfileInvoke* x) = 0; virtual void do_RuntimeCall (RuntimeCall* x) = 0; virtual void do_MemBar (MemBar* x) = 0; @@ -207,9 +210,10 @@ class InstructionVisitor: public StackObj { // of ValueMap - make changes carefully! #define HASH1(x1 ) ((intx)(x1)) -#define HASH2(x1, x2 ) ((HASH1(x1 ) << 7) ^ HASH1(x2)) -#define HASH3(x1, x2, x3 ) ((HASH2(x1, x2 ) << 7) ^ HASH1(x3)) -#define HASH4(x1, x2, x3, x4) ((HASH3(x1, x2, x3) << 7) ^ HASH1(x4)) +#define HASH2(x1, x2 ) ((HASH1(x1 ) << 7) ^ HASH1(x2)) +#define HASH3(x1, x2, x3 ) ((HASH2(x1, x2 ) << 7) ^ HASH1(x3)) +#define HASH4(x1, x2, x3, x4) ((HASH3(x1, x2, x3 ) << 7) ^ HASH1(x4)) +#define HASH5(x1, x2, x3, x4, x5) ((HASH4(x1, x2, x3, x4) << 7) ^ HASH1(x5)) // The following macros are used to implement instruction-specific hashing. @@ -268,6 +272,21 @@ class InstructionVisitor: public StackObj { return true; \ } \ +#define HASHING4(class_name, enabled, f1, f2, f3, f4) \ + virtual intx hash() const { \ + return (enabled) ? HASH5(name(), f1, f2, f3, f4) : 0; \ + } \ + virtual bool is_equal(Value v) const { \ + if (!(enabled) ) return false; \ + class_name* _v = v->as_##class_name(); \ + if (_v == nullptr ) return false; \ + if (f1 != _v->f1) return false; \ + if (f2 != _v->f2) return false; \ + if (f3 != _v->f3) return false; \ + if (f4 != _v->f4) return false; \ + return true; \ + } \ + // The mother of all instructions... @@ -290,6 +309,7 @@ class Instruction: public CompilationResourceObj { XHandlers* _exception_handlers; // Flat list of exception handlers covering this instruction friend class UseCountComputer; + friend class GraphBuilder; void update_exception_state(ValueStack* state); @@ -342,6 +362,7 @@ class Instruction: public CompilationResourceObj { enum InstructionFlag { NeedsNullCheckFlag = 0, + NeverNullFlag, CanTrapFlag, DirectCompareFlag, IsSafepointFlag, @@ -432,6 +453,8 @@ class Instruction: public CompilationResourceObj { void set_needs_null_check(bool f) { set_flag(NeedsNullCheckFlag, f); } bool needs_null_check() const { return check_flag(NeedsNullCheckFlag); } + void set_null_free(bool f) { set_flag(NeverNullFlag, f); } + bool is_null_free() const { return check_flag(NeverNullFlag); } bool is_linked() const { return check_flag(IsLinkedInBlockFlag); } bool can_be_linked() { return as_Local() == nullptr && as_Phi() == nullptr; } @@ -442,6 +465,7 @@ class Instruction: public CompilationResourceObj { ValueStack* exception_state() const { return _exception_state; } virtual bool needs_exception_state() const { return true; } XHandlers* exception_handlers() const { return _exception_handlers; } + ciKlass* as_loaded_klass_or_null() const; // manipulation void pin(PinReason reason) { _pin_state |= reason; } @@ -486,6 +510,10 @@ class Instruction: public CompilationResourceObj { return _next; } + bool is_loaded_flat_array() const; + bool maybe_flat_array(); + bool maybe_null_free_array(); + Instruction *insert_after_same_bci(Instruction *i) { #ifndef PRODUCT i->set_printable_bci(printable_bci()); @@ -813,7 +841,9 @@ LEAF(LoadField, AccessField) LoadField(Value obj, int offset, ciField* field, bool is_static, ValueStack* state_before, bool needs_patching) : AccessField(obj, offset, field, is_static, state_before, needs_patching) - {} + { + set_null_free(field->is_null_free()); + } ciType* declared_type() const; @@ -825,20 +855,17 @@ LEAF(LoadField, AccessField) LEAF(StoreField, AccessField) private: Value _value; + ciField* _enclosing_field; // enclosing field (the flat one) for nested fields public: // creation StoreField(Value obj, int offset, ciField* field, Value value, bool is_static, - ValueStack* state_before, bool needs_patching) - : AccessField(obj, offset, field, is_static, state_before, needs_patching) - , _value(value) - { - ASSERT_VALUES - pin(); - } + ValueStack* state_before, bool needs_patching); // accessors Value value() const { return _value; } + ciField* enclosing_field() const { return _enclosing_field; } + void set_enclosing_field(ciField* field) { _enclosing_field = field; } // generic virtual void input_values_do(ValueVisitor* f) { AccessField::input_values_do(f); f->visit(&_value); } @@ -896,6 +923,8 @@ BASE(AccessIndexed, AccessArray) Value _length; BasicType _elt_type; bool _mismatched; + ciMethod* _profiled_method; + int _profiled_bci; public: // creation @@ -905,6 +934,7 @@ BASE(AccessIndexed, AccessArray) , _length(length) , _elt_type(elt_type) , _mismatched(mismatched) + , _profiled_method(nullptr), _profiled_bci(0) { set_flag(Instruction::NeedsRangeCheckFlag, true); ASSERT_VALUES @@ -920,20 +950,32 @@ BASE(AccessIndexed, AccessArray) // perform elimination of range checks involving constants bool compute_needs_range_check(); + // Helpers for MethodData* profiling + void set_should_profile(bool value) { set_flag(ProfileMDOFlag, value); } + void set_profiled_method(ciMethod* method) { _profiled_method = method; } + void set_profiled_bci(int bci) { _profiled_bci = bci; } + bool should_profile() const { return check_flag(ProfileMDOFlag); } + ciMethod* profiled_method() const { return _profiled_method; } + int profiled_bci() const { return _profiled_bci; } + + // generic virtual void input_values_do(ValueVisitor* f) { AccessArray::input_values_do(f); f->visit(&_index); if (_length != nullptr) f->visit(&_length); } }; +class DelayedLoadIndexed; LEAF(LoadIndexed, AccessIndexed) private: NullCheck* _explicit_null_check; // For explicit null check elimination + NewInstance* _vt; + DelayedLoadIndexed* _delayed; public: // creation LoadIndexed(Value array, Value index, Value length, BasicType elt_type, ValueStack* state_before, bool mismatched = false) : AccessIndexed(array, index, length, elt_type, state_before, mismatched) - , _explicit_null_check(nullptr) {} + , _explicit_null_check(nullptr), _vt(nullptr), _delayed(nullptr) {} // accessors NullCheck* explicit_null_check() const { return _explicit_null_check; } @@ -945,40 +987,57 @@ LEAF(LoadIndexed, AccessIndexed) ciType* exact_type() const; ciType* declared_type() const; + NewInstance* vt() const { return _vt; } + void set_vt(NewInstance* vt) { _vt = vt; } + + DelayedLoadIndexed* delayed() const { return _delayed; } + void set_delayed(DelayedLoadIndexed* delayed) { _delayed = delayed; } + // generic; - HASHING3(LoadIndexed, true, elt_type(), array()->subst(), index()->subst()) + HASHING4(LoadIndexed, delayed() == nullptr && !should_profile(), elt_type(), array()->subst(), index()->subst(), vt()) }; +class DelayedLoadIndexed : public CompilationResourceObj { +private: + LoadIndexed* _load_instr; + ValueStack* _state_before; + ciField* _field; + int _offset; + public: + DelayedLoadIndexed(LoadIndexed* load, ValueStack* state_before) + : _load_instr(load) + , _state_before(state_before) + , _field(nullptr) + , _offset(0) { } + + void update(ciField* field, int offset) { + _field = field; + _offset += offset; + } + + LoadIndexed* load_instr() const { return _load_instr; } + ValueStack* state_before() const { return _state_before; } + ciField* field() const { return _field; } + int offset() const { return _offset; } +}; LEAF(StoreIndexed, AccessIndexed) private: Value _value; - ciMethod* _profiled_method; - int _profiled_bci; bool _check_boolean; public: // creation StoreIndexed(Value array, Value index, Value length, BasicType elt_type, Value value, ValueStack* state_before, - bool check_boolean, bool mismatched = false) - : AccessIndexed(array, index, length, elt_type, state_before, mismatched) - , _value(value), _profiled_method(nullptr), _profiled_bci(0), _check_boolean(check_boolean) - { - ASSERT_VALUES - pin(); - } + bool check_boolean, bool mismatched = false); // accessors Value value() const { return _value; } bool check_boolean() const { return _check_boolean; } - // Helpers for MethodData* profiling - void set_should_profile(bool value) { set_flag(ProfileMDOFlag, value); } - void set_profiled_method(ciMethod* method) { _profiled_method = method; } - void set_profiled_bci(int bci) { _profiled_bci = bci; } - bool should_profile() const { return check_flag(ProfileMDOFlag); } - ciMethod* profiled_method() const { return _profiled_method; } - int profiled_bci() const { return _profiled_bci; } + + // Flattened array support + bool is_exact_flat_array_store() const; // generic virtual void input_values_do(ValueVisitor* f) { AccessIndexed::input_values_do(f); f->visit(&_value); } }; @@ -1089,16 +1148,19 @@ LEAF(IfOp, Op2) private: Value _tval; Value _fval; + bool _substitutability_check; public: // creation - IfOp(Value x, Condition cond, Value y, Value tval, Value fval) + IfOp(Value x, Condition cond, Value y, Value tval, Value fval, ValueStack* state_before, bool substitutability_check) : Op2(tval->type()->meet(fval->type()), (Bytecodes::Code)cond, x, y) , _tval(tval) , _fval(fval) + , _substitutability_check(substitutability_check) { ASSERT_VALUES assert(tval->type()->tag() == fval->type()->tag(), "types must match"); + set_state_before(state_before); } // accessors @@ -1107,7 +1169,7 @@ LEAF(IfOp, Op2) Condition cond() const { return (Condition)Op2::op(); } Value tval() const { return _tval; } Value fval() const { return _fval; } - + bool substitutability_check() const { return _substitutability_check; } // generic virtual void input_values_do(ValueVisitor* f) { Op2::input_values_do(f); f->visit(&_tval); f->visit(&_fval); } }; @@ -1264,17 +1326,19 @@ LEAF(NewInstance, StateSplit) private: ciInstanceKlass* _klass; bool _is_unresolved; + bool _needs_state_before; public: // creation - NewInstance(ciInstanceKlass* klass, ValueStack* state_before, bool is_unresolved) + NewInstance(ciInstanceKlass* klass, ValueStack* state_before, bool is_unresolved, bool needs_state_before) : StateSplit(instanceType, state_before) - , _klass(klass), _is_unresolved(is_unresolved) + , _klass(klass), _is_unresolved(is_unresolved), _needs_state_before(needs_state_before) {} // accessors ciInstanceKlass* klass() const { return _klass; } bool is_unresolved() const { return _is_unresolved; } + bool needs_state_before() const { return _needs_state_before; } virtual bool needs_exception_state() const { return false; } @@ -1284,7 +1348,6 @@ LEAF(NewInstance, StateSplit) ciType* declared_type() const; }; - BASE(NewArray, StateSplit) private: Value _length; @@ -1338,7 +1401,8 @@ LEAF(NewObjectArray, NewArray) public: // creation - NewObjectArray(ciKlass* klass, Value length, ValueStack* state_before) : NewArray(length, state_before), _klass(klass) {} + NewObjectArray(ciKlass* klass, Value length, ValueStack* state_before) + : NewArray(length, state_before), _klass(klass) { } // accessors ciKlass* klass() const { return _klass; } @@ -1373,6 +1437,8 @@ LEAF(NewMultiArray, NewArray) StateSplit::input_values_do(f); for (int i = 0; i < _dims->length(); i++) f->visit(_dims->adr_at(i)); } + + ciType* exact_type() const; }; @@ -1420,7 +1486,7 @@ LEAF(CheckCast, TypeCheck) public: // creation CheckCast(ciKlass* klass, Value obj, ValueStack* state_before) - : TypeCheck(klass, obj, objectType, state_before) {} + : TypeCheck(klass, obj, objectType, state_before) { } void set_incompatible_class_change_check() { set_flag(ThrowIncompatibleClassChangeErrorFlag, true); @@ -1478,14 +1544,19 @@ BASE(AccessMonitor, StateSplit) LEAF(MonitorEnter, AccessMonitor) + bool _maybe_inlinetype; public: // creation - MonitorEnter(Value obj, int monitor_no, ValueStack* state_before) + MonitorEnter(Value obj, int monitor_no, ValueStack* state_before, bool maybe_inlinetype) : AccessMonitor(obj, monitor_no, state_before) + , _maybe_inlinetype(maybe_inlinetype) { ASSERT_VALUES } + // accessors + bool maybe_inlinetype() const { return _maybe_inlinetype; } + // generic virtual bool can_trap() const { return true; } }; @@ -1941,10 +2012,11 @@ LEAF(If, BlockEnd) int _profiled_bci; // Canonicalizer may alter bci of If node bool _swapped; // Is the order reversed with respect to the original If in the // bytecode stream? + bool _substitutability_check; public: // creation // unordered_is_true is valid for float/double compares only - If(Value x, Condition cond, bool unordered_is_true, Value y, BlockBegin* tsux, BlockBegin* fsux, ValueStack* state_before, bool is_safepoint) + If(Value x, Condition cond, bool unordered_is_true, Value y, BlockBegin* tsux, BlockBegin* fsux, ValueStack* state_before, bool is_safepoint, bool substitutability_check=false) : BlockEnd(illegalType, state_before, is_safepoint) , _x(x) , _cond(cond) @@ -1952,6 +2024,7 @@ LEAF(If, BlockEnd) , _profiled_method(nullptr) , _profiled_bci(0) , _swapped(false) + , _substitutability_check(substitutability_check) { ASSERT_VALUES set_flag(UnorderedIsTrueFlag, unordered_is_true); @@ -1986,6 +2059,7 @@ LEAF(If, BlockEnd) void set_profiled_method(ciMethod* method) { _profiled_method = method; } void set_profiled_bci(int bci) { _profiled_bci = bci; } void set_swapped(bool value) { _swapped = value; } + bool substitutability_check() const { return _substitutability_check; } // generic virtual void input_values_do(ValueVisitor* f) { BlockEnd::input_values_do(f); f->visit(&_x); f->visit(&_y); } }; @@ -2296,7 +2370,7 @@ LEAF(ProfileReturnType, Instruction) , _ret(ret) { set_needs_null_check(true); - // The ProfileType has side-effects and must occur precisely where located + // The ProfileReturnType has side-effects and must occur precisely where located pin(); } @@ -2312,6 +2386,48 @@ LEAF(ProfileReturnType, Instruction) } }; +LEAF(ProfileACmpTypes, Instruction) + private: + ciMethod* _method; + int _bci; + Value _left; + Value _right; + bool _left_maybe_null; + bool _right_maybe_null; + + public: + ProfileACmpTypes(ciMethod* method, int bci, Value left, Value right) + : Instruction(voidType) + , _method(method) + , _bci(bci) + , _left(left) + , _right(right) + { + // The ProfileACmp has side-effects and must occur precisely where located + pin(); + _left_maybe_null = true; + _right_maybe_null = true; + } + + ciMethod* method() const { return _method; } + int bci() const { return _bci; } + Value left() const { return _left; } + Value right() const { return _right; } + bool left_maybe_null() const { return _left_maybe_null; } + bool right_maybe_null() const { return _right_maybe_null; } + void set_left_maybe_null(bool v) { _left_maybe_null = v; } + void set_right_maybe_null(bool v) { _right_maybe_null = v; } + + virtual void input_values_do(ValueVisitor* f) { + if (_left != nullptr) { + f->visit(&_left); + } + if (_right != nullptr) { + f->visit(&_right); + } + } +}; + // Call some C runtime function that doesn't safepoint, // optionally passing the current thread as the first argument. LEAF(RuntimeCall, Instruction) diff --git a/src/hotspot/share/c1/c1_InstructionPrinter.cpp b/src/hotspot/share/c1/c1_InstructionPrinter.cpp index 59e24c3e6c5..b9d5ce6e452 100644 --- a/src/hotspot/share/c1/c1_InstructionPrinter.cpp +++ b/src/hotspot/share/c1/c1_InstructionPrinter.cpp @@ -25,6 +25,7 @@ #include "c1/c1_InstructionPrinter.hpp" #include "c1/c1_ValueStack.hpp" #include "ci/ciArray.hpp" +#include "ci/ciInlineKlass.hpp" #include "ci/ciInstance.hpp" #include "ci/ciObject.hpp" #include "classfile/vmSymbols.hpp" @@ -380,7 +381,12 @@ void InstructionPrinter::do_ArrayLength(ArrayLength* x) { void InstructionPrinter::do_LoadIndexed(LoadIndexed* x) { print_indexed(x); - output()->print(" (%c)", type2char(x->elt_type())); + if (x->delayed() != nullptr) { + output()->print(" +%d", x->delayed()->offset()); + output()->print(" (%c)", type2char(x->delayed()->field()->type()->basic_type())); + } else { + output()->print(" (%c)", type2char(x->elt_type())); + } if (x->check_flag(Instruction::NeedsRangeCheckFlag)) { output()->print(" [rc]"); } @@ -494,7 +500,6 @@ void InstructionPrinter::do_NewTypeArray(NewTypeArray* x) { output()->put(']'); } - void InstructionPrinter::do_NewObjectArray(NewObjectArray* x) { output()->print("new object array ["); print_value(x->length()); @@ -514,7 +519,6 @@ void InstructionPrinter::do_NewMultiArray(NewMultiArray* x) { print_klass(x->klass()); } - void InstructionPrinter::do_MonitorEnter(MonitorEnter* x) { output()->print("enter "); print_monitor(x); @@ -845,6 +849,7 @@ void InstructionPrinter::do_ProfileReturnType(ProfileReturnType* x) { output()->print(" %s.%s", x->method()->holder()->name()->as_utf8(), x->method()->name()->as_utf8()); output()->put(')'); } + void InstructionPrinter::do_ProfileInvoke(ProfileInvoke* x) { output()->print("profile_invoke "); output()->print(" %s.%s", x->inlinee()->holder()->name()->as_utf8(), x->inlinee()->name()->as_utf8()); @@ -852,6 +857,13 @@ void InstructionPrinter::do_ProfileInvoke(ProfileInvoke* x) { } +void InstructionPrinter::do_ProfileACmpTypes(ProfileACmpTypes* x) { + output()->print("profile acmp types "); + print_value(x->left()); + output()->print(", "); + print_value(x->right()); +} + void InstructionPrinter::do_RuntimeCall(RuntimeCall* x) { output()->print("call_rt %s(", x->entry_name()); for (int i = 0; i < x->number_of_arguments(); i++) { diff --git a/src/hotspot/share/c1/c1_InstructionPrinter.hpp b/src/hotspot/share/c1/c1_InstructionPrinter.hpp index b7bac561327..b16a1e3bf9f 100644 --- a/src/hotspot/share/c1/c1_InstructionPrinter.hpp +++ b/src/hotspot/share/c1/c1_InstructionPrinter.hpp @@ -125,6 +125,7 @@ class InstructionPrinter: public InstructionVisitor { virtual void do_UnsafeGetAndSet(UnsafeGetAndSet* x); virtual void do_ProfileCall (ProfileCall* x); virtual void do_ProfileReturnType (ProfileReturnType* x); + virtual void do_ProfileACmpTypes(ProfileACmpTypes* x); virtual void do_ProfileInvoke (ProfileInvoke* x); virtual void do_RuntimeCall (RuntimeCall* x); virtual void do_MemBar (MemBar* x); diff --git a/src/hotspot/share/c1/c1_LIR.cpp b/src/hotspot/share/c1/c1_LIR.cpp index 4c8ebd5a09d..7fa328cb1b5 100644 --- a/src/hotspot/share/c1/c1_LIR.cpp +++ b/src/hotspot/share/c1/c1_LIR.cpp @@ -27,6 +27,7 @@ #include "c1/c1_LIR.hpp" #include "c1/c1_LIRAssembler.hpp" #include "c1/c1_ValueStack.hpp" +#include "ci/ciInlineKlass.hpp" #include "ci/ciInstance.hpp" #include "runtime/safepointMechanism.inline.hpp" #include "runtime/sharedRuntime.hpp" @@ -286,7 +287,7 @@ void LIR_OpBranch::negate_cond() { LIR_OpTypeCheck::LIR_OpTypeCheck(LIR_Code code, LIR_Opr result, LIR_Opr object, ciKlass* klass, LIR_Opr tmp1, LIR_Opr tmp2, LIR_Opr tmp3, bool fast_check, CodeEmitInfo* info_for_exception, CodeEmitInfo* info_for_patch, - CodeStub* stub) + CodeStub* stub, bool need_null_check) : LIR_Op(code, result, nullptr) , _object(object) @@ -302,6 +303,7 @@ LIR_OpTypeCheck::LIR_OpTypeCheck(LIR_Code code, LIR_Opr result, LIR_Opr object, , _profiled_bci(-1) , _should_profile(false) , _fast_check(fast_check) + , _need_null_check(need_null_check) { if (code == lir_checkcast) { assert(info_for_exception != nullptr, "checkcast throws exceptions"); @@ -329,6 +331,7 @@ LIR_OpTypeCheck::LIR_OpTypeCheck(LIR_Code code, LIR_Opr object, LIR_Opr array, L , _profiled_bci(-1) , _should_profile(false) , _fast_check(false) + , _need_null_check(true) { if (code == lir_store_check) { _stub = new ArrayStoreExceptionStub(object, info_for_exception); @@ -338,6 +341,37 @@ LIR_OpTypeCheck::LIR_OpTypeCheck(LIR_Code code, LIR_Opr object, LIR_Opr array, L } } +LIR_OpFlattenedArrayCheck::LIR_OpFlattenedArrayCheck(LIR_Opr array, LIR_Opr value, LIR_Opr tmp, CodeStub* stub) + : LIR_Op(lir_flat_array_check, LIR_OprFact::illegalOpr, nullptr) + , _array(array) + , _value(value) + , _tmp(tmp) + , _stub(stub) {} + + +LIR_OpNullFreeArrayCheck::LIR_OpNullFreeArrayCheck(LIR_Opr array, LIR_Opr tmp) + : LIR_Op(lir_null_free_array_check, LIR_OprFact::illegalOpr, nullptr) + , _array(array) + , _tmp(tmp) {} + + +LIR_OpSubstitutabilityCheck::LIR_OpSubstitutabilityCheck(LIR_Opr result, LIR_Opr left, LIR_Opr right, LIR_Opr equal_result, LIR_Opr not_equal_result, + LIR_Opr tmp1, LIR_Opr tmp2, + ciKlass* left_klass, ciKlass* right_klass, LIR_Opr left_klass_op, LIR_Opr right_klass_op, + CodeEmitInfo* info, CodeStub* stub) + : LIR_Op(lir_substitutability_check, result, info) + , _left(left) + , _right(right) + , _equal_result(equal_result) + , _not_equal_result(not_equal_result) + , _tmp1(tmp1) + , _tmp2(tmp2) + , _left_klass(left_klass) + , _right_klass(right_klass) + , _left_klass_op(left_klass_op) + , _right_klass_op(right_klass_op) + , _stub(stub) {} + LIR_OpArrayCopy::LIR_OpArrayCopy(LIR_Opr src, LIR_Opr src_pos, LIR_Opr dst, LIR_Opr dst_pos, LIR_Opr length, LIR_Opr tmp, ciArrayKlass* expected_type, int flags, CodeEmitInfo* info) @@ -412,6 +446,7 @@ void LIR_OpVisitState::visit(LIR_Op* op) { case lir_membar_storestore: // result and info always invalid case lir_membar_loadstore: // result and info always invalid case lir_membar_storeload: // result and info always invalid + case lir_check_orig_pc: // result and info always invalid case lir_on_spin_wait: { assert(op->as_Op0() != nullptr, "must be"); @@ -795,6 +830,7 @@ void LIR_OpVisitState::visit(LIR_Op* op) { assert(opLock->_result->is_illegal(), "unused"); do_stub(opLock->_stub); + do_stub(opLock->_throw_ie_stub); break; } @@ -831,6 +867,53 @@ void LIR_OpVisitState::visit(LIR_Op* op) { break; } +// LIR_OpFlattenedArrayCheck + case lir_flat_array_check: { + assert(op->as_OpFlattenedArrayCheck() != nullptr, "must be"); + LIR_OpFlattenedArrayCheck* opFlattenedArrayCheck = (LIR_OpFlattenedArrayCheck*)op; + + if (opFlattenedArrayCheck->_array->is_valid()) do_input(opFlattenedArrayCheck->_array); + if (opFlattenedArrayCheck->_value->is_valid()) do_input(opFlattenedArrayCheck->_value); + if (opFlattenedArrayCheck->_tmp->is_valid()) do_temp(opFlattenedArrayCheck->_tmp); + + do_stub(opFlattenedArrayCheck->_stub); + + break; + } + +// LIR_OpNullFreeArrayCheck + case lir_null_free_array_check: { + assert(op->as_OpNullFreeArrayCheck() != nullptr, "must be"); + LIR_OpNullFreeArrayCheck* opNullFreeArrayCheck = (LIR_OpNullFreeArrayCheck*)op; + + if (opNullFreeArrayCheck->_array->is_valid()) do_input(opNullFreeArrayCheck->_array); + if (opNullFreeArrayCheck->_tmp->is_valid()) do_temp(opNullFreeArrayCheck->_tmp); + break; + } + +// LIR_OpSubstitutabilityCheck + case lir_substitutability_check: { + assert(op->as_OpSubstitutabilityCheck() != nullptr, "must be"); + LIR_OpSubstitutabilityCheck* opSubstitutabilityCheck = (LIR_OpSubstitutabilityCheck*)op; + do_input(opSubstitutabilityCheck->_left); + do_temp (opSubstitutabilityCheck->_left); + do_input(opSubstitutabilityCheck->_right); + do_temp (opSubstitutabilityCheck->_right); + do_input(opSubstitutabilityCheck->_equal_result); + do_temp (opSubstitutabilityCheck->_equal_result); + do_input(opSubstitutabilityCheck->_not_equal_result); + do_temp (opSubstitutabilityCheck->_not_equal_result); + if (opSubstitutabilityCheck->_tmp1->is_valid()) do_temp(opSubstitutabilityCheck->_tmp1); + if (opSubstitutabilityCheck->_tmp2->is_valid()) do_temp(opSubstitutabilityCheck->_tmp2); + if (opSubstitutabilityCheck->_left_klass_op->is_valid()) do_temp(opSubstitutabilityCheck->_left_klass_op); + if (opSubstitutabilityCheck->_right_klass_op->is_valid()) do_temp(opSubstitutabilityCheck->_right_klass_op); + if (opSubstitutabilityCheck->_result->is_valid()) do_output(opSubstitutabilityCheck->_result); + + do_info(opSubstitutabilityCheck->_info); + do_stub(opSubstitutabilityCheck->_stub); + break; + } + // LIR_OpCompareAndSwap case lir_cas_long: case lir_cas_obj: @@ -908,7 +991,18 @@ void LIR_OpVisitState::visit(LIR_Op* op) { do_temp(opProfileType->_tmp); break; } - default: + + // LIR_OpProfileInlineType: + case lir_profile_inline_type: { + assert(op->as_OpProfileInlineType() != nullptr, "must be"); + LIR_OpProfileInlineType* opProfileInlineType = (LIR_OpProfileInlineType*)op; + + do_input(opProfileInlineType->_mdp); do_temp(opProfileInlineType->_mdp); + do_input(opProfileInlineType->_obj); + do_temp(opProfileInlineType->_tmp); + break; + } +default: op->visit(this); } } @@ -981,6 +1075,34 @@ void LIR_OpJavaCall::emit_code(LIR_Assembler* masm) { masm->emit_call(this); } +bool LIR_OpJavaCall::maybe_return_as_fields(ciInlineKlass** vk_ret) const { + ciType* return_type = method()->return_type(); + if (InlineTypeReturnedAsFields) { + if (return_type->is_inlinetype()) { + ciInlineKlass* vk = return_type->as_inline_klass(); + if (vk->can_be_returned_as_fields()) { + if (vk_ret != nullptr) { + *vk_ret = vk; + } + return true; + } + } else if (return_type->is_instance_klass() && + (method()->is_method_handle_intrinsic() || !return_type->is_loaded() || + StressCallingConvention)) { + // An inline type might be returned from the call but we don't know its type. + // This can happen with method handle intrinsics or when the return type is + // not loaded (method holder is not loaded or preload attribute is missing). + // If an inline type is returned, we either get an oop to a buffer and nothing + // needs to be done or one of the values being returned is the klass of the + // inline type (RAX on x64, with LSB set to 1) and we need to allocate an inline + // type instance of that type and initialize it with the fields values being + // returned in other registers. + return true; + } + } + return false; +} + void LIR_OpRTCall::emit_code(LIR_Assembler* masm) { masm->emit_rtcall(this); } @@ -1044,6 +1166,24 @@ void LIR_OpTypeCheck::emit_code(LIR_Assembler* masm) { } } +void LIR_OpFlattenedArrayCheck::emit_code(LIR_Assembler* masm) { + masm->emit_opFlattenedArrayCheck(this); + if (stub() != nullptr) { + masm->append_code_stub(stub()); + } +} + +void LIR_OpNullFreeArrayCheck::emit_code(LIR_Assembler* masm) { + masm->emit_opNullFreeArrayCheck(this); +} + +void LIR_OpSubstitutabilityCheck::emit_code(LIR_Assembler* masm) { + masm->emit_opSubstitutabilityCheck(this); + if (stub() != nullptr) { + masm->append_code_stub(stub()); + } +} + void LIR_OpCompareAndSwap::emit_code(LIR_Assembler* masm) { masm->emit_compare_and_swap(this); } @@ -1061,6 +1201,9 @@ void LIR_OpLock::emit_code(LIR_Assembler* masm) { if (stub()) { masm->append_code_stub(stub()); } + if (throw_ie_stub()) { + masm->append_code_stub(throw_ie_stub()); + } } void LIR_OpLoadKlass::emit_code(LIR_Assembler* masm) { @@ -1085,6 +1228,10 @@ void LIR_OpProfileType::emit_code(LIR_Assembler* masm) { masm->emit_profile_type(this); } +void LIR_OpProfileInlineType::emit_code(LIR_Assembler* masm) { + masm->emit_profile_inline_type(this); +} + // LIR_List LIR_List::LIR_List(Compilation* compilation, BlockBegin* block) : _operations(8) @@ -1360,7 +1507,7 @@ void LIR_List::allocate_object(LIR_Opr dst, LIR_Opr t1, LIR_Opr t2, LIR_Opr t3, stub)); } -void LIR_List::allocate_array(LIR_Opr dst, LIR_Opr len, LIR_Opr t1,LIR_Opr t2, LIR_Opr t3,LIR_Opr t4, BasicType type, LIR_Opr klass, CodeStub* stub, bool zero_array) { +void LIR_List::allocate_array(LIR_Opr dst, LIR_Opr len, LIR_Opr t1,LIR_Opr t2, LIR_Opr t3,LIR_Opr t4, BasicType type, LIR_Opr klass, CodeStub* stub, bool zero_array, bool always_slow_path) { append(new LIR_OpAllocArray( klass, len, @@ -1371,7 +1518,8 @@ void LIR_List::allocate_array(LIR_Opr dst, LIR_Opr len, LIR_Opr t1,LIR_Opr t2, L t4, type, stub, - zero_array)); + zero_array, + always_slow_path)); } void LIR_List::shift_left(LIR_Opr value, LIR_Opr count, LIR_Opr dst, LIR_Opr tmp) { @@ -1409,7 +1557,7 @@ void LIR_List::fcmp2int(LIR_Opr left, LIR_Opr right, LIR_Opr dst, bool is_unorde dst)); } -void LIR_List::lock_object(LIR_Opr hdr, LIR_Opr obj, LIR_Opr lock, LIR_Opr scratch, CodeStub* stub, CodeEmitInfo* info) { +void LIR_List::lock_object(LIR_Opr hdr, LIR_Opr obj, LIR_Opr lock, LIR_Opr scratch, CodeStub* stub, CodeEmitInfo* info, CodeStub* throw_ie_stub) { append(new LIR_OpLock( lir_lock, hdr, @@ -1417,7 +1565,8 @@ void LIR_List::lock_object(LIR_Opr hdr, LIR_Opr obj, LIR_Opr lock, LIR_Opr scrat lock, scratch, stub, - info)); + info, + throw_ie_stub)); } void LIR_List::unlock_object(LIR_Opr hdr, LIR_Opr obj, LIR_Opr lock, LIR_Opr scratch, CodeStub* stub) { @@ -1442,9 +1591,13 @@ void check_LIR() { void LIR_List::checkcast (LIR_Opr result, LIR_Opr object, ciKlass* klass, LIR_Opr tmp1, LIR_Opr tmp2, LIR_Opr tmp3, bool fast_check, CodeEmitInfo* info_for_exception, CodeEmitInfo* info_for_patch, CodeStub* stub, - ciMethod* profiled_method, int profiled_bci) { + ciMethod* profiled_method, int profiled_bci, bool is_null_free) { + // If klass is non-nullable, LIRGenerator::do_CheckCast has already performed null-check + // on the object. + bool need_null_check = !is_null_free; LIR_OpTypeCheck* c = new LIR_OpTypeCheck(lir_checkcast, result, object, klass, - tmp1, tmp2, tmp3, fast_check, info_for_exception, info_for_patch, stub); + tmp1, tmp2, tmp3, fast_check, info_for_exception, info_for_patch, stub, + need_null_check); if (profiled_method != nullptr && TypeProfileCasts) { c->set_profiled_method(profiled_method); c->set_profiled_bci(profiled_bci); @@ -1466,6 +1619,7 @@ void LIR_List::instanceof(LIR_Opr result, LIR_Opr object, ciKlass* klass, LIR_Op void LIR_List::store_check(LIR_Opr object, LIR_Opr array, LIR_Opr tmp1, LIR_Opr tmp2, LIR_Opr tmp3, CodeEmitInfo* info_for_exception, ciMethod* profiled_method, int profiled_bci) { + // FIXME -- if the types of the array and/or the object are known statically, we can avoid loading the klass LIR_OpTypeCheck* c = new LIR_OpTypeCheck(lir_store_check, object, array, tmp1, tmp2, tmp3, info_for_exception); if (profiled_method != nullptr && TypeProfileCasts) { c->set_profiled_method(profiled_method); @@ -1487,6 +1641,27 @@ void LIR_List::null_check(LIR_Opr opr, CodeEmitInfo* info, bool deoptimize_on_nu } } +void LIR_List::check_flat_array(LIR_Opr array, LIR_Opr value, LIR_Opr tmp, CodeStub* stub) { + LIR_OpFlattenedArrayCheck* c = new LIR_OpFlattenedArrayCheck(array, value, tmp, stub); + append(c); +} + +void LIR_List::check_null_free_array(LIR_Opr array, LIR_Opr tmp) { + LIR_OpNullFreeArrayCheck* c = new LIR_OpNullFreeArrayCheck(array, tmp); + append(c); +} + +void LIR_List::substitutability_check(LIR_Opr result, LIR_Opr left, LIR_Opr right, LIR_Opr equal_result, LIR_Opr not_equal_result, + LIR_Opr tmp1, LIR_Opr tmp2, + ciKlass* left_klass, ciKlass* right_klass, LIR_Opr left_klass_op, LIR_Opr right_klass_op, + CodeEmitInfo* info, CodeStub* stub) { + LIR_OpSubstitutabilityCheck* c = new LIR_OpSubstitutabilityCheck(result, left, right, equal_result, not_equal_result, + tmp1, tmp2, + left_klass, right_klass, left_klass_op, right_klass_op, + info, stub); + append(c); +} + void LIR_List::cas_long(LIR_Opr addr, LIR_Opr cmp_value, LIR_Opr new_value, LIR_Opr t1, LIR_Opr t2, LIR_Opr result) { append(new LIR_OpCompareAndSwap(lir_cas_long, addr, cmp_value, new_value, t1, t2, result)); @@ -1702,6 +1877,7 @@ const char * LIR_Op::name() const { case lir_osr_entry: s = "osr_entry"; break; case lir_breakpoint: s = "breakpoint"; break; case lir_get_thread: s = "get_thread"; break; + case lir_check_orig_pc: s = "check_orig_pc"; break; // LIR_Op1 case lir_push: s = "push"; break; case lir_pop: s = "pop"; break; @@ -1767,6 +1943,12 @@ const char * LIR_Op::name() const { case lir_instanceof: s = "instanceof"; break; case lir_checkcast: s = "checkcast"; break; case lir_store_check: s = "store_check"; break; + // LIR_OpFlattenedArrayCheck + case lir_flat_array_check: s = "flat_array_check"; break; + // LIR_OpNullFreeArrayCheck + case lir_null_free_array_check: s = "null_free_array_check"; break; + // LIR_OpSubstitutabilityCheck + case lir_substitutability_check: s = "substitutability_check"; break; // LIR_OpCompareAndSwap case lir_cas_long: s = "cas_long"; break; case lir_cas_obj: s = "cas_obj"; break; @@ -1775,6 +1957,8 @@ const char * LIR_Op::name() const { case lir_profile_call: s = "profile_call"; break; // LIR_OpProfileType case lir_profile_type: s = "profile_type"; break; + // LIR_OpProfileInlineType + case lir_profile_inline_type: s = "profile_inline_type"; break; // LIR_OpAssert #ifdef ASSERT case lir_assert: s = "assert"; break; @@ -2000,6 +2184,44 @@ void LIR_OpTypeCheck::print_instr(outputStream* out) const { if (info_for_exception() != nullptr) out->print(" [bci:%d]", info_for_exception()->stack()->bci()); } +void LIR_OpFlattenedArrayCheck::print_instr(outputStream* out) const { + array()->print(out); out->print(" "); + value()->print(out); out->print(" "); + tmp()->print(out); out->print(" "); + if (stub() != nullptr) { + out->print("[label:" INTPTR_FORMAT "]", p2i(stub()->entry())); + } +} + +void LIR_OpNullFreeArrayCheck::print_instr(outputStream* out) const { + array()->print(out); out->print(" "); + tmp()->print(out); out->print(" "); +} + +void LIR_OpSubstitutabilityCheck::print_instr(outputStream* out) const { + result_opr()->print(out); out->print(" "); + left()->print(out); out->print(" "); + right()->print(out); out->print(" "); + equal_result()->print(out); out->print(" "); + not_equal_result()->print(out); out->print(" "); + tmp1()->print(out); out->print(" "); + tmp2()->print(out); out->print(" "); + if (left_klass() == nullptr) { + out->print("unknown "); + } else { + left_klass()->print(out); out->print(" "); + } + if (right_klass() == nullptr) { + out->print("unknown "); + } else { + right_klass()->print(out); out->print(" "); + } + left_klass_op()->print(out); out->print(" "); + right_klass_op()->print(out); out->print(" "); + if (stub() != nullptr) { + out->print("[label:" INTPTR_FORMAT "]", p2i(stub()->entry())); + } +} // LIR_Op3 void LIR_Op3::print_instr(outputStream* out) const { @@ -2075,6 +2297,14 @@ void LIR_OpProfileType::print_instr(outputStream* out) const { tmp()->print(out); out->print(" "); } +// LIR_OpProfileInlineType +void LIR_OpProfileInlineType::print_instr(outputStream* out) const { + out->print(" flag = %x ", flag()); + mdp()->print(out); out->print(" "); + obj()->print(out); out->print(" "); + tmp()->print(out); out->print(" "); +} + #endif // PRODUCT // Implementation of LIR_InsertionBuffer diff --git a/src/hotspot/share/c1/c1_LIR.hpp b/src/hotspot/share/c1/c1_LIR.hpp index c7726bf5c3f..380652db22e 100644 --- a/src/hotspot/share/c1/c1_LIR.hpp +++ b/src/hotspot/share/c1/c1_LIR.hpp @@ -890,10 +890,14 @@ class LIR_OpArrayCopy; class LIR_OpUpdateCRC32; class LIR_OpLock; class LIR_OpTypeCheck; +class LIR_OpFlattenedArrayCheck; +class LIR_OpNullFreeArrayCheck; +class LIR_OpSubstitutabilityCheck; class LIR_OpCompareAndSwap; class LIR_OpLoadKlass; class LIR_OpProfileCall; class LIR_OpProfileType; +class LIR_OpProfileInlineType; #ifdef ASSERT class LIR_OpAssert; #endif @@ -917,6 +921,7 @@ enum LIR_Code { , lir_membar_storeload , lir_get_thread , lir_on_spin_wait + , lir_check_orig_pc , end_op0 , begin_op1 , lir_push @@ -993,6 +998,15 @@ enum LIR_Code { , lir_checkcast , lir_store_check , end_opTypeCheck + , begin_opFlattenedArrayCheck + , lir_flat_array_check + , end_opFlattenedArrayCheck + , begin_opNullFreeArrayCheck + , lir_null_free_array_check + , end_opNullFreeArrayCheck + , begin_opSubstitutabilityCheck + , lir_substitutability_check + , end_opSubstitutabilityCheck , begin_opCompareAndSwap , lir_cas_long , lir_cas_obj @@ -1001,6 +1015,7 @@ enum LIR_Code { , begin_opMDOProfile , lir_profile_call , lir_profile_type + , lir_profile_inline_type , end_opMDOProfile , begin_opAssert , lir_assert @@ -1140,10 +1155,14 @@ class LIR_Op: public CompilationResourceObj { virtual LIR_OpArrayCopy* as_OpArrayCopy() { return nullptr; } virtual LIR_OpUpdateCRC32* as_OpUpdateCRC32() { return nullptr; } virtual LIR_OpTypeCheck* as_OpTypeCheck() { return nullptr; } + virtual LIR_OpFlattenedArrayCheck* as_OpFlattenedArrayCheck() { return nullptr; } + virtual LIR_OpNullFreeArrayCheck* as_OpNullFreeArrayCheck() { return nullptr; } + virtual LIR_OpSubstitutabilityCheck* as_OpSubstitutabilityCheck() { return nullptr; } virtual LIR_OpCompareAndSwap* as_OpCompareAndSwap() { return nullptr; } virtual LIR_OpLoadKlass* as_OpLoadKlass() { return nullptr; } virtual LIR_OpProfileCall* as_OpProfileCall() { return nullptr; } virtual LIR_OpProfileType* as_OpProfileType() { return nullptr; } + virtual LIR_OpProfileInlineType* as_OpProfileInlineType() { return nullptr; } #ifdef ASSERT virtual LIR_OpAssert* as_OpAssert() { return nullptr; } #endif @@ -1216,6 +1235,8 @@ class LIR_OpJavaCall: public LIR_OpCall { virtual void emit_code(LIR_Assembler* masm); virtual LIR_OpJavaCall* as_OpJavaCall() { return this; } virtual void print_instr(outputStream* out) const PRODUCT_RETURN; + + bool maybe_return_as_fields(ciInlineKlass** vk = nullptr) const; }; // -------------------------------------------------- @@ -1267,7 +1288,10 @@ class LIR_OpArrayCopy: public LIR_Op { unaligned = 1 << 9, src_objarray = 1 << 10, dst_objarray = 1 << 11, - all_flags = (1 << 12) - 1 + always_slow_path = 1 << 12, + src_inlinetype_check = 1 << 13, + dst_inlinetype_check = 1 << 14, + all_flags = (1 << 15) - 1 }; LIR_OpArrayCopy(LIR_Opr src, LIR_Opr src_pos, LIR_Opr dst, LIR_Opr dst_pos, LIR_Opr length, LIR_Opr tmp, @@ -1527,11 +1551,12 @@ class LIR_OpTypeCheck: public LIR_Op { int _profiled_bci; bool _should_profile; bool _fast_check; + bool _need_null_check; public: LIR_OpTypeCheck(LIR_Code code, LIR_Opr result, LIR_Opr object, ciKlass* klass, LIR_Opr tmp1, LIR_Opr tmp2, LIR_Opr tmp3, bool fast_check, - CodeEmitInfo* info_for_exception, CodeEmitInfo* info_for_patch, CodeStub* stub); + CodeEmitInfo* info_for_exception, CodeEmitInfo* info_for_patch, CodeStub* stub, bool need_null_check = true); LIR_OpTypeCheck(LIR_Code code, LIR_Opr object, LIR_Opr array, LIR_Opr tmp1, LIR_Opr tmp2, LIR_Opr tmp3, CodeEmitInfo* info_for_exception); @@ -1553,13 +1578,89 @@ class LIR_OpTypeCheck: public LIR_Op { ciMethod* profiled_method() const { return _profiled_method; } int profiled_bci() const { return _profiled_bci; } bool should_profile() const { return _should_profile; } - + bool need_null_check() const { return _need_null_check; } virtual bool is_patching() { return _info_for_patch != nullptr; } virtual void emit_code(LIR_Assembler* masm); virtual LIR_OpTypeCheck* as_OpTypeCheck() { return this; } void print_instr(outputStream* out) const PRODUCT_RETURN; }; +// LIR_OpFlattenedArrayCheck +class LIR_OpFlattenedArrayCheck: public LIR_Op { + friend class LIR_OpVisitState; + + private: + LIR_Opr _array; + LIR_Opr _value; + LIR_Opr _tmp; + CodeStub* _stub; +public: + LIR_OpFlattenedArrayCheck(LIR_Opr array, LIR_Opr value, LIR_Opr tmp, CodeStub* stub); + LIR_Opr array() const { return _array; } + LIR_Opr value() const { return _value; } + LIR_Opr tmp() const { return _tmp; } + CodeStub* stub() const { return _stub; } + + virtual void emit_code(LIR_Assembler* masm); + virtual LIR_OpFlattenedArrayCheck* as_OpFlattenedArrayCheck() { return this; } + virtual void print_instr(outputStream* out) const PRODUCT_RETURN; +}; + +// LIR_OpNullFreeArrayCheck +class LIR_OpNullFreeArrayCheck: public LIR_Op { + friend class LIR_OpVisitState; + + private: + LIR_Opr _array; + LIR_Opr _tmp; +public: + LIR_OpNullFreeArrayCheck(LIR_Opr array, LIR_Opr tmp); + LIR_Opr array() const { return _array; } + LIR_Opr tmp() const { return _tmp; } + + virtual void emit_code(LIR_Assembler* masm); + virtual LIR_OpNullFreeArrayCheck* as_OpNullFreeArrayCheck() { return this; } + virtual void print_instr(outputStream* out) const PRODUCT_RETURN; +}; + +class LIR_OpSubstitutabilityCheck: public LIR_Op { + friend class LIR_OpVisitState; + + private: + LIR_Opr _left; + LIR_Opr _right; + LIR_Opr _equal_result; + LIR_Opr _not_equal_result; + LIR_Opr _tmp1; + LIR_Opr _tmp2; + ciKlass* _left_klass; + ciKlass* _right_klass; + LIR_Opr _left_klass_op; + LIR_Opr _right_klass_op; + CodeStub* _stub; +public: + LIR_OpSubstitutabilityCheck(LIR_Opr result, LIR_Opr left, LIR_Opr right, LIR_Opr equal_result, LIR_Opr not_equal_result, + LIR_Opr tmp1, LIR_Opr tmp2, + ciKlass* left_klass, ciKlass* right_klass, LIR_Opr left_klass_op, LIR_Opr right_klass_op, + CodeEmitInfo* info, CodeStub* stub); + + LIR_Opr left() const { return _left; } + LIR_Opr right() const { return _right; } + LIR_Opr equal_result() const { return _equal_result; } + LIR_Opr not_equal_result() const { return _not_equal_result; } + LIR_Opr tmp1() const { return _tmp1; } + LIR_Opr tmp2() const { return _tmp2; } + ciKlass* left_klass() const { return _left_klass; } + ciKlass* right_klass() const { return _right_klass; } + LIR_Opr left_klass_op() const { return _left_klass_op; } + LIR_Opr right_klass_op() const { return _right_klass_op; } + CodeStub* stub() const { return _stub; } + + virtual void emit_code(LIR_Assembler* masm); + virtual LIR_OpSubstitutabilityCheck* as_OpSubstitutabilityCheck() { return this; } + virtual void print_instr(outputStream* out) const PRODUCT_RETURN; +}; + // LIR_Op2 class LIR_Op2: public LIR_Op { friend class LIR_OpVisitState; @@ -1718,9 +1819,10 @@ class LIR_OpAllocArray : public LIR_Op { CodeStub* _stub; BasicType _type; bool _zero_array; + bool _always_slow_path; public: - LIR_OpAllocArray(LIR_Opr klass, LIR_Opr len, LIR_Opr result, LIR_Opr t1, LIR_Opr t2, LIR_Opr t3, LIR_Opr t4, BasicType type, CodeStub* stub, bool zero_array) + LIR_OpAllocArray(LIR_Opr klass, LIR_Opr len, LIR_Opr result, LIR_Opr t1, LIR_Opr t2, LIR_Opr t3, LIR_Opr t4, BasicType type, CodeStub* stub, bool zero_array, bool always_slow_path) : LIR_Op(lir_alloc_array, result, nullptr) , _klass(klass) , _len(len) @@ -1730,7 +1832,8 @@ class LIR_OpAllocArray : public LIR_Op { , _tmp4(t4) , _stub(stub) , _type(type) - , _zero_array(zero_array) {} + , _zero_array(zero_array) + , _always_slow_path(always_slow_path) {} LIR_Opr klass() const { return _klass; } LIR_Opr len() const { return _len; } @@ -1741,7 +1844,8 @@ class LIR_OpAllocArray : public LIR_Op { LIR_Opr tmp4() const { return _tmp4; } BasicType type() const { return _type; } CodeStub* stub() const { return _stub; } - bool zero_array() const { return _zero_array; } + bool zero_array() const { return _zero_array; } + bool always_slow_path() const { return _always_slow_path; } virtual void emit_code(LIR_Assembler* masm); virtual LIR_OpAllocArray * as_OpAllocArray () { return this; } @@ -1848,20 +1952,23 @@ class LIR_OpLock: public LIR_Op { LIR_Opr _lock; LIR_Opr _scratch; CodeStub* _stub; + CodeStub* _throw_ie_stub; public: - LIR_OpLock(LIR_Code code, LIR_Opr hdr, LIR_Opr obj, LIR_Opr lock, LIR_Opr scratch, CodeStub* stub, CodeEmitInfo* info) + LIR_OpLock(LIR_Code code, LIR_Opr hdr, LIR_Opr obj, LIR_Opr lock, LIR_Opr scratch, CodeStub* stub, CodeEmitInfo* info, CodeStub* throw_ie_stub=nullptr) : LIR_Op(code, LIR_OprFact::illegalOpr, info) , _hdr(hdr) , _obj(obj) , _lock(lock) , _scratch(scratch) - , _stub(stub) {} + , _stub(stub) + , _throw_ie_stub(throw_ie_stub) {} LIR_Opr hdr_opr() const { return _hdr; } LIR_Opr obj_opr() const { return _obj; } LIR_Opr lock_opr() const { return _lock; } LIR_Opr scratch_opr() const { return _scratch; } CodeStub* stub() const { return _stub; } + CodeStub* throw_ie_stub() const { return _throw_ie_stub; } virtual void emit_code(LIR_Assembler* masm); virtual LIR_OpLock* as_OpLock() { return this; } @@ -2046,6 +2153,38 @@ class LIR_OpProfileType : public LIR_Op { virtual void print_instr(outputStream* out) const PRODUCT_RETURN; }; +// LIR_OpProfileInlineType +class LIR_OpProfileInlineType : public LIR_Op { + friend class LIR_OpVisitState; + + private: + LIR_Opr _mdp; + LIR_Opr _obj; + int _flag; + LIR_Opr _tmp; + bool _not_null; // true if we know statically that _obj cannot be null + + public: + // Destroys recv + LIR_OpProfileInlineType(LIR_Opr mdp, LIR_Opr obj, int flag, LIR_Opr tmp, bool not_null) + : LIR_Op(lir_profile_inline_type, LIR_OprFact::illegalOpr, nullptr) // no result, no info + , _mdp(mdp) + , _obj(obj) + , _flag(flag) + , _tmp(tmp) + , _not_null(not_null) { } + + LIR_Opr mdp() const { return _mdp; } + LIR_Opr obj() const { return _obj; } + int flag() const { return _flag; } + LIR_Opr tmp() const { return _tmp; } + bool not_null() const { return _not_null; } + + virtual void emit_code(LIR_Assembler* masm); + virtual LIR_OpProfileInlineType* as_OpProfileInlineType() { return this; } + virtual void print_instr(outputStream* out) const PRODUCT_RETURN; +}; + class LIR_InsertionBuffer; //--------------------------------LIR_List--------------------------------------------------- @@ -2270,7 +2409,7 @@ class LIR_List: public CompilationResourceObj { void irem(LIR_Opr left, int right, LIR_Opr res, LIR_Opr tmp, CodeEmitInfo* info); void allocate_object(LIR_Opr dst, LIR_Opr t1, LIR_Opr t2, LIR_Opr t3, LIR_Opr t4, int header_size, int object_size, LIR_Opr klass, bool init_check, CodeStub* stub); - void allocate_array(LIR_Opr dst, LIR_Opr len, LIR_Opr t1,LIR_Opr t2, LIR_Opr t3,LIR_Opr t4, BasicType type, LIR_Opr klass, CodeStub* stub, bool zero_array = true); + void allocate_array(LIR_Opr dst, LIR_Opr len, LIR_Opr t1,LIR_Opr t2, LIR_Opr t3,LIR_Opr t4, BasicType type, LIR_Opr klass, CodeStub* stub, bool zero_array = true, bool always_slow_path = false); // jump is an unconditional branch void jump(BlockBegin* block) { @@ -2317,7 +2456,7 @@ class LIR_List: public CompilationResourceObj { void load_stack_address_monitor(int monitor_ix, LIR_Opr dst) { append(new LIR_Op1(lir_monaddr, LIR_OprFact::intConst(monitor_ix), dst)); } void unlock_object(LIR_Opr hdr, LIR_Opr obj, LIR_Opr lock, LIR_Opr scratch, CodeStub* stub); - void lock_object(LIR_Opr hdr, LIR_Opr obj, LIR_Opr lock, LIR_Opr scratch, CodeStub* stub, CodeEmitInfo* info); + void lock_object(LIR_Opr hdr, LIR_Opr obj, LIR_Opr lock, LIR_Opr scratch, CodeStub* stub, CodeEmitInfo* info, CodeStub* throw_ie_stub=nullptr); void breakpoint() { append(new LIR_Op0(lir_breakpoint)); } @@ -2327,11 +2466,17 @@ class LIR_List: public CompilationResourceObj { void instanceof(LIR_Opr result, LIR_Opr object, ciKlass* klass, LIR_Opr tmp1, LIR_Opr tmp2, LIR_Opr tmp3, bool fast_check, CodeEmitInfo* info_for_patch, ciMethod* profiled_method, int profiled_bci); void store_check(LIR_Opr object, LIR_Opr array, LIR_Opr tmp1, LIR_Opr tmp2, LIR_Opr tmp3, CodeEmitInfo* info_for_exception, ciMethod* profiled_method, int profiled_bci); + void check_flat_array(LIR_Opr array, LIR_Opr value, LIR_Opr tmp, CodeStub* stub); + void check_null_free_array(LIR_Opr array, LIR_Opr tmp); + void substitutability_check(LIR_Opr result, LIR_Opr left, LIR_Opr right, LIR_Opr equal_result, LIR_Opr not_equal_result, + LIR_Opr tmp1, LIR_Opr tmp2, + ciKlass* left_klass, ciKlass* right_klass, LIR_Opr left_klass_op, LIR_Opr right_klass_op, + CodeEmitInfo* info, CodeStub* stub); void checkcast (LIR_Opr result, LIR_Opr object, ciKlass* klass, LIR_Opr tmp1, LIR_Opr tmp2, LIR_Opr tmp3, bool fast_check, CodeEmitInfo* info_for_exception, CodeEmitInfo* info_for_patch, CodeStub* stub, - ciMethod* profiled_method, int profiled_bci); + ciMethod* profiled_method, int profiled_bci, bool is_null_free); // MethodData* profiling void profile_call(ciMethod* method, int bci, ciMethod* callee, LIR_Opr mdo, LIR_Opr recv, LIR_Opr t1, ciKlass* cha_klass) { append(new LIR_OpProfileCall(method, bci, callee, mdo, recv, t1, cha_klass)); @@ -2339,6 +2484,9 @@ class LIR_List: public CompilationResourceObj { void profile_type(LIR_Address* mdp, LIR_Opr obj, ciKlass* exact_klass, intptr_t current_klass, LIR_Opr tmp, bool not_null, bool no_conflict) { append(new LIR_OpProfileType(LIR_OprFact::address(mdp), obj, exact_klass, current_klass, tmp, not_null, no_conflict)); } + void profile_inline_type(LIR_Address* mdp, LIR_Opr obj, int flag, LIR_Opr tmp, bool not_null) { + append(new LIR_OpProfileInlineType(LIR_OprFact::address(mdp), obj, flag, tmp, not_null)); + } void xadd(LIR_Opr src, LIR_Opr add, LIR_Opr res, LIR_Opr tmp) { append(new LIR_Op2(lir_xadd, src, add, res, tmp)); } void xchg(LIR_Opr src, LIR_Opr set, LIR_Opr res, LIR_Opr tmp) { append(new LIR_Op2(lir_xchg, src, set, res, tmp)); } diff --git a/src/hotspot/share/c1/c1_LIRAssembler.cpp b/src/hotspot/share/c1/c1_LIRAssembler.cpp index 7cf414ae7dc..5a9f11634fe 100644 --- a/src/hotspot/share/c1/c1_LIRAssembler.cpp +++ b/src/hotspot/share/c1/c1_LIRAssembler.cpp @@ -29,9 +29,11 @@ #include "c1/c1_LIRAssembler.hpp" #include "c1/c1_MacroAssembler.hpp" #include "c1/c1_ValueStack.hpp" +#include "ci/ciInlineKlass.hpp" #include "compiler/compilerDefinitions.inline.hpp" #include "compiler/oopMap.hpp" #include "runtime/os.hpp" +#include "runtime/sharedRuntime.hpp" #include "runtime/vm_version.hpp" void LIR_Assembler::patching_epilog(PatchingStub* patch, LIR_PatchCode patch_code, Register obj, CodeEmitInfo* info) { @@ -117,6 +119,7 @@ LIR_Assembler::~LIR_Assembler() { // The unwind handler label may be unnbound if this destructor is invoked because of a bail-out. // Reset it here to avoid an assertion. _unwind_handler_entry.reset(); + _verified_inline_entry.reset(); } @@ -328,7 +331,6 @@ void LIR_Assembler::check_no_unbound_labels() { //----------------------------------debug info-------------------------------- - void LIR_Assembler::add_debug_info_for_branch(CodeEmitInfo* info) { int pc_offset = code_offset(); flush_debug_info(pc_offset); @@ -338,10 +340,9 @@ void LIR_Assembler::add_debug_info_for_branch(CodeEmitInfo* info) { } } - -void LIR_Assembler::add_call_info(int pc_offset, CodeEmitInfo* cinfo) { +void LIR_Assembler::add_call_info(int pc_offset, CodeEmitInfo* cinfo, bool maybe_return_as_fields) { flush_debug_info(pc_offset); - cinfo->record_debug_info(compilation()->debug_info_recorder(), pc_offset); + cinfo->record_debug_info(compilation()->debug_info_recorder(), pc_offset, maybe_return_as_fields); if (cinfo->exception_handlers() != nullptr) { compilation()->add_exception_handlers_for_pco(pc_offset, cinfo->exception_handlers()); } @@ -485,6 +486,12 @@ void LIR_Assembler::emit_call(LIR_OpJavaCall* op) { if (op->is_method_handle_invoke()) { compilation()->set_has_method_handle_invokes(true); } + + ciInlineKlass* vk = nullptr; + if (op->maybe_return_as_fields(&vk)) { + int offset = store_inline_type_fields_to_buf(vk); + add_call_info(offset, op->info(), true); + } } @@ -584,6 +591,141 @@ void LIR_Assembler::emit_op1(LIR_Op1* op) { } } +void LIR_Assembler::add_scalarized_entry_info(int pc_offset) { + flush_debug_info(pc_offset); + DebugInformationRecorder* debug_info = compilation()->debug_info_recorder(); + // The VEP and VIEP(RO) of a C1-compiled method call buffer_inline_args_xxx() + // before doing any argument shuffling. This call may cause GC. When GC happens, + // all the parameters are still as passed by the caller, so we just use + // map->set_include_argument_oops() inside frame::sender_for_compiled_frame(RegisterMap* map). + // There's no need to build a GC map here. + OopMap* oop_map = new OopMap(0, 0); + debug_info->add_safepoint(pc_offset, oop_map); + DebugToken* locvals = debug_info->create_scope_values(nullptr); // FIXME is this needed (for Java debugging to work properly??) + DebugToken* expvals = debug_info->create_scope_values(nullptr); // FIXME is this needed (for Java debugging to work properly??) + DebugToken* monvals = debug_info->create_monitor_values(nullptr); // FIXME: need testing with synchronized method + bool reexecute = false; + bool return_oop = false; // This flag will be ignored since it used only for C2 with escape analysis. + bool rethrow_exception = false; + bool is_method_handle_invoke = false; + debug_info->describe_scope(pc_offset, methodHandle(), method(), 0, reexecute, rethrow_exception, is_method_handle_invoke, return_oop, false, locvals, expvals, monvals); + debug_info->end_safepoint(pc_offset); +} + +// The entries points of C1-compiled methods can have the following types: +// (1) Methods with no inline type args +// (2) Methods with inline type receiver but no inline type args +// VIEP_RO is the same as VIEP +// (3) Methods with non-inline type receiver and some inline type args +// VIEP_RO is the same as VEP +// (4) Methods with inline type receiver and other inline type args +// Separate VEP, VIEP and VIEP_RO +// +// (1) (2) (3) (4) +// UEP/UIEP: VEP: UEP: UEP: +// check_icache pack receiver check_icache check_icache +// VEP/VIEP/VIEP_RO jump to VIEP VEP/VIEP_RO: VIEP_RO: +// body UEP/UIEP: pack inline args pack inline args (except receiver) +// check_icache jump to VIEP jump to VIEP +// VIEP/VIEP_RO UIEP: VEP: +// body check_icache pack all inline args +// VIEP: jump to VIEP +// body UIEP: +// check_icache +// VIEP: +// body +void LIR_Assembler::emit_std_entries() { + offsets()->set_value(CodeOffsets::OSR_Entry, _masm->offset()); + + _masm->align(CodeEntryAlignment); + const CompiledEntrySignature* ces = compilation()->compiled_entry_signature(); + if (ces->has_scalarized_args()) { + assert(InlineTypePassFieldsAsArgs && method()->get_Method()->has_scalarized_args(), "must be"); + CodeOffsets::Entries ro_entry_type = ces->c1_inline_ro_entry_type(); + + // UEP: check icache and fall-through + if (ro_entry_type != CodeOffsets::Verified_Inline_Entry) { + offsets()->set_value(CodeOffsets::Entry, _masm->offset()); + if (needs_icache(method())) { + check_icache(); + } + } + + // VIEP_RO: pack all value parameters, except the receiver + if (ro_entry_type == CodeOffsets::Verified_Inline_Entry_RO) { + emit_std_entry(CodeOffsets::Verified_Inline_Entry_RO, ces); + } + + // VEP: pack all value parameters + _masm->align(CodeEntryAlignment); + emit_std_entry(CodeOffsets::Verified_Entry, ces); + + // UIEP: check icache and fall-through + _masm->align(CodeEntryAlignment); + offsets()->set_value(CodeOffsets::Inline_Entry, _masm->offset()); + if (ro_entry_type == CodeOffsets::Verified_Inline_Entry) { + // Special case if we have VIEP == VIEP(RO): + // this means UIEP (called by C1) == UEP (called by C2). + offsets()->set_value(CodeOffsets::Entry, _masm->offset()); + } + if (needs_icache(method())) { + check_icache(); + } + + // VIEP: all value parameters are passed as refs - no packing. + emit_std_entry(CodeOffsets::Verified_Inline_Entry, nullptr); + + if (ro_entry_type != CodeOffsets::Verified_Inline_Entry_RO) { + // The VIEP(RO) is the same as VEP or VIEP + assert(ro_entry_type == CodeOffsets::Verified_Entry || + ro_entry_type == CodeOffsets::Verified_Inline_Entry, "must be"); + offsets()->set_value(CodeOffsets::Verified_Inline_Entry_RO, + offsets()->value(ro_entry_type)); + } + } else { + // All 3 entries are the same (no inline type packing) + offsets()->set_value(CodeOffsets::Entry, _masm->offset()); + offsets()->set_value(CodeOffsets::Inline_Entry, _masm->offset()); + if (needs_icache(method())) { + check_icache(); + } + emit_std_entry(CodeOffsets::Verified_Inline_Entry, nullptr); + offsets()->set_value(CodeOffsets::Verified_Entry, offsets()->value(CodeOffsets::Verified_Inline_Entry)); + offsets()->set_value(CodeOffsets::Verified_Inline_Entry_RO, offsets()->value(CodeOffsets::Verified_Inline_Entry)); + } +} + +void LIR_Assembler::emit_std_entry(CodeOffsets::Entries entry, const CompiledEntrySignature* ces) { + offsets()->set_value(entry, _masm->offset()); + _masm->verified_entry(compilation()->directive()->BreakAtExecuteOption); + switch (entry) { + case CodeOffsets::Verified_Entry: { + if (needs_clinit_barrier_on_entry(method())) { + clinit_barrier(method()); + } + int rt_call_offset = _masm->verified_entry(ces, initial_frame_size_in_bytes(), bang_size_in_bytes(), in_bytes(frame_map()->sp_offset_for_orig_pc()), _verified_inline_entry); + add_scalarized_entry_info(rt_call_offset); + break; + } + case CodeOffsets::Verified_Inline_Entry_RO: { + assert(!needs_clinit_barrier_on_entry(method()), "can't be static"); + int rt_call_offset = _masm->verified_inline_ro_entry(ces, initial_frame_size_in_bytes(), bang_size_in_bytes(), in_bytes(frame_map()->sp_offset_for_orig_pc()), _verified_inline_entry); + add_scalarized_entry_info(rt_call_offset); + break; + } + case CodeOffsets::Verified_Inline_Entry: { + if (needs_clinit_barrier_on_entry(method())) { + clinit_barrier(method()); + } + build_frame(); + offsets()->set_value(CodeOffsets::Frame_Complete, _masm->offset()); + break; + } + default: + ShouldNotReachHere(); + break; + } +} void LIR_Assembler::emit_op0(LIR_Op0* op) { switch (op->code()) { @@ -596,23 +738,9 @@ void LIR_Assembler::emit_op0(LIR_Op0* op) { Unimplemented(); break; - case lir_std_entry: { - // init offsets - offsets()->set_value(CodeOffsets::OSR_Entry, _masm->offset()); - if (needs_icache(compilation()->method())) { - int offset = check_icache(); - offsets()->set_value(CodeOffsets::Entry, offset); - } - _masm->align(CodeEntryAlignment); - offsets()->set_value(CodeOffsets::Verified_Entry, _masm->offset()); - _masm->verified_entry(compilation()->directive()->BreakAtExecuteOption); - if (needs_clinit_barrier_on_entry(compilation()->method())) { - clinit_barrier(compilation()->method()); - } - build_frame(); - offsets()->set_value(CodeOffsets::Frame_Complete, _masm->offset()); + case lir_std_entry: + emit_std_entries(); break; - } case lir_osr_entry: offsets()->set_value(CodeOffsets::OSR_Entry, _masm->offset()); @@ -665,6 +793,10 @@ void LIR_Assembler::emit_op0(LIR_Op0* op) { on_spin_wait(); break; + case lir_check_orig_pc: + check_orig_pc(); + break; + default: ShouldNotReachHere(); break; @@ -750,7 +882,8 @@ void LIR_Assembler::emit_op4(LIR_Op4* op) { } void LIR_Assembler::build_frame() { - _masm->build_frame(initial_frame_size_in_bytes(), bang_size_in_bytes()); + _masm->build_frame(initial_frame_size_in_bytes(), bang_size_in_bytes(), in_bytes(frame_map()->sp_offset_for_orig_pc()), + needs_stack_repair(), method()->has_scalarized_args(), &_verified_inline_entry); } diff --git a/src/hotspot/share/c1/c1_LIRAssembler.hpp b/src/hotspot/share/c1/c1_LIRAssembler.hpp index a4c5fd61d4c..ca71c1bd1fa 100644 --- a/src/hotspot/share/c1/c1_LIRAssembler.hpp +++ b/src/hotspot/share/c1/c1_LIRAssembler.hpp @@ -31,6 +31,7 @@ #include "utilities/macros.hpp" class Compilation; +class CompiledEntrySignature; class ScopeValue; class LIR_Assembler: public CompilationResourceObj { @@ -47,6 +48,7 @@ class LIR_Assembler: public CompilationResourceObj { int _immediate_oops_patched; Label _unwind_handler_entry; + Label _verified_inline_entry; #ifdef ASSERT BlockList _branch_target_blocks; @@ -91,6 +93,10 @@ class LIR_Assembler: public CompilationResourceObj { void emit_stubs(CodeStubList* stub_list); + bool needs_stack_repair() const { + return compilation()->needs_stack_repair(); + } + public: // addresses Address as_Address(LIR_Address* addr); @@ -98,7 +104,7 @@ class LIR_Assembler: public CompilationResourceObj { Address as_Address_hi(LIR_Address* addr); // debug information - void add_call_info(int pc_offset, CodeEmitInfo* cinfo); + void add_call_info(int pc_offset, CodeEmitInfo* cinfo, bool maybe_return_as_fields = false); void add_debug_info_for_branch(CodeEmitInfo* info); void add_debug_info_for_div0(int pc_offset, CodeEmitInfo* cinfo); void add_debug_info_for_div0_here(CodeEmitInfo* info); @@ -196,6 +202,9 @@ class LIR_Assembler: public CompilationResourceObj { void emit_alloc_obj(LIR_OpAllocObj* op); void emit_alloc_array(LIR_OpAllocArray* op); void emit_opTypeCheck(LIR_OpTypeCheck* op); + void emit_opFlattenedArrayCheck(LIR_OpFlattenedArrayCheck* op); + void emit_opNullFreeArrayCheck(LIR_OpNullFreeArrayCheck* op); + void emit_opSubstitutabilityCheck(LIR_OpSubstitutabilityCheck* op); void emit_typecheck_helper(LIR_OpTypeCheck *op, Label* success, Label* failure, Label* obj_is_null); void emit_compare_and_swap(LIR_OpCompareAndSwap* op); void emit_lock(LIR_OpLock* op); @@ -204,7 +213,11 @@ class LIR_Assembler: public CompilationResourceObj { void emit_rtcall(LIR_OpRTCall* op); void emit_profile_call(LIR_OpProfileCall* op); void emit_profile_type(LIR_OpProfileType* op); + void emit_profile_inline_type(LIR_OpProfileInlineType* op); void emit_delay(LIR_OpDelay* op); + void emit_std_entries(); + void emit_std_entry(CodeOffsets::Entries entry, const CompiledEntrySignature* ces); + void add_scalarized_entry_info(int call_offset); void arith_op(LIR_Code code, LIR_Opr left, LIR_Opr right, LIR_Opr dest, CodeEmitInfo* info); void arithmetic_idiv(LIR_Code code, LIR_Opr left, LIR_Opr right, LIR_Opr temp, LIR_Opr result, CodeEmitInfo* info); @@ -225,6 +238,7 @@ class LIR_Assembler: public CompilationResourceObj { void call( LIR_OpJavaCall* op, relocInfo::relocType rtype); void ic_call( LIR_OpJavaCall* op); void vtable_call( LIR_OpJavaCall* op); + int store_inline_type_fields_to_buf(ciInlineKlass* vk); void osr_entry(); @@ -251,6 +265,7 @@ class LIR_Assembler: public CompilationResourceObj { void membar_storeload(); void on_spin_wait(); void get_thread(LIR_Opr result); + void check_orig_pc(); void verify_oop_map(CodeEmitInfo* info); diff --git a/src/hotspot/share/c1/c1_LIRGenerator.cpp b/src/hotspot/share/c1/c1_LIRGenerator.cpp index cbd84b3a11e..720f94899fb 100644 --- a/src/hotspot/share/c1/c1_LIRGenerator.cpp +++ b/src/hotspot/share/c1/c1_LIRGenerator.cpp @@ -30,8 +30,11 @@ #include "c1/c1_LIRGenerator.hpp" #include "c1/c1_ValueStack.hpp" #include "ci/ciArrayKlass.hpp" +#include "ci/ciFlatArrayKlass.hpp" +#include "ci/ciInlineKlass.hpp" #include "ci/ciInstance.hpp" #include "ci/ciObjArray.hpp" +#include "ci/ciObjArrayKlass.hpp" #include "ci/ciUtilities.hpp" #include "compiler/compilerDefinitions.inline.hpp" #include "compiler/compilerOracle.hpp" @@ -215,6 +218,8 @@ void LIRItem::set_result(LIR_Opr opr) { } void LIRItem::load_item() { + assert(!_gen->in_conditional_code(), "LIRItem cannot be loaded in conditional code"); + if (result()->is_illegal()) { // update the items result _result = value()->operand(); @@ -622,12 +627,13 @@ void LIRGenerator::logic_op (Bytecodes::Code code, LIR_Opr result_op, LIR_Opr le } -void LIRGenerator::monitor_enter(LIR_Opr object, LIR_Opr lock, LIR_Opr hdr, LIR_Opr scratch, int monitor_no, CodeEmitInfo* info_for_exception, CodeEmitInfo* info) { +void LIRGenerator::monitor_enter(LIR_Opr object, LIR_Opr lock, LIR_Opr hdr, LIR_Opr scratch, int monitor_no, + CodeEmitInfo* info_for_exception, CodeEmitInfo* info, CodeStub* throw_ie_stub) { // for slow path, use debug info for state after successful locking - CodeStub* slow_path = new MonitorEnterStub(object, lock, info); + CodeStub* slow_path = new MonitorEnterStub(object, lock, info, throw_ie_stub, scratch); __ load_stack_address_monitor(monitor_no, lock); // for handling NullPointerException, use debug info representing just the lock stack before this monitorenter - __ lock_object(hdr, object, lock, scratch, slow_path, info_for_exception); + __ lock_object(hdr, object, lock, scratch, slow_path, info_for_exception, throw_ie_stub); } @@ -650,10 +656,15 @@ void LIRGenerator::print_if_not_loaded(const NewInstance* new_instance) { } #endif -void LIRGenerator::new_instance(LIR_Opr dst, ciInstanceKlass* klass, bool is_unresolved, LIR_Opr scratch1, LIR_Opr scratch2, LIR_Opr scratch3, LIR_Opr scratch4, LIR_Opr klass_reg, CodeEmitInfo* info) { - klass2reg_with_patching(klass_reg, klass, info, is_unresolved); - // If klass is not loaded we do not know if the klass has finalizers: - if (UseFastNewInstance && klass->is_loaded() +void LIRGenerator::new_instance(LIR_Opr dst, ciInstanceKlass* klass, bool is_unresolved, bool allow_inline, LIR_Opr scratch1, LIR_Opr scratch2, LIR_Opr scratch3, LIR_Opr scratch4, LIR_Opr klass_reg, CodeEmitInfo* info) { + if (allow_inline) { + assert(!is_unresolved && klass->is_loaded(), "inline type klass should be resolved"); + __ metadata2reg(klass->constant_encoding(), klass_reg); + } else { + klass2reg_with_patching(klass_reg, klass, info, is_unresolved); + } + // If klass is not loaded we do not know if the klass has finalizers or is an unexpected inline klass + if (UseFastNewInstance && klass->is_loaded() && (allow_inline || !klass->is_inlinetype()) && !Klass::layout_helper_needs_slow_path(klass->layout_helper())) { StubId stub_id = klass->is_initialized() ? StubId::c1_fast_new_instance_id : StubId::c1_fast_new_instance_init_check_id; @@ -668,7 +679,7 @@ void LIRGenerator::new_instance(LIR_Opr dst, ciInstanceKlass* klass, bool is_unr oopDesc::header_size(), instance_size, klass_reg, !klass->is_initialized(), slow_path); } else { CodeStub* slow_path = new NewInstanceStub(klass_reg, dst, klass, info, StubId::c1_new_instance_id); - __ branch(lir_cond_always, slow_path); + __ jump(slow_path); __ branch_destination(slow_path->continuation()); } } @@ -768,6 +779,16 @@ void LIRGenerator::arraycopy_helper(Intrinsic* x, int* flagsp, ciArrayKlass** ex // of the required checks for a fast case can be elided. int flags = LIR_OpArrayCopy::all_flags; + if (!src->is_loaded_flat_array() && !dst->is_loaded_flat_array()) { + flags &= ~LIR_OpArrayCopy::always_slow_path; + } + if (!src->maybe_flat_array()) { + flags &= ~LIR_OpArrayCopy::src_inlinetype_check; + } + if (!dst->maybe_flat_array() && !dst->maybe_null_free_array()) { + flags &= ~LIR_OpArrayCopy::dst_inlinetype_check; + } + if (!src_objarray) flags &= ~LIR_OpArrayCopy::src_objarray; if (!dst_objarray) @@ -875,6 +896,12 @@ void LIRGenerator::arraycopy_helper(Intrinsic* x, int* flagsp, ciArrayKlass** ex } } *flagsp = flags; + + // TODO 8366668 + if (expected_type != nullptr && expected_type->is_obj_array_klass()) { + expected_type = ciArrayKlass::make(expected_type->as_array_klass()->element_klass(), false, true, true); + } + *expected_typep = (ciArrayKlass*)expected_type; } @@ -1460,7 +1487,7 @@ LIR_Opr LIRGenerator::load_constant(Constant* x) { LIR_Opr LIRGenerator::load_constant(LIR_Const* c) { BasicType t = c->type(); - for (int i = 0; i < _constants.length(); i++) { + for (int i = 0; i < _constants.length() && !in_conditional_code(); i++) { LIR_Const* other = _constants.at(i); if (t == other->type()) { switch (t) { @@ -1485,11 +1512,19 @@ LIR_Opr LIRGenerator::load_constant(LIR_Const* c) { LIR_Opr result = new_register(t); __ move((LIR_Opr)c, result); - _constants.append(c); - _reg_for_constants.append(result); + if (!in_conditional_code()) { + _constants.append(c); + _reg_for_constants.append(result); + } return result; } +void LIRGenerator::set_in_conditional_code(bool v) { + assert(v != _in_conditional_code, "must change state"); + _in_conditional_code = v; +} + + //------------------------field access-------------------------------------- void LIRGenerator::do_CompareAndSwap(Intrinsic* x, ValueType* type) { @@ -1507,6 +1542,14 @@ void LIRGenerator::do_CompareAndSwap(Intrinsic* x, ValueType* type) { set_result(x, result); } +// Returns a int/long value with the null marker bit set +static LIR_Opr null_marker_mask(BasicType bt, ciField* field) { + assert(field->null_marker_offset() != -1, "field does not have null marker"); + int nm_offset = field->null_marker_offset() - field->offset_in_bytes(); + jlong null_marker = 1ULL << (nm_offset << LogBitsPerByte); + return (bt == T_LONG) ? LIR_OprFact::longConst(null_marker) : LIR_OprFact::intConst(null_marker); +} + // Comment copied form templateTable_i486.cpp // ---------------------------------------------------------------------------- // Volatile variables demand their effects be made known to all CPU's in @@ -1536,8 +1579,9 @@ void LIRGenerator::do_CompareAndSwap(Intrinsic* x, ValueType* type) { void LIRGenerator::do_StoreField(StoreField* x) { + ciField* field = x->field(); bool needs_patching = x->needs_patching(); - bool is_volatile = x->field()->is_volatile(); + bool is_volatile = field->is_volatile(); BasicType field_type = x->field_type(); CodeEmitInfo* info = nullptr; @@ -1558,18 +1602,22 @@ void LIRGenerator::do_StoreField(StoreField* x) { object.load_item(); - if (is_volatile || needs_patching) { - // load item if field is volatile (fewer special cases for volatiles) - // load item if field not initialized - // load item if field not constant - // because of code patching we cannot inline constants - if (field_type == T_BYTE || field_type == T_BOOLEAN) { - value.load_byte_item(); - } else { - value.load_item(); - } + if (field->is_flat()) { + value.load_item(); } else { - value.load_for_store(field_type); + if (is_volatile || needs_patching) { + // load item if field is volatile (fewer special cases for volatiles) + // load item if field not initialized + // load item if field not constant + // because of code patching we cannot inline constants + if (field_type == T_BYTE || field_type == T_BOOLEAN) { + value.load_byte_item(); + } else { + value.load_item(); + } + } else { + value.load_for_store(field_type); + } } set_no_result(x); @@ -1598,18 +1646,218 @@ void LIRGenerator::do_StoreField(StoreField* x) { decorators |= C1_NEEDS_PATCHING; } + if (field->is_flat()) { + ciInlineKlass* vk = field->type()->as_inline_klass(); + +#ifdef ASSERT + bool is_naturally_atomic = vk->nof_declared_nonstatic_fields() <= 1; + bool needs_atomic_access = !field->is_null_free() || (field->is_volatile() && !is_naturally_atomic); + assert(needs_atomic_access, "No atomic access required"); + // ZGC does not support compressed oops, so only one oop can be in the payload which is written by a "normal" oop store. + assert(!vk->contains_oops() || !UseZGC, "ZGC does not support embedded oops in flat fields"); +#endif + + // Zero the payload + BasicType bt = vk->atomic_size_to_basic_type(field->is_null_free()); + LIR_Opr payload = new_register((bt == T_LONG) ? bt : T_INT); + LIR_Opr zero = (bt == T_LONG) ? LIR_OprFact::longConst(0) : LIR_OprFact::intConst(0); + __ move(zero, payload); + + bool is_constant_null = value.is_constant() && value.value()->is_null_obj(); + if (!is_constant_null) { + LabelObj* L_isNull = new LabelObj(); + bool needs_null_check = !value.is_constant() || value.value()->is_null_obj(); + if (needs_null_check) { + __ cmp(lir_cond_equal, value.result(), LIR_OprFact::oopConst(nullptr)); + __ branch(lir_cond_equal, L_isNull->label()); + } + // Load payload (if not empty) and set null marker (if not null-free) + if (!vk->is_empty()) { + access_load_at(decorators, bt, value, LIR_OprFact::intConst(vk->payload_offset()), payload); + } + if (!field->is_null_free()) { + __ logical_or(payload, null_marker_mask(bt, field), payload); + } + if (needs_null_check) { + __ branch_destination(L_isNull->label()); + } + } + access_store_at(decorators, bt, object, LIR_OprFact::intConst(x->offset()), payload, + // Make sure to emit an implicit null check and pass the information + // that this is a flat store that might require gc barriers for oop fields. + info != nullptr ? new CodeEmitInfo(info) : nullptr, info, vk); + return; + } + access_store_at(decorators, field_type, object, LIR_OprFact::intConst(x->offset()), value.result(), info != nullptr ? new CodeEmitInfo(info) : nullptr, info); } +// FIXME -- I can't find any other way to pass an address to access_load_at(). +class TempResolvedAddress: public Instruction { + public: + TempResolvedAddress(ValueType* type, LIR_Opr addr) : Instruction(type) { + set_operand(addr); + } + virtual void input_values_do(ValueVisitor*) {} + virtual void visit(InstructionVisitor* v) {} + virtual const char* name() const { return "TempResolvedAddress"; } +}; + +LIR_Opr LIRGenerator::get_and_load_element_address(LIRItem& array, LIRItem& index) { + ciType* array_type = array.value()->declared_type(); + ciFlatArrayKlass* flat_array_klass = array_type->as_flat_array_klass(); + assert(flat_array_klass->is_loaded(), "must be"); + + int array_header_size = flat_array_klass->array_header_in_bytes(); + int shift = flat_array_klass->log2_element_size(); + +#ifndef _LP64 + LIR_Opr index_op = new_register(T_INT); + // FIXME -- on 32-bit, the shift below can overflow, so we need to check that + // the top (shift+1) bits of index_op must be zero, or + // else throw ArrayIndexOutOfBoundsException + if (index.result()->is_constant()) { + jint const_index = index.result()->as_jint(); + __ move(LIR_OprFact::intConst(const_index << shift), index_op); + } else { + __ shift_left(index_op, shift, index.result()); + } +#else + LIR_Opr index_op = new_register(T_LONG); + if (index.result()->is_constant()) { + jint const_index = index.result()->as_jint(); + __ move(LIR_OprFact::longConst(const_index << shift), index_op); + } else { + __ convert(Bytecodes::_i2l, index.result(), index_op); + // Need to shift manually, as LIR_Address can scale only up to 3. + __ shift_left(index_op, shift, index_op); + } +#endif + + LIR_Opr elm_op = new_pointer_register(); + LIR_Address* elm_address = generate_address(array.result(), index_op, 0, array_header_size, T_ADDRESS); + __ leal(LIR_OprFact::address(elm_address), elm_op); + return elm_op; +} + +void LIRGenerator::access_sub_element(LIRItem& array, LIRItem& index, LIR_Opr& result, ciField* field, int sub_offset) { + assert(field != nullptr, "Need a subelement type specified"); + + // Find the starting address of the source (inside the array) + LIR_Opr elm_op = get_and_load_element_address(array, index); + + BasicType subelt_type = field->type()->basic_type(); + TempResolvedAddress* elm_resolved_addr = new TempResolvedAddress(as_ValueType(subelt_type), elm_op); + LIRItem elm_item(elm_resolved_addr, this); + + DecoratorSet decorators = IN_HEAP; + access_load_at(decorators, subelt_type, + elm_item, LIR_OprFact::intConst(sub_offset), result, + nullptr, nullptr); +} + +void LIRGenerator::access_flat_array(bool is_load, LIRItem& array, LIRItem& index, LIRItem& obj_item, + ciField* field, int sub_offset) { + assert(sub_offset == 0 || field != nullptr, "Sanity check"); + + // Find the starting address of the source (inside the array) + LIR_Opr elm_op = get_and_load_element_address(array, index); + + ciInlineKlass* elem_klass = nullptr; + if (field != nullptr) { + elem_klass = field->type()->as_inline_klass(); + } else { + elem_klass = array.value()->declared_type()->as_flat_array_klass()->element_klass()->as_inline_klass(); + } + for (int i = 0; i < elem_klass->nof_nonstatic_fields(); i++) { + ciField* inner_field = elem_klass->nonstatic_field_at(i); + assert(!inner_field->is_flat(), "flat fields must have been expanded"); + int obj_offset = inner_field->offset_in_bytes(); + int elm_offset = obj_offset - elem_klass->payload_offset() + sub_offset; // object header is not stored in array. + BasicType field_type = inner_field->type()->basic_type(); + + // Types which are smaller than int are still passed in an int register. + BasicType reg_type = field_type; + switch (reg_type) { + case T_BYTE: + case T_BOOLEAN: + case T_SHORT: + case T_CHAR: + reg_type = T_INT; + break; + default: + break; + } + + LIR_Opr temp = new_register(reg_type); + TempResolvedAddress* elm_resolved_addr = new TempResolvedAddress(as_ValueType(field_type), elm_op); + LIRItem elm_item(elm_resolved_addr, this); + + DecoratorSet decorators = IN_HEAP; + if (is_load) { + access_load_at(decorators, field_type, + elm_item, LIR_OprFact::intConst(elm_offset), temp, + nullptr, nullptr); + access_store_at(decorators, field_type, + obj_item, LIR_OprFact::intConst(obj_offset), temp, + nullptr, nullptr); + } else { + access_load_at(decorators, field_type, + obj_item, LIR_OprFact::intConst(obj_offset), temp, + nullptr, nullptr); + access_store_at(decorators, field_type, + elm_item, LIR_OprFact::intConst(elm_offset), temp, + nullptr, nullptr); + } + } +} + +void LIRGenerator::check_flat_array(LIR_Opr array, LIR_Opr value, CodeStub* slow_path) { + LIR_Opr tmp = new_register(T_METADATA); + __ check_flat_array(array, value, tmp, slow_path); +} + +void LIRGenerator::check_null_free_array(LIRItem& array, LIRItem& value, CodeEmitInfo* info) { + LabelObj* L_end = new LabelObj(); + LIR_Opr tmp = new_register(T_METADATA); + __ check_null_free_array(array.result(), tmp); + __ branch(lir_cond_equal, L_end->label()); + __ null_check(value.result(), info); + __ branch_destination(L_end->label()); +} + +bool LIRGenerator::needs_flat_array_store_check(StoreIndexed* x) { + if (x->elt_type() == T_OBJECT && x->array()->maybe_flat_array()) { + ciType* type = x->value()->declared_type(); + if (type != nullptr && type->is_klass()) { + ciKlass* klass = type->as_klass(); + if (!klass->can_be_inline_klass() || (klass->is_inlinetype() && !klass->as_inline_klass()->maybe_flat_in_array())) { + // This is known to be a non-flat object. If the array is a flat array, + // it will be caught by the code generated by array_store_check(). + return false; + } + } + // We're not 100% sure, so let's do the flat_array_store_check. + return true; + } + return false; +} + +bool LIRGenerator::needs_null_free_array_store_check(StoreIndexed* x) { + return x->elt_type() == T_OBJECT && x->array()->maybe_null_free_array(); +} + void LIRGenerator::do_StoreIndexed(StoreIndexed* x) { assert(x->is_pinned(),""); + assert(x->elt_type() != T_ARRAY, "never used"); + bool is_loaded_flat_array = x->array()->is_loaded_flat_array(); bool needs_range_check = x->compute_needs_range_check(); bool use_length = x->length() != nullptr; bool obj_store = is_reference_type(x->elt_type()); - bool needs_store_check = obj_store && (x->value()->as_Constant() == nullptr || - !get_jobject_constant(x->value())->is_null_object() || - x->should_profile()); + bool needs_store_check = obj_store && !(is_loaded_flat_array && x->is_exact_flat_array_store()) && + (x->value()->as_Constant() == nullptr || + !get_jobject_constant(x->value())->is_null_object()); LIRItem array(x->array(), this); LIRItem index(x->index(), this); @@ -1622,9 +1870,10 @@ void LIRGenerator::do_StoreIndexed(StoreIndexed* x) { if (use_length && needs_range_check) { length.set_instruction(x->length()); length.load_item(); - } - if (needs_store_check || x->check_boolean()) { + + if (needs_store_check || x->check_boolean() + || is_loaded_flat_array || needs_flat_array_store_check(x) || needs_null_free_array_store_check(x)) { value.load_item(); } else { value.load_for_store(x->elt_type()); @@ -1652,18 +1901,65 @@ void LIRGenerator::do_StoreIndexed(StoreIndexed* x) { } } + if (x->should_profile()) { + if (is_loaded_flat_array) { + // No need to profile a store to a flat array of known type. This can happen if + // the type only became known after optimizations (for example, after the PhiSimplifier). + x->set_should_profile(false); + } else { + int bci = x->profiled_bci(); + ciMethodData* md = x->profiled_method()->method_data(); + assert(md != nullptr, "Sanity"); + ciProfileData* data = md->bci_to_data(bci); + assert(data != nullptr && data->is_ArrayStoreData(), "incorrect profiling entry"); + ciArrayStoreData* store_data = (ciArrayStoreData*)data; + profile_array_type(x, md, store_data); + assert(store_data->is_ArrayStoreData(), "incorrect profiling entry"); + if (x->array()->maybe_null_free_array()) { + profile_null_free_array(array, md, store_data); + } + } + } + if (GenerateArrayStoreCheck && needs_store_check) { CodeEmitInfo* store_check_info = new CodeEmitInfo(range_check_info); array_store_check(value.result(), array.result(), store_check_info, x->profiled_method(), x->profiled_bci()); } - DecoratorSet decorators = IN_HEAP | IS_ARRAY; - if (x->check_boolean()) { - decorators |= C1_MASK_BOOLEAN; - } + if (is_loaded_flat_array) { + // TODO 8350865 This is currently dead code + if (!x->value()->is_null_free()) { + __ null_check(value.result(), new CodeEmitInfo(range_check_info)); + } + // If array element is an empty inline type, no need to copy anything + if (!x->array()->declared_type()->as_flat_array_klass()->element_klass()->as_inline_klass()->is_empty()) { + access_flat_array(false, array, index, value); + } + } else { + StoreFlattenedArrayStub* slow_path = nullptr; + + if (needs_flat_array_store_check(x)) { + // Check if we indeed have a flat array + index.load_item(); + slow_path = new StoreFlattenedArrayStub(array.result(), index.result(), value.result(), state_for(x, x->state_before())); + check_flat_array(array.result(), value.result(), slow_path); + set_in_conditional_code(true); + } else if (needs_null_free_array_store_check(x)) { + CodeEmitInfo* info = new CodeEmitInfo(range_check_info); + check_null_free_array(array, value, info); + } - access_store_at(decorators, x->elt_type(), array, index.result(), value.result(), - nullptr, null_check_info); + DecoratorSet decorators = IN_HEAP | IS_ARRAY; + if (x->check_boolean()) { + decorators |= C1_MASK_BOOLEAN; + } + + access_store_at(decorators, x->elt_type(), array, index.result(), value.result(), nullptr, null_check_info); + if (slow_path != nullptr) { + __ branch_destination(slow_path->continuation()); + set_in_conditional_code(false); + } + } } void LIRGenerator::access_load_at(DecoratorSet decorators, BasicType type, @@ -1692,9 +1988,10 @@ void LIRGenerator::access_load(DecoratorSet decorators, BasicType type, void LIRGenerator::access_store_at(DecoratorSet decorators, BasicType type, LIRItem& base, LIR_Opr offset, LIR_Opr value, - CodeEmitInfo* patch_info, CodeEmitInfo* store_emit_info) { + CodeEmitInfo* patch_info, CodeEmitInfo* store_emit_info, + ciInlineKlass* vk) { decorators |= ACCESS_WRITE; - LIRAccess access(this, decorators, base, offset, type, patch_info, store_emit_info); + LIRAccess access(this, decorators, base, offset, type, patch_info, store_emit_info, vk); if (access.is_raw()) { _barrier_set->BarrierSetC1::store_at(access, value); } else { @@ -1745,8 +2042,9 @@ LIR_Opr LIRGenerator::access_atomic_add_at(DecoratorSet decorators, BasicType ty } void LIRGenerator::do_LoadField(LoadField* x) { + ciField* field = x->field(); bool needs_patching = x->needs_patching(); - bool is_volatile = x->field()->is_volatile(); + bool is_volatile = field->is_volatile(); BasicType field_type = x->field_type(); CodeEmitInfo* info = nullptr; @@ -1797,6 +2095,43 @@ void LIRGenerator::do_LoadField(LoadField* x) { decorators |= C1_NEEDS_PATCHING; } + if (field->is_flat()) { + ciInlineKlass* vk = field->type()->as_inline_klass(); +#ifdef ASSERT + bool is_naturally_atomic = vk->nof_declared_nonstatic_fields() <= 1; + bool needs_atomic_access = !field->is_null_free() || (field->is_volatile() && !is_naturally_atomic); + assert(needs_atomic_access, "No atomic access required"); + assert(x->state_before() != nullptr, "Needs state before"); +#endif + + // Allocate buffer (we can't easily do this conditionally on the null check below + // because branches added in the LIR are opaque to the register allocator). + NewInstance* buffer = new NewInstance(vk, x->state_before(), false, true); + do_NewInstance(buffer); + LIRItem dest(buffer, this); + + // Copy the payload to the buffer + BasicType bt = vk->atomic_size_to_basic_type(field->is_null_free()); + LIR_Opr payload = new_register((bt == T_LONG) ? bt : T_INT); + access_load_at(decorators, bt, object, LIR_OprFact::intConst(field->offset_in_bytes()), payload, + // Make sure to emit an implicit null check + info ? new CodeEmitInfo(info) : nullptr, info); + access_store_at(decorators, bt, dest, LIR_OprFact::intConst(vk->payload_offset()), payload); + + if (field->is_null_free()) { + set_result(x, buffer->operand()); + } else { + // Check the null marker and set result to null if it's not set + __ logical_and(payload, null_marker_mask(bt, field), payload); + __ cmp(lir_cond_equal, payload, (bt == T_LONG) ? LIR_OprFact::longConst(0) : LIR_OprFact::intConst(0)); + __ cmove(lir_cond_equal, LIR_OprFact::oopConst(nullptr), buffer->operand(), rlock_result(x), T_OBJECT); + } + + // Ensure the copy is visible before any subsequent store that publishes the buffer. + __ membar_storestore(); + return; + } + LIR_Opr result = rlock_result(x, field_type); access_load_at(decorators, field_type, object, LIR_OprFact::intConst(x->offset()), result, @@ -1945,12 +2280,71 @@ void LIRGenerator::do_LoadIndexed(LoadIndexed* x) { } } - DecoratorSet decorators = IN_HEAP | IS_ARRAY; + ciMethodData* md = nullptr; + ciArrayLoadData* load_data = nullptr; + if (x->should_profile()) { + if (x->array()->is_loaded_flat_array()) { + // No need to profile a load from a flat array of known type. This can happen if + // the type only became known after optimizations (for example, after the PhiSimplifier). + x->set_should_profile(false); + } else { + int bci = x->profiled_bci(); + md = x->profiled_method()->method_data(); + assert(md != nullptr, "Sanity"); + ciProfileData* data = md->bci_to_data(bci); + assert(data != nullptr && data->is_ArrayLoadData(), "incorrect profiling entry"); + load_data = (ciArrayLoadData*)data; + profile_array_type(x, md, load_data); + } + } + + Value element; + if (x->vt() != nullptr) { + assert(x->array()->is_loaded_flat_array(), "must be"); + // Find the destination address (of the NewInlineTypeInstance). + LIRItem obj_item(x->vt(), this); + + access_flat_array(true, array, index, obj_item, + x->delayed() == nullptr ? 0 : x->delayed()->field(), + x->delayed() == nullptr ? 0 : x->delayed()->offset()); + set_no_result(x); + } else if (x->delayed() != nullptr) { + assert(x->array()->is_loaded_flat_array(), "must be"); + LIR_Opr result = rlock_result(x, x->delayed()->field()->type()->basic_type()); + access_sub_element(array, index, result, x->delayed()->field(), x->delayed()->offset()); + } else { + LIR_Opr result = rlock_result(x, x->elt_type()); + LoadFlattenedArrayStub* slow_path = nullptr; + + if (x->should_profile() && x->array()->maybe_null_free_array()) { + profile_null_free_array(array, md, load_data); + } + + if (x->elt_type() == T_OBJECT && x->array()->maybe_flat_array()) { + assert(x->delayed() == nullptr, "Delayed LoadIndexed only apply to loaded_flat_arrays"); + index.load_item(); + // if we are loading from a flat array, load it using a runtime call + slow_path = new LoadFlattenedArrayStub(array.result(), index.result(), result, state_for(x, x->state_before())); + check_flat_array(array.result(), LIR_OprFact::illegalOpr, slow_path); + set_in_conditional_code(true); + } + + DecoratorSet decorators = IN_HEAP | IS_ARRAY; + access_load_at(decorators, x->elt_type(), + array, index.result(), result, + nullptr, null_check_info); + + if (slow_path != nullptr) { + __ branch_destination(slow_path->continuation()); + set_in_conditional_code(false); + } + + element = x; + } - LIR_Opr result = rlock_result(x, x->elt_type()); - access_load_at(decorators, x->elt_type(), - array, index.result(), result, - nullptr, null_check_info); + if (x->should_profile()) { + profile_element_type(element, md, load_data); + } } @@ -2425,6 +2819,16 @@ ciKlass* LIRGenerator::profile_type(ciMethodData* md, int md_base_offset, int md assert(type == nullptr || type->is_klass(), "type should be class"); exact_klass = (type != nullptr && type->is_loaded()) ? (ciKlass*)type : nullptr; + // TODO 8366668 + if (exact_klass != nullptr && exact_klass->is_obj_array_klass()) { + if (exact_klass->as_obj_array_klass()->element_klass()->is_inlinetype()) { + // Could be flat, null free etc. + exact_klass = nullptr; + } else { + exact_klass = ciObjArrayKlass::make(exact_klass->as_array_klass()->element_klass(), true); + } + } + do_update = exact_klass == nullptr || ciTypeEntries::valid_ciklass(profiled_k) != exact_klass; } @@ -2433,7 +2837,7 @@ ciKlass* LIRGenerator::profile_type(ciMethodData* md, int md_base_offset, int md } ciKlass* exact_signature_k = nullptr; - if (do_update) { + if (do_update && signature_at_call_k != nullptr) { // Is the type from the signature exact (the only one possible)? exact_signature_k = signature_at_call_k->exact_klass(); if (exact_signature_k == nullptr) { @@ -2462,6 +2866,17 @@ ciKlass* LIRGenerator::profile_type(ciMethodData* md, int md_base_offset, int md exact_klass = exact_signature_k; } } + + // TODO 8366668 + if (exact_klass != nullptr && exact_klass->is_obj_array_klass()) { + if (exact_klass->as_obj_array_klass()->element_klass()->is_inlinetype()) { + // Could be flat, null free etc. + exact_klass = nullptr; + } else { + exact_klass = ciObjArrayKlass::make(exact_klass->as_array_klass()->element_klass(), true); + } + } + do_update = exact_klass == nullptr || ciTypeEntries::valid_ciklass(profiled_k) != exact_klass; } @@ -2518,6 +2933,46 @@ void LIRGenerator::profile_parameters(Base* x) { } } +void LIRGenerator::profile_flags(ciMethodData* md, ciProfileData* data, int flag, LIR_Condition condition) { + assert(md != nullptr && data != nullptr, "should have been initialized"); + LIR_Opr mdp = new_register(T_METADATA); + __ metadata2reg(md->constant_encoding(), mdp); + LIR_Address* addr = new LIR_Address(mdp, md->byte_offset_of_slot(data, DataLayout::flags_offset()), T_BYTE); + LIR_Opr flags = new_register(T_INT); + __ move(addr, flags); + if (condition != lir_cond_always) { + LIR_Opr update = new_register(T_INT); + __ cmove(condition, LIR_OprFact::intConst(0), LIR_OprFact::intConst(flag), update, T_INT); + } else { + __ logical_or(flags, LIR_OprFact::intConst(flag), flags); + } + __ store(flags, addr); +} + +template void LIRGenerator::profile_null_free_array(LIRItem array, ciMethodData* md, ArrayData* load_store) { + assert(compilation()->profile_array_accesses(), "array access profiling is disabled"); + LabelObj* L_end = new LabelObj(); + LIR_Opr tmp = new_register(T_METADATA); + __ check_null_free_array(array.result(), tmp); + + profile_flags(md, load_store, ArrayStoreData::null_free_array_byte_constant(), lir_cond_equal); +} + +template void LIRGenerator::profile_array_type(AccessIndexed* x, ciMethodData*& md, ArrayData*& load_store) { + assert(compilation()->profile_array_accesses(), "array access profiling is disabled"); + LIR_Opr mdp = LIR_OprFact::illegalOpr; + profile_type(md, md->byte_offset_of_slot(load_store, ArrayData::array_offset()), 0, + load_store->array()->type(), x->array(), mdp, true, nullptr, nullptr); +} + +void LIRGenerator::profile_element_type(Value element, ciMethodData* md, ciArrayLoadData* load_data) { + assert(compilation()->profile_array_accesses(), "array access profiling is disabled"); + assert(md != nullptr && load_data != nullptr, "should have been initialized"); + LIR_Opr mdp = LIR_OprFact::illegalOpr; + profile_type(md, md->byte_offset_of_slot(load_data, ArrayLoadData::element_offset()), 0, + load_data->element()->type(), element, mdp, false, nullptr, nullptr); +} + void LIRGenerator::do_Base(Base* x) { __ std_entry(LIR_OprFact::illegalOpr); // Emit moves from physical registers / stack slots to virtual registers @@ -2559,6 +3014,12 @@ void LIRGenerator::do_Base(Base* x) { java_index += type2size[t]; } + // Check if we need a membar at the beginning of the java.lang.Object + // constructor to satisfy the memory model for strict fields. + if (EnableValhalla && method()->intrinsic_id() == vmIntrinsics::_Object_init) { + __ membar_storestore(); + } + if (compilation()->env()->dtrace_method_probes()) { BasicTypeList signature; signature.append(LP64_ONLY(T_LONG) NOT_LP64(T_INT)); // thread @@ -2600,6 +3061,14 @@ void LIRGenerator::do_Base(Base* x) { CodeEmitInfo* info = new CodeEmitInfo(scope()->start()->state()->copy(ValueStack::StateBefore, SynchronizationEntryBCI), nullptr, false); increment_invocation_counter(info); } + if (method()->has_scalarized_args()) { + // Check if deoptimization was triggered (i.e. orig_pc was set) while buffering scalarized inline type arguments + // in the entry point (see comments in frame::deoptimize). If so, deoptimize only now that we have the right state. + CodeEmitInfo* info = new CodeEmitInfo(scope()->start()->state()->copy(ValueStack::StateBefore, 0), nullptr, false); + CodeStub* deopt_stub = new DeoptimizeStub(info, Deoptimization::Reason_none, Deoptimization::Action_none); + __ append(new LIR_Op0(lir_check_orig_pc)); + __ branch(lir_cond_notEqual, deopt_stub); + } // all blocks with a successor must end with an unconditional jump // to the successor even if they are consecutive @@ -2615,6 +3084,19 @@ void LIRGenerator::do_OsrEntry(OsrEntry* x) { __ move(LIR_Assembler::osrBufferPointer(), result); } +void LIRGenerator::invoke_load_one_argument(LIRItem* param, LIR_Opr loc) { + if (loc->is_register()) { + param->load_item_force(loc); + } else { + LIR_Address* addr = loc->as_address_ptr(); + param->load_for_store(addr->type()); + if (addr->type() == T_OBJECT) { + __ move_wide(param->result(), addr); + } else { + __ move(param->result(), addr); + } + } +} void LIRGenerator::invoke_load_arguments(Invoke* x, LIRItemList* args, const LIR_OprList* arg_list) { assert(args->length() == arg_list->length(), @@ -2622,16 +3104,7 @@ void LIRGenerator::invoke_load_arguments(Invoke* x, LIRItemList* args, const LIR for (int i = x->has_receiver() ? 1 : 0; i < args->length(); i++) { LIRItem* param = args->at(i); LIR_Opr loc = arg_list->at(i); - if (loc->is_register()) { - param->load_item_force(loc); - } else { - LIR_Address* addr = loc->as_address_ptr(); - param->load_for_store(addr->type()); - if (addr->type() == T_OBJECT) { - __ move_wide(param->result(), addr); - } else - __ move(param->result(), addr); - } + invoke_load_one_argument(param, loc); } if (x->has_receiver()) { @@ -2797,9 +3270,10 @@ void LIRGenerator::do_IfOp(IfOp* x) { LIRItem left(x->x(), this); LIRItem right(x->y(), this); left.load_item(); - if (can_inline_as_constant(right.value())) { + if (can_inline_as_constant(right.value()) && !x->substitutability_check()) { right.dont_load_item(); } else { + // substitutability_check() needs to use right as a base register. right.load_item(); } @@ -2807,10 +3281,67 @@ void LIRGenerator::do_IfOp(IfOp* x) { LIRItem f_val(x->fval(), this); t_val.dont_load_item(); f_val.dont_load_item(); - LIR_Opr reg = rlock_result(x); - __ cmp(lir_cond(x->cond()), left.result(), right.result()); - __ cmove(lir_cond(x->cond()), t_val.result(), f_val.result(), reg, as_BasicType(x->x()->type())); + if (x->substitutability_check()) { + substitutability_check(x, left, right, t_val, f_val); + } else { + LIR_Opr reg = rlock_result(x); + __ cmp(lir_cond(x->cond()), left.result(), right.result()); + __ cmove(lir_cond(x->cond()), t_val.result(), f_val.result(), reg, as_BasicType(x->x()->type())); + } +} + +void LIRGenerator::substitutability_check(IfOp* x, LIRItem& left, LIRItem& right, LIRItem& t_val, LIRItem& f_val) { + assert(x->cond() == If::eql || x->cond() == If::neq, "must be"); + bool is_acmpeq = (x->cond() == If::eql); + LIR_Opr equal_result = is_acmpeq ? t_val.result() : f_val.result(); + LIR_Opr not_equal_result = is_acmpeq ? f_val.result() : t_val.result(); + LIR_Opr result = rlock_result(x); + CodeEmitInfo* info = state_for(x, x->state_before()); + + substitutability_check_common(x->x(), x->y(), left, right, equal_result, not_equal_result, result, info); +} + +void LIRGenerator::substitutability_check(If* x, LIRItem& left, LIRItem& right) { + LIR_Opr equal_result = LIR_OprFact::intConst(1); + LIR_Opr not_equal_result = LIR_OprFact::intConst(0); + LIR_Opr result = new_register(T_INT); + CodeEmitInfo* info = state_for(x, x->state_before()); + + substitutability_check_common(x->x(), x->y(), left, right, equal_result, not_equal_result, result, info); + + assert(x->cond() == If::eql || x->cond() == If::neq, "must be"); + __ cmp(lir_cond(x->cond()), result, equal_result); +} + +void LIRGenerator::substitutability_check_common(Value left_val, Value right_val, LIRItem& left, LIRItem& right, + LIR_Opr equal_result, LIR_Opr not_equal_result, LIR_Opr result, + CodeEmitInfo* info) { + LIR_Opr tmp1 = LIR_OprFact::illegalOpr; + LIR_Opr tmp2 = LIR_OprFact::illegalOpr; + LIR_Opr left_klass_op = LIR_OprFact::illegalOpr; + LIR_Opr right_klass_op = LIR_OprFact::illegalOpr; + + ciKlass* left_klass = left_val ->as_loaded_klass_or_null(); + ciKlass* right_klass = right_val->as_loaded_klass_or_null(); + + if ((left_klass == nullptr || right_klass == nullptr) ||// The klass is still unloaded, or came from a Phi node. + !left_klass->is_inlinetype() || !right_klass->is_inlinetype()) { + init_temps_for_substitutability_check(tmp1, tmp2); + } + + if (left_klass != nullptr && left_klass->is_inlinetype() && left_klass == right_klass) { + // No need to load klass -- the operands are statically known to be the same inline klass. + } else { + BasicType t_klass = UseCompressedOops ? T_INT : T_METADATA; + left_klass_op = new_register(t_klass); + right_klass_op = new_register(t_klass); + } + + CodeStub* slow_path = new SubstitutabilityCheckStub(left.result(), right.result(), info); + __ substitutability_check(result, left.result(), right.result(), equal_result, not_equal_result, + tmp1, tmp2, + left_klass, right_klass, left_klass_op, right_klass_op, info, slow_path); } void LIRGenerator::do_RuntimeCall(address routine, Intrinsic* x) { @@ -3085,7 +3616,7 @@ void LIRGenerator::do_ProfileReturnType(ProfileReturnType* x) { ciProfileData* data = md->bci_to_data(bci); if (data != nullptr) { assert(data->is_CallTypeData() || data->is_VirtualCallTypeData(), "wrong profile data type"); - ciReturnTypeEntry* ret = data->is_CallTypeData() ? ((ciCallTypeData*)data)->ret() : ((ciVirtualCallTypeData*)data)->ret(); + ciSingleTypeEntry* ret = data->is_CallTypeData() ? ((ciCallTypeData*)data)->ret() : ((ciVirtualCallTypeData*)data)->ret(); LIR_Opr mdp = LIR_OprFact::illegalOpr; bool ignored_will_link; @@ -3106,6 +3637,53 @@ void LIRGenerator::do_ProfileReturnType(ProfileReturnType* x) { } } +bool LIRGenerator::profile_inline_klass(ciMethodData* md, ciProfileData* data, Value value, int flag) { + ciKlass* klass = value->as_loaded_klass_or_null(); + if (klass != nullptr) { + if (klass->is_inlinetype()) { + profile_flags(md, data, flag, lir_cond_always); + } else if (klass->can_be_inline_klass()) { + return false; + } + } else { + return false; + } + return true; +} + + +void LIRGenerator::do_ProfileACmpTypes(ProfileACmpTypes* x) { + ciMethod* method = x->method(); + assert(method != nullptr, "method should be set if branch is profiled"); + ciMethodData* md = method->method_data_or_null(); + assert(md != nullptr, "Sanity"); + ciProfileData* data = md->bci_to_data(x->bci()); + assert(data != nullptr, "must have profiling data"); + assert(data->is_ACmpData(), "need BranchData for two-way branches"); + ciACmpData* acmp = (ciACmpData*)data; + LIR_Opr mdp = LIR_OprFact::illegalOpr; + profile_type(md, md->byte_offset_of_slot(acmp, ACmpData::left_offset()), 0, + acmp->left()->type(), x->left(), mdp, !x->left_maybe_null(), nullptr, nullptr); + int flags_offset = md->byte_offset_of_slot(data, DataLayout::flags_offset()); + if (!profile_inline_klass(md, acmp, x->left(), ACmpData::left_inline_type_byte_constant())) { + LIR_Opr mdp = new_register(T_METADATA); + __ metadata2reg(md->constant_encoding(), mdp); + LIRItem value(x->left(), this); + value.load_item(); + __ profile_inline_type(new LIR_Address(mdp, flags_offset, T_INT), value.result(), ACmpData::left_inline_type_byte_constant(), new_register(T_INT), !x->left_maybe_null()); + } + profile_type(md, md->byte_offset_of_slot(acmp, ACmpData::left_offset()), + in_bytes(ACmpData::right_offset()) - in_bytes(ACmpData::left_offset()), + acmp->right()->type(), x->right(), mdp, !x->right_maybe_null(), nullptr, nullptr); + if (!profile_inline_klass(md, acmp, x->right(), ACmpData::right_inline_type_byte_constant())) { + LIR_Opr mdp = new_register(T_METADATA); + __ metadata2reg(md->constant_encoding(), mdp); + LIRItem value(x->right(), this); + value.load_item(); + __ profile_inline_type(new LIR_Address(mdp, flags_offset, T_INT), value.result(), ACmpData::right_inline_type_byte_constant(), new_register(T_INT), !x->left_maybe_null()); + } +} + void LIRGenerator::do_ProfileInvoke(ProfileInvoke* x) { // We can safely ignore accessors here, since c2 will inline them anyway, // accessors are also always mature. diff --git a/src/hotspot/share/c1/c1_LIRGenerator.hpp b/src/hotspot/share/c1/c1_LIRGenerator.hpp index ec0ea5dc047..9971ed86df1 100644 --- a/src/hotspot/share/c1/c1_LIRGenerator.hpp +++ b/src/hotspot/share/c1/c1_LIRGenerator.hpp @@ -169,6 +169,7 @@ class LIRGenerator: public InstructionVisitor, public BlockClosure { #endif BitMap2D _vreg_flags; // flags which can be set on a per-vreg basis LIR_List* _lir; + bool _in_conditional_code; LIRGenerator* gen() { return this; @@ -195,6 +196,7 @@ class LIRGenerator: public InstructionVisitor, public BlockClosure { friend class PhiResolver; + void set_in_conditional_code(bool v); public: // unified bailout support void bailout(const char* msg) const { compilation()->bailout(msg); } @@ -214,6 +216,7 @@ class LIRGenerator: public InstructionVisitor, public BlockClosure { LIR_Opr load_constant(Constant* x); LIR_Opr load_constant(LIR_Const* constant); + bool in_conditional_code() { return _in_conditional_code; } // Given an immediate value, return an operand usable in logical ops. LIR_Opr load_immediate(jlong x, BasicType type); @@ -272,6 +275,19 @@ class LIRGenerator: public InstructionVisitor, public BlockClosure { void do_vectorizedMismatch(Intrinsic* x); void do_blackhole(Intrinsic* x); + void access_flat_array(bool is_load, LIRItem& array, LIRItem& index, LIRItem& obj_item, ciField* field = nullptr, int offset = 0); + void access_sub_element(LIRItem& array, LIRItem& index, LIR_Opr& result, ciField* field, int sub_offset); + LIR_Opr get_and_load_element_address(LIRItem& array, LIRItem& index); + bool needs_flat_array_store_check(StoreIndexed* x); + void check_flat_array(LIR_Opr array, LIR_Opr value, CodeStub* slow_path); + bool needs_null_free_array_store_check(StoreIndexed* x); + void check_null_free_array(LIRItem& array, LIRItem& value, CodeEmitInfo* info); + void substitutability_check(IfOp* x, LIRItem& left, LIRItem& right, LIRItem& t_val, LIRItem& f_val); + void substitutability_check(If* x, LIRItem& left, LIRItem& right); + void substitutability_check_common(Value left_val, Value right_val, LIRItem& left, LIRItem& right, + LIR_Opr equal_result, LIR_Opr not_equal_result, LIR_Opr result, CodeEmitInfo* info); + void init_temps_for_substitutability_check(LIR_Opr& tmp1, LIR_Opr& tmp2); + public: LIR_Opr call_runtime(BasicTypeArray* signature, LIRItemList* args, address entry, ValueType* result_type, CodeEmitInfo* info); LIR_Opr call_runtime(BasicTypeArray* signature, LIR_OprList* args, address entry, ValueType* result_type, CodeEmitInfo* info); @@ -288,7 +304,7 @@ class LIRGenerator: public InstructionVisitor, public BlockClosure { public: void access_store_at(DecoratorSet decorators, BasicType type, LIRItem& base, LIR_Opr offset, LIR_Opr value, - CodeEmitInfo* patch_info = nullptr, CodeEmitInfo* store_emit_info = nullptr); + CodeEmitInfo* patch_info = nullptr, CodeEmitInfo* store_emit_info = nullptr, ciInlineKlass* vk = nullptr); void access_load_at(DecoratorSet decorators, BasicType type, LIRItem& base, LIR_Opr offset, LIR_Opr result, @@ -325,7 +341,7 @@ class LIRGenerator: public InstructionVisitor, public BlockClosure { LIRItemList* invoke_visit_arguments(Invoke* x); void invoke_load_arguments(Invoke* x, LIRItemList* args, const LIR_OprList* arg_list); - + void invoke_load_one_argument(LIRItem* param, LIR_Opr loc); void trace_block_entry(BlockBegin* block); // volatile field operations are never patchable because a klass @@ -361,10 +377,10 @@ class LIRGenerator: public InstructionVisitor, public BlockClosure { void logic_op (Bytecodes::Code code, LIR_Opr dst_reg, LIR_Opr left, LIR_Opr right); - void monitor_enter (LIR_Opr object, LIR_Opr lock, LIR_Opr hdr, LIR_Opr scratch, int monitor_no, CodeEmitInfo* info_for_exception, CodeEmitInfo* info); + void monitor_enter (LIR_Opr object, LIR_Opr lock, LIR_Opr hdr, LIR_Opr scratch, int monitor_no, CodeEmitInfo* info_for_exception, CodeEmitInfo* info, CodeStub* throw_ie_stub); void monitor_exit (LIR_Opr object, LIR_Opr lock, LIR_Opr hdr, LIR_Opr scratch, int monitor_no); - void new_instance (LIR_Opr dst, ciInstanceKlass* klass, bool is_unresolved, LIR_Opr scratch1, LIR_Opr scratch2, LIR_Opr scratch3, LIR_Opr scratch4, LIR_Opr klass_reg, CodeEmitInfo* info); + void new_instance(LIR_Opr dst, ciInstanceKlass* klass, bool is_unresolved, bool allow_inline, LIR_Opr scratch1, LIR_Opr scratch2, LIR_Opr scratch3, LIR_Opr scratch4, LIR_Opr klass_reg, CodeEmitInfo* info); // machine dependent void cmp_mem_int(LIR_Condition condition, LIR_Opr base, int disp, int c, CodeEmitInfo* info); @@ -476,6 +492,11 @@ class LIRGenerator: public InstructionVisitor, public BlockClosure { void profile_arguments(ProfileCall* x); void profile_parameters(Base* x); void profile_parameters_at_call(ProfileCall* x); + void profile_flags(ciMethodData* md, ciProfileData* load_store, int flag, LIR_Condition condition = lir_cond_always); + template void profile_null_free_array(LIRItem array, ciMethodData* md, ArrayData* load_store); + template void profile_array_type(AccessIndexed* x, ciMethodData*& md, ArrayData*& load_store); + void profile_element_type(Value element, ciMethodData* md, ciArrayLoadData* load_store); + bool profile_inline_klass(ciMethodData* md, ciProfileData* data, Value value, int flag); LIR_Opr mask_boolean(LIR_Opr array, LIR_Opr value, CodeEmitInfo*& null_check_info); public: @@ -503,6 +524,7 @@ class LIRGenerator: public InstructionVisitor, public BlockClosure { , _method(method) , _virtual_register_number(LIR_Opr::vreg_base) , _vreg_flags(num_vreg_flags) + , _in_conditional_code(false) , _barrier_set(BarrierSet::barrier_set()->barrier_set_c1()) { } @@ -584,6 +606,7 @@ class LIRGenerator: public InstructionVisitor, public BlockClosure { virtual void do_ProfileCall (ProfileCall* x); virtual void do_ProfileReturnType (ProfileReturnType* x); virtual void do_ProfileInvoke (ProfileInvoke* x); + virtual void do_ProfileACmpTypes(ProfileACmpTypes* x); virtual void do_RuntimeCall (RuntimeCall* x); virtual void do_MemBar (MemBar* x); virtual void do_RangeCheckPredicate(RangeCheckPredicate* x); diff --git a/src/hotspot/share/c1/c1_LinearScan.cpp b/src/hotspot/share/c1/c1_LinearScan.cpp index b00ab25b8f0..b09f061b735 100644 --- a/src/hotspot/share/c1/c1_LinearScan.cpp +++ b/src/hotspot/share/c1/c1_LinearScan.cpp @@ -62,9 +62,9 @@ // Map BasicType to spill size in 32-bit words, matching VMReg's notion of words #ifdef _LP64 -static int type2spill_size[T_CONFLICT+1]={ -1, 0, 0, 0, 1, 1, 1, 2, 1, 1, 1, 2, 2, 2, 0, 2, 1, 2, 1, -1}; +static int type2spill_size[T_CONFLICT+1]={ -1, 0, 0, 0, 1, 1, 1, 2, 1, 1, 1, 2, 2, 2, 2, 0, 2, 1, 2, 1, -1}; #else -static int type2spill_size[T_CONFLICT+1]={ -1, 0, 0, 0, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 0, 1, -1, 1, 1, -1}; +static int type2spill_size[T_CONFLICT+1]={ -1, 0, 0, 0, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 0, 1, -1, 1, 1, -1}; #endif @@ -256,7 +256,7 @@ void LinearScan::assign_spill_slot(Interval* it) { } void LinearScan::propagate_spill_slots() { - if (!frame_map()->finalize_frame(max_spills())) { + if (!frame_map()->finalize_frame(max_spills(), compilation()->needs_stack_repair())) { bailout("frame too large"); } } @@ -2888,7 +2888,7 @@ IRScopeDebugInfo* LinearScan::compute_debug_info_for_scope(int op_id, IRScope* c } } - return new IRScopeDebugInfo(cur_scope, cur_state->bci(), locals, expressions, monitors, caller_debug_info); + return new IRScopeDebugInfo(cur_scope, cur_state->bci(), locals, expressions, monitors, caller_debug_info, cur_state->should_reexecute()); } diff --git a/src/hotspot/share/c1/c1_MacroAssembler.hpp b/src/hotspot/share/c1/c1_MacroAssembler.hpp index 0fe0d0ff285..4c8539f9b6b 100644 --- a/src/hotspot/share/c1/c1_MacroAssembler.hpp +++ b/src/hotspot/share/c1/c1_MacroAssembler.hpp @@ -29,8 +29,11 @@ #include "utilities/macros.hpp" class CodeEmitInfo; - +class CompiledEntrySignature; class C1_MacroAssembler: public MacroAssembler { + private: + int scalarized_entry(const CompiledEntrySignature* ces, int frame_size_in_bytes, int bang_size_in_bytes, int sp_offset_for_orig_pc, Label& verified_inline_entry_label, bool is_inline_ro_entry); + void build_frame_helper(int frame_size_in_bytes, int sp_offset_for_orig_pc, int sp_inc, bool reset_orig_pc, bool needs_stack_repair); public: // creation C1_MacroAssembler(CodeBuffer* code) : MacroAssembler(code) { pd_init(); } @@ -38,10 +41,16 @@ class C1_MacroAssembler: public MacroAssembler { //---------------------------------------------------- void explicit_null_check(Register base); - void build_frame(int frame_size_in_bytes, int bang_size_in_bytes); - void remove_frame(int frame_size_in_bytes); + void build_frame(int frame_size_in_bytes, int bang_size_in_bytes, int sp_offset_for_orig_pc = 0, bool needs_stack_repair = false, bool has_scalarized_args = false, Label* verified_inline_entry_label = nullptr); + int verified_entry(const CompiledEntrySignature* ces, int frame_size_in_bytes, int bang_size_in_bytes, int sp_offset_for_orig_pc, Label& verified_inline_entry_label) { + return scalarized_entry(ces, frame_size_in_bytes, bang_size_in_bytes, sp_offset_for_orig_pc, verified_inline_entry_label, false); + } + int verified_inline_ro_entry(const CompiledEntrySignature* ces, int frame_size_in_bytes, int bang_size_in_bytes, int sp_offset_for_orig_pc, Label& verified_inline_entry_label) { + return scalarized_entry(ces, frame_size_in_bytes, bang_size_in_bytes, sp_offset_for_orig_pc, verified_inline_entry_label, true); + } void verified_entry(bool breakAtEntry); + void verify_stack_oop(int offset) PRODUCT_RETURN; void verify_not_null_oop(Register r) PRODUCT_RETURN; diff --git a/src/hotspot/share/c1/c1_Optimizer.cpp b/src/hotspot/share/c1/c1_Optimizer.cpp index 0c18694df78..f05d61ee3e4 100644 --- a/src/hotspot/share/c1/c1_Optimizer.cpp +++ b/src/hotspot/share/c1/c1_Optimizer.cpp @@ -86,7 +86,8 @@ class CE_Eliminator: public BlockClosure { virtual void block_do(BlockBegin* block); private: - Value make_ifop(Value x, Instruction::Condition cond, Value y, Value tval, Value fval); + Value make_ifop(Value x, Instruction::Condition cond, Value y, Value tval, Value fval, + ValueStack* state_before, bool substitutability_check); }; void CE_Eliminator::block_do(BlockBegin* block) { @@ -215,7 +216,8 @@ void CE_Eliminator::block_do(BlockBegin* block) { cur_end = cur_end->set_next(f_value); } - Value result = make_ifop(if_->x(), if_->cond(), if_->y(), t_value, f_value); + Value result = make_ifop(if_->x(), if_->cond(), if_->y(), t_value, f_value, + if_->state_before(), if_->substitutability_check()); assert(result != nullptr, "make_ifop must return a non-null instruction"); if (!result->is_linked() && result->can_be_linked()) { NOT_PRODUCT(result->set_printable_bci(if_->printable_bci())); @@ -270,9 +272,10 @@ void CE_Eliminator::block_do(BlockBegin* block) { } -Value CE_Eliminator::make_ifop(Value x, Instruction::Condition cond, Value y, Value tval, Value fval) { +Value CE_Eliminator::make_ifop(Value x, Instruction::Condition cond, Value y, Value tval, Value fval, + ValueStack* state_before, bool substitutability_check) { if (!OptimizeIfOps) { - return new IfOp(x, cond, y, tval, fval); + return new IfOp(x, cond, y, tval, fval, state_before, substitutability_check); } tval = tval->subst(); @@ -307,7 +310,7 @@ Value CE_Eliminator::make_ifop(Value x, Instruction::Condition cond, Value y, Va if (new_tval == new_fval) { return new_tval; } else { - return new IfOp(x_ifop->x(), x_ifop_cond, x_ifop->y(), new_tval, new_fval); + return new IfOp(x_ifop->x(), x_ifop_cond, x_ifop->y(), new_tval, new_fval, state_before, substitutability_check); } } } @@ -323,7 +326,7 @@ Value CE_Eliminator::make_ifop(Value x, Instruction::Condition cond, Value y, Va } } } - return new IfOp(x, cond, y, tval, fval); + return new IfOp(x, cond, y, tval, fval, state_before, substitutability_check); } void Optimizer::eliminate_conditional_expressions() { @@ -463,7 +466,7 @@ class BlockMerger: public BlockClosure { con = if_->x()->as_Constant(); swapped = true; } - if (con && ifop) { + if (con && ifop && !ifop->substitutability_check()) { Constant* tval = ifop->tval()->as_Constant(); Constant* fval = ifop->fval()->as_Constant(); if (tval && fval) { @@ -488,7 +491,7 @@ class BlockMerger: public BlockClosure { BlockBegin* fblock = fval->compare(cond, con, tsux, fsux); if (tblock != fblock && !if_->is_safepoint()) { If* newif = new If(ifop->x(), ifop->cond(), false, ifop->y(), - tblock, fblock, if_->state_before(), if_->is_safepoint()); + tblock, fblock, if_->state_before(), if_->is_safepoint(), ifop->substitutability_check()); newif->set_state(if_->state()->copy()); assert(prev->next() == if_, "must be guaranteed by above search"); @@ -582,6 +585,7 @@ class NullCheckVisitor: public InstructionVisitor { void do_UnsafeGetAndSet(UnsafeGetAndSet* x); void do_ProfileCall (ProfileCall* x); void do_ProfileReturnType (ProfileReturnType* x); + void do_ProfileACmpTypes(ProfileACmpTypes* x); void do_ProfileInvoke (ProfileInvoke* x); void do_RuntimeCall (RuntimeCall* x); void do_MemBar (MemBar* x); @@ -710,6 +714,7 @@ class NullCheckEliminator: public ValueVisitor { void handle_Phi (Phi* x); void handle_ProfileCall (ProfileCall* x); void handle_ProfileReturnType (ProfileReturnType* x); + void handle_ProfileACmpTypes(ProfileACmpTypes* x); void handle_Constant (Constant* x); void handle_IfOp (IfOp* x); }; @@ -768,6 +773,7 @@ void NullCheckVisitor::do_ProfileCall (ProfileCall* x) { nce()->clear_las nce()->handle_ProfileCall(x); } void NullCheckVisitor::do_ProfileReturnType (ProfileReturnType* x) { nce()->handle_ProfileReturnType(x); } void NullCheckVisitor::do_ProfileInvoke (ProfileInvoke* x) {} +void NullCheckVisitor::do_ProfileACmpTypes(ProfileACmpTypes* x) { nce()->handle_ProfileACmpTypes(x); } void NullCheckVisitor::do_RuntimeCall (RuntimeCall* x) {} void NullCheckVisitor::do_MemBar (MemBar* x) {} void NullCheckVisitor::do_RangeCheckPredicate(RangeCheckPredicate* x) {} @@ -1197,6 +1203,11 @@ void NullCheckEliminator::handle_ProfileReturnType(ProfileReturnType* x) { x->set_needs_null_check(!set_contains(x->ret())); } +void NullCheckEliminator::handle_ProfileACmpTypes(ProfileACmpTypes* x) { + x->set_left_maybe_null(!set_contains(x->left())); + x->set_right_maybe_null(!set_contains(x->right())); +} + void NullCheckEliminator::handle_Constant(Constant *x) { ObjectType* ot = x->type()->as_ObjectType(); if (ot != nullptr && ot->is_loaded()) { diff --git a/src/hotspot/share/c1/c1_RangeCheckElimination.hpp b/src/hotspot/share/c1/c1_RangeCheckElimination.hpp index 833f5dd1e99..80476e7a12d 100644 --- a/src/hotspot/share/c1/c1_RangeCheckElimination.hpp +++ b/src/hotspot/share/c1/c1_RangeCheckElimination.hpp @@ -159,6 +159,7 @@ class RangeCheckEliminator { void do_UnsafeGetAndSet(UnsafeGetAndSet* x) { /* nothing to do */ }; void do_ProfileCall (ProfileCall* x) { /* nothing to do */ }; void do_ProfileReturnType (ProfileReturnType* x) { /* nothing to do */ }; + void do_ProfileACmpTypes(ProfileACmpTypes* x) { /* nothing to do */ }; void do_ProfileInvoke (ProfileInvoke* x) { /* nothing to do */ }; void do_RuntimeCall (RuntimeCall* x) { /* nothing to do */ }; void do_MemBar (MemBar* x) { /* nothing to do */ }; diff --git a/src/hotspot/share/c1/c1_Runtime1.cpp b/src/hotspot/share/c1/c1_Runtime1.cpp index 4a1969788f3..78e4b4dbe10 100644 --- a/src/hotspot/share/c1/c1_Runtime1.cpp +++ b/src/hotspot/share/c1/c1_Runtime1.cpp @@ -50,6 +50,8 @@ #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "oops/access.inline.hpp" +#include "oops/flatArrayKlass.hpp" +#include "oops/flatArrayOop.inline.hpp" #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" @@ -114,8 +116,14 @@ uint Runtime1::_arraycopy_checkcast_cnt = 0; uint Runtime1::_arraycopy_checkcast_attempt_cnt = 0; uint Runtime1::_new_type_array_slowcase_cnt = 0; uint Runtime1::_new_object_array_slowcase_cnt = 0; +uint Runtime1::_new_null_free_array_slowcase_cnt = 0; uint Runtime1::_new_instance_slowcase_cnt = 0; uint Runtime1::_new_multi_array_slowcase_cnt = 0; +uint Runtime1::_load_flat_array_slowcase_cnt = 0; +uint Runtime1::_store_flat_array_slowcase_cnt = 0; +uint Runtime1::_substitutability_check_slowcase_cnt = 0; +uint Runtime1::_buffer_inline_args_slowcase_cnt = 0; +uint Runtime1::_buffer_inline_args_no_receiver_slowcase_cnt = 0; uint Runtime1::_monitorenter_slowcase_cnt = 0; uint Runtime1::_monitorexit_slowcase_cnt = 0; uint Runtime1::_patch_code_slowcase_cnt = 0; @@ -125,6 +133,8 @@ uint Runtime1::_throw_div0_exception_count = 0; uint Runtime1::_throw_null_pointer_exception_count = 0; uint Runtime1::_throw_class_cast_exception_count = 0; uint Runtime1::_throw_incompatible_class_change_error_count = 0; +uint Runtime1::_throw_illegal_monitor_state_exception_count = 0; +uint Runtime1::_throw_identity_exception_count = 0; uint Runtime1::_throw_count = 0; static uint _byte_arraycopy_stub_cnt = 0; @@ -372,11 +382,10 @@ const char* Runtime1::name_for_address(address entry) { return pd_name_for_address(entry); } - -JRT_ENTRY(void, Runtime1::new_instance(JavaThread* current, Klass* klass)) +static void allocate_instance(JavaThread* current, Klass* klass, TRAPS) { #ifndef PRODUCT if (PrintC1Statistics) { - _new_instance_slowcase_cnt++; + Runtime1::_new_instance_slowcase_cnt++; } #endif assert(klass->is_klass(), "not a class"); @@ -390,6 +399,9 @@ JRT_ENTRY(void, Runtime1::new_instance(JavaThread* current, Klass* klass)) current->set_vm_result_oop(obj); JRT_END +JRT_ENTRY(void, Runtime1::new_instance(JavaThread* current, Klass* klass)) + allocate_instance(current, klass, CHECK); +JRT_END JRT_ENTRY(void, Runtime1::new_type_array(JavaThread* current, Klass* klass, jint length)) #ifndef PRODUCT @@ -435,6 +447,30 @@ JRT_ENTRY(void, Runtime1::new_object_array(JavaThread* current, Klass* array_kla JRT_END +JRT_ENTRY(void, Runtime1::new_null_free_array(JavaThread* current, Klass* array_klass, jint length)) + NOT_PRODUCT(_new_null_free_array_slowcase_cnt++;) + // TODO 8350865 This is dead code since 8325660 because null-free arrays can only be created via the factory methods that are not yet implemented in C1. Should probably be fixed by 8265122. + + // Note: no handle for klass needed since they are not used + // anymore after new_objArray() and no GC can happen before. + // (This may have to change if this code changes!) + assert(array_klass->is_klass(), "not a class"); + Handle holder(THREAD, array_klass->klass_holder()); // keep the klass alive + Klass* elem_klass = ObjArrayKlass::cast(array_klass)->element_klass(); + assert(elem_klass->is_inline_klass(), "must be"); + InlineKlass* vk = InlineKlass::cast(elem_klass); + // Logically creates elements, ensure klass init + elem_klass->initialize(CHECK); + arrayOop obj= oopFactory::new_objArray(elem_klass, length, ArrayKlass::ArrayProperties::NULL_RESTRICTED, CHECK); + current->set_vm_result_oop(obj); + // This is pretty rare but this runtime patch is stressful to deoptimization + // if we deoptimize here so force a deopt to stress the path. + if (DeoptimizeALot) { + deopt_caller(current); + } +JRT_END + + JRT_ENTRY(void, Runtime1::new_multi_array(JavaThread* current, Klass* klass, int rank, jint* dims)) #ifndef PRODUCT if (PrintC1Statistics) { @@ -449,6 +485,102 @@ JRT_ENTRY(void, Runtime1::new_multi_array(JavaThread* current, Klass* klass, int JRT_END +static void profile_flat_array(JavaThread* current, bool load, bool null_free) { + ResourceMark rm(current); + vframeStream vfst(current, true); + assert(!vfst.at_end(), "Java frame must exist"); + // Check if array access profiling is enabled + if (vfst.nm()->comp_level() != CompLevel_full_profile || !C1UpdateMethodData) { + return; + } + int bci = vfst.bci(); + Method* method = vfst.method(); + MethodData* md = method->method_data(); + if (md != nullptr) { + // Lock to access ProfileData, and ensure lock is not broken by a safepoint + MutexLocker ml(md->extra_data_lock(), Mutex::_no_safepoint_check_flag); + + ProfileData* data = md->bci_to_data(bci); + assert(data != nullptr, "incorrect profiling entry"); + if (data->is_ArrayLoadData()) { + assert(load, "should be an array load"); + ArrayLoadData* load_data = (ArrayLoadData*) data; + load_data->set_flat_array(); + if (null_free) { + load_data->set_null_free_array(); + } + } else { + assert(data->is_ArrayStoreData(), ""); + assert(!load, "should be an array store"); + ArrayStoreData* store_data = (ArrayStoreData*) data; + store_data->set_flat_array(); + if (null_free) { + store_data->set_null_free_array(); + } + } + } +} + +JRT_ENTRY(void, Runtime1::load_flat_array(JavaThread* current, flatArrayOopDesc* array, int index)) + assert(array->klass()->is_flatArray_klass(), "should not be called"); + profile_flat_array(current, true, array->is_null_free_array()); + + NOT_PRODUCT(_load_flat_array_slowcase_cnt++;) + assert(array->length() > 0 && index < array->length(), "already checked"); + flatArrayHandle vah(current, array); + oop obj = array->obj_at(index, CHECK); + current->set_vm_result_oop(obj); +JRT_END + +JRT_ENTRY(void, Runtime1::store_flat_array(JavaThread* current, flatArrayOopDesc* array, int index, oopDesc* value)) + // TOOD 8350865 We can call here with a non-flat array because of LIR_Assembler::emit_opFlattenedArrayCheck + if (array->klass()->is_flatArray_klass()) { + profile_flat_array(current, false, array->is_null_free_array()); + } + + NOT_PRODUCT(_store_flat_array_slowcase_cnt++;) + if (value == nullptr && array->is_null_free_array()) { + SharedRuntime::throw_and_post_jvmti_exception(current, vmSymbols::java_lang_NullPointerException()); + } else { + assert(array->klass()->is_flatArray_klass(), "should not be called"); + array->obj_at_put(index, value, CHECK); + } +JRT_END + +JRT_ENTRY(int, Runtime1::substitutability_check(JavaThread* current, oopDesc* left, oopDesc* right)) + NOT_PRODUCT(_substitutability_check_slowcase_cnt++;) + JavaCallArguments args; + args.push_oop(Handle(THREAD, left)); + args.push_oop(Handle(THREAD, right)); + JavaValue result(T_BOOLEAN); + JavaCalls::call_static(&result, + vmClasses::ValueObjectMethods_klass(), + vmSymbols::isSubstitutable_name(), + vmSymbols::object_object_boolean_signature(), + &args, CHECK_0); + return result.get_jboolean() ? 1 : 0; +JRT_END + + +extern "C" void ps(); + +void Runtime1::buffer_inline_args_impl(JavaThread* current, Method* m, bool allocate_receiver) { + JavaThread* THREAD = current; + methodHandle method(current, m); // We are inside the verified_entry or verified_inline_ro_entry of this method. + oop obj = SharedRuntime::allocate_inline_types_impl(current, method, allocate_receiver, CHECK); + current->set_vm_result_oop(obj); +} + +JRT_ENTRY(void, Runtime1::buffer_inline_args(JavaThread* current, Method* method)) + NOT_PRODUCT(_buffer_inline_args_slowcase_cnt++;) + buffer_inline_args_impl(current, method, true); +JRT_END + +JRT_ENTRY(void, Runtime1::buffer_inline_args_no_receiver(JavaThread* current, Method* method)) + NOT_PRODUCT(_buffer_inline_args_no_receiver_slowcase_cnt++;) + buffer_inline_args_impl(current, method, false); +JRT_END + JRT_ENTRY(void, Runtime1::unimplemented_entry(JavaThread* current, StubId id)) tty->print_cr("Runtime1::entry_for(%d) returned unimplemented entry point", (int)id); JRT_END @@ -772,6 +904,19 @@ JRT_ENTRY(void, Runtime1::throw_incompatible_class_change_error(JavaThread* curr JRT_END +JRT_ENTRY(void, Runtime1::throw_illegal_monitor_state_exception(JavaThread* current)) + NOT_PRODUCT(_throw_illegal_monitor_state_exception_count++;) + ResourceMark rm(current); + SharedRuntime::throw_and_post_jvmti_exception(current, vmSymbols::java_lang_IllegalMonitorStateException()); +JRT_END + +JRT_ENTRY(void, Runtime1::throw_identity_exception(JavaThread* current, oopDesc* object)) + NOT_PRODUCT(_throw_identity_exception_count++;) + ResourceMark rm(current); + char* message = SharedRuntime::generate_identity_exception_message(current, object->klass()); + SharedRuntime::throw_and_post_jvmti_exception(current, vmSymbols::java_lang_IdentityException(), message); +JRT_END + JRT_BLOCK_ENTRY(void, Runtime1::monitorenter(JavaThread* current, oopDesc* obj, BasicObjectLock* lock)) #ifndef PRODUCT if (PrintC1Statistics) { @@ -977,6 +1122,9 @@ JRT_ENTRY(void, Runtime1::patch_code(JavaThread* current, StubId stub_id )) BasicType patch_field_type = T_ILLEGAL; bool deoptimize_for_volatile = false; bool deoptimize_for_atomic = false; + bool deoptimize_for_null_free = false; + bool deoptimize_for_flat = false; + bool deoptimize_for_strict_static = false; int patch_field_offset = -1; Klass* init_klass = nullptr; // klass needed by load_klass_patching code Klass* load_klass = nullptr; // klass needed by load_klass_patching code @@ -1020,6 +1168,19 @@ JRT_ENTRY(void, Runtime1::patch_code(JavaThread* current, StubId stub_id )) patch_field_type = result.field_type(); deoptimize_for_atomic = (AlwaysAtomicAccesses && (patch_field_type == T_DOUBLE || patch_field_type == T_LONG)); + // The field we are patching is null-free. Deoptimize and regenerate + // the compiled code if we patch a putfield/putstatic because it + // does not contain the required null check. + deoptimize_for_null_free = result.is_null_free_inline_type() && (field_access.is_putfield() || field_access.is_putstatic()); + + // The field we are patching is flat. Deoptimize and regenerate + // the compiled code which can't handle the layout of the flat + // field because it was unknown at compile time. + deoptimize_for_flat = result.is_flat(); + + // Strict statics may require tracking if their class is not fully initialized. + // For now we can bail out of the compiler and let the interpreter handle it. + deoptimize_for_strict_static = result.is_strict_static_unset(); } else if (load_klass_or_mirror_patch_id) { Klass* k = nullptr; switch (code) { @@ -1054,6 +1215,12 @@ JRT_ENTRY(void, Runtime1::patch_code(JavaThread* current, StubId stub_id )) { Bytecode_anewarray anew(caller_method(), caller_method->bcp_from(bci)); Klass* ek = caller_method->constants()->klass_at(anew.index(), CHECK); k = ek->array_klass(CHECK); + if (!k->is_typeArray_klass() && !k->is_refArray_klass() && !k->is_flatArray_klass()) { + k = ObjArrayKlass::cast(k)->klass_with_properties(ArrayKlass::ArrayProperties::DEFAULT, THREAD); + } + if (k->is_flatArray_klass()) { + deoptimize_for_flat = true; + } } break; case Bytecodes::_ldc: @@ -1092,7 +1259,11 @@ JRT_ENTRY(void, Runtime1::patch_code(JavaThread* current, StubId stub_id )) ShouldNotReachHere(); } - if (deoptimize_for_volatile || deoptimize_for_atomic) { + if (deoptimize_for_volatile || + deoptimize_for_atomic || + deoptimize_for_null_free || + deoptimize_for_flat || + deoptimize_for_strict_static) { // At compile time we assumed the field wasn't volatile/atomic but after // loading it turns out it was volatile/atomic so we have to throw the // compiled code out and let it be regenerated. @@ -1103,6 +1274,15 @@ JRT_ENTRY(void, Runtime1::patch_code(JavaThread* current, StubId stub_id )) if (deoptimize_for_atomic) { tty->print_cr("Deoptimizing for patching atomic field reference"); } + if (deoptimize_for_null_free) { + tty->print_cr("Deoptimizing for patching null-free field reference"); + } + if (deoptimize_for_flat) { + tty->print_cr("Deoptimizing for patching flat field or array reference"); + } + if (deoptimize_for_strict_static) { + tty->print_cr("Deoptimizing for patching strict static field reference"); + } } // It's possible the nmethod was invalidated in the last @@ -1553,8 +1733,15 @@ void Runtime1::print_statistics() { tty->print_cr(" _new_type_array_slowcase_cnt: %u", _new_type_array_slowcase_cnt); tty->print_cr(" _new_object_array_slowcase_cnt: %u", _new_object_array_slowcase_cnt); + tty->print_cr(" _new_null_free_array_slowcase_cnt: %u", _new_null_free_array_slowcase_cnt); tty->print_cr(" _new_instance_slowcase_cnt: %u", _new_instance_slowcase_cnt); tty->print_cr(" _new_multi_array_slowcase_cnt: %u", _new_multi_array_slowcase_cnt); + tty->print_cr(" _load_flat_array_slowcase_cnt: %u", _load_flat_array_slowcase_cnt); + tty->print_cr(" _store_flat_array_slowcase_cnt: %u", _store_flat_array_slowcase_cnt); + tty->print_cr(" _substitutability_check_slowcase_cnt: %u", _substitutability_check_slowcase_cnt); + tty->print_cr(" _buffer_inline_args_slowcase_cnt:%u", _buffer_inline_args_slowcase_cnt); + tty->print_cr(" _buffer_inline_args_no_receiver_slowcase_cnt:%u", _buffer_inline_args_no_receiver_slowcase_cnt); + tty->print_cr(" _monitorenter_slowcase_cnt: %u", _monitorenter_slowcase_cnt); tty->print_cr(" _monitorexit_slowcase_cnt: %u", _monitorexit_slowcase_cnt); tty->print_cr(" _patch_code_slowcase_cnt: %u", _patch_code_slowcase_cnt); @@ -1565,6 +1752,8 @@ void Runtime1::print_statistics() { tty->print_cr(" _throw_null_pointer_exception_count: %u:", _throw_null_pointer_exception_count); tty->print_cr(" _throw_class_cast_exception_count: %u:", _throw_class_cast_exception_count); tty->print_cr(" _throw_incompatible_class_change_error_count: %u:", _throw_incompatible_class_change_error_count); + tty->print_cr(" _throw_illegal_monitor_state_exception_count: %u:", _throw_illegal_monitor_state_exception_count); + tty->print_cr(" _throw_identity_exception_count: %u:", _throw_identity_exception_count); tty->print_cr(" _throw_count: %u:", _throw_count); SharedRuntime::print_ic_miss_histogram(); diff --git a/src/hotspot/share/c1/c1_Runtime1.hpp b/src/hotspot/share/c1/c1_Runtime1.hpp index 6fa17e53c19..e77e4a1a8a0 100644 --- a/src/hotspot/share/c1/c1_Runtime1.hpp +++ b/src/hotspot/share/c1/c1_Runtime1.hpp @@ -56,8 +56,14 @@ class Runtime1: public AllStatic { static uint _arraycopy_checkcast_attempt_cnt; static uint _new_type_array_slowcase_cnt; static uint _new_object_array_slowcase_cnt; + static uint _new_null_free_array_slowcase_cnt; static uint _new_instance_slowcase_cnt; static uint _new_multi_array_slowcase_cnt; + static uint _load_flat_array_slowcase_cnt; + static uint _store_flat_array_slowcase_cnt; + static uint _substitutability_check_slowcase_cnt; + static uint _buffer_inline_args_slowcase_cnt; + static uint _buffer_inline_args_no_receiver_slowcase_cnt; static uint _monitorenter_slowcase_cnt; static uint _monitorexit_slowcase_cnt; static uint _patch_code_slowcase_cnt; @@ -67,11 +73,14 @@ class Runtime1: public AllStatic { static uint _throw_null_pointer_exception_count; static uint _throw_class_cast_exception_count; static uint _throw_incompatible_class_change_error_count; + static uint _throw_illegal_monitor_state_exception_count; + static uint _throw_identity_exception_count; static uint _throw_count; #endif private: static CodeBlob* _blobs[(int)StubInfo::C1_STUB_COUNT]; + static void buffer_inline_args_impl(JavaThread* current, Method* m, bool allocate_receiver); // stub generation public: @@ -89,9 +98,16 @@ class Runtime1: public AllStatic { // runtime entry points static void new_instance (JavaThread* current, Klass* klass); + static void new_instance_no_inline(JavaThread* current, Klass* klass); static void new_type_array (JavaThread* current, Klass* klass, jint length); static void new_object_array(JavaThread* current, Klass* klass, jint length); + static void new_null_free_array(JavaThread* current, Klass* klass, jint length); static void new_multi_array (JavaThread* current, Klass* klass, int rank, jint* dims); + static void load_flat_array(JavaThread* current, flatArrayOopDesc* array, int index); + static void store_flat_array(JavaThread* current, flatArrayOopDesc* array, int index, oopDesc* value); + static int substitutability_check(JavaThread* current, oopDesc* left, oopDesc* right); + static void buffer_inline_args(JavaThread* current, Method* method); + static void buffer_inline_args_no_receiver(JavaThread* current, Method* method); static address counter_overflow(JavaThread* current, int bci, Method* method); @@ -105,6 +121,8 @@ class Runtime1: public AllStatic { static void throw_null_pointer_exception(JavaThread* current); static void throw_class_cast_exception(JavaThread* current, oopDesc* object); static void throw_incompatible_class_change_error(JavaThread* current); + static void throw_illegal_monitor_state_exception(JavaThread* current); + static void throw_identity_exception(JavaThread* current, oopDesc* object); static void throw_array_store_exception(JavaThread* current, oopDesc* object); static void monitorenter(JavaThread* current, oopDesc* obj, BasicObjectLock* lock); diff --git a/src/hotspot/share/c1/c1_ValueMap.cpp b/src/hotspot/share/c1/c1_ValueMap.cpp index 2d7634f6308..56bbdaf6c44 100644 --- a/src/hotspot/share/c1/c1_ValueMap.cpp +++ b/src/hotspot/share/c1/c1_ValueMap.cpp @@ -613,6 +613,8 @@ void GlobalValueNumbering::substitute(Instruction* instr) { assert(!instr->has_subst(), "substitution already set"); Value subst = current_map()->find_insert(instr); if (subst != instr) { + assert(instr->as_LoadIndexed() == nullptr || !instr->as_LoadIndexed()->should_profile(), "should not be optimized out"); + assert(instr->as_StoreIndexed() == nullptr, "should not be optimized out"); assert(!subst->has_subst(), "can't have a substitution"); TRACE_VALUE_NUMBERING(tty->print_cr("substitution for %c%d set to %c%d", instr->type()->tchar(), instr->id(), subst->type()->tchar(), subst->id())); diff --git a/src/hotspot/share/c1/c1_ValueMap.hpp b/src/hotspot/share/c1/c1_ValueMap.hpp index 6583a07c920..d5c26e28ff3 100644 --- a/src/hotspot/share/c1/c1_ValueMap.hpp +++ b/src/hotspot/share/c1/c1_ValueMap.hpp @@ -147,6 +147,9 @@ class ValueNumberingVisitor: public InstructionVisitor { kill_memory(); } else { kill_field(x->field(), x->needs_patching()); + if (x->enclosing_field() != nullptr) { + kill_field(x->enclosing_field(), true); + } } } void do_StoreIndexed (StoreIndexed* x) { kill_array(x->type()); } @@ -211,6 +214,7 @@ class ValueNumberingVisitor: public InstructionVisitor { void do_ExceptionObject(ExceptionObject* x) { /* nothing to do */ } void do_ProfileCall (ProfileCall* x) { /* nothing to do */ } void do_ProfileReturnType (ProfileReturnType* x) { /* nothing to do */ } + void do_ProfileACmpTypes(ProfileACmpTypes* x) { /* nothing to do */ } void do_ProfileInvoke (ProfileInvoke* x) { /* nothing to do */ }; void do_RuntimeCall (RuntimeCall* x) { /* nothing to do */ }; void do_MemBar (MemBar* x) { /* nothing to do */ }; diff --git a/src/hotspot/share/c1/c1_ValueStack.cpp b/src/hotspot/share/c1/c1_ValueStack.cpp index 9a09c186541..2430850c671 100644 --- a/src/hotspot/share/c1/c1_ValueStack.cpp +++ b/src/hotspot/share/c1/c1_ValueStack.cpp @@ -34,6 +34,7 @@ ValueStack::ValueStack(IRScope* scope, ValueStack* caller_state) , _caller_state(caller_state) , _bci(-99) , _kind(Parsing) +, _should_reexecute(false) , _locals(scope->method()->max_locals(), scope->method()->max_locals(), nullptr) , _stack(scope->method()->max_stack()) , _locks(nullptr) @@ -42,11 +43,12 @@ ValueStack::ValueStack(IRScope* scope, ValueStack* caller_state) verify(); } -ValueStack::ValueStack(ValueStack* copy_from, Kind kind, int bci) +ValueStack::ValueStack(ValueStack* copy_from, Kind kind, int bci, bool reexecute) : _scope(copy_from->scope()) , _caller_state(copy_from->caller_state()) , _bci(bci) , _kind(kind) + , _should_reexecute(reexecute) , _locals(copy_from->locals_size_for_copy(kind)) , _stack(copy_from->stack_size_for_copy(kind)) , _locks(copy_from->locks_size() == 0 ? nullptr : new Values(copy_from->locks_size())) @@ -210,7 +212,6 @@ int ValueStack::unlock() { return total_locks_size(); } - void ValueStack::setup_phi_for_stack(BlockBegin* b, int index) { assert(stack_at(index)->as_Phi() == nullptr || stack_at(index)->as_Phi()->block() != b, "phi function already created"); diff --git a/src/hotspot/share/c1/c1_ValueStack.hpp b/src/hotspot/share/c1/c1_ValueStack.hpp index bb0c475585c..9b8bdc217d3 100644 --- a/src/hotspot/share/c1/c1_ValueStack.hpp +++ b/src/hotspot/share/c1/c1_ValueStack.hpp @@ -54,6 +54,7 @@ class ValueStack: public CompilationResourceObj { ValueStack* _caller_state; int _bci; Kind _kind; + bool _should_reexecute; Values _locals; // the locals Values _stack; // the expression stack @@ -74,7 +75,7 @@ class ValueStack: public CompilationResourceObj { static void apply(const Values& list, ValueVisitor* f); // for simplified copying - ValueStack(ValueStack* copy_from, Kind kind, int bci); + ValueStack(ValueStack* copy_from, Kind kind, int bci, bool reexecute); int locals_size_for_copy(Kind kind) const; int stack_size_for_copy(Kind kind) const; @@ -82,9 +83,9 @@ class ValueStack: public CompilationResourceObj { // creation ValueStack(IRScope* scope, ValueStack* caller_state); - ValueStack* copy() { return new ValueStack(this, _kind, _bci); } - ValueStack* copy(Kind new_kind, int new_bci) { return new ValueStack(this, new_kind, new_bci); } - ValueStack* copy_for_parsing() { return new ValueStack(this, Parsing, -99); } + ValueStack* copy() { return new ValueStack(this, _kind, _bci, _should_reexecute); } + ValueStack* copy(Kind new_kind, int new_bci) { return new ValueStack(this, new_kind, new_bci, _should_reexecute); } + ValueStack* copy_for_parsing() { return new ValueStack(this, Parsing, -99, false); } // Used when no exception handler is found static Kind empty_exception_kind(bool caller = false) { @@ -106,6 +107,8 @@ class ValueStack: public CompilationResourceObj { ValueStack* caller_state() const { return _caller_state; } int bci() const { return _bci; } Kind kind() const { return _kind; } + bool should_reexecute() const { return _should_reexecute; } + void set_should_reexecute(bool reexec) { _should_reexecute = reexec; } int locals_size() const { return _locals.length(); } int stack_size() const { return _stack.length(); } diff --git a/src/hotspot/share/c1/c1_globals.hpp b/src/hotspot/share/c1/c1_globals.hpp index ff392312874..fad45f747b2 100644 --- a/src/hotspot/share/c1/c1_globals.hpp +++ b/src/hotspot/share/c1/c1_globals.hpp @@ -294,7 +294,10 @@ "Update MethodData*s in Tier 3 C1 generated code") \ \ develop(bool, PrintCFGToFile, false, \ - "print control flow graph to a separate file during compilation") + "print control flow graph to a separate file during compilation") \ + \ + develop(bool, C1UseDelayedFlattenedFieldReads, true, \ + "Use delayed reads of flat fields to reduce heap buffering") // end of C1_FLAGS diff --git a/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp b/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp index 99d7c26b293..f54e516e368 100644 --- a/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp +++ b/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp @@ -376,6 +376,7 @@ bool AOTLinkedClassBulkLoader::is_pending_aot_linked_class(Klass* k) { if (k->is_objArray_klass()) { k = ObjArrayKlass::cast(k)->bottom_klass(); } + if (!k->is_instance_klass()) { // type array klasses (and their higher dimensions), // must have been loaded before a GC can ever happen. diff --git a/src/hotspot/share/cds/archiveBuilder.cpp b/src/hotspot/share/cds/archiveBuilder.cpp index 00aca188f96..77f2874c1e3 100644 --- a/src/hotspot/share/cds/archiveBuilder.cpp +++ b/src/hotspot/share/cds/archiveBuilder.cpp @@ -855,14 +855,20 @@ void ArchiveBuilder::make_klasses_shareable() { address narrow_klass_base = _requested_static_archive_bottom; // runtime encoding base == runtime mapping start const int narrow_klass_shift = precomputed_narrow_klass_shift(); narrowKlass nk = CompressedKlassPointers::encode_not_null_without_asserts(requested_k, narrow_klass_base, narrow_klass_shift); - k->set_prototype_header(markWord::prototype().set_narrow_klass(nk)); + k->set_prototype_header_klass(nk); } #endif //_LP64 - if (k->is_objArray_klass()) { + if (k->is_flatArray_klass()) { + num_obj_array_klasses ++; + type = "flat array"; + } else if (k->is_refArray_klass()) { + num_obj_array_klasses ++; + type = "ref array"; + } else if (k->is_objArray_klass()) { // InstanceKlass and TypeArrayKlass will in turn call remove_unshareable_info // on their array classes. num_obj_array_klasses ++; - type = "array"; + type = "obj array"; } else if (k->is_typeArray_klass()) { num_type_array_klasses ++; type = "array"; diff --git a/src/hotspot/share/cds/archiveHeapWriter.cpp b/src/hotspot/share/cds/archiveHeapWriter.cpp index c7750c70f1b..6b8ca8ec2cc 100644 --- a/src/hotspot/share/cds/archiveHeapWriter.cpp +++ b/src/hotspot/share/cds/archiveHeapWriter.cpp @@ -224,12 +224,13 @@ void ArchiveHeapWriter::ensure_buffer_space(size_t min_bytes) { objArrayOop ArchiveHeapWriter::allocate_root_segment(size_t offset, int element_count) { HeapWord* mem = offset_to_buffered_address(offset); - memset(mem, 0, objArrayOopDesc::object_size(element_count)); + memset(mem, 0, refArrayOopDesc::object_size(element_count)); // The initialization code is copied from MemAllocator::finish and ObjArrayAllocator::initialize. if (UseCompactObjectHeaders) { oopDesc::release_set_mark(mem, Universe::objectArrayKlass()->prototype_header()); } else { + assert(!EnableValhalla || Universe::objectArrayKlass()->prototype_header() == markWord::prototype(), "should be the same"); oopDesc::set_mark(mem, markWord::prototype()); oopDesc::release_set_klass(mem, Universe::objectArrayKlass()); } @@ -260,7 +261,7 @@ void ArchiveHeapWriter::copy_roots_to_buffer(GrowableArrayCHeapfast_no_hash_check()) { + if (!src_obj->fast_no_hash_check() && (!(EnableValhalla && src_obj->mark().is_inline_type()))) { intptr_t src_hash = src_obj->identity_hash(); if (UseCompactObjectHeaders) { fake_oop->set_mark(markWord::prototype().set_narrow_klass(nk).copy_set_hash(src_hash)); + } else if (EnableValhalla) { + fake_oop->set_mark(src_klass->prototype_header().copy_set_hash(src_hash)); } else { fake_oop->set_mark(markWord::prototype().copy_set_hash(src_hash)); } diff --git a/src/hotspot/share/cds/archiveUtils.cpp b/src/hotspot/share/cds/archiveUtils.cpp index 3c900cad035..f700c0e22ad 100644 --- a/src/hotspot/share/cds/archiveUtils.cpp +++ b/src/hotspot/share/cds/archiveUtils.cpp @@ -391,7 +391,7 @@ bool ArchiveUtils::has_aot_initialized_mirror(InstanceKlass* src_ik) { size_t HeapRootSegments::size_in_bytes(size_t seg_idx) { assert(seg_idx < _count, "In range"); - return objArrayOopDesc::object_size(size_in_elems(seg_idx)) * HeapWordSize; + return refArrayOopDesc::object_size(size_in_elems(seg_idx)) * HeapWordSize; } int HeapRootSegments::size_in_elems(size_t seg_idx) { diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp index 6922b66d7cd..1eef97f843a 100644 --- a/src/hotspot/share/cds/cdsConfig.cpp +++ b/src/hotspot/share/cds/cdsConfig.cpp @@ -37,6 +37,7 @@ #include "memory/universe.hpp" #include "prims/jvmtiAgentList.hpp" #include "runtime/arguments.hpp" +#include "runtime/globals.hpp" #include "runtime/globals_extension.hpp" #include "runtime/java.hpp" #include "runtime/vmThread.hpp" @@ -57,6 +58,9 @@ bool CDSConfig::_old_cds_flags_used = false; bool CDSConfig::_new_aot_flags_used = false; bool CDSConfig::_disable_heap_dumping = false; +bool CDSConfig::_module_patching_disables_cds = false; +bool CDSConfig::_java_base_module_patching_disables_cds = false; + const char* CDSConfig::_default_archive_path = nullptr; const char* CDSConfig::_input_static_archive_path = nullptr; const char* CDSConfig::_input_dynamic_archive_path = nullptr; @@ -142,6 +146,9 @@ const char* CDSConfig::default_archive_path() { tmp.print_raw("_coh"); } #endif + if (is_valhalla_preview()) { + tmp.print_raw("_valhalla"); + } tmp.print_raw(".jsa"); _default_archive_path = os::strdup(tmp.base()); } @@ -297,7 +304,7 @@ void CDSConfig::ergo_init_classic_archive_paths() { } void CDSConfig::check_internal_module_property(const char* key, const char* value) { - if (Arguments::is_incompatible_cds_internal_module_property(key)) { + if (Arguments::is_incompatible_cds_internal_module_property(key) && !Arguments::patching_migrated_classes(key, value)) { stop_using_optimized_module_handling(); aot_log_info(aot)("optimized module handling: disabled due to incompatible property: %s=%s", key, value); } @@ -330,13 +337,11 @@ static const char* find_any_unsupported_module_option() { // directly specified in the command-line. static const char* unsupported_module_properties[] = { "jdk.module.limitmods", - "jdk.module.upgrade.path", - "jdk.module.patch.0" + "jdk.module.upgrade.path" }; static const char* unsupported_module_options[] = { "--limit-modules", - "--upgrade-module-path", - "--patch-module" + "--upgrade-module-path" }; assert(ARRAY_SIZE(unsupported_module_properties) == ARRAY_SIZE(unsupported_module_options), "must be"); @@ -359,6 +364,12 @@ void CDSConfig::check_unsupported_dumping_module_options() { if (option != nullptr) { vm_exit_during_initialization("Cannot use the following option when dumping the shared archive", option); } + + if (module_patching_disables_cds()) { + vm_exit_during_initialization( + "Cannot use the following option when dumping the shared archive", "--patch-module"); + } + // Check for an exploded module build in use with -Xshare:dump. if (!Arguments::has_jimage()) { vm_exit_during_initialization("Dumping the shared archive is not supported with an exploded module build"); @@ -387,6 +398,16 @@ bool CDSConfig::has_unsupported_runtime_module_options() { } return true; } + + if (module_patching_disables_cds()) { + if (RequireSharedSpaces) { + warning("CDS is disabled when the %s option is specified.", "--patch-module"); + } else { + log_info(cds)("CDS is disabled when the %s option is specified.", "--patch-module"); + } + return true; + } + return false; } @@ -625,7 +646,7 @@ void CDSConfig::ergo_init_aot_paths() { } } -bool CDSConfig::check_vm_args_consistency(bool patch_mod_javabase, bool mode_flag_cmd_line) { +bool CDSConfig::check_vm_args_consistency(bool mode_flag_cmd_line) { assert(!_cds_ergo_initialize_started, "This is called earlier than CDSConfig::ergo_initialize()"); check_aot_flags(); @@ -700,7 +721,7 @@ bool CDSConfig::check_vm_args_consistency(bool patch_mod_javabase, bool mode_fla } } - if (is_using_archive() && patch_mod_javabase) { + if (is_using_archive() && java_base_module_patching_disables_cds() && module_patching_disables_cds()) { Arguments::no_shared_spaces("CDS is disabled when " JAVA_BASE_NAME " module is patched."); } if (is_using_archive() && has_unsupported_runtime_module_options()) { @@ -928,6 +949,10 @@ bool CDSConfig::are_vm_options_incompatible_with_dumping_heap() { } bool CDSConfig::is_dumping_heap() { + if (is_valhalla_preview()) { + // Not working yet -- e.g., HeapShared::oop_hash() needs to be implemented for value oops + return false; + } if (!(is_dumping_classic_static_archive() || is_dumping_final_static_archive()) || are_vm_options_incompatible_with_dumping_heap() || _disable_heap_dumping) { diff --git a/src/hotspot/share/cds/cdsConfig.hpp b/src/hotspot/share/cds/cdsConfig.hpp index 1fd229ff34f..2090449f2c6 100644 --- a/src/hotspot/share/cds/cdsConfig.hpp +++ b/src/hotspot/share/cds/cdsConfig.hpp @@ -26,6 +26,7 @@ #define SHARE_CDS_CDSCONFIG_HPP #include "memory/allStatic.hpp" +#include "runtime/arguments.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" @@ -44,6 +45,9 @@ class CDSConfig : public AllStatic { static bool _is_single_command_training; static bool _has_temp_aot_config_file; + static bool _module_patching_disables_cds; + static bool _java_base_module_patching_disables_cds; + const static char* _default_archive_path; const static char* _input_static_archive_path; const static char* _input_dynamic_archive_path; @@ -94,7 +98,12 @@ class CDSConfig : public AllStatic { static void check_internal_module_property(const char* key, const char* value) NOT_CDS_RETURN; static void check_incompatible_property(const char* key, const char* value) NOT_CDS_RETURN; static bool has_unsupported_runtime_module_options() NOT_CDS_RETURN_(false); - static bool check_vm_args_consistency(bool patch_mod_javabase, bool mode_flag_cmd_line) NOT_CDS_RETURN_(true); + static bool check_vm_args_consistency(bool mode_flag_cmd_line) NOT_CDS_RETURN_(true); + + static bool module_patching_disables_cds() { return CDS_ONLY(_module_patching_disables_cds) NOT_CDS(false); } + static void set_module_patching_disables_cds() { CDS_ONLY(_module_patching_disables_cds = true;) } + static bool java_base_module_patching_disables_cds() { return CDS_ONLY(_java_base_module_patching_disables_cds) NOT_CDS(false); } + static void set_java_base_module_patching_disables_cds() { CDS_ONLY(_java_base_module_patching_disables_cds = true;) } static const char* type_of_archive_being_loaded(); static const char* type_of_archive_being_written(); static void prepare_for_dumping(); @@ -188,6 +197,10 @@ class CDSConfig : public AllStatic { static void stop_dumping_full_module_graph(const char* reason = nullptr) NOT_CDS_JAVA_HEAP_RETURN; static void stop_using_full_module_graph(const char* reason = nullptr) NOT_CDS_JAVA_HEAP_RETURN; + static bool is_valhalla_preview() { + return Arguments::enable_preview() && EnableValhalla; + } + // --- AOT code static bool is_dumping_aot_code() NOT_CDS_RETURN_(false); diff --git a/src/hotspot/share/cds/cdsEnumKlass.cpp b/src/hotspot/share/cds/cdsEnumKlass.cpp index e46a7eb84f4..8c230412392 100644 --- a/src/hotspot/share/cds/cdsEnumKlass.cpp +++ b/src/hotspot/share/cds/cdsEnumKlass.cpp @@ -89,10 +89,13 @@ void CDSEnumKlass::archive_static_field(int level, KlassSubGraphInfo* subgraph_i ik->external_name(), fd.name()->as_C_string()); } oop oop_field = mirror->obj_field(fd.offset()); + // There should be no oops for ObjArrayKlass but InstanceKlass::array_klasses holds a list of ObjArrayKlass, + // therefore we need the super of the refined array klass. + Klass* oop_field_klass = oop_field->is_refined_objArray() ? oop_field->klass()->super() : oop_field->klass(); if (oop_field == nullptr) { guarantee(false, "static field %s::%s must not be null", ik->external_name(), fd.name()->as_C_string()); - } else if (oop_field->klass() != ik && oop_field->klass() != ik->array_klass_or_null()) { + } else if (oop_field_klass != ik && oop_field_klass != ik->array_klass_or_null()) { guarantee(false, "static field %s::%s is of the wrong type", ik->external_name(), fd.name()->as_C_string()); } diff --git a/src/hotspot/share/cds/cdsProtectionDomain.cpp b/src/hotspot/share/cds/cdsProtectionDomain.cpp index c95fd8b64df..ebf00931305 100644 --- a/src/hotspot/share/cds/cdsProtectionDomain.cpp +++ b/src/hotspot/share/cds/cdsProtectionDomain.cpp @@ -36,6 +36,7 @@ #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "oops/instanceKlass.hpp" +#include "oops/refArrayOop.hpp" #include "oops/symbol.hpp" #include "runtime/javaCalls.hpp" @@ -294,7 +295,7 @@ void CDSProtectionDomain::atomic_set_array_index(OopHandle array, int index, oop // The important thing here is that all threads pick up the same result. // It doesn't matter which racing thread wins, as long as only one // result is used by all threads, and all future queries. - ((objArrayOop)array.resolve())->replace_if_null(index, o); + refArrayOopDesc::cast(array.resolve())->replace_if_null(index, o); } oop CDSProtectionDomain::shared_protection_domain(int index) { diff --git a/src/hotspot/share/cds/classListParser.cpp b/src/hotspot/share/cds/classListParser.cpp index 75737b1432e..6ce28030306 100644 --- a/src/hotspot/share/cds/classListParser.cpp +++ b/src/hotspot/share/cds/classListParser.cpp @@ -566,11 +566,15 @@ InstanceKlass* ClassListParser::load_class_from_source(Symbol* class_name, TRAPS specified_super->external_name(), _super, k->super()->external_name()); } - if (k->local_interfaces()->length() != _interfaces->length()) { + const int actual_num_interfaces = k->local_interfaces()->length(); + const int specified_num_interfaces = _interfaces->length(); // specified in classlist + int expected_num_interfaces = actual_num_interfaces; + + if (specified_num_interfaces != expected_num_interfaces) { print_specified_interfaces(); print_actual_interfaces(k); error("The number of interfaces (%d) specified in class list does not match the class file (%d)", - _interfaces->length(), k->local_interfaces()->length()); + specified_num_interfaces, expected_num_interfaces); } for (int i = 0; i < _interfaces->length(); i++) { InstanceKlass* specified_interface = specified_interfaces.at(i); diff --git a/src/hotspot/share/cds/cppVtables.cpp b/src/hotspot/share/cds/cppVtables.cpp index 261f5332526..8ea9c43bf74 100644 --- a/src/hotspot/share/cds/cppVtables.cpp +++ b/src/hotspot/share/cds/cppVtables.cpp @@ -28,13 +28,17 @@ #include "cds/cppVtables.hpp" #include "cds/metaspaceShared.hpp" #include "logging/log.hpp" +#include "oops/flatArrayKlass.hpp" +#include "oops/inlineKlass.hpp" #include "oops/instanceClassLoaderKlass.hpp" +#include "oops/instanceKlass.inline.hpp" #include "oops/instanceMirrorKlass.hpp" #include "oops/instanceRefKlass.hpp" #include "oops/instanceStackChunkKlass.hpp" #include "oops/methodCounters.hpp" #include "oops/methodData.hpp" #include "oops/objArrayKlass.hpp" +#include "oops/refArrayKlass.hpp" #include "oops/trainingData.hpp" #include "oops/typeArrayKlass.hpp" #include "runtime/arguments.hpp" @@ -54,6 +58,7 @@ // into our own tables. // Currently, the archive contains ONLY the following types of objects that have C++ vtables. +// NOTE: this table must be in-sync with sun.jvm.hotspot.memory.FileMapInfo::populateMetadataTypeArray(). #define CPP_VTABLE_TYPES_DO(f) \ f(ConstantPool) \ f(InstanceKlass) \ @@ -66,6 +71,9 @@ f(MethodCounters) \ f(ObjArrayKlass) \ f(TypeArrayKlass) \ + f(FlatArrayKlass) \ + f(InlineKlass) \ + f(RefArrayKlass) \ f(KlassTrainingData) \ f(MethodTrainingData) \ f(CompileTrainingData) diff --git a/src/hotspot/share/cds/dynamicArchive.cpp b/src/hotspot/share/cds/dynamicArchive.cpp index 8499dced126..c4222e52e9d 100644 --- a/src/hotspot/share/cds/dynamicArchive.cpp +++ b/src/hotspot/share/cds/dynamicArchive.cpp @@ -366,6 +366,9 @@ void DynamicArchiveBuilder::gather_array_klasses() { for (int i = 0; i < klasses()->length(); i++) { if (klasses()->at(i)->is_objArray_klass()) { ObjArrayKlass* oak = ObjArrayKlass::cast(klasses()->at(i)); + if (oak->is_refined_objArray_klass()) { + oak = ObjArrayKlass::cast(oak->super()); + } Klass* elem = oak->element_klass(); if (MetaspaceShared::in_aot_cache_static_region(elem)) { // Only capture the array klass whose element_klass is in the static archive. @@ -436,14 +439,16 @@ void DynamicArchive::setup_array_klasses() { Klass* elm = oak->element_klass(); assert(MetaspaceShared::in_aot_cache_static_region((void*)elm), "must be"); - - if (elm->is_instance_klass()) { - assert(InstanceKlass::cast(elm)->array_klasses() == nullptr, "must be"); - InstanceKlass::cast(elm)->set_array_klasses(oak); - } else { - assert(elm->is_array_klass(), "sanity"); - assert(ArrayKlass::cast(elm)->higher_dimension() == nullptr, "must be"); - ArrayKlass::cast(elm)->set_higher_dimension(oak); + // Higher dimension may have been set when doing setup on ObjArrayKlass + if (!oak->is_refined_objArray_klass()) { + if (elm->is_instance_klass()) { + assert(InstanceKlass::cast(elm)->array_klasses() == nullptr, "must be"); + InstanceKlass::cast(elm)->set_array_klasses(oak); + } else { + assert(elm->is_array_klass(), "sanity"); + assert(ArrayKlass::cast(elm)->higher_dimension() == nullptr, "must be"); + ArrayKlass::cast(elm)->set_higher_dimension(oak); + } } } log_debug(aot)("Total array klasses read from dynamic archive: %d", _dynamic_archive_array_klasses->length()); diff --git a/src/hotspot/share/cds/filemap.cpp b/src/hotspot/share/cds/filemap.cpp index 78f02161477..a29b7460363 100644 --- a/src/hotspot/share/cds/filemap.cpp +++ b/src/hotspot/share/cds/filemap.cpp @@ -87,6 +87,71 @@ #define O_BINARY 0 // otherwise do nothing. #endif +inline void CDSMustMatchFlags::do_print(outputStream* st, bool v) { + st->print("%s", v ? "true" : "false"); +} + +inline void CDSMustMatchFlags::do_print(outputStream* st, intx v) { + st->print("%zd", v); +} + +inline void CDSMustMatchFlags::do_print(outputStream* st, uintx v) { + st->print("%zu", v); +} + +inline void CDSMustMatchFlags::do_print(outputStream* st, double v) { + st->print("%f", v); +} + +void CDSMustMatchFlags::init() { + assert(CDSConfig::is_dumping_archive(), "sanity"); + _max_name_width = 0; + +#define INIT_CDS_MUST_MATCH_FLAG(n) \ + _v_##n = n; \ + _max_name_width = MAX2(_max_name_width,strlen(#n)); + CDS_MUST_MATCH_FLAGS_DO(INIT_CDS_MUST_MATCH_FLAG); +#undef INIT_CDS_MUST_MATCH_FLAG +} + +bool CDSMustMatchFlags::runtime_check() const { +#define CHECK_CDS_MUST_MATCH_FLAG(n) \ + if (_v_##n != n) { \ + ResourceMark rm; \ + stringStream ss; \ + ss.print("VM option %s is different between dumptime (", #n); \ + do_print(&ss, _v_ ## n); \ + ss.print(") and runtime ("); \ + do_print(&ss, n); \ + ss.print(")"); \ + log_info(cds)("%s", ss.as_string()); \ + return false; \ + } + CDS_MUST_MATCH_FLAGS_DO(CHECK_CDS_MUST_MATCH_FLAG); +#undef CHECK_CDS_MUST_MATCH_FLAG + + return true; +} + +void CDSMustMatchFlags::print_info() const { + LogTarget(Info, cds) lt; + if (lt.is_enabled()) { + LogStream ls(lt); + ls.print_cr("Recorded VM flags during dumptime:"); + print(&ls); + } +} + +void CDSMustMatchFlags::print(outputStream* st) const { +#define PRINT_CDS_MUST_MATCH_FLAG(n) \ + st->print("- %-s ", #n); \ + st->sp(int(_max_name_width - strlen(#n))); \ + do_print(st, _v_##n); \ + st->cr(); + CDS_MUST_MATCH_FLAGS_DO(PRINT_CDS_MUST_MATCH_FLAG); +#undef PRINT_CDS_MUST_MATCH_FLAG +} + // Fill in the fileMapInfo structure with data about this VM instance. // This method copies the vm version info into header_version. If the version is too @@ -246,6 +311,7 @@ void FileMapHeader::populate(FileMapInfo *info, size_t core_region_alignment, _use_optimized_module_handling = CDSConfig::is_using_optimized_module_handling(); _has_aot_linked_classes = CDSConfig::is_dumping_aot_linked_classes(); _has_full_module_graph = CDSConfig::is_dumping_full_module_graph(); + _has_valhalla_patched_classes = CDSConfig::is_valhalla_preview(); // The following fields are for sanity checks for whether this archive // will function correctly with this JVM and the bootclasspath it's @@ -260,6 +326,7 @@ void FileMapHeader::populate(FileMapInfo *info, size_t core_region_alignment, _requested_base_address = (char*)SharedBaseAddress; _mapped_base_address = (char*)SharedBaseAddress; _allow_archiving_with_java_agent = AllowArchivingWithJavaAgent; + _must_match.init(); } void FileMapHeader::copy_base_archive_name(const char* archive) { @@ -319,6 +386,8 @@ void FileMapHeader::print(outputStream* st) { st->print_cr("- allow_archiving_with_java_agent:%d", _allow_archiving_with_java_agent); st->print_cr("- use_optimized_module_handling: %d", _use_optimized_module_handling); st->print_cr("- has_full_module_graph %d", _has_full_module_graph); + st->print_cr("- has_valhalla_patched_classes %d", _has_valhalla_patched_classes); + _must_match.print(st); st->print_cr("- has_aot_linked_classes %d", _has_aot_linked_classes); } @@ -702,6 +771,10 @@ bool FileMapInfo::init_from_file(int fd) { } } + if (!header()->check_must_match_flags()) { + return false; + } + return true; } @@ -2074,6 +2147,24 @@ bool FileMapHeader::validate() { return false; } + if (is_static()) { + const char* err = nullptr; + if (CDSConfig::is_valhalla_preview()) { + if (!_has_valhalla_patched_classes) { + err = "not created"; + } + } else { + if (_has_valhalla_patched_classes) { + err = "created"; + } + } + if (err != nullptr) { + log_warning(cds)("This archive was %s with --enable-preview -XX:+EnableValhalla. It is " + "incompatible with the current JVM setting", err); + return false; + } + } + if (compact_headers() != UseCompactObjectHeaders) { aot_log_warning(aot)("Unable to use %s.\nThe %s's UseCompactObjectHeaders setting (%s)" " does not equal the current UseCompactObjectHeaders setting (%s).", file_type, file_type, diff --git a/src/hotspot/share/cds/filemap.hpp b/src/hotspot/share/cds/filemap.hpp index 02390874f39..526da20d12d 100644 --- a/src/hotspot/share/cds/filemap.hpp +++ b/src/hotspot/share/cds/filemap.hpp @@ -32,6 +32,7 @@ #include "memory/allocation.hpp" #include "oops/array.hpp" #include "oops/compressedOops.hpp" +#include "runtime/globals.hpp" #include "utilities/align.hpp" // To understand the layout of the CDS archive file: @@ -98,6 +99,37 @@ class FileMapRegion: private CDSFileMapRegion { void print(outputStream* st, int region_index); }; +#define CDS_MUST_MATCH_FLAGS_DO(f) \ + f(EnableValhalla) \ + f(UseArrayFlattening) \ + f(UseFieldFlattening) \ + f(InlineTypePassFieldsAsArgs) \ + f(InlineTypeReturnedAsFields) \ + f(UseNonAtomicValueFlattening) \ + f(UseAtomicValueFlattening) \ + f(UseNullableValueFlattening) + + +class CDSMustMatchFlags { +private: + size_t _max_name_width; +#define DECLARE_CDS_MUST_MATCH_FLAG(n) \ + decltype(n) _v_##n; + CDS_MUST_MATCH_FLAGS_DO(DECLARE_CDS_MUST_MATCH_FLAG); +#undef DECLARE_CDS_MUST_MATCH_FLAG + + inline static void do_print(outputStream* st, bool v); + inline static void do_print(outputStream* st, intx v); + inline static void do_print(outputStream* st, uintx v); + inline static void do_print(outputStream* st, double v); + void print_info() const; + +public: + void init(); + bool runtime_check() const; + void print(outputStream* st) const; +}; + class FileMapHeader: private CDSFileMapHeaderBase { friend class CDSConstants; friend class VMStructs; @@ -140,6 +172,8 @@ class FileMapHeader: private CDSFileMapHeaderBase { // some expensive operations. bool _has_aot_linked_classes; // Was the CDS archive created with -XX:+AOTClassLinking bool _has_full_module_graph; // Does this CDS archive contain the full archived module graph? + bool _has_valhalla_patched_classes; // Is this archived dumped with --enable-preview -XX:+EnableValhalla? + CDSMustMatchFlags _must_match; // These flags must be the same between dumptime and runtime HeapRootSegments _heap_root_segments; // Heap root segments info size_t _heap_oopmap_start_pos; // The first bit in the oopmap corresponds to this position in the heap. size_t _heap_ptrmap_start_pos; // The first bit in the ptrmap corresponds to this position in the heap. @@ -248,6 +282,10 @@ class FileMapHeader: private CDSFileMapHeaderBase { return (0 <= region && region < NUM_CDS_REGIONS); } + bool check_must_match_flags() const { + return _must_match.runtime_check(); + } + void print(outputStream* st); }; diff --git a/src/hotspot/share/cds/heapShared.cpp b/src/hotspot/share/cds/heapShared.cpp index cfa2944d974..be01d111790 100644 --- a/src/hotspot/share/cds/heapShared.cpp +++ b/src/hotspot/share/cds/heapShared.cpp @@ -594,6 +594,7 @@ static void copy_java_mirror_hashcode(oop orig_mirror, oop scratch_m) { narrowKlass nk = CompressedKlassPointers::encode(orig_mirror->klass()); scratch_m->set_mark(markWord::prototype().set_narrow_klass(nk).copy_set_hash(src_hash)); } else { + // For valhalla, the prototype header is the same as markWord::prototype(); scratch_m->set_mark(markWord::prototype().copy_set_hash(src_hash)); } assert(scratch_m->mark().is_unlocked(), "sanity"); @@ -1302,7 +1303,11 @@ void HeapShared::resolve_or_init(Klass* k, bool do_init, TRAPS) { if (!do_init) { if (k->class_loader_data() == nullptr) { Klass* resolved_k = SystemDictionary::resolve_or_null(k->name(), CHECK); - assert(resolved_k == k, "classes used by archived heap must not be replaced by JVMTI ClassFileLoadHook"); + if (resolved_k->is_array_klass()) { + assert(resolved_k == k || resolved_k == k->super(), "classes used by archived heap must not be replaced by JVMTI ClassFileLoadHook"); + } else { + assert(resolved_k == k, "classes used by archived heap must not be replaced by JVMTI ClassFileLoadHook"); + } } } else { assert(k->class_loader_data() != nullptr, "must have been resolved by HeapShared::resolve_classes"); @@ -2044,6 +2049,13 @@ void HeapShared::archive_object_subgraphs(ArchivableStaticFieldInfo fields[], for (int i = 0; fields[i].valid(); ) { ArchivableStaticFieldInfo* info = &fields[i]; const char* klass_name = info->klass_name; + + if (CDSConfig::is_valhalla_preview() && strcmp(klass_name, "jdk/internal/module/ArchivedModuleGraph") == 0) { + // FIXME -- ArchivedModuleGraph doesn't work when java.base is patched with valhalla classes. + i++; + continue; + } + start_recording_subgraph(info->klass, klass_name, is_full_module_graph); // If you have specified consecutive fields of the same klass in diff --git a/src/hotspot/share/cds/metaspaceShared.cpp b/src/hotspot/share/cds/metaspaceShared.cpp index 92773c5be90..b93c1cc3398 100644 --- a/src/hotspot/share/cds/metaspaceShared.cpp +++ b/src/hotspot/share/cds/metaspaceShared.cpp @@ -77,6 +77,8 @@ #include "memory/universe.hpp" #include "nmt/memTracker.hpp" #include "oops/compressedKlass.hpp" +#include "oops/flatArrayKlass.hpp" +#include "oops/inlineKlass.hpp" #include "oops/instanceMirrorKlass.hpp" #include "oops/klass.inline.hpp" #include "oops/objArrayOop.hpp" @@ -124,7 +126,7 @@ bool MetaspaceShared::_use_optimized_module_handling = true; // These regions are aligned with MetaspaceShared::core_region_alignment(). // // These 2 regions are populated in the following steps: -// [0] All classes are loaded in MetaspaceShared::preload_classes(). All metadata are +// [0] All classes are loaded in MetaspaceShared::loadable_descriptors(). All metadata are // temporarily allocated outside of the shared regions. // [1] We enter a safepoint and allocate a buffer for the rw/ro regions. // [2] C++ vtables are copied into the rw region. @@ -478,7 +480,7 @@ void MetaspaceShared::serialize(SerializeClosure* soc) { soc->do_tag(arrayOopDesc::base_offset_in_bytes(T_BYTE)); soc->do_tag(sizeof(ConstantPool)); soc->do_tag(sizeof(ConstantPoolCache)); - soc->do_tag(objArrayOopDesc::base_offset_in_bytes()); + soc->do_tag(refArrayOopDesc::base_offset_in_bytes()); soc->do_tag(typeArrayOopDesc::base_offset_in_bytes(T_BYTE)); soc->do_tag(sizeof(Symbol)); @@ -893,7 +895,7 @@ void MetaspaceShared::get_default_classlist(char* default_classlist, const size_ Arguments::get_java_home(), filesep, filesep); } -void MetaspaceShared::preload_classes(TRAPS) { +void MetaspaceShared::loadable_descriptors(TRAPS) { char default_classlist[JVM_MAXPATHLEN]; const char* classlist_path; @@ -940,7 +942,7 @@ void MetaspaceShared::exercise_runtime_cds_code(TRAPS) { void MetaspaceShared::preload_and_dump_impl(StaticArchiveBuilder& builder, TRAPS) { if (CDSConfig::is_dumping_classic_static_archive()) { // We are running with -Xshare:dump - preload_classes(CHECK); + loadable_descriptors(CHECK); if (SharedArchiveConfigFile) { log_info(aot)("Reading extra data from %s ...", SharedArchiveConfigFile); @@ -1245,6 +1247,11 @@ bool MetaspaceShared::try_link_class(JavaThread* current, InstanceKlass* ik) { } void VM_PopulateDumpSharedSpace::dump_java_heap_objects() { + if (CDSConfig::is_valhalla_preview()) { + log_info(cds)("Archived java heap is not yet supported with Valhalla preview"); + return; + } + if (CDSConfig::is_dumping_heap()) { HeapShared::write_heap(&_heap_info); } else { diff --git a/src/hotspot/share/cds/metaspaceShared.hpp b/src/hotspot/share/cds/metaspaceShared.hpp index 7f0f1128f96..fdaca9c873b 100644 --- a/src/hotspot/share/cds/metaspaceShared.hpp +++ b/src/hotspot/share/cds/metaspaceShared.hpp @@ -80,7 +80,7 @@ class MetaspaceShared : AllStatic { private: static void exercise_runtime_cds_code(TRAPS) NOT_CDS_RETURN; static void preload_and_dump_impl(StaticArchiveBuilder& builder, TRAPS) NOT_CDS_RETURN; - static void preload_classes(TRAPS) NOT_CDS_RETURN; + static void loadable_descriptors(TRAPS) NOT_CDS_RETURN; public: static Symbol* symbol_rs_base() { diff --git a/src/hotspot/share/ci/bcEscapeAnalyzer.cpp b/src/hotspot/share/ci/bcEscapeAnalyzer.cpp index 712f7af4139..946cbea2e06 100644 --- a/src/hotspot/share/ci/bcEscapeAnalyzer.cpp +++ b/src/hotspot/share/ci/bcEscapeAnalyzer.cpp @@ -554,6 +554,9 @@ void BCEscapeAnalyzer::iterate_one_block(ciBlock *blk, StateInfo &state, Growabl set_global_escape(state.apop()); state.spop(); ArgumentMap arr = state.apop(); + // If the array is a flat array, a larger part of it is modified than + // the size of a reference. However, if OFFSET_ANY is given as + // parameter to set_modified(), size is not taken into account. set_modified(arr, OFFSET_ANY, type2size[T_OBJECT]*HeapWordSize); break; } diff --git a/src/hotspot/share/ci/ciArray.cpp b/src/hotspot/share/ci/ciArray.cpp index 6b1a30fec6a..82d8b04e9f9 100644 --- a/src/hotspot/share/ci/ciArray.cpp +++ b/src/hotspot/share/ci/ciArray.cpp @@ -37,6 +37,7 @@ // This class represents an arrayOop in the HotSpot virtual // machine. static BasicType fixup_element_type(BasicType bt) { + if (bt == T_FLAT_ELEMENT) return T_OBJECT; if (is_reference_type(bt)) return T_OBJECT; if (bt == T_BOOLEAN) return T_BYTE; return bt; @@ -119,6 +120,26 @@ ciConstant ciArray::element_value_by_offset(intptr_t element_offset) { return element_value((jint) index); } +bool ciArray::is_null_free() { + VM_ENTRY_MARK; + return get_oop()->is_null_free_array(); +} + +bool ciArray::is_atomic() { + VM_ENTRY_MARK; + arrayOop oop = get_arrayOop(); + if (oop->is_refArray()) { + return oop->klass()->is_inline_klass(); + } + if (oop->is_flatArray()) { + FlatArrayKlass* fak = FlatArrayKlass::cast(oop->klass()); + if (fak->element_klass()->is_naturally_atomic() || fak->layout_kind() == LayoutKind::ATOMIC_FLAT || fak->layout_kind() == LayoutKind::NULLABLE_ATOMIC_FLAT) { + return true; + } + } + return false; +} + // ------------------------------------------------------------------ // ciArray::print_impl // diff --git a/src/hotspot/share/ci/ciArray.hpp b/src/hotspot/share/ci/ciArray.hpp index d7b913436fe..ae01682ca16 100644 --- a/src/hotspot/share/ci/ciArray.hpp +++ b/src/hotspot/share/ci/ciArray.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,6 +29,7 @@ #include "ci/ciConstant.hpp" #include "ci/ciObject.hpp" #include "oops/arrayOop.hpp" +#include "oops/flatArrayOop.hpp" #include "oops/objArrayOop.hpp" #include "oops/typeArrayOop.hpp" @@ -43,6 +44,7 @@ class ciArray : public ciObject { protected: ciArray( objArrayHandle h_a) : ciObject(h_a), _length(h_a()->length()) {} ciArray(typeArrayHandle h_a) : ciObject(h_a), _length(h_a()->length()) {} + ciArray(flatArrayHandle h_a) : ciObject(h_a), _length(h_a()->length()) {} arrayOop get_arrayOop() const { return (arrayOop)get_oop(); } @@ -70,6 +72,10 @@ class ciArray : public ciObject { // What kind of ciObject is this? bool is_array() { return true; } + + virtual bool is_flat() { return false; } + bool is_null_free(); + bool is_atomic(); }; #endif // SHARE_CI_CIARRAY_HPP diff --git a/src/hotspot/share/ci/ciArrayKlass.cpp b/src/hotspot/share/ci/ciArrayKlass.cpp index 947cc0cb6fc..4e04930216f 100644 --- a/src/hotspot/share/ci/ciArrayKlass.cpp +++ b/src/hotspot/share/ci/ciArrayKlass.cpp @@ -23,10 +23,13 @@ */ #include "ci/ciArrayKlass.hpp" +#include "ci/ciFlatArrayKlass.hpp" +#include "ci/ciInlineKlass.hpp" #include "ci/ciObjArrayKlass.hpp" #include "ci/ciTypeArrayKlass.hpp" #include "ci/ciUtilities.inline.hpp" #include "memory/universe.hpp" +#include "oops/inlineKlass.inline.hpp" // ciArrayKlass // @@ -59,7 +62,7 @@ ciType* ciArrayKlass::element_type() { if (is_type_array_klass()) { return ciType::make(as_type_array_klass()->element_type()); } else { - return as_obj_array_klass()->element_klass()->as_klass(); + return element_klass()->as_klass(); } } @@ -71,12 +74,14 @@ ciType* ciArrayKlass::element_type() { ciType* ciArrayKlass::base_element_type() { if (is_type_array_klass()) { return ciType::make(as_type_array_klass()->element_type()); - } else { + } else if (is_obj_array_klass()) { ciKlass* ek = as_obj_array_klass()->base_element_klass(); if (ek->is_type_array_klass()) { return ciType::make(ek->as_type_array_klass()->element_type()); } return ek; + } else { + return as_flat_array_klass()->base_element_klass(); } } @@ -96,11 +101,57 @@ bool ciArrayKlass::is_leaf_type() { // ciArrayKlass::make // // Make an array klass of the specified element type. -ciArrayKlass* ciArrayKlass::make(ciType* element_type) { +ciArrayKlass* ciArrayKlass::make(ciType* element_type, bool null_free, bool atomic, bool vm_type) { if (element_type->is_primitive_type()) { return ciTypeArrayKlass::make(element_type->basic_type()); - } else { - return ciObjArrayKlass::make(element_type->as_klass()); } + + ciKlass* klass = element_type->as_klass(); + assert(!null_free || !klass->is_loaded() || klass->is_inlinetype() || klass->is_abstract() || + klass->is_java_lang_Object(), "only value classes are null free"); + if (klass->is_loaded() && klass->is_inlinetype() && vm_type) { + GUARDED_VM_ENTRY( + EXCEPTION_CONTEXT; + InlineKlass* vk = InlineKlass::cast(klass->get_Klass()); + ArrayKlass::ArrayProperties props = ArrayKlass::ArrayProperties::DEFAULT; + if (null_free) { + props = (ArrayKlass::ArrayProperties)(props | ArrayKlass::ArrayProperties::NULL_RESTRICTED); + } + if (!atomic) { + props = (ArrayKlass::ArrayProperties)(props | ArrayKlass::ArrayProperties::NON_ATOMIC); + } + ArrayKlass* ak = vk->array_klass(THREAD); + ak = ObjArrayKlass::cast(ak)->klass_with_properties(props, THREAD); + if (HAS_PENDING_EXCEPTION) { + CLEAR_PENDING_EXCEPTION; + } else if (ak->is_flatArray_klass()) { + return CURRENT_THREAD_ENV->get_flat_array_klass(ak); + } else if (ak->is_refArray_klass()) { + return CURRENT_THREAD_ENV->get_obj_array_klass(ak); + } + ) + } + return ciObjArrayKlass::make(klass, vm_type); +} + +int ciArrayKlass::array_header_in_bytes() { + return get_ArrayKlass()->array_header_in_bytes(); +} + +ciInstance* ciArrayKlass::component_mirror_instance() const { + GUARDED_VM_ENTRY( + oop component_mirror = ArrayKlass::cast(get_Klass())->component_mirror(); + return CURRENT_ENV->get_instance(component_mirror); + ) } +bool ciArrayKlass::is_elem_null_free() const { + GUARDED_VM_ENTRY(return is_loaded() && get_Klass()->is_null_free_array_klass();) +} + +bool ciArrayKlass::is_elem_atomic() { + ciKlass* elem = element_klass(); + GUARDED_VM_ENTRY(return elem != nullptr && elem->is_inlinetype() && + (ArrayKlass::cast(get_Klass())->properties() & ArrayKlass::ArrayProperties::INVALID) == 0 && + (ArrayKlass::cast(get_Klass())->properties() & ArrayKlass::ArrayProperties::NON_ATOMIC) == 0;) +} diff --git a/src/hotspot/share/ci/ciArrayKlass.hpp b/src/hotspot/share/ci/ciArrayKlass.hpp index a64d80b20f3..185d15430b1 100644 --- a/src/hotspot/share/ci/ciArrayKlass.hpp +++ b/src/hotspot/share/ci/ciArrayKlass.hpp @@ -55,7 +55,16 @@ class ciArrayKlass : public ciKlass { // What kind of vmObject is this? bool is_array_klass() const { return true; } - static ciArrayKlass* make(ciType* element_type); + // The one-level type of the array elements. + virtual ciKlass* element_klass() { return nullptr; } + + static ciArrayKlass* make(ciType* klass, bool null_free = false, bool atomic = false, bool vm_type = false); + + int array_header_in_bytes(); + ciInstance* component_mirror_instance() const; + + bool is_elem_null_free() const; + bool is_elem_atomic(); }; #endif // SHARE_CI_CIARRAYKLASS_HPP diff --git a/src/hotspot/share/ci/ciClassList.hpp b/src/hotspot/share/ci/ciClassList.hpp index 618a052765e..f3427e68294 100644 --- a/src/hotspot/share/ci/ciClassList.hpp +++ b/src/hotspot/share/ci/ciClassList.hpp @@ -59,10 +59,13 @@ class ciMethod; class ciMethodData; class ciReceiverTypeData; // part of ciMethodData class ciType; +class ciWrapper; class ciReturnAddress; class ciKlass; class ciInstanceKlass; +class ciInlineKlass; class ciArrayKlass; +class ciFlatArrayKlass; class ciObjArrayKlass; class ciTypeArrayKlass; @@ -110,9 +113,12 @@ friend class ciReplay; \ friend class ciTypeArray; \ friend class ciType; \ friend class ciReturnAddress; \ +friend class ciWrapper; \ friend class ciKlass; \ friend class ciInstanceKlass; \ +friend class ciInlineKlass; \ friend class ciArrayKlass; \ +friend class ciFlatArrayKlass; \ friend class ciObjArrayKlass; \ friend class ciTypeArrayKlass; \ diff --git a/src/hotspot/share/ci/ciEnv.cpp b/src/hotspot/share/ci/ciEnv.cpp index 79ab881e7f6..e977ee3878c 100644 --- a/src/hotspot/share/ci/ciEnv.cpp +++ b/src/hotspot/share/ci/ciEnv.cpp @@ -25,6 +25,7 @@ #include "ci/ciConstant.hpp" #include "ci/ciEnv.hpp" #include "ci/ciField.hpp" +#include "ci/ciInlineKlass.hpp" #include "ci/ciInstance.hpp" #include "ci/ciInstanceKlass.hpp" #include "ci/ciMethod.hpp" @@ -476,7 +477,8 @@ ciKlass* ciEnv::get_klass_by_name_impl(ciKlass* accessing_klass, // to be loaded if their element klasses are loaded, except when memory // is exhausted. if (Signature::is_array(sym) && - (sym->char_at(1) == JVM_SIGNATURE_ARRAY || sym->char_at(1) == JVM_SIGNATURE_CLASS)) { + (sym->char_at(1) == JVM_SIGNATURE_ARRAY || + sym->char_at(1) == JVM_SIGNATURE_CLASS )) { // We have an unloaded array. // Build it on the fly if the element class exists. SignatureStream ss(sym, false); @@ -489,7 +491,7 @@ ciKlass* ciEnv::get_klass_by_name_impl(ciKlass* accessing_klass, require_local); if (elem_klass != nullptr && elem_klass->is_loaded()) { // Now make an array for it - return ciObjArrayKlass::make_impl(elem_klass); + return ciArrayKlass::make(elem_klass); } } @@ -515,6 +517,10 @@ ciKlass* ciEnv::get_klass_by_name_impl(ciKlass* accessing_klass, // Not yet loaded into the VM, or not governed by loader constraints. // Make a CI representative for it. + int i = 0; + while (sym->char_at(i) == JVM_SIGNATURE_ARRAY) { + i++; + } return get_unloaded_klass(accessing_klass, name); } diff --git a/src/hotspot/share/ci/ciEnv.hpp b/src/hotspot/share/ci/ciEnv.hpp index a22975c7453..c8fd26e2c1d 100644 --- a/src/hotspot/share/ci/ciEnv.hpp +++ b/src/hotspot/share/ci/ciEnv.hpp @@ -199,6 +199,10 @@ class ciEnv : StackObj { if (o == nullptr) return nullptr; return get_object(o)->as_instance(); } + ciFlatArrayKlass* get_flat_array_klass(Klass* o) { + if (o == nullptr) return nullptr; + return get_metadata(o)->as_flat_array_klass(); + } ciObjArrayKlass* get_obj_array_klass(Klass* o) { if (o == nullptr) return nullptr; return get_metadata(o)->as_obj_array_klass(); @@ -500,6 +504,14 @@ class ciEnv : StackObj { void dump_compile_data(outputStream* out); void dump_replay_data_version(outputStream* out); + ciWrapper* make_early_larval_wrapper(ciType* type) { + return _factory->make_early_larval_wrapper(type); + } + + ciWrapper* make_null_free_wrapper(ciType* type) { + return _factory->make_null_free_wrapper(type); + } + const char *dyno_name(const InstanceKlass* ik) const; const char *replay_name(const InstanceKlass* ik) const; const char *replay_name(ciKlass* i) const; diff --git a/src/hotspot/share/ci/ciField.cpp b/src/hotspot/share/ci/ciField.cpp index cbe0cadbc93..7a56e1518c2 100644 --- a/src/hotspot/share/ci/ciField.cpp +++ b/src/hotspot/share/ci/ciField.cpp @@ -22,19 +22,24 @@ * */ +#include "ci/ciConstant.hpp" #include "ci/ciField.hpp" +#include "ci/ciInlineKlass.hpp" #include "ci/ciInstanceKlass.hpp" +#include "ci/ciSymbol.hpp" #include "ci/ciSymbols.hpp" #include "ci/ciUtilities.inline.hpp" #include "classfile/javaClasses.hpp" #include "classfile/vmClasses.hpp" #include "gc/shared/collectedHeap.inline.hpp" #include "interpreter/linkResolver.hpp" +#include "jvm_io.h" #include "oops/klass.inline.hpp" #include "oops/oop.inline.hpp" #include "runtime/fieldDescriptor.inline.hpp" #include "runtime/handles.inline.hpp" #include "runtime/reflection.hpp" +#include "utilities/globalDefinitions.hpp" // ciField // @@ -70,7 +75,7 @@ // ------------------------------------------------------------------ // ciField::ciField ciField::ciField(ciInstanceKlass* klass, int index, Bytecodes::Code bc) : - _known_to_link_with_put(nullptr), _known_to_link_with_get(nullptr) { + _original_holder(nullptr), _is_flat(false), _known_to_link_with_put(nullptr), _known_to_link_with_get(nullptr) { ASSERT_IN_VM; CompilerThread *THREAD = CompilerThread::current(); @@ -102,6 +107,9 @@ ciField::ciField(ciInstanceKlass* klass, int index, Bytecodes::Code bc) : _type = ciType::make(field_type); } + _is_null_free = false; + _null_marker_offset = -1; + // Get the field's declared holder. // // Note: we actually create a ciInstanceKlass for this klass, @@ -213,6 +221,61 @@ ciField::ciField(fieldDescriptor *fd) : "bootstrap classes must not create & cache unshared fields"); } +// Special copy constructor used to flatten inline type fields by +// copying the fields of the inline type to a new holder klass. +ciField::ciField(ciField* declared_field, ciField* subfield) { + assert(subfield->holder()->is_inlinetype() || subfield->holder()->is_abstract(), "should only be used for inline type field flattening"); + assert(!subfield->is_flat(), "subfield must not be flat"); + assert(declared_field->is_flat(), "declared field must be flat"); + + _flags = declared_field->flags(); + _holder = declared_field->holder(); + _offset = declared_field->offset_in_bytes() + (subfield->offset_in_bytes() - declared_field->type()->as_inline_klass()->payload_offset()); + + char buffer[256]; + jio_snprintf(buffer, sizeof(buffer), "%s.%s", declared_field->name()->as_utf8(), subfield->name()->as_utf8()); + _name = ciSymbol::make(buffer); + + _signature = subfield->_signature; + _type = subfield->_type; + _is_constant = declared_field->is_strict() && declared_field->is_final(); + _known_to_link_with_put = subfield->_known_to_link_with_put; + _known_to_link_with_get = subfield->_known_to_link_with_get; + _constant_value = ciConstant(); + + _is_flat = false; + _is_null_free = false; + _null_marker_offset = -1; + _original_holder = (subfield->_original_holder != nullptr) ? subfield->_original_holder : subfield->_holder; +} + +// Constructor for the ciField of a null marker +ciField::ciField(ciField* declared_field) { + assert(declared_field->is_flat(), "declared field must be flat"); + assert(!declared_field->is_null_free(), "must have a null marker"); + + _flags = declared_field->flags(); + _holder = declared_field->holder(); + _offset = declared_field->null_marker_offset(); + + char buffer[256]; + jio_snprintf(buffer, sizeof(buffer), "%s.$nullMarker$", declared_field->name()->as_utf8()); + _name = ciSymbol::make(buffer); + + _signature = ciSymbols::bool_signature(); + _type = ciType::make(T_BOOLEAN); + + _is_constant = declared_field->is_strict() && declared_field->is_final(); + _known_to_link_with_put = nullptr; + _known_to_link_with_get = nullptr; + _constant_value = ciConstant(); + + _is_flat = false; + _is_null_free = false; + _null_marker_offset = -1; + _original_holder = nullptr; +} + static bool trust_final_non_static_fields(ciInstanceKlass* holder) { if (holder == nullptr) return false; @@ -230,6 +293,9 @@ static bool trust_final_non_static_fields(ciInstanceKlass* holder) { // can't be serialized, so there is no hacking of finals going on with them. if (holder->is_hidden()) return true; + // Trust final fields in inline type buffers + if (holder->is_inlinetype()) + return true; // Trust final fields in all boxed classes if (holder->is_box_klass()) return true; @@ -254,9 +320,18 @@ void ciField::initialize_from(fieldDescriptor* fd) { // Get the flags, offset, and canonical holder of the field. _flags = ciFlags(fd->access_flags(), fd->field_flags().is_stable(), fd->field_status().is_initialized_final_update()); _offset = fd->offset(); - Klass* field_holder = fd->field_holder(); + InstanceKlass* field_holder = fd->field_holder(); assert(field_holder != nullptr, "null field_holder"); _holder = CURRENT_ENV->get_instance_klass(field_holder); + _is_flat = fd->is_flat(); + _is_null_free = fd->is_null_free_inline_type(); + if (fd->has_null_marker()) { + InlineLayoutInfo* li = field_holder->inline_layout_info_adr(fd->index()); + _null_marker_offset = li->null_marker_offset(); + } else { + _null_marker_offset = -1; + } + _original_holder = nullptr; // Check to see if the field is constant. Klass* k = _holder->get_Klass(); @@ -339,7 +414,9 @@ ciType* ciField::compute_type() { } ciType* ciField::compute_type_impl() { - ciKlass* type = CURRENT_ENV->get_klass_by_name_impl(_holder, constantPoolHandle(), _signature, false); + // Use original holder for fields that came in through flattening + ciKlass* accessing_klass = (_original_holder != nullptr) ? _original_holder : _holder; + ciKlass* type = CURRENT_ENV->get_klass_by_name_impl(accessing_klass, constantPoolHandle(), _signature, false); if (!type->is_primitive_type() && is_shared()) { // We must not cache a pointer to an unshared type, in a shared field. bool type_is_also_shared = false; @@ -402,6 +479,18 @@ bool ciField::will_link(ciMethod* accessing_method, fieldDescriptor result; LinkResolver::resolve_field(result, link_info, bc, false, CHECK_AND_CLEAR_(false)); + // Strict statics may require tracking if their class is not fully initialized. + // For now we can bail out of the compiler and let the interpreter handle it. + if (is_static && result.is_strict_static_unset()) { + // If we left out this logic, we would get (a) spurious + // failures for C2 code because compiled putstatic would not write + // the "unset" bits, and (b) missed failures for too-early reads, + // since the compiled getstatic would not check the "unset" bits. + // Test C1 on with "-XX:TieredStopAtLevel=2 -Xcomp -Xbatch". + // Test C2 on with "-XX:-TieredCompilation -Xcomp -Xbatch". + return false; + } + // update the hit-cache, unless there is a problem with memory scoping: if (accessing_method->holder()->is_shared() || !is_shared()) { if (is_put) { @@ -452,6 +541,9 @@ void ciField::print() { tty->print(" constant_value="); _constant_value.print(); } + tty->print(" is_flat=%s", bool_to_str(_is_flat)); + tty->print(" is_null_free=%s", bool_to_str(_is_null_free)); + tty->print(" null_marker_offset=%d", _null_marker_offset); tty->print(">"); } diff --git a/src/hotspot/share/ci/ciField.hpp b/src/hotspot/share/ci/ciField.hpp index ffc8730705f..e5b9a6637bc 100644 --- a/src/hotspot/share/ci/ciField.hpp +++ b/src/hotspot/share/ci/ciField.hpp @@ -44,11 +44,15 @@ class ciField : public ArenaObj { private: ciFlags _flags; ciInstanceKlass* _holder; + ciInstanceKlass* _original_holder; // For fields nested in flat fields ciSymbol* _name; ciSymbol* _signature; ciType* _type; int _offset; bool _is_constant; + bool _is_flat; + bool _is_null_free; + int _null_marker_offset; ciMethod* _known_to_link_with_put; ciInstanceKlass* _known_to_link_with_get; ciConstant _constant_value; @@ -58,6 +62,8 @@ class ciField : public ArenaObj { ciField(ciInstanceKlass* klass, int index, Bytecodes::Code bc); ciField(fieldDescriptor* fd); + ciField(ciField* declared_field, ciField* sudfield); + ciField(ciField* declared_field); // shared constructor code void initialize_from(fieldDescriptor* fd); @@ -169,6 +175,11 @@ class ciField : public ArenaObj { bool is_stable () const { return flags().is_stable(); } bool is_volatile () const { return flags().is_volatile(); } bool is_transient () const { return flags().is_transient(); } + bool is_strict () const { return flags().is_strict(); } + bool is_flat () const { return _is_flat; } + bool is_null_free () const { return _is_null_free; } + int null_marker_offset () const { return _null_marker_offset; } + // The field is modified outside of instance initializer methods // (or class/initializer methods if the field is static). bool has_initialized_final_update() const { return flags().has_initialized_final_update(); } diff --git a/src/hotspot/share/ci/ciFlags.cpp b/src/hotspot/share/ci/ciFlags.cpp index 5eade4a12c1..f710d786596 100644 --- a/src/hotspot/share/ci/ciFlags.cpp +++ b/src/hotspot/share/ci/ciFlags.cpp @@ -40,9 +40,6 @@ void ciFlags::print_klass_flags(outputStream* st) { if (is_final()) { st->print(",final"); } - if (is_super()) { - st->print(",super"); - } if (is_interface()) { st->print(",interface"); } diff --git a/src/hotspot/share/ci/ciFlags.hpp b/src/hotspot/share/ci/ciFlags.hpp index 426f953611f..02872850198 100644 --- a/src/hotspot/share/ci/ciFlags.hpp +++ b/src/hotspot/share/ci/ciFlags.hpp @@ -54,12 +54,15 @@ class ciFlags { bool is_static () const { return _flags.is_static(); } bool is_final () const { return _flags.is_final(); } bool is_synchronized () const { return _flags.is_synchronized(); } - bool is_super () const { return _flags.is_super(); } bool is_volatile () const { return _flags.is_volatile(); } bool is_transient () const { return _flags.is_transient(); } bool is_native () const { return _flags.is_native(); } bool is_interface () const { return _flags.is_interface(); } bool is_abstract () const { return _flags.is_abstract(); } + bool has_vararg () const { return _flags.has_vararg(); } + bool is_identity () const { return _flags.is_identity_class(); } + bool is_strict () const { return _flags.is_strict(); } + bool is_stable () const { return _stable; } // In case the current object represents a field, return true if // the field is modified outside of instance initializer methods diff --git a/src/hotspot/share/ci/ciFlatArray.hpp b/src/hotspot/share/ci/ciFlatArray.hpp new file mode 100644 index 00000000000..ec4fb9d80df --- /dev/null +++ b/src/hotspot/share/ci/ciFlatArray.hpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_CI_CIFLATARRAY_HPP +#define SHARE_VM_CI_CIFLATARRAY_HPP + +#include "ci/ciArray.hpp" +#include "ci/ciClassList.hpp" +#include "oops/flatArrayOop.hpp" + +// ciFlatArray +// +// This class represents a flatArrayOop in the HotSpot virtual machine. +class ciFlatArray : public ciArray { + CI_PACKAGE_ACCESS + +protected: + ciFlatArray(flatArrayHandle h_o) : ciArray(h_o) {} + + const char* type_string() { return "ciFlatArray"; } + +public: + bool is_flat() { return true; } +}; + +#endif // SHARE_VM_CI_CIFLATARRAY_HPP diff --git a/src/hotspot/share/ci/ciFlatArrayKlass.cpp b/src/hotspot/share/ci/ciFlatArrayKlass.cpp new file mode 100644 index 00000000000..c6c0b7c75b1 --- /dev/null +++ b/src/hotspot/share/ci/ciFlatArrayKlass.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved. + * 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 "ci/ciFlatArrayKlass.hpp" +#include "ci/ciInlineKlass.hpp" +#include "ci/ciInstanceKlass.hpp" +#include "ci/ciObjArrayKlass.hpp" +#include "ci/ciSymbol.hpp" +#include "ci/ciUtilities.hpp" +#include "ci/ciUtilities.inline.hpp" +#include "oops/flatArrayKlass.hpp" +#include "oops/inlineKlass.inline.hpp" + +// ciFlatArrayKlass +// +// This class represents a Klass* in the HotSpot virtual machine +// whose Klass part is a FlatArrayKlass. + +// ------------------------------------------------------------------ +// ciFlatArrayKlass::ciFlatArrayKlass +// +// Constructor for loaded inline type array klasses. +ciFlatArrayKlass::ciFlatArrayKlass(Klass* h_k) : ciArrayKlass(h_k) { + assert(get_Klass()->is_flatArray_klass(), "wrong type"); + InlineKlass* element_Klass = get_FlatArrayKlass()->element_klass(); + _base_element_klass = CURRENT_ENV->get_klass(element_Klass); + assert(_base_element_klass->is_inlinetype(), "bad base klass"); + if (dimension() == 1) { + _element_klass = _base_element_klass; + } else { + _element_klass = nullptr; + } + if (!ciObjectFactory::is_initialized()) { + assert(_element_klass->is_java_lang_Object(), "only arrays of object are shared"); + } +} + +// ------------------------------------------------------------------ +// ciFlatArrayKlass::element_klass +// +// What is the one-level element type of this array? +ciKlass* ciFlatArrayKlass::element_klass() { + if (_element_klass == nullptr) { + assert(dimension() > 1, "_element_klass should not be nullptr"); + assert(is_loaded(), "FlatArrayKlass must be loaded"); + // Produce the element klass. + VM_ENTRY_MARK; + Klass* element_Klass = get_FlatArrayKlass()->element_klass(); + _element_klass = CURRENT_THREAD_ENV->get_klass(element_Klass); + } + return _element_klass; +} + +ciKlass* ciFlatArrayKlass::exact_klass() { + assert(element_klass()->is_loaded() && element_klass()->as_inline_klass()->exact_klass() != nullptr, "must have exact klass"); + return this; +} diff --git a/src/hotspot/share/ci/ciFlatArrayKlass.hpp b/src/hotspot/share/ci/ciFlatArrayKlass.hpp new file mode 100644 index 00000000000..2a28743e783 --- /dev/null +++ b/src/hotspot/share/ci/ciFlatArrayKlass.hpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved. + * 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_VM_CI_CIFLATARRAYKLASS_HPP +#define SHARE_VM_CI_CIFLATARRAYKLASS_HPP + +#include "ci/ciArrayKlass.hpp" + +// ciFlatArrayKlass +// +// This class represents a Klass* in the HotSpot virtual machine +// whose Klass part is a FlatArrayKlass. +class ciFlatArrayKlass : public ciArrayKlass { + CI_PACKAGE_ACCESS + friend class ciEnv; + +private: + ciKlass* _element_klass; + ciKlass* _base_element_klass; + +protected: + ciFlatArrayKlass(Klass* h_k); + + FlatArrayKlass* get_FlatArrayKlass() { + return (FlatArrayKlass*)get_Klass(); + } + + const char* type_string() { return "ciFlatArrayKlass"; } + + oop loader() { return _base_element_klass->loader(); } + jobject loader_handle() { return _base_element_klass->loader_handle(); } + +public: + // The one-level type of the array elements. + ciKlass* element_klass(); + + int log2_element_size() { + return Klass::layout_helper_log2_element_size(layout_helper()); + } + int element_byte_size() { return 1 << log2_element_size(); } + + // The innermost type of the array elements. + ciKlass* base_element_klass() { return _base_element_klass; } + + // What kind of ciObject is this? + bool is_flat_array_klass() const { return true; } + + virtual ciKlass* exact_klass(); + + virtual bool can_be_inline_array_klass() { + return true; + } +}; + + +#endif // SHARE_VM_CI_CIFLATARRAYKLASS_HPP diff --git a/src/hotspot/share/ci/ciInlineKlass.cpp b/src/hotspot/share/ci/ciInlineKlass.cpp new file mode 100644 index 00000000000..7d483dfc307 --- /dev/null +++ b/src/hotspot/share/ci/ciInlineKlass.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "ci/ciField.hpp" +#include "ci/ciInlineKlass.hpp" +#include "ci/ciUtilities.inline.hpp" +#include "oops/inlineKlass.inline.hpp" + +// Offset of the first field in the inline type +int ciInlineKlass::payload_offset() const { + GUARDED_VM_ENTRY(return to_InlineKlass()->payload_offset();) +} + +// Are arrays containing this inline type flat arrays? +bool ciInlineKlass::maybe_flat_in_array() const { + GUARDED_VM_ENTRY(return to_InlineKlass()->maybe_flat_in_array();) +} + +// Can this inline type be passed as multiple values? +bool ciInlineKlass::can_be_passed_as_fields() const { + GUARDED_VM_ENTRY(return to_InlineKlass()->can_be_passed_as_fields();) +} + +// Can this inline type be returned as multiple values? +bool ciInlineKlass::can_be_returned_as_fields() const { + GUARDED_VM_ENTRY(return to_InlineKlass()->can_be_returned_as_fields();) +} + +bool ciInlineKlass::is_empty() { + // Do not use InlineKlass::is_empty_inline_type here because it does + // consider the container empty even if fields of empty inline types + // are not flat + return nof_declared_nonstatic_fields() == 0; +} + +// When passing an inline type's fields as arguments, count the number +// of argument slots that are needed +int ciInlineKlass::inline_arg_slots() { + VM_ENTRY_MARK; + const Array* sig_vk = get_InlineKlass()->extended_sig(); + int slots = 0; + for (int i = 0; i < sig_vk->length(); i++) { + BasicType bt = sig_vk->at(i)._bt; + if (bt == T_METADATA || bt == T_VOID) { + continue; + } + slots += type2size[bt]; + } + return slots; +} + +bool ciInlineKlass::contains_oops() const { + GUARDED_VM_ENTRY(return get_InlineKlass()->contains_oops();) +} + +int ciInlineKlass::oop_count() const { + GUARDED_VM_ENTRY(return get_InlineKlass()->nonstatic_oop_count();) +} + +address ciInlineKlass::pack_handler() const { + GUARDED_VM_ENTRY(return get_InlineKlass()->pack_handler();) +} + +address ciInlineKlass::unpack_handler() const { + GUARDED_VM_ENTRY(return get_InlineKlass()->unpack_handler();) +} + +InlineKlass* ciInlineKlass::get_InlineKlass() const { + GUARDED_VM_ENTRY(return to_InlineKlass();) +} + +bool ciInlineKlass::has_non_atomic_layout() const { + GUARDED_VM_ENTRY(return get_InlineKlass()->has_non_atomic_layout();) +} + +bool ciInlineKlass::has_atomic_layout() const { + GUARDED_VM_ENTRY(return get_InlineKlass()->has_atomic_layout();) +} + +bool ciInlineKlass::has_nullable_atomic_layout() const { + GUARDED_VM_ENTRY(return get_InlineKlass()->has_nullable_atomic_layout();) +} + +int ciInlineKlass::null_marker_offset_in_payload() const { + GUARDED_VM_ENTRY(return get_InlineKlass()->null_marker_offset_in_payload();) +} + +// Convert size of atomic layout in bytes to corresponding BasicType +BasicType ciInlineKlass::atomic_size_to_basic_type(bool null_free) const { + VM_ENTRY_MARK + InlineKlass* vk = get_InlineKlass(); + assert(!null_free || vk->has_atomic_layout(), "No null-free atomic layout available"); + assert( null_free || vk->has_nullable_atomic_layout(), "No nullable atomic layout available"); + int size = null_free ? vk->atomic_size_in_bytes() : vk->nullable_atomic_size_in_bytes(); + BasicType bt; + if (size == sizeof(jlong)) { + bt = T_LONG; + } else if (size == sizeof(jint)) { + bt = T_INT; + } else if (size == sizeof(jshort)) { + bt = T_SHORT; + } else if (size == sizeof(jbyte)) { + bt = T_BYTE; + } else { + assert(false, "Unsupported size: %d", size); + } + return bt; +} + +bool ciInlineKlass::must_be_atomic() const { + GUARDED_VM_ENTRY(return get_InlineKlass()->must_be_atomic();) +} + +bool ciInlineKlass::is_naturally_atomic(bool null_free) { + return null_free ? (nof_nonstatic_fields() <= 1) : (nof_nonstatic_fields() == 0); +} diff --git a/src/hotspot/share/ci/ciInlineKlass.hpp b/src/hotspot/share/ci/ciInlineKlass.hpp new file mode 100644 index 00000000000..0bc35249889 --- /dev/null +++ b/src/hotspot/share/ci/ciInlineKlass.hpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_CI_CIINLINEKLASS_HPP +#define SHARE_VM_CI_CIINLINEKLASS_HPP + +#include "ci/ciConstantPoolCache.hpp" +#include "ci/ciEnv.hpp" +#include "ci/ciFlags.hpp" +#include "ci/ciInstanceKlass.hpp" +#include "ci/ciSymbol.hpp" +#include "oops/inlineKlass.hpp" + +// ciInlineKlass +// +// Specialized ciInstanceKlass for inline types. +class ciInlineKlass : public ciInstanceKlass { + CI_PACKAGE_ACCESS + +private: + + InlineKlass* to_InlineKlass() const { + return InlineKlass::cast(get_Klass()); + } + +protected: + ciInlineKlass(Klass* h_k) : ciInstanceKlass(h_k) { + assert(is_final(), "InlineKlass must be final"); + }; + + ciInlineKlass(ciSymbol* name, jobject loader) : + ciInstanceKlass(name, loader, T_OBJECT) {} + + const char* type_string() { return "ciInlineKlass"; } + +public: + bool is_inlinetype() const { return true; } + + // Inline type fields + int payload_offset() const; + + bool maybe_flat_in_array() const; + bool can_be_passed_as_fields() const; + bool can_be_returned_as_fields() const; + bool is_empty(); + int inline_arg_slots(); + bool contains_oops() const; + int oop_count() const; + address pack_handler() const; + address unpack_handler() const; + InlineKlass* get_InlineKlass() const; + int nullable_size_in_bytes() const; + bool has_non_atomic_layout() const; + bool has_atomic_layout() const; + bool has_nullable_atomic_layout() const; + int null_marker_offset_in_payload() const; + BasicType atomic_size_to_basic_type(bool null_free) const; + + bool must_be_atomic() const; + bool is_naturally_atomic(bool null_free); +}; + +#endif // SHARE_VM_CI_CIINLINEKLASS_HPP diff --git a/src/hotspot/share/ci/ciInstance.cpp b/src/hotspot/share/ci/ciInstance.cpp index 9591298e3ab..49581db487a 100644 --- a/src/hotspot/share/ci/ciInstance.cpp +++ b/src/hotspot/share/ci/ciInstance.cpp @@ -107,7 +107,9 @@ ciConstant ciInstance::field_value_impl(BasicType field_btype, int offset) { ciConstant ciInstance::field_value(ciField* field) { assert(is_loaded(), "invalid access - must be loaded"); assert(field->holder()->is_loaded(), "invalid access - holder must be loaded"); - assert(field->is_static() || klass()->is_subclass_of(field->holder()), "invalid access - must be subclass"); + assert(field->is_static() || field->holder()->is_inlinetype() || klass()->is_subclass_of(field->holder()), + "invalid access - must be subclass"); + return field_value_impl(field->type()->basic_type(), field->offset_in_bytes()); } diff --git a/src/hotspot/share/ci/ciInstanceKlass.cpp b/src/hotspot/share/ci/ciInstanceKlass.cpp index c367170f2c2..5c4d3869a43 100644 --- a/src/hotspot/share/ci/ciInstanceKlass.cpp +++ b/src/hotspot/share/ci/ciInstanceKlass.cpp @@ -23,15 +23,18 @@ */ #include "ci/ciField.hpp" +#include "ci/ciInlineKlass.hpp" #include "ci/ciInstance.hpp" #include "ci/ciInstanceKlass.hpp" #include "ci/ciUtilities.inline.hpp" #include "classfile/javaClasses.hpp" +#include "classfile/systemDictionary.hpp" #include "classfile/vmClasses.hpp" #include "memory/allocation.hpp" #include "memory/allocation.inline.hpp" #include "memory/resourceArea.hpp" #include "oops/fieldStreams.inline.hpp" +#include "oops/inlineKlass.inline.hpp" #include "oops/instanceKlass.inline.hpp" #include "oops/klass.inline.hpp" #include "oops/oop.inline.hpp" @@ -65,9 +68,10 @@ ciInstanceKlass::ciInstanceKlass(Klass* k) : _has_nonstatic_concrete_methods = ik->has_nonstatic_concrete_methods(); _is_hidden = ik->is_hidden(); _is_record = ik->is_record(); - _nonstatic_fields = nullptr; // initialized lazily by compute_nonstatic_fields: + _declared_nonstatic_fields = nullptr; // initialized lazily by compute_nonstatic_fields + _nonstatic_fields = nullptr; // initialized lazily by compute_nonstatic_fields _has_injected_fields = -1; - _implementor = nullptr; // we will fill these lazily + _implementor = nullptr; // we will fill these lazily _transitive_interfaces = nullptr; // Ensure that the metadata wrapped by the ciMetadata is kept alive by GC. @@ -114,13 +118,15 @@ ciInstanceKlass::ciInstanceKlass(Klass* k) : // Version for unloaded classes: ciInstanceKlass::ciInstanceKlass(ciSymbol* name, - jobject loader) - : ciKlass(name, T_OBJECT) + jobject loader, + BasicType bt) + : ciKlass(name, bt) { assert(name->char_at(0) != JVM_SIGNATURE_ARRAY, "not an instance klass"); _init_state = (InstanceKlass::ClassState)0; _has_nonstatic_fields = false; - _nonstatic_fields = nullptr; + _declared_nonstatic_fields = nullptr; // initialized lazily by compute_nonstatic_fields + _nonstatic_fields = nullptr; // initialized lazily by compute_nonstatic_fields _has_injected_fields = -1; _is_hidden = false; _is_record = false; @@ -318,11 +324,11 @@ void ciInstanceKlass::print_impl(outputStream* st) { bool_to_str(has_subklass()), layout_helper()); - _flags.print_klass_flags(); + _flags.print_klass_flags(st); if (_super) { st->print(" super="); - _super->print_name(); + _super->print_name_on(st); } if (_java_mirror) { st->print(" mirror=PRESENT"); @@ -396,13 +402,15 @@ bool ciInstanceKlass::contains_field_offset(int offset) { ciField* ciInstanceKlass::get_field_by_offset(int field_offset, bool is_static) { if (!is_static) { for (int i = 0, len = nof_nonstatic_fields(); i < len; i++) { - ciField* field = _nonstatic_fields->at(i); - int field_off = field->offset_in_bytes(); - if (field_off == field_offset) + ciField* field = nonstatic_field_at(i); + int field_off = field->offset_in_bytes(); + if (field_off == field_offset) { return field; + } } return nullptr; } + VM_ENTRY_MARK; InstanceKlass* k = get_instanceKlass(); fieldDescriptor fd; @@ -413,6 +421,40 @@ ciField* ciInstanceKlass::get_field_by_offset(int field_offset, bool is_static) return field; } +ciField* ciInstanceKlass::get_non_flat_field_by_offset(int field_offset) { + for (int i = 0, len = nof_declared_nonstatic_fields(); i < len; i++) { + ciField* field = declared_nonstatic_field_at(i); + int field_off = field->offset_in_bytes(); + if (field_off == field_offset) { + return field; + } + } + return nullptr; +} + +int ciInstanceKlass::field_index_by_offset(int offset) { + assert(contains_field_offset(offset), "invalid field offset"); + int best_offset = 0; + int best_index = -1; + // Search the field with the given offset + for (int i = 0; i < nof_declared_nonstatic_fields(); ++i) { + int field_offset = declared_nonstatic_field_at(i)->offset_in_bytes(); + if (field_offset == offset) { + // Exact match + return i; + } else if (field_offset < offset && field_offset > best_offset) { + // No exact match. Save the index of the field with the closest offset that + // is smaller than the given field offset. This index corresponds to the + // flat field that holds the field we are looking for. + best_offset = field_offset; + best_index = i; + } + } + assert(best_index >= 0, "field not found"); + assert(best_offset == offset || declared_nonstatic_field_at(best_index)->type()->is_inlinetype(), "offset should match for non-inline types"); + return best_index; +} + // ------------------------------------------------------------------ // ciInstanceKlass::get_field_by_name ciField* ciInstanceKlass::get_field_by_name(ciSymbol* name, ciSymbol* signature, bool is_static) { @@ -427,84 +469,122 @@ ciField* ciInstanceKlass::get_field_by_name(ciSymbol* name, ciSymbol* signature, return field; } +const GrowableArray empty_field_array(0, MemTag::mtCompiler); -// ------------------------------------------------------------------ -// ciInstanceKlass::compute_nonstatic_fields -int ciInstanceKlass::compute_nonstatic_fields() { +void ciInstanceKlass::compute_nonstatic_fields() { assert(is_loaded(), "must be loaded"); - if (_nonstatic_fields != nullptr) - return _nonstatic_fields->length(); + if (_nonstatic_fields != nullptr) { + assert(_declared_nonstatic_fields != nullptr, "must be initialized at the same time, class %s", name()->as_utf8()); + return; + } if (!has_nonstatic_fields()) { - Arena* arena = CURRENT_ENV->arena(); - _nonstatic_fields = new (arena) GrowableArray(arena, 0, 0, nullptr); - return 0; + _declared_nonstatic_fields = &empty_field_array; + _nonstatic_fields = &empty_field_array; + return; } assert(!is_java_lang_Object(), "bootstrap OK"); ciInstanceKlass* super = this->super(); - GrowableArray* super_fields = nullptr; - if (super != nullptr && super->has_nonstatic_fields()) { - int super_flen = super->nof_nonstatic_fields(); - super_fields = super->_nonstatic_fields; - assert(super_flen == 0 || super_fields != nullptr, "first get nof_fields"); - } + assert(super != nullptr, "must have a super class, current class: %s", name()->as_utf8()); + super->compute_nonstatic_fields(); + const GrowableArray* super_declared_fields = super->_declared_nonstatic_fields;; + const GrowableArray* super_fields = super->_nonstatic_fields; + assert(super_declared_fields != nullptr && super_fields != nullptr, "must have been initialized, current class: %s, super class: %s", name()->as_utf8(), super->name()->as_utf8()); - GrowableArray* fields = nullptr; GUARDED_VM_ENTRY({ - fields = compute_nonstatic_fields_impl(super_fields); - }); - - if (fields == nullptr) { - // This can happen if this class (java.lang.Class) has invisible fields. - if (super_fields != nullptr) { - _nonstatic_fields = super_fields; - return super_fields->length(); - } else { - return 0; - } - } - - int flen = fields->length(); - - _nonstatic_fields = fields; - return flen; + compute_nonstatic_fields_impl(super_declared_fields, super_fields); + }); } -GrowableArray* -ciInstanceKlass::compute_nonstatic_fields_impl(GrowableArray* - super_fields) { +void ciInstanceKlass::compute_nonstatic_fields_impl(const GrowableArray* super_declared_fields, const GrowableArray* super_fields) { + assert(_declared_nonstatic_fields == nullptr && _nonstatic_fields == nullptr, "initialized already"); ASSERT_IN_VM; Arena* arena = CURRENT_ENV->arena(); - int flen = 0; - GrowableArray* fields = nullptr; - InstanceKlass* k = get_instanceKlass(); - for (JavaFieldStream fs(k); !fs.done(); fs.next()) { - if (fs.access_flags().is_static()) continue; - flen += 1; - } - // allocate the array: - if (flen == 0) { - return nullptr; // return nothing if none are locally declared + InstanceKlass* this_klass = get_instanceKlass(); + int declared_field_num = 0; + int field_num = 0; + for (JavaFieldStream fs(this_klass); !fs.done(); fs.next()) { + if (fs.access_flags().is_static()) { + continue; + } + + declared_field_num++; + + fieldDescriptor& fd = fs.field_descriptor(); + if (fd.is_flat()) { + InlineKlass* k = this_klass->get_inline_type_field_klass(fd.index()); + ciInlineKlass* vk = CURRENT_ENV->get_klass(k)->as_inline_klass(); + field_num += vk->nof_nonstatic_fields(); + field_num += fd.has_null_marker() ? 1 : 0; + } else { + field_num++; + } } - if (super_fields != nullptr) { - flen += super_fields->length(); + + GrowableArray* tmp_declared_fields = nullptr; + if (declared_field_num != 0) { + tmp_declared_fields = new (arena) GrowableArray(arena, declared_field_num + super_declared_fields->length(), 0, nullptr); + tmp_declared_fields->appendAll(super_declared_fields); } - fields = new (arena) GrowableArray(arena, flen, 0, nullptr); - if (super_fields != nullptr) { - fields->appendAll(super_fields); + + GrowableArray* tmp_fields = nullptr; + if (field_num != 0) { + tmp_fields = new (arena) GrowableArray(arena, field_num + super_fields->length(), 0, nullptr); + tmp_fields->appendAll(super_fields); } - for (JavaFieldStream fs(k); !fs.done(); fs.next()) { - if (fs.access_flags().is_static()) continue; + // For later assertion + declared_field_num += super_declared_fields->length(); + field_num += super_fields->length(); + + for (JavaFieldStream fs(this_klass); !fs.done(); fs.next()) { + if (fs.access_flags().is_static()) { + continue; + } + fieldDescriptor& fd = fs.field_descriptor(); - ciField* field = new (arena) ciField(&fd); - fields->append(field); + ciField* declared_field = new (arena) ciField(&fd); + assert(tmp_declared_fields != nullptr, "should be initialized"); + tmp_declared_fields->append(declared_field); + + if (fd.is_flat()) { + // Flat fields are embedded + Klass* k = get_instanceKlass()->get_inline_type_field_klass(fd.index()); + ciInlineKlass* vk = CURRENT_ENV->get_klass(k)->as_inline_klass(); + // Iterate over fields of the flat inline type and copy them to 'this' + for (int i = 0; i < vk->nof_nonstatic_fields(); ++i) { + assert(tmp_fields != nullptr, "should be initialized"); + tmp_fields->append(new (arena) ciField(declared_field, vk->nonstatic_field_at(i))); + } + if (fd.has_null_marker()) { + assert(tmp_fields != nullptr, "should be initialized"); + tmp_fields->append(new (arena) ciField(declared_field)); + } + } else { + assert(tmp_fields != nullptr, "should be initialized"); + tmp_fields->append(declared_field); + } + } + + // Now sort them by offset, ascending. In principle, they could mix with superclass fields. + if (tmp_declared_fields != nullptr) { + assert(tmp_declared_fields->length() == declared_field_num, "sanity check failed for class: %s, number of declared fields: %d, expected: %d", + name()->as_utf8(), tmp_declared_fields->length(), declared_field_num); + _declared_nonstatic_fields = tmp_declared_fields; + } else { + _declared_nonstatic_fields = super_declared_fields; + } + + if (tmp_fields != nullptr) { + assert(tmp_fields->length() == field_num, "sanity check failed for class: %s, number of fields: %d, expected: %d", + name()->as_utf8(), tmp_fields->length(), field_num); + _nonstatic_fields = tmp_fields; + } else { + _nonstatic_fields = super_fields; } - assert(fields->length() == flen, "sanity"); - return fields; } bool ciInstanceKlass::compute_injected_fields_helper() { @@ -615,6 +695,22 @@ ciInstanceKlass* ciInstanceKlass::implementor() { return impl; } +bool ciInstanceKlass::can_be_inline_klass(bool is_exact) { + if (!EnableValhalla) { + return false; + } + if (!is_loaded() || is_inlinetype()) { + // Not loaded or known to be an inline klass + return true; + } + if (!is_exact) { + // Not exact, check if this is a valid super for an inline klass + VM_ENTRY_MARK; + return !get_instanceKlass()->access_flags().is_identity_class() || is_java_lang_Object() ; + } + return false; +} + // Utility class for printing of the contents of the static fields for // use by compilation replay. It only prints out the information that // could be consumed by the compiler, so for primitive types it prints @@ -623,74 +719,127 @@ ciInstanceKlass* ciInstanceKlass::implementor() { // only value which statically unchangeable. For all other reference // types it simply prints out the dynamic type. -class StaticFinalFieldPrinter : public FieldClosure { +class StaticFieldPrinter : public FieldClosure { +protected: outputStream* _out; +public: + StaticFieldPrinter(outputStream* out) : + _out(out) { + } + void do_field_helper(fieldDescriptor* fd, oop obj, bool is_flat); +}; + +class StaticFinalFieldPrinter : public StaticFieldPrinter { const char* _holder; public: StaticFinalFieldPrinter(outputStream* out, const char* holder) : - _out(out), - _holder(holder) { + StaticFieldPrinter(out), _holder(holder) { } void do_field(fieldDescriptor* fd) { if (fd->is_final() && !fd->has_initial_value()) { 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()); - 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; - case T_CHAR: _out->print_cr("%d", mirror->char_field(fd->offset())); break; - case T_INT: _out->print_cr("%d", mirror->int_field(fd->offset())); break; - case T_LONG: _out->print_cr(INT64_FORMAT, (int64_t)(mirror->long_field(fd->offset()))); break; - case T_FLOAT: { - float f = mirror->float_field(fd->offset()); - _out->print_cr("%d", *(int*)&f); - break; - } - case T_DOUBLE: { - double d = mirror->double_field(fd->offset()); - _out->print_cr(INT64_FORMAT, *(int64_t*)&d); - break; - } - case T_ARRAY: // fall-through - case T_OBJECT: { - oop value = mirror->obj_field_acquire(fd->offset()); - if (value == nullptr) { - if (field_type == T_ARRAY) { - _out->print("%d", -1); - } - _out->cr(); - } else if (value->is_instance()) { - assert(field_type == T_OBJECT, ""); - if (value->is_a(vmClasses::String_klass())) { - const char* ascii_value = java_lang_String::as_quoted_ascii(value); - _out->print_cr("\"%s\"", (ascii_value != nullptr) ? ascii_value : ""); - } else { - const char* klass_name = value->klass()->name()->as_quoted_ascii(); - _out->print_cr("%s", klass_name); - } - } else if (value->is_array()) { - typeArrayOop ta = (typeArrayOop)value; - _out->print("%d", ta->length()); - if (value->is_objArray()) { - objArrayOop oa = (objArrayOop)value; - const char* klass_name = value->klass()->name()->as_quoted_ascii(); - _out->print(" %s", klass_name); - } - _out->cr(); + InstanceKlass* holder = fd->field_holder(); + oop mirror = holder->java_mirror(); + _out->print("staticfield %s %s ", _holder, fd->name()->as_quoted_ascii()); + BasicType bt = fd->field_type(); + if (bt != T_OBJECT && bt != T_ARRAY) { + _out->print("%s ", fd->signature()->as_quoted_ascii()); + } + do_field_helper(fd, mirror, false); + _out->cr(); + } + } +}; + +class InlineTypeFieldPrinter : public StaticFieldPrinter { + oop _obj; +public: + InlineTypeFieldPrinter(outputStream* out, oop obj) : + StaticFieldPrinter(out), _obj(obj) { + } + void do_field(fieldDescriptor* fd) { + do_field_helper(fd, _obj, true); + _out->print(" "); + } +}; + +void StaticFieldPrinter::do_field_helper(fieldDescriptor* fd, oop mirror, bool is_flat) { + BasicType field_type = fd->field_type(); + switch (field_type) { + case T_BYTE: _out->print("%d", mirror->byte_field(fd->offset())); break; + case T_BOOLEAN: _out->print("%d", mirror->bool_field(fd->offset())); break; + case T_SHORT: _out->print("%d", mirror->short_field(fd->offset())); break; + case T_CHAR: _out->print("%d", mirror->char_field(fd->offset())); break; + case T_INT: _out->print("%d", mirror->int_field(fd->offset())); break; + case T_LONG: _out->print(INT64_FORMAT, (int64_t)(mirror->long_field(fd->offset()))); break; + case T_FLOAT: { + float f = mirror->float_field(fd->offset()); + _out->print("%d", *(int*)&f); + break; + } + case T_DOUBLE: { + double d = mirror->double_field(fd->offset()); + _out->print(INT64_FORMAT, *(int64_t*)&d); + break; + } + case T_ARRAY: // fall-through + case T_OBJECT: + if (!fd->is_null_free_inline_type()) { + _out->print("%s ", fd->signature()->as_quoted_ascii()); + oop value = mirror->obj_field_acquire(fd->offset()); + if (value == nullptr) { + if (field_type == T_ARRAY) { + _out->print("%d", -1); + } + _out->cr(); + } else if (value->is_instance()) { + assert(field_type == T_OBJECT, ""); + if (value->is_a(vmClasses::String_klass())) { + const char* ascii_value = java_lang_String::as_quoted_ascii(value); + _out->print("\"%s\"", (ascii_value != nullptr) ? ascii_value : ""); } else { - ShouldNotReachHere(); + const char* klass_name = value->klass()->name()->as_quoted_ascii(); + _out->print("%s", klass_name); } - break; - } - default: + } else if (value->is_array()) { + typeArrayOop ta = (typeArrayOop)value; + _out->print("%d", ta->length()); + if (value->is_objArray() || value->is_flatArray()) { + objArrayOop oa = (objArrayOop)value; + const char* klass_name = value->klass()->name()->as_quoted_ascii(); + _out->print(" %s", klass_name); + } + } else { ShouldNotReachHere(); } - } + break; + } else { + // handling of null free inline type + ResetNoHandleMark rnhm; + Thread* THREAD = Thread::current(); + SignatureStream ss(fd->signature(), false); + Symbol* name = ss.as_symbol(); + assert(!HAS_PENDING_EXCEPTION, "can resolve klass?"); + InstanceKlass* holder = fd->field_holder(); + InstanceKlass* k = SystemDictionary::find_instance_klass(THREAD, name, + Handle(THREAD, holder->class_loader())); + assert(k != nullptr && !HAS_PENDING_EXCEPTION, "can resolve klass?"); + InlineKlass* vk = InlineKlass::cast(k); + oop obj; + if (is_flat) { + int field_offset = fd->offset() - vk->payload_offset(); + obj = cast_to_oop(cast_from_oop
(mirror) + field_offset); + } else { + obj = mirror->obj_field_acquire(fd->offset()); + } + InlineTypeFieldPrinter print_field(_out, obj); + vk->do_nonstatic_fields(&print_field); + break; + } + default: + ShouldNotReachHere(); } -}; +} const char *ciInstanceKlass::replay_name() const { return CURRENT_ENV->replay_name(get_instanceKlass()); diff --git a/src/hotspot/share/ci/ciInstanceKlass.hpp b/src/hotspot/share/ci/ciInstanceKlass.hpp index d3d988e8735..83ce4859af4 100644 --- a/src/hotspot/share/ci/ciInstanceKlass.hpp +++ b/src/hotspot/share/ci/ciInstanceKlass.hpp @@ -68,7 +68,20 @@ class ciInstanceKlass : public ciKlass { ciInstance* _java_mirror; ciConstantPoolCache* _field_cache; // cached map index->field - GrowableArray* _nonstatic_fields; // ordered by JavaFieldStream + + // Fields declared in the bytecode (without nested fields in flat fields), + // ordered in JavaFieldStream order, with superclasses first (i.e. from lang.java.Object + // to most derived class). + const GrowableArray* _declared_nonstatic_fields; + + // Fields laid out in memory (flat fields are expanded into their components). The ciField object + // for each primitive component has the holder being this ciInstanceKlass or one of its + // superclasses. + // Fields are in the same order as in _declared_nonstatic_fields, but flat fields are replaced by + // the list of their own fields, ordered the same way (hierarchy traversed top-down, in + // JavaFieldStream order). + const GrowableArray* _nonstatic_fields; + int _has_injected_fields; // any non static injected fields? lazily initialized. // The possible values of the _implementor fall into following three cases: @@ -84,7 +97,7 @@ class ciInstanceKlass : public ciKlass { protected: ciInstanceKlass(Klass* k); - ciInstanceKlass(ciSymbol* name, jobject loader); + ciInstanceKlass(ciSymbol* name, jobject loader, BasicType bt = T_OBJECT); // for unloaded klasses InstanceKlass* get_instanceKlass() const { return InstanceKlass::cast(get_Klass()); @@ -105,8 +118,8 @@ class ciInstanceKlass : public ciKlass { void compute_shared_init_state(); bool compute_shared_has_subklass(); - int compute_nonstatic_fields(); - GrowableArray* compute_nonstatic_fields_impl(GrowableArray* super_fields); + void compute_nonstatic_fields(); + void compute_nonstatic_fields_impl(const GrowableArray* super_declared_fields, const GrowableArray* super_fields); bool compute_has_trusted_loader(); // Update the init_state for shared klasses @@ -204,13 +217,24 @@ class ciInstanceKlass : public ciKlass { ciInstanceKlass* get_canonical_holder(int offset); ciField* get_field_by_offset(int field_offset, bool is_static); ciField* get_field_by_name(ciSymbol* name, ciSymbol* signature, bool is_static); + // Get field descriptor at field_offset ignoring flattening + ciField* get_non_flat_field_by_offset(int field_offset); + // Get the index of the declared field that contains this offset + int field_index_by_offset(int offset); + + // Total number of nonstatic fields (including inherited) + int nof_declared_nonstatic_fields() { + if (_declared_nonstatic_fields == nullptr) { + compute_nonstatic_fields(); + } + return _declared_nonstatic_fields->length(); + } - // total number of nonstatic fields (including inherited): int nof_nonstatic_fields() { - if (_nonstatic_fields == nullptr) - return compute_nonstatic_fields(); - else - return _nonstatic_fields->length(); + if (_nonstatic_fields == nullptr) { + compute_nonstatic_fields(); + } + return _nonstatic_fields->length(); } bool has_injected_fields() { @@ -222,7 +246,11 @@ class ciInstanceKlass : public ciKlass { bool has_object_fields() const; - // nth nonstatic field (presented by ascending address) + ciField* declared_nonstatic_field_at(int i) { + assert(_declared_nonstatic_fields != nullptr, "should be initialized"); + return _declared_nonstatic_fields->at(i); + } + ciField* nonstatic_field_at(int i) { assert(_nonstatic_fields != nullptr, ""); return _nonstatic_fields->at(i); @@ -243,9 +271,9 @@ class ciInstanceKlass : public ciKlass { // Java access flags bool is_public () { return flags().is_public(); } bool is_final () { return flags().is_final(); } - bool is_super () { return flags().is_super(); } bool is_interface () { return flags().is_interface(); } bool is_abstract () { return flags().is_abstract(); } + bool is_abstract_value_klass() { return is_abstract() && !flags().is_identity(); } ciMethod* find_method(ciSymbol* name, ciSymbol* signature); // Note: To find a method from name and type strings, use ciSymbol::make, @@ -260,6 +288,8 @@ class ciInstanceKlass : public ciKlass { return (impl != this ? impl : nullptr); } + virtual bool can_be_inline_klass(bool is_exact = false); + // Is the defining class loader of this class the default loader? bool uses_default_loader() const; diff --git a/src/hotspot/share/ci/ciKlass.cpp b/src/hotspot/share/ci/ciKlass.cpp index f3e49634d29..348cfcb2c33 100644 --- a/src/hotspot/share/ci/ciKlass.cpp +++ b/src/hotspot/share/ci/ciKlass.cpp @@ -225,6 +225,15 @@ jint ciKlass::access_flags() { ) } +// ------------------------------------------------------------------ +// ciKlass::prototype_header +markWord ciKlass::prototype_header() const { + assert(is_loaded(), "not loaded"); + GUARDED_VM_ENTRY( + return get_Klass()->prototype_header(); + ) +} + // ------------------------------------------------------------------ // ciKlass::misc_flags klass_flags_t ciKlass::misc_flags() { @@ -242,6 +251,11 @@ void ciKlass::print_impl(outputStream* st) { st->print(" name="); print_name_on(st); st->print(" loaded=%s", (is_loaded() ? "true" : "false")); + GUARDED_VM_ENTRY( + if (is_flat_array_klass()) { + st->print(" layout_kind=%d", (int)((FlatArrayKlass*)get_Klass())->layout_kind()); + } + ) } // ------------------------------------------------------------------ diff --git a/src/hotspot/share/ci/ciKlass.hpp b/src/hotspot/share/ci/ciKlass.hpp index 37091471a2a..7db747c1dd3 100644 --- a/src/hotspot/share/ci/ciKlass.hpp +++ b/src/hotspot/share/ci/ciKlass.hpp @@ -44,8 +44,10 @@ class ciKlass : public ciType { friend class ciMethod; friend class ciMethodData; friend class ciObjArrayKlass; - friend class ciSignature; friend class ciReceiverTypeData; + friend class ciSignature; + friend class ciFlatArrayKlass; + friend class ciArrayKlass; private: ciSymbol* _name; @@ -104,6 +106,14 @@ class ciKlass : public ciType { return false; } + virtual bool can_be_inline_klass(bool is_exact = false) { + return false; + } + + virtual bool can_be_inline_array_klass() { + return EnableValhalla && is_java_lang_Object(); + } + bool is_in_encoding_range() { Klass* k = get_Klass(); bool is_in_encoding_range = CompressedKlassPointers::is_encodable(k); @@ -125,6 +135,8 @@ class ciKlass : public ciType { // Fetch Klass::access_flags. jint access_flags(); + markWord prototype_header() const; + // Fetch Klass::misc_flags. klass_flags_t misc_flags(); diff --git a/src/hotspot/share/ci/ciMetadata.hpp b/src/hotspot/share/ci/ciMetadata.hpp index 2e232c59188..de02d780dc8 100644 --- a/src/hotspot/share/ci/ciMetadata.hpp +++ b/src/hotspot/share/ci/ciMetadata.hpp @@ -55,9 +55,13 @@ class ciMetadata: public ciBaseObject { virtual bool is_method_data() const { return false; } virtual bool is_klass() const { return false; } virtual bool is_instance_klass() const { return false; } + virtual bool is_inlinetype() const { return false; } virtual bool is_array_klass() const { return false; } + virtual bool is_flat_array_klass() const { return false; } virtual bool is_obj_array_klass() const { return false; } virtual bool is_type_array_klass() const { return false; } + virtual bool is_early_larval() const { return false; } + virtual bool maybe_flat_in_array() const { return false; } virtual void dump_replay_data(outputStream* st) { /* do nothing */ } ciMethod* as_method() { @@ -92,6 +96,10 @@ class ciMetadata: public ciBaseObject { assert(is_array_klass(), "bad cast"); return (ciArrayKlass*)this; } + ciFlatArrayKlass* as_flat_array_klass() { + assert(is_flat_array_klass(), "bad cast"); + return (ciFlatArrayKlass*)this; + } ciObjArrayKlass* as_obj_array_klass() { assert(is_obj_array_klass(), "bad cast"); return (ciObjArrayKlass*)this; @@ -100,6 +108,10 @@ class ciMetadata: public ciBaseObject { assert(is_type_array_klass(), "bad cast"); return (ciTypeArrayKlass*)this; } + ciInlineKlass* as_inline_klass() { + assert(is_inlinetype(), "bad cast"); + return (ciInlineKlass*)this; + } Metadata* constant_encoding() { return _metadata; } diff --git a/src/hotspot/share/ci/ciMethod.cpp b/src/hotspot/share/ci/ciMethod.cpp index d3b8b8f13cc..d967dc8603e 100644 --- a/src/hotspot/share/ci/ciMethod.cpp +++ b/src/hotspot/share/ci/ciMethod.cpp @@ -52,6 +52,7 @@ #include "prims/methodHandles.hpp" #include "runtime/deoptimization.hpp" #include "runtime/handles.inline.hpp" +#include "runtime/sharedRuntime.hpp" #include "utilities/bitMap.inline.hpp" #include "utilities/xmlstream.hpp" #ifdef COMPILER2 @@ -662,6 +663,60 @@ bool ciMethod::parameter_profiled_type(int i, ciKlass*& type, ProfilePtrKind& pt return false; } +bool ciMethod::array_access_profiled_type(int bci, ciKlass*& array_type, ciKlass*& element_type, ProfilePtrKind& element_ptr, bool &flat_array, bool &null_free_array) { + if (method_data() != nullptr && method_data()->is_mature()) { + ciProfileData* data = method_data()->bci_to_data(bci); + if (data != nullptr) { + if (data->is_ArrayLoadData()) { + ciArrayLoadData* array_access = (ciArrayLoadData*) data->as_ArrayLoadData(); + array_type = array_access->array()->valid_type(); + element_type = array_access->element()->valid_type(); + element_ptr = array_access->element()->ptr_kind(); + flat_array = array_access->flat_array(); + null_free_array = array_access->null_free_array(); + return true; + } else if (data->is_ArrayStoreData()) { + ciArrayStoreData* array_access = (ciArrayStoreData*) data->as_ArrayStoreData(); + array_type = array_access->array()->valid_type(); + flat_array = array_access->flat_array(); + null_free_array = array_access->null_free_array(); + ciCallProfile call_profile = call_profile_at_bci(bci); + if (call_profile.morphism() == 1) { + element_type = call_profile.receiver(0); + } else { + element_type = nullptr; + } + if (!array_access->null_seen()) { + element_ptr = ProfileNeverNull; + } else if (call_profile.count() == 0) { + element_ptr = ProfileAlwaysNull; + } else { + element_ptr = ProfileMaybeNull; + } + return true; + } + } + } + return false; +} + +bool ciMethod::acmp_profiled_type(int bci, ciKlass*& left_type, ciKlass*& right_type, ProfilePtrKind& left_ptr, ProfilePtrKind& right_ptr, bool &left_inline_type, bool &right_inline_type) { + if (method_data() != nullptr && method_data()->is_mature()) { + ciProfileData* data = method_data()->bci_to_data(bci); + if (data != nullptr && data->is_ACmpData()) { + ciACmpData* acmp = (ciACmpData*)data->as_ACmpData(); + left_type = acmp->left()->valid_type(); + right_type = acmp->right()->valid_type(); + left_ptr = acmp->left()->ptr_kind(); + right_ptr = acmp->right()->ptr_kind(); + left_inline_type = acmp->left_inline_type(); + right_inline_type = acmp->right_inline_type(); + return true; + } + } + return false; +} + // ------------------------------------------------------------------ // ciMethod::find_monomorphic_target @@ -974,10 +1029,13 @@ bool ciMethod::is_compiled_lambda_form() const { } // ------------------------------------------------------------------ -// ciMethod::is_object_initializer +// ciMethod::is_object_constructor // -bool ciMethod::is_object_initializer() const { - return name() == ciSymbols::object_initializer_name(); +bool ciMethod::is_object_constructor() const { + return (name() == ciSymbols::object_initializer_name() + && signature()->return_type()->is_void()); + // Note: We can't test is_static, because that would + // require the method to be loaded. Sometimes it isn't. } // ------------------------------------------------------------------ @@ -1529,6 +1587,25 @@ bool ciMethod::is_consistent_info(ciMethod* declared_method, ciMethod* resolved_ } // ------------------------------------------------------------------ + +bool ciMethod::is_scalarized_arg(int idx) const { + VM_ENTRY_MARK; + return get_Method()->is_scalarized_arg(idx); +} + +bool ciMethod::has_scalarized_args() const { + VM_ENTRY_MARK; + return get_Method()->has_scalarized_args(); +} + +const GrowableArray* ciMethod::get_sig_cc() const { + VM_ENTRY_MARK; + if (get_Method()->adapter() == nullptr) { + return nullptr; + } + return get_Method()->adapter()->get_sig_cc(); +} + // ciMethod::is_old // // Return true for redefined methods diff --git a/src/hotspot/share/ci/ciMethod.hpp b/src/hotspot/share/ci/ciMethod.hpp index eecd9427585..11b9b0f2344 100644 --- a/src/hotspot/share/ci/ciMethod.hpp +++ b/src/hotspot/share/ci/ciMethod.hpp @@ -46,6 +46,7 @@ class xmlStream; // Whether profiling found an oop to be always, never or sometimes // null enum ProfilePtrKind { + ProfileUnknownNull, ProfileAlwaysNull, ProfileNeverNull, ProfileMaybeNull @@ -198,7 +199,7 @@ class ciMethod : public ciMetadata { bool force_inline() const { return get_Method()->force_inline(); } bool dont_inline() const { return get_Method()->dont_inline(); } bool intrinsic_candidate() const { return get_Method()->intrinsic_candidate(); } - bool is_static_initializer() const { return get_Method()->is_static_initializer(); } + bool is_class_initializer() const { return get_Method()->is_class_initializer(); } bool changes_current_thread() const { return get_Method()->changes_current_thread(); } bool deprecated() const { return is_loaded() && get_Method()->deprecated(); } @@ -269,7 +270,10 @@ class ciMethod : public ciMetadata { bool argument_profiled_type(int bci, int i, ciKlass*& type, ProfilePtrKind& ptr_kind); bool parameter_profiled_type(int i, ciKlass*& type, ProfilePtrKind& ptr_kind); bool return_profiled_type(int bci, ciKlass*& type, ProfilePtrKind& ptr_kind); - + bool array_access_profiled_type(int bci, ciKlass*& array_type, ciKlass*& element_type, ProfilePtrKind& element_ptr, bool &flat_array, bool &null_free); + bool acmp_profiled_type(int bci, ciKlass*& left_type, ciKlass*& right_type, + ProfilePtrKind& left_ptr, ProfilePtrKind& right_ptr, + bool &left_inline_type, bool &right_inline_type); ciField* get_field_at_bci( int bci, bool &will_link); ciMethod* get_method_at_bci(int bci, bool &will_link, ciSignature* *declared_signature); ciMethod* get_method_at_bci(int bci) { @@ -341,6 +345,7 @@ class ciMethod : public ciMetadata { bool is_native () const { return flags().is_native(); } bool is_interface () const { return flags().is_interface(); } bool is_abstract () const { return flags().is_abstract(); } + bool has_vararg () const { return flags().has_vararg(); } // Other flags bool is_final_method() const { return is_final() || holder()->is_final(); } @@ -357,8 +362,8 @@ class ciMethod : public ciMetadata { bool has_reserved_stack_access() const { return _has_reserved_stack_access; } bool is_boxing_method() const; bool is_unboxing_method() const; + bool is_object_constructor() const; bool is_vector_method() const; - bool is_object_initializer() const; bool is_scoped() const; bool is_old() const; @@ -383,6 +388,11 @@ class ciMethod : public ciMetadata { void print_short_name(outputStream* st = tty); static bool is_consistent_info(ciMethod* declared_method, ciMethod* resolved_method); + + // Support for the inline type calling convention + bool is_scalarized_arg(int idx) const; + bool has_scalarized_args() const; + const GrowableArray* get_sig_cc() const; }; #endif // SHARE_CI_CIMETHOD_HPP diff --git a/src/hotspot/share/ci/ciMethodData.cpp b/src/hotspot/share/ci/ciMethodData.cpp index 533e8659968..554455b6a23 100644 --- a/src/hotspot/share/ci/ciMethodData.cpp +++ b/src/hotspot/share/ci/ciMethodData.cpp @@ -337,12 +337,12 @@ void ciTypeStackSlotEntries::translate_type_data_from(const TypeStackSlotEntries } } -void ciReturnTypeEntry::translate_type_data_from(const ReturnTypeEntry* ret) { +void ciSingleTypeEntry::translate_type_data_from(const SingleTypeEntry* ret) { intptr_t k = ret->type(); Klass* klass = (Klass*)klass_part(k); if (klass == nullptr || !klass->is_loader_present_and_alive() || !is_klass_loaded(klass)) { // With concurrent class unloading, the MDO could have stale metadata; override it - set_type(ReturnTypeEntry::with_status((Klass*)nullptr, k)); + set_type(SingleTypeEntry::with_status((Klass*)nullptr, k)); } else { set_type(translate_klass(k)); } @@ -393,6 +393,12 @@ ciProfileData* ciMethodData::data_from(DataLayout* data_layout) { return new ciVirtualCallTypeData(data_layout); case DataLayout::parameters_type_data_tag: return new ciParametersTypeData(data_layout); + case DataLayout::array_store_data_tag: + return new ciArrayStoreData(data_layout); + case DataLayout::array_load_data_tag: + return new ciArrayLoadData(data_layout); + case DataLayout::acmp_data_tag: + return new ciACmpData(data_layout); }; } @@ -810,12 +816,29 @@ void ciMethodData::dump_replay_data(outputStream* out) { ciVirtualCallTypeData* call_type_data = (ciVirtualCallTypeData*)pdata; dump_replay_data_call_type_helper(out, round, count, call_type_data); } + } else if (pdata->is_CallTypeData()) { + ciCallTypeData* call_type_data = (ciCallTypeData*)pdata; + dump_replay_data_call_type_helper(out, round, count, call_type_data); + } else if (pdata->is_ArrayStoreData()) { + ciArrayStoreData* array_store_data = (ciArrayStoreData*)pdata; + dump_replay_data_type_helper(out, round, count, array_store_data, ciArrayStoreData::array_offset(), + array_store_data->array()->valid_type()); + dump_replay_data_receiver_type_helper(out, round, count, array_store_data); + } else if (pdata->is_ArrayLoadData()) { + ciArrayLoadData* array_load_data = (ciArrayLoadData*)pdata; + dump_replay_data_type_helper(out, round, count, array_load_data, ciArrayLoadData::array_offset(), + array_load_data->array()->valid_type()); + dump_replay_data_type_helper(out, round, count, array_load_data, ciArrayLoadData::element_offset(), + array_load_data->element()->valid_type()); + } else if (pdata->is_ACmpData()) { + ciACmpData* acmp_data = (ciACmpData*)pdata; + dump_replay_data_type_helper(out, round, count, acmp_data, ciACmpData::left_offset(), + acmp_data->left()->valid_type()); + dump_replay_data_type_helper(out, round, count, acmp_data, ciACmpData::right_offset(), + acmp_data->right()->valid_type()); } else if (pdata->is_ReceiverTypeData()) { ciReceiverTypeData* vdata = (ciReceiverTypeData*)pdata; dump_replay_data_receiver_type_helper(out, round, count, vdata); - } else if (pdata->is_CallTypeData()) { - ciCallTypeData* call_type_data = (ciCallTypeData*)pdata; - dump_replay_data_call_type_helper(out, round, count, call_type_data); } } if (parameters != nullptr) { @@ -898,7 +921,7 @@ void ciTypeStackSlotEntries::print_data_on(outputStream* st) const { } } -void ciReturnTypeEntry::print_data_on(outputStream* st) const { +void ciSingleTypeEntry::print_data_on(outputStream* st) const { _pd->tab(st); st->print("ret "); print_ciklass(st, type()); @@ -971,4 +994,35 @@ void ciSpeculativeTrapData::print_data_on(outputStream* st, const char* extra) c method()->print_short_name(st); st->cr(); } + +void ciArrayStoreData::print_data_on(outputStream* st, const char* extra) const { + print_shared(st, "ciArrayStoreData", extra); + tab(st, true); + st->print("array"); + array()->print_data_on(st); + tab(st, true); + st->print("element"); + rtd_super()->print_receiver_data_on(st); +} + +void ciArrayLoadData::print_data_on(outputStream* st, const char* extra) const { + print_shared(st, "ciArrayLoadData", extra); + tab(st, true); + st->print("array"); + array()->print_data_on(st); + tab(st, true); + st->print("element"); + element()->print_data_on(st); +} + +void ciACmpData::print_data_on(outputStream* st, const char* extra) const { + BranchData::print_data_on(st, extra); + st->cr(); + tab(st, true); + st->print("left"); + left()->print_data_on(st); + tab(st, true); + st->print("right"); + right()->print_data_on(st); +} #endif diff --git a/src/hotspot/share/ci/ciMethodData.hpp b/src/hotspot/share/ci/ciMethodData.hpp index a43d011b77e..fca4f07099f 100644 --- a/src/hotspot/share/ci/ciMethodData.hpp +++ b/src/hotspot/share/ci/ciMethodData.hpp @@ -124,9 +124,9 @@ class ciTypeStackSlotEntries : public TypeStackSlotEntries, ciTypeEntries { #endif }; -class ciReturnTypeEntry : public ReturnTypeEntry, ciTypeEntries { +class ciSingleTypeEntry : public SingleTypeEntry, ciTypeEntries { public: - void translate_type_data_from(const ReturnTypeEntry* ret); + void translate_type_data_from(const SingleTypeEntry* ret); ciKlass* valid_type() const { return valid_ciklass(type()); @@ -146,7 +146,7 @@ class ciCallTypeData : public CallTypeData { ciCallTypeData(DataLayout* layout) : CallTypeData(layout) {} ciTypeStackSlotEntries* args() const { return (ciTypeStackSlotEntries*)CallTypeData::args(); } - ciReturnTypeEntry* ret() const { return (ciReturnTypeEntry*)CallTypeData::ret(); } + ciSingleTypeEntry* ret() const { return (ciSingleTypeEntry*)CallTypeData::ret(); } void translate_from(const ProfileData* data) { if (has_arguments()) { @@ -258,7 +258,7 @@ class ciVirtualCallTypeData : public VirtualCallTypeData { } ciTypeStackSlotEntries* args() const { return (ciTypeStackSlotEntries*)VirtualCallTypeData::args(); } - ciReturnTypeEntry* ret() const { return (ciReturnTypeEntry*)VirtualCallTypeData::ret(); } + ciSingleTypeEntry* ret() const { return (ciSingleTypeEntry*)VirtualCallTypeData::ret(); } // Copy & translate from oop based VirtualCallData virtual void translate_from(const ProfileData* data) { @@ -362,6 +362,63 @@ class ciSpeculativeTrapData : public SpeculativeTrapData { #endif }; +class ciArrayStoreData : public ArrayStoreData { + // Fake multiple inheritance... It's a ciReceiverTypeData also. + ciReceiverTypeData* rtd_super() const { return (ciReceiverTypeData*) this; } + +public: + ciArrayStoreData(DataLayout* layout) : ArrayStoreData(layout) {} + + ciSingleTypeEntry* array() const { return (ciSingleTypeEntry*)ArrayStoreData::array(); } + + virtual void translate_from(const ProfileData* data) { + array()->translate_type_data_from(data->as_ArrayStoreData()->array()); + rtd_super()->translate_receiver_data_from(data); + } + + ciKlass* receiver(uint row) { + return rtd_super()->receiver(row); + } +#ifndef PRODUCT + void print_data_on(outputStream* st, const char* extra = nullptr) const; +#endif +}; + +class ciArrayLoadData : public ArrayLoadData { +public: + ciArrayLoadData(DataLayout* layout) : ArrayLoadData(layout) {} + + ciSingleTypeEntry* array() const { return (ciSingleTypeEntry*)ArrayLoadData::array(); } + ciSingleTypeEntry* element() const { return (ciSingleTypeEntry*)ArrayLoadData::element(); } + + virtual void translate_from(const ProfileData* data) { + array()->translate_type_data_from(data->as_ArrayLoadData()->array()); + element()->translate_type_data_from(data->as_ArrayLoadData()->element()); + } + +#ifndef PRODUCT + void print_data_on(outputStream* st, const char* extra = nullptr) const; +#endif +}; + + +class ciACmpData : public ACmpData { +public: + ciACmpData(DataLayout* layout) : ACmpData(layout) {} + + ciSingleTypeEntry* left() const { return (ciSingleTypeEntry*)ACmpData::left(); } + ciSingleTypeEntry* right() const { return (ciSingleTypeEntry*)ACmpData::right(); } + + virtual void translate_from(const ProfileData* data) { + left()->translate_type_data_from(data->as_ACmpData()->left()); + right()->translate_type_data_from(data->as_ACmpData()->right()); + } + +#ifndef PRODUCT + void print_data_on(outputStream* st, const char* extra = nullptr) const; +#endif +}; + // ciMethodData // // This class represents a MethodData* in the HotSpot virtual diff --git a/src/hotspot/share/ci/ciObjArray.cpp b/src/hotspot/share/ci/ciObjArray.cpp index 2a485fb2618..3f8a511d9b0 100644 --- a/src/hotspot/share/ci/ciObjArray.cpp +++ b/src/hotspot/share/ci/ciObjArray.cpp @@ -26,6 +26,7 @@ #include "ci/ciObjArray.hpp" #include "ci/ciUtilities.inline.hpp" #include "oops/objArrayOop.inline.hpp" +#include "oops/oop.inline.hpp" // ciObjArray // diff --git a/src/hotspot/share/ci/ciObjArray.hpp b/src/hotspot/share/ci/ciObjArray.hpp index 4f1dfc501cd..c1282469df3 100644 --- a/src/hotspot/share/ci/ciObjArray.hpp +++ b/src/hotspot/share/ci/ciObjArray.hpp @@ -50,6 +50,8 @@ class ciObjArray : public ciArray { bool is_obj_array() { return true; } ciObject* obj_at(int index); + + bool is_flat() { return false; } }; #endif // SHARE_CI_CIOBJARRAY_HPP diff --git a/src/hotspot/share/ci/ciObjArrayKlass.cpp b/src/hotspot/share/ci/ciObjArrayKlass.cpp index 191e4e67522..7df5298f747 100644 --- a/src/hotspot/share/ci/ciObjArrayKlass.cpp +++ b/src/hotspot/share/ci/ciObjArrayKlass.cpp @@ -26,6 +26,7 @@ #include "ci/ciObjArrayKlass.hpp" #include "ci/ciSymbol.hpp" #include "ci/ciUtilities.inline.hpp" +#include "oops/inlineKlass.inline.hpp" #include "oops/objArrayKlass.hpp" #include "runtime/signature.hpp" @@ -63,14 +64,15 @@ ciObjArrayKlass::ciObjArrayKlass(ciSymbol* array_name, int dimension) : ciArrayKlass(array_name, dimension, T_OBJECT) { - _base_element_klass = base_element_klass; - assert(_base_element_klass->is_instance_klass() || - _base_element_klass->is_type_array_klass(), "bad base klass"); - if (dimension == 1) { - _element_klass = base_element_klass; - } else { - _element_klass = nullptr; - } + _base_element_klass = base_element_klass; + assert(_base_element_klass->is_instance_klass() || + _base_element_klass->is_type_array_klass() || + _base_element_klass->is_flat_array_klass(), "bad base klass"); + if (dimension == 1) { + _element_klass = base_element_klass; + } else { + _element_klass = nullptr; + } } // ------------------------------------------------------------------ @@ -115,7 +117,6 @@ ciSymbol* ciObjArrayKlass::construct_array_name(ciSymbol* element_name, name[pos] = JVM_SIGNATURE_ARRAY; } Symbol* base_name_sym = element_name->get_symbol(); - if (Signature::is_array(base_name_sym) || Signature::has_envelope(base_name_sym)) { strncpy(&name[pos], (char*)element_name->base(), element_len); @@ -133,8 +134,7 @@ ciSymbol* ciObjArrayKlass::construct_array_name(ciSymbol* element_name, // ciObjArrayKlass::make_impl // // Implementation of make. -ciObjArrayKlass* ciObjArrayKlass::make_impl(ciKlass* element_klass) { - +ciObjArrayKlass* ciObjArrayKlass::make_impl(ciKlass* element_klass, bool vm_type) { if (element_klass->is_loaded()) { EXCEPTION_CONTEXT; // The element klass is loaded @@ -144,11 +144,14 @@ ciObjArrayKlass* ciObjArrayKlass::make_impl(ciKlass* element_klass) { CURRENT_THREAD_ENV->record_out_of_memory_failure(); return ciEnv::unloaded_ciobjarrayklass(); } + if (array->is_objArray_klass() && vm_type) { + assert(!array->is_refArray_klass() && !array->is_flatArray_klass(), "Unexpected refined klass"); + array = ObjArrayKlass::cast(array)->klass_with_properties(ArrayKlass::ArrayProperties::DEFAULT, THREAD); + } return CURRENT_THREAD_ENV->get_obj_array_klass(array); } - // The array klass was unable to be made or the element klass was - // not loaded. + // The array klass was unable to be made or the element klass was not loaded. ciSymbol* array_name = construct_array_name(element_klass->name(), 1); if (array_name == ciEnv::unloaded_cisymbol()) { return ciEnv::unloaded_ciobjarrayklass(); @@ -162,8 +165,8 @@ ciObjArrayKlass* ciObjArrayKlass::make_impl(ciKlass* element_klass) { // ciObjArrayKlass::make // // Make an array klass corresponding to the specified primitive type. -ciObjArrayKlass* ciObjArrayKlass::make(ciKlass* element_klass) { - GUARDED_VM_ENTRY(return make_impl(element_klass);) +ciObjArrayKlass* ciObjArrayKlass::make(ciKlass* element_klass, bool vm_type) { + GUARDED_VM_ENTRY(return make_impl(element_klass, vm_type);) } ciObjArrayKlass* ciObjArrayKlass::make(ciKlass* element_klass, int dims) { @@ -175,9 +178,17 @@ ciObjArrayKlass* ciObjArrayKlass::make(ciKlass* element_klass, int dims) { } ciKlass* ciObjArrayKlass::exact_klass() { + if (!is_loaded()) { + return nullptr; + } ciType* base = base_element_type(); if (base->is_instance_klass()) { ciInstanceKlass* ik = base->as_instance_klass(); + // Even though MyValue is final, [LMyValue is only exact if the array + // is null-free due to null-free [LMyValue <: null-able [LMyValue. + if (ik->is_inlinetype() && !is_elem_null_free()) { + return nullptr; + } if (ik->exact_klass() != nullptr) { return this; } diff --git a/src/hotspot/share/ci/ciObjArrayKlass.hpp b/src/hotspot/share/ci/ciObjArrayKlass.hpp index 3fb37c5088c..22eba0f30d9 100644 --- a/src/hotspot/share/ci/ciObjArrayKlass.hpp +++ b/src/hotspot/share/ci/ciObjArrayKlass.hpp @@ -49,7 +49,7 @@ class ciObjArrayKlass : public ciArrayKlass { return (ObjArrayKlass*)get_Klass(); } - static ciObjArrayKlass* make_impl(ciKlass* element_klass); + static ciObjArrayKlass* make_impl(ciKlass* element_klass, bool vm_type = false); static ciSymbol* construct_array_name(ciSymbol* element_name, int dimension); @@ -68,10 +68,14 @@ class ciObjArrayKlass : public ciArrayKlass { // What kind of ciObject is this? bool is_obj_array_klass() const { return true; } - static ciObjArrayKlass* make(ciKlass* element_klass); + static ciObjArrayKlass* make(ciKlass* element_klass, bool vm_type = false); static ciObjArrayKlass* make(ciKlass* element_klass, int dims); virtual ciKlass* exact_klass(); + + virtual bool can_be_inline_array_klass() { + return element_klass()->can_be_inline_klass(); + } }; #endif // SHARE_CI_CIOBJARRAYKLASS_HPP diff --git a/src/hotspot/share/ci/ciObjectFactory.cpp b/src/hotspot/share/ci/ciObjectFactory.cpp index 2af5d812922..3234125bd6f 100644 --- a/src/hotspot/share/ci/ciObjectFactory.cpp +++ b/src/hotspot/share/ci/ciObjectFactory.cpp @@ -23,6 +23,9 @@ */ #include "ci/ciCallSite.hpp" +#include "ci/ciFlatArray.hpp" +#include "ci/ciFlatArrayKlass.hpp" +#include "ci/ciInlineKlass.hpp" #include "ci/ciInstance.hpp" #include "ci/ciInstanceKlass.hpp" #include "ci/ciMemberName.hpp" @@ -144,7 +147,7 @@ void ciObjectFactory::init_shared_objects() { for (int i = T_BOOLEAN; i <= T_CONFLICT; i++) { BasicType t = (BasicType)i; - if (type2name(t) != nullptr && !is_reference_type(t) && + if (type2name(t) != nullptr && t != T_FLAT_ELEMENT && !is_reference_type(t) && t != T_NARROWOOP && t != T_NARROWKLASS) { ciType::_basic_types[t] = new (_arena) ciType(t); init_ident_of(ciType::_basic_types[t]); @@ -375,12 +378,15 @@ ciObject* ciObjectFactory::create_new_object(oop o) { return new (arena()) ciMethodType(h_i); else return new (arena()) ciInstance(h_i); - } else if (o->is_objArray()) { + } else if (o->is_refArray()) { objArrayHandle h_oa(THREAD, (objArrayOop)o); return new (arena()) ciObjArray(h_oa); } else if (o->is_typeArray()) { typeArrayHandle h_ta(THREAD, (typeArrayOop)o); return new (arena()) ciTypeArray(h_ta); + } else if (o->is_flatArray()) { + flatArrayHandle h_ta(THREAD, (flatArrayOop)o); + return new (arena()) ciFlatArray(h_ta); } // The oop is of some type not supported by the compiler interface. @@ -400,10 +406,14 @@ ciMetadata* ciObjectFactory::create_new_metadata(Metadata* o) { if (o->is_klass()) { Klass* k = (Klass*)o; - if (k->is_instance_klass()) { + if (k->is_inline_klass()) { + return new (arena()) ciInlineKlass(k); + } else if (k->is_instance_klass()) { assert(!ReplayCompiles || ciReplay::no_replay_state() || !ciReplay::is_klass_unresolved((InstanceKlass*)k), "must be whitelisted for replay compilation"); return new (arena()) ciInstanceKlass(k); - } else if (k->is_objArray_klass()) { + } else if (k->is_flatArray_klass()) { + return new (arena()) ciFlatArrayKlass(k); + } else if (k->is_refArray_klass() || k->is_objArray_klass()) { return new (arena()) ciObjArrayKlass(k); } else if (k->is_typeArray_klass()) { return new (arena()) ciTypeArrayKlass(k); @@ -639,6 +649,18 @@ ciReturnAddress* ciObjectFactory::get_return_address(int bci) { return new_ret_addr; } +ciWrapper* ciObjectFactory::make_early_larval_wrapper(ciType* type) { + ciWrapper* wrapper = new (arena()) ciWrapper(type, ciWrapper::EarlyLarval); + init_ident_of(wrapper); + return wrapper; +} + +ciWrapper* ciObjectFactory::make_null_free_wrapper(ciType* type) { + ciWrapper* wrapper = new (arena()) ciWrapper(type, ciWrapper::NullFree); + init_ident_of(wrapper); + return wrapper; +} + // ------------------------------------------------------------------ // ciObjectFactory::init_ident_of void ciObjectFactory::init_ident_of(ciBaseObject* obj) { diff --git a/src/hotspot/share/ci/ciObjectFactory.hpp b/src/hotspot/share/ci/ciObjectFactory.hpp index 15ff2e48eb6..4ec108d601d 100644 --- a/src/hotspot/share/ci/ciObjectFactory.hpp +++ b/src/hotspot/share/ci/ciObjectFactory.hpp @@ -142,6 +142,9 @@ class ciObjectFactory : public ArenaObj { ciReturnAddress* get_return_address(int bci); + ciWrapper* make_early_larval_wrapper(ciType* type); + ciWrapper* make_null_free_wrapper(ciType* type); + GrowableArray* get_ci_metadata() { return &_ci_metadata; } // RedefineClasses support void metadata_do(MetadataClosure* f); diff --git a/src/hotspot/share/ci/ciReplay.cpp b/src/hotspot/share/ci/ciReplay.cpp index 6266c024260..3a69bed4780 100644 --- a/src/hotspot/share/ci/ciReplay.cpp +++ b/src/hotspot/share/ci/ciReplay.cpp @@ -41,6 +41,7 @@ #include "oops/constantPool.inline.hpp" #include "oops/cpCache.inline.hpp" #include "oops/fieldStreams.inline.hpp" +#include "oops/inlineKlass.inline.hpp" #include "oops/klass.inline.hpp" #include "oops/method.inline.hpp" #include "oops/oop.inline.hpp" @@ -511,7 +512,7 @@ class CompileReplay : public StackObj { return k; } obj = ciReplay::obj_field(obj, field); - // array + // TODO 8350865 I think we need to handle null-free/flat arrays here if (obj != nullptr && obj->is_objArray()) { objArrayOop arr = (objArrayOop)obj; int index = parse_int("index"); @@ -961,6 +962,7 @@ class CompileReplay : public StackObj { } break; } + case JVM_CONSTANT_Long: case JVM_CONSTANT_Double: parsed_two_word = i + 1; @@ -1007,36 +1009,89 @@ class CompileReplay : public StackObj { } } - // staticfield - // - // Initialize a class and fill in the value for a static field. - // This is useful when the compile was dependent on the value of - // static fields but it's impossible to properly rerun the static - // initializer. - void process_staticfield(TRAPS) { - InstanceKlass* k = (InstanceKlass *)parse_klass(CHECK); - - if (k == nullptr || ReplaySuppressInitializers == 0 || - (ReplaySuppressInitializers == 2 && k->class_loader() == nullptr)) { - skip_remaining(); - return; - } - - assert(k->is_initialized(), "must be"); + class InlineTypeFieldInitializer : public FieldClosure { + oop _vt; + CompileReplay* _replay; + public: + InlineTypeFieldInitializer(oop vt, CompileReplay* replay) + : _vt(vt), _replay(replay) {} - const char* field_name = parse_escaped_string(); - const char* field_signature = parse_string(); - fieldDescriptor fd; - Symbol* name = SymbolTable::new_symbol(field_name); - Symbol* sig = SymbolTable::new_symbol(field_signature); - if (!k->find_local_field(name, sig, &fd) || - !fd.is_static() || - fd.has_initial_value()) { - report_error(field_name); - return; + void do_field(fieldDescriptor* fd) { + BasicType bt = fd->field_type(); + const char* string_value = fd->is_null_free_inline_type() ? nullptr : _replay->parse_escaped_string(); + switch (bt) { + case T_BYTE: { + int value = atoi(string_value); + _vt->byte_field_put(fd->offset(), value); + break; + } + case T_BOOLEAN: { + int value = atoi(string_value); + _vt->bool_field_put(fd->offset(), value); + break; + } + case T_SHORT: { + int value = atoi(string_value); + _vt->short_field_put(fd->offset(), value); + break; + } + case T_CHAR: { + int value = atoi(string_value); + _vt->char_field_put(fd->offset(), value); + break; + } + case T_INT: { + int value = atoi(string_value); + _vt->int_field_put(fd->offset(), value); + break; + } + case T_LONG: { + jlong value; + if (sscanf(string_value, JLONG_FORMAT, &value) != 1) { + fprintf(stderr, "Error parsing long: %s\n", string_value); + break; + } + _vt->long_field_put(fd->offset(), value); + break; + } + case T_FLOAT: { + float value = atof(string_value); + _vt->float_field_put(fd->offset(), value); + break; + } + case T_DOUBLE: { + double value = atof(string_value); + _vt->double_field_put(fd->offset(), value); + break; + } + case T_ARRAY: + case T_OBJECT: + if (!fd->is_null_free_inline_type()) { + JavaThread* THREAD = JavaThread::current(); + bool res = _replay->process_staticfield_reference(string_value, _vt, fd, THREAD); + assert(res, "should succeed for arrays & objects"); + break; + } else { + InlineKlass* vk = InlineKlass::cast(fd->field_holder()->get_inline_type_field_klass(fd->index())); + if (fd->is_flat()) { + int field_offset = fd->offset() - vk->payload_offset(); + oop obj = cast_to_oop(cast_from_oop
(_vt) + field_offset); + InlineTypeFieldInitializer init_fields(obj, _replay); + vk->do_nonstatic_fields(&init_fields); + } else { + oop value = vk->allocate_instance(JavaThread::current()); + _vt->obj_field_put(fd->offset(), value); + } + break; + } + default: { + fatal("Unhandled type: %s", type2name(bt)); + } + } } + }; - oop java_mirror = k->java_mirror(); + bool process_staticfield_reference(const char* field_signature, oop java_mirror, fieldDescriptor* fd, TRAPS) { if (field_signature[0] == JVM_SIGNATURE_ARRAY) { int length = parse_int("array length"); oop value = nullptr; @@ -1044,10 +1099,8 @@ class CompileReplay : public StackObj { if (length != -1) { if (field_signature[1] == JVM_SIGNATURE_ARRAY) { // multi dimensional array - ArrayKlass* kelem = (ArrayKlass *)parse_klass(CHECK); - if (kelem == nullptr) { - return; - } + Klass* k = resolve_klass(field_signature, CHECK_(true)); + ArrayKlass* kelem = (ArrayKlass *)k; int rank = 0; while (field_signature[rank] == JVM_SIGNATURE_ARRAY) { rank++; @@ -1057,76 +1110,131 @@ class CompileReplay : public StackObj { for (int i = 1; i < rank; i++) { dims[i] = 1; // These aren't relevant to the compiler } - value = kelem->multi_allocate(rank, dims, CHECK); + value = kelem->multi_allocate(rank, dims, CHECK_(true)); } else { if (strcmp(field_signature, "[B") == 0) { - value = oopFactory::new_byteArray(length, CHECK); + value = oopFactory::new_byteArray(length, CHECK_(true)); } else if (strcmp(field_signature, "[Z") == 0) { - value = oopFactory::new_boolArray(length, CHECK); + value = oopFactory::new_boolArray(length, CHECK_(true)); } else if (strcmp(field_signature, "[C") == 0) { - value = oopFactory::new_charArray(length, CHECK); + value = oopFactory::new_charArray(length, CHECK_(true)); } else if (strcmp(field_signature, "[S") == 0) { - value = oopFactory::new_shortArray(length, CHECK); + value = oopFactory::new_shortArray(length, CHECK_(true)); } else if (strcmp(field_signature, "[F") == 0) { - value = oopFactory::new_floatArray(length, CHECK); + value = oopFactory::new_floatArray(length, CHECK_(true)); } else if (strcmp(field_signature, "[D") == 0) { - value = oopFactory::new_doubleArray(length, CHECK); + value = oopFactory::new_doubleArray(length, CHECK_(true)); } else if (strcmp(field_signature, "[I") == 0) { - value = oopFactory::new_intArray(length, CHECK); + value = oopFactory::new_intArray(length, CHECK_(true)); } else if (strcmp(field_signature, "[J") == 0) { - value = oopFactory::new_longArray(length, CHECK); + value = oopFactory::new_longArray(length, CHECK_(true)); } else if (field_signature[0] == JVM_SIGNATURE_ARRAY && field_signature[1] == JVM_SIGNATURE_CLASS) { - Klass* actual_array_klass = parse_klass(CHECK); + Klass* actual_array_klass = parse_klass(CHECK_(true)); + // TODO 8350865 I think we need to handle null-free/flat arrays here + // This handling will change the array property argument passed to the + // factory below Klass* kelem = ObjArrayKlass::cast(actual_array_klass)->element_klass(); - value = oopFactory::new_objArray(kelem, length, CHECK); + value = oopFactory::new_objArray(kelem, length, CHECK_(true)); } else { report_error("unhandled array staticfield"); } } + java_mirror->obj_field_put(fd->offset(), value); + return true; + } + } else if (strcmp(field_signature, "Ljava/lang/String;") == 0) { + const char* string_value = parse_escaped_string(); + Handle value = java_lang_String::create_from_str(string_value, CHECK_(true)); + java_mirror->obj_field_put(fd->offset(), value()); + return true; + } else if (field_signature[0] == JVM_SIGNATURE_CLASS) { + const char* instance = parse_escaped_string(); + oop value = nullptr; + if (instance != nullptr) { + Klass* k = resolve_klass(instance, CHECK_(true)); + value = InstanceKlass::cast(k)->allocate_instance(CHECK_(true)); } + java_mirror->obj_field_put(fd->offset(), value); + return true; + } + return false; + } + + // Initialize a class and fill in the value for a static field. + // This is useful when the compile was dependent on the value of + // static fields but it's impossible to properly rerun the static + // initializer. + void process_staticfield(TRAPS) { + InstanceKlass* k = (InstanceKlass *)parse_klass(CHECK); + + if (k == nullptr || ReplaySuppressInitializers == 0 || + (ReplaySuppressInitializers == 2 && k->class_loader() == nullptr)) { + skip_remaining(); + return; + } + + assert(k->is_initialized(), "must be"); + + const char* field_name = parse_escaped_string(); + const char* field_signature = parse_string(); + fieldDescriptor fd; + Symbol* name = SymbolTable::new_symbol(field_name); + Symbol* sig = SymbolTable::new_symbol(field_signature); + if (!k->find_local_field(name, sig, &fd) || + !fd.is_static() || + fd.has_initial_value()) { + report_error(field_name); + return; + } + + oop java_mirror = k->java_mirror(); + if (strcmp(field_signature, "I") == 0) { + const char* string_value = parse_escaped_string(); + int value = atoi(string_value); + java_mirror->int_field_put(fd.offset(), value); + } else if (strcmp(field_signature, "B") == 0) { + const char* string_value = parse_escaped_string(); + int value = atoi(string_value); + java_mirror->byte_field_put(fd.offset(), value); + } else if (strcmp(field_signature, "C") == 0) { + const char* string_value = parse_escaped_string(); + int value = atoi(string_value); + java_mirror->char_field_put(fd.offset(), value); + } else if (strcmp(field_signature, "S") == 0) { + const char* string_value = parse_escaped_string(); + int value = atoi(string_value); + java_mirror->short_field_put(fd.offset(), value); + } else if (strcmp(field_signature, "Z") == 0) { + const char* string_value = parse_escaped_string(); + int value = atoi(string_value); + java_mirror->bool_field_put(fd.offset(), value); + } else if (strcmp(field_signature, "J") == 0) { + const char* string_value = parse_escaped_string(); + jlong value; + if (sscanf(string_value, JLONG_FORMAT, &value) != 1) { + fprintf(stderr, "Error parsing long: %s\n", string_value); + return; + } + java_mirror->long_field_put(fd.offset(), value); + } else if (strcmp(field_signature, "F") == 0) { + const char* string_value = parse_escaped_string(); + float value = atof(string_value); + java_mirror->float_field_put(fd.offset(), value); + } else if (strcmp(field_signature, "D") == 0) { + const char* string_value = parse_escaped_string(); + double value = atof(string_value); + java_mirror->double_field_put(fd.offset(), value); + } else if (fd.is_null_free_inline_type()) { + Klass* kelem = resolve_klass(field_signature, CHECK); + InlineKlass* vk = InlineKlass::cast(kelem); + oop value = vk->allocate_instance(CHECK); + InlineTypeFieldInitializer init_fields(value, this); + vk->do_nonstatic_fields(&init_fields); java_mirror->obj_field_put(fd.offset(), value); } else { - const char* string_value = parse_escaped_string(); - if (strcmp(field_signature, "I") == 0) { - int value = atoi(string_value); - java_mirror->int_field_put(fd.offset(), value); - } else if (strcmp(field_signature, "B") == 0) { - int value = atoi(string_value); - java_mirror->byte_field_put(fd.offset(), value); - } else if (strcmp(field_signature, "C") == 0) { - int value = atoi(string_value); - java_mirror->char_field_put(fd.offset(), value); - } else if (strcmp(field_signature, "S") == 0) { - int value = atoi(string_value); - java_mirror->short_field_put(fd.offset(), value); - } else if (strcmp(field_signature, "Z") == 0) { - int value = atoi(string_value); - java_mirror->bool_field_put(fd.offset(), value); - } else if (strcmp(field_signature, "J") == 0) { - jlong value; - if (sscanf(string_value, JLONG_FORMAT, &value) != 1) { - fprintf(stderr, "Error parsing long: %s\n", string_value); - return; - } - java_mirror->long_field_put(fd.offset(), value); - } else if (strcmp(field_signature, "F") == 0) { - float value = atof(string_value); - java_mirror->float_field_put(fd.offset(), value); - } else if (strcmp(field_signature, "D") == 0) { - double value = atof(string_value); - java_mirror->double_field_put(fd.offset(), value); - } else if (strcmp(field_signature, "Ljava/lang/String;") == 0) { - 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) { - 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 { + bool res = process_staticfield_reference(field_signature, java_mirror, &fd, CHECK); + if (!res) { report_error("unhandled staticfield"); } } diff --git a/src/hotspot/share/ci/ciSignature.hpp b/src/hotspot/share/ci/ciSignature.hpp index 058f1a72c81..e0e4c322e89 100644 --- a/src/hotspot/share/ci/ciSignature.hpp +++ b/src/hotspot/share/ci/ciSignature.hpp @@ -27,6 +27,7 @@ #include "ci/ciClassList.hpp" #include "ci/ciSymbol.hpp" +#include "ci/ciType.hpp" #include "interpreter/bytecodes.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/growableArray.hpp" @@ -56,7 +57,7 @@ class ciSignature : public ArenaObj { ciKlass* accessing_klass() const { return _accessing_klass; } ciType* return_type() const { return _return_type; } - ciType* type_at(int index) const { return _types.at(index); } + ciType* type_at(int index) const { return _types.at(index)->unwrap(); } int size() const { return _size; } int count() const { return _types.length(); } diff --git a/src/hotspot/share/ci/ciSymbol.cpp b/src/hotspot/share/ci/ciSymbol.cpp index c3e2b8d53e4..1912588bf1c 100644 --- a/src/hotspot/share/ci/ciSymbol.cpp +++ b/src/hotspot/share/ci/ciSymbol.cpp @@ -79,6 +79,20 @@ char ciSymbol::char_at(int i) { bool ciSymbol::starts_with(const char* prefix, int len) const { GUARDED_VM_ENTRY(return get_symbol()->starts_with(prefix, len);) } +bool ciSymbol::starts_with(char prefix_char) const { + GUARDED_VM_ENTRY(return get_symbol()->starts_with(prefix_char);) +} + +// ------------------------------------------------------------------ +// ciSymbol::ends_with +// +// Tests if the symbol ends with the given suffix. +bool ciSymbol::ends_with(const char* suffix, int len) const { + GUARDED_VM_ENTRY(return get_symbol()->ends_with(suffix, len);) +} +bool ciSymbol::ends_with(char suffix_char) const { + GUARDED_VM_ENTRY(return get_symbol()->ends_with(suffix_char);) +} bool ciSymbol::is_signature_polymorphic_name() const { GUARDED_VM_ENTRY(return MethodHandles::is_signature_polymorphic_name(get_symbol());) diff --git a/src/hotspot/share/ci/ciSymbol.hpp b/src/hotspot/share/ci/ciSymbol.hpp index 93d29d56706..36bc97864d1 100644 --- a/src/hotspot/share/ci/ciSymbol.hpp +++ b/src/hotspot/share/ci/ciSymbol.hpp @@ -45,6 +45,7 @@ class ciSymbol : public ciBaseObject { friend class ciMethod; friend class ciField; friend class ciObjArrayKlass; + friend class ciFlatArrayKlass; private: const vmSymbolID _sid; @@ -79,6 +80,11 @@ class ciSymbol : public ciBaseObject { // Tests if the symbol starts with the given prefix. bool starts_with(const char* prefix, int len) const; + bool starts_with(char prefix_char) const; + + // Tests if the symbol ends with the given suffix. + bool ends_with(const char* suffix, int len) const; + bool ends_with(char suffix_char) const; // Determines where the symbol contains the given substring. int index_of_at(int i, const char* str, int len) const; diff --git a/src/hotspot/share/ci/ciType.cpp b/src/hotspot/share/ci/ciType.cpp index 9340f1cda3d..69c29795751 100644 --- a/src/hotspot/share/ci/ciType.cpp +++ b/src/hotspot/share/ci/ciType.cpp @@ -33,7 +33,7 @@ ciType* ciType::_basic_types[T_CONFLICT+1]; // ciType // -// This class represents a Java reference or primitive type. +// This class represents a Java reference, inline type or primitive type. // ------------------------------------------------------------------ // ciType::ciType @@ -140,3 +140,16 @@ void ciReturnAddress::print_impl(outputStream* st) { ciReturnAddress* ciReturnAddress::make(int bci) { GUARDED_VM_ENTRY(return CURRENT_ENV->get_return_address(bci);) } + +ciWrapper::ciWrapper(ciType* type, int properties) + : ciType(type->basic_type()), + _type(type), + _properties(properties) { + assert(!type->is_wrapper(), "Thou shall not double wrap!"); + assert(type->is_inlinetype() + // An abstract value type is an instance_klass + || (type->is_instance_klass() && !type->as_instance_klass()->flags().is_identity()) + // An unloaded inline type is an instance_klass (see ciEnv::get_klass_by_name_impl()) + || (type->is_instance_klass() && !type->is_loaded()), + "should only be used for inline types"); +} diff --git a/src/hotspot/share/ci/ciType.hpp b/src/hotspot/share/ci/ciType.hpp index 58d8c07d3e0..332918569aa 100644 --- a/src/hotspot/share/ci/ciType.hpp +++ b/src/hotspot/share/ci/ciType.hpp @@ -29,12 +29,13 @@ // ciType // -// This class represents a Java reference or primitive type. +// This class represents a Java reference, inline type or primitive type. class ciType : public ciMetadata { CI_PACKAGE_ACCESS friend class ciKlass; friend class ciReturnAddress; + friend class ciWrapper; private: BasicType _basic_type; @@ -70,6 +71,10 @@ class ciType : public ciMetadata { // What kind of ciObject is this? bool is_type() const { return true; } bool is_classless() const { return is_primitive_type(); } + virtual bool is_wrapper() const { return false; } + + virtual ciType* unwrap() { return this; } + virtual bool is_null_free() const { return false; } const char* name(); virtual void print_name_on(outputStream* st); @@ -106,4 +111,31 @@ class ciReturnAddress : public ciType { static ciReturnAddress* make(int bci); }; +// ciWrapper +// +// This class wraps another type to carry additional information. +class ciWrapper : public ciType { + CI_PACKAGE_ACCESS + +private: + ciType* _type; + enum Property { + NullFree = 1, + EarlyLarval = NullFree << 1, + }; + int _properties; + + ciWrapper(ciType* type, int properties); + + const char* type_string() override { return "ciWrapper"; } + + void print_impl(outputStream* st) override { _type->print_impl(st); } + +public: + ciType* unwrap() override { return _type; } + bool is_null_free() const override { return (_properties & (NullFree | EarlyLarval)) != 0; } + bool is_early_larval() const override { return (_properties & EarlyLarval) != 0; } + bool is_wrapper() const override { return true; } +}; + #endif // SHARE_CI_CITYPE_HPP diff --git a/src/hotspot/share/ci/ciTypeArrayKlass.hpp b/src/hotspot/share/ci/ciTypeArrayKlass.hpp index 294e307f886..34f82bce328 100644 --- a/src/hotspot/share/ci/ciTypeArrayKlass.hpp +++ b/src/hotspot/share/ci/ciTypeArrayKlass.hpp @@ -61,6 +61,10 @@ class ciTypeArrayKlass : public ciArrayKlass { virtual ciKlass* exact_klass() { return this; } + + virtual bool can_be_inline_array_klass() { + return false; + } }; #endif // SHARE_CI_CITYPEARRAYKLASS_HPP diff --git a/src/hotspot/share/ci/ciTypeFlow.cpp b/src/hotspot/share/ci/ciTypeFlow.cpp index 6df090a7ce5..f4289bee8b3 100644 --- a/src/hotspot/share/ci/ciTypeFlow.cpp +++ b/src/hotspot/share/ci/ciTypeFlow.cpp @@ -24,6 +24,7 @@ #include "ci/ciConstant.hpp" #include "ci/ciField.hpp" +#include "ci/ciInlineKlass.hpp" #include "ci/ciMethod.hpp" #include "ci/ciMethodData.hpp" #include "ci/ciObjArrayKlass.hpp" @@ -272,9 +273,21 @@ ciType* ciTypeFlow::StateVector::type_meet_internal(ciType* t1, ciType* t2, ciTy return t2; } else if (t2->equals(top_type())) { return t1; - } else if (t1->is_primitive_type() || t2->is_primitive_type()) { + } + // Unwrap after saving nullness information and handling top meets + assert(t1->is_early_larval() == t2->is_early_larval(), "States should be compatible."); + bool is_early_larval = t1->is_early_larval(); + bool null_free1 = t1->is_null_free(); + bool null_free2 = t2->is_null_free(); + if (t1->unwrap() == t2->unwrap() && null_free1 == null_free2) { + return t1; + } + t1 = t1->unwrap(); + t2 = t2->unwrap(); + + if (t1->is_primitive_type() || t2->is_primitive_type()) { // Special case null_type. null_type meet any reference type T - // is T. null_type meet null_type is null_type. + // is T. null_type meet null_type is null_type. if (t1->equals(null_type())) { if (!t2->is_primitive_type() || t2->equals(null_type())) { return t2; @@ -288,50 +301,60 @@ ciType* ciTypeFlow::StateVector::type_meet_internal(ciType* t1, ciType* t2, ciTy // At least one of the two types is a non-top primitive type. // The other type is not equal to it. Fall to bottom. return bottom_type(); - } else { - // Both types are non-top non-primitive types. That is, - // both types are either instanceKlasses or arrayKlasses. - ciKlass* object_klass = analyzer->env()->Object_klass(); - ciKlass* k1 = t1->as_klass(); - ciKlass* k2 = t2->as_klass(); - if (k1->equals(object_klass) || k2->equals(object_klass)) { - return object_klass; - } else if (!k1->is_loaded() || !k2->is_loaded()) { - // Unloaded classes fall to java.lang.Object at a merge. - return object_klass; - } else if (k1->is_interface() != k2->is_interface()) { - // When an interface meets a non-interface, we get Object; - // This is what the verifier does. - return object_klass; - } else if (k1->is_array_klass() || k2->is_array_klass()) { - // When an array meets a non-array, we get Object. - // When objArray meets typeArray, we also get Object. - // And when typeArray meets different typeArray, we again get Object. - // But when objArray meets objArray, we look carefully at element types. - if (k1->is_obj_array_klass() && k2->is_obj_array_klass()) { - // Meet the element types, then construct the corresponding array type. - ciKlass* elem1 = k1->as_obj_array_klass()->element_klass(); - ciKlass* elem2 = k2->as_obj_array_klass()->element_klass(); - ciKlass* elem = type_meet_internal(elem1, elem2, analyzer)->as_klass(); - // Do an easy shortcut if one type is a super of the other. - if (elem == elem1) { - assert(k1 == ciObjArrayKlass::make(elem), "shortcut is OK"); - return k1; - } else if (elem == elem2) { - assert(k2 == ciObjArrayKlass::make(elem), "shortcut is OK"); - return k2; - } else { - return ciObjArrayKlass::make(elem); - } + } + + // Both types are non-top non-primitive types. That is, + // both types are either instanceKlasses or arrayKlasses. + ciKlass* object_klass = analyzer->env()->Object_klass(); + ciKlass* k1 = t1->as_klass(); + ciKlass* k2 = t2->as_klass(); + if (k1->equals(object_klass) || k2->equals(object_klass)) { + return object_klass; + } else if (!k1->is_loaded() || !k2->is_loaded()) { + // Unloaded classes fall to java.lang.Object at a merge. + return object_klass; + } else if (k1->is_interface() != k2->is_interface()) { + // When an interface meets a non-interface, we get Object; + // This is what the verifier does. + return object_klass; + } else if (k1->is_array_klass() || k2->is_array_klass()) { + // When an array meets a non-array, we get Object. + // When (obj/flat)Array meets typeArray, we also get Object. + // And when typeArray meets different typeArray, we again get Object. + // But when (obj/flat)Array meets (obj/flat)Array, we look carefully at element types. + if ((k1->is_obj_array_klass() || k1->is_flat_array_klass()) && + (k2->is_obj_array_klass() || k2->is_flat_array_klass())) { + ciType* elem1 = k1->as_array_klass()->element_klass(); + ciType* elem2 = k2->as_array_klass()->element_klass(); + ciType* elem = elem1; + if (elem1 != elem2) { + elem = type_meet_internal(elem1, elem2, analyzer)->as_klass(); + } + // Do an easy shortcut if one type is a super of the other. + if (elem == elem1 && !elem->is_inlinetype()) { + assert(k1 == ciArrayKlass::make(elem), "shortcut is OK"); + return k1; + } else if (elem == elem2 && !elem->is_inlinetype()) { + assert(k2 == ciArrayKlass::make(elem), "shortcut is OK"); + return k2; } else { - return object_klass; + return ciArrayKlass::make(elem); } } else { - // Must be two plain old instance klasses. - assert(k1->is_instance_klass(), "previous cases handle non-instances"); - assert(k2->is_instance_klass(), "previous cases handle non-instances"); - return k1->least_common_ancestor(k2); + return object_klass; + } + } else { + // Must be two plain old instance klasses. + assert(k1->is_instance_klass(), "previous cases handle non-instances"); + assert(k2->is_instance_klass(), "previous cases handle non-instances"); + ciType* result = k1->least_common_ancestor(k2); + if (null_free1 && null_free2 && result->is_inlinetype()) { + result = analyzer->mark_as_null_free(result); } + if (is_early_larval) { + result = analyzer->mark_as_early_larval(result); + } + return result; } } @@ -393,7 +416,19 @@ const ciTypeFlow::StateVector* ciTypeFlow::get_start_state() { // "Push" the method signature into the first few locals. state->set_stack_size(-max_locals()); if (!method()->is_static()) { - state->push(method()->holder()); + ciType* holder = method()->holder(); + if (method()->is_object_constructor()) { + if (holder->is_inlinetype() || (holder->is_instance_klass() && !holder->as_instance_klass()->flags().is_identity())) { + // The receiver is early larval (so also null-free) + holder = mark_as_early_larval(holder); + } + } else { + if (holder->is_inlinetype()) { + // The receiver is null-free + holder = mark_as_null_free(holder); + } + } + state->push(holder); assert(state->tos() == state->local(0), ""); } for (ciSignatureStream str(method()->signature()); @@ -543,12 +578,12 @@ void ciTypeFlow::StateVector::push_translate(ciType* type) { } // ------------------------------------------------------------------ -// ciTypeFlow::StateVector::do_aaload -void ciTypeFlow::StateVector::do_aaload(ciBytecodeStream* str) { +// ciTypeFlow::StateVector::do_aload +void ciTypeFlow::StateVector::do_aload(ciBytecodeStream* str) { pop_int(); - ciObjArrayKlass* array_klass = pop_objArray(); + ciArrayKlass* array_klass = pop_objOrFlatArray(); if (array_klass == nullptr) { - // Did aaload on a null reference; push a null and ignore the exception. + // Did aload on a null reference; push a null and ignore the exception. // This instruction will never continue normally. All we have to do // is report a value that will meet correctly with any downstream // reference types on paths that will truly be executed. This null type @@ -566,6 +601,7 @@ void ciTypeFlow::StateVector::do_aaload(ciBytecodeStream* str) { return; } ciKlass* element_klass = array_klass->element_klass(); + // TODO 8350865 Can we check that array_klass is null_free and use mark_as_null_free on the result here? if (!element_klass->is_loaded() && element_klass->is_instance_klass()) { Untested("unloaded array element class in ciTypeFlow"); trap(str, element_klass, @@ -584,14 +620,20 @@ void ciTypeFlow::StateVector::do_checkcast(ciBytecodeStream* str) { bool will_link; ciKlass* klass = str->get_klass(will_link); if (!will_link) { - // VM's interpreter will not load 'klass' if object is null. + // VM's interpreter will not load 'klass' if object is nullptr. // Type flow after this block may still be needed in two situations: // 1) C2 uses do_null_assert() and continues compilation for later blocks // 2) C2 does an OSR compile in a later block (see bug 4778368). pop_object(); do_null_assert(klass); } else { - pop_object(); + ciType* type = pop_value(); + type = type->unwrap(); + if (type->is_loaded() && klass->is_loaded() && + type != klass && type->is_subtype_of(klass)) { + // Useless cast, propagate more precise type of object + klass = type->as_klass(); + } push_object(klass); } } @@ -613,7 +655,16 @@ void ciTypeFlow::StateVector::do_getstatic(ciBytecodeStream* str) { trap(str, field->holder(), str->get_field_holder_index()); } else { ciType* field_type = field->type(); - if (!field_type->is_loaded()) { + if (field->is_static() && field->is_null_free() && + !field_type->as_instance_klass()->is_initialized()) { + // Deoptimize if we load from a static field with an uninitialized inline type + // because we need to throw an exception if initialization of the type failed. + trap(str, field_type->as_klass(), + Deoptimization::make_trap_request + (Deoptimization::Reason_unloaded, + Deoptimization::Action_reinterpret)); + return; + } else if (!field_type->is_loaded()) { // Normally, we need the field's type to be loaded if we are to // do anything interesting with its value. // We used to do this: trap(str, str->get_field_signature_index()); @@ -634,6 +685,9 @@ void ciTypeFlow::StateVector::do_getstatic(ciBytecodeStream* str) { // (See bug 4379915.) do_null_assert(field_type->as_klass()); } else { + if (field->is_null_free()) { + field_type = outer()->mark_as_null_free(field_type); + } push_translate(field_type); } } @@ -681,7 +735,17 @@ void ciTypeFlow::StateVector::do_invoke(ciBytecodeStream* str, pop(); } if (has_receiver) { - // Check this? + if (type_at_tos()->is_early_larval()) { + // Call with larval receiver accepted by verifier + // => this is and the receiver is no longer larval after that. + Cell limit = limit_cell(); + for (Cell c = start_cell(); c < limit; c = next_cell(c)) { + if (type_at(c)->ident() == type_at_tos()->ident()) { + assert(type_at(c) == type_at_tos(), "Sin! Abomination!"); + set_type_at(c, type_at_tos()->unwrap()); + } + } + } pop_object(); } assert(!sigstr.is_done(), "must have return type"); @@ -737,7 +801,11 @@ void ciTypeFlow::StateVector::do_ldc(ciBytecodeStream* str) { push_null(); } else { assert(obj->is_instance() || obj->is_array(), "must be java_mirror of klass"); - push_object(obj->klass()); + ciType* type = obj->klass(); + if (type->is_inlinetype()) { + type = outer()->mark_as_null_free(type); + } + push(type); } } else { assert(basic_type == con.basic_type() || con.basic_type() == T_OBJECT, @@ -775,6 +843,10 @@ void ciTypeFlow::StateVector::do_new(ciBytecodeStream* str) { if (!will_link || str->is_unresolved_klass()) { trap(str, klass, str->get_klass_index()); } else { + if (klass->is_inlinetype()) { + push(outer()->mark_as_early_larval(klass)); + return; + } push_object(klass); } } @@ -884,13 +956,13 @@ bool ciTypeFlow::StateVector::apply_one_bytecode(ciBytecodeStream* str) { } switch(str->cur_bc()) { - case Bytecodes::_aaload: do_aaload(str); break; + case Bytecodes::_aaload: do_aload(str); break; case Bytecodes::_aastore: { pop_object(); pop_int(); - pop_objArray(); + pop_objOrFlatArray(); break; } case Bytecodes::_aconst_null: @@ -912,7 +984,7 @@ bool ciTypeFlow::StateVector::apply_one_bytecode(ciBytecodeStream* str) { if (!will_link) { trap(str, element_klass, str->get_klass_index()); } else { - push_object(ciObjArrayKlass::make(element_klass)); + push_object(ciArrayKlass::make(element_klass)); } break; } @@ -1471,6 +1543,7 @@ bool ciTypeFlow::StateVector::apply_one_bytecode(ciBytecodeStream* str) { push(value2); break; } + case Bytecodes::_wide: default: { @@ -1491,7 +1564,7 @@ bool ciTypeFlow::StateVector::apply_one_bytecode(ciBytecodeStream* str) { // ------------------------------------------------------------------ // ciTypeFlow::StateVector::print_cell_on void ciTypeFlow::StateVector::print_cell_on(outputStream* st, Cell c) const { - ciType* type = type_at(c); + ciType* type = type_at(c)->unwrap(); if (type == top_type()) { st->print("top"); } else if (type == bottom_type()) { @@ -1754,9 +1827,12 @@ ciTypeFlow::Block::successors(ciBytecodeStream* str, break; } - case Bytecodes::_athrow: case Bytecodes::_ireturn: - case Bytecodes::_lreturn: case Bytecodes::_freturn: - case Bytecodes::_dreturn: case Bytecodes::_areturn: + case Bytecodes::_athrow: + case Bytecodes::_ireturn: + case Bytecodes::_lreturn: + case Bytecodes::_freturn: + case Bytecodes::_dreturn: + case Bytecodes::_areturn: case Bytecodes::_return: _successors = new (arena) GrowableArray(arena, 1, 0, nullptr); @@ -3149,6 +3225,17 @@ void ciTypeFlow::record_failure(const char* reason) { } } +ciType* ciTypeFlow::mark_as_early_larval(ciType* type) { + // Wrap the type to carry the information that it is null-free + return env()->make_early_larval_wrapper(type); +} + + +ciType* ciTypeFlow::mark_as_null_free(ciType* type) { + // Wrap the type to carry the information that it is null-free + return env()->make_null_free_wrapper(type); +} + #ifndef PRODUCT void ciTypeFlow::print() const { print_on(tty); } diff --git a/src/hotspot/share/ci/ciTypeFlow.hpp b/src/hotspot/share/ci/ciTypeFlow.hpp index adfb85dc17f..724913a0ddc 100644 --- a/src/hotspot/share/ci/ciTypeFlow.hpp +++ b/src/hotspot/share/ci/ciTypeFlow.hpp @@ -334,14 +334,15 @@ class ciTypeFlow : public ArenaObj { type_at_tos()->is_array_klass(), "must be array type"); pop(); } - // pop_objArray and pop_typeArray narrow the tos to ciObjArrayKlass - // or ciTypeArrayKlass (resp.). In the rare case that an explicit + // pop_objOrFlatArray and pop_typeArray narrow the tos to ciObjArrayKlass, + // ciFlatArrayKlass or ciTypeArrayKlass (resp.). In the rare case that an explicit // null is popped from the stack, we return null. Caller beware. - ciObjArrayKlass* pop_objArray() { + ciArrayKlass* pop_objOrFlatArray() { ciType* array = pop_value(); if (array == null_type()) return nullptr; - assert(array->is_obj_array_klass(), "must be object array type"); - return array->as_obj_array_klass(); + assert(array->is_obj_array_klass() || array->is_flat_array_klass(), + "must be a flat or an object array type"); + return array->as_array_klass(); } ciTypeArrayKlass* pop_typeArray() { ciType* array = pop_value(); @@ -355,7 +356,7 @@ class ciTypeFlow : public ArenaObj { void do_null_assert(ciKlass* unloaded_klass); // Helper convenience routines. - void do_aaload(ciBytecodeStream* str); + void do_aload(ciBytecodeStream* str); void do_checkcast(ciBytecodeStream* str); void do_getfield(ciBytecodeStream* str); void do_getstatic(ciBytecodeStream* str); @@ -852,6 +853,9 @@ class ciTypeFlow : public ArenaObj { return _block_map[rpo]; } int inc_next_pre_order() { return _next_pre_order++; } + ciType* mark_as_early_larval(ciType* type); + ciType* mark_as_null_free(ciType* type); + private: // A work list used during flow analysis. Block* _work_list; diff --git a/src/hotspot/share/classfile/classFileParser.cpp b/src/hotspot/share/classfile/classFileParser.cpp index 6c019f7c612..42d91b2cd42 100644 --- a/src/hotspot/share/classfile/classFileParser.cpp +++ b/src/hotspot/share/classfile/classFileParser.cpp @@ -21,6 +21,7 @@ * questions. * */ + #include "cds/cdsConfig.hpp" #include "classfile/classFileParser.hpp" #include "classfile/classFileStream.hpp" @@ -50,6 +51,7 @@ #include "oops/constantPool.inline.hpp" #include "oops/fieldInfo.hpp" #include "oops/fieldStreams.inline.hpp" +#include "oops/inlineKlass.inline.hpp" #include "oops/instanceKlass.inline.hpp" #include "oops/instanceMirrorKlass.hpp" #include "oops/klass.inline.hpp" @@ -84,6 +86,7 @@ #include "utilities/hashTable.hpp" #include "utilities/macros.hpp" #include "utilities/ostream.hpp" +#include "utilities/stringUtils.hpp" #include "utilities/utf8.hpp" #if INCLUDE_CDS #include "classfile/systemDictionaryShared.hpp" @@ -156,6 +159,8 @@ #define JAVA_26_VERSION 70 +#define CONSTANT_CLASS_DESCRIPTORS 70 + void ClassFileParser::set_class_bad_constant_seen(short bad_constant) { assert((bad_constant == JVM_CONSTANT_Module || bad_constant == JVM_CONSTANT_Package) && _major_version >= JAVA_9_VERSION, @@ -194,7 +199,7 @@ void ClassFileParser::parse_constant_pool_entries(const ClassFileStream* const s // so we don't need bounds-check for reading tag. const u1 tag = cfs->get_u1_fast(); switch (tag) { - case JVM_CONSTANT_Class : { + case JVM_CONSTANT_Class: { cfs->guarantee_more(3, CHECK); // name_index, tag/access_flags const u2 name_index = cfs->get_u2_fast(); cp->klass_index_at_put(index, name_index); @@ -498,6 +503,9 @@ void ClassFileParser::parse_constant_pool(const ClassFileStream* const stream, guarantee_property(valid_symbol_at(class_index), "Invalid constant pool index %u in class file %s", class_index, CHECK); + + Symbol* const name = cp->symbol_at(class_index); + const unsigned int name_len = name->utf8_length(); cp->unresolved_klass_at_put(index, class_index, num_klasses++); break; } @@ -707,7 +715,7 @@ void ClassFileParser::parse_constant_pool(const ClassFileStream* const stream, "Bad method name at constant pool index %u in class file %s", name_ref_index, THREAD); return; - } else if (!Signature::is_void_method(signature)) { // must have void signature. + } else if (!Signature::is_void_method(signature)) { // must have void signature. throwIllegalSignature("Method", name, signature, CHECK); } } @@ -727,15 +735,24 @@ void ClassFileParser::parse_constant_pool(const ClassFileStream* const stream, const int name_ref_index = cp->name_ref_index_at(name_and_type_ref_index); const Symbol* const name = cp->symbol_at(name_ref_index); - if (ref_kind == JVM_REF_newInvokeSpecial) { - if (name != vmSymbols::object_initializer_name()) { + + if (name != vmSymbols::object_initializer_name()) { // ! + if (ref_kind == JVM_REF_newInvokeSpecial) { classfile_parse_error( "Bad constructor name at constant pool index %u in class file %s", name_ref_index, THREAD); return; } - } else { - if (name == vmSymbols::object_initializer_name()) { + } else { // + // The allowed invocation mode of depends on its signature. + // This test corresponds to verify_invoke_instructions in the verifier. + const int signature_ref_index = + cp->signature_ref_index_at(name_and_type_ref_index); + const Symbol* const signature = cp->symbol_at(signature_ref_index); + if (signature->is_void_method_signature() + && ref_kind == JVM_REF_newInvokeSpecial) { + // OK, could be a constructor call + } else { classfile_parse_error( "Bad method name at constant pool index %u in class file %s", name_ref_index, THREAD); @@ -787,11 +804,26 @@ using NameSigHashtable = HashTable; -// Side-effects: populates the _local_interfaces field -void ClassFileParser::parse_interfaces(const ClassFileStream* const stream, - const int itfs_len, - ConstantPool* const cp, +static void check_identity_and_value_modifiers(ClassFileParser* current, const InstanceKlass* super_type, TRAPS) { + assert(super_type != nullptr,"Method doesn't support null super type"); + if (super_type->access_flags().is_identity_class() && !current->access_flags().is_identity_class() + && super_type->name() != vmSymbols::java_lang_Object()) { + THROW_MSG(vmSymbols::java_lang_IncompatibleClassChangeError(), + err_msg("Value type %s has an identity type as supertype", + current->class_name()->as_klass_external_name())); + } +} + +void ClassFileParser::parse_interfaces(const ClassFileStream* stream, + int itfs_len, + ConstantPool* cp, bool* const has_nonstatic_concrete_methods, + // FIXME: lots of these functions + // declare their parameters as const, + // which adds only noise to the code. + // Remove the spurious const modifiers. + // Many are of the form "const int x" + // or "T* const x". TRAPS) { assert(stream != nullptr, "invariant"); assert(cp != nullptr, "invariant"); @@ -799,47 +831,18 @@ void ClassFileParser::parse_interfaces(const ClassFileStream* const stream, if (itfs_len == 0) { _local_interfaces = Universe::the_empty_instance_klass_array(); + } else { assert(itfs_len > 0, "only called for len>0"); - _local_interfaces = MetadataFactory::new_array(_loader_data, itfs_len, nullptr, CHECK); - - int index; + _local_interface_indexes = new GrowableArray(itfs_len); + int index = 0; for (index = 0; index < itfs_len; index++) { const u2 interface_index = stream->get_u2(CHECK); - Klass* interf; guarantee_property( valid_klass_reference_at(interface_index), "Interface name has bad constant pool index %u in class file %s", interface_index, CHECK); - if (cp->tag_at(interface_index).is_klass()) { - interf = cp->resolved_klass_at(interface_index); - } else { - Symbol* const unresolved_klass = cp->klass_name_at(interface_index); - - // Don't need to check legal name because it's checked when parsing constant pool. - // But need to make sure it's not an array type. - guarantee_property(unresolved_klass->char_at(0) != JVM_SIGNATURE_ARRAY, - "Bad interface name in class file %s", CHECK); - - // Call resolve on the interface class name with class circularity checking - interf = SystemDictionary::resolve_super_or_fail(_class_name, - unresolved_klass, - Handle(THREAD, _loader_data->class_loader()), - false, CHECK); - } - - if (!interf->is_interface()) { - THROW_MSG(vmSymbols::java_lang_IncompatibleClassChangeError(), - err_msg("class %s can not implement %s, because it is not an interface (%s)", - _class_name->as_klass_external_name(), - interf->external_name(), - interf->class_in_module_of_loader())); - } - - if (InstanceKlass::cast(interf)->has_nonstatic_concrete_methods()) { - *has_nonstatic_concrete_methods = true; - } - _local_interfaces->at_put(index, InstanceKlass::cast(interf)); + _local_interface_indexes->at_put_grow(index, interface_index); } if (!_need_verify || itfs_len <= 1) { @@ -851,8 +854,7 @@ void ClassFileParser::parse_interfaces(const ClassFileStream* const stream, // Set containing interface names HashTable* interface_names = new HashTable(); for (index = 0; index < itfs_len; index++) { - const InstanceKlass* const k = _local_interfaces->at(index); - Symbol* interface_name = k->name(); + Symbol* interface_name = cp->klass_name_at(_local_interface_indexes->at(index)); // If no duplicates, add (name, nullptr) in hashtable interface_names. if (!interface_names->put(interface_name, 0)) { classfile_parse_error("Duplicate interface name \"%s\" in class file %s", @@ -939,6 +941,8 @@ class AnnotationCollector : public ResourceObj{ _field_Stable, _jdk_internal_vm_annotation_ReservedStackAccess, _jdk_internal_ValueBased, + _jdk_internal_LooselyConsistentValue, + _jdk_internal_NullRestricted, _java_lang_Deprecated, _java_lang_Deprecated_for_removal, _jdk_internal_vm_annotation_AOTSafeClassInitializer, @@ -1365,7 +1369,7 @@ void ClassFileParser::parse_field_attributes(const ClassFileStream* const cfs, // Side-effects: populates the _fields, _fields_annotations, // _fields_type_annotations fields void ClassFileParser::parse_fields(const ClassFileStream* const cfs, - bool is_interface, + AccessFlags class_access_flags, ConstantPool* cp, const int cp_size, u2* const java_fields_count_ptr, @@ -1378,6 +1382,7 @@ void ClassFileParser::parse_fields(const ClassFileStream* const cfs, assert(nullptr == _fields_annotations, "invariant"); assert(nullptr == _fields_type_annotations, "invariant"); + bool is_inline_type = !class_access_flags.is_identity_class() && !class_access_flags.is_abstract(); cfs->guarantee_more(2, CHECK); // length const u2 length = cfs->get_u2_fast(); *java_fields_count_ptr = length; @@ -1385,20 +1390,30 @@ void ClassFileParser::parse_fields(const ClassFileStream* const cfs, int num_injected = 0; const InjectedField* const injected = JavaClasses::get_injected(_class_name, &num_injected); - const int total_fields = length + num_injected; + + // two more slots are required for inline classes: + // one for the static field with a reference to the pre-allocated default value + // one for the field the JVM injects when detecting an empty inline class + const int total_fields = length + num_injected + (is_inline_type ? 2 : 0); // Allocate a temporary resource array to collect field data. // After parsing all fields, data are stored in a UNSIGNED5 compressed stream. _temp_field_info = new GrowableArray(total_fields); + int instance_fields_count = 0; ResourceMark rm(THREAD); for (int n = 0; n < length; n++) { // access_flags, name_index, descriptor_index, attributes_count cfs->guarantee_more(8, CHECK); + jint recognized_modifiers = JVM_RECOGNIZED_FIELD_MODIFIERS; + if (!supports_inline_types()) { + recognized_modifiers &= ~JVM_ACC_STRICT; + } + + const jint flags = cfs->get_u2_fast() & recognized_modifiers; + verify_legal_field_modifiers(flags, class_access_flags, CHECK); AccessFlags access_flags; - const jint flags = cfs->get_u2_fast() & JVM_RECOGNIZED_FIELD_MODIFIERS; - verify_legal_field_modifiers(flags, is_interface, CHECK); access_flags.set_flags(flags); FieldInfo::FieldFlags fieldFlags(0); @@ -1415,6 +1430,7 @@ void ClassFileParser::parse_fields(const ClassFileStream* const cfs, signature_index, CHECK); const Symbol* const sig = cp->symbol_at(signature_index); verify_legal_field_signature(name, sig, CHECK); + if (!access_flags.is_static()) instance_fields_count++; u2 constantvalue_index = 0; bool is_synthetic = false; @@ -1422,6 +1438,8 @@ void ClassFileParser::parse_fields(const ClassFileStream* const cfs, const bool is_static = access_flags.is_static(); FieldAnnotationCollector parsed_annotations(_loader_data); + bool is_null_restricted = false; + const u2 attributes_count = cfs->get_u2_fast(); if (attributes_count > 0) { parse_field_attributes(cfs, @@ -1441,6 +1459,24 @@ void ClassFileParser::parse_fields(const ClassFileStream* const cfs, CHECK); } _fields_annotations->at_put(n, parsed_annotations.field_annotations()); + if (parsed_annotations.has_annotation(AnnotationCollector::_jdk_internal_NullRestricted)) { + if (!Signature::has_envelope(sig)) { + Exceptions::fthrow( + THREAD_AND_LOCATION, + vmSymbols::java_lang_ClassFormatError(), + "Illegal use of @jdk.internal.vm.annotation.NullRestricted annotation on field %s.%s with signature %s (primitive types can never be null)", + class_name()->as_C_string(), name->as_C_string(), sig->as_C_string()); + } + const bool is_strict = (flags & JVM_ACC_STRICT) != 0; + if (!is_strict) { + Exceptions::fthrow( + THREAD_AND_LOCATION, + vmSymbols::java_lang_ClassFormatError(), + "Illegal use of @jdk.internal.vm.annotation.NullRestricted annotation on field %s.%s which doesn't have the @jdk.internal.vm.annotation.Strict annotation", + class_name()->as_C_string(), name->as_C_string()); + } + is_null_restricted = true; + } parsed_annotations.set_field_annotations(nullptr); } if (parsed_annotations.field_type_annotations() != nullptr) { @@ -1463,6 +1499,10 @@ void ClassFileParser::parse_fields(const ClassFileStream* const cfs, } } + if (is_null_restricted) { + fieldFlags.update_null_free_inline_type(true); + } + const BasicType type = cp->basic_type_for_signature_at(signature_index); // Update number of static oop fields. @@ -1479,11 +1519,13 @@ void ClassFileParser::parse_fields(const ClassFileStream* const cfs, if (fi.field_flags().is_contended()) { _has_contended_fields = true; } + if (access_flags.is_strict() && access_flags.is_static()) { + _has_strict_static_fields = true; + } _temp_field_info->append(fi); } assert(_temp_field_info->length() == length, "Must be"); - int index = length; if (num_injected != 0) { for (int n = 0; n < num_injected; n++) { // Check for duplicates @@ -1511,13 +1553,28 @@ void ClassFileParser::parse_fields(const ClassFileStream* const cfs, fflags.update_injected(true); AccessFlags aflags; FieldInfo fi(aflags, (u2)(injected[n].name_index), (u2)(injected[n].signature_index), 0, fflags); - fi.set_index(index); - _temp_field_info->append(fi); - index++; + int idx = _temp_field_info->append(fi); + _temp_field_info->adr_at(idx)->set_index(idx); } } - assert(_temp_field_info->length() == index, "Must be"); + if (is_inline_type) { + // Inject static ".null_reset" field. This is an all-zero value with its null-channel set to zero. + // IT should never be seen by user code, it is used when writing "null" to a nullable flat field + // The all-zero value ensure that any embedded oop will be set to null, to avoid keeping dead objects + // alive. + FieldInfo::FieldFlags fflags2(0); + fflags2.update_injected(true); + AccessFlags aflags2(JVM_ACC_STATIC); + FieldInfo fi2(aflags2, + (u2)vmSymbols::as_int(VM_SYMBOL_ENUM_NAME(null_reset_value_name)), + (u2)vmSymbols::as_int(VM_SYMBOL_ENUM_NAME(object_signature)), + 0, + fflags2); + int idx2 = _temp_field_info->append(fi2); + _temp_field_info->adr_at(idx2)->set_index(idx2); + _static_oop_count++; + } if (_need_verify && length > 1) { // Check duplicated fields @@ -1897,6 +1954,14 @@ AnnotationCollector::annotation_index(const ClassLoaderData* loader_data, if (!privileged) break; // only allow in privileged code return _jdk_internal_ValueBased; } + case VM_SYMBOL_ENUM_NAME(jdk_internal_vm_annotation_LooselyConsistentValue_signature): { + if (_location != _in_class) break; // only allow for classes + return _jdk_internal_LooselyConsistentValue; + } + case VM_SYMBOL_ENUM_NAME(jdk_internal_vm_annotation_NullRestricted_signature): { + if (_location != _in_field) break; // only allow for fields + return _jdk_internal_NullRestricted; + } case VM_SYMBOL_ENUM_NAME(java_lang_Deprecated): { return _java_lang_Deprecated; } @@ -2133,6 +2198,8 @@ void ClassFileParser::copy_method_annotations(ConstMethod* cm, Method* ClassFileParser::parse_method(const ClassFileStream* const cfs, bool is_interface, + bool is_value_class, + bool is_abstract_class, const ConstantPool* cp, bool* const has_localvariable_table, TRAPS) { @@ -2174,7 +2241,7 @@ Method* ClassFileParser::parse_method(const ClassFileStream* const cfs, return nullptr; } } else { - verify_legal_method_modifiers(flags, is_interface, name, CHECK_NULL); + verify_legal_method_modifiers(flags, access_flags() , name, CHECK_NULL); } if (name == vmSymbols::object_initializer_name() && is_interface) { @@ -2182,6 +2249,15 @@ Method* ClassFileParser::parse_method(const ClassFileStream* const cfs, return nullptr; } + if (EnableValhalla) { + if (((flags & JVM_ACC_SYNCHRONIZED) == JVM_ACC_SYNCHRONIZED) + && ((flags & JVM_ACC_STATIC) == 0 ) + && !_access_flags.is_identity_class()) { + classfile_parse_error("Invalid synchronized method in non-identity class %s", THREAD); + return nullptr; + } + } + int args_size = -1; // only used when _need_verify is true if (_need_verify) { verify_legal_name_with_signature(name, signature, CHECK_NULL); @@ -2716,6 +2792,8 @@ Method* ClassFileParser::parse_method(const ClassFileStream* const cfs, // Side-effects: populates the _methods field in the parser void ClassFileParser::parse_methods(const ClassFileStream* const cfs, bool is_interface, + bool is_value_class, + bool is_abstract_type, bool* const has_localvariable_table, bool* has_final_method, bool* declares_nonstatic_concrete_methods, @@ -2740,6 +2818,8 @@ void ClassFileParser::parse_methods(const ClassFileStream* const cfs, for (int index = 0; index < length; index++) { Method* method = parse_method(cfs, is_interface, + is_value_class, + is_abstract_type, _cp, has_localvariable_table, CHECK); @@ -3005,6 +3085,7 @@ u2 ClassFileParser::parse_classfile_inner_classes_attribute(const ClassFileStrea guarantee_property(inner_class_info_index != outer_class_info_index, "Class is both outer and inner class in class file %s", CHECK_0); } + // Access flags u2 flags; // JVM_ACC_MODULE is defined in JDK-9 and later. @@ -3013,10 +3094,20 @@ u2 ClassFileParser::parse_classfile_inner_classes_attribute(const ClassFileStrea } else { flags = cfs->get_u2_fast() & RECOGNIZED_INNER_CLASS_MODIFIERS; } + if ((flags & JVM_ACC_INTERFACE) && _major_version < JAVA_6_VERSION) { // Set abstract bit for old class files for backward compatibility flags |= JVM_ACC_ABSTRACT; } + + if (!supports_inline_types()) { + const bool is_module = (flags & JVM_ACC_MODULE) != 0; + const bool is_interface = (flags & JVM_ACC_INTERFACE) != 0; + if (!is_module && !is_interface) { + flags |= JVM_ACC_IDENTITY; + } + } + verify_legal_class_modifiers(flags, CHECK_0); AccessFlags inner_access_flags(flags); @@ -3121,6 +3212,49 @@ u2 ClassFileParser::parse_classfile_permitted_subclasses_attribute(const ClassFi return length; } +u2 ClassFileParser::parse_classfile_loadable_descriptors_attribute(const ClassFileStream* const cfs, + const u1* const loadable_descriptors_attribute_start, + TRAPS) { + const u1* const current_mark = cfs->current(); + u2 length = 0; + if (loadable_descriptors_attribute_start != nullptr) { + cfs->set_current(loadable_descriptors_attribute_start); + cfs->guarantee_more(2, CHECK_0); // length + length = cfs->get_u2_fast(); + } + const int size = length; + Array* const loadable_descriptors = MetadataFactory::new_array(_loader_data, size, CHECK_0); + _loadable_descriptors = loadable_descriptors; + if (length > 0) { + int index = 0; + cfs->guarantee_more(2 * length, CHECK_0); + for (int n = 0; n < length; n++) { + const u2 descriptor_index = cfs->get_u2_fast(); + guarantee_property( + valid_symbol_at(descriptor_index), + "LoadableDescriptors descriptor_index %u has bad constant type in class file %s", + descriptor_index, CHECK_0); + Symbol* descriptor = _cp->symbol_at(descriptor_index); + bool valid = legal_field_signature(descriptor, CHECK_0); + if(!valid) { + ResourceMark rm(THREAD); + Exceptions::fthrow(THREAD_AND_LOCATION, + vmSymbols::java_lang_ClassFormatError(), + "Descriptor from LoadableDescriptors attribute at index \"%d\" in class %s has illegal signature \"%s\"", + descriptor_index, _class_name->as_C_string(), descriptor->as_C_string()); + return 0; + } + loadable_descriptors->at_put(index++, descriptor_index); + } + assert(index == size, "wrong size"); + } + + // Restore buffer's current position. + cfs->set_current(current_mark); + + return length; +} + // Record { // u2 attribute_name_index; // u4 attribute_length; @@ -3386,12 +3520,15 @@ void ClassFileParser::parse_classfile_attributes(const ClassFileStream* const cf _nest_members = Universe::the_empty_short_array(); // Set _permitted_subclasses attribute to default sentinel _permitted_subclasses = Universe::the_empty_short_array(); + // Set _loadable_descriptors attribute to default sentinel + _loadable_descriptors = Universe::the_empty_short_array(); cfs->guarantee_more(2, CHECK); // attributes_count u2 attributes_count = cfs->get_u2_fast(); bool parsed_sourcefile_attribute = false; bool parsed_innerclasses_attribute = false; bool parsed_nest_members_attribute = false; bool parsed_permitted_subclasses_attribute = false; + bool parsed_loadable_descriptors_attribute = false; bool parsed_nest_host_attribute = false; bool parsed_record_attribute = false; bool parsed_enclosingmethod_attribute = false; @@ -3413,6 +3550,8 @@ void ClassFileParser::parse_classfile_attributes(const ClassFileStream* const cf u4 record_attribute_length = 0; const u1* permitted_subclasses_attribute_start = nullptr; u4 permitted_subclasses_attribute_length = 0; + const u1* loadable_descriptors_attribute_start = nullptr; + u4 loadable_descriptors_attribute_length = 0; // Iterate over attributes while (attributes_count--) { @@ -3628,6 +3767,15 @@ void ClassFileParser::parse_classfile_attributes(const ClassFileStream* const cf permitted_subclasses_attribute_start = cfs->current(); permitted_subclasses_attribute_length = attribute_length; } + if (EnableValhalla && tag == vmSymbols::tag_loadable_descriptors()) { + if (parsed_loadable_descriptors_attribute) { + classfile_parse_error("Multiple LoadableDescriptors attributes in class file %s", CHECK); + return; + } + parsed_loadable_descriptors_attribute = true; + loadable_descriptors_attribute_start = cfs->current(); + loadable_descriptors_attribute_length = attribute_length; + } } // Skip attribute_length for any attribute where major_verson >= JAVA_17_VERSION cfs->skip_u1(attribute_length, CHECK); @@ -3704,6 +3852,18 @@ void ClassFileParser::parse_classfile_attributes(const ClassFileStream* const cf } } + if (parsed_loadable_descriptors_attribute) { + const u2 num_classes = parse_classfile_loadable_descriptors_attribute( + cfs, + loadable_descriptors_attribute_start, + CHECK); + if (_need_verify) { + guarantee_property( + loadable_descriptors_attribute_length == sizeof(num_classes) + sizeof(u2) * num_classes, + "Wrong LoadableDescriptors attribute length in class file %s", CHECK); + } + } + if (_max_bootstrap_specifier_index >= 0) { guarantee_property(parsed_bootstrap_methods_attribute, "Missing BootstrapMethods attribute in class file %s", CHECK); @@ -3770,9 +3930,11 @@ void ClassFileParser::apply_parsed_class_metadata( this_klass->set_inner_classes(_inner_classes); this_klass->set_nest_members(_nest_members); this_klass->set_nest_host_index(_nest_host); + this_klass->set_loadable_descriptors(_loadable_descriptors); this_klass->set_annotations(_combined_annotations); this_klass->set_permitted_subclasses(_permitted_subclasses); this_klass->set_record_components(_record_components); + this_klass->set_inline_layout_info_array(_inline_layout_info_array); DEBUG_ONLY(FieldInfoStream::validate_search_table(_cp, _fieldinfo_stream, _fieldinfo_search_table)); @@ -3812,9 +3974,8 @@ const InstanceKlass* ClassFileParser::parse_super_class(ConstantPool* const cp, if (super_class_index == 0) { guarantee_property(_class_name == vmSymbols::java_lang_Object(), - "Invalid superclass index %u in class file %s", - super_class_index, - CHECK_NULL); + "Invalid superclass index 0 in class file %s", + CHECK_NULL); } else { guarantee_property(valid_klass_reference_at(super_class_index), "Invalid superclass index %u in class file %s", @@ -3822,15 +3983,11 @@ const InstanceKlass* ClassFileParser::parse_super_class(ConstantPool* const cp, CHECK_NULL); // The class name should be legal because it is checked when parsing constant pool. // However, make sure it is not an array type. - bool is_array = false; if (cp->tag_at(super_class_index).is_klass()) { super_klass = InstanceKlass::cast(cp->resolved_klass_at(super_class_index)); - if (need_verify) - is_array = super_klass->is_array_klass(); - } else if (need_verify) { - is_array = (cp->klass_name_at(super_class_index)->char_at(0) == JVM_SIGNATURE_ARRAY); } if (need_verify) { + bool is_array = (cp->klass_name_at(super_class_index)->char_at(0) == JVM_SIGNATURE_ARRAY); guarantee_property(!is_array, "Bad superclass name in class file %s", CHECK_NULL); } @@ -4013,6 +4170,12 @@ void ClassFileParser::set_precomputed_flags(InstanceKlass* ik) { } } +bool ClassFileParser::supports_inline_types() const { + // Inline types are only supported by class file version 70.65535 and later + return _major_version > JAVA_26_VERSION || + (_major_version == JAVA_26_VERSION && _minor_version == JAVA_PREVIEW_MINOR_VERSION); +} + // utility methods for appending an array with check for duplicates static void append_interfaces(GrowableArray* result, @@ -4056,9 +4219,10 @@ static Array* compute_transitive_interfaces(const InstanceKlass* } else if (max_transitive_size == super_size) { // no new local interfaces added, share superklass' transitive interface array return super->transitive_interfaces(); - } else if (max_transitive_size == local_size) { - // only local interfaces added, share local interface array - return local_ifs; + // The three lines below are commented to work around bug JDK-8245487 +// } else if (max_transitive_size == local_size) { +// // only local interfaces added, share local interface array +// return local_ifs; } else { ResourceMark rm; GrowableArray* const result = new GrowableArray(max_transitive_size); @@ -4079,6 +4243,7 @@ static Array* compute_transitive_interfaces(const InstanceKlass* // length will be less than the max_transitive_size if duplicates were removed const int length = result->length(); assert(length <= max_transitive_size, "just checking"); + Array* const new_result = MetadataFactory::new_array(loader_data, length, CHECK_NULL); for (int i = 0; i < length; i++) { @@ -4109,6 +4274,16 @@ void ClassFileParser::check_super_class_access(const InstanceKlass* this_klass, } } + // The JVMS says that super classes for value types must not have the ACC_IDENTITY + // flag set. But, java.lang.Object must still be allowed to be a direct super class + // for a value classes. So, it is treated as a special case for now. + if (!this_klass->access_flags().is_identity_class() && + super->name() != vmSymbols::java_lang_Object() && + super->is_identity_class()) { + classfile_icce_error("value class %s cannot inherit from class %s", super, THREAD); + return; + } + Reflection::VerifyClassAccessResults vca_result = Reflection::verify_class_access(this_klass, super, false); if (vca_result != Reflection::ACCESS_OK) { @@ -4300,22 +4475,28 @@ void ClassFileParser::verify_legal_class_modifiers(jint flags, TRAPS) const { const bool is_interface = (flags & JVM_ACC_INTERFACE) != 0; const bool is_abstract = (flags & JVM_ACC_ABSTRACT) != 0; const bool is_final = (flags & JVM_ACC_FINAL) != 0; - const bool is_super = (flags & JVM_ACC_SUPER) != 0; + const bool is_identity = (flags & JVM_ACC_IDENTITY) != 0; const bool is_enum = (flags & JVM_ACC_ENUM) != 0; const bool is_annotation = (flags & JVM_ACC_ANNOTATION) != 0; const bool major_gte_1_5 = _major_version >= JAVA_1_5_VERSION; + const bool valid_value_class = is_identity || is_interface || + (supports_inline_types() && (!is_identity && (is_abstract || is_final))); if ((is_abstract && is_final) || (is_interface && !is_abstract) || - (is_interface && major_gte_1_5 && (is_super || is_enum)) || - (!is_interface && major_gte_1_5 && is_annotation)) { + (is_interface && major_gte_1_5 && (is_identity || is_enum)) || // ACC_SUPER (now ACC_IDENTITY) was illegal for interfaces + (!is_interface && major_gte_1_5 && is_annotation) || + (!valid_value_class)) { ResourceMark rm(THREAD); - // Names are all known to be < 64k so we know this formatted message is not excessively large. + const char* class_note = ""; + if (!valid_value_class) { + class_note = " (a value class must be final or else abstract)"; + } Exceptions::fthrow( THREAD_AND_LOCATION, vmSymbols::java_lang_ClassFormatError(), - "Illegal class modifiers in class %s: 0x%X", - _class_name->as_C_string(), flags + "Illegal class modifiers in class %s%s: 0x%X", + _class_name->as_C_string(), class_note, flags ); return; } @@ -4385,8 +4566,8 @@ void ClassFileParser::verify_class_version(u2 major, u2 minor, Symbol* class_nam } } -void ClassFileParser::verify_legal_field_modifiers(jint flags, - bool is_interface, +void ClassFileParser:: verify_legal_field_modifiers(jint flags, + AccessFlags class_access_flags, TRAPS) const { if (!_need_verify) { return; } @@ -4398,19 +4579,42 @@ void ClassFileParser::verify_legal_field_modifiers(jint flags, const bool is_volatile = (flags & JVM_ACC_VOLATILE) != 0; const bool is_transient = (flags & JVM_ACC_TRANSIENT) != 0; const bool is_enum = (flags & JVM_ACC_ENUM) != 0; + const bool is_strict = (flags & JVM_ACC_STRICT) != 0; const bool major_gte_1_5 = _major_version >= JAVA_1_5_VERSION; - bool is_illegal = false; + const bool is_interface = class_access_flags.is_interface(); + const bool is_identity_class = class_access_flags.is_identity_class(); - if (is_interface) { - if (!is_public || !is_static || !is_final || is_private || - is_protected || is_volatile || is_transient || - (major_gte_1_5 && is_enum)) { - is_illegal = true; - } - } else { // not interface - if (has_illegal_visibility(flags) || (is_final && is_volatile)) { - is_illegal = true; + bool is_illegal = false; + const char* error_msg = ""; + + // There is some overlap in the checks that apply, for example interface fields + // must be static, static fields can't be strict, and therefore interfaces can't + // have strict fields. So we don't have to check every possible invalid combination + // individually as long as all are covered. Once we have found an illegal combination + // we can stop checking. + + if (!is_illegal) { + if (is_interface) { + if (!is_public || !is_static || !is_final || is_private || + is_protected || is_volatile || is_transient || + (major_gte_1_5 && is_enum)) { + is_illegal = true; + error_msg = "interface fields must be public, static and final, and may be synthetic"; + } + } else { // not interface + if (has_illegal_visibility(flags)) { + is_illegal = true; + error_msg = "invalid visibility flags for class field"; + } else if (is_final && is_volatile) { + is_illegal = true; + error_msg = "fields cannot be final and volatile"; + } else if (supports_inline_types()) { + if (!is_identity_class && !is_static && (!is_strict || !is_final)) { + is_illegal = true; + error_msg = "value class fields must be either non-static final and strict, or static"; + } + } } } @@ -4420,14 +4624,14 @@ void ClassFileParser::verify_legal_field_modifiers(jint flags, Exceptions::fthrow( THREAD_AND_LOCATION, vmSymbols::java_lang_ClassFormatError(), - "Illegal field modifiers in class %s: 0x%X", - _class_name->as_C_string(), flags); + "Illegal field modifiers (%s) in class %s: 0x%X", + error_msg, _class_name->as_C_string(), flags); return; } } void ClassFileParser::verify_legal_method_modifiers(jint flags, - bool is_interface, + AccessFlags class_access_flags, const Symbol* name, TRAPS) const { if (!_need_verify) { return; } @@ -4446,9 +4650,14 @@ void ClassFileParser::verify_legal_method_modifiers(jint flags, const bool major_gte_8 = _major_version >= JAVA_8_VERSION; const bool major_gte_17 = _major_version >= JAVA_17_VERSION; const bool is_initializer = (name == vmSymbols::object_initializer_name()); + // LW401 CR required: removal of value factories support + const bool is_interface = class_access_flags.is_interface(); + const bool is_identity_class = class_access_flags.is_identity_class(); + const bool is_abstract_class = class_access_flags.is_abstract(); bool is_illegal = false; + const char* class_note = ""; if (is_interface) { if (major_gte_8) { // Class file version is JAVA_8_VERSION or later Methods of @@ -4488,10 +4697,15 @@ void ClassFileParser::verify_legal_method_modifiers(jint flags, is_illegal = true; } } else { // not initializer - if (is_abstract) { - if ((is_final || is_native || is_private || is_static || - (major_gte_1_5 && (is_synchronized || (!major_gte_17 && is_strict))))) { - is_illegal = true; + if (!is_identity_class && is_synchronized && !is_static) { + is_illegal = true; + class_note = " (not an identity class)"; + } else { + if (is_abstract) { + if ((is_final || is_native || is_private || is_static || + (major_gte_1_5 && (is_synchronized || (!major_gte_17 && is_strict))))) { + is_illegal = true; + } } } } @@ -4504,8 +4718,9 @@ void ClassFileParser::verify_legal_method_modifiers(jint flags, Exceptions::fthrow( THREAD_AND_LOCATION, vmSymbols::java_lang_ClassFormatError(), - "Method %s in class %s has illegal modifiers: 0x%X", - name->as_C_string(), _class_name->as_C_string(), flags); + "Method %s in class %s%s has illegal modifiers: 0x%X", + name->as_C_string(), _class_name->as_C_string(), + class_note, flags); return; } } @@ -4563,6 +4778,15 @@ bool ClassFileParser::verify_unqualified_name(const char* name, return true; } +bool ClassFileParser::is_class_in_loadable_descriptors_attribute(Symbol *klass) { + if (_loadable_descriptors == nullptr) return false; + for (int i = 0; i < _loadable_descriptors->length(); i++) { + Symbol* class_name = _cp->symbol_at(_loadable_descriptors->at(i)); + if (class_name == klass) return true; + } + return false; +} + // Take pointer to a UTF8 byte string (not NUL-terminated). // Skip over the longest part of the string that could // be taken as a fieldname. Allow non-trailing '/'s if slash_ok is true. @@ -4664,7 +4888,8 @@ const char* ClassFileParser::skip_over_field_signature(const char* signature, case JVM_SIGNATURE_LONG: case JVM_SIGNATURE_DOUBLE: return signature + 1; - case JVM_SIGNATURE_CLASS: { + case JVM_SIGNATURE_CLASS: + { if (_major_version < JAVA_1_5_VERSION) { // Skip over the class name if one is there const char* const p = skip_over_field_name(signature + 1, true, --length); @@ -4675,7 +4900,7 @@ const char* ClassFileParser::skip_over_field_signature(const char* signature, } } else { - // Skip leading 'L' and ignore first appearance of ';' + // Skip leading 'L' or 'Q' and ignore first appearance of ';' signature++; const char* c = (const char*) memchr(signature, JVM_SIGNATURE_ENDCLASS, length - 1); // Format check signature @@ -4731,6 +4956,10 @@ void ClassFileParser::verify_legal_class_name(const Symbol* name, TRAPS) const { p = skip_over_field_name(bytes, true, length); legal = (p != nullptr) && ((p - bytes) == (int)length); } + } else if ((_major_version >= CONSTANT_CLASS_DESCRIPTORS || _class_name->starts_with("jdk/internal/reflect/")) + && bytes[length - 1] == ';' ) { + // Support for L...; descriptors + legal = verify_unqualified_name(bytes + 1, length - 2, LegalClass); } else { // 4900761: relax the constraints based on JSR202 spec // Class names may be drawn from the entire Unicode character set. @@ -4798,7 +5027,8 @@ void ClassFileParser::verify_legal_method_name(const Symbol* name, TRAPS) const if (length > 0) { if (bytes[0] == JVM_SIGNATURE_SPECIAL) { - if (name == vmSymbols::object_initializer_name() || name == vmSymbols::class_initializer_name()) { + if (name == vmSymbols::object_initializer_name() || + name == vmSymbols::class_initializer_name()) { legal = true; } } else if (_major_version < JAVA_1_5_VERSION) { @@ -4825,6 +5055,16 @@ void ClassFileParser::verify_legal_method_name(const Symbol* name, TRAPS) const } } +bool ClassFileParser::legal_field_signature(const Symbol* signature, TRAPS) const { + const char* const bytes = (const char*)signature->bytes(); + const unsigned int length = signature->utf8_length(); + const char* const p = skip_over_field_signature(bytes, false, length, CHECK_false); + + if (p == nullptr || (p - bytes) != (int)length) { + return false; + } + return true; +} // Checks if signature is a legal field signature. void ClassFileParser::verify_legal_field_signature(const Symbol* name, @@ -4860,9 +5100,9 @@ void ClassFileParser::verify_legal_name_with_signature(const Symbol* name, int sig_length = signature->utf8_length(); if (name->utf8_length() > 0 && - name->char_at(0) == JVM_SIGNATURE_SPECIAL && - sig_length > 0 && - signature->char_at(sig_length - 1) != JVM_SIGNATURE_VOID) { + name->char_at(0) == JVM_SIGNATURE_SPECIAL && + sig_length > 0 && + signature->char_at(sig_length - 1) != JVM_SIGNATURE_VOID) { throwIllegalSignature("Method", name, signature, THREAD); } } @@ -4913,18 +5153,18 @@ int ClassFileParser::verify_legal_method_signature(const Symbol* name, } int ClassFileParser::static_field_size() const { - assert(_field_info != nullptr, "invariant"); - return _field_info->_static_field_size; + assert(_layout_info != nullptr, "invariant"); + return _layout_info->_static_field_size; } int ClassFileParser::total_oop_map_count() const { - assert(_field_info != nullptr, "invariant"); - return _field_info->oop_map_blocks->_nonstatic_oop_map_count; + assert(_layout_info != nullptr, "invariant"); + return _layout_info->oop_map_blocks->_nonstatic_oop_map_count; } jint ClassFileParser::layout_size() const { - assert(_field_info != nullptr, "invariant"); - return _field_info->_instance_size; + assert(_layout_info != nullptr, "invariant"); + return _layout_info->_instance_size; } static void check_methods_for_intrinsics(const InstanceKlass* ik, @@ -5039,7 +5279,6 @@ InstanceKlass* ClassFileParser::create_instance_klass(bool changed_by_loadhook, fill_instance_klass(ik, changed_by_loadhook, cl_inst_info, CHECK_NULL); assert(_klass == ik, "invariant"); - return ik; } @@ -5062,25 +5301,38 @@ void ClassFileParser::fill_instance_klass(InstanceKlass* ik, set_klass_to_deallocate(ik); - assert(_field_info != nullptr, "invariant"); - assert(ik->static_field_size() == _field_info->_static_field_size, "sanity"); - assert(ik->nonstatic_oop_map_count() == _field_info->oop_map_blocks->_nonstatic_oop_map_count, + assert(_layout_info != nullptr, "invariant"); + assert(ik->static_field_size() == _layout_info->_static_field_size, "sanity"); + assert(ik->nonstatic_oop_map_count() == _layout_info->oop_map_blocks->_nonstatic_oop_map_count, "sanity"); assert(ik->is_instance_klass(), "sanity"); - assert(ik->size_helper() == _field_info->_instance_size, "sanity"); + assert(ik->size_helper() == _layout_info->_instance_size, "sanity"); // Fill in information already parsed ik->set_should_verify_class(_need_verify); // Not yet: supers are done below to support the new subtype-checking fields - ik->set_nonstatic_field_size(_field_info->_nonstatic_field_size); - ik->set_has_nonstatic_fields(_field_info->_has_nonstatic_fields); + ik->set_nonstatic_field_size(_layout_info->_nonstatic_field_size); + ik->set_has_nonstatic_fields(_layout_info->_has_nonstatic_fields); + ik->set_has_strict_static_fields(_has_strict_static_fields); + + if (_layout_info->_is_naturally_atomic) { + ik->set_is_naturally_atomic(); + } + + if (_layout_info->_must_be_atomic) { + ik->set_must_be_atomic(); + } + ik->set_static_oop_field_count(_static_oop_count); // this transfers ownership of a lot of arrays from // the parser onto the InstanceKlass* apply_parsed_class_metadata(ik, _java_fields_count); + if (ik->is_inline_klass()) { + InlineKlass::cast(ik)->init_fixed_block(); + } // can only set dynamic nest-host after static nest information is set if (cl_inst_info.dynamic_nest_host() != nullptr) { @@ -5095,9 +5347,11 @@ void ClassFileParser::fill_instance_klass(InstanceKlass* ik, assert(nullptr == _methods, "invariant"); assert(nullptr == _inner_classes, "invariant"); assert(nullptr == _nest_members, "invariant"); + assert(nullptr == _loadable_descriptors, "invariant"); assert(nullptr == _combined_annotations, "invariant"); assert(nullptr == _record_components, "invariant"); assert(nullptr == _permitted_subclasses, "invariant"); + assert(nullptr == _inline_layout_info_array, "invariant"); if (_has_localvariable_table) { ik->set_has_localvariable_table(true); @@ -5214,7 +5468,7 @@ void ClassFileParser::fill_instance_klass(InstanceKlass* ik, // Compute transitive closure of interfaces this class implements // Do final class setup - OopMapBlocksBuilder* oop_map_blocks = _field_info->oop_map_blocks; + OopMapBlocksBuilder* oop_map_blocks = _layout_info->oop_map_blocks; if (oop_map_blocks->_nonstatic_oop_map_count > 0) { oop_map_blocks->copy(ik->start_of_nonstatic_oop_maps()); } @@ -5275,6 +5529,21 @@ void ClassFileParser::fill_instance_klass(InstanceKlass* ik, } } + if (is_inline_type()) { + InlineKlass* vk = InlineKlass::cast(ik); + vk->set_payload_alignment(_layout_info->_payload_alignment); + vk->set_payload_offset(_layout_info->_payload_offset); + vk->set_payload_size_in_bytes(_layout_info->_payload_size_in_bytes); + vk->set_non_atomic_size_in_bytes(_layout_info->_non_atomic_size_in_bytes); + vk->set_non_atomic_alignment(_layout_info->_non_atomic_alignment); + vk->set_atomic_size_in_bytes(_layout_info->_atomic_layout_size_in_bytes); + vk->set_nullable_size_in_bytes(_layout_info->_nullable_layout_size_in_bytes); + vk->set_null_marker_offset(_layout_info->_null_marker_offset); + vk->set_null_reset_value_offset(_layout_info->_null_reset_value_offset); + if (_layout_info->_is_empty_inline_klass) vk->set_is_empty_inline_type(); + vk->initialize_calling_convention(CHECK); + } + ClassLoadingService::notify_class_loaded(ik, false /* not shared class */); if (!is_internal()) { @@ -5357,8 +5626,10 @@ ClassFileParser::ClassFileParser(ClassFileStream* stream, _nest_members(nullptr), _nest_host(0), _permitted_subclasses(nullptr), + _loadable_descriptors(nullptr), _record_components(nullptr), _local_interfaces(nullptr), + _local_interface_indexes(nullptr), _transitive_interfaces(nullptr), _combined_annotations(nullptr), _class_annotations(nullptr), @@ -5368,7 +5639,8 @@ ClassFileParser::ClassFileParser(ClassFileStream* stream, _klass(nullptr), _klass_to_deallocate(nullptr), _parsed_annotations(nullptr), - _field_info(nullptr), + _layout_info(nullptr), + _inline_layout_info_array(nullptr), _temp_field_info(nullptr), _method_ordering(nullptr), _all_mirandas(nullptr), @@ -5397,6 +5669,11 @@ ClassFileParser::ClassFileParser(ClassFileStream* stream, _has_final_method(false), _has_contended_fields(false), _has_aot_runtime_setup_method(false), + _has_strict_static_fields(false), + _has_inline_type_fields(false), + _is_naturally_atomic(false), + _must_be_atomic(true), + _has_loosely_consistent_annotation(false), _has_finalizer(false), _has_empty_finalizer(false), _max_bootstrap_specifier_index(-1) { @@ -5434,10 +5711,12 @@ void ClassFileParser::clear_class_metadata() { _inner_classes = nullptr; _nest_members = nullptr; _permitted_subclasses = nullptr; + _loadable_descriptors = nullptr; _combined_annotations = nullptr; _class_annotations = _class_type_annotations = nullptr; _fields_annotations = _fields_type_annotations = nullptr; _record_components = nullptr; + _inline_layout_info_array = nullptr; } // Destructor to clean up @@ -5457,6 +5736,10 @@ ClassFileParser::~ClassFileParser() { MetadataFactory::free_array(_loader_data, _fields_status); } + if (_inline_layout_info_array != nullptr) { + MetadataFactory::free_array(_loader_data, _inline_layout_info_array); + } + if (_methods != nullptr) { // Free methods InstanceKlass::deallocate_methods(_loader_data, _methods); @@ -5479,6 +5762,10 @@ ClassFileParser::~ClassFileParser() { MetadataFactory::free_array(_loader_data, _permitted_subclasses); } + if (_loadable_descriptors != nullptr && _loadable_descriptors != Universe::the_empty_short_array()) { + MetadataFactory::free_array(_loader_data, _loadable_descriptors); + } + // Free interfaces InstanceKlass::deallocate_interfaces(_loader_data, _super_klass, _local_interfaces, _transitive_interfaces); @@ -5577,6 +5864,15 @@ void ClassFileParser::parse_stream(const ClassFileStream* const stream, flags |= JVM_ACC_ABSTRACT; } + // Fixing ACC_SUPER/ACC_IDENTITY for old class files + if (!supports_inline_types()) { + const bool is_module = (flags & JVM_ACC_MODULE) != 0; + const bool is_interface = (flags & JVM_ACC_INTERFACE) != 0; + if (!is_module && !is_interface) { + flags |= JVM_ACC_IDENTITY; + } + } + verify_legal_class_modifiers(flags, CHECK); short bad_constant = class_bad_constant_seen(); @@ -5680,11 +5976,9 @@ void ClassFileParser::parse_stream(const ClassFileStream* const stream, &_has_nonstatic_concrete_methods, CHECK); - assert(_local_interfaces != nullptr, "invariant"); - // Fields (offsets are filled in later) parse_fields(stream, - _access_flags.is_interface(), + _access_flags, cp, cp_size, &_java_fields_count, @@ -5694,7 +5988,9 @@ void ClassFileParser::parse_stream(const ClassFileStream* const stream, // Methods parse_methods(stream, - _access_flags.is_interface(), + is_interface(), + !is_identity_class(), + is_abstract_class(), &_has_localvariable_table, &_has_final_method, &_declares_nonstatic_concrete_methods, @@ -5774,13 +6070,13 @@ void ClassFileParser::post_process_parsed_stream(const ClassFileStream* const st if (_class_name == vmSymbols::java_lang_Object()) { guarantee_property(_local_interfaces == Universe::the_empty_instance_klass_array(), - "java.lang.Object cannot implement an interface in class file %s", - CHECK); + "java.lang.Object cannot implement an interface in class file %s", + CHECK); } // We check super class after class file is parsed and format is checked if (_super_class_index > 0 && nullptr == _super_klass) { Symbol* const super_class_name = cp->klass_name_at(_super_class_index); - if (_access_flags.is_interface()) { + if (is_interface()) { // Before attempting to resolve the superclass, check for class format // errors not checked yet. guarantee_property(super_class_name == vmSymbols::java_lang_Object(), @@ -5801,15 +6097,97 @@ void ClassFileParser::post_process_parsed_stream(const ClassFileStream* const st } if (_super_klass != nullptr) { + if (_super_klass->is_interface()) { + classfile_icce_error("class %s has interface %s as super class", _super_klass, THREAD); + return; + } + + if (_super_klass->is_final()) { + classfile_icce_error("class %s cannot inherit from final class %s", _super_klass, THREAD); + return; + } + + if (EnableValhalla) { + check_identity_and_value_modifiers(this, _super_klass, CHECK); + } + if (_super_klass->has_nonstatic_concrete_methods()) { _has_nonstatic_concrete_methods = true; } + } - if (_super_klass->is_interface()) { - classfile_icce_error("class %s has interface %s as super class", _super_klass, THREAD); - return; + if (_parsed_annotations->has_annotation(AnnotationCollector::_jdk_internal_LooselyConsistentValue) && _access_flags.is_identity_class()) { + THROW_MSG(vmSymbols::java_lang_ClassFormatError(), + err_msg("class %s cannot have annotation jdk.internal.vm.annotation.LooselyConsistentValue, because it is not a value class", + _class_name->as_klass_external_name())); + } + + // Determining is the class allows tearing or not (default is not) + if (EnableValhalla && !_access_flags.is_identity_class()) { + if (_parsed_annotations->has_annotation(ClassAnnotationCollector::_jdk_internal_LooselyConsistentValue) + && (_super_klass == vmClasses::Object_klass() || !_super_klass->must_be_atomic())) { + // Conditions above are not sufficient to determine atomicity requirements, + // the presence of fields with atomic requirements could force the current class to have atomicy requirements too + // Marking as not needing atomicity for now, can be updated when computing the fields layout + // The InstanceKlass must be filled with the value from the FieldLayoutInfo returned by + // the FieldLayoutBuilder, not with this _must_be_atomic field. + _must_be_atomic = false; + } + // Apply VM options override + if (*ForceNonTearable != '\0') { + // Allow a command line switch to force the same atomicity property: + const char* class_name_str = _class_name->as_C_string(); + if (StringUtils::class_list_match(ForceNonTearable, class_name_str)) { + _must_be_atomic = true; + } + } + } + + int itfs_len = _local_interface_indexes == nullptr ? 0 : _local_interface_indexes->length(); + _local_interfaces = MetadataFactory::new_array(_loader_data, itfs_len, nullptr, CHECK); + if (_local_interface_indexes != nullptr) { + for (int i = 0; i < _local_interface_indexes->length(); i++) { + u2 interface_index = _local_interface_indexes->at(i); + Klass* interf; + if (cp->tag_at(interface_index).is_klass()) { + interf = cp->resolved_klass_at(interface_index); + } else { + Symbol* const unresolved_klass = cp->klass_name_at(interface_index); + + // Don't need to check legal name because it's checked when parsing constant pool. + // But need to make sure it's not an array type. + guarantee_property(unresolved_klass->char_at(0) != JVM_SIGNATURE_ARRAY, + "Bad interface name in class file %s", CHECK); + + // Call resolve on the interface class name with class circularity checking + interf = SystemDictionary::resolve_super_or_fail( + _class_name, + unresolved_klass, + Handle(THREAD, _loader_data->class_loader()), + false, + CHECK); + } + + if (!interf->is_interface()) { + THROW_MSG(vmSymbols::java_lang_IncompatibleClassChangeError(), + err_msg("class %s can not implement %s, because it is not an interface (%s)", + _class_name->as_klass_external_name(), + interf->external_name(), + interf->class_in_module_of_loader())); + } + + if (EnableValhalla) { + // Check modifiers and set carries_identity_modifier/carries_value_modifier flags + check_identity_and_value_modifiers(this, InstanceKlass::cast(interf), CHECK); + } + + if (InstanceKlass::cast(interf)->has_nonstatic_concrete_methods()) { + _has_nonstatic_concrete_methods = true; + } + _local_interfaces->at_put(i, InstanceKlass::cast(interf)); } } + assert(_local_interfaces != nullptr, "invariant"); // Compute the transitive list of all unique interfaces implemented by this class _transitive_interfaces = @@ -5838,15 +6216,105 @@ void ClassFileParser::post_process_parsed_stream(const ClassFileStream* const st _local_interfaces); // Size of Java itable (in words) - _itable_size = _access_flags.is_interface() ? 0 : + _itable_size = is_interface() ? 0 : klassItable::compute_itable_size(_transitive_interfaces); assert(_parsed_annotations != nullptr, "invariant"); - _field_info = new FieldLayoutInfo(); - FieldLayoutBuilder lb(class_name(), super_klass(), _cp, /*_fields*/ _temp_field_info, - _parsed_annotations->is_contended(), _field_info); + if (EnableValhalla) { + _inline_layout_info_array = MetadataFactory::new_array(_loader_data, + java_fields_count(), + CHECK); + for (GrowableArrayIterator it = _temp_field_info->begin(); it != _temp_field_info->end(); ++it) { + FieldInfo fieldinfo = *it; + if (fieldinfo.access_flags().is_static()) continue; // Only non-static fields are processed at load time + Symbol* sig = fieldinfo.signature(cp); + if (fieldinfo.field_flags().is_null_free_inline_type()) { + // Pre-load classes of null-free fields that are candidate for flattening + TempNewSymbol s = Signature::strip_envelope(sig); + if (s == _class_name) { + THROW_MSG(vmSymbols::java_lang_ClassCircularityError(), + err_msg("Class %s cannot have a null-free non-static field of its own type", _class_name->as_C_string())); + } + log_info(class, preload)("Preloading of class %s during loading of class %s. " + "Cause: a null-free non-static field is declared with this type", + s->as_C_string(), _class_name->as_C_string()); + InstanceKlass* klass = SystemDictionary::resolve_with_circularity_detection(_class_name, s, + Handle(THREAD, + _loader_data->class_loader()), + false, THREAD); + if (HAS_PENDING_EXCEPTION) { + log_warning(class, preload)("Preloading of class %s during loading of class %s " + "(cause: null-free non-static field) failed: %s", + s->as_C_string(), _class_name->as_C_string(), + PENDING_EXCEPTION->klass()->name()->as_C_string()); + return; // Exception is still pending + } + assert(klass != nullptr, "Sanity check"); + InstanceKlass::check_can_be_annotated_with_NullRestricted(klass, _class_name, CHECK); + InlineKlass* vk = InlineKlass::cast(klass); + _inline_layout_info_array->adr_at(fieldinfo.index())->set_klass(vk); + log_info(class, preload)("Preloading of class %s during loading of class %s " + "(cause: null-free non-static field) succeeded", + s->as_C_string(), _class_name->as_C_string()); + } else if (Signature::has_envelope(sig) && PreloadClasses) { + // Preloading classes for nullable fields that are listed in the LoadableDescriptors attribute + // Those classes would be required later for the flattening of nullable inline type fields + TempNewSymbol name = Signature::strip_envelope(sig); + if (name != _class_name && is_class_in_loadable_descriptors_attribute(sig)) { + log_info(class, preload)("Preloading of class %s during loading of class %s. " + "Cause: field type in LoadableDescriptors attribute", + name->as_C_string(), _class_name->as_C_string()); + oop loader = loader_data()->class_loader(); + Klass* klass = SystemDictionary::resolve_super_or_fail(_class_name, name, + Handle(THREAD, loader), + false, THREAD); + if (klass != nullptr) { + if (klass->is_inline_klass()) { + _inline_layout_info_array->adr_at(fieldinfo.index())->set_klass(InlineKlass::cast(klass)); + log_info(class, preload)("Preloading of class %s during loading of class %s " + "(cause: field type in LoadableDescriptors attribute) succeeded", + name->as_C_string(), _class_name->as_C_string()); + } else { + // Non value class are allowed by the current spec, but it could be an indication of an issue so let's log a warning + log_warning(class, preload)("Preloading of class %s during loading of class %s " + "(cause: field type in LoadableDescriptors attribute) but loaded class is not a value class", + name->as_C_string(), _class_name->as_C_string()); + } + } else { + log_warning(class, preload)("Preloading of class %s during loading of class %s " + "(cause: field type in LoadableDescriptors attribute) failed : %s", + name->as_C_string(), _class_name->as_C_string(), + PENDING_EXCEPTION->klass()->name()->as_C_string()); + } + // Loads triggered by the LoadableDescriptors attribute are speculative, failures must not impact loading of current class + if (HAS_PENDING_EXCEPTION) { + CLEAR_PENDING_EXCEPTION; + } + } else { + // Just poking the system dictionary to see if the class has already be loaded. Looking for migrated classes + // used when --enable-preview when jdk isn't compiled with --enable-preview so doesn't include LoadableDescriptors. + // This is temporary. + oop loader = loader_data()->class_loader(); + InstanceKlass* klass = SystemDictionary::find_instance_klass(THREAD, name, Handle(THREAD, loader)); + if (klass != nullptr && klass->is_inline_klass()) { + _inline_layout_info_array->adr_at(fieldinfo.index())->set_klass(InlineKlass::cast(klass)); + log_info(class, preload)("Preloading of class %s during loading of class %s " + "(cause: field type not in LoadableDescriptors attribute) succeeded", + name->as_C_string(), _class_name->as_C_string()); + } + } + } + } + } + + _layout_info = new FieldLayoutInfo(); + FieldLayoutBuilder lb(class_name(), loader_data(), super_klass(), _cp, /*_fields*/ _temp_field_info, + _parsed_annotations->is_contended(), is_inline_type(), + access_flags().is_abstract() && !access_flags().is_identity_class() && !access_flags().is_interface(), + _must_be_atomic, _layout_info, _inline_layout_info_array); lb.build_layout(); + _has_inline_type_fields = _layout_info->_has_inline_fields; int injected_fields_count = _temp_field_info->length() - _java_fields_count; _fieldinfo_stream = @@ -5856,6 +6324,27 @@ void ClassFileParser::post_process_parsed_stream(const ClassFileStream* const st _fields_status = MetadataFactory::new_array(_loader_data, _temp_field_info->length(), FieldStatus(0), CHECK); + + // Strict static fields track initialization status from the beginning of time. + // After this class runs , they will be verified as being "not unset". + // See Step 8 of InstanceKlass::initialize_impl. + if (_has_strict_static_fields) { + bool found_one = false; + for (int i = 0; i < _temp_field_info->length(); i++) { + FieldInfo& fi = *_temp_field_info->adr_at(i); + if (fi.access_flags().is_strict() && fi.access_flags().is_static()) { + found_one = true; + if (fi.initializer_index() != 0) { + // skip strict static fields with ConstantValue attributes + } else { + _fields_status->adr_at(fi.index())->update_strict_static_unset(true); + _fields_status->adr_at(fi.index())->update_strict_static_unread(true); + } + } + } + assert(found_one == _has_strict_static_fields, + "correct prediction = %d", (int)_has_strict_static_fields); + } } void ClassFileParser::set_klass(InstanceKlass* klass) { diff --git a/src/hotspot/share/classfile/classFileParser.hpp b/src/hotspot/share/classfile/classFileParser.hpp index 2f5a1aef39f..1476cb2a041 100644 --- a/src/hotspot/share/classfile/classFileParser.hpp +++ b/src/hotspot/share/classfile/classFileParser.hpp @@ -74,7 +74,20 @@ class FieldLayoutInfo : public ResourceObj { int _instance_size; int _nonstatic_field_size; int _static_field_size; - bool _has_nonstatic_fields; + int _payload_alignment; + int _payload_offset; + int _payload_size_in_bytes; + int _non_atomic_size_in_bytes; + int _non_atomic_alignment; + int _atomic_layout_size_in_bytes; + int _nullable_layout_size_in_bytes; + int _null_marker_offset; + int _null_reset_value_offset; + bool _has_nonstatic_fields; + bool _is_naturally_atomic; + bool _must_be_atomic; + bool _has_inline_fields; + bool _is_empty_inline_klass; }; // Parser for for .class files @@ -130,8 +143,10 @@ class ClassFileParser { Array* _nest_members; u2 _nest_host; Array* _permitted_subclasses; + Array* _loadable_descriptors; Array* _record_components; Array* _local_interfaces; + GrowableArray* _local_interface_indexes; Array* _transitive_interfaces; Annotations* _combined_annotations; AnnotationArray* _class_annotations; @@ -142,7 +157,8 @@ class ClassFileParser { InstanceKlass* _klass_to_deallocate; // an InstanceKlass* to be destroyed ClassAnnotationCollector* _parsed_annotations; - FieldLayoutInfo* _field_info; + FieldLayoutInfo* _layout_info; + Array* _inline_layout_info_array; GrowableArray* _temp_field_info; const intArray* _method_ordering; GrowableArray* _all_mirandas; @@ -156,6 +172,7 @@ class ClassFileParser { int _num_miranda_methods; + Handle _protection_domain; AccessFlags _access_flags; @@ -193,6 +210,12 @@ class ClassFileParser { bool _has_final_method; bool _has_contended_fields; bool _has_aot_runtime_setup_method; + bool _has_strict_static_fields; + + bool _has_inline_type_fields; + bool _is_naturally_atomic; + bool _must_be_atomic; + bool _has_loosely_consistent_annotation; // precomputed flags bool _has_finalizer; @@ -259,7 +282,7 @@ class ClassFileParser { TRAPS); void parse_fields(const ClassFileStream* const cfs, - bool is_interface, + AccessFlags class_access_flags, ConstantPool* cp, const int cp_size, u2* const java_fields_count_ptr, @@ -268,12 +291,16 @@ class ClassFileParser { // Method parsing Method* parse_method(const ClassFileStream* const cfs, bool is_interface, + bool is_value_class, + bool is_abstract_class, const ConstantPool* cp, bool* const has_localvariable_table, TRAPS); void parse_methods(const ClassFileStream* const cfs, bool is_interface, + bool is_value_class, + bool is_abstract_class, bool* const has_localvariable_table, bool* const has_final_method, bool* const declares_nonstatic_concrete_methods, @@ -328,6 +355,10 @@ class ClassFileParser { const u1* const permitted_subclasses_attribute_start, TRAPS); + u2 parse_classfile_loadable_descriptors_attribute(const ClassFileStream* const cfs, + const u1* const loadable_descriptors_attribute_start, + TRAPS); + u4 parse_classfile_record_attribute(const ClassFileStream* const cfs, const ConstantPool* cp, const u1* const record_attribute_start, @@ -420,6 +451,8 @@ class ClassFileParser { void verify_legal_field_name(const Symbol* name, TRAPS) const; void verify_legal_method_name(const Symbol* name, TRAPS) const; + bool legal_field_signature(const Symbol* signature, TRAPS) const; + void verify_legal_field_signature(const Symbol* fieldname, const Symbol* signature, TRAPS) const; @@ -433,9 +466,11 @@ class ClassFileParser { void verify_class_version(u2 major, u2 minor, Symbol* class_name, TRAPS); void verify_legal_class_modifiers(jint flags, TRAPS) const; - void verify_legal_field_modifiers(jint flags, bool is_interface, TRAPS) const; + void verify_legal_field_modifiers(jint flags, + AccessFlags class_access_flags, + TRAPS) const; void verify_legal_method_modifiers(jint flags, - bool is_interface, + AccessFlags class_access_flags, const Symbol* name, TRAPS) const; @@ -487,6 +522,9 @@ class ClassFileParser { void update_class_name(Symbol* new_name); + // Check if the class file supports inline types + bool supports_inline_types() const; + public: ClassFileParser(ClassFileStream* stream, Symbol* name, @@ -514,6 +552,13 @@ class ClassFileParser { bool is_hidden() const { return _is_hidden; } bool is_interface() const { return _access_flags.is_interface(); } + // Being an inline type means being a concrete value class + bool is_inline_type() const { return !_access_flags.is_identity_class() && !_access_flags.is_interface() && !_access_flags.is_abstract(); } + bool is_abstract_class() const { return _access_flags.is_abstract(); } + bool is_identity_class() const { return _access_flags.is_identity_class(); } + bool has_inline_fields() const { return _has_inline_type_fields; } + + u2 java_fields_count() const { return _java_fields_count; } bool is_abstract() const { return _access_flags.is_abstract(); } // Returns true if the Klass to be generated will need to be addressable @@ -532,6 +577,8 @@ class ClassFileParser { bool is_internal() const { return INTERNAL == _pub_level; } + bool is_class_in_loadable_descriptors_attribute(Symbol *klass); + static bool verify_unqualified_name(const char* name, unsigned int length, int type); #ifdef ASSERT diff --git a/src/hotspot/share/classfile/classLoader.cpp b/src/hotspot/share/classfile/classLoader.cpp index e5159662708..399b3d42588 100644 --- a/src/hotspot/share/classfile/classLoader.cpp +++ b/src/hotspot/share/classfile/classLoader.cpp @@ -1044,6 +1044,7 @@ InstanceKlass* ClassLoader::load_class(Symbol* name, PackageEntry* pkg_entry, bo ClassFileStream* stream = nullptr; s2 classpath_index = 0; ClassPathEntry* e = nullptr; + bool is_patched = false; // If search_append_only is true, boot loader visibility boundaries are // set to be _first_append_entry to the end. This includes: @@ -1063,8 +1064,22 @@ InstanceKlass* ClassLoader::load_class(Symbol* name, PackageEntry* pkg_entry, bo // Note: The --patch-module entries are never searched if the boot loader's // visibility boundary is limited to only searching the append entries. if (_patch_mod_entries != nullptr && !search_append_only) { - assert(!CDSConfig::is_dumping_archive(), "CDS doesn't support --patch-module during dumping"); - stream = search_module_entries(THREAD, _patch_mod_entries, pkg_entry, file_name); + // At CDS dump time, the --patch-module entries are ignored. That means a + // class is still loaded from the runtime image even if it might + // appear in the _patch_mod_entries. The runtime shared class visibility + // check will determine if a shared class is visible based on the runtime + // environment, including the runtime --patch-module setting. + if (!CDSConfig::is_valhalla_preview()) { + // Dynamic dumping requires UseSharedSpaces to be enabled. Since --patch-module + // is not supported with UseSharedSpaces, we can never come here during dynamic dumping. + assert(!CDSConfig::is_dumping_archive(), "CDS doesn't support --patch-module during dumping"); + } + if (CDSConfig::is_valhalla_preview() || !CDSConfig::is_dumping_static_archive()) { + stream = search_module_entries(THREAD, _patch_mod_entries, pkg_entry, file_name); + if (stream != nullptr) { + is_patched = true; + } + } } // Load Attempt #2: [jimage | exploded build] @@ -1113,6 +1128,9 @@ InstanceKlass* ClassLoader::load_class(Symbol* name, PackageEntry* pkg_entry, bo cl_info, CHECK_NULL); result->set_classpath_index(classpath_index); + if (is_patched) { + result->set_shared_classpath_index(0); + } return result; } @@ -1189,6 +1207,10 @@ void ClassLoader::record_result(JavaThread* current, InstanceKlass* ik, return; } + if (ik->shared_classpath_index() == 0 && ik->defined_by_boot_loader()) { + return; + } + oop loader = ik->class_loader(); char* src = (char*)stream->source(); if (src == nullptr) { diff --git a/src/hotspot/share/classfile/classLoaderData.cpp b/src/hotspot/share/classfile/classLoaderData.cpp index deb2035eef2..ce454c6afdb 100644 --- a/src/hotspot/share/classfile/classLoaderData.cpp +++ b/src/hotspot/share/classfile/classLoaderData.cpp @@ -65,6 +65,7 @@ #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "oops/access.inline.hpp" +#include "oops/inlineKlass.inline.hpp" #include "oops/jmethodIDTable.hpp" #include "oops/klass.inline.hpp" #include "oops/oop.inline.hpp" @@ -444,6 +445,16 @@ void ClassLoaderData::classes_do(void f(InstanceKlass*)) { } } +void ClassLoaderData::inline_classes_do(void f(InlineKlass*)) { + // Lock-free access requires load_acquire + for (Klass* k = Atomic::load_acquire(&_klasses); k != nullptr; k = k->next_link()) { + if (k->is_inline_klass()) { + f(InlineKlass::cast(k)); + } + assert(k != k->next_link(), "no loops!"); + } +} + void ClassLoaderData::modules_do(void f(ModuleEntry*)) { assert_locked_or_safepoint(Module_lock); if (_unnamed_module != nullptr) { @@ -622,6 +633,8 @@ void ClassLoaderData::unload() { // if they are not already on the _klasses list. free_deallocate_list_C_heap_structures(); + inline_classes_do(InlineKlass::cleanup); + // Clean up class dependencies and tell serviceability tools // these classes are unloading. This must be called // after erroneous classes are released. @@ -901,7 +914,11 @@ void ClassLoaderData::free_deallocate_list() { } else if (m->is_constantPool()) { MetadataFactory::free_metadata(this, (ConstantPool*)m); } else if (m->is_klass()) { - MetadataFactory::free_metadata(this, (InstanceKlass*)m); + if (!((Klass*)m)->is_inline_klass()) { + MetadataFactory::free_metadata(this, (InstanceKlass*)m); + } else { + MetadataFactory::free_metadata(this, (InlineKlass*)m); + } } else { ShouldNotReachHere(); } diff --git a/src/hotspot/share/classfile/classLoaderData.hpp b/src/hotspot/share/classfile/classLoaderData.hpp index 63004d1458f..09cfbc3577a 100644 --- a/src/hotspot/share/classfile/classLoaderData.hpp +++ b/src/hotspot/share/classfile/classLoaderData.hpp @@ -210,6 +210,7 @@ class ClassLoaderData : public CHeapObj { void loaded_classes_do(KlassClosure* klass_closure); void classes_do(void f(InstanceKlass*)); + void inline_classes_do(void f(InlineKlass*)); void methods_do(void f(Method*)); void modules_do(void f(ModuleEntry*)); void packages_do(void f(PackageEntry*)); diff --git a/src/hotspot/share/classfile/fieldLayoutBuilder.cpp b/src/hotspot/share/classfile/fieldLayoutBuilder.cpp index a87e12edc96..e5805c59467 100644 --- a/src/hotspot/share/classfile/fieldLayoutBuilder.cpp +++ b/src/hotspot/share/classfile/fieldLayoutBuilder.cpp @@ -24,41 +24,107 @@ #include "classfile/classFileParser.hpp" #include "classfile/fieldLayoutBuilder.hpp" +#include "classfile/systemDictionary.hpp" +#include "classfile/vmSymbols.hpp" #include "jvm.h" #include "memory/resourceArea.hpp" #include "oops/array.hpp" #include "oops/fieldStreams.inline.hpp" +#include "oops/inlineKlass.inline.hpp" #include "oops/instanceKlass.inline.hpp" #include "oops/instanceMirrorKlass.hpp" #include "oops/klass.inline.hpp" #include "runtime/fieldDescriptor.inline.hpp" +#include "utilities/powerOfTwo.hpp" +static LayoutKind field_layout_selection(FieldInfo field_info, Array* inline_layout_info_array, + bool use_atomic_flat) { + + if (!UseFieldFlattening) { + return LayoutKind::REFERENCE; + } + + if (field_info.field_flags().is_injected()) { + // don't flatten injected fields + return LayoutKind::REFERENCE; + } + + if (field_info.access_flags().is_volatile()) { + // volatile is used as a keyword to prevent flattening + return LayoutKind::REFERENCE; + } + + if (inline_layout_info_array == nullptr || inline_layout_info_array->adr_at(field_info.index())->klass() == nullptr) { + // field's type is not a known value class, using a reference + return LayoutKind::REFERENCE; + } + + InlineLayoutInfo* inline_field_info = inline_layout_info_array->adr_at(field_info.index()); + InlineKlass* vk = inline_field_info->klass(); + + if (field_info.field_flags().is_null_free_inline_type()) { + assert(field_info.access_flags().is_strict(), "null-free fields must be strict"); + if (vk->must_be_atomic() || AlwaysAtomicAccesses) { + if (vk->is_naturally_atomic() && vk->has_non_atomic_layout()) return LayoutKind::NON_ATOMIC_FLAT; + return (vk->has_atomic_layout() && use_atomic_flat) ? LayoutKind::ATOMIC_FLAT : LayoutKind::REFERENCE; + } else { + return vk->has_non_atomic_layout() ? LayoutKind::NON_ATOMIC_FLAT : LayoutKind::REFERENCE; + } + } else { + if (UseNullableValueFlattening && vk->has_nullable_atomic_layout()) { + return use_atomic_flat ? LayoutKind::NULLABLE_ATOMIC_FLAT : LayoutKind::REFERENCE; + } else { + return LayoutKind::REFERENCE; + } + } +} + +static void get_size_and_alignment(InlineKlass* vk, LayoutKind kind, int* size, int* alignment) { + switch(kind) { + case LayoutKind::NON_ATOMIC_FLAT: + *size = vk->non_atomic_size_in_bytes(); + *alignment = vk->non_atomic_alignment(); + break; + case LayoutKind::ATOMIC_FLAT: + *size = vk->atomic_size_in_bytes(); + *alignment = *size; + break; + case LayoutKind::NULLABLE_ATOMIC_FLAT: + *size = vk->nullable_atomic_size_in_bytes(); + *alignment = *size; + break; + default: + ShouldNotReachHere(); + } +} LayoutRawBlock::LayoutRawBlock(Kind kind, int size) : _next_block(nullptr), _prev_block(nullptr), - _kind(kind), + _inline_klass(nullptr), + _block_kind(kind), + _layout_kind(LayoutKind::UNKNOWN), _offset(-1), _alignment(1), _size(size), - _field_index(-1), - _is_reference(false) { - assert(kind == EMPTY || kind == RESERVED || kind == PADDING || kind == INHERITED, + _field_index(-1) { + assert(kind == EMPTY || kind == RESERVED || kind == PADDING || kind == INHERITED || kind == NULL_MARKER, "Otherwise, should use the constructor with a field index argument"); assert(size > 0, "Sanity check"); } -LayoutRawBlock::LayoutRawBlock(int index, Kind kind, int size, int alignment, bool is_reference) : +LayoutRawBlock::LayoutRawBlock(int index, Kind kind, int size, int alignment) : _next_block(nullptr), _prev_block(nullptr), - _kind(kind), + _inline_klass(nullptr), + _block_kind(kind), + _layout_kind(LayoutKind::UNKNOWN), _offset(-1), _alignment(alignment), _size(size), - _field_index(index), - _is_reference(is_reference) { - assert(kind == REGULAR || kind == FLATTENED || kind == INHERITED, + _field_index(index) { + assert(kind == REGULAR || kind == FLAT || kind == INHERITED, "Other kind do not have a field index"); assert(size > 0, "Sanity check"); assert(alignment > 0, "Sanity check"); @@ -74,23 +140,25 @@ bool LayoutRawBlock::fit(int size, int alignment) { FieldGroup::FieldGroup(int contended_group) : _next(nullptr), - _primitive_fields(nullptr), + _small_primitive_fields(nullptr), + _big_primitive_fields(nullptr), _oop_fields(nullptr), _contended_group(contended_group), // -1 means no contended group, 0 means default contended group _oop_count(0) {} void FieldGroup::add_primitive_field(int idx, BasicType type) { int size = type2aelembytes(type); - LayoutRawBlock* block = new LayoutRawBlock(idx, LayoutRawBlock::REGULAR, size, size /* alignment == size for primitive types */, false); - if (_primitive_fields == nullptr) { - _primitive_fields = new GrowableArray(INITIAL_LIST_SIZE); + LayoutRawBlock* block = new LayoutRawBlock(idx, LayoutRawBlock::REGULAR, size, size /* alignment == size for primitive types */); + if (size >= oopSize) { + add_to_big_primitive_list(block); + } else { + add_to_small_primitive_list(block); } - _primitive_fields->append(block); } void FieldGroup::add_oop_field(int idx) { int size = type2aelembytes(T_OBJECT); - LayoutRawBlock* block = new LayoutRawBlock(idx, LayoutRawBlock::REGULAR, size, size /* alignment == size for oops */, true); + LayoutRawBlock* block = new LayoutRawBlock(idx, LayoutRawBlock::REGULAR, size, size /* alignment == size for oops */); if (_oop_fields == nullptr) { _oop_fields = new GrowableArray(INITIAL_LIST_SIZE); } @@ -98,18 +166,53 @@ void FieldGroup::add_oop_field(int idx) { _oop_count++; } +void FieldGroup::add_flat_field(int idx, InlineKlass* vk, LayoutKind lk, int size, int alignment) { + LayoutRawBlock* block = new LayoutRawBlock(idx, LayoutRawBlock::FLAT, size, alignment); + block->set_inline_klass(vk); + block->set_layout_kind(lk); + if (block->size() >= oopSize) { + add_to_big_primitive_list(block); + } else { + add_to_small_primitive_list(block); + } +} + void FieldGroup::sort_by_size() { - if (_primitive_fields != nullptr) { - _primitive_fields->sort(LayoutRawBlock::compare_size_inverted); + if (_small_primitive_fields != nullptr) { + _small_primitive_fields->sort(LayoutRawBlock::compare_size_inverted); + } + if (_big_primitive_fields != nullptr) { + _big_primitive_fields->sort(LayoutRawBlock::compare_size_inverted); + } +} + +void FieldGroup::add_to_small_primitive_list(LayoutRawBlock* block) { + if (_small_primitive_fields == nullptr) { + _small_primitive_fields = new GrowableArray(INITIAL_LIST_SIZE); + } + _small_primitive_fields->append(block); +} + +void FieldGroup::add_to_big_primitive_list(LayoutRawBlock* block) { + if (_big_primitive_fields == nullptr) { + _big_primitive_fields = new GrowableArray(INITIAL_LIST_SIZE); } + _big_primitive_fields->append(block); } -FieldLayout::FieldLayout(GrowableArray* field_info, ConstantPool* cp) : +FieldLayout::FieldLayout(GrowableArray* field_info, Array* inline_layout_info_array, ConstantPool* cp) : _field_info(field_info), + _inline_layout_info_array(inline_layout_info_array), _cp(cp), _blocks(nullptr), _start(_blocks), - _last(_blocks) {} + _last(_blocks), + _super_first_field_offset(-1), + _super_alignment(-1), + _super_min_align_required(-1), + _null_reset_value_offset(-1), + _super_has_fields(false), + _has_inherited_fields(false) {} void FieldLayout::initialize_static_layout() { _blocks = new LayoutRawBlock(LayoutRawBlock::EMPTY, INT_MAX); @@ -135,10 +238,9 @@ void FieldLayout::initialize_instance_layout(const InstanceKlass* super_klass, b _start = _blocks; insert(first_empty_block(), new LayoutRawBlock(LayoutRawBlock::RESERVED, instanceOopDesc::base_offset_in_bytes())); } else { - bool super_has_instance_fields = false; - reconstruct_layout(super_klass, super_has_instance_fields, super_ends_with_oop); + reconstruct_layout(super_klass, _super_has_fields, super_ends_with_oop); fill_holes(super_klass); - if (!super_klass->has_contended_annotations() || !super_has_instance_fields) { + if ((!super_klass->has_contended_annotations()) || !_super_has_fields) { _start = _blocks; // start allocating fields from the first empty block } else { _start = _last; // append fields at the end of the reconstructed layout @@ -147,17 +249,19 @@ void FieldLayout::initialize_instance_layout(const InstanceKlass* super_klass, b } LayoutRawBlock* FieldLayout::first_field_block() { - LayoutRawBlock* block = _start; - while (block->kind() != LayoutRawBlock::INHERITED && block->kind() != LayoutRawBlock::REGULAR - && block->kind() != LayoutRawBlock::FLATTENED && block->kind() != LayoutRawBlock::PADDING) { + LayoutRawBlock* block = _blocks; + while (block != nullptr + && block->block_kind() != LayoutRawBlock::INHERITED + && block->block_kind() != LayoutRawBlock::REGULAR + && block->block_kind() != LayoutRawBlock::FLAT + && block->block_kind() != LayoutRawBlock::NULL_MARKER) { block = block->next_block(); } return block; } - -// Insert a set of fields into a layout using a best-fit strategy. -// For each field, search for the smallest empty slot able to fit the field +// Insert a set of fields into a layout. +// For each field, search for an empty slot able to fit the field // (satisfying both size and alignment requirements), if none is found, // add the field at the end of the layout. // Fields cannot be inserted before the block specified in the "start" argument @@ -171,7 +275,6 @@ void FieldLayout::add(GrowableArray* list, LayoutRawBlock* star LayoutRawBlock* b = list->at(i); LayoutRawBlock* cursor = nullptr; LayoutRawBlock* candidate = nullptr; - // if start is the last block, just append the field if (start == last_block()) { candidate = last_block(); @@ -189,8 +292,9 @@ void FieldLayout::add(GrowableArray* list, LayoutRawBlock* star cursor = last_block()->prev_block(); assert(cursor != nullptr, "Sanity check"); last_search_success = true; + while (cursor != start) { - if (cursor->kind() == LayoutRawBlock::EMPTY && cursor->fit(b->size(), b->alignment())) { + if (cursor->block_kind() == LayoutRawBlock::EMPTY && cursor->fit(b->size(), b->alignment())) { if (candidate == nullptr || cursor->size() < candidate->size()) { candidate = cursor; } @@ -202,10 +306,9 @@ void FieldLayout::add(GrowableArray* list, LayoutRawBlock* star last_search_success = false; } assert(candidate != nullptr, "Candidate must not be null"); - assert(candidate->kind() == LayoutRawBlock::EMPTY, "Candidate must be an empty block"); + assert(candidate->block_kind() == LayoutRawBlock::EMPTY, "Candidate must be an empty block"); assert(candidate->fit(b->size(), b->alignment()), "Candidate must be able to store the block"); } - insert_field_block(candidate, b); } } @@ -221,8 +324,8 @@ void FieldLayout::add_field_at_offset(LayoutRawBlock* block, int offset, LayoutR while (slot != nullptr) { if ((slot->offset() <= block->offset() && (slot->offset() + slot->size()) > block->offset()) || slot == _last){ - assert(slot->kind() == LayoutRawBlock::EMPTY, "Matching slot must be an empty slot"); - assert(slot->size() >= block->offset() + block->size() ,"Matching slot must be big enough"); + assert(slot->block_kind() == LayoutRawBlock::EMPTY, "Matching slot must be an empty slot"); + assert(slot->size() >= block->offset() - slot->offset() + block->size() ,"Matching slot must be big enough"); if (slot->offset() < block->offset()) { int adjustment = block->offset() - slot->offset(); LayoutRawBlock* adj = new LayoutRawBlock(LayoutRawBlock::EMPTY, adjustment); @@ -232,7 +335,9 @@ void FieldLayout::add_field_at_offset(LayoutRawBlock* block, int offset, LayoutR if (slot->size() == 0) { remove(slot); } - _field_info->adr_at(block->field_index())->set_offset(block->offset()); + if (block->block_kind() == LayoutRawBlock::REGULAR || block->block_kind() == LayoutRawBlock::FLAT) { + _field_info->adr_at(block->field_index())->set_offset(block->offset()); + } return; } slot = slot->next_block(); @@ -261,7 +366,7 @@ void FieldLayout::add_contiguously(GrowableArray* list, LayoutR } else { LayoutRawBlock* first = list->at(0); candidate = last_block()->prev_block(); - while (candidate->kind() != LayoutRawBlock::EMPTY || !candidate->fit(size, first->alignment())) { + while (candidate->block_kind() != LayoutRawBlock::EMPTY || !candidate->fit(size, first->alignment())) { if (candidate == start) { candidate = last_block(); break; @@ -269,7 +374,7 @@ void FieldLayout::add_contiguously(GrowableArray* list, LayoutR candidate = candidate->prev_block(); } assert(candidate != nullptr, "Candidate must not be null"); - assert(candidate->kind() == LayoutRawBlock::EMPTY, "Candidate must be an empty block"); + assert(candidate->block_kind() == LayoutRawBlock::EMPTY, "Candidate must be an empty block"); assert(candidate->fit(size, first->alignment()), "Candidate must be able to store the whole contiguous block"); } @@ -281,22 +386,38 @@ void FieldLayout::add_contiguously(GrowableArray* list, LayoutR } LayoutRawBlock* FieldLayout::insert_field_block(LayoutRawBlock* slot, LayoutRawBlock* block) { - assert(slot->kind() == LayoutRawBlock::EMPTY, "Blocks can only be inserted in empty blocks"); + assert(slot->block_kind() == LayoutRawBlock::EMPTY, "Blocks can only be inserted in empty blocks"); if (slot->offset() % block->alignment() != 0) { int adjustment = block->alignment() - (slot->offset() % block->alignment()); LayoutRawBlock* adj = new LayoutRawBlock(LayoutRawBlock::EMPTY, adjustment); insert(slot, adj); } + assert(block->size() >= block->size(), "Enough space must remain after adjustment"); insert(slot, block); if (slot->size() == 0) { remove(slot); } - _field_info->adr_at(block->field_index())->set_offset(block->offset()); + // NULL_MARKER blocks are not real fields, so they don't have an entry in the FieldInfo array + if (block->block_kind() != LayoutRawBlock::NULL_MARKER) { + _field_info->adr_at(block->field_index())->set_offset(block->offset()); + if (_field_info->adr_at(block->field_index())->name(_cp) == vmSymbols::null_reset_value_name()) { + _null_reset_value_offset = block->offset(); + } + } + if (block->block_kind() == LayoutRawBlock::FLAT && block->layout_kind() == LayoutKind::NULLABLE_ATOMIC_FLAT) { + int nm_offset = block->inline_klass()->null_marker_offset() - block->inline_klass()->payload_offset() + block->offset(); + _field_info->adr_at(block->field_index())->set_null_marker_offset(nm_offset); + _inline_layout_info_array->adr_at(block->field_index())->set_null_marker_offset(nm_offset); + } + return block; } void FieldLayout::reconstruct_layout(const InstanceKlass* ik, bool& has_instance_fields, bool& ends_with_oop) { has_instance_fields = ends_with_oop = false; + if (ik->is_abstract() && !ik->is_identity_class()) { + _super_alignment = type2aelembytes(BasicType::T_LONG); + } GrowableArray* all_fields = new GrowableArray(32); BasicType last_type; int last_offset = -1; @@ -306,13 +427,31 @@ void FieldLayout::reconstruct_layout(const InstanceKlass* ik, bool& has_instance // distinction between static and non-static fields is missing if (fs.access_flags().is_static()) continue; has_instance_fields = true; + _has_inherited_fields = true; + if (_super_first_field_offset == -1 || fs.offset() < _super_first_field_offset) { + _super_first_field_offset = fs.offset(); + } + LayoutRawBlock* block; + if (fs.is_flat()) { + InlineLayoutInfo layout_info = ik->inline_layout_info(fs.index()); + InlineKlass* vk = layout_info.klass(); + block = new LayoutRawBlock(fs.index(), LayoutRawBlock::INHERITED, + vk->layout_size_in_bytes(layout_info.kind()), + vk->layout_alignment(layout_info.kind())); + assert(_super_alignment == -1 || _super_alignment >= vk->payload_alignment(), "Invalid value alignment"); + _super_min_align_required = _super_min_align_required > vk->payload_alignment() ? _super_min_align_required : vk->payload_alignment(); + } else { + int size = type2aelembytes(type); + // INHERITED blocks are marked as non-reference because oop_maps are handled by their holder class + block = new LayoutRawBlock(fs.index(), LayoutRawBlock::INHERITED, size, size); + // For primitive types, the alignment is equal to the size + assert(_super_alignment == -1 || _super_alignment >= size, "Invalid value alignment"); + _super_min_align_required = _super_min_align_required > size ? _super_min_align_required : size; + } if (fs.offset() > last_offset) { last_offset = fs.offset(); last_type = type; } - int size = type2aelembytes(type); - // INHERITED blocks are marked as non-reference because oop_maps are handled by their holder class - LayoutRawBlock* block = new LayoutRawBlock(fs.index(), LayoutRawBlock::INHERITED, size, size, false); block->set_offset(fs.offset()); all_fields->append(block); } @@ -328,7 +467,6 @@ void FieldLayout::reconstruct_layout(const InstanceKlass* ik, bool& has_instance _blocks = new LayoutRawBlock(LayoutRawBlock::RESERVED, instanceOopDesc::base_offset_in_bytes()); _blocks->set_offset(0); _last = _blocks; - for(int i = 0; i < all_fields->length(); i++) { LayoutRawBlock* b = all_fields->at(i); _last->set_next_block(b); @@ -352,6 +490,7 @@ void FieldLayout::fill_holes(const InstanceKlass* super_klass) { while (b->next_block() != nullptr) { if (b->next_block()->offset() > (b->offset() + b->size())) { int size = b->next_block()->offset() - (b->offset() + b->size()); + // FIXME it would be better if initial empty block where tagged as PADDING for value classes LayoutRawBlock* empty = new LayoutRawBlock(filling_type, size); empty->set_offset(b->offset() + b->size()); empty->set_next_block(b->next_block()); @@ -362,8 +501,7 @@ void FieldLayout::fill_holes(const InstanceKlass* super_klass) { b = b->next_block(); } assert(b->next_block() == nullptr, "Invariant at this point"); - assert(b->kind() != LayoutRawBlock::EMPTY, "Sanity check"); - + assert(b->block_kind() != LayoutRawBlock::EMPTY, "Sanity check"); // If the super class has @Contended annotation, a padding block is // inserted at the end to ensure that fields from the subclasses won't share // the cache line of the last field of the contended class @@ -384,7 +522,7 @@ void FieldLayout::fill_holes(const InstanceKlass* super_klass) { } LayoutRawBlock* FieldLayout::insert(LayoutRawBlock* slot, LayoutRawBlock* block) { - assert(slot->kind() == LayoutRawBlock::EMPTY, "Blocks can only be inserted in empty blocks"); + assert(slot->block_kind() == LayoutRawBlock::EMPTY, "Blocks can only be inserted in empty blocks"); assert(slot->offset() % block->alignment() == 0, "Incompatible alignment"); block->set_offset(slot->offset()); slot->set_offset(slot->offset() + block->size()); @@ -400,6 +538,9 @@ LayoutRawBlock* FieldLayout::insert(LayoutRawBlock* slot, LayoutRawBlock* block) if (_blocks == slot) { _blocks = block; } + if (_start == slot) { + _start = block; + } return block; } @@ -421,38 +562,118 @@ void FieldLayout::remove(LayoutRawBlock* block) { } } -void FieldLayout::print(outputStream* output, bool is_static, const InstanceKlass* super) { +void FieldLayout::shift_fields(int shift) { + LayoutRawBlock* b = first_field_block(); + LayoutRawBlock* previous = b->prev_block(); + if (previous->block_kind() == LayoutRawBlock::EMPTY) { + previous->set_size(previous->size() + shift); + } else { + LayoutRawBlock* nb = new LayoutRawBlock(LayoutRawBlock::PADDING, shift); + nb->set_offset(b->offset()); + previous->set_next_block(nb); + nb->set_prev_block(previous); + b->set_prev_block(nb); + nb->set_next_block(b); + } + while (b != nullptr) { + b->set_offset(b->offset() + shift); + if (b->block_kind() == LayoutRawBlock::REGULAR || b->block_kind() == LayoutRawBlock::FLAT) { + _field_info->adr_at(b->field_index())->set_offset(b->offset()); + if (b->layout_kind() == LayoutKind::NULLABLE_ATOMIC_FLAT) { + int new_nm_offset = _field_info->adr_at(b->field_index())->null_marker_offset() + shift; + _field_info->adr_at(b->field_index())->set_null_marker_offset(new_nm_offset); + _inline_layout_info_array->adr_at(b->field_index())->set_null_marker_offset(new_nm_offset); + + } + } + assert(b->block_kind() == LayoutRawBlock::EMPTY || b->offset() % b->alignment() == 0, "Must still be correctly aligned"); + b = b->next_block(); + } +} + +LayoutRawBlock* FieldLayout::find_null_marker() { + LayoutRawBlock* b = _blocks; + while (b != nullptr) { + if (b->block_kind() == LayoutRawBlock::NULL_MARKER) { + return b; + } + b = b->next_block(); + } + ShouldNotReachHere(); +} + +void FieldLayout::remove_null_marker() { + LayoutRawBlock* b = first_field_block(); + while (b != nullptr) { + if (b->block_kind() == LayoutRawBlock::NULL_MARKER) { + if (b->next_block()->block_kind() == LayoutRawBlock::EMPTY) { + LayoutRawBlock* n = b->next_block(); + remove(b); + n->set_offset(b->offset()); + n->set_size(n->size() + b->size()); + } else { + b->set_block_kind(LayoutRawBlock::EMPTY); + } + return; + } + b = b->next_block(); + } + ShouldNotReachHere(); // if we reach this point, the null marker was not found! +} + +static const char* layout_kind_to_string(LayoutKind lk) { + switch(lk) { + case LayoutKind::REFERENCE: + return "REFERENCE"; + case LayoutKind::NON_ATOMIC_FLAT: + return "NON_ATOMIC_FLAT"; + case LayoutKind::ATOMIC_FLAT: + return "ATOMIC_FLAT"; + case LayoutKind::NULLABLE_ATOMIC_FLAT: + return "NULLABLE_ATOMIC_FLAT"; + case LayoutKind::UNKNOWN: + return "UNKNOWN"; + default: + ShouldNotReachHere(); + } +} + +void FieldLayout::print(outputStream* output, bool is_static, const InstanceKlass* super, Array* inline_fields) { ResourceMark rm; LayoutRawBlock* b = _blocks; while(b != _last) { - switch(b->kind()) { + switch(b->block_kind()) { case LayoutRawBlock::REGULAR: { FieldInfo* fi = _field_info->adr_at(b->field_index()); - output->print_cr(" @%d \"%s\" %s %d/%d %s", + output->print_cr(" @%d %s %d/%d \"%s\" %s", b->offset(), - fi->name(_cp)->as_C_string(), - fi->signature(_cp)->as_C_string(), + "REGULAR", b->size(), b->alignment(), - "REGULAR"); + fi->name(_cp)->as_C_string(), + fi->signature(_cp)->as_C_string()); break; } - case LayoutRawBlock::FLATTENED: { + case LayoutRawBlock::FLAT: { FieldInfo* fi = _field_info->adr_at(b->field_index()); - output->print_cr(" @%d \"%s\" %s %d/%d %s", + InlineKlass* ik = inline_fields->adr_at(fi->index())->klass(); + assert(ik != nullptr, ""); + output->print_cr(" @%d %s %d/%d \"%s\" %s %s@%p %s", b->offset(), - fi->name(_cp)->as_C_string(), - fi->signature(_cp)->as_C_string(), + "FLAT", b->size(), b->alignment(), - "FLATTENED"); + fi->name(_cp)->as_C_string(), + fi->signature(_cp)->as_C_string(), + ik->name()->as_C_string(), + ik->class_loader_data(), layout_kind_to_string(b->layout_kind())); break; } case LayoutRawBlock::RESERVED: { - output->print_cr(" @%d %d/- %s", + output->print_cr(" @%d %s %d/-", b->offset(), - b->size(), - "RESERVED"); + "RESERVED", + b->size()); break; } case LayoutRawBlock::INHERITED: { @@ -462,56 +683,85 @@ void FieldLayout::print(outputStream* output, bool is_static, const InstanceKlas const InstanceKlass* ik = super; while (!found && ik != nullptr) { for (AllFieldStream fs(ik); !fs.done(); fs.next()) { - if (fs.offset() == b->offset()) { - output->print_cr(" @%d \"%s\" %s %d/%d %s", + if (fs.offset() == b->offset() && fs.access_flags().is_static() == is_static) { + output->print_cr(" @%d %s %d/%d \"%s\" %s", b->offset(), - fs.name()->as_C_string(), - fs.signature()->as_C_string(), + "INHERITED", b->size(), - b->size(), // so far, alignment constraint == size, will change with Valhalla - "INHERITED"); + b->size(), // so far, alignment constraint == size, will change with Valhalla => FIXME + fs.name()->as_C_string(), + fs.signature()->as_C_string()); found = true; break; } - } - ik = ik->super(); } - break; + ik = ik->super(); } - case LayoutRawBlock::EMPTY: - output->print_cr(" @%d %d/1 %s", - b->offset(), - b->size(), - "EMPTY"); - break; - case LayoutRawBlock::PADDING: - output->print_cr(" @%d %d/1 %s", - b->offset(), - b->size(), - "PADDING"); - break; + break; + } + case LayoutRawBlock::EMPTY: + output->print_cr(" @%d %s %d/1", + b->offset(), + "EMPTY", + b->size()); + break; + case LayoutRawBlock::PADDING: + output->print_cr(" @%d %s %d/1", + b->offset(), + "PADDING", + b->size()); + break; + case LayoutRawBlock::NULL_MARKER: + { + output->print_cr(" @%d %s %d/1 ", + b->offset(), + "NULL_MARKER", + b->size()); + break; + } + default: + fatal("Unknown block type"); } b = b->next_block(); } } -FieldLayoutBuilder::FieldLayoutBuilder(const Symbol* classname, const InstanceKlass* super_klass, ConstantPool* constant_pool, - GrowableArray* field_info, bool is_contended, FieldLayoutInfo* info) : +FieldLayoutBuilder::FieldLayoutBuilder(const Symbol* classname, ClassLoaderData* loader_data, const InstanceKlass* super_klass, ConstantPool* constant_pool, + GrowableArray* field_info, bool is_contended, bool is_inline_type,bool is_abstract_value, + bool must_be_atomic, FieldLayoutInfo* info, Array* inline_layout_info_array) : _classname(classname), + _loader_data(loader_data), _super_klass(super_klass), _constant_pool(constant_pool), _field_info(field_info), _info(info), + _inline_layout_info_array(inline_layout_info_array), _root_group(nullptr), _contended_groups(GrowableArray(8)), _static_fields(nullptr), _layout(nullptr), _static_layout(nullptr), _nonstatic_oopmap_count(0), - _alignment(-1), + _payload_alignment(-1), + _payload_offset(-1), + _null_marker_offset(-1), + _payload_size_in_bytes(-1), + _non_atomic_layout_size_in_bytes(-1), + _non_atomic_layout_alignment(-1), + _atomic_layout_size_in_bytes(-1), + _nullable_layout_size_in_bytes(-1), + _fields_size_sum(0), + _declared_non_static_fields_count(0), + _has_non_naturally_atomic_fields(false), + _is_naturally_atomic(false), + _must_be_atomic(must_be_atomic), _has_nonstatic_fields(false), - _is_contended(is_contended) {} - + _has_inline_type_fields(false), + _is_contended(is_contended), + _is_inline_type(is_inline_type), + _is_abstract_value(is_abstract_value), + _has_flattening_information(is_inline_type), + _is_empty_inline_class(false) {} FieldGroup* FieldLayoutBuilder::get_or_create_contended_group(int g) { assert(g > 0, "must only be called for named contended groups"); @@ -526,27 +776,28 @@ FieldGroup* FieldLayoutBuilder::get_or_create_contended_group(int g) { } void FieldLayoutBuilder::prologue() { - _layout = new FieldLayout(_field_info, _constant_pool); + _layout = new FieldLayout(_field_info, _inline_layout_info_array, _constant_pool); const InstanceKlass* super_klass = _super_klass; _layout->initialize_instance_layout(super_klass, _super_ends_with_oop); + _nonstatic_oopmap_count = super_klass == nullptr ? 0 : super_klass->nonstatic_oop_map_count(); if (super_klass != nullptr) { _has_nonstatic_fields = super_klass->has_nonstatic_fields(); } - _static_layout = new FieldLayout(_field_info, _constant_pool); + _static_layout = new FieldLayout(_field_info, _inline_layout_info_array, _constant_pool); _static_layout->initialize_static_layout(); _static_fields = new FieldGroup(); _root_group = new FieldGroup(); } -// Field sorting for regular classes: +// Field sorting for regular (non-inline) classes: // - fields are sorted in static and non-static fields // - non-static fields are also sorted according to their contention group // (support of the @Contended annotation) // - @Contended annotation is ignored for static fields +// - field flattening decisions are taken in this method void FieldLayoutBuilder::regular_field_sorting() { int idx = 0; for (GrowableArrayIterator it = _field_info->begin(); it != _field_info->end(); ++it, ++idx) { - FieldInfo ctrl = _field_info->at(0); FieldGroup* group = nullptr; FieldInfo fieldinfo = *it; if (fieldinfo.access_flags().is_static()) { @@ -568,23 +819,46 @@ void FieldLayoutBuilder::regular_field_sorting() { assert(group != nullptr, "invariant"); BasicType type = Signature::basic_type(fieldinfo.signature(_constant_pool)); switch(type) { - case T_BYTE: - case T_CHAR: - case T_DOUBLE: - case T_FLOAT: - case T_INT: - case T_LONG: - case T_SHORT: - case T_BOOLEAN: - group->add_primitive_field(idx, type); - break; - case T_OBJECT: - case T_ARRAY: + case T_BYTE: + case T_CHAR: + case T_DOUBLE: + case T_FLOAT: + case T_INT: + case T_LONG: + case T_SHORT: + case T_BOOLEAN: + group->add_primitive_field(idx, type); + break; + case T_OBJECT: + case T_ARRAY: + { + LayoutKind lk = field_layout_selection(fieldinfo, _inline_layout_info_array, true); + if (fieldinfo.field_flags().is_null_free_inline_type() || lk != LayoutKind::REFERENCE + || (!fieldinfo.field_flags().is_injected() + && _inline_layout_info_array != nullptr && _inline_layout_info_array->adr_at(fieldinfo.index())->klass() != nullptr + && !_inline_layout_info_array->adr_at(fieldinfo.index())->klass()->is_identity_class())) { + _has_inline_type_fields = true; + _has_flattening_information = true; + } + if (lk == LayoutKind::REFERENCE) { if (group != _static_fields) _nonstatic_oopmap_count++; group->add_oop_field(idx); - break; - default: - fatal("Something wrong?"); + } else { + _has_flattening_information = true; + InlineKlass* vk = _inline_layout_info_array->adr_at(fieldinfo.index())->klass(); + int size, alignment; + get_size_and_alignment(vk, lk, &size, &alignment); + group->add_flat_field(idx, vk, lk, size, alignment); + _inline_layout_info_array->adr_at(fieldinfo.index())->set_kind(lk); + _nonstatic_oopmap_count += vk->nonstatic_oop_map_count(); + _field_info->adr_at(idx)->field_flags_addr()->update_flat(true); + _field_info->adr_at(idx)->set_layout_kind(lk); + // no need to update _must_be_atomic if vk->must_be_atomic() is true because current class is not an inline class + } + break; + } + default: + fatal("Something wrong?"); } } _root_group->sort_by_size(); @@ -596,6 +870,90 @@ void FieldLayoutBuilder::regular_field_sorting() { } } +/* Field sorting for inline classes: + * - because inline classes are immutable, the @Contended annotation is ignored + * when computing their layout (with only read operation, there's no false + * sharing issue) + * - this method also records the alignment of the field with the most + * constraining alignment, this value is then used as the alignment + * constraint when flattening this inline type into another container + * - field flattening decisions are taken in this method (those decisions are + * currently only based in the size of the fields to be flattened, the size + * of the resulting instance is not considered) + */ +void FieldLayoutBuilder::inline_class_field_sorting() { + assert(_is_inline_type || _is_abstract_value, "Should only be used for inline classes"); + int alignment = -1; + int idx = 0; + for (GrowableArrayIterator it = _field_info->begin(); it != _field_info->end(); ++it, ++idx) { + FieldGroup* group = nullptr; + FieldInfo fieldinfo = *it; + int field_alignment = 1; + if (fieldinfo.access_flags().is_static()) { + group = _static_fields; + } else { + _has_nonstatic_fields = true; + _declared_non_static_fields_count++; + group = _root_group; + } + assert(group != nullptr, "invariant"); + BasicType type = Signature::basic_type(fieldinfo.signature(_constant_pool)); + switch(type) { + case T_BYTE: + case T_CHAR: + case T_DOUBLE: + case T_FLOAT: + case T_INT: + case T_LONG: + case T_SHORT: + case T_BOOLEAN: + if (group != _static_fields) { + field_alignment = type2aelembytes(type); // alignment == size for primitive types + } + group->add_primitive_field(fieldinfo.index(), type); + break; + case T_OBJECT: + case T_ARRAY: + { + bool use_atomic_flat = _must_be_atomic; // flatten atomic fields only if the container is itself atomic + LayoutKind lk = field_layout_selection(fieldinfo, _inline_layout_info_array, use_atomic_flat); + if (fieldinfo.field_flags().is_null_free_inline_type() || lk != LayoutKind::REFERENCE + || (!fieldinfo.field_flags().is_injected() + && _inline_layout_info_array != nullptr && _inline_layout_info_array->adr_at(fieldinfo.index())->klass() != nullptr + && !_inline_layout_info_array->adr_at(fieldinfo.index())->klass()->is_identity_class())) { + _has_inline_type_fields = true; + _has_flattening_information = true; + } + if (lk == LayoutKind::REFERENCE) { + if (group != _static_fields) { + _nonstatic_oopmap_count++; + field_alignment = type2aelembytes(type); // alignment == size for oops + } + group->add_oop_field(idx); + } else { + _has_flattening_information = true; + InlineKlass* vk = _inline_layout_info_array->adr_at(fieldinfo.index())->klass(); + if (!vk->is_naturally_atomic()) _has_non_naturally_atomic_fields = true; + int size, alignment; + get_size_and_alignment(vk, lk, &size, &alignment); + group->add_flat_field(idx, vk, lk, size, alignment); + _inline_layout_info_array->adr_at(fieldinfo.index())->set_kind(lk); + _nonstatic_oopmap_count += vk->nonstatic_oop_map_count(); + field_alignment = alignment; + _field_info->adr_at(idx)->field_flags_addr()->update_flat(true); + _field_info->adr_at(idx)->set_layout_kind(lk); + } + break; + } + default: + fatal("Unexpected BasicType"); + } + if (!fieldinfo.access_flags().is_static() && field_alignment > alignment) alignment = field_alignment; + } + _payload_alignment = alignment; + assert(_has_nonstatic_fields || _is_abstract_value, "Concrete value types do not support zero instance size yet"); +} + void FieldLayoutBuilder::insert_contended_padding(LayoutRawBlock* slot) { if (ContendedPaddingWidth > 0) { LayoutRawBlock* padding = new LayoutRawBlock(LayoutRawBlock::PADDING, ContendedPaddingWidth); @@ -605,7 +963,8 @@ void FieldLayoutBuilder::insert_contended_padding(LayoutRawBlock* slot) { // Computation of regular classes layout is an evolution of the previous default layout // (FieldAllocationStyle 1): -// - primitive fields are allocated first (from the biggest to the smallest) +// - primitive fields (both primitive types and flat inline types) are allocated +// first (from the biggest to the smallest) // - oop fields are allocated, either in existing gaps or at the end of // the layout. We allocate oops in a single block to have a single oop map entry. // - if the super class ended with an oop, we lead with oops. That will cause the @@ -618,7 +977,6 @@ void FieldLayoutBuilder::compute_regular_layout() { bool need_tail_padding = false; prologue(); regular_field_sorting(); - if (_is_contended) { _layout->set_start(_layout->last_block()); // insertion is currently easy because the current strategy doesn't try to fill holes @@ -629,9 +987,11 @@ void FieldLayoutBuilder::compute_regular_layout() { if (_super_ends_with_oop) { _layout->add(_root_group->oop_fields()); - _layout->add(_root_group->primitive_fields()); + _layout->add(_root_group->big_primitive_fields()); + _layout->add(_root_group->small_primitive_fields()); } else { - _layout->add(_root_group->primitive_fields()); + _layout->add(_root_group->big_primitive_fields()); + _layout->add(_root_group->small_primitive_fields()); _layout->add(_root_group->oop_fields()); } @@ -640,7 +1000,8 @@ void FieldLayoutBuilder::compute_regular_layout() { FieldGroup* cg = _contended_groups.at(i); LayoutRawBlock* start = _layout->last_block(); insert_contended_padding(start); - _layout->add(cg->primitive_fields(), start); + _layout->add(cg->big_primitive_fields()); + _layout->add(cg->small_primitive_fields(), start); _layout->add(cg->oop_fields(), start); need_tail_padding = true; } @@ -650,41 +1011,302 @@ void FieldLayoutBuilder::compute_regular_layout() { insert_contended_padding(_layout->last_block()); } - _static_layout->add_contiguously(this->_static_fields->oop_fields()); - _static_layout->add(this->_static_fields->primitive_fields()); + // Warning: IntanceMirrorKlass expects static oops to be allocated first + _static_layout->add_contiguously(_static_fields->oop_fields()); + _static_layout->add(_static_fields->big_primitive_fields()); + _static_layout->add(_static_fields->small_primitive_fields()); epilogue(); } -void FieldLayoutBuilder::epilogue() { - // Computing oopmaps - int super_oop_map_count = (_super_klass == nullptr) ? 0 :_super_klass->nonstatic_oop_map_count(); - int max_oop_map_count = super_oop_map_count + _nonstatic_oopmap_count; +/* Computation of inline classes has a slightly different strategy than for + * regular classes. Regular classes have their oop fields allocated at the end + * of the layout to increase GC performances. Unfortunately, this strategy + * increases the number of empty slots inside an instance. Because the purpose + * of inline classes is to be embedded into other containers, it is critical + * to keep their size as small as possible. For this reason, the allocation + * strategy is: + * - big primitive fields (primitive types and flat inline type smaller + * than an oop) are allocated first (from the biggest to the smallest) + * - then oop fields + * - then small primitive fields (from the biggest to the smallest) + */ +void FieldLayoutBuilder::compute_inline_class_layout() { - OopMapBlocksBuilder* nonstatic_oop_maps = - new OopMapBlocksBuilder(max_oop_map_count); - if (super_oop_map_count > 0) { - nonstatic_oop_maps->initialize_inherited_blocks(_super_klass->start_of_nonstatic_oop_maps(), - _super_klass->nonstatic_oop_map_count()); + // Test if the concrete inline class is an empty class (no instance fields) + // and insert a dummy field if needed + if (!_is_abstract_value) { + bool declares_non_static_fields = false; + for (GrowableArrayIterator it = _field_info->begin(); it != _field_info->end(); ++it) { + FieldInfo fieldinfo = *it; + if (!fieldinfo.access_flags().is_static()) { + declares_non_static_fields = true; + break; + } + } + if (!declares_non_static_fields) { + bool has_inherited_fields = false; + const InstanceKlass* super = _super_klass; + while(super != nullptr) { + if (super->has_nonstatic_fields()) { + has_inherited_fields = true; + break; + } + super = super->super() == nullptr ? nullptr : InstanceKlass::cast(super->super()); + } + + if (!has_inherited_fields) { + // Inject ".empty" dummy field + _is_empty_inline_class = true; + FieldInfo::FieldFlags fflags(0); + fflags.update_injected(true); + AccessFlags aflags; + FieldInfo fi(aflags, + (u2)vmSymbols::as_int(VM_SYMBOL_ENUM_NAME(empty_marker_name)), + (u2)vmSymbols::as_int(VM_SYMBOL_ENUM_NAME(byte_signature)), + 0, + fflags); + int idx = _field_info->append(fi); + _field_info->adr_at(idx)->set_index(idx); + } + } + } + + prologue(); + inline_class_field_sorting(); + + assert(_layout->start()->block_kind() == LayoutRawBlock::RESERVED, "Unexpected"); + + if (_layout->super_has_fields() && !_is_abstract_value) { // non-static field layout + if (!_has_nonstatic_fields) { + assert(_is_abstract_value, "Concrete value types have at least one field"); + // Nothing to do + } else { + // decide which alignment to use, then set first allowed field offset + + assert(_layout->super_alignment() >= _payload_alignment, "Incompatible alignment"); + assert(_layout->super_alignment() % _payload_alignment == 0, "Incompatible alignment"); + + if (_payload_alignment < _layout->super_alignment()) { + int new_alignment = _payload_alignment > _layout->super_min_align_required() ? _payload_alignment : _layout->super_min_align_required(); + assert(new_alignment % _payload_alignment == 0, "Must be"); + assert(new_alignment % _layout->super_min_align_required() == 0, "Must be"); + _payload_alignment = new_alignment; + } + _layout->set_start(_layout->first_field_block()); + } + } else { + if (_is_abstract_value && _has_nonstatic_fields) { + _payload_alignment = type2aelembytes(BasicType::T_LONG); + } + assert(_layout->start()->next_block()->block_kind() == LayoutRawBlock::EMPTY || !UseCompressedClassPointers, "Unexpected"); + LayoutRawBlock* first_empty = _layout->start()->next_block(); + if (first_empty->offset() % _payload_alignment != 0) { + LayoutRawBlock* padding = new LayoutRawBlock(LayoutRawBlock::PADDING, _payload_alignment - (first_empty->offset() % _payload_alignment)); + _layout->insert(first_empty, padding); + if (first_empty->size() == 0) { + _layout->remove(first_empty); + } + _layout->set_start(padding); + } + } + + _layout->add(_root_group->big_primitive_fields()); + _layout->add(_root_group->oop_fields()); + _layout->add(_root_group->small_primitive_fields()); + + LayoutRawBlock* first_field = _layout->first_field_block(); + if (first_field != nullptr) { + _payload_offset = _layout->first_field_block()->offset(); + _payload_size_in_bytes = _layout->last_block()->offset() - _layout->first_field_block()->offset(); + } else { + assert(_is_abstract_value, "Concrete inline types must have at least one field"); + _payload_offset = _layout->blocks()->size(); + _payload_size_in_bytes = 0; + } + + // Determining if the value class is naturally atomic: + if ((!_layout->super_has_fields() && _declared_non_static_fields_count <= 1 && !_has_non_naturally_atomic_fields) + || (_layout->super_has_fields() && _super_klass->is_naturally_atomic() && _declared_non_static_fields_count == 0)) { + _is_naturally_atomic = true; + } + + // At this point, the characteristics of the raw layout (used in standalone instances) are known. + // From this, additional layouts will be computed: atomic and nullable layouts + // Once those additional layouts are computed, the raw layout might need some adjustments + + bool vm_uses_flattening = UseFieldFlattening || UseArrayFlattening; + + if (!_is_abstract_value && vm_uses_flattening) { // Flat layouts are only for concrete value classes + // Validation of the non atomic layout + if (UseNonAtomicValueFlattening && !AlwaysAtomicAccesses && (!_must_be_atomic || _is_naturally_atomic)) { + _non_atomic_layout_size_in_bytes = _payload_size_in_bytes; + _non_atomic_layout_alignment = _payload_alignment; + } + + // Next step is to compute the characteristics for a layout enabling atomic updates + if (UseAtomicValueFlattening) { + int atomic_size = _payload_size_in_bytes == 0 ? 0 : round_up_power_of_2(_payload_size_in_bytes); + if (atomic_size <= (int)MAX_ATOMIC_OP_SIZE) { + _atomic_layout_size_in_bytes = atomic_size; + } + } + + // Next step is the nullable layout: the layout must include a null marker and must also be atomic + if (UseNullableValueFlattening) { + // Looking if there's an empty slot inside the layout that could be used to store a null marker + // FIXME: could it be possible to re-use the .empty field as a null marker for empty values? + LayoutRawBlock* b = _layout->first_field_block(); + assert(b != nullptr, "A concrete value class must have at least one (possible dummy) field"); + int null_marker_offset = -1; + if (_is_empty_inline_class) { + // Reusing the dummy field as a field marker + assert(_field_info->adr_at(b->field_index())->name(_constant_pool) == vmSymbols::empty_marker_name(), "b must be the dummy field"); + null_marker_offset = b->offset(); + } else { + while (b != _layout->last_block()) { + if (b->block_kind() == LayoutRawBlock::EMPTY) { + break; + } + b = b->next_block(); + } + if (b != _layout->last_block()) { + // found an empty slot, register its offset from the beginning of the payload + null_marker_offset = b->offset(); + LayoutRawBlock* marker = new LayoutRawBlock(LayoutRawBlock::NULL_MARKER, 1); + _layout->add_field_at_offset(marker, b->offset()); + } + if (null_marker_offset == -1) { // no empty slot available to store the null marker, need to inject one + int last_offset = _layout->last_block()->offset(); + LayoutRawBlock* marker = new LayoutRawBlock(LayoutRawBlock::NULL_MARKER, 1); + _layout->insert_field_block(_layout->last_block(), marker); + assert(marker->offset() == last_offset, "Null marker should have been inserted at the end"); + null_marker_offset = marker->offset(); + } + } + + // Now that the null marker is there, the size of the nullable layout must computed (remember, must be atomic too) + int new_raw_size = _layout->last_block()->offset() - _layout->first_field_block()->offset(); + int nullable_size = round_up_power_of_2(new_raw_size); + if (nullable_size <= (int)MAX_ATOMIC_OP_SIZE) { + _nullable_layout_size_in_bytes = nullable_size; + _null_marker_offset = null_marker_offset; + } else { + // If the nullable layout is rejected, the NULL_MARKER block should be removed + // from the layout, otherwise it will appear anyway if the layout is printer + if (!_is_empty_inline_class) { // empty values don't have a dedicated NULL_MARKER block + _layout->remove_null_marker(); + } + _null_marker_offset = -1; + } + } + // If the inline class has an atomic or nullable (which is also atomic) layout, + // we want the raw layout to have the same alignment as those atomic layouts so access codes + // could remain simple (single instruction without intermediate copy). This might required + // to shift all fields in the raw layout, but this operation is possible only if the class + // doesn't have inherited fields (offsets of inherited fields cannot be changed). If a + // field shift is needed but not possible, all atomic layouts are disabled and only reference + // and loosely consistent are supported. + int required_alignment = _payload_alignment; + if (has_atomic_layout() && _payload_alignment < atomic_layout_size_in_bytes()) { + required_alignment = atomic_layout_size_in_bytes(); + } + if (has_nullable_atomic_layout() && _payload_alignment < nullable_layout_size_in_bytes()) { + required_alignment = nullable_layout_size_in_bytes(); + } + int shift = first_field->offset() % required_alignment; + if (shift != 0) { + if (required_alignment > _payload_alignment && !_layout->has_inherited_fields()) { + assert(_layout->first_field_block() != nullptr, "A concrete value class must have at least one (possible dummy) field"); + _layout->shift_fields(shift); + _payload_offset = _layout->first_field_block()->offset(); + if (has_nullable_atomic_layout()) { + assert(!_is_empty_inline_class, "Should not get here with empty values"); + _null_marker_offset = _layout->find_null_marker()->offset(); + } + _payload_alignment = required_alignment; + } else { + _atomic_layout_size_in_bytes = -1; + if (has_nullable_atomic_layout() && !_is_empty_inline_class) { // empty values don't have a dedicated NULL_MARKER block + _layout->remove_null_marker(); + } + _nullable_layout_size_in_bytes = -1; + _null_marker_offset = -1; + } + } else { + _payload_alignment = required_alignment; + } + + // If the inline class has a nullable layout, the layout used in heap allocated standalone + // instances must also be the nullable layout, in order to be able to set the null marker to + // non-null before copying the payload to other containers. + if (has_nullable_atomic_layout() && payload_layout_size_in_bytes() < nullable_layout_size_in_bytes()) { + _payload_size_in_bytes = nullable_layout_size_in_bytes(); + } + } + // Warning:: InstanceMirrorKlass expects static oops to be allocated first + _static_layout->add_contiguously(_static_fields->oop_fields()); + _static_layout->add(_static_fields->big_primitive_fields()); + _static_layout->add(_static_fields->small_primitive_fields()); + + epilogue(); +} + +void FieldLayoutBuilder::add_flat_field_oopmap(OopMapBlocksBuilder* nonstatic_oop_maps, + InlineKlass* vklass, int offset) { + int diff = offset - vklass->payload_offset(); + const OopMapBlock* map = vklass->start_of_nonstatic_oop_maps(); + const OopMapBlock* last_map = map + vklass->nonstatic_oop_map_count(); + while (map < last_map) { + nonstatic_oop_maps->add(map->offset() + diff, map->count()); + map++; } +} - if (_root_group->oop_fields() != nullptr) { - for (int i = 0; i < _root_group->oop_fields()->length(); i++) { - LayoutRawBlock* b = _root_group->oop_fields()->at(i); +void FieldLayoutBuilder::register_embedded_oops_from_list(OopMapBlocksBuilder* nonstatic_oop_maps, GrowableArray* list) { + if (list == nullptr) return; + for (int i = 0; i < list->length(); i++) { + LayoutRawBlock* f = list->at(i); + if (f->block_kind() == LayoutRawBlock::FLAT) { + InlineKlass* vk = f->inline_klass(); + assert(vk != nullptr, "Should have been initialized"); + if (vk->contains_oops()) { + add_flat_field_oopmap(nonstatic_oop_maps, vk, f->offset()); + } + } + } +} + +void FieldLayoutBuilder::register_embedded_oops(OopMapBlocksBuilder* nonstatic_oop_maps, FieldGroup* group) { + if (group->oop_fields() != nullptr) { + for (int i = 0; i < group->oop_fields()->length(); i++) { + LayoutRawBlock* b = group->oop_fields()->at(i); nonstatic_oop_maps->add(b->offset(), 1); } } + register_embedded_oops_from_list(nonstatic_oop_maps, group->big_primitive_fields()); + register_embedded_oops_from_list(nonstatic_oop_maps, group->small_primitive_fields()); +} +void FieldLayoutBuilder::epilogue() { + // Computing oopmaps + OopMapBlocksBuilder* nonstatic_oop_maps = + new OopMapBlocksBuilder(_nonstatic_oopmap_count); + int super_oop_map_count = (_super_klass == nullptr) ? 0 :_super_klass->nonstatic_oop_map_count(); + if (super_oop_map_count > 0) { + nonstatic_oop_maps->initialize_inherited_blocks(_super_klass->start_of_nonstatic_oop_maps(), + _super_klass->nonstatic_oop_map_count()); + } + register_embedded_oops(nonstatic_oop_maps, _root_group); if (!_contended_groups.is_empty()) { for (int i = 0; i < _contended_groups.length(); i++) { FieldGroup* cg = _contended_groups.at(i); if (cg->oop_count() > 0) { assert(cg->oop_fields() != nullptr && cg->oop_fields()->at(0) != nullptr, "oop_count > 0 but no oop fields found"); - nonstatic_oop_maps->add(cg->oop_fields()->at(0)->offset(), cg->oop_count()); + register_embedded_oops(nonstatic_oop_maps, cg); } } } - nonstatic_oop_maps->compact(); int instance_end = align_up(_layout->last_block()->offset(), wordSize); @@ -700,19 +1322,106 @@ void FieldLayoutBuilder::epilogue() { _info->_static_field_size = static_fields_size; _info->_nonstatic_field_size = (nonstatic_field_end - instanceOopDesc::base_offset_in_bytes()) / heapOopSize; _info->_has_nonstatic_fields = _has_nonstatic_fields; + _info->_has_inline_fields = _has_inline_type_fields; + _info->_is_naturally_atomic = _is_naturally_atomic; + if (_is_inline_type) { + _info->_must_be_atomic = _must_be_atomic; + _info->_payload_alignment = _payload_alignment; + _info->_payload_offset = _payload_offset; + _info->_payload_size_in_bytes = _payload_size_in_bytes; + _info->_non_atomic_size_in_bytes = _non_atomic_layout_size_in_bytes; + _info->_non_atomic_alignment = _non_atomic_layout_alignment; + _info->_atomic_layout_size_in_bytes = _atomic_layout_size_in_bytes; + _info->_nullable_layout_size_in_bytes = _nullable_layout_size_in_bytes; + _info->_null_marker_offset = _null_marker_offset; + _info->_null_reset_value_offset = _static_layout->null_reset_value_offset(); + _info->_is_empty_inline_klass = _is_empty_inline_class; + } + + // This may be too restrictive, since if all the fields fit in 64 + // bits we could make the decision to align instances of this class + // to 64-bit boundaries, and load and store them as single words. + // And on machines which supported larger atomics we could similarly + // allow larger values to be atomic, if properly aligned. + +#ifdef ASSERT + // Tests verifying integrity of field layouts are using the output of -XX:+PrintFieldLayout + // which prints the details of LayoutRawBlocks used to compute the layout. + // The code below checks that offsets in the _field_info meta-data match offsets + // in the LayoutRawBlocks + LayoutRawBlock* b = _layout->blocks(); + while(b != _layout->last_block()) { + if (b->block_kind() == LayoutRawBlock::REGULAR || b->block_kind() == LayoutRawBlock::FLAT) { + if (_field_info->adr_at(b->field_index())->offset() != (u4)b->offset()) { + tty->print_cr("Offset from field info = %d, offset from block = %d", (int)_field_info->adr_at(b->field_index())->offset(), b->offset()); + } + assert(_field_info->adr_at(b->field_index())->offset() == (u4)b->offset()," Must match"); + } + b = b->next_block(); + } + b = _static_layout->blocks(); + while(b != _static_layout->last_block()) { + if (b->block_kind() == LayoutRawBlock::REGULAR || b->block_kind() == LayoutRawBlock::FLAT) { + assert(_field_info->adr_at(b->field_index())->offset() == (u4)b->offset()," Must match"); + } + b = b->next_block(); + } +#endif // ASSERT - if (PrintFieldLayout) { + static bool first_layout_print = true; + + + if (PrintFieldLayout || (PrintInlineLayout && _has_flattening_information)) { ResourceMark rm; - tty->print_cr("Layout of class %s", _classname->as_C_string()); - tty->print_cr("Instance fields:"); - _layout->print(tty, false, _super_klass); - tty->print_cr("Static fields:"); - _static_layout->print(tty, true, nullptr); - tty->print_cr("Instance size = %d bytes", _info->_instance_size * wordSize); - tty->print_cr("---"); + stringStream st; + if (first_layout_print) { + st.print_cr("Field layout log format: @offset size/alignment [name] [signature] [comment]"); + st.print_cr("Heap oop size = %d", heapOopSize); + first_layout_print = false; + } + if (_super_klass != nullptr) { + st.print_cr("Layout of class %s@%p extends %s@%p", _classname->as_C_string(), + _loader_data, _super_klass->name()->as_C_string(), _super_klass->class_loader_data()); + } else { + st.print_cr("Layout of class %s@%p", _classname->as_C_string(), _loader_data); + } + st.print_cr("Instance fields:"); + _layout->print(&st, false, _super_klass, _inline_layout_info_array); + st.print_cr("Static fields:"); + _static_layout->print(&st, true, nullptr, _inline_layout_info_array); + st.print_cr("Instance size = %d bytes", _info->_instance_size * wordSize); + if (_is_inline_type) { + st.print_cr("First field offset = %d", _payload_offset); + st.print_cr("Payload layout: %d/%d", _payload_size_in_bytes, _payload_alignment); + if (has_non_atomic_flat_layout()) { + st.print_cr("Non atomic flat layout: %d/%d", _non_atomic_layout_size_in_bytes, _non_atomic_layout_alignment); + } else { + st.print_cr("Non atomic flat layout: -/-"); + } + if (has_atomic_layout()) { + st.print_cr("Atomic flat layout: %d/%d", _atomic_layout_size_in_bytes, _atomic_layout_size_in_bytes); + } else { + st.print_cr("Atomic flat layout: -/-"); + } + if (has_nullable_atomic_layout()) { + st.print_cr("Nullable flat layout: %d/%d", _nullable_layout_size_in_bytes, _nullable_layout_size_in_bytes); + } else { + st.print_cr("Nullable flat layout: -/-"); + } + if (_null_marker_offset != -1) { + st.print_cr("Null marker offset = %d", _null_marker_offset); + } + } + st.print_cr("---"); + // Print output all together. + tty->print_raw(st.as_string()); } } void FieldLayoutBuilder::build_layout() { - compute_regular_layout(); + if (_is_inline_type || _is_abstract_value) { + compute_inline_class_layout(); + } else { + compute_regular_layout(); + } } diff --git a/src/hotspot/share/classfile/fieldLayoutBuilder.hpp b/src/hotspot/share/classfile/fieldLayoutBuilder.hpp index 82bbaefc623..0c857f84412 100644 --- a/src/hotspot/share/classfile/fieldLayoutBuilder.hpp +++ b/src/hotspot/share/classfile/fieldLayoutBuilder.hpp @@ -29,11 +29,12 @@ #include "classfile/classLoaderData.hpp" #include "memory/allocation.hpp" #include "oops/fieldStreams.hpp" +#include "oops/inlineKlass.hpp" +#include "oops/instanceKlass.hpp" #include "utilities/growableArray.hpp" // Classes below are used to compute the field layout of classes. - // A LayoutRawBlock describes an element of a layout. // Each field is represented by a LayoutRawBlock. // LayoutRawBlocks can also represent elements injected by the JVM: @@ -50,36 +51,43 @@ // next/prev pointers are included in the LayoutRawBlock class to narrow // the number of allocation required during the computation of a layout. // + +#define MAX_ATOMIC_OP_SIZE sizeof(uint64_t) + class LayoutRawBlock : public ResourceObj { public: // Some code relies on the order of values below. enum Kind { - EMPTY, // empty slot, space is taken from this to allocate fields - RESERVED, // reserved for JVM usage (for instance object header) - PADDING, // padding (because of alignment constraints or @Contended) - REGULAR, // primitive or oop field (including non-flattened inline fields) - FLATTENED, // flattened field - INHERITED // field(s) inherited from super classes + EMPTY, // empty slot, space is taken from this to allocate fields + RESERVED, // reserved for JVM usage (for instance object header) + PADDING, // padding (because of alignment constraints or @Contended) + REGULAR, // primitive or oop field (including not flat inline type fields) + FLAT, // flat field + INHERITED, // field(s) inherited from super classes + NULL_MARKER // stores the null marker for a flat field }; private: LayoutRawBlock* _next_block; LayoutRawBlock* _prev_block; - Kind _kind; + InlineKlass* _inline_klass; + Kind _block_kind; + LayoutKind _layout_kind; int _offset; int _alignment; int _size; int _field_index; - bool _is_reference; public: LayoutRawBlock(Kind kind, int size); - LayoutRawBlock(int index, Kind kind, int size, int alignment, bool is_reference = false); + + LayoutRawBlock(int index, Kind kind, int size, int alignment); LayoutRawBlock* next_block() const { return _next_block; } void set_next_block(LayoutRawBlock* next) { _next_block = next; } LayoutRawBlock* prev_block() const { return _prev_block; } void set_prev_block(LayoutRawBlock* prev) { _prev_block = prev; } - Kind kind() const { return _kind; } + Kind block_kind() const { return _block_kind; } + void set_block_kind(LayoutRawBlock::Kind kind) { _block_kind = kind; } // Dangerous operation, is only used by remove_null_marker(); int offset() const { assert(_offset >= 0, "Must be initialized"); return _offset; @@ -92,7 +100,18 @@ class LayoutRawBlock : public ResourceObj { assert(_field_index != -1, "Must be initialized"); return _field_index; } - bool is_reference() const { return _is_reference; } + void set_field_index(int field_index) { + assert(_field_index == -1, "Must not be initialized"); + _field_index = field_index; + } + InlineKlass* inline_klass() const { + assert(_inline_klass != nullptr, "Must be initialized"); + return _inline_klass; + } + void set_inline_klass(InlineKlass* inline_klass) { _inline_klass = inline_klass; } + + LayoutKind layout_kind() const { return _layout_kind; } + void set_layout_kind(LayoutKind kind) { _layout_kind = kind; } bool fit(int size, int alignment); @@ -109,19 +128,20 @@ class LayoutRawBlock : public ResourceObj { } return diff; } - }; // A Field group represents a set of fields that have to be allocated together, // this is the way the @Contended annotation is supported. // Inside a FieldGroup, fields are sorted based on their kind: primitive, -// oop, or flattened. +// oop, or flat. // class FieldGroup : public ResourceObj { private: FieldGroup* _next; - GrowableArray* _primitive_fields; + + GrowableArray* _small_primitive_fields; + GrowableArray* _big_primitive_fields; GrowableArray* _oop_fields; int _contended_group; int _oop_count; @@ -132,14 +152,20 @@ class FieldGroup : public ResourceObj { FieldGroup* next() const { return _next; } void set_next(FieldGroup* next) { _next = next; } - GrowableArray* primitive_fields() const { return _primitive_fields; } + GrowableArray* small_primitive_fields() const { return _small_primitive_fields; } + GrowableArray* big_primitive_fields() const { return _big_primitive_fields; } GrowableArray* oop_fields() const { return _oop_fields; } int contended_group() const { return _contended_group; } int oop_count() const { return _oop_count; } void add_primitive_field(int idx, BasicType type); void add_oop_field(int idx); + void add_flat_field(int idx, InlineKlass* vk, LayoutKind lk, int size, int alignment); + void add_block(LayoutRawBlock** list, LayoutRawBlock* block); void sort_by_size(); + private: + void add_to_small_primitive_list(LayoutRawBlock* block); + void add_to_big_primitive_list(LayoutRawBlock* block); }; // The FieldLayout class represents a set of fields organized @@ -161,27 +187,45 @@ class FieldGroup : public ResourceObj { class FieldLayout : public ResourceObj { private: GrowableArray* _field_info; + Array* _inline_layout_info_array; ConstantPool* _cp; LayoutRawBlock* _blocks; // the layout being computed LayoutRawBlock* _start; // points to the first block where a field can be inserted LayoutRawBlock* _last; // points to the last block of the layout (big empty block) + int _super_first_field_offset; + int _super_alignment; + int _super_min_align_required; + int _null_reset_value_offset; // offset of the reset value in class mirror, only for static layout of inline classes + bool _super_has_fields; + bool _has_inherited_fields; public: - FieldLayout(GrowableArray* field_info, ConstantPool* cp); + FieldLayout(GrowableArray* field_info, Array* inline_layout_info_array, ConstantPool* cp); void initialize_static_layout(); void initialize_instance_layout(const InstanceKlass* ik, bool& super_ends_with_oop); LayoutRawBlock* first_empty_block() { LayoutRawBlock* block = _start; - while (block->kind() != LayoutRawBlock::EMPTY) { + while (block->block_kind() != LayoutRawBlock::EMPTY) { block = block->next_block(); } return block; } - LayoutRawBlock* start() { return _start; } + LayoutRawBlock* blocks() const { return _blocks; } + + LayoutRawBlock* start() const { return _start; } void set_start(LayoutRawBlock* start) { _start = start; } - LayoutRawBlock* last_block() { return _last; } + LayoutRawBlock* last_block() const { return _last; } + int super_first_field_offset() const { return _super_first_field_offset; } + int super_alignment() const { return _super_alignment; } + int super_min_align_required() const { return _super_min_align_required; } + int null_reset_value_offset() const { + assert(_null_reset_value_offset != -1, "Must have been set"); + return _null_reset_value_offset; + } + bool super_has_fields() const { return _super_has_fields; } + bool has_inherited_fields() const { return _has_inherited_fields; } LayoutRawBlock* first_field_block(); void add(GrowableArray* list, LayoutRawBlock* start = nullptr); @@ -192,16 +236,18 @@ class FieldLayout : public ResourceObj { void fill_holes(const InstanceKlass* ik); LayoutRawBlock* insert(LayoutRawBlock* slot, LayoutRawBlock* block); void remove(LayoutRawBlock* block); - void print(outputStream* output, bool is_static, const InstanceKlass* super); + void shift_fields(int shift); + LayoutRawBlock* find_null_marker(); + void remove_null_marker(); + void print(outputStream* output, bool is_static, const InstanceKlass* super, Array* inline_fields); }; // FieldLayoutBuilder is the main entry point for layout computation. -// This class has three methods to generate layout: one for regular classes -// and two for classes with hard coded offsets (java,lang.ref.Reference -// and the boxing classes). The rationale for having multiple methods -// is that each kind of class has a different set goals regarding -// its layout, so instead of mixing several layout strategies into a +// This class has two methods to generate layout: one for identity classes +// and one for inline classes. The rational for having two methods +// is that each kind of classes has a different set goals regarding +// its layout, so instead of mixing two layout strategies into a // single method, each kind has its own method (see comments below // for more details about the allocation strategies). // @@ -218,45 +264,79 @@ class FieldLayout : public ResourceObj { // static field size, non-static field size, etc.) // // Steps 1 and 4 are common to all layout computations. Step 2 and 3 -// can vary with the allocation strategy. +// differ for inline classes and identity classes. // class FieldLayoutBuilder : public ResourceObj { - private: + private: const Symbol* _classname; + ClassLoaderData* _loader_data; const InstanceKlass* _super_klass; ConstantPool* _constant_pool; GrowableArray* _field_info; FieldLayoutInfo* _info; + Array* _inline_layout_info_array; FieldGroup* _root_group; GrowableArray _contended_groups; FieldGroup* _static_fields; FieldLayout* _layout; FieldLayout* _static_layout; int _nonstatic_oopmap_count; - int _alignment; + int _payload_alignment; + int _payload_offset; + int _null_marker_offset; // if any, -1 means no internal null marker + int _payload_size_in_bytes; + int _non_atomic_layout_size_in_bytes; + int _non_atomic_layout_alignment; + int _atomic_layout_size_in_bytes; + int _nullable_layout_size_in_bytes; + int _fields_size_sum; + int _declared_non_static_fields_count; + bool _has_non_naturally_atomic_fields; + bool _is_naturally_atomic; + bool _must_be_atomic; bool _has_nonstatic_fields; - bool _is_contended; // is a contended class? + bool _has_inline_type_fields; + bool _is_contended; bool _super_ends_with_oop; + bool _is_inline_type; + bool _is_abstract_value; + bool _has_flattening_information; + bool _is_empty_inline_class; + + FieldGroup* get_or_create_contended_group(int g); public: - FieldLayoutBuilder(const Symbol* classname, const InstanceKlass* super_klass, ConstantPool* constant_pool, - GrowableArray* field_info, bool is_contended, FieldLayoutInfo* info); + FieldLayoutBuilder(const Symbol* classname, ClassLoaderData* loader_data, const InstanceKlass* super_klass, ConstantPool* constant_pool, + GrowableArray* field_info, bool is_contended, bool is_inline_type, bool is_abstract_value, + bool must_be_atomic, FieldLayoutInfo* info, Array* inline_layout_info_array); - int get_alignment() { - assert(_alignment != -1, "Uninitialized"); - return _alignment; - } + int payload_offset() const { assert(_payload_offset != -1, "Uninitialized"); return _payload_offset; } + int payload_layout_size_in_bytes() const { return _payload_size_in_bytes; } + int payload_layout_alignment() const { assert(_payload_alignment != -1, "Uninitialized"); return _payload_alignment; } + bool has_non_atomic_flat_layout() const { return _non_atomic_layout_size_in_bytes != -1; } + int non_atomic_layout_size_in_bytes() const { return _non_atomic_layout_size_in_bytes; } + int non_atomic_layout_alignment() const { return _non_atomic_layout_alignment; } + bool has_atomic_layout() const { return _atomic_layout_size_in_bytes != -1; } + int atomic_layout_size_in_bytes() const { return _atomic_layout_size_in_bytes; } + bool has_nullable_atomic_layout() const { return _nullable_layout_size_in_bytes != -1; } + int nullable_layout_size_in_bytes() const { return _nullable_layout_size_in_bytes; } + int null_marker_offset() const { return _null_marker_offset; } + bool is_empty_inline_class() const { return _is_empty_inline_class; } void build_layout(); void compute_regular_layout(); + void compute_inline_class_layout(); void insert_contended_padding(LayoutRawBlock* slot); - private: + protected: void prologue(); void epilogue(); void regular_field_sorting(); - FieldGroup* get_or_create_contended_group(int g); + void inline_class_field_sorting(); + void add_flat_field_oopmap(OopMapBlocksBuilder* nonstatic_oop_map, InlineKlass* vk, int offset); + void register_embedded_oops_from_list(OopMapBlocksBuilder* nonstatic_oop_maps, GrowableArray* list); + void register_embedded_oops(OopMapBlocksBuilder* nonstatic_oop_maps, FieldGroup* group); }; #endif // SHARE_CLASSFILE_FIELDLAYOUTBUILDER_HPP diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index 683fc22b27a..e13b63b6824 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -53,8 +53,10 @@ #include "memory/universe.hpp" #include "oops/fieldInfo.hpp" #include "oops/fieldStreams.inline.hpp" +#include "oops/flatArrayKlass.hpp" +#include "oops/inlineKlass.inline.hpp" #include "oops/instanceKlass.inline.hpp" -#include "oops/instanceMirrorKlass.hpp" +#include "oops/instanceMirrorKlass.inline.hpp" #include "oops/klass.inline.hpp" #include "oops/method.inline.hpp" #include "oops/objArrayKlass.hpp" @@ -1085,7 +1087,15 @@ void java_lang_Class::allocate_mirror(Klass* k, bool is_scratch, Handle protecti // It might also have a component mirror. This mirror must already exist. if (k->is_array_klass()) { - if (k->is_typeArray_klass()) { + if (k->is_flatArray_klass()) { + Klass* element_klass = (Klass*) FlatArrayKlass::cast(k)->element_klass(); + assert(element_klass->is_inline_klass(), "Must be inline type component"); + if (is_scratch) { + comp_mirror = Handle(THREAD, HeapShared::scratch_java_mirror(element_klass)); + } else { + comp_mirror = Handle(THREAD, element_klass->java_mirror()); + } + } else if (k->is_typeArray_klass()) { BasicType type = TypeArrayKlass::cast(k)->element_type(); if (is_scratch) { comp_mirror = Handle(THREAD, HeapShared::scratch_java_mirror(type)); @@ -1094,12 +1104,14 @@ void java_lang_Class::allocate_mirror(Klass* k, bool is_scratch, Handle protecti } } else { assert(k->is_objArray_klass(), "Must be"); + assert(!k->is_refArray_klass() || !k->is_flatArray_klass(), "Must not have mirror"); Klass* element_klass = ObjArrayKlass::cast(k)->element_klass(); assert(element_klass != nullptr, "Must have an element klass"); + oop comp_oop = element_klass->java_mirror(); if (is_scratch) { comp_mirror = Handle(THREAD, HeapShared::scratch_java_mirror(element_klass)); } else { - comp_mirror = Handle(THREAD, element_klass->java_mirror()); + comp_mirror = Handle(THREAD, comp_oop); } } assert(comp_mirror() != nullptr, "must have a mirror"); @@ -1129,10 +1141,18 @@ void java_lang_Class::create_mirror(Klass* k, Handle class_loader, Handle classData, TRAPS) { assert(k != nullptr, "Use create_basic_type_mirror for primitive types"); assert(k->java_mirror() == nullptr, "should only assign mirror once"); - // Class_klass has to be loaded because it is used to allocate // the mirror. if (vmClasses::Class_klass_loaded()) { + + if (k->is_refined_objArray_klass()) { + Klass* super_klass = k->super(); + assert(super_klass != nullptr, "Must be"); + Handle mirror(THREAD, super_klass->java_mirror()); + k->set_java_mirror(mirror); + return; + } + Handle mirror; Handle comp_mirror; @@ -1155,6 +1175,7 @@ void java_lang_Class::create_mirror(Klass* k, Handle class_loader, // concurrently doesn't expect a k to have a null java_mirror. release_set_array_klass(comp_mirror(), k); } + if (CDSConfig::is_dumping_heap()) { create_scratch_mirror(k, CHECK); } @@ -1177,9 +1198,9 @@ void java_lang_Class::create_mirror(Klass* k, Handle class_loader, // latter may contain dumptime-specific information that cannot be archived // (e.g., ClassLoaderData*, or static fields that are modified by Java code execution). void java_lang_Class::create_scratch_mirror(Klass* k, TRAPS) { - if (k->class_loader() != nullptr && - k->class_loader() != SystemDictionary::java_platform_loader() && - k->class_loader() != SystemDictionary::java_system_loader()) { + if ((k->class_loader() != nullptr && + k->class_loader() != SystemDictionary::java_platform_loader() && + k->class_loader() != SystemDictionary::java_system_loader())) { // We only archive the mirrors of classes loaded by the built-in loaders return; } @@ -1218,10 +1239,10 @@ bool java_lang_Class::restore_archived_mirror(Klass *k, // mirror is archived, restore log_debug(aot, mirror)("Archived mirror is: " PTR_FORMAT, p2i(m)); - assert(as_Klass(m) == k, "must be"); Handle mirror(THREAD, m); if (!k->is_array_klass()) { + assert(as_Klass(m) == k, "must be"); // - local static final fields with initial values were initialized at dump time // create the init_lock @@ -1231,6 +1252,10 @@ bool java_lang_Class::restore_archived_mirror(Klass *k, if (protection_domain.not_null()) { set_protection_domain(mirror(), protection_domain()); } + } else { + ObjArrayKlass* objarray_k = (ObjArrayKlass*)as_Klass(m); + // Mirror should be restored for an ObjArrayKlass or one of its refined array klasses + assert(objarray_k == k || objarray_k->next_refined_array_klass() == k, "must be"); } assert(class_loader() == k->class_loader(), "should be same"); @@ -1409,7 +1434,9 @@ void java_lang_Class::print_signature(oop java_class, outputStream* st) { st->print(""); return; } - if (is_instance) st->print("L"); + if (is_instance) { + st->print("L"); + } st->write((char*) name->base(), (int) name->utf8_length()); if (is_instance) st->print(";"); } @@ -1468,6 +1495,7 @@ Klass* java_lang_Class::array_klass_acquire(oop java_class) { void java_lang_Class::release_set_array_klass(oop java_class, Klass* klass) { assert(klass->is_klass() && klass->is_array_klass(), "should be array klass"); + assert(!klass->is_refined_objArray_klass(), "should not be ref or flat array klass"); java_class->release_metadata_field_put(_array_klass_offset, klass); } @@ -2867,7 +2895,7 @@ void java_lang_Throwable::fill_in_stack_trace(Handle throwable, const methodHand // skip methods of the exception class and superclasses // This is similar to classic VM. - if (method->name() == vmSymbols::object_initializer_name() && + if (method->is_object_constructor() && throwable->is_a(method->method_holder())) { continue; } else { @@ -3226,8 +3254,8 @@ void java_lang_ClassFrameInfo::serialize_offsets(SerializeClosure* f) { static int get_flags(const methodHandle& m) { int flags = m->access_flags().as_method_flags(); - if (m->is_object_initializer()) { - flags |= java_lang_invoke_MemberName::MN_IS_CONSTRUCTOR; + if (m->is_object_constructor()) { + flags |= java_lang_invoke_MemberName::MN_IS_OBJECT_CONSTRUCTOR; } else { // Note: Static initializers can be here. Record them as plain methods. flags |= java_lang_invoke_MemberName::MN_IS_METHOD; @@ -3618,7 +3646,7 @@ int java_lang_reflect_Field::_name_offset; int java_lang_reflect_Field::_type_offset; int java_lang_reflect_Field::_slot_offset; int java_lang_reflect_Field::_modifiers_offset; -int java_lang_reflect_Field::_trusted_final_offset; +int java_lang_reflect_Field::_flags_offset; int java_lang_reflect_Field::_signature_offset; int java_lang_reflect_Field::_annotations_offset; @@ -3628,7 +3656,7 @@ int java_lang_reflect_Field::_annotations_offset; macro(_type_offset, k, vmSymbols::type_name(), class_signature, false); \ macro(_slot_offset, k, vmSymbols::slot_name(), int_signature, false); \ macro(_modifiers_offset, k, vmSymbols::modifiers_name(), int_signature, false); \ - macro(_trusted_final_offset, k, vmSymbols::trusted_final_name(), bool_signature, false); \ + macro(_flags_offset, k, vmSymbols::flags_name(), int_signature, false); \ macro(_signature_offset, k, vmSymbols::signature_name(), string_signature, false); \ macro(_annotations_offset, k, vmSymbols::annotations_name(), byte_array_signature, false); @@ -3693,8 +3721,8 @@ void java_lang_reflect_Field::set_modifiers(oop field, int value) { field->int_field_put(_modifiers_offset, value); } -void java_lang_reflect_Field::set_trusted_final(oop field) { - field->bool_field_put(_trusted_final_offset, true); +void java_lang_reflect_Field::set_flags(oop field, int value) { + field->int_field_put(_flags_offset, value); } void java_lang_reflect_Field::set_signature(oop field, oop value) { @@ -3997,21 +4025,30 @@ bool java_lang_ref_Reference::is_referent_field(oop obj, ptrdiff_t offset) { return is_reference; } -int java_lang_boxing_object::_value_offset; -int java_lang_boxing_object::_long_value_offset; +int* java_lang_boxing_object::_offsets; -#define BOXING_FIELDS_DO(macro) \ - macro(_value_offset, integerKlass, "value", int_signature, false); \ - macro(_long_value_offset, longKlass, "value", long_signature, false); +#define BOXING_FIELDS_DO(macro) \ + macro(java_lang_boxing_object::_offsets[T_BOOLEAN - T_BOOLEAN], vmClasses::Boolean_klass(), "value", bool_signature, false); \ + macro(java_lang_boxing_object::_offsets[T_CHAR - T_BOOLEAN], vmClasses::Character_klass(), "value", char_signature, false); \ + macro(java_lang_boxing_object::_offsets[T_FLOAT - T_BOOLEAN], vmClasses::Float_klass(), "value", float_signature, false); \ + macro(java_lang_boxing_object::_offsets[T_DOUBLE - T_BOOLEAN], vmClasses::Double_klass(), "value", double_signature, false); \ + macro(java_lang_boxing_object::_offsets[T_BYTE - T_BOOLEAN], vmClasses::Byte_klass(), "value", byte_signature, false); \ + macro(java_lang_boxing_object::_offsets[T_SHORT - T_BOOLEAN], vmClasses::Short_klass(), "value", short_signature, false); \ + macro(java_lang_boxing_object::_offsets[T_INT - T_BOOLEAN], vmClasses::Integer_klass(), "value", int_signature, false); \ + macro(java_lang_boxing_object::_offsets[T_LONG - T_BOOLEAN], vmClasses::Long_klass(), "value", long_signature, false); void java_lang_boxing_object::compute_offsets() { - InstanceKlass* integerKlass = vmClasses::Integer_klass(); - InstanceKlass* longKlass = vmClasses::Long_klass(); + assert(T_LONG - T_BOOLEAN == 7, "Sanity check"); + java_lang_boxing_object::_offsets = NEW_C_HEAP_ARRAY(int, 8, mtInternal); BOXING_FIELDS_DO(FIELD_COMPUTE_OFFSET); } #if INCLUDE_CDS void java_lang_boxing_object::serialize_offsets(SerializeClosure* f) { + if (f->reading()) { + assert(T_LONG - T_BOOLEAN == 7, "Sanity check"); + java_lang_boxing_object::_offsets = NEW_C_HEAP_ARRAY(int, 8, mtInternal); + } BOXING_FIELDS_DO(FIELD_SERIALIZE_OFFSET); } #endif @@ -4032,28 +4069,28 @@ oop java_lang_boxing_object::create(BasicType type, jvalue* value, TRAPS) { if (box == nullptr) return nullptr; switch (type) { case T_BOOLEAN: - box->bool_field_put(_value_offset, value->z); + box->bool_field_put(value_offset(type), value->z); break; case T_CHAR: - box->char_field_put(_value_offset, value->c); + box->char_field_put(value_offset(type), value->c); break; case T_FLOAT: - box->float_field_put(_value_offset, value->f); + box->float_field_put(value_offset(type), value->f); break; case T_DOUBLE: - box->double_field_put(_long_value_offset, value->d); + box->double_field_put(value_offset(type), value->d); break; case T_BYTE: - box->byte_field_put(_value_offset, value->b); + box->byte_field_put(value_offset(type), value->b); break; case T_SHORT: - box->short_field_put(_value_offset, value->s); + box->short_field_put(value_offset(type), value->s); break; case T_INT: - box->int_field_put(_value_offset, value->i); + box->int_field_put(value_offset(type), value->i); break; case T_LONG: - box->long_field_put(_long_value_offset, value->j); + box->long_field_put(value_offset(type), value->j); break; default: return nullptr; @@ -4075,28 +4112,28 @@ BasicType java_lang_boxing_object::get_value(oop box, jvalue* value) { BasicType type = vmClasses::box_klass_type(box->klass()); switch (type) { case T_BOOLEAN: - value->z = box->bool_field(_value_offset); + value->z = box->bool_field(value_offset(type)); break; case T_CHAR: - value->c = box->char_field(_value_offset); + value->c = box->char_field(value_offset(type)); break; case T_FLOAT: - value->f = box->float_field(_value_offset); + value->f = box->float_field(value_offset(type)); break; case T_DOUBLE: - value->d = box->double_field(_long_value_offset); + value->d = box->double_field(value_offset(type)); break; case T_BYTE: - value->b = box->byte_field(_value_offset); + value->b = box->byte_field(value_offset(type)); break; case T_SHORT: - value->s = box->short_field(_value_offset); + value->s = box->short_field(value_offset(type)); break; case T_INT: - value->i = box->int_field(_value_offset); + value->i = box->int_field(value_offset(type)); break; case T_LONG: - value->j = box->long_field(_long_value_offset); + value->j = box->long_field(value_offset(type)); break; default: return T_ILLEGAL; @@ -4109,28 +4146,28 @@ BasicType java_lang_boxing_object::set_value(oop box, jvalue* value) { BasicType type = vmClasses::box_klass_type(box->klass()); switch (type) { case T_BOOLEAN: - box->bool_field_put(_value_offset, value->z); + box->bool_field_put(value_offset(type), value->z); break; case T_CHAR: - box->char_field_put(_value_offset, value->c); + box->char_field_put(value_offset(type), value->c); break; case T_FLOAT: - box->float_field_put(_value_offset, value->f); + box->float_field_put(value_offset(type), value->f); break; case T_DOUBLE: - box->double_field_put(_long_value_offset, value->d); + box->double_field_put(value_offset(type), value->d); break; case T_BYTE: - box->byte_field_put(_value_offset, value->b); + box->byte_field_put(value_offset(type), value->b); break; case T_SHORT: - box->short_field_put(_value_offset, value->s); + box->short_field_put(value_offset(type), value->s); break; case T_INT: - box->int_field_put(_value_offset, value->i); + box->int_field_put(value_offset(type), value->i); break; case T_LONG: - box->long_field_put(_long_value_offset, value->j); + box->long_field_put(value_offset(type), value->j); break; default: return T_ILLEGAL; @@ -4526,7 +4563,6 @@ void java_lang_invoke_MemberName::set_flags(oop mname, int flags) { mname->int_field_put(_flags_offset, flags); } - // Return vmtarget from ResolvedMethodName method field through indirection Method* java_lang_invoke_MemberName::vmtarget(oop mname) { assert(is_instance(mname), "wrong type"); @@ -4536,7 +4572,7 @@ Method* java_lang_invoke_MemberName::vmtarget(oop mname) { bool java_lang_invoke_MemberName::is_method(oop mname) { assert(is_instance(mname), "must be MemberName"); - return (flags(mname) & (MN_IS_METHOD | MN_IS_CONSTRUCTOR)) > 0; + return (flags(mname) & (MN_IS_METHOD | MN_IS_OBJECT_CONSTRUCTOR)) > 0; } void java_lang_invoke_MemberName::set_method(oop mname, oop resolved_method) { @@ -5529,22 +5565,17 @@ bool JavaClasses::check_offset(const char *klass_name, int deserialized_offset, void JavaClasses::check_offsets() { bool valid = true; -#define CHECK_OFFSET(klass_name, cpp_klass_name, field_name, field_sig) \ - valid &= check_offset(klass_name, cpp_klass_name :: _##field_name ## _offset, #field_name, field_sig) - -#define CHECK_LONG_OFFSET(klass_name, cpp_klass_name, field_name, field_sig) \ - valid &= check_offset(klass_name, cpp_klass_name :: _##long_ ## field_name ## _offset, #field_name, field_sig) - - // Boxed primitive objects (java_lang_boxing_object) - - CHECK_OFFSET("java/lang/Boolean", java_lang_boxing_object, value, "Z"); - CHECK_OFFSET("java/lang/Character", java_lang_boxing_object, value, "C"); - CHECK_OFFSET("java/lang/Float", java_lang_boxing_object, value, "F"); - CHECK_LONG_OFFSET("java/lang/Double", java_lang_boxing_object, value, "D"); - CHECK_OFFSET("java/lang/Byte", java_lang_boxing_object, value, "B"); - CHECK_OFFSET("java/lang/Short", java_lang_boxing_object, value, "S"); - CHECK_OFFSET("java/lang/Integer", java_lang_boxing_object, value, "I"); - CHECK_LONG_OFFSET("java/lang/Long", java_lang_boxing_object, value, "J"); +#define CHECK_OFFSET(klass_name, type, field_sig) \ + valid &= check_offset(klass_name, java_lang_boxing_object::value_offset(type), "value", field_sig) + + CHECK_OFFSET("java/lang/Boolean", T_BOOLEAN, "Z"); + CHECK_OFFSET("java/lang/Character", T_CHAR, "C"); + CHECK_OFFSET("java/lang/Float", T_FLOAT, "F"); + CHECK_OFFSET("java/lang/Double", T_DOUBLE, "D"); + CHECK_OFFSET("java/lang/Byte", T_BYTE, "B"); + CHECK_OFFSET("java/lang/Short", T_SHORT, "S"); + CHECK_OFFSET("java/lang/Integer", T_INT, "I"); + CHECK_OFFSET("java/lang/Long", T_LONG, "J"); if (!valid) vm_exit_during_initialization("Field offset verification failed"); } diff --git a/src/hotspot/share/classfile/javaClasses.hpp b/src/hotspot/share/classfile/javaClasses.hpp index 70fa519f0f0..48deed785a1 100644 --- a/src/hotspot/share/classfile/javaClasses.hpp +++ b/src/hotspot/share/classfile/javaClasses.hpp @@ -251,6 +251,7 @@ class java_lang_Class : AllStatic { static int _class_loader_offset; static int _module_offset; static int _component_mirror_offset; + static int _name_offset; static int _source_file_offset; static int _classData_offset; @@ -269,6 +270,7 @@ class java_lang_Class : AllStatic { static void set_protection_domain(oop java_class, oop protection_domain); static void set_class_loader(oop java_class, oop class_loader); static void set_component_mirror(oop java_class, oop comp_mirror); + static void initialize_mirror_fields(Klass* k, Handle mirror, Handle protection_domain, Handle classData, TRAPS); static void set_mirror_module_field(JavaThread* current, Klass* K, Handle mirror, Handle module); @@ -313,6 +315,7 @@ class java_lang_Class : AllStatic { // compiler support for class operations static int klass_offset() { CHECK_INIT(_klass_offset); } static int array_klass_offset() { CHECK_INIT(_array_klass_offset); } + // Support for classRedefinedCount field static int classRedefinedCount(oop the_class_mirror); static void set_classRedefinedCount(oop the_class_mirror, int value); @@ -827,7 +830,7 @@ class java_lang_reflect_Field : public java_lang_reflect_AccessibleObject { static int _type_offset; static int _slot_offset; static int _modifiers_offset; - static int _trusted_final_offset; + static int _flags_offset; static int _signature_offset; static int _annotations_offset; @@ -855,7 +858,7 @@ class java_lang_reflect_Field : public java_lang_reflect_AccessibleObject { static int modifiers(oop field); static void set_modifiers(oop field, int value); - static void set_trusted_final(oop field); + static void set_flags(oop field, int value); static void set_signature(oop constructor, oop value); static void set_annotations(oop constructor, oop value); @@ -974,8 +977,7 @@ class reflect_ConstantPool { class java_lang_boxing_object: AllStatic { private: - static int _value_offset; - static int _long_value_offset; + static int* _offsets; static void compute_offsets(); static oop initialize_and_allocate(BasicType type, TRAPS); @@ -992,7 +994,9 @@ class java_lang_boxing_object: AllStatic { static void print(BasicType type, jvalue* value, outputStream* st); static int value_offset(BasicType type) { - return is_double_word_type(type) ? _long_value_offset : _value_offset; + assert(type >= T_BOOLEAN && type <= T_LONG, "BasicType out of range"); + assert(_offsets != nullptr, "Uninitialized offsets"); + return _offsets[type - T_BOOLEAN]; } static void serialize_offsets(SerializeClosure* f); @@ -1354,14 +1358,17 @@ class java_lang_invoke_MemberName: AllStatic { // Relevant integer codes (keep these in synch. with MethodHandleNatives.Constants): enum { MN_IS_METHOD = 0x00010000, // method (not constructor) - MN_IS_CONSTRUCTOR = 0x00020000, // constructor + MN_IS_OBJECT_CONSTRUCTOR = 0x00020000, // constructor MN_IS_FIELD = 0x00040000, // field MN_IS_TYPE = 0x00080000, // nested type MN_CALLER_SENSITIVE = 0x00100000, // @CallerSensitive annotation detected MN_TRUSTED_FINAL = 0x00200000, // trusted final field MN_HIDDEN_MEMBER = 0x00400000, // @Hidden annotation detected + MN_NULL_RESTRICTED_FIELD = 0x00800000, // null-restricted field MN_REFERENCE_KIND_SHIFT = 24, // refKind - MN_REFERENCE_KIND_MASK = 0x0F000000 >> MN_REFERENCE_KIND_SHIFT, + MN_REFERENCE_KIND_MASK = 0x0F000000 >> MN_REFERENCE_KIND_SHIFT, // 4 bits + MN_LAYOUT_SHIFT = 28, // field layout + MN_LAYOUT_MASK = 0x70000000 >> MN_LAYOUT_SHIFT, // 3 bits MN_NESTMATE_CLASS = 0x00000001, MN_HIDDEN_CLASS = 0x00000002, MN_STRONG_LOADER_LINK = 0x00000004, @@ -1865,7 +1872,6 @@ class java_lang_Byte_ByteCache : AllStatic { static void serialize_offsets(SerializeClosure* f) NOT_CDS_RETURN; }; - // Interface to java.lang.InternalError objects #define INTERNALERROR_INJECTED_FIELDS(macro) \ diff --git a/src/hotspot/share/classfile/placeholders.cpp b/src/hotspot/share/classfile/placeholders.cpp index f4c381eafdf..fab090fd448 100644 --- a/src/hotspot/share/classfile/placeholders.cpp +++ b/src/hotspot/share/classfile/placeholders.cpp @@ -61,6 +61,8 @@ static InternalPlaceholderTable* _placeholders; // For DEFINE_CLASS, the head of the queue owns the // define token and the rest of the threads wait to return the // result the first thread gets. +// For DETECT_CIRCULARITY, set when loading super class, interfaces, or inline type +// fields for class circularity checking. class SeenThread: public CHeapObj { private: JavaThread* _thread; diff --git a/src/hotspot/share/classfile/placeholders.hpp b/src/hotspot/share/classfile/placeholders.hpp index 6348f76a14e..1bace6f1bbc 100644 --- a/src/hotspot/share/classfile/placeholders.hpp +++ b/src/hotspot/share/classfile/placeholders.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/hotspot/share/classfile/stackMapFrame.cpp b/src/hotspot/share/classfile/stackMapFrame.cpp index 4b847894e02..9f43aa7cc09 100644 --- a/src/hotspot/share/classfile/stackMapFrame.cpp +++ b/src/hotspot/share/classfile/stackMapFrame.cpp @@ -31,10 +31,10 @@ #include "runtime/handles.inline.hpp" #include "utilities/globalDefinitions.hpp" -StackMapFrame::StackMapFrame(u2 max_locals, u2 max_stack, ClassVerifier* v) : +StackMapFrame::StackMapFrame(u2 max_locals, u2 max_stack, AssertUnsetFieldTable* initial_strict_fields, ClassVerifier* v) : _offset(0), _locals_size(0), _stack_size(0), _stack_mark(0), _max_locals(max_locals), - _max_stack(max_stack), _flags(0), _verifier(v) { + _max_stack(max_stack), _flags(0), _assert_unset_fields(initial_strict_fields), _verifier(v) { Thread* thr = v->thread(); _locals = NEW_RESOURCE_ARRAY_IN_THREAD(thr, VerificationType, max_locals); _stack = NEW_RESOURCE_ARRAY_IN_THREAD(thr, VerificationType, max_stack); @@ -47,10 +47,47 @@ StackMapFrame::StackMapFrame(u2 max_locals, u2 max_stack, ClassVerifier* v) : } } +void StackMapFrame::unsatisfied_strict_fields_error(InstanceKlass* klass, int bci) { + Symbol* name; + Symbol* sig; + int num_uninit_fields = 0; + + auto find_unset = [&] (const NameAndSig& key, const bool& value) { + if (!value) { + name = key._name; + sig = key._signature; + num_uninit_fields++; + } + }; + assert_unset_fields()->iterate_all(find_unset); + + verifier()->verify_error( + ErrorContext::bad_strict_fields(bci, this), + "All strict final fields must be initialized before super(): %d field(s), %s:%s in %s", + num_uninit_fields, + name->as_C_string(), + sig->as_C_string(), + klass->name()->as_C_string() + ); +} + +void StackMapFrame::print_strict_fields(AssertUnsetFieldTable* table) { + ResourceMark rm; + auto printfields = [&] (const NameAndSig& key, const bool& value) { + log_info(verification)("Strict field: %s%s (Satisfied: %s)", + key._name->as_C_string(), + key._signature->as_C_string(), + value ? "true" : "false"); + }; + table->iterate_all(printfields); +} + StackMapFrame* StackMapFrame::frame_in_exception_handler(u1 flags) { Thread* thr = _verifier->thread(); VerificationType* stack = NEW_RESOURCE_ARRAY_IN_THREAD(thr, VerificationType, 1); - StackMapFrame* frame = new StackMapFrame(_offset, flags, _locals_size, 0, _max_locals, _max_stack, _locals, stack, _verifier); + StackMapFrame* frame = new StackMapFrame(_offset, flags, _locals_size, 0, + _max_locals, _max_stack, _locals, stack, + _assert_unset_fields, _verifier); return frame; } @@ -80,7 +117,7 @@ VerificationType StackMapFrame::set_locals_from_arg( if (!m->is_static()) { init_local_num++; // add one extra argument for instance method - if (m->name() == vmSymbols::object_initializer_name() && + if (m->is_object_constructor() && thisKlass.name() != vmSymbols::java_lang_Object()) { _locals[0] = VerificationType::uninitialized_this_type(); _flags |= FLAG_THIS_UNINIT; @@ -188,6 +225,16 @@ bool StackMapFrame::is_assignable_to( return false; } + // Check that assert unset fields are compatible + bool compatible = verify_unset_fields_compatibility(target->assert_unset_fields()); + if (!compatible) { + print_strict_fields(assert_unset_fields()); + print_strict_fields(target->assert_unset_fields()); + *ctx = ErrorContext::strict_fields_mismatch(target->offset(), + (StackMapFrame*)this, (StackMapFrame*)target); + return false; + } + if ((_flags | target->flags()) == target->flags()) { return true; } else { diff --git a/src/hotspot/share/classfile/stackMapFrame.hpp b/src/hotspot/share/classfile/stackMapFrame.hpp index 5958154dc50..980f483b6b4 100644 --- a/src/hotspot/share/classfile/stackMapFrame.hpp +++ b/src/hotspot/share/classfile/stackMapFrame.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -41,6 +41,21 @@ enum { }; class StackMapFrame : public ResourceObj { + public: + static unsigned int nameandsig_hash(NameAndSig const& field) { + Symbol* name = field._name; + return (unsigned int) name->identity_hash(); + } + + static inline bool nameandsig_equals(NameAndSig const& f1, NameAndSig const& f2) { + return f1._name == f2._name && + f1._signature == f2._signature; + } + + // Maps a strict field's name and signature to whether or not it was initialized + typedef HashTable AssertUnsetFieldTable; private: int32_t _offset; @@ -60,6 +75,8 @@ class StackMapFrame : public ResourceObj { VerificationType* _locals; // local variable type array VerificationType* _stack; // operand stack type array + AssertUnsetFieldTable* _assert_unset_fields; // List of unsatisfied strict fields in the basic block + ClassVerifier* _verifier; // the verifier verifying this method StackMapFrame(const StackMapFrame& cp) : @@ -85,6 +102,7 @@ class StackMapFrame : public ResourceObj { _stack[i] = VerificationType::bogus_type(); } } + _assert_unset_fields = cp._assert_unset_fields; _verifier = nullptr; } @@ -94,7 +112,7 @@ class StackMapFrame : public ResourceObj { // This constructor is used by the type checker to allocate frames // in type state, which have _max_locals and _max_stack array elements // in _locals and _stack. - StackMapFrame(u2 max_locals, u2 max_stack, ClassVerifier* verifier); + StackMapFrame(u2 max_locals, u2 max_stack, AssertUnsetFieldTable* initial_strict_fields, ClassVerifier* verifier); // This constructor is used to initialize stackmap frames in stackmap table, // which have _locals_size and _stack_size array elements in _locals and _stack. @@ -106,6 +124,7 @@ class StackMapFrame : public ResourceObj { u2 max_stack, VerificationType* locals, VerificationType* stack, + AssertUnsetFieldTable* assert_unset_fields, ClassVerifier* v) : _offset(offset), _locals_size(locals_size), _stack_size(stack_size), @@ -113,6 +132,7 @@ class StackMapFrame : public ResourceObj { _max_locals(max_locals), _max_stack(max_stack), _flags(flags), _locals(locals), _stack(stack), + _assert_unset_fields(assert_unset_fields), _verifier(v) { } static StackMapFrame* copy(StackMapFrame* smf) { @@ -136,6 +156,67 @@ class StackMapFrame : public ResourceObj { inline u2 max_stack() const { return _max_stack; } inline bool flag_this_uninit() const { return _flags & FLAG_THIS_UNINIT; } + AssertUnsetFieldTable* assert_unset_fields() const { + return _assert_unset_fields; + } + + void set_assert_unset_fields(AssertUnsetFieldTable* table) { + _assert_unset_fields = table; + } + + // Called when verifying putfields to mark strict instance fields as satisfied + bool satisfy_unset_field(Symbol* name, Symbol* signature) { + NameAndSig dummy_field(name, signature); + + if (_assert_unset_fields->contains(dummy_field)) { + _assert_unset_fields->put(dummy_field, true); + return true; + } + return false; + } + + // Verify that all strict fields have been initialized + // Strict fields must be initialized before the super constructor is called + bool verify_unset_fields_satisfied() { + bool all_satisfied = true; + auto check_satisfied = [&] (const NameAndSig& key, const bool& value) { + all_satisfied &= value; + }; + _assert_unset_fields->iterate_all(check_satisfied); + return all_satisfied; + } + + // Merge incoming unset strict fields from StackMapTable with + // initial strict instance fields + AssertUnsetFieldTable* merge_unset_fields(AssertUnsetFieldTable* new_fields) { + auto merge_satisfied = [&] (const NameAndSig& key, const bool& value) { + if (!new_fields->contains(key)) { + new_fields->put(key, true); + } + }; + _assert_unset_fields->iterate_all(merge_satisfied); + return new_fields; + } + + // Verify that strict fields are compatible between the current frame and the successor + // Called during merging of frames + bool verify_unset_fields_compatibility(AssertUnsetFieldTable* target_table) const { + bool compatible = true; + auto is_unset = [&] (const NameAndSig& key, const bool& value) { + // Successor must have same debts as current frame + if (!value) { + if (*target_table->get(key) == true) { + compatible = false; + } + } + }; + _assert_unset_fields->iterate_all(is_unset); + return compatible; + } + + void unsatisfied_strict_fields_error(InstanceKlass* ik, int bci); + static void print_strict_fields(AssertUnsetFieldTable* table); + // Set locals and stack types to bogus inline void reset() { int32_t i; diff --git a/src/hotspot/share/classfile/stackMapTable.cpp b/src/hotspot/share/classfile/stackMapTable.cpp index 9e02956aceb..b0da0e7b3ec 100644 --- a/src/hotspot/share/classfile/stackMapTable.cpp +++ b/src/hotspot/share/classfile/stackMapTable.cpp @@ -127,6 +127,7 @@ bool StackMapTable::match_stackmap( frame->set_stack_size(ssize); frame->copy_stack(stackmap_frame); frame->set_flags(stackmap_frame->flags()); + frame->set_assert_unset_fields(stackmap_frame->assert_unset_fields()); } return result; } @@ -157,11 +158,13 @@ void StackMapTable::print_on(outputStream* str) const { StackMapReader::StackMapReader(ClassVerifier* v, StackMapStream* stream, char* code_data, int32_t code_len, StackMapFrame* init_frame, - u2 max_locals, u2 max_stack, TRAPS) : + u2 max_locals, u2 max_stack, + StackMapFrame::AssertUnsetFieldTable* initial_strict_fields, TRAPS) : _verifier(v), _stream(stream), _code_data(code_data), _code_length(code_len), _parsed_frame_count(0), _prev_frame(init_frame), _max_locals(max_locals), - _max_stack(max_stack), _first(true) { + _max_stack(max_stack), _assert_unset_fields_buffer(initial_strict_fields), + _first(true) { methodHandle m = v->method(); if (m->has_stackmap_table()) { _cp = constantPoolHandle(THREAD, m->constants()); @@ -203,7 +206,8 @@ VerificationType StackMapReader::parse_verification_type(u1* flags, TRAPS) { _stream->stackmap_format_error("bad class index", THREAD); return VerificationType::bogus_type(); } - return VerificationType::reference_type(_cp->klass_name_at(class_index)); + Symbol* klass_name = _cp->klass_name_at(class_index); + return VerificationType::reference_type(klass_name); } if (tag == ITEM_UninitializedThis) { if (flags != nullptr) { @@ -244,6 +248,63 @@ StackMapFrame* StackMapReader::next_helper(TRAPS) { int offset; VerificationType* locals = nullptr; u1 frame_type = _stream->get_u1(CHECK_NULL); + if (frame_type == EARLY_LARVAL) { + u2 num_unset_fields = _stream->get_u2(CHECK_NULL); + StackMapFrame::AssertUnsetFieldTable* new_fields = new StackMapFrame::AssertUnsetFieldTable(); + + for (u2 i = 0; i < num_unset_fields; i++) { + u2 index = _stream->get_u2(CHECK_NULL); + + if (!_cp->is_within_bounds(index) || !_cp->tag_at(index).is_name_and_type()) { + _prev_frame->verifier()->verify_error( + ErrorContext::bad_strict_fields(_prev_frame->offset(), _prev_frame), + "Invalid constant pool index in early larval frame: %d", index); + return nullptr; + } + + Symbol* name = _cp->symbol_at(_cp->name_ref_index_at(index)); + Symbol* sig = _cp->symbol_at(_cp->signature_ref_index_at(index)); + NameAndSig tmp(name, sig); + + if (!_prev_frame->assert_unset_fields()->contains(tmp)) { + log_info(verification)("NameAndType %s%s(CP index: %d) is not found among initial strict instance fields", name->as_C_string(), sig->as_C_string(), index); + StackMapFrame::print_strict_fields(_prev_frame->assert_unset_fields()); + _prev_frame->verifier()->verify_error( + ErrorContext::bad_strict_fields(_prev_frame->offset(), _prev_frame), + "Strict fields not a subset of initial strict instance fields: %s:%s", name->as_C_string(), sig->as_C_string()); + return nullptr; + } else { + new_fields->put(tmp, false); + } + } + + // Only modify strict instance fields the frame has uninitialized this + if (_prev_frame->flag_this_uninit()) { + _assert_unset_fields_buffer = _prev_frame->merge_unset_fields(new_fields); + } else if (new_fields->number_of_entries() > 0) { + _prev_frame->verifier()->verify_error( + ErrorContext::bad_strict_fields(_prev_frame->offset(), _prev_frame), + "Cannot have uninitialized strict fields after class initialization"); + return nullptr; + } + + // Continue reading frame data + if (at_end()) { + _prev_frame->verifier()->verify_error( + ErrorContext::bad_strict_fields(_prev_frame->offset(), _prev_frame), + "Early larval frame must be followed by a base frame"); + return nullptr; + } + + frame_type = _stream->get_u1(CHECK_NULL); + if (frame_type == EARLY_LARVAL) { + _prev_frame->verifier()->verify_error( + ErrorContext::bad_strict_fields(_prev_frame->offset(), _prev_frame), + "Early larval frame must be followed by a base frame"); + return nullptr; + } + } + if (frame_type <= SAME_FRAME_END) { // same_frame if (_first) { @@ -259,7 +320,8 @@ StackMapFrame* StackMapReader::next_helper(TRAPS) { } frame = new StackMapFrame( offset, _prev_frame->flags(), _prev_frame->locals_size(), 0, - _max_locals, _max_stack, locals, nullptr, _verifier); + _max_locals, _max_stack, locals, nullptr, + _assert_unset_fields_buffer, _verifier); if (_first && locals != nullptr) { frame->copy_locals(_prev_frame); } @@ -291,7 +353,8 @@ StackMapFrame* StackMapReader::next_helper(TRAPS) { stack_size, _max_stack, CHECK_VERIFY_(_verifier, nullptr)); frame = new StackMapFrame( offset, _prev_frame->flags(), _prev_frame->locals_size(), stack_size, - _max_locals, _max_stack, locals, stack, _verifier); + _max_locals, _max_stack, locals, stack, + _assert_unset_fields_buffer, _verifier); if (_first && locals != nullptr) { frame->copy_locals(_prev_frame); } @@ -301,7 +364,7 @@ StackMapFrame* StackMapReader::next_helper(TRAPS) { u2 offset_delta = _stream->get_u2(CHECK_NULL); - if (frame_type < SAME_LOCALS_1_STACK_ITEM_EXTENDED) { + if (frame_type < EARLY_LARVAL) { // reserved frame types _stream->stackmap_format_error( "reserved frame type", CHECK_VERIFY_(_verifier, nullptr)); @@ -332,7 +395,8 @@ StackMapFrame* StackMapReader::next_helper(TRAPS) { stack_size, _max_stack, CHECK_VERIFY_(_verifier, nullptr)); frame = new StackMapFrame( offset, _prev_frame->flags(), _prev_frame->locals_size(), stack_size, - _max_locals, _max_stack, locals, stack, _verifier); + _max_locals, _max_stack, locals, stack, + _assert_unset_fields_buffer, _verifier); if (_first && locals != nullptr) { frame->copy_locals(_prev_frame); } @@ -375,7 +439,8 @@ StackMapFrame* StackMapReader::next_helper(TRAPS) { } frame = new StackMapFrame( offset, flags, new_length, 0, _max_locals, _max_stack, - locals, nullptr, _verifier); + locals, nullptr, + _assert_unset_fields_buffer, _verifier); if (_first && locals != nullptr) { frame->copy_locals(_prev_frame); } @@ -410,7 +475,8 @@ StackMapFrame* StackMapReader::next_helper(TRAPS) { } frame = new StackMapFrame( offset, flags, real_length, 0, _max_locals, - _max_stack, locals, nullptr, _verifier); + _max_stack, locals, nullptr, + _assert_unset_fields_buffer, _verifier); _first = false; return frame; } @@ -458,7 +524,8 @@ StackMapFrame* StackMapReader::next_helper(TRAPS) { } frame = new StackMapFrame( offset, flags, real_locals_size, real_stack_size, - _max_locals, _max_stack, locals, stack, _verifier); + _max_locals, _max_stack, locals, stack, + _assert_unset_fields_buffer, _verifier); _first = false; return frame; } diff --git a/src/hotspot/share/classfile/stackMapTable.hpp b/src/hotspot/share/classfile/stackMapTable.hpp index 6d4c0ce36c0..e2dd9ad4a4d 100644 --- a/src/hotspot/share/classfile/stackMapTable.hpp +++ b/src/hotspot/share/classfile/stackMapTable.hpp @@ -128,6 +128,9 @@ class StackMapReader : StackObj { u2 _max_locals; u2 _max_stack; + // Contains assert_unset_fields generated from classfile + StackMapFrame::AssertUnsetFieldTable* _assert_unset_fields_buffer; + // Check if reading first entry bool _first; @@ -154,7 +157,8 @@ class StackMapReader : StackObj { SAME_LOCALS_1_STACK_ITEM_FRAME_START = 64, SAME_LOCALS_1_STACK_ITEM_FRAME_END = 127, RESERVED_START = 128, - RESERVED_END = 246, + RESERVED_END = 245, + EARLY_LARVAL = 246, SAME_LOCALS_1_STACK_ITEM_EXTENDED = 247, CHOP_FRAME_START = 248, CHOP_FRAME_END = 250, @@ -169,7 +173,8 @@ class StackMapReader : StackObj { StackMapReader(ClassVerifier* v, StackMapStream* stream, char* code_data, int32_t code_len, StackMapFrame* init_frame, - u2 max_locals, u2 max_stack, TRAPS); + u2 max_locals, u2 max_stack, + StackMapFrame::AssertUnsetFieldTable* initial_strict_fields, TRAPS); inline int32_t get_frame_count() const { return _frame_count; } inline StackMapFrame* prev_frame() const { return _prev_frame; } diff --git a/src/hotspot/share/classfile/stringTable.cpp b/src/hotspot/share/classfile/stringTable.cpp index cf5d98650ce..a68cbbe96c7 100644 --- a/src/hotspot/share/classfile/stringTable.cpp +++ b/src/hotspot/share/classfile/stringTable.cpp @@ -973,7 +973,7 @@ void StringTable::allocate_shared_strings_array(TRAPS) { } int total = (int)items_count_acquire(); - size_t single_array_size = objArrayOopDesc::object_size(total); + size_t single_array_size = refArrayOopDesc::object_size(total); log_info(aot)("allocated string table for %d strings", total); @@ -985,8 +985,8 @@ void StringTable::allocate_shared_strings_array(TRAPS) { } else { // Split the table in two levels of arrays. int primary_array_length = (total + _secondary_array_max_length - 1) / _secondary_array_max_length; - size_t primary_array_size = objArrayOopDesc::object_size(primary_array_length); - size_t secondary_array_size = objArrayOopDesc::object_size(_secondary_array_max_length); + size_t primary_array_size = refArrayOopDesc::object_size(primary_array_length); + size_t secondary_array_size = refArrayOopDesc::object_size(_secondary_array_max_length); if (ArchiveHeapWriter::is_too_large_to_archive(secondary_array_size)) { // This can only happen if you have an extremely large number of classes that @@ -1026,7 +1026,7 @@ void StringTable::allocate_shared_strings_array(TRAPS) { void StringTable::verify_secondary_array_index_bits() { int max; for (max = 1; ; max++) { - size_t next_size = objArrayOopDesc::object_size(1 << (max + 1)); + size_t next_size = refArrayOopDesc::object_size(1 << (max + 1)); if (ArchiveHeapWriter::is_too_large_to_archive(next_size)) { break; } diff --git a/src/hotspot/share/classfile/systemDictionary.cpp b/src/hotspot/share/classfile/systemDictionary.cpp index 946fbc07f28..a235814ae0a 100644 --- a/src/hotspot/share/classfile/systemDictionary.cpp +++ b/src/hotspot/share/classfile/systemDictionary.cpp @@ -54,6 +54,8 @@ #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "oops/access.inline.hpp" +#include "oops/fieldStreams.inline.hpp" +#include "oops/inlineKlass.inline.hpp" #include "oops/instanceKlass.hpp" #include "oops/klass.inline.hpp" #include "oops/method.inline.hpp" @@ -71,6 +73,7 @@ #include "runtime/java.hpp" #include "runtime/javaCalls.hpp" #include "runtime/mutexLocker.hpp" +#include "runtime/os.hpp" #include "runtime/sharedRuntime.hpp" #include "runtime/signature.hpp" #include "runtime/synchronizer.hpp" @@ -185,13 +188,35 @@ inline ClassLoaderData* class_loader_data(Handle class_loader) { return ClassLoaderData::class_loader_data(class_loader()); } +// These migrated value classes are loaded by the bootstrap class loader but are added to the initiating +// loaders automatically so that fields of these types can be found and potentially flattened during +// field layout. +static void add_migrated_value_classes(ClassLoaderData* cld) { + JavaThread* current = JavaThread::current(); + auto add_klass = [&] (Symbol* classname) { + InstanceKlass* ik = SystemDictionary::find_instance_klass(current, classname, Handle(current, nullptr)); + assert(ik != nullptr, "Must exist"); + SystemDictionary::add_to_initiating_loader(current, ik, cld); + }; + + MonitorLocker mu1(SystemDictionary_lock); + vmSymbols::migrated_class_names_do(add_klass); +} + ClassLoaderData* SystemDictionary::register_loader(Handle class_loader, bool create_mirror_cld) { if (create_mirror_cld) { // Add a new class loader data to the graph. return ClassLoaderDataGraph::add(class_loader, true); } else { - return (class_loader() == nullptr) ? ClassLoaderData::the_null_class_loader_data() : - ClassLoaderDataGraph::find_or_create(class_loader); + if (class_loader() == nullptr) { + return ClassLoaderData::the_null_class_loader_data(); + } else { + ClassLoaderData* cld = ClassLoaderDataGraph::find_or_create(class_loader); + if (EnableValhalla) { + add_migrated_value_classes(cld); + } + return cld; + } } } @@ -394,7 +419,8 @@ static inline void log_circularity_error(Symbol* name, PlaceholderEntry* probe) } // Must be called for any superclass or superinterface resolution -// during class definition to allow class circularity checking +// during class definition, or may be called for inline field layout processing +// to detect class circularity errors. // superinterface callers: // parse_interfaces - from defineClass // superclass callers: @@ -407,10 +433,12 @@ static inline void log_circularity_error(Symbol* name, PlaceholderEntry* probe) // If another thread is trying to resolve the class, it must do // superclass checks on its own thread to catch class circularity and // to avoid deadlock. +// inline field layout callers: +// The field's class must be loaded to determine layout. // // resolve_with_circularity_detection adds a DETECT_CIRCULARITY placeholder to the placeholder table before calling // resolve_instance_class_or_null. ClassCircularityError is detected when a DETECT_CIRCULARITY or LOAD_INSTANCE -// placeholder for the same thread, class, classloader is found. +// placeholder for the same thread, class, and classloader is found. // This can be seen with logging option: -Xlog:class+load+placeholders=debug. // InstanceKlass* SystemDictionary::resolve_with_circularity_detection(Symbol* class_name, @@ -445,7 +473,7 @@ InstanceKlass* SystemDictionary::resolve_with_circularity_detection(Symbol* clas { MutexLocker mu(THREAD, SystemDictionary_lock); - // Must check ClassCircularity before resolving next_name (superclass or interface). + // Must check ClassCircularity before resolving next_name (superclass, interface, field types or speculatively preloaded argument types). PlaceholderEntry* probe = PlaceholderTable::get_entry(class_name, loader_data); if (probe != nullptr && probe->check_seen_thread(THREAD, PlaceholderTable::DETECT_CIRCULARITY)) { log_circularity_error(class_name, probe); @@ -469,7 +497,7 @@ InstanceKlass* SystemDictionary::resolve_with_circularity_detection(Symbol* clas THROW_MSG_NULL(vmSymbols::java_lang_ClassCircularityError(), class_name->as_C_string()); } - // Resolve the superclass or superinterface, check results on return + // Resolve the superclass, superinterface, field type or speculatively preloaded argument types and check results on return. InstanceKlass* superk = SystemDictionary::resolve_instance_class_or_null(next_name, class_loader, @@ -915,8 +943,7 @@ bool SystemDictionary::is_shared_class_visible(Symbol* class_name, InstanceKlass* ik, PackageEntry* pkg_entry, Handle class_loader) { - assert(!ModuleEntryTable::javabase_moduleEntry()->is_patched(), - "Cannot use sharing if java.base is patched"); + assert(!CDSConfig::module_patching_disables_cds(), "Cannot use CDS"); // (1) Check if we are loading into the same loader as in dump time. @@ -992,7 +1019,7 @@ bool SystemDictionary::is_shared_class_visible_impl(Symbol* class_name, // Is the module loaded from the same location as during dump time? visible = mod_entry->shared_path_index() == scp_index; if (visible) { - assert(!mod_entry->is_patched(), "cannot load archived classes for patched module"); + assert(!CDSConfig::module_patching_disables_cds(), "Cannot use CDS"); } } else { // During dump time, this class was in a named module, but at run time, this class should be @@ -1070,6 +1097,75 @@ bool SystemDictionary::check_shared_class_super_types(InstanceKlass* ik, Handle return true; } +// Pre-load class referred to in non-static null-free instance field. These fields trigger MANDATORY loading. +// Some pre-loading does not fail fatally +bool SystemDictionary::preload_from_null_free_field(InstanceKlass* ik, Handle class_loader, Symbol* sig, int field_index, TRAPS) { + TempNewSymbol name = Signature::strip_envelope(sig); + log_info(class, preload)("Preloading of class %s during loading of shared class %s. " + "Cause: a null-free non-static field is declared with this type", + name->as_C_string(), ik->name()->as_C_string()); + InstanceKlass* real_k = SystemDictionary::resolve_with_circularity_detection(ik->name(), name, + class_loader, false, CHECK_false); + if (HAS_PENDING_EXCEPTION) { + log_warning(class, preload)("Preloading of class %s during loading of class %s " + "(cause: null-free non-static field) failed: %s", + name->as_C_string(), ik->name()->as_C_string(), + PENDING_EXCEPTION->klass()->name()->as_C_string()); + return false; // Exception is still pending + } + + InstanceKlass* k = ik->get_inline_type_field_klass_or_null(field_index); + if (real_k != k) { + // oops, the app has substituted a different version of k! Does not fail fatally + log_warning(class, preload)("Preloading of class %s during loading of shared class %s " + "(cause: null-free non-static field) failed : " + "app substituted a different version of %s", + name->as_C_string(), ik->name()->as_C_string(), + name->as_C_string()); + return false; + } + log_info(class, preload)("Preloading of class %s during loading of shared class %s " + "(cause: null-free non-static field) succeeded", + name->as_C_string(), ik->name()->as_C_string()); + + assert(real_k != nullptr, "Sanity check"); + InstanceKlass::check_can_be_annotated_with_NullRestricted(real_k, ik->name(), CHECK_false); + + return true; +} + +// Tries to pre-load classes referred to in non-static nullable instance fields if they are found in the +// loadable descriptors attribute. If loading fails, we can fail silently. +void SystemDictionary::try_preload_from_loadable_descriptors(InstanceKlass* ik, Handle class_loader, Symbol* sig, int field_index, TRAPS) { + TempNewSymbol name = Signature::strip_envelope(sig); + if (name != ik->name() && ik->is_class_in_loadable_descriptors_attribute(sig)) { + log_info(class, preload)("Preloading of class %s during loading of shared class %s. " + "Cause: field type in LoadableDescriptors attribute", + name->as_C_string(), ik->name()->as_C_string()); + InstanceKlass* real_k = SystemDictionary::resolve_with_circularity_detection(ik->name(), name, + class_loader, false, THREAD); + if (HAS_PENDING_EXCEPTION) { + CLEAR_PENDING_EXCEPTION; + } + + InstanceKlass* k = ik->get_inline_type_field_klass_or_null(field_index); + if (real_k != k) { + // oops, the app has substituted a different version of k! + log_warning(class, preload)("Preloading of class %s during loading of shared class %s " + "(cause: field type in LoadableDescriptors attribute) failed : " + "app substituted a different version of %s", + name->as_C_string(), ik->name()->as_C_string(), + k->name()->as_C_string()); + return; + } else if (real_k != nullptr) { + log_info(class, preload)("Preloading of class %s during loading of shared class %s " + "(cause: field type in LoadableDescriptors attribute) succeeded", + name->as_C_string(), ik->name()->as_C_string()); + } + } +} + + InstanceKlass* SystemDictionary::load_shared_class(InstanceKlass* ik, Handle class_loader, Handle protection_domain, @@ -1093,6 +1189,27 @@ InstanceKlass* SystemDictionary::load_shared_class(InstanceKlass* ik, return nullptr; } + if (ik->has_inline_type_fields()) { + for (AllFieldStream fs(ik); !fs.done(); fs.next()) { + if (fs.access_flags().is_static()) continue; + + Symbol* sig = fs.signature(); + int field_index = fs.index(); + + if (fs.is_null_free_inline_type()) { + // A false return means that the class didn't load for other reasons than an exception. + bool check = preload_from_null_free_field(ik, class_loader, sig, field_index, CHECK_NULL); + if (!check) { + ik->set_shared_loading_failed(); + return nullptr; + } + } else if (Signature::has_envelope(sig)) { + // Pending exceptions are cleared so we can fail silently + try_preload_from_loadable_descriptors(ik, class_loader, sig, field_index, CHECK_NULL); + } + } + } + InstanceKlass* new_ik = nullptr; // CFLH check is skipped for VM hidden classes (see KlassFactory::create_from_stream). // It will be skipped for shared VM hidden lambda proxy classes. @@ -1128,6 +1245,7 @@ InstanceKlass* SystemDictionary::load_shared_class(InstanceKlass* ik, } load_shared_class_misc(ik, loader_data); + return ik; } @@ -1650,23 +1768,23 @@ void SystemDictionary::update_dictionary(JavaThread* current, mu1.notify_all(); } -#if INCLUDE_CDS // Indicate that loader_data has initiated the loading of class k, which // has already been defined by a parent loader. -// This API should be used only by AOTLinkedClassBulkLoader +// This API is used by AOTLinkedClassBulkLoader and to register boxing +// classes from java.lang in all class loaders to enable more value +// classes optimizations void SystemDictionary::add_to_initiating_loader(JavaThread* current, InstanceKlass* k, ClassLoaderData* loader_data) { - assert(CDSConfig::is_using_aot_linked_classes(), "must be"); assert_locked_or_safepoint(SystemDictionary_lock); Symbol* name = k->name(); Dictionary* dictionary = loader_data->dictionary(); assert(k->is_loaded(), "must be"); assert(k->class_loader_data() != loader_data, "only for classes defined by a parent loader"); - assert(dictionary->find_class(current, name) == nullptr, "sanity"); - dictionary->add_klass(current, name, k); + if (dictionary->find_class(current, name) == nullptr) { + dictionary->add_klass(current, name, k); + } } -#endif // Try to find a class name using the loader constraints. The // loader constraints might know about a class that isn't fully loaded diff --git a/src/hotspot/share/classfile/systemDictionary.hpp b/src/hotspot/share/classfile/systemDictionary.hpp index 8cf2cd83b82..c5047c1e832 100644 --- a/src/hotspot/share/classfile/systemDictionary.hpp +++ b/src/hotspot/share/classfile/systemDictionary.hpp @@ -139,6 +139,7 @@ class SystemDictionary : AllStatic { static oop get_platform_class_loader_impl(TRAPS); public: + // Resolve either a hidden or normal class from a stream of bytes, based on ClassLoadInfo static InstanceKlass* resolve_from_stream(ClassFileStream* st, Symbol* class_name, @@ -284,7 +285,7 @@ class SystemDictionary : AllStatic { static const char* find_nest_host_error(const constantPoolHandle& pool, int which); static void add_to_initiating_loader(JavaThread* current, InstanceKlass* k, - ClassLoaderData* loader_data) NOT_CDS_RETURN; + ClassLoaderData* loader_data); static OopHandle _java_system_loader; static OopHandle _java_platform_loader; @@ -331,6 +332,8 @@ class SystemDictionary : AllStatic { static bool add_loader_constraint(Symbol* name, Klass* klass_being_linked, Handle loader1, Handle loader2); static void post_class_load_event(EventClassLoad* event, const InstanceKlass* k, const ClassLoaderData* init_cld); + static bool preload_from_null_free_field(InstanceKlass* ik, Handle class_loader, Symbol* sig, int field_index, TRAPS); + static void try_preload_from_loadable_descriptors(InstanceKlass* ik, Handle class_loader, Symbol* sig, int field_index, TRAPS); static InstanceKlass* load_shared_class(InstanceKlass* ik, Handle class_loader, Handle protection_domain, diff --git a/src/hotspot/share/classfile/verificationType.cpp b/src/hotspot/share/classfile/verificationType.cpp index aedb620aabe..88b3bccfdf5 100644 --- a/src/hotspot/share/classfile/verificationType.cpp +++ b/src/hotspot/share/classfile/verificationType.cpp @@ -66,6 +66,13 @@ bool VerificationType::resolve_and_check_assignability(InstanceKlass* current_kl } } + // Need to do this check when called from CDS. + // if (this_class->access_flags().is_primitive_class()) { + // Klass* from_class = SystemDictionary::resolve_or_fail( + // from_name, Handle(THREAD, klass->class_loader()), + // Handle(THREAD, klass->protection_domain()), true, CHECK_false); + // return from_class == this_class; + // } bool is_intf = target_klass->is_interface(); if (target_is_interface != nullptr) { *target_is_interface = is_intf; @@ -75,7 +82,7 @@ bool VerificationType::resolve_and_check_assignability(InstanceKlass* current_kl from_name != vmSymbols::java_lang_Object())) { // If we are not trying to access a protected field or method in // java.lang.Object then, for arrays, we only allow assignability - // to interfaces java.lang.Cloneable and java.io.Serializable. + // to interfaces java.lang.Cloneable and java.io.Serializable // Otherwise, we treat interfaces as java.lang.Object. return !from_is_array || target_klass == vmClasses::Cloneable_klass() || @@ -134,6 +141,7 @@ bool VerificationType::is_reference_assignable_from( } else if (is_array() && from.is_array()) { VerificationType comp_this = get_component(context); VerificationType comp_from = from.get_component(context); + if (!comp_this.is_bogus() && !comp_from.is_bogus()) { return comp_this.is_component_assignable_from(comp_from, context, from_field_is_protected, THREAD); diff --git a/src/hotspot/share/classfile/verificationType.hpp b/src/hotspot/share/classfile/verificationType.hpp index 788e0029fad..db553684d88 100644 --- a/src/hotspot/share/classfile/verificationType.hpp +++ b/src/hotspot/share/classfile/verificationType.hpp @@ -68,12 +68,12 @@ class VerificationType { // Enum for the _data field enum : uint { - // Bottom two bits determine if the type is a reference, primitive, - // uninitialized or a query-type. - TypeMask = 0x00000003, + // Bottom three bits determine if the type is a reference, inline type, + // primitive, uninitialized or a query-type. + TypeMask = 0x00000007, // Topmost types encoding - Reference = 0x0, // _sym contains the name + Reference = 0x0, // _sym contains the name of an object Primitive = 0x1, // see below for primitive list Uninitialized = 0x2, // 0x00ffff00 contains bci TypeQuery = 0x3, // Meta-types used for category testing @@ -114,7 +114,7 @@ class VerificationType { ReferenceQuery = (ReferenceFlag << 1 * BitsPerByte) | TypeQuery, Category1Query = (Category1Flag << 1 * BitsPerByte) | TypeQuery, Category2Query = (Category2Flag << 1 * BitsPerByte) | TypeQuery, - Category2_2ndQuery = (Category2_2ndFlag << 1 * BitsPerByte) | TypeQuery + Category2_2ndQuery = (Category2_2ndFlag << 1 * BitsPerByte) | TypeQuery, }; VerificationType(uintptr_t raw_data) { @@ -156,7 +156,7 @@ class VerificationType { // For reference types, store the actual Symbol static VerificationType reference_type(Symbol* sh) { - assert(((uintptr_t)sh & 0x3) == 0, "Symbols must be aligned"); + assert(((uintptr_t)sh & TypeMask) == 0, "Symbols must be aligned"); // If the above assert fails in the future because oop* isn't aligned, // then this type encoding system will have to change to have a tag value // to discriminate between oops and primitives. @@ -185,8 +185,8 @@ class VerificationType { bool is_reference() const { return ((_u._data & TypeMask) == Reference); } bool is_category1() const { // This should return true for all one-word types, which are category1 - // primitives, and references (including uninitialized refs). Though - // the 'query' types should technically return 'false' here, if we + // primitives, references (including uninitialized refs) and inline types. + // Though the 'query' types should technically return 'false' here, if we // allow this to return true, we can perform the test using only // 2 operations rather than 8 (3 masks, 3 compares and 2 logical 'ands'). // Since no one should call this on a query type anyway, this is ok. @@ -220,6 +220,8 @@ class VerificationType { bool is_array_array() const { return is_x_array(JVM_SIGNATURE_ARRAY); } bool is_reference_array() const { return is_object_array() || is_array_array(); } + bool is_nonscalar_array() const + { return is_object_array() || is_array_array(); } bool is_object() const { return (is_reference() && !is_null() && name()->utf8_length() >= 1 && name()->char_at(0) != JVM_SIGNATURE_ARRAY); } @@ -242,14 +244,15 @@ class VerificationType { } Symbol* name() const { - assert(is_reference() && !is_null(), "Must be a non-null reference"); + assert(!is_null() && is_reference(), "Must be a non-null reference"); return _u._sym; } bool equals(const VerificationType& t) const { return (_u._data == t._u._data || - (is_reference() && t.is_reference() && !is_null() && !t.is_null() && - name() == t.name())); + (((is_reference() && t.is_reference())) && + !is_null() && !t.is_null() && name() == t.name())); + } bool operator ==(const VerificationType& t) const { diff --git a/src/hotspot/share/classfile/verifier.cpp b/src/hotspot/share/classfile/verifier.cpp index f6f5aa70fbd..9b1bfe384b2 100644 --- a/src/hotspot/share/classfile/verifier.cpp +++ b/src/hotspot/share/classfile/verifier.cpp @@ -43,12 +43,13 @@ #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "oops/constantPool.inline.hpp" +#include "oops/fieldStreams.inline.hpp" #include "oops/instanceKlass.inline.hpp" #include "oops/klass.inline.hpp" #include "oops/oop.inline.hpp" #include "oops/typeArrayOop.hpp" #include "runtime/arguments.hpp" -#include "runtime/fieldDescriptor.hpp" +#include "runtime/fieldDescriptor.inline.hpp" #include "runtime/handles.inline.hpp" #include "runtime/interfaceSupport.inline.hpp" #include "runtime/javaCalls.hpp" @@ -66,6 +67,7 @@ #define NOFAILOVER_MAJOR_VERSION 51 #define NONZERO_PADDING_BYTES_IN_SWITCH_MAJOR_VERSION 51 #define STATIC_METHOD_IN_INTERFACE_MAJOR_VERSION 52 +#define INLINE_TYPE_MAJOR_VERSION 56 #define MAX_ARRAY_DIMENSIONS 255 // Access to external entry for VerifyClassForMajorVersion - old byte code verifier @@ -477,12 +479,18 @@ void ErrorContext::reason_details(outputStream* ss) const { case BAD_LOCAL_INDEX: ss->print("Local index %d is invalid", _type.index()); break; + case BAD_STRICT_FIELDS: + ss->print("Invalid use of strict instance fields"); + break; case LOCALS_SIZE_MISMATCH: ss->print("Current frame's local size doesn't match stackmap."); break; case STACK_SIZE_MISMATCH: ss->print("Current frame's stack size doesn't match stackmap."); break; + case STRICT_FIELDS_MISMATCH: + ss->print("Current frame's strict instance fields not compatible with stackmap."); + break; case STACK_OVERFLOW: ss->print("Exceeded max stack size."); break; @@ -495,6 +503,13 @@ void ErrorContext::reason_details(outputStream* ss) const { case BAD_STACKMAP: ss->print("Invalid stackmap specification."); break; + case WRONG_INLINE_TYPE: + ss->print("Type "); + _type.details(ss); + ss->print(" and type "); + _expected.details(ss); + ss->print(" must be identical inline types."); + break; case UNKNOWN: default: ShouldNotReachHere(); @@ -617,6 +632,11 @@ TypeOrigin ClassVerifier::ref_ctx(const char* sig) { return TypeOrigin::implicit(vt); } +static bool supports_strict_fields(InstanceKlass* klass) { + int ver = klass->major_version(); + return ver > Verifier::VALUE_TYPES_MAJOR_VERSION || + (ver == Verifier::VALUE_TYPES_MAJOR_VERSION && klass->minor_version() == Verifier::JAVA_PREVIEW_MINOR_VERSION); +} void ClassVerifier::verify_class(TRAPS) { log_info(verification)("Verifying class %s with new format", _klass->external_name()); @@ -712,8 +732,23 @@ void ClassVerifier::verify_method(const methodHandle& m, TRAPS) { assert(SignatureVerifier::is_valid_method_signature(m->signature()), "Invalid method signature"); + // Collect the initial strict instance fields + StackMapFrame::AssertUnsetFieldTable* strict_fields = new StackMapFrame::AssertUnsetFieldTable(); + if (m->is_object_constructor()) { + for (AllFieldStream fs(m->method_holder()); !fs.done(); fs.next()) { + if (fs.access_flags().is_strict() && !fs.access_flags().is_static()) { + NameAndSig new_field(fs.name(), fs.signature()); + if (IgnoreAssertUnsetFields) { + strict_fields->put(new_field, true); + } else { + strict_fields->put(new_field, false); + } + } + } + } + // Initial stack map frame: offset is 0, stack is initially empty. - StackMapFrame current_frame(max_locals, max_stack, this); + StackMapFrame current_frame(max_locals, max_stack, strict_fields, this); // Set initial locals VerificationType return_type = current_frame.set_locals_from_arg( m, current_type()); @@ -740,7 +775,7 @@ void ClassVerifier::verify_method(const methodHandle& m, TRAPS) { Array* stackmap_data = m->stackmap_data(); StackMapStream stream(stackmap_data); - StackMapReader reader(this, &stream, code_data, code_length, ¤t_frame, max_locals, max_stack, THREAD); + StackMapReader reader(this, &stream, code_data, code_length, ¤t_frame, max_locals, max_stack, strict_fields, THREAD); StackMapTable stackmap_table(&reader, CHECK_VERIFY(this)); LogTarget(Debug, verification) lt; @@ -1613,12 +1648,12 @@ void ClassVerifier::verify_method(const methodHandle& m, TRAPS) { case Bytecodes::_if_acmpeq : case Bytecodes::_if_acmpne : current_frame.pop_stack( - VerificationType::reference_check(), CHECK_VERIFY(this)); + object_type(), CHECK_VERIFY(this)); // fall through case Bytecodes::_ifnull : case Bytecodes::_ifnonnull : current_frame.pop_stack( - VerificationType::reference_check(), CHECK_VERIFY(this)); + object_type(), CHECK_VERIFY(this)); target = bcs.dest(); stackmap_table.check_jump_target (¤t_frame, target, CHECK_VERIFY(this)); @@ -1681,7 +1716,7 @@ void ClassVerifier::verify_method(const methodHandle& m, TRAPS) { } // Make sure "this" has been initialized if current method is an // . - if (_method->name() == vmSymbols::object_initializer_name() && + if (_method->is_object_constructor() && current_frame.flag_this_uninit()) { verify_error(ErrorContext::bad_code(bci), "Constructor must call super() or this() " @@ -1704,15 +1739,11 @@ void ClassVerifier::verify_method(const methodHandle& m, TRAPS) { case Bytecodes::_invokevirtual : case Bytecodes::_invokespecial : case Bytecodes::_invokestatic : - verify_invoke_instructions( - &bcs, code_length, ¤t_frame, (bci >= ex_min && bci < ex_max), - &this_uninit, return_type, cp, &stackmap_table, CHECK_VERIFY(this)); - no_control_flow = false; break; case Bytecodes::_invokeinterface : case Bytecodes::_invokedynamic : verify_invoke_instructions( &bcs, code_length, ¤t_frame, (bci >= ex_min && bci < ex_max), - &this_uninit, return_type, cp, &stackmap_table, CHECK_VERIFY(this)); + &this_uninit, cp, &stackmap_table, CHECK_VERIFY(this)); no_control_flow = false; break; case Bytecodes::_new : { @@ -1770,10 +1801,11 @@ void ClassVerifier::verify_method(const methodHandle& m, TRAPS) { no_control_flow = false; break; } case Bytecodes::_monitorenter : - case Bytecodes::_monitorexit : - current_frame.pop_stack( + case Bytecodes::_monitorexit : { + VerificationType ref = current_frame.pop_stack( VerificationType::reference_check(), CHECK_VERIFY(this)); no_control_flow = false; break; + } case Bytecodes::_multianewarray : { u2 index = bcs.get_index_u2(); @@ -2151,7 +2183,7 @@ void ClassVerifier::verify_ldc( if (opcode == Bytecodes::_ldc || opcode == Bytecodes::_ldc_w) { if (!tag.is_unresolved_klass()) { types = (1 << JVM_CONSTANT_Integer) | (1 << JVM_CONSTANT_Float) - | (1 << JVM_CONSTANT_String) | (1 << JVM_CONSTANT_Class) + | (1 << JVM_CONSTANT_String) | (1 << JVM_CONSTANT_Class) | (1 << JVM_CONSTANT_MethodHandle) | (1 << JVM_CONSTANT_MethodType) | (1 << JVM_CONSTANT_Dynamic); // Note: The class file parser already verified the legality of @@ -2327,13 +2359,14 @@ void ClassVerifier::verify_field_instructions(RawBytecodeStream* bcs, VerificationType ref_class_type = cp_ref_index_to_type( index, cp, CHECK_VERIFY(this)); if (!ref_class_type.is_object() && - (!allow_arrays || !ref_class_type.is_array())) { + (!allow_arrays || !ref_class_type.is_array())) { verify_error(ErrorContext::bad_type(bcs->bci(), TypeOrigin::cp(index, ref_class_type)), "Expecting reference to class in class %s at constant pool index %d", _klass->external_name(), index); return; } + VerificationType target_class_type = ref_class_type; assert(sizeof(VerificationType) == sizeof(uintptr_t), @@ -2375,13 +2408,33 @@ void ClassVerifier::verify_field_instructions(RawBytecodeStream* bcs, } stack_object_type = current_frame->pop_stack(CHECK_VERIFY(this)); - // The JVMS 2nd edition allows field initialization before the superclass + // Field initialization is allowed before the superclass // initializer, if the field is defined within the current class. fieldDescriptor fd; - if (stack_object_type == VerificationType::uninitialized_this_type() && - target_class_type.equals(current_type()) && - _klass->find_local_field(field_name, field_sig, &fd)) { - stack_object_type = current_type(); + bool is_local_field = _klass->find_local_field(field_name, field_sig, &fd) && + target_class_type.equals(current_type()); + if (stack_object_type == VerificationType::uninitialized_this_type()) { + if (is_local_field) { + // Set the type to the current type so the is_assignable check passes. + stack_object_type = current_type(); + + if (fd.access_flags().is_strict()) { + ResourceMark rm(THREAD); + if (!current_frame->satisfy_unset_field(fd.name(), fd.signature())) { + log_info(verification)("Attempting to initialize field not found in initial strict instance fields: %s%s", + fd.name()->as_C_string(), fd.signature()->as_C_string()); + verify_error(ErrorContext::bad_strict_fields(bci, current_frame), + "Initializing unknown strict field: %s:%s", fd.name()->as_C_string(), fd.signature()->as_C_string()); + } + } + } + } else if (supports_strict_fields(_klass)) { + // `strict` fields are not writable, but only local fields produce verification errors + if (is_local_field && fd.access_flags().is_strict() && fd.access_flags().is_final()) { + verify_error(ErrorContext::bad_code(bci), + "Illegal use of putfield on a strict field"); + return; + } } is_assignable = target_class_type.is_assignable_from( stack_object_type, this, false, CHECK_VERIFY(this)); @@ -2663,6 +2716,13 @@ void ClassVerifier::verify_invoke_init( TypeOrigin::implicit(current_type())), "Bad method call"); return; + } else if (ref_class_type.name() == superk->name()) { + // Strict final fields must be satisfied by this point + if (!current_frame->verify_unset_fields_satisfied()) { + log_info(verification)("Strict instance fields not initialized"); + StackMapFrame::print_strict_fields(current_frame->assert_unset_fields()); + current_frame->unsatisfied_strict_fields_error(current_class(), bci); + } } // If this invokespecial call is done from inside of a TRY block then make @@ -2786,7 +2846,7 @@ bool ClassVerifier::is_same_or_direct_interface( void ClassVerifier::verify_invoke_instructions( RawBytecodeStream* bcs, u4 code_length, StackMapFrame* current_frame, - bool in_try_block, bool *this_uninit, VerificationType return_type, + bool in_try_block, bool *this_uninit, const constantPoolHandle& cp, StackMapTable* stackmap_table, TRAPS) { // Make sure the constant pool item is the right type u2 index = bcs->get_index_u2(); @@ -2818,7 +2878,7 @@ void ClassVerifier::verify_invoke_instructions( assert(SignatureVerifier::is_valid_method_signature(method_sig), "Invalid method signature"); - // Get referenced class type + // Get referenced class VerificationType ref_class_type; if (opcode == Bytecodes::_invokedynamic) { if (_klass->major_version() < Verifier::INVOKEDYNAMIC_MAJOR_VERSION) { @@ -2884,9 +2944,10 @@ void ClassVerifier::verify_invoke_instructions( } if (method_name->char_at(0) == JVM_SIGNATURE_SPECIAL) { - // Make sure can only be invoked by invokespecial + // Make sure: + // can only be invoked by invokespecial. if (opcode != Bytecodes::_invokespecial || - method_name != vmSymbols::object_initializer_name()) { + method_name != vmSymbols::object_initializer_name()) { verify_error(ErrorContext::bad_code(bci), "Illegal call to internal method"); return; @@ -2896,7 +2957,7 @@ void ClassVerifier::verify_invoke_instructions( // or any superclass (including Object). else if (opcode == Bytecodes::_invokespecial && !is_same_or_direct_interface(current_class(), current_type(), ref_class_type) - && !ref_class_type.equals(VerificationType::reference_type(current_class()->super()->name()))) { + && !ref_class_type.equals(VerificationType::reference_type(current_class()->super()->name()))) { // super() can never be an inline_type. // We know it is not current class, direct superinterface or immediate superclass. That means it // could be: @@ -2996,9 +3057,7 @@ void ClassVerifier::verify_invoke_instructions( int sig_verif_types_len = sig_verif_types->length(); if (sig_verif_types_len > nargs) { // There's a return type if (method_name == vmSymbols::object_initializer_name()) { - // method must have a void return type - /* Unreachable? Class file parser verifies that methods with '<' have - * void return */ + // an method must have a void return type verify_error(ErrorContext::bad_code(bci), "Return type must be void in method"); return; diff --git a/src/hotspot/share/classfile/verifier.hpp b/src/hotspot/share/classfile/verifier.hpp index 87ea8bd2970..bb6cd346b90 100644 --- a/src/hotspot/share/classfile/verifier.hpp +++ b/src/hotspot/share/classfile/verifier.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,6 +33,13 @@ #include "utilities/growableArray.hpp" #include "utilities/hashTable.hpp" +struct NameAndSig { + Symbol* _name; + Symbol* _signature; + + NameAndSig(Symbol* n, Symbol* s) : _name(n), _signature(s) {} +}; + // The verifier class class Verifier : AllStatic { public: @@ -40,7 +47,9 @@ class Verifier : AllStatic { STACKMAP_ATTRIBUTE_MAJOR_VERSION = 50, INVOKEDYNAMIC_MAJOR_VERSION = 51, NO_RELAX_ACCESS_CTRL_CHECK_VERSION = 52, - DYNAMICCONSTANT_MAJOR_VERSION = 55 + DYNAMICCONSTANT_MAJOR_VERSION = 55, + VALUE_TYPES_MAJOR_VERSION = 70, + JAVA_PREVIEW_MINOR_VERSION = 65535, }; // Verify the bytecodes for a class. @@ -148,12 +157,15 @@ class ErrorContext { FLAGS_MISMATCH, // Frame flags are not assignable BAD_CP_INDEX, // Invalid constant pool index BAD_LOCAL_INDEX, // Invalid local index + BAD_STRICT_FIELDS, // Strict instance fields must be initialized before super constructor LOCALS_SIZE_MISMATCH, // Frames have differing local counts STACK_SIZE_MISMATCH, // Frames have different stack sizes + STRICT_FIELDS_MISMATCH, // Frames have incompatible uninitialized strict instance fields STACK_OVERFLOW, // Attempt to push onto a full expression stack STACK_UNDERFLOW, // Attempt to pop and empty expression stack MISSING_STACKMAP, // No stackmap for this location and there should be BAD_STACKMAP, // Format error in stackmap + WRONG_INLINE_TYPE, // Mismatched inline type NO_FAULT, // No error UNKNOWN } FaultType; @@ -195,6 +207,9 @@ class ErrorContext { static ErrorContext bad_local_index(int bci, int index) { return ErrorContext(bci, BAD_LOCAL_INDEX, TypeOrigin::bad_index(index)); } + static ErrorContext bad_strict_fields(int bci, StackMapFrame* cur) { + return ErrorContext(bci, BAD_STRICT_FIELDS, TypeOrigin::frame(cur)); + } static ErrorContext locals_size_mismatch( int bci, StackMapFrame* frame0, StackMapFrame* frame1) { return ErrorContext(bci, LOCALS_SIZE_MISMATCH, @@ -205,6 +220,11 @@ class ErrorContext { return ErrorContext(bci, STACK_SIZE_MISMATCH, TypeOrigin::frame(frame0), TypeOrigin::frame(frame1)); } + static ErrorContext strict_fields_mismatch( + int bci, StackMapFrame* frame0, StackMapFrame* frame1) { + return ErrorContext(bci, STRICT_FIELDS_MISMATCH, + TypeOrigin::frame(frame0), TypeOrigin::frame(frame1)); + } static ErrorContext stack_overflow(int bci, StackMapFrame* frame) { return ErrorContext(bci, STACK_OVERFLOW, TypeOrigin::frame(frame)); } @@ -217,6 +237,9 @@ class ErrorContext { static ErrorContext bad_stackmap(int index, StackMapFrame* frame) { return ErrorContext(0, BAD_STACKMAP, TypeOrigin::frame(frame)); } + static ErrorContext bad_inline_type(int bci, TypeOrigin type, TypeOrigin exp) { + return ErrorContext(bci, WRONG_INLINE_TYPE, type, exp); + } bool is_valid() const { return _fault != NO_FAULT; } int bci() const { return _bci; } @@ -347,7 +370,7 @@ class ClassVerifier : public StackObj { void verify_invoke_instructions( RawBytecodeStream* bcs, u4 code_length, StackMapFrame* current_frame, - bool in_try_block, bool* this_uninit, VerificationType return_type, + bool in_try_block, bool* this_uninit, const constantPoolHandle& cp, StackMapTable* stackmap_table, TRAPS); VerificationType get_newarray_type(u2 index, int bci, TRAPS); @@ -444,7 +467,8 @@ class ClassVerifier : public StackObj { SignatureStream* sig_type, VerificationType* inference_type); VerificationType cp_index_to_type(int index, const constantPoolHandle& cp, TRAPS) { - return VerificationType::reference_type(cp->klass_name_at(index)); + Symbol* name = cp->klass_name_at(index); + return VerificationType::reference_type(name); } // Keep a list of temporary symbols created during verification because @@ -482,8 +506,7 @@ inline int ClassVerifier::change_sig_to_verificationType( // Create another symbol to save as signature stream unreferences this symbol. Symbol* name_copy = create_temporary_symbol(name); assert(name_copy == name, "symbols don't match"); - *inference_type = - VerificationType::reference_type(name_copy); + *inference_type = VerificationType::reference_type(name_copy); return 1; } case T_LONG: diff --git a/src/hotspot/share/classfile/vmClassMacros.hpp b/src/hotspot/share/classfile/vmClassMacros.hpp index 1edc13054d4..a15f5fac92f 100644 --- a/src/hotspot/share/classfile/vmClassMacros.hpp +++ b/src/hotspot/share/classfile/vmClassMacros.hpp @@ -126,6 +126,7 @@ do_klass(CallConv_klass, jdk_internal_foreign_abi_CallConv ) \ do_klass(ConstantCallSite_klass, java_lang_invoke_ConstantCallSite ) \ do_klass(MutableCallSite_klass, java_lang_invoke_MutableCallSite ) \ + do_klass(ValueObjectMethods_klass, java_lang_runtime_ValueObjectMethods ) \ do_klass(VolatileCallSite_klass, java_lang_invoke_VolatileCallSite ) \ \ do_klass(AssertionStatusDirectives_klass, java_lang_AssertionStatusDirectives ) \ @@ -173,6 +174,30 @@ do_klass(Long_klass, java_lang_Long ) \ do_klass(Void_klass, java_lang_Void ) \ \ + /* Other valhalla migrated klasses. */ \ + do_klass(Number_klass, java_lang_Number ) \ + do_klass(Optional_klass, java_util_Optional ) \ + do_klass(OptionalInt_klass, java_util_OptionalInt ) \ + do_klass(OptionalLong_klass, java_util_OptionalLong ) \ + do_klass(OptionalDouble_klass, java_util_OptionalDouble ) \ + do_klass(LocalDate_klass, java_time_LocalDate ) \ + do_klass(LocalDateTime_klass, java_time_LocalDateTime ) \ + do_klass(LocalTime_klass, java_time_LocalTime ) \ + do_klass(Duration_klass, java_time_Duration ) \ + do_klass(Instant_klass, java_time_Instant ) \ + do_klass(MonthDay_klass, java_time_MonthDay ) \ + do_klass(ZonedDateTime_klass, java_time_ZonedDateTime ) \ + do_klass(OffsetDateTime_klass, java_time_OffsetDateTime ) \ + do_klass(OffsetTime_klass, java_time_OffsetTime ) \ + do_klass(YearMonth_klass, java_time_YearMonth ) \ + do_klass(Year_klass, java_time_Year ) \ + do_klass(Period_klass, java_time_Period ) \ + do_klass(chrono_ChronoLocalDateImpl_klass, java_time_chrono_ChronoLocalDateImpl ) \ + do_klass(chrono_MinguoDate_klass, java_time_chrono_MinguoDate ) \ + do_klass(chrono_HijrahDate_klass, java_time_chrono_HijrahDate ) \ + do_klass(chrono_JapaneseDate_klass, java_time_chrono_JapaneseDate ) \ + do_klass(chrono_ThaiBuddhistDate_klass, java_time_chrono_ThaiBuddhistDate ) \ + \ /* force inline of iterators */ \ do_klass(Iterator_klass, java_util_Iterator ) \ \ diff --git a/src/hotspot/share/classfile/vmIntrinsics.cpp b/src/hotspot/share/classfile/vmIntrinsics.cpp index f9b12df84ca..b2fe4ca2415 100644 --- a/src/hotspot/share/classfile/vmIntrinsics.cpp +++ b/src/hotspot/share/classfile/vmIntrinsics.cpp @@ -265,6 +265,9 @@ bool vmIntrinsics::disabled_by_jvm_flags(vmIntrinsics::ID id) { case vmIntrinsics::_Class_cast: case vmIntrinsics::_getLength: case vmIntrinsics::_newArray: + case vmIntrinsics::_newNullRestrictedNonAtomicArray: + case vmIntrinsics::_newNullRestrictedAtomicArray: + case vmIntrinsics::_newNullableAtomicArray: case vmIntrinsics::_getClass: if (!InlineClassNatives) return true; break; @@ -336,6 +339,8 @@ bool vmIntrinsics::disabled_by_jvm_flags(vmIntrinsics::ID id) { case vmIntrinsics::_updateByteBufferCRC32: if (!UseCRC32Intrinsics) return true; break; + case vmIntrinsics::_makePrivateBuffer: + case vmIntrinsics::_finishPrivateBuffer: case vmIntrinsics::_getReference: case vmIntrinsics::_getBoolean: case vmIntrinsics::_getByte: @@ -345,6 +350,8 @@ bool vmIntrinsics::disabled_by_jvm_flags(vmIntrinsics::ID id) { case vmIntrinsics::_getLong: case vmIntrinsics::_getFloat: case vmIntrinsics::_getDouble: + case vmIntrinsics::_getValue: + case vmIntrinsics::_getFlatValue: case vmIntrinsics::_putReference: case vmIntrinsics::_putBoolean: case vmIntrinsics::_putByte: @@ -354,6 +361,8 @@ bool vmIntrinsics::disabled_by_jvm_flags(vmIntrinsics::ID id) { case vmIntrinsics::_putLong: case vmIntrinsics::_putFloat: case vmIntrinsics::_putDouble: + case vmIntrinsics::_putValue: + case vmIntrinsics::_putFlatValue: case vmIntrinsics::_getReferenceVolatile: case vmIntrinsics::_getBooleanVolatile: case vmIntrinsics::_getByteVolatile: diff --git a/src/hotspot/share/classfile/vmIntrinsics.hpp b/src/hotspot/share/classfile/vmIntrinsics.hpp index c9c5c925f86..38498d6066a 100644 --- a/src/hotspot/share/classfile/vmIntrinsics.hpp +++ b/src/hotspot/share/classfile/vmIntrinsics.hpp @@ -325,6 +325,14 @@ class methodHandle; do_intrinsic(_newArray, java_lang_reflect_Array, newArray_name, newArray_signature, F_SN) \ do_name( newArray_name, "newArray") \ do_signature(newArray_signature, "(Ljava/lang/Class;I)Ljava/lang/Object;") \ + do_intrinsic(_newNullRestrictedAtomicArray, jdk_internal_value_ValueClass, newNullRestrictedAtomicArray_name, newArray_signature3, F_SN) \ + do_name( newNullRestrictedAtomicArray_name, "newNullRestrictedAtomicArray") \ + do_intrinsic(_newNullRestrictedNonAtomicArray, jdk_internal_value_ValueClass, newNullRestrictedNonAtomicArray_name, newArray_signature3, F_SN) \ + do_name( newNullRestrictedNonAtomicArray_name, "newNullRestrictedNonAtomicArray") \ + do_intrinsic(_newNullableAtomicArray, jdk_internal_value_ValueClass, newNullableAtomicArray_name, newArray_signature2, F_SN) \ + do_name( newNullableAtomicArray_name, "newNullableAtomicArray") \ + do_signature(newArray_signature2, "(Ljava/lang/Class;I)[Ljava/lang/Object;") \ + do_signature(newArray_signature3, "(Ljava/lang/Class;ILjava/lang/Object;)[Ljava/lang/Object;") \ \ do_intrinsic(_onSpinWait, java_lang_Thread, onSpinWait_name, onSpinWait_signature, F_S) \ do_name( onSpinWait_name, "onSpinWait") \ @@ -728,6 +736,10 @@ class methodHandle; do_signature(putFloat_signature, "(Ljava/lang/Object;JF)V") \ do_signature(getDouble_signature, "(Ljava/lang/Object;J)D") \ do_signature(putDouble_signature, "(Ljava/lang/Object;JD)V") \ + do_signature(getValue_signature, "(Ljava/lang/Object;JLjava/lang/Class;)Ljava/lang/Object;") \ + do_signature(putValue_signature, "(Ljava/lang/Object;JLjava/lang/Class;Ljava/lang/Object;)V") \ + do_signature(getFlatValue_signature, "(Ljava/lang/Object;JILjava/lang/Class;)Ljava/lang/Object;") \ + do_signature(putFlatValue_signature, "(Ljava/lang/Object;JILjava/lang/Class;Ljava/lang/Object;)V") \ \ do_name(getReference_name,"getReference") do_name(putReference_name,"putReference") \ do_name(getBoolean_name,"getBoolean") do_name(putBoolean_name,"putBoolean") \ @@ -738,6 +750,10 @@ class methodHandle; do_name(getLong_name,"getLong") do_name(putLong_name,"putLong") \ do_name(getFloat_name,"getFloat") do_name(putFloat_name,"putFloat") \ do_name(getDouble_name,"getDouble") do_name(putDouble_name,"putDouble") \ + do_name(getValue_name,"getValue") do_name(putValue_name,"putValue") \ + do_name(getFlatValue_name,"getFlatValue") do_name(putFlatValue_name,"putFlatValue") \ + do_name(makePrivateBuffer_name,"makePrivateBuffer") \ + do_name(finishPrivateBuffer_name,"finishPrivateBuffer") \ \ do_intrinsic(_getReference, jdk_internal_misc_Unsafe, getReference_name, getReference_signature, F_RN) \ do_intrinsic(_getBoolean, jdk_internal_misc_Unsafe, getBoolean_name, getBoolean_signature, F_RN) \ @@ -748,6 +764,8 @@ class methodHandle; do_intrinsic(_getLong, jdk_internal_misc_Unsafe, getLong_name, getLong_signature, F_RN) \ do_intrinsic(_getFloat, jdk_internal_misc_Unsafe, getFloat_name, getFloat_signature, F_RN) \ do_intrinsic(_getDouble, jdk_internal_misc_Unsafe, getDouble_name, getDouble_signature, F_RN) \ + do_intrinsic(_getValue, jdk_internal_misc_Unsafe, getValue_name, getValue_signature, F_RN) \ + do_intrinsic(_getFlatValue, jdk_internal_misc_Unsafe, getFlatValue_name, getFlatValue_signature, F_RN) \ do_intrinsic(_putReference, jdk_internal_misc_Unsafe, putReference_name, putReference_signature, F_RN) \ do_intrinsic(_putBoolean, jdk_internal_misc_Unsafe, putBoolean_name, putBoolean_signature, F_RN) \ do_intrinsic(_putByte, jdk_internal_misc_Unsafe, putByte_name, putByte_signature, F_RN) \ @@ -757,6 +775,11 @@ class methodHandle; do_intrinsic(_putLong, jdk_internal_misc_Unsafe, putLong_name, putLong_signature, F_RN) \ do_intrinsic(_putFloat, jdk_internal_misc_Unsafe, putFloat_name, putFloat_signature, F_RN) \ do_intrinsic(_putDouble, jdk_internal_misc_Unsafe, putDouble_name, putDouble_signature, F_RN) \ + do_intrinsic(_putValue, jdk_internal_misc_Unsafe, putValue_name, putValue_signature, F_RN) \ + do_intrinsic(_putFlatValue, jdk_internal_misc_Unsafe, putFlatValue_name, putFlatValue_signature, F_RN) \ + \ + do_intrinsic(_makePrivateBuffer, jdk_internal_misc_Unsafe, makePrivateBuffer_name, object_object_signature, F_RN) \ + do_intrinsic(_finishPrivateBuffer, jdk_internal_misc_Unsafe, finishPrivateBuffer_name, object_object_signature, F_RN) \ \ do_name(getReferenceVolatile_name,"getReferenceVolatile") do_name(putReferenceVolatile_name,"putReferenceVolatile") \ do_name(getBooleanVolatile_name,"getBooleanVolatile") do_name(putBooleanVolatile_name,"putBooleanVolatile") \ diff --git a/src/hotspot/share/classfile/vmSymbols.cpp b/src/hotspot/share/classfile/vmSymbols.cpp index bd32eac4f34..d884f7ed852 100644 --- a/src/hotspot/share/classfile/vmSymbols.cpp +++ b/src/hotspot/share/classfile/vmSymbols.cpp @@ -110,6 +110,8 @@ void vmSymbols::initialize() { #endif } + initialize_migrated_class_names(); + #ifdef ASSERT // Check for duplicates: @@ -296,3 +298,44 @@ vmSymbolID vmSymbols::find_sid(const char* symbol_name) { if (symbol == nullptr) return vmSymbolID::NO_SID; return find_sid(symbol); } + +// The list of these migrated value classes is in +// open/make/modules/java.base/gensrc/GensrcValueClasses.gmk. + +Symbol* vmSymbols::_migrated_class_names[_migrated_class_names_length]; + +void vmSymbols::initialize_migrated_class_names() { + int i = 0; + _migrated_class_names[i++] = java_lang_Byte(); + _migrated_class_names[i++] = java_lang_Short(); + _migrated_class_names[i++] = java_lang_Integer(); + _migrated_class_names[i++] = java_lang_Long(); + _migrated_class_names[i++] = java_lang_Float(); + _migrated_class_names[i++] = java_lang_Double(); + _migrated_class_names[i++] = java_lang_Boolean(); + _migrated_class_names[i++] = java_lang_Character(); + _migrated_class_names[i++] = java_lang_Number(); + _migrated_class_names[i++] = java_lang_Record(); + _migrated_class_names[i++] = java_util_Optional(); + _migrated_class_names[i++] = java_util_OptionalInt(); + _migrated_class_names[i++] = java_util_OptionalLong(); + _migrated_class_names[i++] = java_util_OptionalDouble(); + _migrated_class_names[i++] = java_time_LocalDate(); + _migrated_class_names[i++] = java_time_LocalDateTime(); + _migrated_class_names[i++] = java_time_LocalTime(); + _migrated_class_names[i++] = java_time_Duration(); + _migrated_class_names[i++] = java_time_Instant(); + _migrated_class_names[i++] = java_time_MonthDay(); + _migrated_class_names[i++] = java_time_ZonedDateTime(); + _migrated_class_names[i++] = java_time_OffsetDateTime(); + _migrated_class_names[i++] = java_time_OffsetTime(); + _migrated_class_names[i++] = java_time_YearMonth(); + _migrated_class_names[i++] = java_time_Year(); + _migrated_class_names[i++] = java_time_Period(); + _migrated_class_names[i++] = java_time_chrono_ChronoLocalDateImpl(); + _migrated_class_names[i++] = java_time_chrono_MinguoDate(); + _migrated_class_names[i++] = java_time_chrono_HijrahDate(); + _migrated_class_names[i++] = java_time_chrono_JapaneseDate(); + _migrated_class_names[i++] = java_time_chrono_ThaiBuddhistDate(); + assert(i == _migrated_class_names_length, "should be"); +} diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 06f27f09c5c..87d83072a91 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -91,6 +91,31 @@ class SerializeClosure; template(java_lang_Long_LongCache, "java/lang/Long$LongCache") \ template(java_lang_Void, "java/lang/Void") \ \ + /* Valhalla migrated classes. */ \ + template(java_lang_Number, "java/lang/Number") \ + template(java_lang_Record, "java/lang/Record") \ + template(java_util_Optional, "java/util/Optional") \ + template(java_util_OptionalInt, "java/util/OptionalInt") \ + template(java_util_OptionalLong, "java/util/OptionalLong") \ + template(java_util_OptionalDouble, "java/util/OptionalDouble") \ + template(java_time_LocalDate, "java/time/LocalDate") \ + template(java_time_LocalDateTime, "java/time/LocalDateTime") \ + template(java_time_LocalTime, "java/time/LocalTime") \ + template(java_time_Duration, "java/time/Duration") \ + template(java_time_Instant, "java/time/Instant") \ + template(java_time_MonthDay, "java/time/MonthDay") \ + template(java_time_ZonedDateTime, "java/time/ZonedDateTime") \ + template(java_time_OffsetDateTime, "java/time/OffsetDateTime") \ + template(java_time_OffsetTime, "java/time/OffsetTime") \ + template(java_time_YearMonth, "java/time/YearMonth") \ + template(java_time_Year, "java/time/Year") \ + template(java_time_Period, "java/time/Period") \ + template(java_time_chrono_ChronoLocalDateImpl, "java/time/chrono/ChronoLocalDateImpl") \ + template(java_time_chrono_MinguoDate, "java/time/chrono/MinguoDate") \ + template(java_time_chrono_HijrahDate, "java/time/chrono/HijrahDate") \ + template(java_time_chrono_JapaneseDate, "java/time/chrono/JapaneseDate") \ + template(java_time_chrono_ThaiBuddhistDate, "java/time/chrono/ThaiBuddhistDate") \ + \ template(jdk_internal_vm_vector_VectorSupport, "jdk/internal/vm/vector/VectorSupport") \ template(jdk_internal_vm_vector_Float16Math, "jdk/internal/vm/vector/Float16Math") \ template(jdk_internal_vm_vector_VectorPayload, "jdk/internal/vm/vector/VectorSupport$VectorPayload") \ @@ -137,7 +162,6 @@ class SerializeClosure; template(java_lang_AssertionStatusDirectives, "java/lang/AssertionStatusDirectives") \ template(jdk_internal_vm_PostVMInitHook, "jdk/internal/vm/PostVMInitHook") \ template(java_util_Iterator, "java/util/Iterator") \ - template(java_lang_Record, "java/lang/Record") \ template(sun_instrument_InstrumentationImpl, "sun/instrument/InstrumentationImpl") \ template(sun_invoke_util_ValueConversions, "sun/invoke/util/ValueConversions") \ \ @@ -167,6 +191,7 @@ class SerializeClosure; template(tag_inner_classes, "InnerClasses") \ template(tag_nest_members, "NestMembers") \ template(tag_nest_host, "NestHost") \ + template(tag_loadable_descriptors, "LoadableDescriptors") \ template(tag_constant_value, "ConstantValue") \ template(tag_code, "Code") \ template(tag_exceptions, "Exceptions") \ @@ -203,6 +228,7 @@ class SerializeClosure; template(java_lang_IllegalCallerException, "java/lang/IllegalCallerException") \ template(java_lang_IllegalStateException, "java/lang/IllegalStateException") \ template(java_lang_IllegalMonitorStateException, "java/lang/IllegalMonitorStateException") \ + template(java_lang_IdentityException, "java/lang/IdentityException") \ template(java_lang_IllegalThreadStateException, "java/lang/IllegalThreadStateException") \ template(java_lang_IndexOutOfBoundsException, "java/lang/IndexOutOfBoundsException") \ template(java_lang_InstantiationException, "java/lang/InstantiationException") \ @@ -250,6 +276,8 @@ class SerializeClosure; template(java_util_concurrent_atomic_AtomicReferenceFieldUpdater_Impl, "java/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl") \ template(jdk_internal_vm_annotation_Contended_signature, "Ljdk/internal/vm/annotation/Contended;") \ template(jdk_internal_vm_annotation_ReservedStackAccess_signature, "Ljdk/internal/vm/annotation/ReservedStackAccess;") \ + template(jdk_internal_vm_annotation_LooselyConsistentValue_signature, "Ljdk/internal/vm/annotation/LooselyConsistentValue;") \ + template(jdk_internal_vm_annotation_NullRestricted_signature, "Ljdk/internal/vm/annotation/NullRestricted;") \ template(jdk_internal_ValueBased_signature, "Ljdk/internal/ValueBased;") \ \ /* class symbols needed by intrinsics */ \ @@ -279,7 +307,6 @@ class SerializeClosure; template(returnType_name, "returnType") \ template(signature_name, "signature") \ template(slot_name, "slot") \ - template(trusted_final_name, "trustedFinal") \ template(blackhole_name, "") /*fake name*/ \ \ /* Support for annotations (JDK 1.5 and above) */ \ @@ -501,6 +528,8 @@ class SerializeClosure; template(module_entry_name, "module_entry") \ template(resolved_references_name, "") \ template(init_lock_name, "") \ + template(null_reset_value_name, ".null_reset") \ + template(empty_marker_name, ".empty") \ template(address_size_name, "ADDRESS_SIZE0") \ template(page_size_name, "PAGE_SIZE") \ template(big_endian_name, "BIG_ENDIAN") \ @@ -575,6 +604,7 @@ class SerializeClosure; template(class_int_signature, "(Ljava/lang/Class;)I") \ template(class_long_signature, "(Ljava/lang/Class;)J") \ template(class_boolean_signature, "(Ljava/lang/Class;)Z") \ + template(class_class_signature, "(Ljava/lang/Class;)Ljava/lang/Class;") \ template(throwable_throwable_signature, "(Ljava/lang/Throwable;)Ljava/lang/Throwable;") \ template(thread_void_signature, "(Ljava/lang/Thread;)V") \ template(runnable_void_signature, "(Ljava/lang/Runnable;)V") \ @@ -584,6 +614,7 @@ class SerializeClosure; template(string_class_signature, "(Ljava/lang/String;)Ljava/lang/Class;") \ template(string_boolean_class_signature, "(Ljava/lang/String;Z)Ljava/lang/Class;") \ template(object_object_object_signature, "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;") \ + template(object_object_boolean_signature, "(Ljava/lang/Object;Ljava/lang/Object;)Z") \ template(string_string_signature, "(Ljava/lang/String;)Ljava/lang/String;") \ template(classloader_class_string_string_long_signature, "(Ljava/lang/ClassLoader;Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)J") \ template(byte_array_void_signature, "([B)V") \ @@ -711,6 +742,8 @@ class SerializeClosure; template(classRedefinedCount_name, "classRedefinedCount") \ template(classLoader_name, "classLoader") \ template(componentType_name, "componentType") \ + template(primaryType_name, "primaryType") \ + template(secondaryType_name, "secondaryType") \ \ /* forEachRemaining support */ \ template(java_util_stream_StreamsRangeIntSpliterator, "java/util/stream/Streams$RangeIntSpliterator") \ @@ -740,6 +773,12 @@ class SerializeClosure; template(toFileURL_name, "toFileURL") \ template(toFileURL_signature, "(Ljava/lang/String;)Ljava/net/URL;") \ \ + template(java_lang_runtime_ValueObjectMethods, "java/lang/runtime/ValueObjectMethods") \ + template(isSubstitutable_name, "isSubstitutable") \ + template(valueObjectHashCode_name, "valueObjectHashCode") \ + template(jdk_internal_value_PrimitiveClass, "jdk/internal/value/PrimitiveClass") \ + template(jdk_internal_value_ValueClass, "jdk/internal/value/ValueClass") \ + \ /* jcmd Thread.dump_to_file */ \ template(jdk_internal_vm_ThreadDumper, "jdk/internal/vm/ThreadDumper") \ template(dumpThreads_name, "dumpThreads") \ @@ -823,6 +862,10 @@ class vmSymbols: AllStatic { // Field signatures indexed by BasicType. static Symbol* _type_signatures[T_VOID+1]; + static void initialize_migrated_class_names(); + + static const int _migrated_class_names_length = 31; + static Symbol* _migrated_class_names[_migrated_class_names_length]; public: // Initialization @@ -858,6 +901,13 @@ class vmSymbols: AllStatic { // No need for this in the product: static const char* name_for(vmSymbolID sid); #endif //PRODUCT + + template + static void migrated_class_names_do(Function f) { + for (int i = 0; i < _migrated_class_names_length; i++) { + f(_migrated_class_names[i]); + } + } }; #endif // SHARE_CLASSFILE_VMSYMBOLS_HPP diff --git a/src/hotspot/share/code/aotCodeCache.cpp b/src/hotspot/share/code/aotCodeCache.cpp index 7e53f493c47..ed562a3d390 100644 --- a/src/hotspot/share/code/aotCodeCache.cpp +++ b/src/hotspot/share/code/aotCodeCache.cpp @@ -1276,6 +1276,7 @@ void AOTCodeAddressTable::init_extrs() { SET_ADDRESS(_extrs, SharedRuntime::handle_wrong_method); SET_ADDRESS(_extrs, SharedRuntime::handle_wrong_method_abstract); SET_ADDRESS(_extrs, SharedRuntime::handle_wrong_method_ic_miss); + SET_ADDRESS(_extrs, SharedRuntime::allocate_inline_types); #if defined(AARCH64) && !defined(ZERO) SET_ADDRESS(_extrs, JavaThread::aarch64_get_thread_helper); #endif @@ -1325,6 +1326,14 @@ void AOTCodeAddressTable::init_extrs() { SET_ADDRESS(_extrs, Runtime1::move_appendix_patching); SET_ADDRESS(_extrs, Runtime1::predicate_failed_trap); SET_ADDRESS(_extrs, Runtime1::unimplemented_entry); + SET_ADDRESS(_extrs, Runtime1::new_null_free_array); + SET_ADDRESS(_extrs, Runtime1::load_flat_array); + SET_ADDRESS(_extrs, Runtime1::store_flat_array); + SET_ADDRESS(_extrs, Runtime1::substitutability_check); + SET_ADDRESS(_extrs, Runtime1::buffer_inline_args); + SET_ADDRESS(_extrs, Runtime1::buffer_inline_args_no_receiver); + SET_ADDRESS(_extrs, Runtime1::throw_identity_exception); + SET_ADDRESS(_extrs, Runtime1::throw_illegal_monitor_state_exception); SET_ADDRESS(_extrs, Thread::current); SET_ADDRESS(_extrs, CompressedKlassPointers::base_addr()); #ifndef PRODUCT @@ -1358,6 +1367,8 @@ void AOTCodeAddressTable::init_extrs() { SET_ADDRESS(_extrs, OptoRuntime::rethrow_C); SET_ADDRESS(_extrs, OptoRuntime::slow_arraycopy_C); SET_ADDRESS(_extrs, OptoRuntime::register_finalizer_C); + SET_ADDRESS(_extrs, OptoRuntime::load_unknown_inline_C); + SET_ADDRESS(_extrs, OptoRuntime::store_unknown_inline_C); #if defined(AARCH64) SET_ADDRESS(_extrs, JavaThread::verify_cross_modify_fence_failure); #endif // AARCH64 @@ -1385,6 +1396,10 @@ void AOTCodeAddressTable::init_extrs() { #endif #endif // ZERO + if (UseCompressedOops) { + SET_ADDRESS(_extrs, CompressedOops::base_addr()); + } + _extrs_complete = true; log_debug(aot, codecache, init)("External addresses recorded"); } diff --git a/src/hotspot/share/code/codeBlob.cpp b/src/hotspot/share/code/codeBlob.cpp index 9ec5478a071..8b8ba1042cc 100644 --- a/src/hotspot/share/code/codeBlob.cpp +++ b/src/hotspot/share/code/codeBlob.cpp @@ -63,6 +63,7 @@ static_assert(!std::is_polymorphic::value, "no virtual metho static_assert(!std::is_polymorphic::value, "no virtual methods are allowed in code blobs"); static_assert(!std::is_polymorphic::value, "no virtual methods are allowed in code blobs"); static_assert(!std::is_polymorphic::value, "no virtual methods are allowed in code blobs"); +static_assert(!std::is_polymorphic::value, "no virtual methods are allowed in code blobs"); static_assert(!std::is_polymorphic::value, "no virtual methods are allowed in code blobs"); static_assert(!std::is_polymorphic::value, "no virtual methods are allowed in code blobs"); static_assert(!std::is_polymorphic::value, "no virtual methods are allowed in code blobs"); @@ -91,6 +92,7 @@ const CodeBlob::Vptr* CodeBlob::vptr(CodeBlobKind kind) { &AdapterBlob::_vpntr, &VtableBlob::_vpntr, &MethodHandlesAdapterBlob::_vpntr, + &BufferedInlineTypeBlob::_vpntr, &RuntimeStub::_vpntr, &DeoptimizationBlob::_vpntr, &SafepointBlob::_vpntr, @@ -426,7 +428,7 @@ BufferBlob* BufferBlob::create(const char* name, CodeBuffer* cb) { assert(name != nullptr, "must provide a name"); { MutexLocker mu(CodeCache_lock, Mutex::_no_safepoint_check_flag); - blob = new (size) BufferBlob(name, CodeBlobKind::Buffer, cb, size); + blob = new (size) BufferBlob(name, CodeBlobKind::Buffer, cb, size, sizeof(BufferBlob)); } // Track memory usage statistic after releasing CodeCache_lock MemoryService::track_code_cache_memory_usage(); @@ -442,12 +444,16 @@ void BufferBlob::free(BufferBlob *blob) { RuntimeBlob::free(blob); } +BufferBlob::BufferBlob(const char* name, CodeBlobKind kind, CodeBuffer* cb, int size, uint16_t header_size, int frame_complete, int frame_size, OopMapSet* oop_maps, bool caller_must_gc_arguments) + : RuntimeBlob(name, kind, cb, size, header_size, frame_complete, frame_size, oop_maps, caller_must_gc_arguments) +{} + //---------------------------------------------------------------------------------------------------- // Implementation of AdapterBlob -AdapterBlob::AdapterBlob(int size, CodeBuffer* cb, int entry_offset[AdapterBlob::ENTRY_COUNT]) : - BufferBlob("I2C/C2I adapters", CodeBlobKind::Adapter, cb, size, sizeof(AdapterBlob)) { +AdapterBlob::AdapterBlob(int size, CodeBuffer* cb, int entry_offset[AdapterBlob::ENTRY_COUNT], int frame_complete, int frame_size, OopMapSet* oop_maps, bool caller_must_gc_arguments) : + BufferBlob("I2C/C2I adapters", CodeBlobKind::Adapter, cb, size, sizeof(AdapterBlob), frame_complete, frame_size, oop_maps, caller_must_gc_arguments) { assert(entry_offset[0] == 0, "sanity check"); for (int i = 1; i < AdapterBlob::ENTRY_COUNT; i++) { // The entry is within the adapter blob or unset. @@ -456,12 +462,15 @@ AdapterBlob::AdapterBlob(int size, CodeBuffer* cb, int entry_offset[AdapterBlob: "invalid entry offset[%d] = 0x%x", i, entry_offset[i]); } _c2i_offset = entry_offset[1]; - _c2i_unverified_offset = entry_offset[2]; - _c2i_no_clinit_check_offset = entry_offset[3]; + _c2i_inline_offset = entry_offset[2]; + _c2i_inline_ro_offset = entry_offset[3]; + _c2i_unverified_offset = entry_offset[4]; + _c2i_unverified_inline_offset = entry_offset[5]; + _c2i_no_clinit_check_offset = entry_offset[6]; CodeCache::commit(this); } -AdapterBlob* AdapterBlob::create(CodeBuffer* cb, int entry_offset[AdapterBlob::ENTRY_COUNT]) { +AdapterBlob* AdapterBlob::create(CodeBuffer* cb, int entry_offset[AdapterBlob::ENTRY_COUNT], int frame_complete, int frame_size, OopMapSet* oop_maps, bool caller_must_gc_arguments) { ThreadInVMfromUnknown __tiv; // get to VM state in case we block on CodeCache_lock CodeCache::gc_on_allocation(); @@ -470,7 +479,7 @@ AdapterBlob* AdapterBlob::create(CodeBuffer* cb, int entry_offset[AdapterBlob::E unsigned int size = CodeBlob::allocation_size(cb, sizeof(AdapterBlob)); { MutexLocker mu(CodeCache_lock, Mutex::_no_safepoint_check_flag); - blob = new (size) AdapterBlob(size, cb, entry_offset); + blob = new (size) AdapterBlob(size, cb, entry_offset, frame_complete, frame_size, oop_maps, caller_must_gc_arguments); } // Track memory usage statistic after releasing CodeCache_lock MemoryService::track_code_cache_memory_usage(); @@ -481,8 +490,11 @@ AdapterBlob* AdapterBlob::create(CodeBuffer* cb, int entry_offset[AdapterBlob::E void AdapterBlob::get_offsets(int entry_offset[ENTRY_COUNT]) { entry_offset[0] = 0; entry_offset[1] = _c2i_offset; - entry_offset[2] = _c2i_unverified_offset; - entry_offset[3] = _c2i_no_clinit_check_offset; + entry_offset[2] = _c2i_inline_offset; + entry_offset[3] = _c2i_inline_ro_offset; + entry_offset[4] = _c2i_unverified_offset; + entry_offset[5] = _c2i_unverified_inline_offset; + entry_offset[6] = _c2i_no_clinit_check_offset; } //---------------------------------------------------------------------------------------------------- @@ -558,6 +570,31 @@ MethodHandlesAdapterBlob* MethodHandlesAdapterBlob::create(int buffer_size) { return blob; } +//---------------------------------------------------------------------------------------------------- +// Implementation of BufferedInlineTypeBlob +BufferedInlineTypeBlob::BufferedInlineTypeBlob(int size, CodeBuffer* cb, int pack_fields_off, int pack_fields_jobject_off, int unpack_fields_off) : + BufferBlob("buffered inline type", CodeBlobKind::BufferedInlineType, cb, size, sizeof(BufferedInlineTypeBlob)), + _pack_fields_off(pack_fields_off), + _pack_fields_jobject_off(pack_fields_jobject_off), + _unpack_fields_off(unpack_fields_off) { + CodeCache::commit(this); +} + +BufferedInlineTypeBlob* BufferedInlineTypeBlob::create(CodeBuffer* cb, int pack_fields_off, int pack_fields_jobject_off, int unpack_fields_off) { + ThreadInVMfromUnknown __tiv; // get to VM state in case we block on CodeCache_lock + + BufferedInlineTypeBlob* blob = nullptr; + unsigned int size = CodeBlob::allocation_size(cb, sizeof(BufferedInlineTypeBlob)); + { + MutexLocker mu(CodeCache_lock, Mutex::_no_safepoint_check_flag); + blob = new (size) BufferedInlineTypeBlob(size, cb, pack_fields_off, pack_fields_jobject_off, unpack_fields_off); + } + // Track memory usage statistic after releasing CodeCache_lock + MemoryService::track_code_cache_memory_usage(); + + return blob; +} + //---------------------------------------------------------------------------------------------------- // Implementation of RuntimeStub diff --git a/src/hotspot/share/code/codeBlob.hpp b/src/hotspot/share/code/codeBlob.hpp index 407974f0428..da88895bfa7 100644 --- a/src/hotspot/share/code/codeBlob.hpp +++ b/src/hotspot/share/code/codeBlob.hpp @@ -58,6 +58,7 @@ enum class CodeBlobType { // AdapterBlob : Used to hold C2I/I2C adapters // VtableBlob : Used for holding vtable chunks // MethodHandlesAdapterBlob : Used to hold MethodHandles adapters +// BufferedInlineTypeBlob : used for pack/unpack handlers // RuntimeStub : Call to VM runtime methods // SingletonBlob : Super-class for all blobs that exist in only one instance // DeoptimizationBlob : Used for deoptimization @@ -83,6 +84,7 @@ enum class CodeBlobKind : u1 { Adapter, Vtable, MHAdapter, + BufferedInlineType, RuntimeStub, Deoptimization, Safepoint, @@ -201,6 +203,7 @@ class CodeBlob { bool is_adapter_blob() const { return _kind == CodeBlobKind::Adapter; } bool is_vtable_blob() const { return _kind == CodeBlobKind::Vtable; } bool is_method_handles_adapter_blob() const { return _kind == CodeBlobKind::MHAdapter; } + bool is_buffered_inline_type_blob() const { return _kind == CodeBlobKind::BufferedInlineType; } bool is_upcall_stub() const { return _kind == CodeBlobKind::Upcall; } // Casting @@ -367,6 +370,7 @@ class BufferBlob: public RuntimeBlob { friend class AdapterBlob; friend class VtableBlob; friend class MethodHandlesAdapterBlob; + friend class BufferedInlineTypeBlob; friend class UpcallStub; friend class WhiteBox; @@ -374,6 +378,7 @@ class BufferBlob: public RuntimeBlob { // Creation support BufferBlob(const char* name, CodeBlobKind kind, int size, uint16_t header_size = sizeof(BufferBlob)); BufferBlob(const char* name, CodeBlobKind kind, CodeBuffer* cb, int size, uint16_t header_size = sizeof(BufferBlob)); + BufferBlob(const char* name, CodeBlobKind kind, CodeBuffer* cb, int size, uint16_t header_size, int frame_complete, int frame_size, OopMapSet* oop_maps, bool caller_must_gc_arguments = false); void* operator new(size_t s, unsigned size) throw(); @@ -405,15 +410,27 @@ class BufferBlob: public RuntimeBlob { class AdapterBlob: public BufferBlob { public: - static const int ENTRY_COUNT = 4; + static const int ENTRY_COUNT = 7; private: - AdapterBlob(int size, CodeBuffer* cb, int entry_offset[ENTRY_COUNT]); + AdapterBlob(int size, CodeBuffer* cb, int entry_offset[ENTRY_COUNT], int frame_complete, int frame_size, OopMapSet* oop_maps, bool caller_must_gc_arguments = false); + // _i2c_offset is always 0 so no need to store it int _c2i_offset; + int _c2i_inline_offset; + int _c2i_inline_ro_offset; int _c2i_unverified_offset; + int _c2i_unverified_inline_offset; int _c2i_no_clinit_check_offset; public: // Creation + static AdapterBlob* create(CodeBuffer* cb, + int entry_offset[ENTRY_COUNT], + int frame_complete, + int frame_size, + OopMapSet* oop_maps, + bool caller_must_gc_arguments = false); + + bool caller_must_gc_arguments(JavaThread* thread) const { return true; } static AdapterBlob* create(CodeBuffer* cb, int entry_offset[ENTRY_COUNT]); void get_offsets(int entry_offset[ENTRY_COUNT]); }; @@ -442,6 +459,25 @@ class MethodHandlesAdapterBlob: public BufferBlob { static MethodHandlesAdapterBlob* create(int buffer_size); }; +//---------------------------------------------------------------------------------------------------- +// BufferedInlineTypeBlob : used for pack/unpack handlers + +class BufferedInlineTypeBlob: public BufferBlob { +private: + const int _pack_fields_off; + const int _pack_fields_jobject_off; + const int _unpack_fields_off; + + BufferedInlineTypeBlob(int size, CodeBuffer* cb, int pack_fields_off, int pack_fields_jobject_off, int unpack_fields_off); + +public: + // Creation + static BufferedInlineTypeBlob* create(CodeBuffer* cb, int pack_fields_off, int pack_fields_jobject_off, int unpack_fields_off); + + address pack_fields() const { return code_begin() + _pack_fields_off; } + address pack_fields_jobject() const { return code_begin() + _pack_fields_jobject_off; } + address unpack_fields() const { return code_begin() + _unpack_fields_off; } +}; //---------------------------------------------------------------------------------------------------- // RuntimeStub: describes stubs used by compiled code to call a (static) C++ runtime routine diff --git a/src/hotspot/share/code/compiledIC.cpp b/src/hotspot/share/code/compiledIC.cpp index 2547b8711db..8141cf6c04e 100644 --- a/src/hotspot/share/code/compiledIC.cpp +++ b/src/hotspot/share/code/compiledIC.cpp @@ -201,7 +201,7 @@ void CompiledIC::set_to_clean() { _call->set_destination_mt_safe(SharedRuntime::get_resolve_virtual_call_stub()); } -void CompiledIC::set_to_monomorphic() { +void CompiledIC::set_to_monomorphic(bool caller_is_c1) { assert(data()->is_initialized(), "must be initialized"); Method* method = data()->speculated_method(); nmethod* code = method->code(); @@ -209,9 +209,9 @@ void CompiledIC::set_to_monomorphic() { bool to_compiled = code != nullptr && code->is_in_use() && !code->is_unloading(); if (to_compiled) { - entry = code->entry_point(); + entry = caller_is_c1 ? code->inline_entry_point() : code->entry_point(); } else { - entry = method->get_c2i_unverified_entry(); + entry = caller_is_c1 ? method->get_c2i_unverified_inline_entry() : method->get_c2i_unverified_entry(); } log_trace(inlinecache)("IC@" INTPTR_FORMAT ": monomorphic to %s: %s", @@ -222,7 +222,7 @@ void CompiledIC::set_to_monomorphic() { _call->set_destination_mt_safe(entry); } -void CompiledIC::set_to_megamorphic(CallInfo* call_info) { +void CompiledIC::set_to_megamorphic(CallInfo* call_info, bool caller_is_c1) { assert(data()->is_initialized(), "must be initialized"); address entry; @@ -233,7 +233,7 @@ void CompiledIC::set_to_megamorphic(CallInfo* call_info) { return; } else if (call_info->call_kind() == CallInfo::itable_call) { int itable_index = call_info->itable_index(); - entry = VtableStubs::find_itable_stub(itable_index); + entry = VtableStubs::find_itable_stub(itable_index, caller_is_c1); if (entry == nullptr) { return; } @@ -249,7 +249,7 @@ void CompiledIC::set_to_megamorphic(CallInfo* call_info) { // Can be different than selected_method->vtable_index(), due to package-private etc. int vtable_index = call_info->vtable_index(); assert(call_info->resolved_klass()->verify_vtable_index(vtable_index), "sanity check"); - entry = VtableStubs::find_vtable_stub(vtable_index); + entry = VtableStubs::find_vtable_stub(vtable_index, caller_is_c1); if (entry == nullptr) { return; } @@ -263,7 +263,7 @@ void CompiledIC::set_to_megamorphic(CallInfo* call_info) { assert(is_megamorphic(), "sanity check"); } -void CompiledIC::update(CallInfo* call_info, Klass* receiver_klass) { +void CompiledIC::update(CallInfo* call_info, Klass* receiver_klass, bool caller_is_c1) { // If this is the first time we fix the inline cache, we ensure it's initialized ensure_initialized(call_info, receiver_klass); @@ -275,11 +275,11 @@ void CompiledIC::update(CallInfo* call_info, Klass* receiver_klass) { if (is_speculated_klass(receiver_klass)) { // If the speculated class matches the receiver klass, we can speculate that will // continue to be the case with a monomorphic inline cache - set_to_monomorphic(); + set_to_monomorphic(caller_is_c1); } else { // If the dynamic type speculation fails, we try to transform to a megamorphic state // for the inline cache using stubs to dispatch in tables - set_to_megamorphic(call_info); + set_to_megamorphic(call_info, caller_is_c1); } } @@ -344,7 +344,7 @@ void CompiledDirectCall::set_to_clean() { log_debug(inlinecache)("DC@" INTPTR_FORMAT ": set to clean", p2i(_call->instruction_address())); } -void CompiledDirectCall::set(const methodHandle& callee_method) { +void CompiledDirectCall::set(const methodHandle& callee_method, bool caller_is_c1) { nmethod* code = callee_method->code(); nmethod* caller = CodeCache::find_nmethod(instruction_address()); assert(caller != nullptr, "did not find caller nmethod"); @@ -355,13 +355,13 @@ void CompiledDirectCall::set(const methodHandle& callee_method) { bool to_compiled = !to_interp_cont_enter && code != nullptr && code->is_in_use() && !code->is_unloading(); if (to_compiled) { - _call->set_destination_mt_safe(code->verified_entry_point()); + _call->set_destination_mt_safe(caller_is_c1 ? code->verified_inline_entry_point() : code->verified_entry_point()); assert(is_call_to_compiled(), "should be compiled after set to compiled"); } else { // Patch call site to C2I adapter if code is deoptimized or unloaded. // We also need to patch the static call stub to set the rmethod register // to the callee_method so the c2i adapter knows how to build the frame - set_to_interpreted(callee_method, callee_method->get_c2i_entry()); + set_to_interpreted(callee_method, caller_is_c1 ? callee_method->get_c2i_inline_entry() : callee_method->get_c2i_entry()); assert(is_call_to_interpreted(), "should be interpreted after set to interpreted"); } diff --git a/src/hotspot/share/code/compiledIC.hpp b/src/hotspot/share/code/compiledIC.hpp index 624c1b428de..3308cbf2cb8 100644 --- a/src/hotspot/share/code/compiledIC.hpp +++ b/src/hotspot/share/code/compiledIC.hpp @@ -109,8 +109,8 @@ class CompiledIC: public ResourceObj { bool is_speculated_klass(Klass* receiver_klass); // Inline cache states - void set_to_monomorphic(); - void set_to_megamorphic(CallInfo* call_info); + void set_to_monomorphic(bool caller_is_c1); + void set_to_megamorphic(CallInfo* call_info, bool caller_is_c1); public: // conversion (machine PC to CompiledIC*) @@ -131,7 +131,7 @@ class CompiledIC: public ResourceObj { // MT-safe patching of inline caches. Note: Only safe to call is_xxx when holding the CompiledICLocker // so you are guaranteed that no patching takes place. The same goes for verify. void set_to_clean(); - void update(CallInfo* call_info, Klass* receiver_klass); + void update(CallInfo* call_info, Klass* receiver_klass, bool caller_is_c1); // GC support void clean_metadata(); @@ -158,7 +158,7 @@ CompiledIC* CompiledIC_at(RelocIterator* reloc_iter); // -----<----- Clean ----->----- // / \ // / \ -// compilled code <------------> interpreted code +// compiled code <------------> interpreted code // // Clean: Calls directly to runtime method for fixup // Compiled code: Calls directly to compiled code @@ -213,7 +213,7 @@ class CompiledDirectCall : public ResourceObj { // Clean static call (will force resolving on next use) void set_to_clean(); - void set(const methodHandle& callee_method); + void set(const methodHandle& callee_method, bool caller_is_c1); // State bool is_clean() const; diff --git a/src/hotspot/share/code/debugInfo.cpp b/src/hotspot/share/code/debugInfo.cpp index b6f58908c2c..5d4b00a1224 100644 --- a/src/hotspot/share/code/debugInfo.cpp +++ b/src/hotspot/share/code/debugInfo.cpp @@ -161,6 +161,7 @@ void ObjectValue::set_value(oop value) { void ObjectValue::read_object(DebugInfoReadStream* stream) { _is_root = stream->read_bool(); _klass = read_from(stream); + _properties = read_from(stream); assert(_klass->is_constant_oop(), "should be constant java mirror oop"); int length = stream->read_int(); for (int i = 0; i < length; i++) { @@ -179,6 +180,7 @@ void ObjectValue::write_on(DebugInfoWriteStream* stream) { stream->write_int(_id); stream->write_bool(_is_root); _klass->write_on(stream); + _properties->write_on(stream); int length = _field_values.length(); stream->write_int(length); for (int i = 0; i < length; i++) { diff --git a/src/hotspot/share/code/debugInfo.hpp b/src/hotspot/share/code/debugInfo.hpp index 6aba5b67cb6..182c46c6274 100644 --- a/src/hotspot/share/code/debugInfo.hpp +++ b/src/hotspot/share/code/debugInfo.hpp @@ -130,6 +130,7 @@ class ObjectValue: public ScopeValue { protected: int _id; ScopeValue* _klass; + ScopeValue* _properties; // Used to pass additional data like the null marker or array properties. GrowableArray _field_values; Handle _value; bool _visited; @@ -140,9 +141,10 @@ class ObjectValue: public ScopeValue { // Otherwise false, meaning it's just a candidate // in an object allocation merge. public: - ObjectValue(int id, ScopeValue* klass = nullptr, bool is_scalar_replaced = true) + ObjectValue(int id, ScopeValue* klass = nullptr, bool is_scalar_replaced = true, ScopeValue* properties = nullptr) : _id(id) , _klass(klass) + , _properties((properties == nullptr) ? new MarkerValue() : properties) , _field_values() , _value() , _visited(false) @@ -155,6 +157,7 @@ class ObjectValue: public ScopeValue { bool is_object() const { return true; } int id() const { return _id; } virtual ScopeValue* klass() const { return _klass; } + virtual ScopeValue* properties() const { return _properties; } virtual GrowableArray* field_values() { return &_field_values; } virtual ScopeValue* field_at(int i) const { return _field_values.at(i); } virtual int field_size() { return _field_values.length(); } @@ -162,6 +165,7 @@ class ObjectValue: public ScopeValue { bool is_visited() const { return _visited; } bool is_scalar_replaced() const { return _is_scalar_replaced; } bool is_root() const { return _is_root; } + bool has_properties() const { return !_properties->is_marker(); } void set_id(int id) { _id = id; } virtual void set_value(oop value); diff --git a/src/hotspot/share/code/debugInfoRec.cpp b/src/hotspot/share/code/debugInfoRec.cpp index 8449f5d6929..d96a4bda2de 100644 --- a/src/hotspot/share/code/debugInfoRec.cpp +++ b/src/hotspot/share/code/debugInfoRec.cpp @@ -285,6 +285,7 @@ void DebugInformationRecorder::describe_scope(int pc_offset, bool rethrow_exception, bool is_method_handle_invoke, bool return_oop, + bool return_scalarized, bool has_ea_local_in_scope, bool arg_escape, DebugToken* locals, @@ -303,6 +304,7 @@ void DebugInformationRecorder::describe_scope(int pc_offset, last_pd->set_rethrow_exception(rethrow_exception); last_pd->set_is_method_handle_invoke(is_method_handle_invoke); last_pd->set_return_oop(return_oop); + last_pd->set_return_scalarized(return_scalarized); last_pd->set_has_ea_local_in_scope(has_ea_local_in_scope); last_pd->set_arg_escape(arg_escape); diff --git a/src/hotspot/share/code/debugInfoRec.hpp b/src/hotspot/share/code/debugInfoRec.hpp index f6d7cf6d278..113a7a59a9a 100644 --- a/src/hotspot/share/code/debugInfoRec.hpp +++ b/src/hotspot/share/code/debugInfoRec.hpp @@ -107,6 +107,7 @@ class DebugInformationRecorder: public ResourceObj { bool rethrow_exception = false, bool is_method_handle_invoke = false, bool return_oop = false, + bool return_scalarized = false, bool has_ea_local_in_scope = false, bool arg_escape = false, DebugToken* locals = nullptr, diff --git a/src/hotspot/share/code/dependencies.cpp b/src/hotspot/share/code/dependencies.cpp index d90695739a1..99c29befa27 100644 --- a/src/hotspot/share/code/dependencies.cpp +++ b/src/hotspot/share/code/dependencies.cpp @@ -1695,7 +1695,8 @@ Klass* Dependencies::check_evol_method(Method* m) { // Or is there a now a breakpoint? // (Assumes compiled code cannot handle bkpts; change if UseFastBreakpoints.) if (m->is_old() - || m->number_of_breakpoints() > 0) { + || m->number_of_breakpoints() > 0 + || m->mismatch()) { return m->method_holder(); } else { return nullptr; diff --git a/src/hotspot/share/code/nmethod.cpp b/src/hotspot/share/code/nmethod.cpp index b8b3a70bf58..4478290b636 100644 --- a/src/hotspot/share/code/nmethod.cpp +++ b/src/hotspot/share/code/nmethod.cpp @@ -720,6 +720,17 @@ void nmethod::preserve_callee_argument_oops(frame fr, const RegisterMap *reg_map has_receiver = !(callee->access_flags().is_static()); has_appendix = false; signature = callee->signature(); + + // If inline types are passed as fields, use the extended signature + // which contains the types of all (oop) fields of the inline type. + if (is_compiled_by_c2() && callee->has_scalarized_args()) { + const GrowableArray* sig = callee->adapter()->get_sig_cc(); + assert(sig != nullptr, "sig should never be null"); + TempNewSymbol tmp_sig = SigEntry::create_symbol(sig); + has_receiver = false; // The extended signature contains the receiver type + fr.oops_compiled_arguments_do(tmp_sig, has_receiver, has_appendix, reg_map, f); + return; + } } else { SimpleScopeDesc ssd(this, pc); @@ -1256,6 +1267,10 @@ void nmethod::init_defaults(CodeBuffer *code_buffer, CodeOffsets* offsets) { CHECKED_CAST(_entry_offset, uint16_t, (offsets->value(CodeOffsets::Entry))); CHECKED_CAST(_verified_entry_offset, uint16_t, (offsets->value(CodeOffsets::Verified_Entry))); + _inline_entry_point = entry_point(); + _verified_inline_entry_point = verified_entry_point(); + _verified_inline_ro_entry_point = verified_entry_point(); + _skipped_instructions_size = code_buffer->total_skipped_instructions_size(); } @@ -1295,7 +1310,7 @@ nmethod::nmethod( { DEBUG_ONLY(NoSafepointVerifier nsv;) assert_locked_or_safepoint(CodeCache_lock); - + assert(!method->has_scalarized_args(), "scalarized native wrappers not supported yet"); init_defaults(code_buffer, offsets); _osr_entry_point = nullptr; @@ -1499,6 +1514,10 @@ nmethod::nmethod( CHECKED_CAST(metadata_size, uint16_t, align_up(code_buffer->total_metadata_size(), wordSize)); JVMCI_ONLY( _metadata_size = metadata_size; ) int jvmci_data_size = 0 JVMCI_ONLY( + align_up(compiler->is_jvmci() ? jvmci_data->size() : 0, oopSize)); + _inline_entry_point = code_begin() + offsets->value(CodeOffsets::Inline_Entry); + _verified_inline_entry_point = code_begin() + offsets->value(CodeOffsets::Verified_Inline_Entry); + _verified_inline_ro_entry_point = code_begin() + offsets->value(CodeOffsets::Verified_Inline_Entry_RO); + assert(_mutable_data_size == _relocation_size + metadata_size + jvmci_data_size, "wrong mutable data size: %d != %d + %d + %d", _mutable_data_size, _relocation_size, metadata_size, jvmci_data_size); @@ -3716,7 +3735,10 @@ const char* nmethod::nmethod_section_label(address pos) const { const char* label = nullptr; if (pos == code_begin()) label = "[Instructions begin]"; if (pos == entry_point()) label = "[Entry Point]"; + if (pos == inline_entry_point()) label = "[Inline Entry Point]"; if (pos == verified_entry_point()) label = "[Verified Entry Point]"; + if (pos == verified_inline_entry_point()) label = "[Verified Inline Entry Point]"; + if (pos == verified_inline_ro_entry_point()) label = "[Verified Inline Entry Point (RO)]"; if (has_method_handle_invokes() && (pos == deopt_mh_handler_begin())) label = "[Deopt MH Handler Code]"; if (pos == consts_begin() && pos != insts_begin()) label = "[Constants]"; // Check stub_code before checking exception_handler or deopt_handler. @@ -3726,105 +3748,144 @@ const char* nmethod::nmethod_section_label(address pos) const { return label; } +static int maybe_print_entry_label(outputStream* stream, address pos, address entry, const char* label) { + if (pos == entry) { + stream->bol(); + stream->print_cr("%s", label); + return 1; + } else { + return 0; + } +} + void nmethod::print_nmethod_labels(outputStream* stream, address block_begin, bool print_section_labels) const { if (print_section_labels) { - const char* label = nmethod_section_label(block_begin); - if (label != nullptr) { - stream->bol(); - stream->print_cr("%s", label); - } - } - - if (block_begin == entry_point()) { - Method* m = method(); - if (m != nullptr) { - stream->print(" # "); - m->print_value_on(stream); - stream->cr(); - } - if (m != nullptr && !is_osr_method()) { - ResourceMark rm; - int sizeargs = m->size_of_parameters(); - BasicType* sig_bt = NEW_RESOURCE_ARRAY(BasicType, sizeargs); - VMRegPair* regs = NEW_RESOURCE_ARRAY(VMRegPair, sizeargs); - { - int sig_index = 0; - if (!m->is_static()) - sig_bt[sig_index++] = T_OBJECT; // 'this' - for (SignatureStream ss(m->signature()); !ss.at_return_type(); ss.next()) { - BasicType t = ss.type(); - sig_bt[sig_index++] = t; - if (type2size[t] == 2) { - sig_bt[sig_index++] = T_VOID; - } else { - assert(type2size[t] == 1, "size is 1 or 2"); - } - } - assert(sig_index == sizeargs, ""); + int n = 0; + // Multiple entry points may be at the same position. Print them all. + n += maybe_print_entry_label(stream, block_begin, entry_point(), "[Entry Point]"); + n += maybe_print_entry_label(stream, block_begin, inline_entry_point(), "[Inline Entry Point]"); + n += maybe_print_entry_label(stream, block_begin, verified_entry_point(), "[Verified Entry Point]"); + n += maybe_print_entry_label(stream, block_begin, verified_inline_entry_point(), "[Verified Inline Entry Point]"); + n += maybe_print_entry_label(stream, block_begin, verified_inline_ro_entry_point(), "[Verified Inline Entry Point (RO)]"); + if (n == 0) { + const char* label = nmethod_section_label(block_begin); + if (label != nullptr) { + stream->bol(); + stream->print_cr("%s", label); } - const char* spname = "sp"; // make arch-specific? - SharedRuntime::java_calling_convention(sig_bt, regs, sizeargs); - int stack_slot_offset = this->frame_size() * wordSize; - int tab1 = 14, tab2 = 24; - int sig_index = 0; - int arg_index = (m->is_static() ? 0 : -1); - bool did_old_sp = false; - for (SignatureStream ss(m->signature()); !ss.at_return_type(); ) { - bool at_this = (arg_index == -1); - bool at_old_sp = false; - BasicType t = (at_this ? T_OBJECT : ss.type()); - assert(t == sig_bt[sig_index], "sigs in sync"); - if (at_this) - stream->print(" # this: "); - else - stream->print(" # parm%d: ", arg_index); - stream->move_to(tab1); - VMReg fst = regs[sig_index].first(); - VMReg snd = regs[sig_index].second(); - if (fst->is_reg()) { - stream->print("%s", fst->name()); - if (snd->is_valid()) { - stream->print(":%s", snd->name()); - } - } else if (fst->is_stack()) { - int offset = fst->reg2stack() * VMRegImpl::stack_slot_size + stack_slot_offset; - if (offset == stack_slot_offset) at_old_sp = true; - stream->print("[%s+0x%x]", spname, offset); - } else { - stream->print("reg%d:%d??", (int)(intptr_t)fst, (int)(intptr_t)snd); - } - stream->print(" "); - stream->move_to(tab2); - stream->print("= "); - if (at_this) { - m->method_holder()->print_value_on(stream); - } else { - bool did_name = false; - if (!at_this && ss.is_reference()) { - Symbol* name = ss.as_symbol(); - name->print_value_on(stream); - did_name = true; - } - if (!did_name) - stream->print("%s", type2name(t)); - } - if (at_old_sp) { - stream->print(" (%s of caller)", spname); - did_old_sp = true; - } - stream->cr(); - sig_index += type2size[t]; - arg_index += 1; - if (!at_this) ss.next(); + } + } + + Method* m = method(); + if (m == nullptr || is_osr_method()) { + return; + } + + // Print the name of the method (only once) + address low = MIN3(entry_point(), + verified_entry_point(), + inline_entry_point()); + // The verified inline entry point and verified inline RO entry point are not always + // used. When they are unused. CodeOffsets::Verified_Inline_Entry(_RO) is -1. Hence, + // the calculated entry point is smaller than the block they are offsetting into. + if (verified_inline_entry_point() >= block_begin) { + low = MIN2(low, verified_inline_entry_point()); + } + if (verified_inline_ro_entry_point() >= block_begin) { + low = MIN2(low, verified_inline_ro_entry_point()); + } + assert(low != 0, "sanity"); + if (block_begin == low) { + stream->print(" # "); + m->print_value_on(stream); + stream->cr(); + } + + // Print the arguments for the 3 types of verified entry points + CompiledEntrySignature ces(m); + ces.compute_calling_conventions(false); + const GrowableArray* sig_cc; + const VMRegPair* regs; + if (block_begin == verified_entry_point()) { + sig_cc = ces.sig_cc(); + regs = ces.regs_cc(); + } else if (block_begin == verified_inline_entry_point()) { + sig_cc = ces.sig(); + regs = ces.regs(); + } else if (block_begin == verified_inline_ro_entry_point()) { + sig_cc = ces.sig_cc_ro(); + regs = ces.regs_cc_ro(); + } else { + return; + } + + bool has_this = !m->is_static(); + if (ces.has_inline_recv() && block_begin == verified_entry_point()) { + // argument is scalarized for verified_entry_point() + has_this = false; + } + const char* spname = "sp"; // make arch-specific? + int stack_slot_offset = this->frame_size() * wordSize; + int tab1 = 14, tab2 = 24; + int sig_index = 0; + int arg_index = has_this ? -1 : 0; + bool did_old_sp = false; + for (ExtendedSignature sig = ExtendedSignature(sig_cc, SigEntryFilter()); !sig.at_end(); ++sig) { + bool at_this = (arg_index == -1); + bool at_old_sp = false; + BasicType t = (*sig)._bt; + if (at_this) { + stream->print(" # this: "); + } else { + stream->print(" # parm%d: ", arg_index); + } + stream->move_to(tab1); + VMReg fst = regs[sig_index].first(); + VMReg snd = regs[sig_index].second(); + if (fst->is_reg()) { + stream->print("%s", fst->name()); + if (snd->is_valid()) { + stream->print(":%s", snd->name()); } - if (!did_old_sp) { - stream->print(" # "); - stream->move_to(tab1); - stream->print("[%s+0x%x]", spname, stack_slot_offset); - stream->print(" (%s of caller)", spname); - stream->cr(); + } else if (fst->is_stack()) { + int offset = fst->reg2stack() * VMRegImpl::stack_slot_size + stack_slot_offset; + if (offset == stack_slot_offset) at_old_sp = true; + stream->print("[%s+0x%x]", spname, offset); + } else { + stream->print("reg%d:%d??", (int)(intptr_t)fst, (int)(intptr_t)snd); + } + stream->print(" "); + stream->move_to(tab2); + stream->print("= "); + if (at_this) { + m->method_holder()->print_value_on(stream); + } else { + bool did_name = false; + if (is_reference_type(t)) { + Symbol* name = (*sig)._name; + name->print_value_on(stream); + did_name = true; + } + if (!did_name) + stream->print("%s", type2name(t)); + if ((*sig)._null_marker) { + stream->print(" (null marker)"); } } + if (at_old_sp) { + stream->print(" (%s of caller)", spname); + did_old_sp = true; + } + stream->cr(); + sig_index += type2size[t]; + arg_index += 1; + } + if (!did_old_sp) { + stream->print(" # "); + stream->move_to(tab1); + stream->print("[%s+0x%x]", spname, stack_slot_offset); + stream->print(" (%s of caller)", spname); + stream->cr(); } } @@ -3948,7 +4009,7 @@ void nmethod::print_code_comment_on(outputStream* st, int column, address begin, break; } } - st->print(" {reexecute=%d rethrow=%d return_oop=%d}", sd->should_reexecute(), sd->rethrow_exception(), sd->return_oop()); + st->print(" {reexecute=%d rethrow=%d return_oop=%d return_scalarized=%d}", sd->should_reexecute(), sd->rethrow_exception(), sd->return_oop(), sd->return_scalarized()); } // Print all scopes diff --git a/src/hotspot/share/code/nmethod.hpp b/src/hotspot/share/code/nmethod.hpp index 301454b6591..981ab7fbaa6 100644 --- a/src/hotspot/share/code/nmethod.hpp +++ b/src/hotspot/share/code/nmethod.hpp @@ -27,6 +27,7 @@ #include "code/codeBlob.hpp" #include "code/pcDesc.hpp" +#include "compiler/compilerDefinitions.hpp" #include "oops/metadata.hpp" #include "oops/method.hpp" @@ -213,6 +214,10 @@ class nmethod : public CodeBlob { address _osr_entry_point; // entry point for on stack replacement uint16_t _entry_offset; // entry point with class check uint16_t _verified_entry_offset; // entry point without class check + // TODO: can these be uint16_t, seem rely on -1 CodeOffset, can change later... + address _inline_entry_point; // inline type entry point (unpack all inline type args) with class check + address _verified_inline_entry_point; // inline type entry point (unpack all inline type args) without class check + address _verified_inline_ro_entry_point; // inline type entry point (unpack receiver only) without class check int _entry_bci; // != InvocationEntryBci if this nmethod is an on-stack replacement method int _immutable_data_size; @@ -683,6 +688,9 @@ class nmethod : public CodeBlob { // entry points address entry_point() const { return code_begin() + _entry_offset; } // normal entry point address verified_entry_point() const { return code_begin() + _verified_entry_offset; } // if klass is correct + address inline_entry_point() const { return _inline_entry_point; } // inline type entry point (unpack all inline type args) + address verified_inline_entry_point() const { return _verified_inline_entry_point; } // inline type entry point (unpack all inline type args) without class check + address verified_inline_ro_entry_point() const { return _verified_inline_ro_entry_point; } // inline type entry point (only unpack receiver) without class check enum : signed char { not_installed = -1, // in construction, only the owner doing the construction is // allowed to advance state @@ -752,6 +760,16 @@ class nmethod : public CodeBlob { bool has_wide_vectors() const { return _has_wide_vectors; } void set_has_wide_vectors(bool z) { _has_wide_vectors = z; } + bool needs_stack_repair() const { + if (is_compiled_by_c1()) { + return method()->c1_needs_stack_repair(); + } else if (is_compiled_by_c2()) { + return method()->c2_needs_stack_repair(); + } else { + return false; + } + } + bool has_flushed_dependencies() const { return _has_flushed_dependencies; } void set_has_flushed_dependencies(bool z) { assert(!has_flushed_dependencies(), "should only happen once"); diff --git a/src/hotspot/share/code/pcDesc.hpp b/src/hotspot/share/code/pcDesc.hpp index 8048c3909c7..8fda7b6c60c 100644 --- a/src/hotspot/share/code/pcDesc.hpp +++ b/src/hotspot/share/code/pcDesc.hpp @@ -44,7 +44,8 @@ class PcDesc { PCDESC_return_oop = 1 << 2, PCDESC_rethrow_exception = 1 << 3, PCDESC_has_ea_local_in_scope = 1 << 4, - PCDESC_arg_escape = 1 << 5 + PCDESC_arg_escape = 1 << 5, + PCDESC_return_scalarized = 1 << 6 }; int _flags; @@ -91,6 +92,8 @@ class PcDesc { bool return_oop() const { return (_flags & PCDESC_return_oop) != 0; } void set_return_oop(bool z) { set_flag(PCDESC_return_oop, z); } + bool return_scalarized() const { return (_flags & PCDESC_return_scalarized) != 0; } + void set_return_scalarized(bool z) { set_flag(PCDESC_return_scalarized, z); } // Indicates if there are objects in scope that, based on escape analysis, are local to the // compiled method or local to the current thread, i.e. NoEscape or ArgEscape bool has_ea_local_in_scope() const { return (_flags & PCDESC_has_ea_local_in_scope) != 0; } diff --git a/src/hotspot/share/code/scopeDesc.cpp b/src/hotspot/share/code/scopeDesc.cpp index d3e08a886e6..adc175b2369 100644 --- a/src/hotspot/share/code/scopeDesc.cpp +++ b/src/hotspot/share/code/scopeDesc.cpp @@ -39,6 +39,7 @@ ScopeDesc::ScopeDesc(const nmethod* code, PcDesc* pd, bool ignore_objects) { _reexecute = pd->should_reexecute(); _rethrow_exception = pd->rethrow_exception(); _return_oop = pd->return_oop(); + _return_scalarized = pd->return_scalarized(); _has_ea_local_in_scope = ignore_objects ? false : pd->has_ea_local_in_scope(); _arg_escape = ignore_objects ? false : pd->arg_escape(); decode_body(); @@ -52,6 +53,7 @@ void ScopeDesc::initialize(const ScopeDesc* parent, int decode_offset) { _reexecute = false; //reexecute only applies to the first scope _rethrow_exception = false; _return_oop = false; + _return_scalarized = false; _has_ea_local_in_scope = parent->has_ea_local_in_scope(); _arg_escape = false; decode_body(); diff --git a/src/hotspot/share/code/scopeDesc.hpp b/src/hotspot/share/code/scopeDesc.hpp index 8e8a876095e..c7ae35000b2 100644 --- a/src/hotspot/share/code/scopeDesc.hpp +++ b/src/hotspot/share/code/scopeDesc.hpp @@ -72,6 +72,7 @@ class ScopeDesc : public ResourceObj { bool should_reexecute() const { return _reexecute; } bool rethrow_exception() const { return _rethrow_exception; } bool return_oop() const { return _return_oop; } + bool return_scalarized() const { return _return_scalarized; } // Returns true if one or more NoEscape or ArgEscape objects exist in // any of the scopes at compiled pc. bool has_ea_local_in_scope() const { return _has_ea_local_in_scope; } @@ -105,10 +106,10 @@ class ScopeDesc : public ResourceObj { bool _reexecute; bool _rethrow_exception; bool _return_oop; + bool _return_scalarized; bool _has_ea_local_in_scope; // One or more NoEscape or ArgEscape objects exist in // any of the scopes at compiled pc. bool _arg_escape; // Compiled Java call in youngest scope passes ArgEscape - // Decoding offsets int _decode_offset; int _sender_decode_offset; diff --git a/src/hotspot/share/code/vtableStubs.cpp b/src/hotspot/share/code/vtableStubs.cpp index 00826f82036..b3dd150d2e7 100644 --- a/src/hotspot/share/code/vtableStubs.cpp +++ b/src/hotspot/share/code/vtableStubs.cpp @@ -204,18 +204,18 @@ void VtableStubs::bookkeeping(MacroAssembler* masm, outputStream* out, VtableStu } -address VtableStubs::find_stub(bool is_vtable_stub, int vtable_index) { +address VtableStubs::find_stub(bool is_vtable_stub, int vtable_index, bool caller_is_c1) { assert(vtable_index >= 0, "must be positive"); VtableStub* s; { MutexLocker ml(VtableStubs_lock, Mutex::_no_safepoint_check_flag); - s = lookup(is_vtable_stub, vtable_index); + s = lookup(is_vtable_stub, vtable_index, caller_is_c1); if (s == nullptr) { if (is_vtable_stub) { - s = create_vtable_stub(vtable_index); + s = create_vtable_stub(vtable_index, caller_is_c1); } else { - s = create_itable_stub(vtable_index); + s = create_itable_stub(vtable_index, caller_is_c1); } // Creation of vtable or itable can fail if there is not enough free space in the code cache. @@ -223,9 +223,10 @@ address VtableStubs::find_stub(bool is_vtable_stub, int vtable_index) { return nullptr; } - enter(is_vtable_stub, vtable_index, s); + enter(is_vtable_stub, vtable_index, caller_is_c1, s); if (PrintAdapterHandlers) { - tty->print_cr("Decoding VtableStub %s[%d]@" PTR_FORMAT " [" PTR_FORMAT ", " PTR_FORMAT "] (%zu bytes)", + tty->print_cr("Decoding VtableStub (%s) %s[%d]@" PTR_FORMAT " [" PTR_FORMAT ", " PTR_FORMAT "] (%zu bytes)", + caller_is_c1 ? "c1" : "full opt", is_vtable_stub? "vtbl": "itbl", vtable_index, p2i(VtableStub::receiver_location()), p2i(s->code_begin()), p2i(s->code_end()), pointer_delta(s->code_end(), s->code_begin(), 1)); Disassembler::decode(s->code_begin(), s->code_end()); @@ -235,7 +236,7 @@ address VtableStubs::find_stub(bool is_vtable_stub, int vtable_index) { // all locks. Only post this event if a new state is not required. Creating a new state would // cause a safepoint and the caller of this code has a NoSafepointVerifier. if (JvmtiExport::should_post_dynamic_code_generated()) { - JvmtiExport::post_dynamic_code_generated_while_holding_locks(is_vtable_stub? "vtable stub": "itable stub", + JvmtiExport::post_dynamic_code_generated_while_holding_locks(is_vtable_stub? "vtable stub": "itable stub", // FIXME: need to pass caller_is_c1?? s->code_begin(), s->code_end()); } } @@ -244,14 +245,17 @@ address VtableStubs::find_stub(bool is_vtable_stub, int vtable_index) { } -inline uint VtableStubs::hash(bool is_vtable_stub, int vtable_index){ +inline uint VtableStubs::hash(bool is_vtable_stub, int vtable_index, bool caller_is_c1) { // Assumption: receiver_location < 4 in most cases. int hash = ((vtable_index << 2) ^ VtableStub::receiver_location()->value()) + vtable_index; + if (caller_is_c1) { + hash = 7 - hash; + } return (is_vtable_stub ? ~hash : hash) & mask; } -inline uint VtableStubs::unsafe_hash(address entry_point) { +inline uint VtableStubs::unsafe_hash(address entry_point, bool caller_is_c1) { // The entrypoint may or may not be a VtableStub. Generate a hash as if it was. address vtable_stub_addr = entry_point - VtableStub::entry_offset(); assert(CodeCache::contains(vtable_stub_addr), "assumed to always be the case"); @@ -261,23 +265,22 @@ inline uint VtableStubs::unsafe_hash(address entry_point) { short vtable_index; static_assert(sizeof(VtableStub::_index) == sizeof(vtable_index), "precondition"); memcpy(&vtable_index, vtable_index_addr, sizeof(vtable_index)); - return hash(is_vtable_stub, vtable_index); + return hash(is_vtable_stub, vtable_index, caller_is_c1); } - -VtableStub* VtableStubs::lookup(bool is_vtable_stub, int vtable_index) { +VtableStub* VtableStubs::lookup(bool is_vtable_stub, int vtable_index, bool caller_is_c1) { assert_lock_strong(VtableStubs_lock); - unsigned hash = VtableStubs::hash(is_vtable_stub, vtable_index); + unsigned hash = VtableStubs::hash(is_vtable_stub, vtable_index, caller_is_c1); VtableStub* s = Atomic::load(&_table[hash]); - while( s && !s->matches(is_vtable_stub, vtable_index)) s = s->next(); + while( s && !s->matches(is_vtable_stub, vtable_index, caller_is_c1)) s = s->next(); return s; } -void VtableStubs::enter(bool is_vtable_stub, int vtable_index, VtableStub* s) { +void VtableStubs::enter(bool is_vtable_stub, int vtable_index, bool caller_is_c1, VtableStub* s) { assert_lock_strong(VtableStubs_lock); - assert(s->matches(is_vtable_stub, vtable_index), "bad vtable stub"); - unsigned int h = VtableStubs::hash(is_vtable_stub, vtable_index); + assert(s->matches(is_vtable_stub, vtable_index, caller_is_c1), "bad vtable stub"); + unsigned int h = VtableStubs::hash(is_vtable_stub, vtable_index, caller_is_c1); // Insert s at the beginning of the corresponding list. s->set_next(Atomic::load(&_table[h])); // Make sure that concurrent readers not taking the mutex observe the writing of "next". @@ -290,7 +293,8 @@ VtableStub* VtableStubs::entry_point(address pc) { // _table will only succeed if there is a VtableStub with an entry point at // the pc. MutexLocker ml(VtableStubs_lock, Mutex::_no_safepoint_check_flag); - uint hash = VtableStubs::unsafe_hash(pc); + VtableStub* stub = (VtableStub*)(pc - VtableStub::entry_offset()); + uint hash = VtableStubs::unsafe_hash(pc, stub->caller_is_c1()); VtableStub* s; for (s = Atomic::load(&_table[hash]); s != nullptr && s->entry_point() != pc; s = s->next()) {} return (s != nullptr && s->entry_point() == pc) ? s : nullptr; diff --git a/src/hotspot/share/code/vtableStubs.hpp b/src/hotspot/share/code/vtableStubs.hpp index 06acd8f25b9..9c425f413af 100644 --- a/src/hotspot/share/code/vtableStubs.hpp +++ b/src/hotspot/share/code/vtableStubs.hpp @@ -88,13 +88,13 @@ class VtableStubs : AllStatic { static int _vtab_stub_size; // current size estimate for vtable stub (quasi-constant) static int _itab_stub_size; // current size estimate for itable stub (quasi-constant) - static VtableStub* create_vtable_stub(int vtable_index); - static VtableStub* create_itable_stub(int vtable_index); - static VtableStub* lookup (bool is_vtable_stub, int vtable_index); - static void enter (bool is_vtable_stub, int vtable_index, VtableStub* s); - static inline uint hash (bool is_vtable_stub, int vtable_index); - static inline uint unsafe_hash (address entry_point); - static address find_stub (bool is_vtable_stub, int vtable_index); + static VtableStub* create_vtable_stub(int vtable_index, bool caller_is_c1); + static VtableStub* create_itable_stub(int vtable_index, bool caller_is_c1); + static VtableStub* lookup (bool is_vtable_stub, int vtable_index, bool caller_is_c1); + static void enter (bool is_vtable_stub, int vtable_index, bool caller_is_c1, VtableStub* s); + static inline uint hash (bool is_vtable_stub, int vtable_index, bool caller_is_c1); + static inline uint unsafe_hash (address entry_point, bool caller_is_c1); + static address find_stub (bool is_vtable_stub, int vtable_index, bool caller_is_c1); static void bookkeeping(MacroAssembler* masm, outputStream* out, VtableStub* s, address npe_addr, address ame_addr, bool is_vtable_stub, int index, int slop_bytes, int index_dependent_slop); @@ -104,8 +104,8 @@ class VtableStubs : AllStatic { int padding); public: - static address find_vtable_stub(int vtable_index) { return find_stub(true, vtable_index); } - static address find_itable_stub(int itable_index) { return find_stub(false, itable_index); } + static address find_vtable_stub(int vtable_index, bool caller_is_c1) { return find_stub(true, vtable_index, caller_is_c1); } + static address find_itable_stub(int itable_index, bool caller_is_c1) { return find_stub(false, itable_index, caller_is_c1); } static VtableStub* entry_point(address pc); // vtable stub entry point for a pc static bool contains(address pc); // is pc within any stub? @@ -134,13 +134,15 @@ class VtableStub { short _ame_offset; // Where an AbstractMethodError might occur short _npe_offset; // Where a NullPointerException might occur Type _type; // Type, either vtable stub or itable stub + bool _caller_is_c1; // True if this is for a caller compiled by C1, + // which doesn't scalarize parameters. /* code follows here */ // The vtableStub code void* operator new(size_t size, int code_size) throw(); - VtableStub(bool is_vtable_stub, short index) + VtableStub(bool is_vtable_stub, short index, bool caller_is_c1) : _next(nullptr), _index(index), _ame_offset(-1), _npe_offset(-1), - _type(is_vtable_stub ? Type::vtable_stub : Type::itable_stub) {} + _type(is_vtable_stub ? Type::vtable_stub : Type::itable_stub), _caller_is_c1(caller_is_c1) {} VtableStub* next() const { return _next; } int index() const { return _index; } static VMReg receiver_location() { return _receiver_location; } @@ -152,8 +154,8 @@ class VtableStub { address entry_point() const { return code_begin(); } static int entry_offset() { return sizeof(class VtableStub); } - bool matches(bool is_vtable_stub, int index) const { - return _index == index && this->is_vtable_stub() == is_vtable_stub; + bool matches(bool is_vtable_stub, int index, bool caller_is_c1) const { + return _index == index && this->is_vtable_stub() == is_vtable_stub && _caller_is_c1 == caller_is_c1; } bool contains(address pc) const { return code_begin() <= pc && pc < code_end(); } @@ -181,6 +183,7 @@ class VtableStub { // Query bool is_itable_stub() const { return _type == Type::itable_stub; } bool is_vtable_stub() const { return _type == Type::vtable_stub; } + bool caller_is_c1() { return _caller_is_c1; } bool is_abstract_method_error(address epc) { return epc == code_begin()+_ame_offset; } bool is_null_pointer_exception(address epc) { return epc == code_begin()+_npe_offset; } diff --git a/src/hotspot/share/compiler/compileBroker.cpp b/src/hotspot/share/compiler/compileBroker.cpp index 5f3bdc5ee3c..3eb6dc9acff 100644 --- a/src/hotspot/share/compiler/compileBroker.cpp +++ b/src/hotspot/share/compiler/compileBroker.cpp @@ -1283,7 +1283,7 @@ void CompileBroker::compile_method_base(const methodHandle& method, // Don't allow blocking compiles if inside a class initializer or while performing class loading vframeStream vfst(JavaThread::cast(thread)); for (; !vfst.at_end(); vfst.next()) { - if (vfst.method()->is_static_initializer() || + if (vfst.method()->is_class_initializer() || (vfst.method()->method_holder()->is_subclass_of(vmClasses::ClassLoader_klass()) && vfst.method()->name() == vmSymbols::loadClass_name())) { blocking = false; diff --git a/src/hotspot/share/compiler/compilerEvent.cpp b/src/hotspot/share/compiler/compilerEvent.cpp index 4bc859ef0d6..323da8056fc 100644 --- a/src/hotspot/share/compiler/compilerEvent.cpp +++ b/src/hotspot/share/compiler/compilerEvent.cpp @@ -56,26 +56,26 @@ class PhaseTypeGuard : public StackObj { Semaphore PhaseTypeGuard::_mutex_semaphore(1); // Table for mapping compiler phases names to int identifiers. -static GrowableArray* phase_names = nullptr; +static GrowableArray* _phase_names = nullptr; class CompilerPhaseTypeConstant : public JfrSerializer { public: void serialize(JfrCheckpointWriter& writer) { PhaseTypeGuard guard; - assert(phase_names != nullptr, "invariant"); - assert(phase_names->is_nonempty(), "invariant"); - const u4 nof_entries = phase_names->length(); + assert(_phase_names != nullptr, "invariant"); + assert(_phase_names->is_nonempty(), "invariant"); + const u4 nof_entries = _phase_names->length(); writer.write_count(nof_entries); for (u4 i = 0; i < nof_entries; i++) { writer.write_key(i); - writer.write(phase_names->at(i)); + writer.write(_phase_names->at(i)); } } }; static int lookup_phase(const char* phase_name) { - for (int i = 0; i < phase_names->length(); i++) { - const char* name = phase_names->at(i); + for (int i = 0; i < _phase_names->length(); i++) { + const char* name = _phase_names->at(i); if (strcmp(name, phase_name) == 0) { return i; } @@ -88,8 +88,8 @@ int CompilerEvent::PhaseEvent::get_phase_id(const char* phase_name, bool may_exi bool register_jfr_serializer = false; { PhaseTypeGuard guard(sync); - if (phase_names == nullptr) { - phase_names = new (mtInternal) GrowableArray(100, mtCompiler); + if (_phase_names == nullptr) { + _phase_names = new (mtInternal) GrowableArray(100, mtCompiler); register_jfr_serializer = true; } else if (may_exist) { index = lookup_phase(phase_name); @@ -100,8 +100,8 @@ int CompilerEvent::PhaseEvent::get_phase_id(const char* phase_name, bool may_exi assert((index = lookup_phase(phase_name)) == -1, "phase name \"%s\" already registered: %d", phase_name, index); } - index = phase_names->length(); - phase_names->append(use_strdup ? os::strdup(phase_name) : phase_name); + index = _phase_names->length(); + _phase_names->append(use_strdup ? os::strdup(phase_name) : phase_name); } if (register_jfr_serializer) { JfrSerializer::register_serializer(TYPE_COMPILERPHASETYPE, false, new CompilerPhaseTypeConstant()); diff --git a/src/hotspot/share/compiler/methodLiveness.cpp b/src/hotspot/share/compiler/methodLiveness.cpp index af21321e05d..004005e5fe7 100644 --- a/src/hotspot/share/compiler/methodLiveness.cpp +++ b/src/hotspot/share/compiler/methodLiveness.cpp @@ -623,15 +623,22 @@ void MethodLiveness::BasicBlock::compute_gen_kill_single(ciBytecodeStream *instr // These bytecodes have no effect on the method's locals. break; - case Bytecodes::_return: - if (instruction->method()->intrinsic_id() == vmIntrinsics::_Object_init) { - // return from Object.init implicitly registers a finalizer - // for the receiver if needed, so keep it alive. + case Bytecodes::_return: { + ciMethod* method = instruction->method(); + ciInstanceKlass* holder = method->holder(); + const bool abstract_klass = holder->is_abstract(); + const bool concrete_value_klass = !abstract_klass && holder->is_inlinetype(); + if (method->intrinsic_id() == vmIntrinsics::_Object_init || + (method->is_object_constructor() && (concrete_value_klass || abstract_klass))) { + // Returning from Object. implicitly registers a finalizer for the receiver if needed, to keep it alive. + // Value class constructors update the scalarized receiver. We need to keep it live so that we can find it after + // (chained) constructor calls and propagate updates to the caller. If the holder of the constructor is abstract, + // we do not know if the constructor was called on a value class or not. We therefore keep the receiver of all + // abstract constructors live. load_one(0); } break; - - + } case Bytecodes::_lload: case Bytecodes::_dload: load_two(instruction->get_index()); diff --git a/src/hotspot/share/compiler/methodMatcher.cpp b/src/hotspot/share/compiler/methodMatcher.cpp index 1f401d2e409..57c77b6516a 100644 --- a/src/hotspot/share/compiler/methodMatcher.cpp +++ b/src/hotspot/share/compiler/methodMatcher.cpp @@ -304,7 +304,7 @@ void MethodMatcher::parse_method_pattern(char*& line, const char*& error_msg, Me (strchr(method_name, JVM_SIGNATURE_ENDSPECIAL) != nullptr)) { if (!vmSymbols::object_initializer_name()->equals(method_name) && !vmSymbols::class_initializer_name()->equals(method_name)) { - error_msg = "Chars '<' and '>' only allowed in and "; + error_msg = "Chars '<' and '>' only allowed in , "; return; } } diff --git a/src/hotspot/share/compiler/oopMap.cpp b/src/hotspot/share/compiler/oopMap.cpp index aeb6ac3828e..73e797685a5 100644 --- a/src/hotspot/share/compiler/oopMap.cpp +++ b/src/hotspot/share/compiler/oopMap.cpp @@ -34,6 +34,7 @@ #include "memory/iterator.hpp" #include "memory/resourceArea.hpp" #include "oops/compressedOops.hpp" +#include "oops/inlineKlass.hpp" #include "runtime/atomic.hpp" #include "runtime/frame.inline.hpp" #include "runtime/handles.inline.hpp" diff --git a/src/hotspot/share/gc/g1/c2/g1BarrierSetC2.cpp b/src/hotspot/share/gc/g1/c2/g1BarrierSetC2.cpp index bca2255479b..1448dd92514 100644 --- a/src/hotspot/share/gc/g1/c2/g1BarrierSetC2.cpp +++ b/src/hotspot/share/gc/g1/c2/g1BarrierSetC2.cpp @@ -212,10 +212,9 @@ Node* G1BarrierSetC2::load_at_resolved(C2Access& access, const Type* val_type) c return CardTableBarrierSetC2::load_at_resolved(access, val_type); } -void G1BarrierSetC2::eliminate_gc_barrier(PhaseMacroExpand* macro, Node* node) const { +void G1BarrierSetC2::eliminate_gc_barrier(PhaseIterGVN* igvn, Node* node) const { eliminate_gc_barrier_data(node); } - void G1BarrierSetC2::eliminate_gc_barrier_data(Node* node) const { if (node->is_LoadStore()) { LoadStoreNode* loadstore = node->as_LoadStore(); diff --git a/src/hotspot/share/gc/g1/c2/g1BarrierSetC2.hpp b/src/hotspot/share/gc/g1/c2/g1BarrierSetC2.hpp index 5f85714d889..378914c096d 100644 --- a/src/hotspot/share/gc/g1/c2/g1BarrierSetC2.hpp +++ b/src/hotspot/share/gc/g1/c2/g1BarrierSetC2.hpp @@ -111,7 +111,7 @@ class G1BarrierSetC2: public CardTableBarrierSetC2 { virtual Node* atomic_xchg_at_resolved(C2AtomicParseAccess& access, Node* new_val, const Type* value_type) const; public: - virtual void eliminate_gc_barrier(PhaseMacroExpand* macro, Node* node) const; + virtual void eliminate_gc_barrier(PhaseIterGVN* igvn, Node* node) const; virtual void eliminate_gc_barrier_data(Node* node) const; virtual bool expand_barriers(Compile* C, PhaseIterGVN& igvn) const; virtual uint estimated_barrier_size(const Node* node) const; diff --git a/src/hotspot/share/gc/g1/g1FullGCMarker.inline.hpp b/src/hotspot/share/gc/g1/g1FullGCMarker.inline.hpp index 6cbfe2674e8..a7fa0a4bdce 100644 --- a/src/hotspot/share/gc/g1/g1FullGCMarker.inline.hpp +++ b/src/hotspot/share/gc/g1/g1FullGCMarker.inline.hpp @@ -107,13 +107,13 @@ void G1FullGCMarker::follow_array_chunk(objArrayOop array, int index) { if (end_index < len) { push_objarray(array, end_index); } - - array->oop_iterate_range(mark_closure(), beg_index, end_index); + assert(array->is_refArray(), "Must be"); + refArrayOop(array)->oop_iterate_range(mark_closure(), beg_index, end_index); } inline void G1FullGCMarker::follow_object(oop obj) { assert(_bitmap->is_marked(obj), "should be marked"); - if (obj->is_objArray()) { + if (obj->is_refArray()) { // Handle object arrays explicitly to allow them to // be split into chunks if needed. follow_array((objArrayOop)obj); diff --git a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp index 388b378fdcd..c869c9cb2e8 100644 --- a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp +++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp @@ -232,7 +232,8 @@ void G1ParScanThreadState::do_partial_array(PartialArrayState* state, bool stole G1HeapRegionAttr dest_attr = _g1h->region_attr(to_array); G1SkipCardEnqueueSetter x(&_scanner, dest_attr.is_new_survivor()); // Process claimed task. - to_array->oop_iterate_range(&_scanner, + assert(to_array->is_refArray(), "Must be"); + refArrayOop(to_array)->oop_iterate_range(&_scanner, checked_cast(claim._start), checked_cast(claim._end)); } @@ -254,7 +255,8 @@ void G1ParScanThreadState::start_partial_objarray(oop from_obj, // Process the initial chunk. No need to process the type in the // klass, as it will already be handled by processing the built-in // module. - to_array->oop_iterate_range(&_scanner, 0, checked_cast(initial_chunk_size)); + assert(to_array->is_refArray(), "Must be"); + refArrayOop(to_array)->oop_iterate_range(&_scanner, 0, checked_cast(initial_chunk_size)); } MAYBE_INLINE_EVACUATION @@ -425,16 +427,16 @@ void G1ParScanThreadState::do_iterate_object(oop const obj, uint age) { // Most objects are not arrays, so do one array check rather than // checking for each array category for each object. - if (klass->is_array_klass()) { + if (klass->is_array_klass() && !klass->is_flatArray_klass()) { assert(!klass->is_stack_chunk_instance_klass(), "must be"); - if (klass->is_objArray_klass()) { + if (klass->is_refArray_klass()) { start_partial_objarray(old, obj); } else { // Nothing needs to be done for typeArrays. Body doesn't contain // any oops to scan, and the type in the klass will already be handled // by processing the built-in module. - assert(klass->is_typeArray_klass(), "invariant"); + assert(klass->is_typeArray_klass() || klass->is_objArray_klass(), "invariant"); } return; } diff --git a/src/hotspot/share/gc/parallel/psCompactionManager.cpp b/src/hotspot/share/gc/parallel/psCompactionManager.cpp index b8ea47eeb09..5c2a2596e94 100644 --- a/src/hotspot/share/gc/parallel/psCompactionManager.cpp +++ b/src/hotspot/share/gc/parallel/psCompactionManager.cpp @@ -36,6 +36,7 @@ #include "memory/iterator.inline.hpp" #include "oops/access.inline.hpp" #include "oops/compressedOops.inline.hpp" +#include "oops/flatArrayKlass.inline.hpp" #include "oops/instanceKlass.inline.hpp" #include "oops/instanceMirrorKlass.inline.hpp" #include "oops/objArrayKlass.inline.hpp" diff --git a/src/hotspot/share/gc/parallel/psCompactionManager.inline.hpp b/src/hotspot/share/gc/parallel/psCompactionManager.inline.hpp index 2c0b8480726..13a29986b66 100644 --- a/src/hotspot/share/gc/parallel/psCompactionManager.inline.hpp +++ b/src/hotspot/share/gc/parallel/psCompactionManager.inline.hpp @@ -133,7 +133,7 @@ inline void ParCompactionManager::follow_contents(const ScannerTask& task, bool } else { oop obj = task.to_oop(); assert(PSParallelCompact::mark_bitmap()->is_marked(obj), "should be marked"); - if (obj->is_objArray()) { + if (obj->is_refArray()) { push_objArray(obj); } else { obj->oop_iterate(&_mark_and_push_closure); diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp index 5a0dbe3d4e6..6c6f0174513 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp @@ -75,6 +75,7 @@ #include "memory/universe.hpp" #include "nmt/memTracker.hpp" #include "oops/access.inline.hpp" +#include "oops/flatArrayKlass.inline.hpp" #include "oops/instanceClassLoaderKlass.inline.hpp" #include "oops/instanceKlass.inline.hpp" #include "oops/instanceMirrorKlass.inline.hpp" @@ -2365,7 +2366,7 @@ void MoveAndUpdateClosure::do_addr(HeapWord* addr, size_t words) { assert(FullGCForwarding::is_forwarded(cast_to_oop(source())), "inv"); assert(FullGCForwarding::forwardee(cast_to_oop(source())) == cast_to_oop(destination()), "inv"); Copy::aligned_conjoint_words(source(), copy_destination(), words); - cast_to_oop(copy_destination())->init_mark(); + cast_to_oop(copy_destination())->reinit_mark(); } update_state(words); diff --git a/src/hotspot/share/gc/parallel/psPromotionManager.cpp b/src/hotspot/share/gc/parallel/psPromotionManager.cpp index 0a463ab7516..fc2975df481 100644 --- a/src/hotspot/share/gc/parallel/psPromotionManager.cpp +++ b/src/hotspot/share/gc/parallel/psPromotionManager.cpp @@ -43,6 +43,7 @@ #include "memory/resourceArea.hpp" #include "oops/access.inline.hpp" #include "oops/compressedOops.inline.hpp" +#include "oops/flatArrayKlass.inline.hpp" #include "utilities/checkedCast.hpp" PaddedEnd* PSPromotionManager::_manager_array = nullptr; diff --git a/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp b/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp index 6154abf1b1c..4ec51932eb5 100644 --- a/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp +++ b/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp @@ -303,7 +303,7 @@ inline oop PSPromotionManager::copy_unmarked_to_survivor_space(oop o, // _min_array_size_for_chunking, and most of them will be arrays. // So, the objArray test would be very infrequent. if (new_obj_size > _min_array_size_for_chunking && - klass->is_objArray_klass() && + klass->is_refArray_klass() && PSChunkLargeArrays) { push_objArray(o, new_obj); } else { diff --git a/src/hotspot/share/gc/serial/serialFullGC.cpp b/src/hotspot/share/gc/serial/serialFullGC.cpp index fc63b81b7ce..cca913201f5 100644 --- a/src/hotspot/share/gc/serial/serialFullGC.cpp +++ b/src/hotspot/share/gc/serial/serialFullGC.cpp @@ -382,6 +382,7 @@ template void SerialFullGC::KeepAliveClosure::do_oop_work(T* p) { } void SerialFullGC::push_objarray(oop obj, size_t index) { + assert(obj->is_refArray(), "Must be"); ObjArrayTask task(obj, index); assert(task.is_valid(), "bad ObjArrayTask"); _objarray_stack.push(task); @@ -397,7 +398,7 @@ void SerialFullGC::follow_array(objArrayOop array) { void SerialFullGC::follow_object(oop obj) { assert(obj->is_gc_marked(), "should be marked"); - if (obj->is_objArray()) { + if (obj->is_refArray()) { // Handle object arrays explicitly to allow them to // be split into chunks if needed. SerialFullGC::follow_array((objArrayOop)obj); @@ -414,7 +415,7 @@ void SerialFullGC::follow_array_chunk(objArrayOop array, int index) { const int stride = MIN2(len - beg_index, (int) ObjArrayMarkingStride); const int end_index = beg_index + stride; - array->oop_iterate_range(&mark_and_push_closure, beg_index, end_index); + refArrayOop(array)->oop_iterate_range(&mark_and_push_closure, beg_index, end_index); if (end_index < len) { SerialFullGC::push_objarray(array, end_index); // Push the continuation. diff --git a/src/hotspot/share/gc/shared/barrierSet.cpp b/src/hotspot/share/gc/shared/barrierSet.cpp index a30b23ce2d9..7861be8923a 100644 --- a/src/hotspot/share/gc/shared/barrierSet.cpp +++ b/src/hotspot/share/gc/shared/barrierSet.cpp @@ -22,10 +22,13 @@ * */ +#include "classfile/vmSymbols.hpp" #include "gc/shared/barrierSet.hpp" #include "gc/shared/barrierSetAssembler.hpp" #include "gc/shared/barrierSetNMethod.hpp" #include "gc/shared/barrierSetStackChunk.hpp" +#include "memory/resourceArea.hpp" +#include "oops/objArrayKlass.inline.hpp" #include "runtime/continuation.hpp" #include "runtime/javaThread.hpp" #include "utilities/debug.hpp" @@ -51,6 +54,33 @@ void BarrierSet::set_barrier_set(BarrierSet* barrier_set) { _barrier_set->on_thread_create(Thread::current()); } +void BarrierSet::throw_array_null_pointer_store_exception(arrayOop src, arrayOop dst, TRAPS) { + ResourceMark rm(THREAD); + Klass* bound = ObjArrayKlass::cast(dst->klass())->element_klass(); + stringStream ss; + ss.print("arraycopy: can not copy null values into %s[]", + bound->external_name()); + THROW_MSG(vmSymbols::java_lang_NullPointerException(), ss.as_string()); +} + +void BarrierSet::throw_array_store_exception(arrayOop src, arrayOop dst, TRAPS) { + ResourceMark rm(THREAD); + Klass* bound = ObjArrayKlass::cast(dst->klass())->element_klass(); + Klass* stype = ObjArrayKlass::cast(src->klass())->element_klass(); + stringStream ss; + if (!bound->is_subtype_of(stype)) { + ss.print("arraycopy: type mismatch: can not copy %s[] into %s[]", + stype->external_name(), bound->external_name()); + } else { + // oop_arraycopy should return the index in the source array that + // contains the problematic oop. + ss.print("arraycopy: element type mismatch: can not cast one of the elements" + " of %s[] to the type of the destination array, %s", + stype->external_name(), bound->external_name()); + } + THROW_MSG(vmSymbols::java_lang_ArrayStoreException(), ss.as_string()); +} + static BarrierSetNMethod* select_barrier_set_nmethod(BarrierSetNMethod* barrier_set_nmethod) { if (barrier_set_nmethod != nullptr) { // The GC needs nmethod entry barriers to do concurrent GC diff --git a/src/hotspot/share/gc/shared/barrierSet.hpp b/src/hotspot/share/gc/shared/barrierSet.hpp index 2040ec32ea0..bcea1b427a3 100644 --- a/src/hotspot/share/gc/shared/barrierSet.hpp +++ b/src/hotspot/share/gc/shared/barrierSet.hpp @@ -30,6 +30,7 @@ #include "oops/access.hpp" #include "oops/accessBackend.hpp" #include "oops/oopsHierarchy.hpp" +#include "utilities/exceptions.hpp" #include "utilities/fakeRttiSupport.hpp" #include "utilities/macros.hpp" @@ -119,6 +120,9 @@ class BarrierSet: public CHeapObj { return COMPILER2_PRESENT(new BarrierSetC2T()) NOT_COMPILER2(nullptr); } + static void throw_array_null_pointer_store_exception(arrayOop src, arrayOop dst, TRAPS); + static void throw_array_store_exception(arrayOop src, arrayOop dst, TRAPS); + public: // Support for optimizing compilers to call the barrier set on slow path allocations // that did not enter a TLAB. Used for e.g. ReduceInitialCardMarks. @@ -281,7 +285,7 @@ class BarrierSet: public CHeapObj { } template - static bool oop_arraycopy_in_heap(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, + static void oop_arraycopy_in_heap(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, T* dst_raw, size_t length); @@ -312,6 +316,11 @@ class BarrierSet: public CHeapObj { static void clone_in_heap(oop src, oop dst, size_t size) { Raw::clone(src, dst, size); } + + static void value_copy_in_heap(void* src, void* dst, InlineKlass* md, LayoutKind lk) { + Raw::value_copy(src, dst, md, lk); + } + }; }; diff --git a/src/hotspot/share/gc/shared/barrierSet.inline.hpp b/src/hotspot/share/gc/shared/barrierSet.inline.hpp index 8b314bb5995..f89e10f899e 100644 --- a/src/hotspot/share/gc/shared/barrierSet.inline.hpp +++ b/src/hotspot/share/gc/shared/barrierSet.inline.hpp @@ -31,31 +31,39 @@ #include "oops/compressedOops.inline.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.hpp" +#include "runtime/javaThread.hpp" +#include "runtime/thread.hpp" template template -inline bool BarrierSet::AccessBarrier::oop_arraycopy_in_heap(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, +inline void BarrierSet::AccessBarrier::oop_arraycopy_in_heap(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, T* dst_raw, size_t length) { T* src = arrayOopDesc::obj_offset_to_raw(src_obj, src_offset_in_bytes, src_raw); T* dst = arrayOopDesc::obj_offset_to_raw(dst_obj, dst_offset_in_bytes, dst_raw); - if (!HasDecorator::value) { + if ((!HasDecorator::value) && + (!HasDecorator::value)) { // Covariant, copy without checks - return Raw::oop_arraycopy(nullptr, 0, src, nullptr, 0, dst, length); + Raw::oop_arraycopy(nullptr, 0, src, nullptr, 0, dst, length); + return; } // Copy each element with checking casts Klass* const dst_klass = objArrayOop(dst_obj)->element_klass(); for (const T* const end = src + length; src < end; src++, dst++) { const T elem = *src; - if (!oopDesc::is_instanceof_or_null(CompressedOops::decode(elem), dst_klass)) { - return false; + if (HasDecorator::value && CompressedOops::is_null(elem)) { + throw_array_null_pointer_store_exception(src_obj, dst_obj, JavaThread::current()); + return; + } + if (HasDecorator::value && + (!oopDesc::is_instanceof_or_null(CompressedOops::decode(elem), dst_klass))) { + throw_array_store_exception(src_obj, dst_obj, JavaThread::current()); + return; } *dst = elem; } - - return true; } #endif // SHARE_GC_SHARED_BARRIERSET_INLINE_HPP diff --git a/src/hotspot/share/gc/shared/barrierSetRuntime.cpp b/src/hotspot/share/gc/shared/barrierSetRuntime.cpp new file mode 100644 index 00000000000..77948706708 --- /dev/null +++ b/src/hotspot/share/gc/shared/barrierSetRuntime.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "gc/shared/barrierSetRuntime.hpp" +#include "oops/access.inline.hpp" +#include "runtime/interfaceSupport.inline.hpp" +#include "utilities/macros.hpp" + +JRT_LEAF(void, BarrierSetRuntime::value_copy(void* src, void* dst, InlineLayoutInfo* li)) + HeapAccess<>::value_copy(src, dst, li->klass(), li->kind()); +JRT_END + +JRT_LEAF(void, BarrierSetRuntime::value_copy_is_dest_uninitialized(void* src, void* dst, InlineLayoutInfo* li)) + HeapAccess::value_copy(src, dst, li->klass(), li->kind()); +JRT_END diff --git a/src/hotspot/share/gc/shared/barrierSetRuntime.hpp b/src/hotspot/share/gc/shared/barrierSetRuntime.hpp new file mode 100644 index 00000000000..7870800a4ba --- /dev/null +++ b/src/hotspot/share/gc/shared/barrierSetRuntime.hpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_GC_SHARED_BARRIERSETRUNTIME_HPP +#define SHARE_GC_SHARED_BARRIERSETRUNTIME_HPP + +#include "memory/allocation.hpp" +#include "oops/inlineKlass.hpp" +#include "oops/oopsHierarchy.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/macros.hpp" + +class oopDesc; +class JavaThread; + +class BarrierSetRuntime: public AllStatic { +public: + // Template interpreter... + static void value_copy(void* src, void* dst, InlineLayoutInfo* layout_info); + static void value_copy_is_dest_uninitialized(void* src, void* dst, InlineLayoutInfo* layout_info); +}; + +#endif // SHARE_GC_SHARED_BARRIERSETRUNTIME_HPP diff --git a/src/hotspot/share/gc/shared/c1/barrierSetC1.hpp b/src/hotspot/share/gc/shared/c1/barrierSetC1.hpp index e618a81b26b..042a3ffb35a 100644 --- a/src/hotspot/share/gc/shared/c1/barrierSetC1.hpp +++ b/src/hotspot/share/gc/shared/c1/barrierSetC1.hpp @@ -70,11 +70,12 @@ class LIRAccess: public StackObj { LIR_Opr _resolved_addr; CodeEmitInfo* _patch_emit_info; CodeEmitInfo* _access_emit_info; + ciInlineKlass* _vk; // For flat, atomic accesses that might require GC barriers on oop fields public: LIRAccess(LIRGenerator* gen, DecoratorSet decorators, LIRAddressOpr base, LIRAddressOpr offset, BasicType type, - CodeEmitInfo* patch_emit_info = nullptr, CodeEmitInfo* access_emit_info = nullptr) : + CodeEmitInfo* patch_emit_info = nullptr, CodeEmitInfo* access_emit_info = nullptr, ciInlineKlass* vk = nullptr) : _gen(gen), _decorators(AccessInternal::decorator_fixup(decorators, type)), _base(base), @@ -82,7 +83,8 @@ class LIRAccess: public StackObj { _type(type), _resolved_addr(), _patch_emit_info(patch_emit_info), - _access_emit_info(access_emit_info) {} + _access_emit_info(access_emit_info), + _vk(vk) {} void load_base() { _base.item().load_item(); } void load_offset() { _offset.item().load_nonconstant(); } @@ -104,6 +106,7 @@ class LIRAccess: public StackObj { DecoratorSet decorators() const { return _decorators; } void clear_decorators(DecoratorSet ds) { _decorators &= ~ds; } bool is_raw() const { return (_decorators & AS_RAW) != 0; } + ciInlineKlass* vk() const { return _vk; } }; // The BarrierSetC1 class is the main entry point for the GC backend of the Access API in C1. diff --git a/src/hotspot/share/gc/shared/c1/modRefBarrierSetC1.cpp b/src/hotspot/share/gc/shared/c1/modRefBarrierSetC1.cpp index d7d463d252e..ebf8d27f164 100644 --- a/src/hotspot/share/gc/shared/c1/modRefBarrierSetC1.cpp +++ b/src/hotspot/share/gc/shared/c1/modRefBarrierSetC1.cpp @@ -22,6 +22,7 @@ * */ +#include "ci/ciInlineKlass.hpp" #include "gc/shared/c1/modRefBarrierSetC1.hpp" #include "utilities/macros.hpp" @@ -36,6 +37,21 @@ void ModRefBarrierSetC1::store_at_resolved(LIRAccess& access, LIR_Opr value) { bool is_array = (decorators & IS_ARRAY) != 0; bool on_anonymous = (decorators & ON_UNKNOWN_OOP_REF) != 0; + // Is this a flat, atomic access that might require gc barriers on oop fields? + ciInlineKlass* vk = access.vk(); + if (vk != nullptr && vk->has_object_fields()) { + // Add pre-barriers for oop fields + for (int i = 0; i < vk->nof_nonstatic_fields(); i++) { + ciField* field = vk->nonstatic_field_at(i); + if (!field->type()->is_primitive_type()) { + int off = access.offset().opr().as_jint() + field->offset_in_bytes() - vk->payload_offset(); + LIRAccess inner_access(access.gen(), decorators, access.base(), LIR_OprFact::intConst(off), field->type()->basic_type(), access.patch_emit_info(), access.access_emit_info()); + pre_barrier(inner_access, resolve_address(inner_access, false), + LIR_OprFact::illegalOpr /* pre_val */, inner_access.patch_emit_info()); + } + } + } + if (access.is_oop()) { pre_barrier(access, access.resolved_addr(), LIR_OprFact::illegalOpr /* pre_val */, access.patch_emit_info()); @@ -48,6 +64,31 @@ void ModRefBarrierSetC1::store_at_resolved(LIRAccess& access, LIR_Opr value) { LIR_Opr post_addr = precise ? access.resolved_addr() : access.base().opr(); post_barrier(access, post_addr, value); } + + if (vk != nullptr && vk->has_object_fields()) { + // Add post-barriers for oop fields + for (int i = 0; i < vk->nof_nonstatic_fields(); i++) { + ciField* field = vk->nonstatic_field_at(i); + if (!field->type()->is_primitive_type()) { + int inner_off = field->offset_in_bytes() - vk->payload_offset(); + int off = access.offset().opr().as_jint() + inner_off; + LIRAccess inner_access(access.gen(), decorators, access.base(), LIR_OprFact::intConst(off), field->type()->basic_type(), access.patch_emit_info(), access.access_emit_info()); + + // Shift long value to extract the narrow oop field value and zero-extend + LIR_Opr field_val = access.gen()->new_register(T_LONG); + access.gen()->lir()->unsigned_shift_right(value, + LIR_OprFact::intConst(inner_off << LogBitsPerByte), + field_val, LIR_Opr::illegalOpr()); + LIR_Opr mask = access.gen()->load_immediate((julong) max_juint, T_LONG); + access.gen()->lir()->logical_and(field_val, mask, field_val); + LIR_Opr oop_val = access.gen()->new_register(T_OBJECT); + access.gen()->lir()->move(field_val, oop_val); + + assert(!is_array && !on_anonymous, "not suppported"); + post_barrier(inner_access, access.base().opr(), oop_val); + } + } + } } LIR_Opr ModRefBarrierSetC1::atomic_cmpxchg_at_resolved(LIRAccess& access, LIRItem& cmp_value, LIRItem& new_value) { diff --git a/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp b/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp index f0ed9687f11..6966d7c93e5 100644 --- a/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp +++ b/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp @@ -48,6 +48,10 @@ void* C2ParseAccess::barrier_set_state() const { PhaseGVN& C2ParseAccess::gvn() const { return _kit->gvn(); } +Node* C2ParseAccess::control() const { + return _ctl == nullptr ? _kit->control() : _ctl; +} + bool C2Access::needs_cpu_membar() const { bool mismatched = (_decorators & C2_MISMATCHED) != 0; bool is_unordered = (_decorators & MO_UNORDERED) != 0; @@ -201,7 +205,7 @@ Node* BarrierSetC2::load_at_resolved(C2Access& access, const Type* val_type) con if (access.is_parse_access()) { C2ParseAccess& parse_access = static_cast(access); GraphKit* kit = parse_access.kit(); - Node* control = control_dependent ? kit->control() : nullptr; + Node* control = control_dependent ? parse_access.control() : nullptr; if (immutable) { Compile* C = Compile::current(); @@ -862,7 +866,7 @@ void BarrierSetC2::clone_in_runtime(PhaseMacroExpand* phase, ArrayCopyNode* ac, TypeRawPtr::BOTTOM, src, dst, full_size_in_heap_words XTOP); phase->transform_later(call); - phase->igvn().replace_node(ac, call); + phase->replace_node(ac, call); } void BarrierSetC2::clone_at_expansion(PhaseMacroExpand* phase, ArrayCopyNode* ac) const { @@ -886,7 +890,7 @@ void BarrierSetC2::clone_at_expansion(PhaseMacroExpand* phase, ArrayCopyNode* ac Node* call = phase->make_leaf_call(ctrl, mem, call_type, copyfunc_addr, copyfunc_name, raw_adr_type, payload_src, payload_dst, length XTOP); phase->transform_later(call); - phase->igvn().replace_node(ac, call); + phase->replace_node(ac, call); } #undef XTOP diff --git a/src/hotspot/share/gc/shared/c2/barrierSetC2.hpp b/src/hotspot/share/gc/shared/c2/barrierSetC2.hpp index 7b9cb985cff..fe0ebcbf3e9 100644 --- a/src/hotspot/share/gc/shared/c2/barrierSetC2.hpp +++ b/src/hotspot/share/gc/shared/c2/barrierSetC2.hpp @@ -147,18 +147,25 @@ class C2Access: public StackObj { class C2ParseAccess: public C2Access { protected: GraphKit* _kit; + Node* _ctl; + const InlineTypeNode* _vt; // For flat, atomic accesses that might require GC barriers on oop fields void* barrier_set_state() const; public: C2ParseAccess(GraphKit* kit, DecoratorSet decorators, - BasicType type, Node* base, C2AccessValuePtr& addr) : + BasicType type, Node* base, C2AccessValuePtr& addr, + Node* ctl = nullptr, const InlineTypeNode* vt = nullptr) : C2Access(decorators, type, base, addr), - _kit(kit) { + _kit(kit), + _ctl(ctl), + _vt (vt) { fixup_decorators(); } GraphKit* kit() const { return _kit; } + Node* control() const; + const InlineTypeNode* vt() const { return _vt; } virtual PhaseGVN& gvn() const; virtual bool is_parse_access() const { return true; } @@ -297,7 +304,7 @@ class BarrierSetC2: public CHeapObj { virtual Node* atomic_xchg_at(C2AtomicParseAccess& access, Node* new_val, const Type* value_type) const; virtual Node* atomic_add_at(C2AtomicParseAccess& access, Node* new_val, const Type* value_type) const; - virtual void clone(GraphKit* kit, Node* src, Node* dst, Node* size, bool is_array) const; + virtual void clone(GraphKit* kit, Node* src_base, Node* dst_base, Node* size, bool is_array) const; virtual Node* obj_allocate(PhaseMacroExpand* macro, Node* mem, Node* toobig_false, Node* size_in_bytes, Node*& i_o, Node*& needgc_ctrl, @@ -325,7 +332,7 @@ class BarrierSetC2: public CHeapObj { // Support for macro expanded GC barriers virtual void register_potential_barrier_node(Node* node) const { } virtual void unregister_potential_barrier_node(Node* node) const { } - virtual void eliminate_gc_barrier(PhaseMacroExpand* macro, Node* node) const { } + virtual void eliminate_gc_barrier(PhaseIterGVN* igvn, Node* node) const { } virtual void eliminate_gc_barrier_data(Node* node) const { } virtual void enqueue_useful_gc_barrier(PhaseIterGVN* igvn, Node* node) const {} virtual void eliminate_useless_gc_barriers(Unique_Node_List &useful, Compile* C) const {} diff --git a/src/hotspot/share/gc/shared/c2/cardTableBarrierSetC2.cpp b/src/hotspot/share/gc/shared/c2/cardTableBarrierSetC2.cpp index 536cd6da1ef..b60873cf948 100644 --- a/src/hotspot/share/gc/shared/c2/cardTableBarrierSetC2.cpp +++ b/src/hotspot/share/gc/shared/c2/cardTableBarrierSetC2.cpp @@ -124,25 +124,29 @@ bool CardTableBarrierSetC2::use_ReduceInitialCardMarks() { return ReduceInitialCardMarks; } -void CardTableBarrierSetC2::eliminate_gc_barrier(PhaseMacroExpand* macro, Node* node) const { +void CardTableBarrierSetC2::eliminate_gc_barrier(PhaseIterGVN* igvn, Node* node) const { assert(node->Opcode() == Op_CastP2X, "ConvP2XNode required"); - Node *shift = node->unique_out(); - Node *addp = shift->unique_out(); - for (DUIterator_Last jmin, j = addp->last_outs(jmin); j >= jmin; --j) { - Node *mem = addp->last_out(j); - if (UseCondCardMark && mem->is_Load()) { - assert(mem->Opcode() == Op_LoadB, "unexpected code shape"); - // The load is checking if the card has been written so - // replace it with zero to fold the test. - macro->replace_node(mem, macro->intcon(0)); - continue; + for (DUIterator_Last imin, i = node->last_outs(imin); i >= imin; --i) { + Node* shift = node->last_out(i); + for (DUIterator_Last jmin, j = shift->last_outs(jmin); j >= jmin; --j) { + Node* addp = shift->last_out(j); + for (DUIterator_Last kmin, k = addp->last_outs(kmin); k >= kmin; --k) { + Node* mem = addp->last_out(k); + if (UseCondCardMark && mem->is_Load()) { + assert(mem->Opcode() == Op_LoadB, "unexpected code shape"); + // The load is checking if the card has been written so + // replace it with zero to fold the test. + igvn->replace_node(mem, igvn->intcon(0)); + continue; + } + assert(mem->is_Store(), "store required"); + igvn->replace_node(mem, mem->in(MemNode::Memory)); + } } - assert(mem->is_Store(), "store required"); - macro->replace_node(mem, mem->in(MemNode::Memory)); } } bool CardTableBarrierSetC2::array_copy_requires_gc_barriers(bool tightly_coupled_alloc, BasicType type, bool is_clone, bool is_clone_instance, ArrayCopyPhase phase) const { - bool is_oop = is_reference_type(type); + bool is_oop = type == T_OBJECT || type == T_ARRAY; return is_oop && (!tightly_coupled_alloc || !use_ReduceInitialCardMarks()); } diff --git a/src/hotspot/share/gc/shared/c2/cardTableBarrierSetC2.hpp b/src/hotspot/share/gc/shared/c2/cardTableBarrierSetC2.hpp index 1263030a8b5..15577b209fa 100644 --- a/src/hotspot/share/gc/shared/c2/cardTableBarrierSetC2.hpp +++ b/src/hotspot/share/gc/shared/c2/cardTableBarrierSetC2.hpp @@ -38,7 +38,7 @@ class CardTableBarrierSetC2: public ModRefBarrierSetC2 { Node* byte_map_base_node(GraphKit* kit) const; public: - virtual void eliminate_gc_barrier(PhaseMacroExpand* macro, Node* node) const; + virtual void eliminate_gc_barrier(PhaseIterGVN* igvn, Node* node) const; virtual bool array_copy_requires_gc_barriers(bool tightly_coupled_alloc, BasicType type, bool is_clone, bool is_clone_instance, ArrayCopyPhase phase) const; static bool use_ReduceInitialCardMarks(); diff --git a/src/hotspot/share/gc/shared/c2/modRefBarrierSetC2.cpp b/src/hotspot/share/gc/shared/c2/modRefBarrierSetC2.cpp index ddc9caedd71..116bd5824f6 100644 --- a/src/hotspot/share/gc/shared/c2/modRefBarrierSetC2.cpp +++ b/src/hotspot/share/gc/shared/c2/modRefBarrierSetC2.cpp @@ -38,7 +38,12 @@ Node* ModRefBarrierSetC2::store_at_resolved(C2Access& access, C2AccessValue& val bool use_precise = is_array || anonymous; bool tightly_coupled_alloc = (decorators & C2_TIGHTLY_COUPLED_ALLOC) != 0; - if (!access.is_oop() || tightly_coupled_alloc || (!in_heap && !anonymous)) { + const InlineTypeNode* vt = nullptr; + if (access.is_parse_access() && static_cast(access).vt() != nullptr) { + vt = static_cast(access).vt(); + } + + if (vt == nullptr && (!access.is_oop() || tightly_coupled_alloc || (!in_heap && !anonymous))) { return BarrierSetC2::store_at_resolved(access, val); } @@ -46,7 +51,25 @@ Node* ModRefBarrierSetC2::store_at_resolved(C2Access& access, C2AccessValue& val C2ParseAccess& parse_access = static_cast(access); Node* store = BarrierSetC2::store_at_resolved(access, val); - post_barrier(parse_access.kit(), access.base(), adr, val.node(), use_precise); + + // TODO 8350865 + // - We actually only need the post barrier once for non-arrays (same for C1, right)? + // - Value is only needed to determine if we are storing null. Maybe we can go with a simple boolean? + GraphKit* kit = parse_access.kit(); + if (vt != nullptr) { + for (uint i = 0; i < vt->field_count(); ++i) { + ciType* type = vt->field_type(i); + if (!type->is_primitive_type()) { + ciInlineKlass* vk = vt->bottom_type()->inline_klass(); + int field_offset = vt->field_offset(i) - vk->payload_offset(); + Node* value = vt->field_value(i); + Node* field_adr = kit->basic_plus_adr(access.base(), adr, field_offset); + post_barrier(kit, access.base(), field_adr, value, use_precise); + } + } + } else { + post_barrier(kit, access.base(), adr, val.node(), use_precise); + } return store; } diff --git a/src/hotspot/share/gc/shared/collectedHeap.hpp b/src/hotspot/share/gc/shared/collectedHeap.hpp index 33d2fad8bba..76d8bc1f5a3 100644 --- a/src/hotspot/share/gc/shared/collectedHeap.hpp +++ b/src/hotspot/share/gc/shared/collectedHeap.hpp @@ -290,6 +290,7 @@ class CollectedHeap : public CHeapObj { GCCause::Cause gc_cause() { return _gc_cause; } oop obj_allocate(Klass* klass, size_t size, TRAPS); + oop obj_buffer_allocate(Klass* klass, size_t size, TRAPS); // doesn't clear memory virtual oop array_allocate(Klass* klass, size_t size, int length, bool do_zero, TRAPS); oop class_allocate(Klass* klass, size_t size, TRAPS); diff --git a/src/hotspot/share/gc/shared/collectedHeap.inline.hpp b/src/hotspot/share/gc/shared/collectedHeap.inline.hpp index 3900d1809ee..2410e1fe50e 100644 --- a/src/hotspot/share/gc/shared/collectedHeap.inline.hpp +++ b/src/hotspot/share/gc/shared/collectedHeap.inline.hpp @@ -36,7 +36,13 @@ inline oop CollectedHeap::obj_allocate(Klass* klass, size_t size, TRAPS) { return allocator.allocate(); } +inline oop CollectedHeap::obj_buffer_allocate(Klass* klass, size_t size, TRAPS) { + ObjBufferAllocator allocator(klass, size, THREAD); + return allocator.allocate(); +} + inline oop CollectedHeap::array_allocate(Klass* klass, size_t size, int length, bool do_zero, TRAPS) { + assert(!klass->is_objArray_klass() || klass->is_refArray_klass() || klass->is_flatArray_klass(), "ObjArrayKlass must never be used to allocate array instances directly"); ObjArrayAllocator allocator(klass, size, length, do_zero, THREAD); return allocator.allocate(); } diff --git a/src/hotspot/share/gc/shared/memAllocator.cpp b/src/hotspot/share/gc/shared/memAllocator.cpp index 741b0ffb020..4b0f34fe7ad 100644 --- a/src/hotspot/share/gc/shared/memAllocator.cpp +++ b/src/hotspot/share/gc/shared/memAllocator.cpp @@ -385,7 +385,11 @@ oop MemAllocator::finish(HeapWord* mem) const { if (UseCompactObjectHeaders) { oopDesc::release_set_mark(mem, _klass->prototype_header()); } else { - oopDesc::set_mark(mem, markWord::prototype()); + if (EnableValhalla) { + oopDesc::set_mark(mem, _klass->prototype_header()); + } else { + oopDesc::set_mark(mem, markWord::prototype()); + } oopDesc::release_set_klass(mem, _klass); } return cast_to_oop(mem); @@ -396,6 +400,12 @@ oop ObjAllocator::initialize(HeapWord* mem) const { return finish(mem); } +oop ObjBufferAllocator::initialize(HeapWord* mem) const { + mem_clear(mem); + return finish(mem); +} + + oop ObjArrayAllocator::initialize(HeapWord* mem) const { // Set array length before setting the _klass field because a // non-null klass field indicates that the object is parsable by diff --git a/src/hotspot/share/gc/shared/memAllocator.hpp b/src/hotspot/share/gc/shared/memAllocator.hpp index ec67616adba..2076d59f5c2 100644 --- a/src/hotspot/share/gc/shared/memAllocator.hpp +++ b/src/hotspot/share/gc/shared/memAllocator.hpp @@ -32,7 +32,7 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" -// These fascilities are used for allocating, and initializing newly allocated objects. +// These facilities are used for allocating, and initializing newly allocated objects. class MemAllocator: StackObj { protected: @@ -89,6 +89,14 @@ class ObjAllocator: public MemAllocator { virtual oop initialize(HeapWord* mem) const; }; +class ObjBufferAllocator: public MemAllocator { +public: + ObjBufferAllocator(Klass* klass, size_t word_size, Thread* thread = Thread::current()) + : MemAllocator(klass, word_size, thread) {} + virtual oop initialize(HeapWord* mem) const; +}; + + class ObjArrayAllocator: public MemAllocator { protected: const int _length; diff --git a/src/hotspot/share/gc/shared/modRefBarrierSet.hpp b/src/hotspot/share/gc/shared/modRefBarrierSet.hpp index 15ac7971118..fee652c8899 100644 --- a/src/hotspot/share/gc/shared/modRefBarrierSet.hpp +++ b/src/hotspot/share/gc/shared/modRefBarrierSet.hpp @@ -82,10 +82,14 @@ class ModRefBarrierSet: public BarrierSet { static oop oop_atomic_xchg_in_heap(T* addr, oop new_value); template - static bool oop_arraycopy_in_heap(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, + static void oop_arraycopy_in_heap(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, T* dst_raw, size_t length); - + private: + // Failing checkcast or check null during copy, still needs barrier + template + static inline void oop_arraycopy_partial_barrier(BarrierSetT *bs, T* dst_raw, T* p); + public: static void clone_in_heap(oop src, oop dst, size_t size); static void oop_store_in_heap_at(oop base, ptrdiff_t offset, oop value) { @@ -99,6 +103,8 @@ class ModRefBarrierSet: public BarrierSet { static oop oop_atomic_cmpxchg_in_heap_at(oop base, ptrdiff_t offset, oop compare_value, oop new_value) { return oop_atomic_cmpxchg_in_heap(AccessInternal::oop_field_addr(base, offset), compare_value, new_value); } + + static void value_copy_in_heap(void* src, void* dst, InlineKlass* md, LayoutKind lk); }; }; diff --git a/src/hotspot/share/gc/shared/modRefBarrierSet.inline.hpp b/src/hotspot/share/gc/shared/modRefBarrierSet.inline.hpp index f5ad4f2c756..09a52379c08 100644 --- a/src/hotspot/share/gc/shared/modRefBarrierSet.inline.hpp +++ b/src/hotspot/share/gc/shared/modRefBarrierSet.inline.hpp @@ -29,6 +29,7 @@ #include "gc/shared/barrierSet.hpp" #include "oops/compressedOops.inline.hpp" +#include "oops/inlineKlass.inline.hpp" #include "oops/objArrayOop.hpp" #include "oops/oop.hpp" #include "runtime/thread.hpp" @@ -93,7 +94,18 @@ oop_atomic_xchg_in_heap(T* addr, oop new_value) { template template -inline bool ModRefBarrierSet::AccessBarrier:: +inline void ModRefBarrierSet::AccessBarrier:: +oop_arraycopy_partial_barrier(BarrierSetT *bs, T* dst_raw, T* p) { + const size_t pd = pointer_delta(p, dst_raw, (size_t)heapOopSize); + // pointer delta is scaled to number of elements (length field in + // objArrayOop) which we assume is 32 bit. + assert(pd == (size_t)(int)pd, "length field overflow"); + bs->write_ref_array((HeapWord*)dst_raw, pd); +} + +template +template +inline void ModRefBarrierSet::AccessBarrier:: oop_arraycopy_in_heap(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, T* dst_raw, size_t length) { @@ -102,7 +114,8 @@ oop_arraycopy_in_heap(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, src_raw = arrayOopDesc::obj_offset_to_raw(src_obj, src_offset_in_bytes, src_raw); dst_raw = arrayOopDesc::obj_offset_to_raw(dst_obj, dst_offset_in_bytes, dst_raw); - if (!HasDecorator::value) { + if ((!HasDecorator::value) && + (!HasDecorator::value)) { // Optimized covariant case bs->write_ref_array_pre(dst_raw, length, HasDecorator::value); @@ -115,22 +128,24 @@ oop_arraycopy_in_heap(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, T* end = from + length; for (T* p = dst_raw; from < end; from++, p++) { T element = *from; - if (oopDesc::is_instanceof_or_null(CompressedOops::decode(element), bound)) { - bs->template write_ref_field_pre(p); - *p = element; - } else { - // We must do a barrier to cover the partial copy. - const size_t pd = pointer_delta(p, dst_raw, (size_t)heapOopSize); - // pointer delta is scaled to number of elements (length field in - // objArrayOop) which we assume is 32 bit. - assert(pd == (size_t)(int)pd, "length field overflow"); - bs->write_ref_array((HeapWord*)dst_raw, pd); - return false; + // Apply any required checks + if (HasDecorator::value && CompressedOops::is_null(element)) { + oop_arraycopy_partial_barrier(bs, dst_raw, p); + throw_array_null_pointer_store_exception(src_obj, dst_obj, JavaThread::current()); + return; + } + if (HasDecorator::value && + (!oopDesc::is_instanceof_or_null(CompressedOops::decode(element), bound))) { + oop_arraycopy_partial_barrier(bs, dst_raw, p); + throw_array_store_exception(src_obj, dst_obj, JavaThread::current()); + return; } + // write + bs->template write_ref_field_pre(p); + *p = element; } bs->write_ref_array((HeapWord*)dst_raw, length); } - return true; } template @@ -141,4 +156,36 @@ clone_in_heap(oop src, oop dst, size_t size) { bs->write_region(MemRegion((HeapWord*)(void*)dst, size)); } +template +inline void ModRefBarrierSet::AccessBarrier:: +value_copy_in_heap(void* src, void* dst, InlineKlass* md, LayoutKind lk) { + if (HasDecorator::value || (!md->contains_oops())) { + Raw::value_copy(src, dst, md, lk); + } else { + BarrierSetT* bs = barrier_set_cast(BarrierSet::barrier_set()); + // src/dst aren't oops, need offset to adjust oop map offset + const address dst_oop_addr_offset = ((address) dst) - md->payload_offset(); + typedef typename ValueOopType::type OopType; + + // Pre-barriers... + OopMapBlock* map = md->start_of_nonstatic_oop_maps(); + OopMapBlock* const end = map + md->nonstatic_oop_map_count(); + while (map != end) { + address doop_address = dst_oop_addr_offset + map->offset(); + bs->write_ref_array_pre((OopType*) doop_address, map->count(), false); + map++; + } + + Raw::value_copy(src, dst, md, lk); + + // Post-barriers... + map = md->start_of_nonstatic_oop_maps(); + while (map != end) { + address doop_address = dst_oop_addr_offset + map->offset(); + bs->write_ref_array((HeapWord*) doop_address, map->count()); + map++; + } + } +} + #endif // SHARE_GC_SHARED_MODREFBARRIERSET_INLINE_HPP diff --git a/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp b/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp index fdfde866cd7..3a07af4cb45 100644 --- a/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp +++ b/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp @@ -889,7 +889,7 @@ void ShenandoahBarrierSetC2::clone_at_expansion(PhaseMacroExpand* phase, ArrayCo Node* mem_phi = new PhiNode(region, Type::MEMORY, TypeRawPtr::BOTTOM); Node* thread = phase->transform_later(new ThreadLocalNode()); - Node* offset = phase->igvn().MakeConX(in_bytes(ShenandoahThreadLocalData::gc_state_offset())); + Node* offset = phase->MakeConX(in_bytes(ShenandoahThreadLocalData::gc_state_offset())); Node* gc_state_addr = phase->transform_later(new AddPNode(phase->C->top(), thread, offset)); uint gc_state_idx = Compile::AliasIdxRaw; @@ -937,7 +937,7 @@ void ShenandoahBarrierSetC2::clone_at_expansion(PhaseMacroExpand* phase, ArrayCo call = phase->transform_later(call); // Hook up the whole thing into the graph - phase->igvn().replace_node(ac, call); + phase->replace_node(ac, call); } else { BarrierSetC2::clone_at_expansion(phase, ac); } @@ -957,9 +957,9 @@ void ShenandoahBarrierSetC2::unregister_potential_barrier_node(Node* node) const } } -void ShenandoahBarrierSetC2::eliminate_gc_barrier(PhaseMacroExpand* macro, Node* node) const { +void ShenandoahBarrierSetC2::eliminate_gc_barrier(PhaseIterGVN* igvn, Node* node) const { if (is_shenandoah_wb_pre_call(node)) { - shenandoah_eliminate_wb_pre(node, ¯o->igvn()); + shenandoah_eliminate_wb_pre(node, igvn); } if (ShenandoahCardBarrier && node->Opcode() == Op_CastP2X) { Node* shift = node->unique_out(); @@ -970,11 +970,11 @@ void ShenandoahBarrierSetC2::eliminate_gc_barrier(PhaseMacroExpand* macro, Node* assert(mem->Opcode() == Op_LoadB, "unexpected code shape"); // The load is checking if the card has been written so // replace it with zero to fold the test. - macro->replace_node(mem, macro->intcon(0)); + igvn->replace_node(mem, igvn->intcon(0)); continue; } assert(mem->is_Store(), "store required"); - macro->replace_node(mem, mem->in(MemNode::Memory)); + igvn->replace_node(mem, mem->in(MemNode::Memory)); } } } @@ -1106,7 +1106,7 @@ void ShenandoahBarrierSetC2::verify_gc_barriers(Compile* compile, CompilePhase p Node* ShenandoahBarrierSetC2::ideal_node(PhaseGVN* phase, Node* n, bool can_reshape) const { if (is_shenandoah_wb_pre_call(n)) { - uint cnt = ShenandoahBarrierSetC2::write_barrier_pre_Type()->domain()->cnt(); + uint cnt = ShenandoahBarrierSetC2::write_barrier_pre_Type()->domain_sig()->cnt(); if (n->req() > cnt) { Node* addp = n->in(cnt); if (has_only_shenandoah_wb_pre_uses(addp)) { @@ -1192,7 +1192,7 @@ bool ShenandoahBarrierSetC2::final_graph_reshaping(Compile* compile, Node* n, ui assert (n->is_Call(), ""); CallNode *call = n->as_Call(); if (ShenandoahBarrierSetC2::is_shenandoah_wb_pre_call(call)) { - uint cnt = ShenandoahBarrierSetC2::write_barrier_pre_Type()->domain()->cnt(); + uint cnt = ShenandoahBarrierSetC2::write_barrier_pre_Type()->domain_sig()->cnt(); if (call->req() > cnt) { assert(call->req() == cnt + 1, "only one extra input"); Node *addp = call->in(cnt); diff --git a/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.hpp b/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.hpp index dd9e9bcc1a5..83ca83e45be 100644 --- a/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.hpp +++ b/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.hpp @@ -126,7 +126,7 @@ class ShenandoahBarrierSetC2 : public BarrierSetC2 { // Support for macro expanded GC barriers virtual void register_potential_barrier_node(Node* node) const; virtual void unregister_potential_barrier_node(Node* node) const; - virtual void eliminate_gc_barrier(PhaseMacroExpand* macro, Node* node) const; + virtual void eliminate_gc_barrier(PhaseIterGVN* igvn, Node* node) const; virtual void enqueue_useful_gc_barrier(PhaseIterGVN* igvn, Node* node) const; virtual void eliminate_useless_gc_barriers(Unique_Node_List &useful, Compile* C) const; diff --git a/src/hotspot/share/gc/shenandoah/c2/shenandoahSupport.cpp b/src/hotspot/share/gc/shenandoah/c2/shenandoahSupport.cpp index 8210718126b..51c3daddd3d 100644 --- a/src/hotspot/share/gc/shenandoah/c2/shenandoahSupport.cpp +++ b/src/hotspot/share/gc/shenandoah/c2/shenandoahSupport.cpp @@ -447,7 +447,7 @@ void ShenandoahBarrierC2Support::verify(RootNode* root) { if (call->is_call_to_arraycopystub()) { Node* dest = nullptr; - const TypeTuple* args = n->as_Call()->_tf->domain(); + const TypeTuple* args = n->as_Call()->_tf->domain_sig(); for (uint i = TypeFunc::Parms, j = 0; i < args->cnt(); i++) { if (args->field_at(i)->isa_ptr()) { j++; @@ -568,7 +568,7 @@ void ShenandoahBarrierC2Support::verify(RootNode* root) { break; } } - uint stop = n->is_Call() ? n->as_Call()->tf()->domain()->cnt() : n->req(); + uint stop = n->is_Call() ? n->as_Call()->tf()->domain_sig()->cnt() : n->req(); if (i != others_len) { const uint inputs_len = sizeof(others[0].inputs) / sizeof(others[0].inputs[0]); for (uint j = 0; j < inputs_len; j++) { @@ -803,18 +803,17 @@ Node* ShenandoahBarrierC2Support::find_bottom_mem(Node* ctrl, PhaseIdealLoop* ph } } else { if (c->is_Call() && c->as_Call()->adr_type() != nullptr) { - CallProjections projs; - c->as_Call()->extract_projections(&projs, true, false); - if (projs.fallthrough_memproj != nullptr) { - if (projs.fallthrough_memproj->adr_type() == TypePtr::BOTTOM) { - if (projs.catchall_memproj == nullptr) { - mem = projs.fallthrough_memproj; + CallProjections* projs = c->as_Call()->extract_projections(true, false); + if (projs->fallthrough_memproj != nullptr) { + if (projs->fallthrough_memproj->adr_type() == TypePtr::BOTTOM) { + if (projs->catchall_memproj == nullptr) { + mem = projs->fallthrough_memproj; } else { - if (phase->is_dominator(projs.fallthrough_catchproj, ctrl)) { - mem = projs.fallthrough_memproj; + if (phase->is_dominator(projs->fallthrough_catchproj, ctrl)) { + mem = projs->fallthrough_memproj; } else { - assert(phase->is_dominator(projs.catchall_catchproj, ctrl), "one proj must dominate barrier"); - mem = projs.catchall_memproj; + assert(phase->is_dominator(projs->catchall_catchproj, ctrl), "one proj must dominate barrier"); + mem = projs->catchall_memproj; } } } @@ -1075,7 +1074,7 @@ void ShenandoahBarrierC2Support::fix_ctrl(Node* barrier, Node* region, const Mem } } -static Node* create_phis_on_call_return(Node* ctrl, Node* c, Node* n, Node* n_clone, const CallProjections& projs, PhaseIdealLoop* phase) { +static Node* create_phis_on_call_return(Node* ctrl, Node* c, Node* n, Node* n_clone, const CallProjections* projs, PhaseIdealLoop* phase) { Node* region = nullptr; while (c != ctrl) { if (c->is_Region()) { @@ -1087,9 +1086,9 @@ static Node* create_phis_on_call_return(Node* ctrl, Node* c, Node* n, Node* n_cl Node* phi = new PhiNode(region, n->bottom_type()); for (uint j = 1; j < region->req(); j++) { Node* in = region->in(j); - if (phase->is_dominator(projs.fallthrough_catchproj, in)) { + if (phase->is_dominator(projs->fallthrough_catchproj, in)) { phi->init_req(j, n); - } else if (phase->is_dominator(projs.catchall_catchproj, in)) { + } else if (phase->is_dominator(projs->catchall_catchproj, in)) { phi->init_req(j, n_clone); } else { phi->init_req(j, create_phis_on_call_return(ctrl, in, n, n_clone, projs, phase)); @@ -1192,13 +1191,12 @@ void ShenandoahBarrierC2Support::pin_and_expand(PhaseIdealLoop* phase) { } while(stack.size() > 0); continue; } - CallProjections projs; - call->extract_projections(&projs, false, false); + CallProjections* projs = call->extract_projections(false, false); // If this is a runtime call, it doesn't have an exception handling path - if (projs.fallthrough_catchproj == nullptr) { + if (projs->fallthrough_catchproj == nullptr) { assert(call->method() == nullptr, "should be runtime call"); - assert(projs.catchall_catchproj == nullptr, "runtime call should not have catch all projection"); + assert(projs->catchall_catchproj == nullptr, "runtime call should not have catch all projection"); continue; } @@ -1207,8 +1205,8 @@ void ShenandoahBarrierC2Support::pin_and_expand(PhaseIdealLoop* phase) { VectorSet cloned; #endif Node* lrb_clone = lrb->clone(); - phase->register_new_node(lrb_clone, projs.catchall_catchproj); - phase->set_ctrl(lrb, projs.fallthrough_catchproj); + phase->register_new_node(lrb_clone, projs->catchall_catchproj); + phase->set_ctrl(lrb, projs->fallthrough_catchproj); stack.push(lrb, 0); clones.push(lrb_clone); @@ -1230,7 +1228,7 @@ void ShenandoahBarrierC2Support::pin_and_expand(PhaseIdealLoop* phase) { if (idx < n->outcnt()) { Node* u = n->raw_out(idx); Node* c = phase->ctrl_or_self(u); - if (phase->is_dominator(call, c) && phase->is_dominator(c, projs.fallthrough_proj)) { + if (phase->is_dominator(call, c) && phase->is_dominator(c, projs->fallthrough_proj)) { stack.set_index(idx+1); assert(!u->is_CFG(), ""); stack.push(u, 0); @@ -1238,30 +1236,30 @@ void ShenandoahBarrierC2Support::pin_and_expand(PhaseIdealLoop* phase) { Node* u_clone = u->clone(); int nb = u_clone->replace_edge(n, n_clone, &phase->igvn()); assert(nb > 0, "should have replaced some uses"); - phase->register_new_node(u_clone, projs.catchall_catchproj); + phase->register_new_node(u_clone, projs->catchall_catchproj); clones.push(u_clone); - phase->set_ctrl(u, projs.fallthrough_catchproj); + phase->set_ctrl(u, projs->fallthrough_catchproj); } else { bool replaced = false; if (u->is_Phi()) { for (uint k = 1; k < u->req(); k++) { if (u->in(k) == n) { - if (phase->is_dominator(projs.catchall_catchproj, u->in(0)->in(k))) { + if (phase->is_dominator(projs->catchall_catchproj, u->in(0)->in(k))) { phase->igvn().replace_input_of(u, k, n_clone); replaced = true; - } else if (!phase->is_dominator(projs.fallthrough_catchproj, u->in(0)->in(k))) { + } else if (!phase->is_dominator(projs->fallthrough_catchproj, u->in(0)->in(k))) { phase->igvn().replace_input_of(u, k, create_phis_on_call_return(ctrl, u->in(0)->in(k), n, n_clone, projs, phase)); replaced = true; } } } } else { - if (phase->is_dominator(projs.catchall_catchproj, c)) { + if (phase->is_dominator(projs->catchall_catchproj, c)) { phase->igvn().rehash_node_delayed(u); int nb = u->replace_edge(n, n_clone, &phase->igvn()); assert(nb > 0, "should have replaced some uses"); replaced = true; - } else if (!phase->is_dominator(projs.fallthrough_catchproj, c)) { + } else if (!phase->is_dominator(projs->fallthrough_catchproj, c)) { if (u->is_If()) { // Can't break If/Bool/Cmp chain assert(n->is_Bool(), "unexpected If shape"); @@ -1852,14 +1850,13 @@ Node* MemoryGraphFixer::get_ctrl(Node* n) const { if (n->is_Proj() && n->in(0) != nullptr && n->in(0)->is_Call()) { assert(c == n->in(0), ""); CallNode* call = c->as_Call(); - CallProjections projs; - call->extract_projections(&projs, true, false); - if (projs.catchall_memproj != nullptr) { - if (projs.fallthrough_memproj == n) { - c = projs.fallthrough_catchproj; + CallProjections* projs = call->extract_projections(true, false); + if (projs->catchall_memproj != nullptr) { + if (projs->fallthrough_memproj == n) { + c = projs->fallthrough_catchproj; } else { - assert(projs.catchall_memproj == n, ""); - c = projs.catchall_catchproj; + assert(projs->catchall_memproj == n, ""); + c = projs->catchall_catchproj; } } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.hpp b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.hpp index 0d38cc757f4..eebde8504dc 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.hpp @@ -173,7 +173,7 @@ class ShenandoahBarrierSet: public BarrierSet { static oop oop_atomic_xchg_in_heap_at(oop base, ptrdiff_t offset, oop new_value); template - static bool oop_arraycopy_in_heap(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, + static void oop_arraycopy_in_heap(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, T* dst_raw, size_t length); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp index b176446452a..027d6db2880 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp @@ -368,19 +368,20 @@ void ShenandoahBarrierSet::AccessBarrier::clone_in_heap template template -bool ShenandoahBarrierSet::AccessBarrier::oop_arraycopy_in_heap(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, +void ShenandoahBarrierSet::AccessBarrier::oop_arraycopy_in_heap(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, T* dst_raw, size_t length) { T* src = arrayOopDesc::obj_offset_to_raw(src_obj, src_offset_in_bytes, src_raw); T* dst = arrayOopDesc::obj_offset_to_raw(dst_obj, dst_offset_in_bytes, dst_raw); ShenandoahBarrierSet* bs = ShenandoahBarrierSet::barrier_set(); - bs->arraycopy_barrier(src, dst, length); - bool result = Raw::oop_arraycopy_in_heap(src_obj, src_offset_in_bytes, src_raw, dst_obj, dst_offset_in_bytes, dst_raw, length); + bs->arraycopy_barrier(arrayOopDesc::obj_offset_to_raw(src_obj, src_offset_in_bytes, src_raw), + arrayOopDesc::obj_offset_to_raw(dst_obj, dst_offset_in_bytes, dst_raw), + length); + Raw::oop_arraycopy_in_heap(src_obj, src_offset_in_bytes, src_raw, dst_obj, dst_offset_in_bytes, dst_raw, length); if (ShenandoahCardBarrier) { bs->write_ref_array((HeapWord*) dst, length); } - return result; } template diff --git a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSetNMethod.cpp b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSetNMethod.cpp index c6e6108fda8..368ae4d9373 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSetNMethod.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSetNMethod.cpp @@ -61,7 +61,7 @@ bool ShenandoahBarrierSetNMethod::nmethod_entry_barrier(nmethod* nm) { // We can end up calling nmethods that are unloading // since we clear compiled ICs lazily. Returning false - // will re-resovle the call and update the compiled IC. + // will re-resolve the call and update the compiled IC. return false; } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp index 2dc0813e513..185aabc0495 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp @@ -85,7 +85,7 @@ void ShenandoahMark::do_task(ShenandoahObjToScanQueue* q, T* cl, ShenandoahLiveD obj->oop_iterate(cl); dedup_string(obj, req); - } else if (obj->is_objArray()) { + } else if (obj->is_refArray()) { // Case 2: Object array instance and no chunk is set. Must be the first // time we visit it, start the chunked processing. do_chunked_array_start(q, cl, obj, weak); @@ -156,7 +156,7 @@ inline void ShenandoahMark::count_liveness(ShenandoahLiveData* live_data, oop ob template inline void ShenandoahMark::do_chunked_array_start(ShenandoahObjToScanQueue* q, T* cl, oop obj, bool weak) { - assert(obj->is_objArray(), "expect object array"); + assert(obj->is_refArray(), "expect object array"); objArrayOop array = objArrayOop(obj); int len = array->length(); @@ -223,7 +223,7 @@ inline void ShenandoahMark::do_chunked_array_start(ShenandoahObjToScanQueue* q, template inline void ShenandoahMark::do_chunked_array(ShenandoahObjToScanQueue* q, T* cl, oop obj, int chunk, int pow, bool weak) { - assert(obj->is_objArray(), "expect object array"); + assert(obj->is_refArray(), "expect object array"); objArrayOop array = objArrayOop(obj); assert (ObjArrayMarkingStride > 0, "sanity"); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp index 68bec5c2071..d7f7bfa4236 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp @@ -179,7 +179,7 @@ void ShenandoahScanRemembered::process_clusters(size_t first_cluster, size_t cou // PREFIX: The object that straddles into this range of dirty cards // from the left may be subject to special treatment unless // it is an object array. - if (p < left && !obj->is_objArray()) { + if (p < left && !obj->is_refArray()) { // The mutator (both compiler and interpreter, but not JNI?) // typically dirty imprecisely (i.e. only the head of an object), // but GC closures typically dirty the object precisely. (It would @@ -253,7 +253,7 @@ void ShenandoahScanRemembered::process_clusters(size_t first_cluster, size_t cou assert(last_p < right, "Error"); // check if last_p suffix needs scanning const oop last_obj = cast_to_oop(last_p); - if (!last_obj->is_objArray()) { + if (!last_obj->is_refArray()) { // scan the remaining suffix of the object const MemRegion last_mr(right, p); assert(p == last_p + last_obj->size(), "Would miss portion of last_obj"); diff --git a/src/hotspot/share/gc/z/c2/zBarrierSetC2.cpp b/src/hotspot/share/gc/z/c2/zBarrierSetC2.cpp index 650918e2d30..b88e2cacad2 100644 --- a/src/hotspot/share/gc/z/c2/zBarrierSetC2.cpp +++ b/src/hotspot/share/gc/z/c2/zBarrierSetC2.cpp @@ -418,7 +418,7 @@ void ZBarrierSetC2::clone_at_expansion(PhaseMacroExpand* phase, ArrayCopyNode* a if (ac->is_clone_array() && ary_ptr != nullptr) { BasicType bt = ary_ptr->elem()->array_element_basic_type(); - if (is_reference_type(bt)) { + if (is_reference_type(bt) && !ary_ptr->is_flat()) { // Clone object array bt = T_OBJECT; } else { @@ -444,7 +444,7 @@ void ZBarrierSetC2::clone_at_expansion(PhaseMacroExpand* phase, ArrayCopyNode* a if (offset != arrayOopDesc::base_offset_in_bytes(T_OBJECT)) { assert(!UseCompressedClassPointers || UseCompactObjectHeaders, "should only happen without compressed class pointers"); assert((arrayOopDesc::base_offset_in_bytes(T_OBJECT) - offset) == BytesPerLong, "unexpected offset"); - length = phase->transform_later(new SubLNode(length, phase->longcon(1))); // Size is in longs + length = phase->transform_later(new SubXNode(length, phase->longcon(1))); // Size is in longs src_offset = phase->longcon(arrayOopDesc::base_offset_in_bytes(T_OBJECT)); dest_offset = src_offset; } @@ -551,7 +551,8 @@ void ZBarrierSetC2::analyze_dominating_barriers() const { elide_dominated_barriers(atomics, atomic_dominators); } -void ZBarrierSetC2::eliminate_gc_barrier(PhaseMacroExpand* macro, Node* node) const { + +void ZBarrierSetC2::eliminate_gc_barrier(PhaseIterGVN* igvn, Node* node) const { eliminate_gc_barrier_data(node); } diff --git a/src/hotspot/share/gc/z/c2/zBarrierSetC2.hpp b/src/hotspot/share/gc/z/c2/zBarrierSetC2.hpp index 370a543f381..39cfb51f8d2 100644 --- a/src/hotspot/share/gc/z/c2/zBarrierSetC2.hpp +++ b/src/hotspot/share/gc/z/c2/zBarrierSetC2.hpp @@ -131,7 +131,7 @@ class ZBarrierSetC2 : public BarrierSetC2 { virtual void late_barrier_analysis() const; virtual int estimate_stub_size() const; virtual void emit_stubs(CodeBuffer& cb) const; - virtual void eliminate_gc_barrier(PhaseMacroExpand* macro, Node* node) const; + virtual void eliminate_gc_barrier(PhaseIterGVN* igvn, Node* node) const; virtual void eliminate_gc_barrier_data(Node* node) const; #ifndef PRODUCT diff --git a/src/hotspot/share/gc/z/zBarrier.hpp b/src/hotspot/share/gc/z/zBarrier.hpp index 3f9e6c78b04..827778f28bb 100644 --- a/src/hotspot/share/gc/z/zBarrier.hpp +++ b/src/hotspot/share/gc/z/zBarrier.hpp @@ -27,6 +27,7 @@ #include "gc/z/zAddress.hpp" #include "memory/allStatic.hpp" #include "memory/iterator.hpp" +#include "oops/inlineKlass.hpp" // == Shift based load barrier == // diff --git a/src/hotspot/share/gc/z/zBarrierSet.hpp b/src/hotspot/share/gc/z/zBarrierSet.hpp index 51eb16319a0..22342862a33 100644 --- a/src/hotspot/share/gc/z/zBarrierSet.hpp +++ b/src/hotspot/share/gc/z/zBarrierSet.hpp @@ -50,6 +50,12 @@ class ZBarrierSet : public BarrierSet { virtual void print_on(outputStream* st) const; + enum OopCopyCheckStatus { + oop_copy_check_ok = 0, // oop array copy sucessful + oop_copy_check_class_cast = 1, // oop array copy failed subtype check (ARRAYCOPY_CHECKCAST) + oop_copy_check_null = 2 // oop array copy failed null check (ARRAYCOPY_NOTNULL) + }; + template class AccessBarrier : public BarrierSet::AccessBarrier { private: @@ -83,11 +89,11 @@ class ZBarrierSet : public BarrierSet { static void store_barrier_native_without_healing(narrowOop* p) { unsupported(); } static zaddress oop_copy_one_barriers(zpointer* dst, zpointer* src); - static bool oop_copy_one_check_cast(zpointer* dst, zpointer* src, Klass* dst_klass); - static void oop_copy_one(zpointer* dst, zpointer* src); + static OopCopyCheckStatus oop_copy_one_check_cast(zpointer* dst, zpointer* src, Klass* dst_klass); + static OopCopyCheckStatus oop_copy_one(zpointer* dst, zpointer* src); - static bool oop_arraycopy_in_heap_check_cast(zpointer* dst, zpointer* src, size_t length, Klass* dst_klass); - static bool oop_arraycopy_in_heap_no_check_cast(zpointer* dst, zpointer* src, size_t length); + static OopCopyCheckStatus oop_arraycopy_in_heap_check_cast(zpointer* dst, zpointer* src, size_t length, Klass* dst_klass); + static OopCopyCheckStatus oop_arraycopy_in_heap_no_check_cast(zpointer* dst, zpointer* src, size_t length); public: // @@ -119,22 +125,24 @@ class ZBarrierSet : public BarrierSet { static oop oop_atomic_xchg_in_heap(narrowOop* p, oop new_value) { unsupported(); return nullptr; } static oop oop_atomic_xchg_in_heap_at(oop base, ptrdiff_t offset, oop new_value); - static bool oop_arraycopy_in_heap(arrayOop src_obj, size_t src_offset_in_bytes, zpointer* src_raw, + static void oop_arraycopy_in_heap(arrayOop src_obj, size_t src_offset_in_bytes, zpointer* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, zpointer* dst_raw, size_t length); - static bool oop_arraycopy_in_heap(arrayOop src_obj, size_t src_offset_in_bytes, oop* src_raw, + static void oop_arraycopy_in_heap(arrayOop src_obj, size_t src_offset_in_bytes, oop* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, oop* dst_raw, size_t length) { - return oop_arraycopy_in_heap(src_obj, src_offset_in_bytes, (zpointer*)src_raw, - dst_obj, dst_offset_in_bytes, (zpointer*)dst_raw, - length); + oop_arraycopy_in_heap(src_obj, src_offset_in_bytes, (zpointer*)src_raw, + dst_obj, dst_offset_in_bytes, (zpointer*)dst_raw, + length); } - static bool oop_arraycopy_in_heap(arrayOop src_obj, size_t src_offset_in_bytes, narrowOop* src_raw, + static void oop_arraycopy_in_heap(arrayOop src_obj, size_t src_offset_in_bytes, narrowOop* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, narrowOop* dst_raw, - size_t length) { unsupported(); return false; } + size_t length) { unsupported(); } static void clone_in_heap(oop src, oop dst, size_t size); + static void value_copy_in_heap(void* src, void* dst, InlineKlass* md, LayoutKind lk); + // // Not in heap // diff --git a/src/hotspot/share/gc/z/zBarrierSet.inline.hpp b/src/hotspot/share/gc/z/zBarrierSet.inline.hpp index f7baf85efbf..ec5f23e2ca2 100644 --- a/src/hotspot/share/gc/z/zBarrierSet.inline.hpp +++ b/src/hotspot/share/gc/z/zBarrierSet.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,11 +27,14 @@ #include "gc/z/zBarrierSet.hpp" #include "gc/shared/accessBarrierSupport.inline.hpp" +#include "gc/z/zAddress.hpp" #include "gc/z/zAddress.inline.hpp" #include "gc/z/zBarrier.inline.hpp" #include "gc/z/zIterator.inline.hpp" #include "gc/z/zNMethod.hpp" #include "memory/iterator.inline.hpp" +#include "oops/inlineKlass.inline.hpp" +#include "utilities/copy.hpp" #include "utilities/debug.hpp" template @@ -329,78 +332,99 @@ inline zaddress ZBarrierSet::AccessBarrier::oop_copy_on } template -inline void ZBarrierSet::AccessBarrier::oop_copy_one(zpointer* dst, zpointer* src) { +inline ZBarrierSet::OopCopyCheckStatus ZBarrierSet::AccessBarrier::oop_copy_one(zpointer* dst, zpointer* src) { const zaddress obj = oop_copy_one_barriers(dst, src); + if (HasDecorator::value && is_null(obj)) { + return oop_copy_check_null; + } + Atomic::store(dst, ZAddress::store_good(obj)); + return oop_copy_check_ok; } template -inline bool ZBarrierSet::AccessBarrier::oop_copy_one_check_cast(zpointer* dst, zpointer* src, Klass* dst_klass) { +inline ZBarrierSet::OopCopyCheckStatus ZBarrierSet::AccessBarrier::oop_copy_one_check_cast(zpointer* dst, zpointer* src, Klass* dst_klass) { const zaddress obj = oop_copy_one_barriers(dst, src); + const bool null_check = HasDecorator::value; - if (!oopDesc::is_instanceof_or_null(to_oop(obj), dst_klass)) { + if (null_check && is_null(obj)) { + return oop_copy_check_null; + } + else if (!oopDesc::is_instanceof_or_null(to_oop(obj), dst_klass)) { // Check cast failed - return false; + return oop_copy_check_class_cast; } Atomic::store(dst, ZAddress::store_good(obj)); - return true; + return oop_copy_check_ok; } template -inline bool ZBarrierSet::AccessBarrier::oop_arraycopy_in_heap_check_cast(zpointer* dst, zpointer* src, size_t length, Klass* dst_klass) { +inline ZBarrierSet::OopCopyCheckStatus ZBarrierSet::AccessBarrier::oop_arraycopy_in_heap_check_cast(zpointer* dst, zpointer* src, size_t length, Klass* dst_klass) { // Check cast and copy each elements - for (const zpointer* const end = src + length; src < end; src++, dst++) { - if (!oop_copy_one_check_cast(dst, src, dst_klass)) { - // Check cast failed - return false; - } + OopCopyCheckStatus check_status = oop_copy_check_ok; + for (const zpointer* const end = src + length; (check_status == oop_copy_check_ok) && (src < end); src++, dst++) { + check_status = oop_copy_one_check_cast(dst, src, dst_klass); } - - return true; + return check_status; } template -inline bool ZBarrierSet::AccessBarrier::oop_arraycopy_in_heap_no_check_cast(zpointer* dst, zpointer* src, size_t length) { +inline ZBarrierSet::OopCopyCheckStatus ZBarrierSet::AccessBarrier::oop_arraycopy_in_heap_no_check_cast(zpointer* dst, zpointer* src, size_t length) { const bool is_disjoint = HasDecorator::value; - + OopCopyCheckStatus check_status = oop_copy_check_ok; if (is_disjoint || src > dst) { - for (const zpointer* const end = src + length; src < end; src++, dst++) { - oop_copy_one(dst, src); + for (const zpointer* const end = src + length; (check_status == oop_copy_check_ok) && (src < end); src++, dst++) { + check_status = oop_copy_one(dst, src); } - return true; + return check_status; } if (src < dst) { const zpointer* const end = src; src += length - 1; dst += length - 1; - for ( ; src >= end; src--, dst--) { - oop_copy_one(dst, src); + for ( ; (check_status == oop_copy_check_ok) && (src >= end); src--, dst--) { + check_status = oop_copy_one(dst, src); } - return true; + return check_status; } // src and dst are the same; nothing to do - return true; + return check_status; } template -inline bool ZBarrierSet::AccessBarrier::oop_arraycopy_in_heap(arrayOop src_obj, size_t src_offset_in_bytes, zpointer* src_raw, +inline void ZBarrierSet::AccessBarrier::oop_arraycopy_in_heap(arrayOop src_obj, size_t src_offset_in_bytes, zpointer* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, zpointer* dst_raw, size_t length) { zpointer* const src = arrayOopDesc::obj_offset_to_raw(src_obj, src_offset_in_bytes, src_raw); zpointer* const dst = arrayOopDesc::obj_offset_to_raw(dst_obj, dst_offset_in_bytes, dst_raw); + OopCopyCheckStatus check_status; if (HasDecorator::value) { Klass* const dst_klass = objArrayOop(dst_obj)->element_klass(); - return oop_arraycopy_in_heap_check_cast(dst, src, length, dst_klass); + check_status = oop_arraycopy_in_heap_check_cast(dst, src, length, dst_klass); + } else { + check_status = oop_arraycopy_in_heap_no_check_cast(dst, src, length); } - return oop_arraycopy_in_heap_no_check_cast(dst, src, length); + switch (check_status) { + case oop_copy_check_ok: + return; + case oop_copy_check_class_cast: + throw_array_store_exception(src_obj, dst_obj, JavaThread::current()); + break; + case oop_copy_check_null: + throw_array_null_pointer_store_exception(src_obj, dst_obj, JavaThread::current()); + break; + default: + ShouldNotReachHere(); + return; + } } class ZColorStoreGoodOopClosure : public BasicOopIterateClosure { @@ -432,7 +456,7 @@ template inline void ZBarrierSet::AccessBarrier::clone_in_heap(oop src, oop dst, size_t size) { check_is_valid_zaddress(src); - if (dst->is_objArray()) { + if (dst->is_refArray()) { // Cloning an object array is similar to performing array copy. // If an array is large enough to have its allocation segmented, // this operation might require GC barriers. However, the intrinsics @@ -457,6 +481,55 @@ inline void ZBarrierSet::AccessBarrier::clone_in_heap(o ZIterator::oop_iterate(dst, &cl_sg); } +static inline void copy_primitive_payload(const void* src, const void* dst, const size_t payload_size_bytes, size_t& copied_bytes) { + if (payload_size_bytes == 0) { + return; + } + void* src_payload = (void*)(address(src) + copied_bytes); + void* dst_payload = (void*)(address(dst) + copied_bytes); + Copy::copy_value_content(src_payload, dst_payload, payload_size_bytes); + copied_bytes += payload_size_bytes; +} + +template +inline void ZBarrierSet::AccessBarrier::value_copy_in_heap(void* src, void* dst, InlineKlass* md, LayoutKind lk) { + if (md->contains_oops()) { + // Iterate over each oop map, performing: + // 1) possibly raw copy for any primitive payload before each map + // 2) load and store barrier for each oop + // 3) possibly raw copy for any primitive payload trailer + + // src/dst may not be oops, need offset to adjust oop map offset + const address src_oop_addr_offset = ((address) src) - md->payload_offset(); + OopMapBlock* map = md->start_of_nonstatic_oop_maps(); + const OopMapBlock* const end = map + md->nonstatic_oop_map_count(); + size_t size_in_bytes = md->layout_size_in_bytes(lk); + size_t copied_bytes = 0; + while (map != end) { + zpointer *src_p = (zpointer*)(src_oop_addr_offset + map->offset()); + const uintptr_t oop_offset = uintptr_t(src_p) - uintptr_t(src); + zpointer *dst_p = (zpointer*)(uintptr_t(dst) + oop_offset); + + // Copy any leading primitive payload before every cluster of oops + assert(copied_bytes < oop_offset || copied_bytes == oop_offset, "Negative sized leading payload segment"); + copy_primitive_payload(src, dst, oop_offset - copied_bytes, copied_bytes); + + // Copy a cluster of oops + for (const zpointer* const src_end = src_p + map->count(); src_p < src_end; src_p++, dst_p++) { + oop_copy_one(dst_p, src_p); + copied_bytes += sizeof(zpointer); + } + map++; + } + + // Copy trailing primitive payload after potential oops + assert(copied_bytes < size_in_bytes || copied_bytes == size_in_bytes, "Negative sized trailing payload segment"); + copy_primitive_payload(src, dst, size_in_bytes - copied_bytes, copied_bytes); + } else { + Raw::value_copy_in_heap(src, dst, md, lk); + } +} + // // Not in heap // diff --git a/src/hotspot/share/gc/z/zHeapIterator.cpp b/src/hotspot/share/gc/z/zHeapIterator.cpp index 63bede6143b..39b31bad38d 100644 --- a/src/hotspot/share/gc/z/zHeapIterator.cpp +++ b/src/hotspot/share/gc/z/zHeapIterator.cpp @@ -462,7 +462,7 @@ void ZHeapIterator::follow_array_chunk(const ZHeapIteratorContext& context, cons template void ZHeapIterator::follow(const ZHeapIteratorContext& context, oop obj) { // Follow - if (obj->is_objArray()) { + if (obj->is_refArray()) { follow_array(context, obj); } else { follow_object(context, obj); diff --git a/src/hotspot/share/gc/z/zIterator.inline.hpp b/src/hotspot/share/gc/z/zIterator.inline.hpp index fb20a424288..22d2bd89ebb 100644 --- a/src/hotspot/share/gc/z/zIterator.inline.hpp +++ b/src/hotspot/share/gc/z/zIterator.inline.hpp @@ -30,6 +30,7 @@ #include "memory/iterator.inline.hpp" #include "oops/objArrayOop.hpp" #include "oops/oop.inline.hpp" +#include "oops/refArrayOop.hpp" inline bool ZIterator::is_invisible_object(oop obj) { // This is a good place to make sure that we can't concurrently iterate over @@ -45,7 +46,7 @@ inline bool ZIterator::is_invisible_object(oop obj) { } inline bool ZIterator::is_invisible_object_array(oop obj) { - return obj->klass()->is_objArray_klass() && is_invisible_object(obj); + return obj->klass()->is_refArray_klass() && is_invisible_object(obj); } // This iterator skips invisible object arrays @@ -68,7 +69,8 @@ void ZIterator::oop_iterate(oop obj, OopClosureT* cl) { template void ZIterator::oop_iterate_range(objArrayOop obj, OopClosureT* cl, int start, int end) { assert(!is_invisible_object_array(obj), "not safe"); - obj->oop_iterate_range(cl, start, end); + assert(obj->is_refArray(), "Must be"); + refArrayOop(obj)->oop_iterate_range(cl, start, end); } template diff --git a/src/hotspot/share/gc/z/zMark.cpp b/src/hotspot/share/gc/z/zMark.cpp index 482b4ddd75f..3e3fde61e1b 100644 --- a/src/hotspot/share/gc/z/zMark.cpp +++ b/src/hotspot/share/gc/z/zMark.cpp @@ -170,7 +170,7 @@ bool ZMark::follow_work_partial() { } bool ZMark::is_array(zaddress addr) const { - return to_oop(addr)->is_objArray(); + return to_oop(addr)->is_refArray(); } static uintptr_t encode_partial_array_offset(zpointer* addr) { diff --git a/src/hotspot/share/gc/z/zObjArrayAllocator.cpp b/src/hotspot/share/gc/z/zObjArrayAllocator.cpp index ddb0ca49278..e8b5a753dd6 100644 --- a/src/hotspot/share/gc/z/zObjArrayAllocator.cpp +++ b/src/hotspot/share/gc/z/zObjArrayAllocator.cpp @@ -27,6 +27,7 @@ #include "oops/arrayKlass.hpp" #include "runtime/interfaceSupport.inline.hpp" #include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" ZObjArrayAllocator::ZObjArrayAllocator(Klass* klass, size_t word_size, int length, bool do_zero, Thread* thread) : ObjArrayAllocator(klass, word_size, length, do_zero, thread) {} @@ -49,7 +50,7 @@ oop ZObjArrayAllocator::initialize(HeapWord* mem) const { // time and time-to-safepoint const size_t segment_max = ZUtils::bytes_to_words(64 * K); - if (_word_size <= segment_max) { + if (_word_size <= segment_max || ArrayKlass::cast(_klass)->is_flatArray_klass()) { // To small to use segmented clearing return ObjArrayAllocator::initialize(mem); } @@ -65,7 +66,11 @@ oop ZObjArrayAllocator::initialize(HeapWord* mem) const { if (UseCompactObjectHeaders) { oopDesc::release_set_mark(mem, _klass->prototype_header().set_marked()); } else { - arrayOopDesc::set_mark(mem, markWord::prototype().set_marked()); + if (EnableValhalla) { + arrayOopDesc::set_mark(mem, _klass->prototype_header().set_marked()); + } else { + arrayOopDesc::set_mark(mem, markWord::prototype().set_marked()); + } arrayOopDesc::release_set_klass(mem, _klass); } assert(_length >= 0, "length should be non-negative"); @@ -155,7 +160,7 @@ oop ZObjArrayAllocator::initialize(HeapWord* mem) const { ZThreadLocalData::clear_invisible_root(_thread); // Signal to the ZIterator that this is no longer an invisible root - if (UseCompactObjectHeaders) { + if (UseCompactObjectHeaders || EnableValhalla) { oopDesc::release_set_mark(mem, _klass->prototype_header()); } else { oopDesc::release_set_mark(mem, markWord::prototype()); diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h index 2f0958bcac4..41221725040 100644 --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -174,6 +174,9 @@ JVM_GetVmArguments(JNIEnv *env); JNIEXPORT jboolean JNICALL JVM_IsPreviewEnabled(void); +JNIEXPORT jboolean JNICALL +JVM_IsValhallaEnabled(void); + JNIEXPORT jboolean JNICALL JVM_IsContinuationsSupported(void); @@ -558,6 +561,9 @@ JVM_IsInterface(JNIEnv *env, jclass cls); JNIEXPORT jboolean JNICALL JVM_IsHiddenClass(JNIEnv *env, jclass cls); +JNIEXPORT jboolean JNICALL +JVM_IsIdentityClass(JNIEnv *env, jclass cls); + JNIEXPORT jobjectArray JNICALL JVM_GetDeclaredClasses(JNIEnv *env, jclass ofClass); @@ -1084,6 +1090,27 @@ JVM_InitAgentProperties(JNIEnv *env, jobject agent_props); JNIEXPORT jstring JNICALL JVM_GetTemporaryDirectory(JNIEnv *env); +JNIEXPORT jarray JNICALL +JVM_CopyOfSpecialArray(JNIEnv *env, jarray orig, jint from, jint to); + +JNIEXPORT jarray JNICALL +JVM_NewNullRestrictedNonAtomicArray(JNIEnv *env, jclass elmClass, jint len, jobject initVal); + +JNIEXPORT jarray JNICALL +JVM_NewNullRestrictedAtomicArray(JNIEnv *env, jclass elmClass, jint len, jobject initVal); + +JNIEXPORT jarray JNICALL +JVM_NewNullableAtomicArray(JNIEnv *env, jclass elmClass, jint len); + +JNIEXPORT jboolean JNICALL +JVM_IsFlatArray(JNIEnv *env, jobject obj); + +JNIEXPORT jboolean JNICALL +JVM_IsNullRestrictedArray(JNIEnv *env, jobject obj); + +JNIEXPORT jboolean JNICALL +JVM_IsAtomicArray(JNIEnv *env, jobject obj); + /* Generics reflection support. * * Returns information about the given class's EnclosingMethod diff --git a/src/hotspot/share/include/jvm_constants.h b/src/hotspot/share/include/jvm_constants.h index e3a8c9d37bb..7406d45a17c 100644 --- a/src/hotspot/share/include/jvm_constants.h +++ b/src/hotspot/share/include/jvm_constants.h @@ -30,7 +30,7 @@ #define JVM_RECOGNIZED_CLASS_MODIFIERS (JVM_ACC_PUBLIC | \ JVM_ACC_FINAL | \ - JVM_ACC_SUPER | \ + JVM_ACC_IDENTITY | \ JVM_ACC_INTERFACE | \ JVM_ACC_ABSTRACT | \ JVM_ACC_ANNOTATION | \ @@ -45,6 +45,7 @@ JVM_ACC_VOLATILE | \ JVM_ACC_TRANSIENT | \ JVM_ACC_ENUM | \ + JVM_ACC_STRICT | \ JVM_ACC_SYNTHETIC) #define JVM_RECOGNIZED_METHOD_MODIFIERS (JVM_ACC_PUBLIC | \ diff --git a/src/hotspot/share/interpreter/abstractInterpreter.cpp b/src/hotspot/share/interpreter/abstractInterpreter.cpp index 05590add8ff..ee15f93b5cf 100644 --- a/src/hotspot/share/interpreter/abstractInterpreter.cpp +++ b/src/hotspot/share/interpreter/abstractInterpreter.cpp @@ -153,7 +153,13 @@ AbstractInterpreter::MethodKind AbstractInterpreter::method_kind(const methodHan if (m->code_size() == 1) { // We need to execute the special return bytecode to check for // finalizer registration so create a normal frame. + // No need to use the method kind with a memory barrier on entry + // because the method is empty and already has a memory barrier on return return zerolocals; + } else if (EnableValhalla) { + // For non-empty Object constructors, we need a memory barrier + // when entering the method to ensure correctness of strict fields + return object_init; } break; default: break; @@ -302,6 +308,7 @@ void AbstractInterpreter::print_method_kind(MethodKind kind) { case getter : tty->print("getter" ); break; case setter : tty->print("setter" ); break; case abstract : tty->print("abstract" ); break; + case object_init : tty->print("object_init" ); break; case java_lang_math_sin : tty->print("java_lang_math_sin" ); break; case java_lang_math_cos : tty->print("java_lang_math_cos" ); break; case java_lang_math_tan : tty->print("java_lang_math_tan" ); break; diff --git a/src/hotspot/share/interpreter/abstractInterpreter.hpp b/src/hotspot/share/interpreter/abstractInterpreter.hpp index 6f7523fd00a..2e7cab6dd0d 100644 --- a/src/hotspot/share/interpreter/abstractInterpreter.hpp +++ b/src/hotspot/share/interpreter/abstractInterpreter.hpp @@ -65,6 +65,7 @@ class AbstractInterpreter: AllStatic { getter, // getter method setter, // setter method abstract, // abstract method (throws an AbstractMethodException) + object_init, // special barrier on entry method_handle_invoke_FIRST, // java.lang.invoke.MethodHandles::invokeExact, etc. method_handle_invoke_LAST = (method_handle_invoke_FIRST + (static_cast(vmIntrinsics::LAST_MH_SIG_POLY) diff --git a/src/hotspot/share/interpreter/bytecodeUtils.cpp b/src/hotspot/share/interpreter/bytecodeUtils.cpp index a5503cc4b88..4cc8b21f266 100644 --- a/src/hotspot/share/interpreter/bytecodeUtils.cpp +++ b/src/hotspot/share/interpreter/bytecodeUtils.cpp @@ -1110,6 +1110,7 @@ int ExceptionMessageBuilder::get_NPE_null_slot(int bci) { case Bytecodes::_athrow: case Bytecodes::_monitorenter: case Bytecodes::_monitorexit: + case Bytecodes::_checkcast: return 0; case Bytecodes::_iaload: case Bytecodes::_faload: @@ -1170,7 +1171,13 @@ int ExceptionMessageBuilder::get_NPE_null_slot(int bci) { bool ExceptionMessageBuilder::print_NPE_cause(outputStream* os, int bci, int slot) { if (print_NPE_cause0(os, bci, slot, _max_cause_detail, false, " because \"")) { - os->print("\" is null"); + address code_base = _method->constMethod()->code_base(); + Bytecodes::Code code = Bytecodes::java_code_at(_method, code_base + bci); + if (code == Bytecodes::_aastore) { + os->print("\" is null or is a null-free array and there's an attempt to store null in it"); + } else { + os->print("\" is null"); + } return true; } return false; @@ -1432,6 +1439,11 @@ void ExceptionMessageBuilder::print_NPE_failed_action(outputStream *os, int bci) print_method_name(os, _method, cp_index, code); os->print("\""); } break; + case Bytecodes::_checkcast: { + int cp_index = Bytes::get_Java_u2(code_base + pos); + ConstantPool* cp = _method->constants(); + os->print("Cannot cast to null-free type \"%s\"", cp->klass_at_noresolve(cp_index)->as_C_string()); + } break; default: assert(0, "We should have checked this bytecode in get_NPE_null_slot()."); diff --git a/src/hotspot/share/interpreter/bytecodes.cpp b/src/hotspot/share/interpreter/bytecodes.cpp index 1526b3c330e..cf8064f6a05 100644 --- a/src/hotspot/share/interpreter/bytecodes.cpp +++ b/src/hotspot/share/interpreter/bytecodes.cpp @@ -30,6 +30,7 @@ #define JVM_BYTECODES_DO(def) \ def(_fast_agetfield , "fast_agetfield" , "bJJ" , nullptr , T_OBJECT , 0, true , _getfield ) \ + def(_fast_vgetfield , "fast_vgetfield" , "bJJ" , nullptr , T_OBJECT , 0, true , _getfield ) \ def(_fast_bgetfield , "fast_bgetfield" , "bJJ" , nullptr , T_INT , 0, true , _getfield ) \ def(_fast_cgetfield , "fast_cgetfield" , "bJJ" , nullptr , T_CHAR , 0, true , _getfield ) \ def(_fast_dgetfield , "fast_dgetfield" , "bJJ" , nullptr , T_DOUBLE , 0, true , _getfield ) \ @@ -39,6 +40,7 @@ def(_fast_sgetfield , "fast_sgetfield" , "bJJ" , nullptr , T_SHORT , 0, true , _getfield ) \ \ def(_fast_aputfield , "fast_aputfield" , "bJJ" , nullptr , T_OBJECT , 0, true , _putfield ) \ + def(_fast_vputfield , "fast_vputfield" , "bJJ" , nullptr , T_OBJECT , 0, true , _putfield ) \ def(_fast_bputfield , "fast_bputfield" , "bJJ" , nullptr , T_INT , 0, true , _putfield ) \ def(_fast_zputfield , "fast_zputfield" , "bJJ" , nullptr , T_INT , 0, true , _putfield ) \ def(_fast_cputfield , "fast_cputfield" , "bJJ" , nullptr , T_CHAR , 0, true , _putfield ) \ diff --git a/src/hotspot/share/interpreter/bytecodes.hpp b/src/hotspot/share/interpreter/bytecodes.hpp index 629cca706ae..5370b8cb7f6 100644 --- a/src/hotspot/share/interpreter/bytecodes.hpp +++ b/src/hotspot/share/interpreter/bytecodes.hpp @@ -26,6 +26,7 @@ #define SHARE_INTERPRETER_BYTECODES_HPP #include "memory/allStatic.hpp" +#include "runtime/globals.hpp" #include "utilities/globalDefinitions.hpp" // Bytecodes specifies all bytecodes used in the VM and @@ -248,6 +249,7 @@ class Bytecodes: AllStatic { // JVM bytecodes _fast_agetfield = number_of_java_codes, + _fast_vgetfield , _fast_bgetfield , _fast_cgetfield , _fast_dgetfield , @@ -257,6 +259,7 @@ class Bytecodes: AllStatic { _fast_sgetfield , _fast_aputfield , + _fast_vputfield , _fast_bputfield , _fast_zputfield , _fast_cputfield , @@ -419,7 +422,9 @@ class Bytecodes: AllStatic { || code == _fconst_0 || code == _dconst_0); } static bool is_return (Code code) { return (_ireturn <= code && code <= _return); } static bool is_invoke (Code code) { return (_invokevirtual <= code && code <= _invokedynamic); } - static bool is_field_code (Code code) { return (_getstatic <= java_code(code) && java_code(code) <= _putfield); } + static bool is_field_code (Code code) { + return (_getstatic <= java_code(code) && java_code(code) <= _putfield); + } static bool has_receiver (Code code) { assert(is_invoke(code), ""); return code == _invokevirtual || code == _invokespecial || code == _invokeinterface; } diff --git a/src/hotspot/share/interpreter/interpreterRuntime.cpp b/src/hotspot/share/interpreter/interpreterRuntime.cpp index ae103c8a339..4a7d68fde44 100644 --- a/src/hotspot/share/interpreter/interpreterRuntime.cpp +++ b/src/hotspot/share/interpreter/interpreterRuntime.cpp @@ -24,6 +24,7 @@ #include "classfile/javaClasses.inline.hpp" #include "classfile/symbolTable.hpp" +#include "classfile/systemDictionary.hpp" #include "classfile/vmClasses.hpp" #include "classfile/vmSymbols.hpp" #include "code/codeCache.hpp" @@ -44,6 +45,9 @@ #include "memory/universe.hpp" #include "oops/constantPool.inline.hpp" #include "oops/cpCache.inline.hpp" +#include "oops/flatArrayKlass.hpp" +#include "oops/flatArrayOop.inline.hpp" +#include "oops/inlineKlass.inline.hpp" #include "oops/instanceKlass.inline.hpp" #include "oops/klass.inline.hpp" #include "oops/method.inline.hpp" @@ -75,6 +79,7 @@ #include "utilities/checkedCast.hpp" #include "utilities/copy.hpp" #include "utilities/events.hpp" +#include "utilities/globalDefinitions.hpp" #if INCLUDE_JFR #include "jfr/jfr.inline.hpp" #endif @@ -225,6 +230,38 @@ JRT_ENTRY(void, InterpreterRuntime::_new(JavaThread* current, ConstantPool* pool current->set_vm_result_oop(obj); JRT_END +JRT_ENTRY(void, InterpreterRuntime::read_flat_field(JavaThread* current, oopDesc* obj, ResolvedFieldEntry* entry)) + assert(oopDesc::is_oop(obj), "Sanity check"); + Handle obj_h(THREAD, obj); + + InstanceKlass* holder = InstanceKlass::cast(entry->field_holder()); + assert(entry->field_holder()->field_is_flat(entry->field_index()), "Sanity check"); + + InlineLayoutInfo* layout_info = holder->inline_layout_info_adr(entry->field_index()); + InlineKlass* field_vklass = layout_info->klass(); + +#ifdef ASSERT + fieldDescriptor fd; + bool found = holder->find_field_from_offset(entry->field_offset(), false, &fd); + assert(found, "Field not found"); + assert(fd.is_flat(), "Field must be flat"); +#endif // ASSERT + + oop res = field_vklass->read_payload_from_addr(obj_h(), entry->field_offset(), layout_info->kind(), CHECK); + current->set_vm_result_oop(res); +JRT_END + +JRT_ENTRY(void, InterpreterRuntime::write_flat_field(JavaThread* current, oopDesc* obj, oopDesc* value, ResolvedFieldEntry* entry)) + assert(oopDesc::is_oop(obj), "Sanity check"); + Handle obj_h(THREAD, obj); + assert(value == nullptr || oopDesc::is_oop(value), "Sanity check"); + Handle val_h(THREAD, value); + + InstanceKlass* holder = entry->field_holder(); + InlineLayoutInfo* li = holder->inline_layout_info_adr(entry->field_index()); + InlineKlass* vk = li->klass(); + vk->write_value_to_addr(val_h(), ((char*)(oopDesc*)obj_h()) + entry->field_offset(), li->kind(), true, CHECK); +JRT_END JRT_ENTRY(void, InterpreterRuntime::newarray(JavaThread* current, BasicType type, jint size)) oop obj = oopFactory::new_typeArray(type, size, CHECK); @@ -234,17 +271,29 @@ JRT_END JRT_ENTRY(void, InterpreterRuntime::anewarray(JavaThread* current, ConstantPool* pool, int index, jint size)) Klass* klass = pool->klass_at(index, CHECK); - objArrayOop obj = oopFactory::new_objArray(klass, size, CHECK); + arrayOop obj = oopFactory::new_objArray(klass, size, CHECK); current->set_vm_result_oop(obj); JRT_END +JRT_ENTRY(void, InterpreterRuntime::flat_array_load(JavaThread* current, arrayOopDesc* array, int index)) + assert(array->is_flatArray(), "Must be"); + flatArrayOop farray = (flatArrayOop)array; + oop res = farray->obj_at(index, CHECK); + current->set_vm_result_oop(res); +JRT_END + +JRT_ENTRY(void, InterpreterRuntime::flat_array_store(JavaThread* current, oopDesc* val, arrayOopDesc* array, int index)) + assert(array->is_flatArray(), "Must be"); + flatArrayOop farray = (flatArrayOop)array; + farray->obj_at_put(index, val, CHECK); +JRT_END JRT_ENTRY(void, InterpreterRuntime::multianewarray(JavaThread* current, jint* first_size_address)) // We may want to pass in more arguments - could make this slightly faster LastFrameAccessor last_frame(current); ConstantPool* constants = last_frame.method()->constants(); - int i = last_frame.get_index_u2(Bytecodes::_multianewarray); - Klass* klass = constants->klass_at(i, CHECK); + int i = last_frame.get_index_u2(Bytecodes::_multianewarray); + Klass* klass = constants->klass_at(i, CHECK); int nof_dims = last_frame.number_of_dimensions(); assert(klass->is_klass(), "not a class"); assert(nof_dims >= 1, "multianewarray rank must be nonzero"); @@ -273,6 +322,30 @@ JRT_ENTRY(void, InterpreterRuntime::register_finalizer(JavaThread* current, oopD InstanceKlass::register_finalizer(instanceOop(obj), CHECK); JRT_END +JRT_ENTRY(jboolean, InterpreterRuntime::is_substitutable(JavaThread* current, oopDesc* aobj, oopDesc* bobj)) + assert(oopDesc::is_oop(aobj) && oopDesc::is_oop(bobj), "must be valid oops"); + + Handle ha(THREAD, aobj); + Handle hb(THREAD, bobj); + JavaValue result(T_BOOLEAN); + JavaCallArguments args; + args.push_oop(ha); + args.push_oop(hb); + methodHandle method(current, Universe::is_substitutable_method()); + method->method_holder()->initialize(CHECK_false); // Ensure class ValueObjectMethods is initialized + JavaCalls::call(&result, method, &args, THREAD); + if (HAS_PENDING_EXCEPTION) { + // Something really bad happened because isSubstitutable() should not throw exceptions + // If it is an error, just let it propagate + // If it is an exception, wrap it into an InternalError + if (!PENDING_EXCEPTION->is_a(vmClasses::Error_klass())) { + Handle e(THREAD, PENDING_EXCEPTION); + CLEAR_PENDING_EXCEPTION; + THROW_MSG_CAUSE_(vmSymbols::java_lang_InternalError(), "Internal error in substitutability test", e, false); + } + } + return result.get_jboolean(); +JRT_END // Quicken instance-of and check-cast bytecodes JRT_ENTRY(void, InterpreterRuntime::quicken_io_cc(JavaThread* current)) @@ -620,6 +693,10 @@ JRT_ENTRY(void, InterpreterRuntime::throw_AbstractMethodErrorVerbose(JavaThread* LinkResolver::throw_abstract_method_error(mh, recvKlass, THREAD); JRT_END +JRT_ENTRY(void, InterpreterRuntime::throw_InstantiationError(JavaThread* current)) + THROW(vmSymbols::java_lang_InstantiationError()); +JRT_END + JRT_ENTRY(void, InterpreterRuntime::throw_IncompatibleClassChangeError(JavaThread* current)) THROW(vmSymbols::java_lang_IncompatibleClassChangeError()); @@ -699,19 +776,40 @@ void InterpreterRuntime::resolve_get_put(Bytecodes::Code bytecode, int field_ind bool uninitialized_static = is_static && !klass->is_initialized(); bool has_initialized_final_update = info.field_holder()->major_version() >= 53 && info.has_initialized_final_update(); + bool strict_static_final = info.is_strict() && info.is_static() && info.is_final(); assert(!(has_initialized_final_update && !info.access_flags().is_final()), "Fields with initialized final updates must be final"); Bytecodes::Code get_code = (Bytecodes::Code)0; Bytecodes::Code put_code = (Bytecodes::Code)0; if (!uninitialized_static) { - get_code = ((is_static) ? Bytecodes::_getstatic : Bytecodes::_getfield); + if (is_static) { + get_code = Bytecodes::_getstatic; + } else { + get_code = Bytecodes::_getfield; + } if ((is_put && !has_initialized_final_update) || !info.access_flags().is_final()) { - put_code = ((is_static) ? Bytecodes::_putstatic : Bytecodes::_putfield); + put_code = ((is_static) ? Bytecodes::_putstatic : Bytecodes::_putfield); } + assert(!info.is_strict_static_unset(), "after initialization, no unset flags"); + } else if (is_static && (info.is_strict_static_unset() || strict_static_final)) { + // During , closely track the state of strict statics. + // 1. if we are reading an uninitialized strict static, throw + // 2. if we are writing one, clear the "unset" flag + // + // Note: If we were handling an attempted write of a null to a + // null-restricted strict static, we would NOT clear the "unset" + // flag. + assert(klass->is_being_initialized(), "else should have thrown"); + assert(klass->is_reentrant_initialization(THREAD), + " must be running in current thread"); + klass->notify_strict_static_access(info.index(), is_put, CHECK); } ResolvedFieldEntry* entry = pool->resolved_field_entry_at(field_index); - entry->set_flags(info.access_flags().is_final(), info.access_flags().is_volatile()); + entry->set_flags(info.access_flags().is_final(), info.access_flags().is_volatile(), + info.is_flat(), info.is_null_free_inline_type(), + info.has_null_marker()); + entry->fill_in(info.field_holder(), info.offset(), checked_cast(info.index()), checked_cast(state), static_cast(get_code), static_cast(put_code)); @@ -758,12 +856,10 @@ JRT_LEAF(void, InterpreterRuntime::monitorexit(BasicObjectLock* elem)) elem->set_obj(nullptr); JRT_END - JRT_ENTRY(void, InterpreterRuntime::throw_illegal_monitor_state_exception(JavaThread* current)) THROW(vmSymbols::java_lang_IllegalMonitorStateException()); JRT_END - JRT_ENTRY(void, InterpreterRuntime::new_illegal_monitor_state_exception(JavaThread* current)) // Returns an illegal exception to install into the current thread. The // pending_exception flag is cleared so normal exception handling does not @@ -778,6 +874,21 @@ JRT_ENTRY(void, InterpreterRuntime::new_illegal_monitor_state_exception(JavaThre current->set_vm_result_oop(exception()); JRT_END +JRT_ENTRY(void, InterpreterRuntime::throw_identity_exception(JavaThread* current, oopDesc* obj)) + Klass* klass = cast_to_oop(obj)->klass(); + ResourceMark rm(THREAD); + const char* desc = "Cannot synchronize on an instance of value class "; + const char* className = klass->external_name(); + size_t msglen = strlen(desc) + strlen(className) + 1; + char* message = NEW_RESOURCE_ARRAY(char, msglen); + if (nullptr == message) { + // Out of memory: can't create detailed error message + THROW_MSG(vmSymbols::java_lang_IdentityException(), className); + } else { + jio_snprintf(message, msglen, "%s%s", desc, className); + THROW_MSG(vmSymbols::java_lang_IdentityException(), message); + } +JRT_END //------------------------------------------------------------------------------------------------------------------------ // Invokes @@ -1188,6 +1299,7 @@ JRT_END JRT_ENTRY(void, InterpreterRuntime::post_field_access(JavaThread* current, oopDesc* obj, ResolvedFieldEntry *entry)) + assert(entry->is_valid(), "Invalid ResolvedFieldEntry"); // check the access_flags for the field in the klass InstanceKlass* ik = entry->field_holder(); @@ -1195,6 +1307,7 @@ JRT_ENTRY(void, InterpreterRuntime::post_field_access(JavaThread* current, oopDe if (!ik->field_status(index).is_access_watched()) return; bool is_static = (obj == nullptr); + bool is_flat = entry->is_flat(); HandleMark hm(current); Handle h_obj; @@ -1203,7 +1316,7 @@ JRT_ENTRY(void, InterpreterRuntime::post_field_access(JavaThread* current, oopDe h_obj = Handle(current, obj); } InstanceKlass* field_holder = entry->field_holder(); // HERE - jfieldID fid = jfieldIDWorkaround::to_jfieldID(field_holder, entry->field_offset(), is_static); + jfieldID fid = jfieldIDWorkaround::to_jfieldID(field_holder, entry->field_offset(), is_static, is_flat); LastFrameAccessor last_frame(current); JvmtiExport::post_field_access(current, last_frame.method(), last_frame.bcp(), field_holder, h_obj, fid); JRT_END @@ -1211,6 +1324,7 @@ JRT_END JRT_ENTRY(void, InterpreterRuntime::post_field_modification(JavaThread* current, oopDesc* obj, ResolvedFieldEntry *entry, jvalue *value)) + assert(entry->is_valid(), "Invalid ResolvedFieldEntry"); InstanceKlass* ik = entry->field_holder(); // check the access_flags for the field in the klass @@ -1232,10 +1346,12 @@ JRT_ENTRY(void, InterpreterRuntime::post_field_modification(JavaThread* current, case dtos: sig_type = JVM_SIGNATURE_DOUBLE; break; default: ShouldNotReachHere(); return; } + bool is_static = (obj == nullptr); + bool is_flat = entry->is_flat(); HandleMark hm(current); - jfieldID fid = jfieldIDWorkaround::to_jfieldID(ik, entry->field_offset(), is_static); + jfieldID fid = jfieldIDWorkaround::to_jfieldID(ik, entry->field_offset(), is_static, is_flat); jvalue fvalue; #ifdef _LP64 fvalue = *value; diff --git a/src/hotspot/share/interpreter/interpreterRuntime.hpp b/src/hotspot/share/interpreter/interpreterRuntime.hpp index 3cc7a938a6a..05b3fc90f0c 100644 --- a/src/hotspot/share/interpreter/interpreterRuntime.hpp +++ b/src/hotspot/share/interpreter/interpreterRuntime.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -58,9 +58,17 @@ class InterpreterRuntime: AllStatic { // Allocation static void _new (JavaThread* current, ConstantPool* pool, int index); static void newarray (JavaThread* current, BasicType type, jint size); - static void anewarray (JavaThread* current, ConstantPool* pool, int index, jint size); + static void anewarray (JavaThread* threcurrentad, ConstantPool* pool, int index, jint size); static void multianewarray(JavaThread* current, jint* first_size_address); static void register_finalizer(JavaThread* current, oopDesc* obj); + static void write_heap_copy (JavaThread* current, oopDesc* value, int offset, oopDesc* rcv); + static void read_flat_field(JavaThread* current, oopDesc* object, ResolvedFieldEntry* entry); + static void write_flat_field(JavaThread* current, oopDesc* object, oopDesc* value, ResolvedFieldEntry* entry); + + static void flat_array_load(JavaThread* current, arrayOopDesc* array, int index); + static void flat_array_store(JavaThread* current, oopDesc* val, arrayOopDesc* array, int index); + + static jboolean is_substitutable(JavaThread* current, oopDesc* aobj, oopDesc* bobj); // Quicken instance-of and check-cast bytecodes static void quicken_io_cc(JavaThread* current); @@ -72,6 +80,7 @@ class InterpreterRuntime: AllStatic { Klass* recvKlass, Method* missingMethod); + static void throw_InstantiationError(JavaThread* current); static void throw_IncompatibleClassChangeError(JavaThread* current); static void throw_IncompatibleClassChangeErrorVerbose(JavaThread* current, Klass* resc, @@ -121,6 +130,7 @@ class InterpreterRuntime: AllStatic { static void throw_illegal_monitor_state_exception(JavaThread* current); static void new_illegal_monitor_state_exception(JavaThread* current); + static void throw_identity_exception(JavaThread* current, oopDesc* obj); // Breakpoints static void _breakpoint(JavaThread* current, Method* method, address bcp); diff --git a/src/hotspot/share/interpreter/linkResolver.cpp b/src/hotspot/share/interpreter/linkResolver.cpp index 22199baef8e..f37e3e1e0f9 100644 --- a/src/hotspot/share/interpreter/linkResolver.cpp +++ b/src/hotspot/share/interpreter/linkResolver.cpp @@ -1001,7 +1001,8 @@ void LinkResolver::resolve_field(fieldDescriptor& fd, (byte == Bytecodes::_nop && !link_info.check_access()), "bad field access bytecode"); bool is_static = (byte == Bytecodes::_getstatic || byte == Bytecodes::_putstatic); - bool is_put = (byte == Bytecodes::_putfield || byte == Bytecodes::_putstatic || byte == Bytecodes::_nofast_putfield); + bool is_put = (byte == Bytecodes::_putfield || byte == Bytecodes::_putstatic || + byte == Bytecodes::_nofast_putfield); // Check if there's a resolved klass containing the field Klass* resolved_klass = link_info.resolved_klass(); Symbol* field = link_info.name(); @@ -1044,8 +1045,8 @@ void LinkResolver::resolve_field(fieldDescriptor& fd, ResourceMark rm(THREAD); stringStream ss; ss.print("Update to %s final field %s.%s attempted from a different class (%s) than the field's declaring class", - is_static ? "static" : "non-static", resolved_klass->external_name(), fd.name()->as_C_string(), - current_klass->external_name()); + is_static ? "static" : "non-static", resolved_klass->external_name(), fd.name()->as_C_string(), + current_klass->external_name()); THROW_MSG(vmSymbols::java_lang_IllegalAccessError(), ss.as_string()); } @@ -1054,10 +1055,10 @@ void LinkResolver::resolve_field(fieldDescriptor& fd, assert(m != nullptr, "information about the current method must be available for 'put' bytecodes"); bool is_initialized_static_final_update = (byte == Bytecodes::_putstatic && fd.is_static() && - !m->is_static_initializer()); + !m->is_class_initializer()); bool is_initialized_instance_final_update = ((byte == Bytecodes::_putfield || byte == Bytecodes::_nofast_putfield) && !fd.is_static() && - !m->is_object_initializer()); + !m->is_object_constructor()); if (is_initialized_static_final_update || is_initialized_instance_final_update) { ResourceMark rm(THREAD); @@ -1182,6 +1183,8 @@ Method* LinkResolver::linktime_resolve_special_method(const LinkInfo& link_info, } // check if method name is , that it is found in same klass as static type + // Since this method is never inherited from a super, any appearance here under + // the wrong class would be an error. if (resolved_method->name() == vmSymbols::object_initializer_name() && resolved_method->method_holder() != resolved_klass) { ResourceMark rm(THREAD); @@ -1248,7 +1251,7 @@ void LinkResolver::runtime_resolve_special_method(CallInfo& result, methodHandle sel_method(THREAD, resolved_method()); if (link_info.check_access() && - // check if the method is not + // check if the method is not , which is never inherited resolved_method->name() != vmSymbols::object_initializer_name()) { Klass* current_klass = link_info.current_klass(); @@ -1712,20 +1715,21 @@ void LinkResolver::resolve_invoke(CallInfo& result, Handle recv, const constantP } void LinkResolver::resolve_invoke(CallInfo& result, Handle& recv, - const methodHandle& attached_method, - Bytecodes::Code byte, TRAPS) { + const methodHandle& attached_method, + Bytecodes::Code byte, bool check_null_and_abstract, TRAPS) { Klass* defc = attached_method->method_holder(); Symbol* name = attached_method->name(); Symbol* type = attached_method->signature(); LinkInfo link_info(defc, name, type); + Klass* recv_klass = recv.is_null() ? defc : recv->klass(); switch(byte) { case Bytecodes::_invokevirtual: - resolve_virtual_call(result, recv, recv->klass(), link_info, - /*check_null_and_abstract=*/true, CHECK); + resolve_virtual_call(result, recv, recv_klass, link_info, + check_null_and_abstract, CHECK); break; case Bytecodes::_invokeinterface: - resolve_interface_call(result, recv, recv->klass(), link_info, - /*check_null_and_abstract=*/true, CHECK); + resolve_interface_call(result, recv, recv_klass, link_info, + check_null_and_abstract, CHECK); break; case Bytecodes::_invokestatic: resolve_static_call(result, link_info, /*initialize_class=*/false, CHECK); diff --git a/src/hotspot/share/interpreter/linkResolver.hpp b/src/hotspot/share/interpreter/linkResolver.hpp index 69bdf56137d..f7463aa6d03 100644 --- a/src/hotspot/share/interpreter/linkResolver.hpp +++ b/src/hotspot/share/interpreter/linkResolver.hpp @@ -357,7 +357,7 @@ class LinkResolver: AllStatic { // runtime resolving from attached method static void resolve_invoke(CallInfo& result, Handle& recv, const methodHandle& attached_method, - Bytecodes::Code byte, TRAPS); + Bytecodes::Code byte, bool check_null_and_abstract, TRAPS); // Only resolved method known. static void throw_abstract_method_error(const methodHandle& resolved_method, TRAPS) { diff --git a/src/hotspot/share/interpreter/oopMapCache.cpp b/src/hotspot/share/interpreter/oopMapCache.cpp index e577ce42c1e..50fc03eb6e1 100644 --- a/src/hotspot/share/interpreter/oopMapCache.cpp +++ b/src/hotspot/share/interpreter/oopMapCache.cpp @@ -281,7 +281,7 @@ bool OopMapCacheEntry::verify_mask(CellTypeState* vars, CellTypeState* stack, in if (log) st.print("Locals (%d): ", max_locals); for(int i = 0; i < max_locals; i++) { bool v1 = is_oop(i) ? true : false; - bool v2 = vars[i].is_reference() ? true : false; + bool v2 = vars[i].is_reference(); assert(v1 == v2, "locals oop mask generation error"); if (log) st.print("%d", v1 ? 1 : 0); } @@ -290,7 +290,7 @@ bool OopMapCacheEntry::verify_mask(CellTypeState* vars, CellTypeState* stack, in if (log) st.print("Stack (%d): ", stack_top); for(int j = 0; j < stack_top; j++) { bool v1 = is_oop(max_locals + j) ? true : false; - bool v2 = stack[j].is_reference() ? true : false; + bool v2 = stack[j].is_reference(); assert(v1 == v2, "stack oop mask generation error"); if (log) st.print("%d", v1 ? 1 : 0); } @@ -374,7 +374,7 @@ void OopMapCacheEntry::set_mask(CellTypeState *vars, CellTypeState *stack, int s } // set oop bit - if ( cell->is_reference()) { + if (cell->is_reference()) { value |= (mask << oop_bit_number ); _num_oops++; } diff --git a/src/hotspot/share/interpreter/rewriter.cpp b/src/hotspot/share/interpreter/rewriter.cpp index 41a96eebfad..0600b741c78 100644 --- a/src/hotspot/share/interpreter/rewriter.cpp +++ b/src/hotspot/share/interpreter/rewriter.cpp @@ -454,11 +454,11 @@ void Rewriter::scan_method(Thread* thread, Method* method, bool reverse, bool* i if (klass->find_field(field_name, field_sig, &fd) != nullptr) { if (fd.access_flags().is_final()) { if (fd.access_flags().is_static()) { - if (!method->is_static_initializer()) { + if (!method->is_class_initializer()) { fd.set_has_initialized_final_update(true); } } else { - if (!method->is_object_initializer()) { + if (!method->is_object_constructor()) { fd.set_has_initialized_final_update(true); } } diff --git a/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp b/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp index 96f53c517a3..9e8d6fa782f 100644 --- a/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp +++ b/src/hotspot/share/interpreter/templateInterpreterGenerator.cpp @@ -190,6 +190,7 @@ void TemplateInterpreterGenerator::generate_all() { method_entry(getter) method_entry(setter) method_entry(abstract) + method_entry(object_init) method_entry(java_lang_math_sin ) method_entry(java_lang_math_cos ) method_entry(java_lang_math_tan ) @@ -417,6 +418,7 @@ address TemplateInterpreterGenerator::generate_method_entry( case Interpreter::empty : break; case Interpreter::getter : break; case Interpreter::setter : break; + case Interpreter::object_init : break; case Interpreter::abstract : entry_point = generate_abstract_entry(); break; default: entry_point = generate_intrinsic_entry(kind); // process the rest @@ -429,14 +431,17 @@ address TemplateInterpreterGenerator::generate_method_entry( // We expect the normal and native entry points to be generated first so we can reuse them. if (native) { + assert(kind != Interpreter::object_init, "Not supported"); entry_point = Interpreter::entry_for_kind(synchronized ? Interpreter::native_synchronized : Interpreter::native); if (entry_point == nullptr) { entry_point = generate_native_entry(synchronized); } } else { - entry_point = Interpreter::entry_for_kind(synchronized ? Interpreter::zerolocals_synchronized : Interpreter::zerolocals); + entry_point = kind == Interpreter::object_init ? + Interpreter::entry_for_kind(Interpreter::object_init) : + Interpreter::entry_for_kind(synchronized ? Interpreter::zerolocals_synchronized : Interpreter::zerolocals); if (entry_point == nullptr) { - entry_point = generate_normal_entry(synchronized); + entry_point = generate_normal_entry(synchronized, kind == Interpreter::object_init); } } diff --git a/src/hotspot/share/interpreter/templateInterpreterGenerator.hpp b/src/hotspot/share/interpreter/templateInterpreterGenerator.hpp index 07a7ca6169d..74da0824fb8 100644 --- a/src/hotspot/share/interpreter/templateInterpreterGenerator.hpp +++ b/src/hotspot/share/interpreter/templateInterpreterGenerator.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -90,7 +90,7 @@ class TemplateInterpreterGenerator: public AbstractInterpreterGenerator { // generate intrinsic method entries address generate_intrinsic_entry(AbstractInterpreter::MethodKind kind); - address generate_normal_entry(bool synchronized); + address generate_normal_entry(bool synchronized, bool object_init); address generate_native_entry(bool synchronized); address generate_abstract_entry(void); address generate_math_entry(AbstractInterpreter::MethodKind kind); diff --git a/src/hotspot/share/interpreter/templateTable.cpp b/src/hotspot/share/interpreter/templateTable.cpp index 55dd0bb8e0f..94b6d68dadc 100644 --- a/src/hotspot/share/interpreter/templateTable.cpp +++ b/src/hotspot/share/interpreter/templateTable.cpp @@ -158,7 +158,8 @@ void TemplateTable::jsr() { // Implementation of TemplateTable: Debugging void TemplateTable::transition(TosState tos_in, TosState tos_out) { - assert(_desc->tos_in() == tos_in , "inconsistent tos_in information"); + assert(_desc->tos_in() == tos_in, + "inconsistent tos_in information"); assert(_desc->tos_out() == tos_out, "inconsistent tos_out information"); } @@ -229,6 +230,7 @@ void TemplateTable::initialize() { const int disp = 1 << Template::does_dispatch_bit; const int clvm = 1 << Template::calls_vm_bit; const int iswd = 1 << Template::wide_bit; + // interpr. templates // Java spec bytecodes ubcp|disp|clvm|iswd in out generator argument def(Bytecodes::_nop , ____|____|____|____, vtos, vtos, nop , _ ); @@ -281,7 +283,7 @@ void TemplateTable::initialize() { def(Bytecodes::_laload , ____|____|____|____, itos, ltos, laload , _ ); def(Bytecodes::_faload , ____|____|____|____, itos, ftos, faload , _ ); def(Bytecodes::_daload , ____|____|____|____, itos, dtos, daload , _ ); - def(Bytecodes::_aaload , ____|____|____|____, itos, atos, aaload , _ ); + def(Bytecodes::_aaload , ____|____|clvm|____, itos, atos, aaload , _ ); def(Bytecodes::_baload , ____|____|____|____, itos, itos, baload , _ ); def(Bytecodes::_caload , ____|____|____|____, itos, itos, caload , _ ); def(Bytecodes::_saload , ____|____|____|____, itos, itos, saload , _ ); @@ -433,6 +435,7 @@ void TemplateTable::initialize() { def(Bytecodes::_ifnonnull , ubcp|____|clvm|____, atos, vtos, if_nullcmp , not_equal ); def(Bytecodes::_goto_w , ubcp|____|clvm|____, vtos, vtos, goto_w , _ ); def(Bytecodes::_jsr_w , ubcp|____|____|____, vtos, vtos, jsr_w , _ ); + def(Bytecodes::_breakpoint , ubcp|disp|clvm|____, vtos, vtos, _breakpoint , _ ); // wide Java spec bytecodes def(Bytecodes::_iload , ubcp|____|____|iswd, vtos, itos, wide_iload , _ ); @@ -451,6 +454,7 @@ void TemplateTable::initialize() { // JVM bytecodes def(Bytecodes::_fast_agetfield , ubcp|____|____|____, atos, atos, fast_accessfield , atos ); + def(Bytecodes::_fast_vgetfield , ubcp|____|clvm|____, atos, atos, fast_accessfield , atos ); def(Bytecodes::_fast_bgetfield , ubcp|____|____|____, atos, itos, fast_accessfield , itos ); def(Bytecodes::_fast_cgetfield , ubcp|____|____|____, atos, itos, fast_accessfield , itos ); def(Bytecodes::_fast_dgetfield , ubcp|____|____|____, atos, dtos, fast_accessfield , dtos ); @@ -460,6 +464,7 @@ void TemplateTable::initialize() { def(Bytecodes::_fast_sgetfield , ubcp|____|____|____, atos, itos, fast_accessfield , itos ); def(Bytecodes::_fast_aputfield , ubcp|____|____|____, atos, vtos, fast_storefield , atos ); + def(Bytecodes::_fast_vputfield , ubcp|____|clvm|____, atos, vtos, fast_storefield , atos ); def(Bytecodes::_fast_bputfield , ubcp|____|____|____, itos, vtos, fast_storefield , itos ); def(Bytecodes::_fast_zputfield , ubcp|____|____|____, itos, vtos, fast_storefield , itos ); def(Bytecodes::_fast_cputfield , ubcp|____|____|____, itos, vtos, fast_storefield , itos ); @@ -496,6 +501,7 @@ void TemplateTable::initialize() { def(Bytecodes::_nofast_aload_0 , ____|____|clvm|____, vtos, atos, nofast_aload_0 , _ ); def(Bytecodes::_nofast_iload , ubcp|____|clvm|____, vtos, itos, nofast_iload , _ ); + def(Bytecodes::_shouldnotreachhere , ____|____|____|____, vtos, vtos, shouldnotreachhere , _ ); } diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp index ddc9d59b295..0febece152e 100644 --- a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp +++ b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampling.cpp @@ -212,10 +212,12 @@ static bool compute_top_frame(const JfrSampleRequest& request, frame& top_frame, // We also do not have to worry about stackbanging because we currently have a huge SafepointBlob stub frame // on the stack. For extra assurance, we know that we can create this frame size at this // very location because we just popped such a frame before we hit the return poll site. + // For frames that need stack repair we skip this trick. This is because the stack walking code reads + // the frame size from the stack, but the memory has already been overwritten by the SafepointBlob. // // Let's attempt to correct for the safepoint bias. const PcDesc* const pc_desc = get_pc_desc(sampled_nm, sampled_pc); - if (is_valid(pc_desc)) { + if (is_valid(pc_desc) && !sampled_nm->needs_stack_repair()) { intptr_t* const synthetic_sp = sender_sp - sampled_nm->frame_size(); top_frame = frame(synthetic_sp, synthetic_sp, sender_sp, pc_desc->real_pc(sampled_nm), sampled_nm); in_continuation = is_in_continuation(top_frame, jt); diff --git a/src/hotspot/share/jvmci/jvmciCodeInstaller.cpp b/src/hotspot/share/jvmci/jvmciCodeInstaller.cpp index 3a9fbc54bf9..2ac960c78fb 100644 --- a/src/hotspot/share/jvmci/jvmciCodeInstaller.cpp +++ b/src/hotspot/share/jvmci/jvmciCodeInstaller.cpp @@ -1182,10 +1182,11 @@ void CodeInstaller::record_scope(jint pc_offset, HotSpotCompiledCodeStream* stre } // has_ea_local_in_scope and arg_escape should be added to JVMCI + const bool return_scalarized = false; const bool has_ea_local_in_scope = false; const bool arg_escape = false; _debug_recorder->describe_scope(pc_offset, method, nullptr, bci, reexecute, rethrow_exception, is_mh_invoke, return_oop, - has_ea_local_in_scope, arg_escape, + return_scalarized, has_ea_local_in_scope, arg_escape, locals_token, stack_token, monitors_token); } } @@ -1329,6 +1330,8 @@ void CodeInstaller::site_Mark(CodeBuffer& buffer, jint pc_offset, HotSpotCompile break; case VERIFIED_ENTRY: _offsets.set_value(CodeOffsets::Verified_Entry, pc_offset); + _offsets.set_value(CodeOffsets::Verified_Inline_Entry, pc_offset); + _offsets.set_value(CodeOffsets::Verified_Inline_Entry_RO, pc_offset); break; case OSR_ENTRY: _offsets.set_value(CodeOffsets::OSR_Entry, pc_offset); diff --git a/src/hotspot/share/jvmci/jvmciCompiler.cpp b/src/hotspot/share/jvmci/jvmciCompiler.cpp index 659297973e5..07f85a214b1 100644 --- a/src/hotspot/share/jvmci/jvmciCompiler.cpp +++ b/src/hotspot/share/jvmci/jvmciCompiler.cpp @@ -86,7 +86,10 @@ void JVMCICompiler::bootstrap(TRAPS) { int len = objectMethods->length(); for (int i = 0; i < len; i++) { methodHandle mh(THREAD, objectMethods->at(i)); - if (!mh->is_native() && !mh->is_static() && !mh->is_object_initializer() && !mh->is_static_initializer()) { + if (!mh->is_native() && + !mh->is_static() && + !mh->is_object_constructor() && + !mh->is_class_initializer()) { ResourceMark rm; int hot_count = 10; // TODO: what's the appropriate value? CompileBroker::compile_method(mh, InvocationEntryBci, CompLevel_full_optimization, hot_count, CompileTask::Reason_Bootstrap, CHECK); diff --git a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp index dbc3ebd9c6e..e15b2073487 100644 --- a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp +++ b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp @@ -1627,7 +1627,7 @@ C2V_VMENTRY_NULL(jobject, iterateFrames, (JNIEnv* env, jobject compilerToVM, job if (objects != nullptr) { RegisterMap reg_map(vf->register_map()); bool realloc_failures = Deoptimization::realloc_objects(thread, vf->frame_pointer(), ®_map, objects, CHECK_NULL); - Deoptimization::reassign_fields(vf->frame_pointer(), ®_map, objects, realloc_failures, false); + Deoptimization::reassign_fields(vf->frame_pointer(), ®_map, objects, realloc_failures, false, CHECK_NULL); realloc_called = true; } @@ -1878,7 +1878,7 @@ C2V_VMENTRY(void, materializeVirtualObjects, (JNIEnv* env, jobject, jobject _hs_ } bool realloc_failures = Deoptimization::realloc_objects(thread, fstAfterDeopt.current(), fstAfterDeopt.register_map(), objects, CHECK); - Deoptimization::reassign_fields(fstAfterDeopt.current(), fstAfterDeopt.register_map(), objects, realloc_failures, false); + Deoptimization::reassign_fields(fstAfterDeopt.current(), fstAfterDeopt.register_map(), objects, realloc_failures, false, THREAD); for (int frame_index = 0; frame_index < virtualFrames->length(); frame_index++) { compiledVFrame* cvf = virtualFrames->at(frame_index); @@ -2206,7 +2206,7 @@ C2V_VMENTRY_NULL(jobjectArray, getDeclaredConstructors, (JNIEnv* env, jobject, A GrowableArray constructors_array; for (int i = 0; i < iklass->methods()->length(); i++) { Method* m = iklass->methods()->at(i); - if (m->is_object_initializer()) { + if (m->is_object_constructor()) { constructors_array.append(m); } } @@ -2233,7 +2233,7 @@ C2V_VMENTRY_NULL(jobjectArray, getDeclaredMethods, (JNIEnv* env, jobject, ARGUME GrowableArray methods_array; for (int i = 0; i < iklass->methods()->length(); i++) { Method* m = iklass->methods()->at(i); - if (!m->is_object_initializer() && !m->is_static_initializer() && !m->is_overpass()) { + if (!(m->is_object_constructor() || m->is_class_initializer()) && !m->is_overpass()) { methods_array.append(m); } } @@ -2973,11 +2973,12 @@ C2V_VMENTRY_NULL(jobject, asReflectionExecutable, (JNIEnv* env, jobject, ARGUMEN requireInHotSpot("asReflectionExecutable", JVMCI_CHECK_NULL); methodHandle m(THREAD, UNPACK_PAIR(Method, method)); oop executable; - if (m->is_object_initializer()) { + if (m->is_class_initializer()) { + JVMCI_THROW_MSG_NULL(IllegalArgumentException, + "Cannot create java.lang.reflect.Method for class initializer"); + } + else if (m->is_object_constructor()) { executable = Reflection::new_constructor(m, CHECK_NULL); - } else if (m->is_static_initializer()) { - JVMCI_THROW_MSG_NULL(IllegalArgumentException, - "Cannot create java.lang.reflect.Method for class initializer"); } else { executable = Reflection::new_method(m, false, CHECK_NULL); } diff --git a/src/hotspot/share/jvmci/vmStructs_jvmci.cpp b/src/hotspot/share/jvmci/vmStructs_jvmci.cpp index 737718096c5..716f8eda8c6 100644 --- a/src/hotspot/share/jvmci/vmStructs_jvmci.cpp +++ b/src/hotspot/share/jvmci/vmStructs_jvmci.cpp @@ -221,7 +221,7 @@ nonstatic_field(InstanceKlass, _constants, ConstantPool*) \ volatile_nonstatic_field(InstanceKlass, _init_state, InstanceKlass::ClassState) \ volatile_nonstatic_field(InstanceKlass, _init_thread, JavaThread*) \ - nonstatic_field(InstanceKlass, _misc_flags._flags, u2) \ + nonstatic_field(InstanceKlass, _misc_flags._flags, u4) \ nonstatic_field(InstanceKlass, _annotations, Annotations*) \ \ volatile_nonstatic_field(JavaFrameAnchor, _last_Java_sp, intptr_t*) \ @@ -735,6 +735,9 @@ declare_constant(DataLayout::virtual_call_type_data_tag) \ declare_constant(DataLayout::parameters_type_data_tag) \ declare_constant(DataLayout::speculative_trap_data_tag) \ + declare_constant(DataLayout::array_store_data_tag) \ + declare_constant(DataLayout::array_load_data_tag) \ + declare_constant(DataLayout::acmp_data_tag) \ \ declare_constant(Deoptimization::Unpack_deopt) \ declare_constant(Deoptimization::Unpack_exception) \ @@ -819,7 +822,7 @@ declare_constant(Klass::_lh_header_size_mask) \ declare_constant(Klass::_lh_array_tag_shift) \ declare_constant(Klass::_lh_array_tag_type_value) \ - declare_constant(Klass::_lh_array_tag_obj_value) \ + declare_constant(Klass::_lh_array_tag_ref_value) \ \ declare_constant(markWord::no_hash) \ \ diff --git a/src/hotspot/share/logging/logTag.hpp b/src/hotspot/share/logging/logTag.hpp index 6d0bd117ad9..f2f35e0a045 100644 --- a/src/hotspot/share/logging/logTag.hpp +++ b/src/hotspot/share/logging/logTag.hpp @@ -161,6 +161,7 @@ class outputStream; LOG_TAG(plab) \ LOG_TAG(placeholders) \ LOG_TAG(preempt) \ + LOG_TAG(preload) /* Trace successfull class preloading */ \ LOG_TAG(preorder) /* Trace all classes loaded in order referenced (not loaded) */ \ LOG_TAG(preview) /* Trace loading of preview feature types */ \ LOG_TAG(promotion) \ @@ -215,6 +216,7 @@ class outputStream; NOT_PRODUCT(LOG_TAG(upcall)) \ LOG_TAG(update) \ LOG_TAG(valuebasedclasses) \ + LOG_TAG(valuetypes) \ LOG_TAG(verification) \ LOG_TAG(verify) \ LOG_TAG(vmatree) \ diff --git a/src/hotspot/share/memory/allocation.hpp b/src/hotspot/share/memory/allocation.hpp index 35180fdba5e..29c29c2f8b5 100644 --- a/src/hotspot/share/memory/allocation.hpp +++ b/src/hotspot/share/memory/allocation.hpp @@ -314,6 +314,7 @@ class MetaspaceObj { f(ConstantPoolCache) \ f(Annotations) \ f(MethodCounters) \ + f(InlineLayoutInfo) \ f(RecordComponent) \ f(KlassTrainingData) \ f(MethodTrainingData) \ diff --git a/src/hotspot/share/memory/heapInspection.cpp b/src/hotspot/share/memory/heapInspection.cpp index 4d7e6c9369c..c2ad92cc316 100644 --- a/src/hotspot/share/memory/heapInspection.cpp +++ b/src/hotspot/share/memory/heapInspection.cpp @@ -33,8 +33,12 @@ #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "nmt/memTracker.hpp" +#include "oops/fieldInfo.hpp" +#include "oops/fieldStreams.inline.hpp" +#include "oops/inlineKlass.inline.hpp" #include "oops/oop.inline.hpp" #include "runtime/atomic.hpp" +#include "runtime/fieldDescriptor.inline.hpp" #include "runtime/os.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" @@ -509,6 +513,130 @@ class HistoClosure : public KlassInfoClosure { } }; + +class FindClassByNameClosure : public KlassInfoClosure { + private: + GrowableArray* _klasses; + Symbol* _classname; + public: + FindClassByNameClosure(GrowableArray* klasses, Symbol* classname) : + _klasses(klasses), _classname(classname) { } + + void do_cinfo(KlassInfoEntry* cie) { + if (cie->klass()->name() == _classname) { + _klasses->append(cie->klass()); + } + } +}; + +class FieldDesc { +private: + Symbol* _name; + Symbol* _signature; + int _offset; + int _index; + InstanceKlass* _holder; + AccessFlags _access_flags; + FieldInfo::FieldFlags _field_flags; + public: + FieldDesc() : _name(nullptr), _signature(nullptr), _offset(-1), _index(-1), _holder(nullptr), + _access_flags(AccessFlags()), _field_flags(FieldInfo::FieldFlags((u4)0)) { } + + FieldDesc(fieldDescriptor& fd) : _name(fd.name()), _signature(fd.signature()), _offset(fd.offset()), + _index(fd.index()), _holder(fd.field_holder()), + _access_flags(fd.access_flags()), _field_flags(fd.field_flags()) { } + + const Symbol* name() { return _name;} + const Symbol* signature() { return _signature; } + int offset() const { return _offset; } + int index() const { return _index; } + const InstanceKlass* holder() { return _holder; } + const AccessFlags& access_flags() { return _access_flags; } + bool is_null_free_inline_type() const { return _field_flags.is_null_free_inline_type(); } +}; + +static int compare_offset(FieldDesc* f1, FieldDesc* f2) { + return f1->offset() > f2->offset() ? 1 : -1; +} + +static void print_field(outputStream* st, int level, int offset, FieldDesc& fd, bool is_inline_type, bool is_flat ) { + const char* flat_field_msg = ""; + if (is_flat) { + flat_field_msg = is_flat ? "flat" : "not flat"; + } + st->print_cr(" @ %d %*s \"%s\" %s %s %s", + offset, level * 3, "", + fd.name()->as_C_string(), + fd.signature()->as_C_string(), + is_inline_type ? " // inline type " : "", + flat_field_msg); +} + +static void print_flat_field(outputStream* st, int level, int offset, InstanceKlass* klass) { + assert(klass->is_inline_klass(), "Only inline types can be flat"); + InlineKlass* vklass = InlineKlass::cast(klass); + GrowableArray* fields = new (mtServiceability) GrowableArray(100, mtServiceability); + for (AllFieldStream fd(klass); !fd.done(); fd.next()) { + if (!fd.access_flags().is_static()) { + fields->append(FieldDesc(fd.field_descriptor())); + } + } + fields->sort(compare_offset); + for(int i = 0; i < fields->length(); i++) { + FieldDesc fd = fields->at(i); + int offset2 = offset + fd.offset() - vklass->payload_offset(); + print_field(st, level, offset2, fd, + fd.is_null_free_inline_type(), fd.holder()->field_is_flat(fd.index())); + if (fd.holder()->field_is_flat(fd.index())) { + print_flat_field(st, level + 1, offset2 , + InstanceKlass::cast(fd.holder()->get_inline_type_field_klass(fd.index()))); + } + } +} + +void PrintClassLayout::print_class_layout(outputStream* st, char* class_name) { + KlassInfoTable cit(true); + if (cit.allocation_failed()) { + st->print_cr("ERROR: Ran out of C-heap; hierarchy not generated"); + return; + } + + Thread* THREAD = Thread::current(); + + Symbol* classname = SymbolTable::probe(class_name, (int)strlen(class_name)); + + GrowableArray* klasses = new (mtServiceability) GrowableArray(100, mtServiceability); + + FindClassByNameClosure fbnc(klasses, classname); + cit.iterate(&fbnc); + + for(int i = 0; i < klasses->length(); i++) { + Klass* klass = klasses->at(i); + if (!klass->is_instance_klass()) continue; // Skip + InstanceKlass* ik = InstanceKlass::cast(klass); + int tab = 1; + st->print_cr("Class %s [@%s]:", klass->name()->as_C_string(), + klass->class_loader_data()->loader_name()); + ResourceMark rm; + GrowableArray* fields = new (mtServiceability) GrowableArray(100, mtServiceability); + for (AllFieldStream fd(ik); !fd.done(); fd.next()) { + if (!fd.access_flags().is_static()) { + fields->append(FieldDesc(fd.field_descriptor())); + } + } + fields->sort(compare_offset); + for(int i = 0; i < fields->length(); i++) { + FieldDesc fd = fields->at(i); + print_field(st, 0, fd.offset(), fd, fd.is_null_free_inline_type(), fd.holder()->field_is_flat(fd.index())); + if (fd.holder()->field_is_flat(fd.index())) { + print_flat_field(st, 1, fd.offset(), + InstanceKlass::cast(fd.holder()->get_inline_type_field_klass(fd.index()))); + } + } + } + st->cr(); +} + class RecordInstanceClosure : public ObjectClosure { private: KlassInfoTable* _cit; diff --git a/src/hotspot/share/memory/heapInspection.hpp b/src/hotspot/share/memory/heapInspection.hpp index 58e91217a32..47e67371ad7 100644 --- a/src/hotspot/share/memory/heapInspection.hpp +++ b/src/hotspot/share/memory/heapInspection.hpp @@ -190,6 +190,11 @@ class KlassInfoHisto : public StackObj { void sort(); }; +class PrintClassLayout : AllStatic { + public: + static void print_class_layout(outputStream* st, char* classname); +}; + #endif // INCLUDE_SERVICES // These declarations are needed since the declaration of KlassInfoTable and diff --git a/src/hotspot/share/memory/iterator.hpp b/src/hotspot/share/memory/iterator.hpp index 044951142b0..24b5d4dee25 100644 --- a/src/hotspot/share/memory/iterator.hpp +++ b/src/hotspot/share/memory/iterator.hpp @@ -54,6 +54,8 @@ class OopClosure : public Closure { public: virtual void do_oop(oop* o) = 0; virtual void do_oop(narrowOop* o) = 0; + virtual void do_oop_no_buffering(oop* o) { do_oop(o); } + virtual void do_oop_no_buffering(narrowOop* o) { do_oop(o); } }; class DoNothingClosure : public OopClosure { @@ -136,6 +138,11 @@ class DerivedOopClosure : public Closure { virtual void do_derived_oop(derived_base* base, derived_pointer* derived) = 0; }; +class BufferedValueClosure : public Closure { +public: + virtual void do_buffered_value(oop* p) = 0; +}; + class KlassClosure : public Closure { public: virtual void do_klass(Klass* k) = 0; diff --git a/src/hotspot/share/memory/iterator.inline.hpp b/src/hotspot/share/memory/iterator.inline.hpp index 498c74fd1d2..830847ee4c9 100644 --- a/src/hotspot/share/memory/iterator.inline.hpp +++ b/src/hotspot/share/memory/iterator.inline.hpp @@ -32,6 +32,7 @@ #include "code/nmethod.hpp" #include "oops/access.inline.hpp" #include "oops/compressedOops.inline.hpp" +#include "oops/flatArrayKlass.inline.hpp" #include "oops/instanceClassLoaderKlass.inline.hpp" #include "oops/instanceKlass.inline.hpp" #include "oops/instanceMirrorKlass.inline.hpp" @@ -39,6 +40,7 @@ #include "oops/instanceStackChunkKlass.inline.hpp" #include "oops/klass.hpp" #include "oops/objArrayKlass.inline.hpp" +#include "oops/refArrayKlass.inline.hpp" #include "oops/typeArrayKlass.inline.hpp" #include "utilities/debug.hpp" @@ -148,12 +150,15 @@ class OopOopIterateDispatch : public AllStatic { Table(){ set_init_function(); + set_init_function(); set_init_function(); set_init_function(); set_init_function(); set_init_function(); set_init_function(); set_init_function(); + set_init_function(); + set_init_function(); } }; @@ -211,12 +216,15 @@ class OopOopIterateBoundedDispatch { Table(){ set_init_function(); + set_init_function(); set_init_function(); set_init_function(); set_init_function(); set_init_function(); set_init_function(); set_init_function(); + set_init_function(); + set_init_function(); } }; @@ -274,12 +282,15 @@ class OopOopIterateBackwardsDispatch { Table(){ set_init_function(); + set_init_function(); set_init_function(); set_init_function(); set_init_function(); set_init_function(); set_init_function(); set_init_function(); + set_init_function(); + set_init_function(); } }; diff --git a/src/hotspot/share/memory/oopFactory.cpp b/src/hotspot/share/memory/oopFactory.cpp index e0b94a8caf8..63c3db64d7f 100644 --- a/src/hotspot/share/memory/oopFactory.cpp +++ b/src/hotspot/share/memory/oopFactory.cpp @@ -29,11 +29,17 @@ #include "memory/oopFactory.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" +#include "oops/arrayKlass.hpp" +#include "oops/flatArrayKlass.hpp" +#include "oops/flatArrayOop.hpp" +#include "oops/flatArrayOop.inline.hpp" #include "oops/instanceKlass.hpp" #include "oops/instanceOop.hpp" #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.hpp" +#include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" +#include "oops/refArrayKlass.hpp" #include "oops/typeArrayKlass.hpp" #include "oops/typeArrayOop.inline.hpp" #include "runtime/handles.inline.hpp" @@ -73,7 +79,7 @@ typeArrayOop oopFactory::new_longArray(int length, TRAPS) { // create java.lang.Object[] objArrayOop oopFactory::new_objectArray(int length, TRAPS) { - return Universe::objectArrayKlass()->allocate_instance(length, THREAD); + return Universe::objectArrayKlass()->allocate_instance(length, ArrayKlass::ArrayProperties::DEFAULT, THREAD); } typeArrayOop oopFactory::new_charArray(const char* utf8_str, TRAPS) { @@ -104,13 +110,35 @@ typeArrayOop oopFactory::new_typeArray_nozero(BasicType type, int length, TRAPS) return klass->allocate_common(length, false, THREAD); } +objArrayOop oopFactory::new_objArray(Klass* klass, int length, ArrayKlass::ArrayProperties properties, TRAPS) { + assert(!klass->is_array_klass() || properties == ArrayKlass::ArrayProperties::DEFAULT, "properties only apply to single dimension arrays"); + ArrayKlass* ak = klass->array_klass(CHECK_NULL); + return ObjArrayKlass::cast(ak)->allocate_instance(length, properties, THREAD); +} + +objArrayOop oopFactory::new_refArray(Klass* array_klass, int length, TRAPS) { + RefArrayKlass* rak = RefArrayKlass::cast(array_klass); // asserts is refArray_klass(). + return rak->allocate_instance(length, rak->properties(), THREAD); +} objArrayOop oopFactory::new_objArray(Klass* klass, int length, TRAPS) { - if (klass->is_array_klass()) { - return ArrayKlass::cast(klass)->allocate_arrayArray(1, length, THREAD); - } else { - return InstanceKlass::cast(klass)->allocate_objArray(1, length, THREAD); - } + return new_objArray(klass, length, ArrayKlass::ArrayProperties::DEFAULT, THREAD); +} + +flatArrayOop oopFactory::new_flatArray(Klass* k, int length, ArrayKlass::ArrayProperties props, LayoutKind lk, TRAPS) { + InlineKlass* klass = InlineKlass::cast(k); + + ArrayKlass* array_type = klass->array_klass(CHECK_NULL); + ObjArrayKlass* oak = ObjArrayKlass::cast(array_type)->klass_with_properties(props, CHECK_NULL); + + assert(oak->is_flatArray_klass(), "Expected to be"); + assert(FlatArrayKlass::cast(oak)->layout_kind() == lk, "Unexpected layout kind"); + + flatArrayOop oop = (flatArrayOop)FlatArrayKlass::cast(oak)->allocate_instance(length, props, CHECK_NULL); + assert(oop == nullptr || oop->is_flatArray(), "sanity"); + assert(oop == nullptr || oop->klass()->is_flatArray_klass(), "sanity"); + + return oop; } objArrayHandle oopFactory::new_objArray_handle(Klass* klass, int length, TRAPS) { diff --git a/src/hotspot/share/memory/oopFactory.hpp b/src/hotspot/share/memory/oopFactory.hpp index 204ce871dc6..016f2d864cb 100644 --- a/src/hotspot/share/memory/oopFactory.hpp +++ b/src/hotspot/share/memory/oopFactory.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,7 @@ #define SHARE_MEMORY_OOPFACTORY_HPP #include "memory/referenceType.hpp" +#include "oops/arrayKlass.hpp" #include "oops/oopsHierarchy.hpp" #include "runtime/handles.hpp" #include "utilities/exceptions.hpp" @@ -55,6 +56,19 @@ class oopFactory: AllStatic { // Regular object arrays static objArrayOop new_objArray(Klass* klass, int length, TRAPS); + static objArrayOop new_objArray(Klass* klass, int length, ArrayKlass::ArrayProperties properties, TRAPS); + + // Allocate refArray instance given a refArrayKlass. + static objArrayOop new_refArray(Klass* array_klass, int length, TRAPS); + + // Value arrays... + // LWorld: + // - Q-type signature allocation should use this path. + // - L-type signature allocation should use new_objArray + // + // Method specifically null free and possibly flat if possible + // i.e. flatArrayOop if flattening can be done, else "null free" objArrayOop + static flatArrayOop new_flatArray(Klass* klass, int length, ArrayKlass::ArrayProperties props, LayoutKind lk, TRAPS); // Helper that returns a Handle static objArrayHandle new_objArray_handle(Klass* klass, int length, TRAPS); diff --git a/src/hotspot/share/memory/universe.cpp b/src/hotspot/share/memory/universe.cpp index 8afa17b0b3d..b178387835d 100644 --- a/src/hotspot/share/memory/universe.cpp +++ b/src/hotspot/share/memory/universe.cpp @@ -67,6 +67,7 @@ #include "oops/objLayout.hpp" #include "oops/oop.inline.hpp" #include "oops/oopHandle.inline.hpp" +#include "oops/refArrayKlass.hpp" #include "oops/typeArrayKlass.hpp" #include "prims/resolvedMethodTable.hpp" #include "runtime/arguments.hpp" @@ -114,6 +115,8 @@ static LatestMethodCache _loader_addClass_cache; // ClassLoader.addCl static LatestMethodCache _throw_illegal_access_error_cache; // Unsafe.throwIllegalAccessError() static LatestMethodCache _throw_no_such_method_error_cache; // Unsafe.throwNoSuchMethodError() static LatestMethodCache _do_stack_walk_cache; // AbstractStackWalker.doStackWalk() +static LatestMethodCache _is_substitutable_cache; // ValueObjectMethods.isSubstitutable() +static LatestMethodCache _value_object_hash_code_cache; // ValueObjectMethods.valueObjectHashCode() // Known objects TypeArrayKlass* Universe::_typeArrayKlasses[T_LONG+1] = { nullptr /*, nullptr...*/ }; @@ -458,6 +461,7 @@ void Universe::genesis(TRAPS) { vmClasses::Cloneable_klass(), "u3"); assert(_the_array_interfaces_array->at(1) == vmClasses::Serializable_klass(), "u3"); + } else #endif { @@ -505,16 +509,13 @@ void Universe::genesis(TRAPS) { // SystemDictionary::initialize(CHECK); is run. See the extra check // for Object_klass_loaded in objArrayKlassKlass::allocate_objArray_klass_impl. { - Klass* oak = vmClasses::Object_klass()->array_klass(CHECK); - _objectArrayKlass = ObjArrayKlass::cast(oak); + ArrayKlass* oak = vmClasses::Object_klass()->array_klass(CHECK); + oak->append_to_sibling_list(); + + // Create a RefArrayKlass (which is the default) and initialize. + ObjArrayKlass* rak = ObjArrayKlass::cast(oak)->klass_with_properties(ArrayKlass::ArrayProperties::DEFAULT, THREAD); + _objectArrayKlass = rak; } - // OLD - // Add the class to the class hierarchy manually to make sure that - // its vtable is initialized after core bootstrapping is completed. - // --- - // New - // Have already been initialized. - _objectArrayKlass->append_to_sibling_list(); #ifdef ASSERT if (FullGCALot) { @@ -646,6 +647,9 @@ static void reinitialize_vtables() { Klass* sub = iter.klass(); sub->vtable().initialize_vtable(); } + + // This isn't added to the subclass list, so need to reinitialize vtables directly. + Universe::objectArrayKlass()->vtable().initialize_vtable(); } static void reinitialize_itables() { @@ -891,7 +895,6 @@ jint universe_init() { Universe::initialize_tlab(); Metaspace::global_initialize(); - // Initialize performance counters for metaspaces MetaspaceCounters::initialize_performance_counters(); @@ -1055,6 +1058,8 @@ Method* Universe::loader_addClass_method() { return _loader_addClass_cach Method* Universe::throw_illegal_access_error() { return _throw_illegal_access_error_cache.get_method(); } Method* Universe::throw_no_such_method_error() { return _throw_no_such_method_error_cache.get_method(); } Method* Universe::do_stack_walk_method() { return _do_stack_walk_cache.get_method(); } +Method* Universe::is_substitutable_method() { return _is_substitutable_cache.get_method(); } +Method* Universe::value_object_hash_code_method() { return _value_object_hash_code_cache.get_method(); } void Universe::initialize_known_methods(JavaThread* current) { // Set up static method for registering finalizers @@ -1084,6 +1089,17 @@ void Universe::initialize_known_methods(JavaThread* current) { vmClasses::AbstractStackWalker_klass(), "doStackWalk", vmSymbols::doStackWalk_signature(), false); + + // Set up substitutability testing + ResourceMark rm(current); + _is_substitutable_cache.init(current, + vmClasses::ValueObjectMethods_klass(), + vmSymbols::isSubstitutable_name()->as_C_string(), + vmSymbols::object_object_boolean_signature(), true); + _value_object_hash_code_cache.init(current, + vmClasses::ValueObjectMethods_klass(), + vmSymbols::valueObjectHashCode_name()->as_C_string(), + vmSymbols::object_int_signature(), true); } void universe2_init() { diff --git a/src/hotspot/share/memory/universe.hpp b/src/hotspot/share/memory/universe.hpp index ee4c05e1e06..3ea0cb8b060 100644 --- a/src/hotspot/share/memory/universe.hpp +++ b/src/hotspot/share/memory/universe.hpp @@ -116,6 +116,7 @@ class Universe: AllStatic { static intptr_t _non_oop_bits; + // array of dummy objects used with +FullGCAlot DEBUG_ONLY(static OopHandle _fullgc_alot_dummy_array;) DEBUG_ONLY(static int _fullgc_alot_dummy_next;) @@ -252,6 +253,9 @@ class Universe: AllStatic { static Method* throw_no_such_method_error(); static Method* do_stack_walk_method(); + static Method* is_substitutable_method(); + static Method* value_object_hash_code_method(); + static oop the_null_sentinel(); static address the_null_sentinel_addr() { return (address) &_the_null_sentinel; } diff --git a/src/hotspot/share/oops/access.hpp b/src/hotspot/share/oops/access.hpp index 917627f445e..bda10eac37e 100644 --- a/src/hotspot/share/oops/access.hpp +++ b/src/hotspot/share/oops/access.hpp @@ -28,6 +28,7 @@ #include "memory/allStatic.hpp" #include "oops/accessBackend.hpp" #include "oops/accessDecorators.hpp" +#include "oops/inlineKlass.hpp" #include "oops/oopsHierarchy.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" @@ -57,6 +58,7 @@ // * atomic_xchg_at: Atomically swap a new value at an internal pointer address without checking the previous value. // * arraycopy: Copy data from one heap array to another heap array. The ArrayAccess class has convenience functions for this. // * clone: Clone the contents of an object to a newly allocated object. +// * value_copy: Copy the contents of a value type from one heap address to another // // == IMPLEMENTATION == // Each access goes through the following steps in a template pipeline. @@ -121,6 +123,12 @@ class Access: public AllStatic { verify_decorators(); } + template + static void verify_heap_value_decorators() { + const DecoratorSet heap_value_decorators = IN_HEAP | IS_DEST_UNINITIALIZED; + verify_decorators(); + } + static const DecoratorSet load_mo_decorators = MO_UNORDERED | MO_RELAXED | MO_ACQUIRE | MO_SEQ_CST; static const DecoratorSet store_mo_decorators = MO_UNORDERED | MO_RELAXED | MO_RELEASE | MO_SEQ_CST; static const DecoratorSet atomic_xchg_mo_decorators = MO_SEQ_CST; @@ -128,14 +136,14 @@ class Access: public AllStatic { protected: template - static inline bool oop_arraycopy(arrayOop src_obj, size_t src_offset_in_bytes, const T* src_raw, + static inline void oop_arraycopy(arrayOop src_obj, size_t src_offset_in_bytes, const T* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, T* dst_raw, size_t length) { verify_decorators(); - return AccessInternal::arraycopy(src_obj, src_offset_in_bytes, src_raw, - dst_obj, dst_offset_in_bytes, dst_raw, - length); + AccessInternal::arraycopy(src_obj, src_offset_in_bytes, src_raw, + dst_obj, dst_offset_in_bytes, dst_raw, + length); } template @@ -211,6 +219,14 @@ class Access: public AllStatic { AccessInternal::clone(src, dst, size); } + // inline type heap access (when flat)... + + // Copy value type data from src to dst + static inline void value_copy(void* src, void* dst, InlineKlass* md, LayoutKind lk) { + verify_heap_value_decorators(); + AccessInternal::value_copy(src, dst, md, lk); + } + // Primitive accesses template static inline P load(P* addr) { @@ -316,19 +332,19 @@ class ArrayAccess: public HeapAccess { length); } - static inline bool oop_arraycopy(arrayOop src_obj, size_t src_offset_in_bytes, + static inline void oop_arraycopy(arrayOop src_obj, size_t src_offset_in_bytes, arrayOop dst_obj, size_t dst_offset_in_bytes, size_t length) { - return AccessT::oop_arraycopy(src_obj, src_offset_in_bytes, static_cast(nullptr), - dst_obj, dst_offset_in_bytes, static_cast(nullptr), - length); + AccessT::oop_arraycopy(src_obj, src_offset_in_bytes, static_cast(nullptr), + dst_obj, dst_offset_in_bytes, static_cast(nullptr), + length); } template - static inline bool oop_arraycopy_raw(T* src, T* dst, size_t length) { - return AccessT::oop_arraycopy(nullptr, 0, src, - nullptr, 0, dst, - length); + static inline void oop_arraycopy_raw(T* src, T* dst, size_t length) { + AccessT::oop_arraycopy(nullptr, 0, src, + nullptr, 0, dst, + length); } }; diff --git a/src/hotspot/share/oops/access.inline.hpp b/src/hotspot/share/oops/access.inline.hpp index b3f15f1168d..fab1f2939e2 100644 --- a/src/hotspot/share/oops/access.inline.hpp +++ b/src/hotspot/share/oops/access.inline.hpp @@ -125,23 +125,22 @@ namespace AccessInternal { template struct PostRuntimeDispatch: public AllStatic { template - static bool access_barrier(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, + static void access_barrier(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, T* dst_raw, size_t length) { GCBarrierType::arraycopy_in_heap(src_obj, src_offset_in_bytes, src_raw, dst_obj, dst_offset_in_bytes, dst_raw, length); - return true; } template - static bool oop_access_barrier(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, + static void oop_access_barrier(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, T* dst_raw, size_t length) { typedef typename HeapOopType::type OopType; - return GCBarrierType::oop_arraycopy_in_heap(src_obj, src_offset_in_bytes, reinterpret_cast(src_raw), - dst_obj, dst_offset_in_bytes, reinterpret_cast(dst_raw), - length); + GCBarrierType::oop_arraycopy_in_heap(src_obj, src_offset_in_bytes, reinterpret_cast(src_raw), + dst_obj, dst_offset_in_bytes, reinterpret_cast(dst_raw), + length); } }; @@ -200,6 +199,13 @@ namespace AccessInternal { } }; + template + struct PostRuntimeDispatch: public AllStatic { + static void access_barrier(void* src, void* dst, InlineKlass* md, LayoutKind lk) { + GCBarrierType::value_copy_in_heap(src, dst, md, lk); + } + }; + // Resolving accessors with barriers from the barrier set happens in two steps. // 1. Expand paths with runtime-decorators, e.g. is UseCompressedOops on or off. // 2. Expand paths for each BarrierSet available in the system. @@ -331,14 +337,14 @@ namespace AccessInternal { } template - bool RuntimeDispatch::arraycopy_init(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, + void RuntimeDispatch::arraycopy_init(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, T* dst_raw, size_t length) { func_t function = BarrierResolver::resolve_barrier(); _arraycopy_func = function; - return function(src_obj, src_offset_in_bytes, src_raw, - dst_obj, dst_offset_in_bytes, dst_raw, - length); + function(src_obj, src_offset_in_bytes, src_raw, + dst_obj, dst_offset_in_bytes, dst_raw, + length); } template @@ -347,6 +353,13 @@ namespace AccessInternal { _clone_func = function; function(src, dst, size); } + + template + void RuntimeDispatch::value_copy_init(void* src, void* dst, InlineKlass* md, LayoutKind lk) { + func_t function = BarrierResolver::resolve_barrier(); + _value_copy_func = function; + function(src, dst, md,lk); + } } #endif // SHARE_OOPS_ACCESS_INLINE_HPP diff --git a/src/hotspot/share/oops/accessBackend.cpp b/src/hotspot/share/oops/accessBackend.cpp index 3b64bbb61f2..2b153edc417 100644 --- a/src/hotspot/share/oops/accessBackend.cpp +++ b/src/hotspot/share/oops/accessBackend.cpp @@ -163,6 +163,10 @@ namespace AccessInternal { Copy::conjoint_memory_atomic(src, dst, length); } + void value_copy_internal(void* src, void* dst, size_t length) { + Copy::copy_value_content(src, dst, length); + } + #ifdef ASSERT void check_access_thread_state() { if (VMError::is_error_reported() || DebuggingContext::is_enabled()) { diff --git a/src/hotspot/share/oops/accessBackend.hpp b/src/hotspot/share/oops/accessBackend.hpp index 5299b57ca46..d87311c8210 100644 --- a/src/hotspot/share/oops/accessBackend.hpp +++ b/src/hotspot/share/oops/accessBackend.hpp @@ -29,6 +29,7 @@ #include "memory/allocation.hpp" #include "metaprogramming/enableIf.hpp" #include "oops/accessDecorators.hpp" +#include "oops/inlineKlass.hpp" #include "oops/oopsHierarchy.hpp" #include "runtime/globals.hpp" #include "utilities/debug.hpp" @@ -45,6 +46,14 @@ struct HeapOopType: AllStatic { using type = std::conditional_t; }; +// This meta-function returns either oop or narrowOop depending on whether +// a back-end needs to consider compressed oops types or not. +template +struct ValueOopType: AllStatic { + static const bool needs_oop_compress = HasDecorator::value; + using type = std::conditional_t; +}; + namespace AccessInternal { enum BarrierType { BARRIER_STORE, @@ -56,7 +65,8 @@ namespace AccessInternal { BARRIER_ATOMIC_XCHG, BARRIER_ATOMIC_XCHG_AT, BARRIER_ARRAYCOPY, - BARRIER_CLONE + BARRIER_CLONE, + BARRIER_VALUE_COPY }; template @@ -93,15 +103,16 @@ namespace AccessInternal { typedef T (*atomic_cmpxchg_func_t)(void* addr, T compare_value, T new_value); typedef T (*atomic_xchg_func_t)(void* addr, T new_value); - typedef bool (*arraycopy_func_t)(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, + typedef void (*arraycopy_func_t)(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, T* dst_raw, size_t length); typedef void (*clone_func_t)(oop src, oop dst, size_t size); + typedef void (*value_copy_func_t)(void* src, void* dst, InlineKlass* md, LayoutKind lk); }; template struct AccessFunctionTypes { - typedef bool (*arraycopy_func_t)(arrayOop src_obj, size_t src_offset_in_bytes, void* src, + typedef void (*arraycopy_func_t)(arrayOop src_obj, size_t src_offset_in_bytes, void* src, arrayOop dst_obj, size_t dst_offset_in_bytes, void* dst, size_t length); }; @@ -123,6 +134,7 @@ namespace AccessInternal { ACCESS_GENERATE_ACCESS_FUNCTION(BARRIER_ATOMIC_XCHG_AT, atomic_xchg_at_func_t); ACCESS_GENERATE_ACCESS_FUNCTION(BARRIER_ARRAYCOPY, arraycopy_func_t); ACCESS_GENERATE_ACCESS_FUNCTION(BARRIER_CLONE, clone_func_t); + ACCESS_GENERATE_ACCESS_FUNCTION(BARRIER_VALUE_COPY, value_copy_func_t); #undef ACCESS_GENERATE_ACCESS_FUNCTION template @@ -148,6 +160,8 @@ namespace AccessInternal { void arraycopy_arrayof_conjoint(T* src, T* dst, size_t length); template void arraycopy_conjoint_atomic(T* src, T* dst, size_t length); + + void value_copy_internal(void* src, void* dst, size_t length); } // This mask specifies what decorators are relevant for raw accesses. When passing @@ -287,7 +301,7 @@ class RawAccessBarrier: public AllStatic { } template - static bool arraycopy(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, + static void arraycopy(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, T* dst_raw, size_t length); @@ -332,11 +346,13 @@ class RawAccessBarrier: public AllStatic { } template - static bool oop_arraycopy(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, + static void oop_arraycopy(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, T* dst_raw, size_t length); static void clone(oop src, oop dst, size_t size); + static void value_copy(void* src, void* dst, InlineKlass* md, LayoutKind lk); + }; namespace AccessInternal { @@ -506,11 +522,11 @@ namespace AccessInternal { typedef typename AccessFunction::type func_t; static func_t _arraycopy_func; - static bool arraycopy_init(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, + static void arraycopy_init(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, T* dst_raw, size_t length); - static inline bool arraycopy(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, + static inline void arraycopy(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, T* dst_raw, size_t length) { assert_access_thread_state(); @@ -533,6 +549,18 @@ namespace AccessInternal { } }; + template + struct RuntimeDispatch: AllStatic { + typedef typename AccessFunction::type func_t; + static func_t _value_copy_func; + + static void value_copy_init(void* src, void* dst, InlineKlass* md, LayoutKind lk); + + static inline void value_copy(void* src, void* dst, InlineKlass* md, LayoutKind lk) { + _value_copy_func(src, dst, md, lk); + } + }; + // Initialize the function pointers to point to the resolving function. template typename AccessFunction::type @@ -574,6 +602,10 @@ namespace AccessInternal { typename AccessFunction::type RuntimeDispatch::_clone_func = &clone_init; + template + typename AccessFunction::type + RuntimeDispatch::_value_copy_func = &value_copy_init; + // Step 3: Pre-runtime dispatching. // The PreRuntimeDispatch class is responsible for filtering the barrier strength // decorators. That is, for AS_RAW, it hardwires the accesses without a runtime @@ -821,56 +853,56 @@ namespace AccessInternal { template inline static typename EnableIf< - HasDecorator::value && CanHardwireRaw::value, bool>::type + HasDecorator::value && CanHardwireRaw::value, void>::type arraycopy(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, T* dst_raw, size_t length) { typedef RawAccessBarrier Raw; if (HasDecorator::value) { - return Raw::oop_arraycopy(src_obj, src_offset_in_bytes, src_raw, - dst_obj, dst_offset_in_bytes, dst_raw, - length); + Raw::oop_arraycopy(src_obj, src_offset_in_bytes, src_raw, + dst_obj, dst_offset_in_bytes, dst_raw, + length); } else { - return Raw::arraycopy(src_obj, src_offset_in_bytes, src_raw, - dst_obj, dst_offset_in_bytes, dst_raw, - length); + Raw::arraycopy(src_obj, src_offset_in_bytes, src_raw, + dst_obj, dst_offset_in_bytes, dst_raw, + length); } } template inline static typename EnableIf< - HasDecorator::value && !CanHardwireRaw::value, bool>::type + HasDecorator::value && !CanHardwireRaw::value, void>::type arraycopy(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, T* dst_raw, size_t length) { if (UseCompressedOops) { const DecoratorSet expanded_decorators = decorators | convert_compressed_oops; - return PreRuntimeDispatch::arraycopy(src_obj, src_offset_in_bytes, src_raw, - dst_obj, dst_offset_in_bytes, dst_raw, - length); + PreRuntimeDispatch::arraycopy(src_obj, src_offset_in_bytes, src_raw, + dst_obj, dst_offset_in_bytes, dst_raw, + length); } else { const DecoratorSet expanded_decorators = decorators & ~convert_compressed_oops; - return PreRuntimeDispatch::arraycopy(src_obj, src_offset_in_bytes, src_raw, - dst_obj, dst_offset_in_bytes, dst_raw, - length); + PreRuntimeDispatch::arraycopy(src_obj, src_offset_in_bytes, src_raw, + dst_obj, dst_offset_in_bytes, dst_raw, + length); } } template inline static typename EnableIf< - !HasDecorator::value, bool>::type + !HasDecorator::value, void>::type arraycopy(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, T* dst_raw, size_t length) { if (is_hardwired_primitive()) { const DecoratorSet expanded_decorators = decorators | AS_RAW; - return PreRuntimeDispatch::arraycopy(src_obj, src_offset_in_bytes, src_raw, - dst_obj, dst_offset_in_bytes, dst_raw, - length); + PreRuntimeDispatch::arraycopy(src_obj, src_offset_in_bytes, src_raw, + dst_obj, dst_offset_in_bytes, dst_raw, + length); } else { - return RuntimeDispatch::arraycopy(src_obj, src_offset_in_bytes, src_raw, - dst_obj, dst_offset_in_bytes, dst_raw, - length); + RuntimeDispatch::arraycopy(src_obj, src_offset_in_bytes, src_raw, + dst_obj, dst_offset_in_bytes, dst_raw, + length); } } @@ -888,6 +920,22 @@ namespace AccessInternal { clone(oop src, oop dst, size_t size) { RuntimeDispatch::clone(src, dst, size); } + + template + inline static typename EnableIf< + HasDecorator::value>::type + value_copy(void* src, void* dst, InlineKlass* md, LayoutKind lk) { + typedef RawAccessBarrier Raw; + Raw::value_copy(src, dst, md, lk); + } + + template + inline static typename EnableIf< + !HasDecorator::value>::type + value_copy(void* src, void* dst, InlineKlass* md, LayoutKind lk) { + const DecoratorSet expanded_decorators = decorators; + RuntimeDispatch::value_copy(src, dst, md, lk); + } }; // Step 2: Reduce types. @@ -1002,33 +1050,33 @@ namespace AccessInternal { } template - inline bool arraycopy_reduce_types(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, + inline void arraycopy_reduce_types(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, T* dst_raw, size_t length) { - return PreRuntimeDispatch::arraycopy(src_obj, src_offset_in_bytes, src_raw, - dst_obj, dst_offset_in_bytes, dst_raw, - length); + PreRuntimeDispatch::arraycopy(src_obj, src_offset_in_bytes, src_raw, + dst_obj, dst_offset_in_bytes, dst_raw, + length); } template - inline bool arraycopy_reduce_types(arrayOop src_obj, size_t src_offset_in_bytes, HeapWord* src_raw, + inline void arraycopy_reduce_types(arrayOop src_obj, size_t src_offset_in_bytes, HeapWord* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, HeapWord* dst_raw, size_t length) { const DecoratorSet expanded_decorators = decorators | INTERNAL_CONVERT_COMPRESSED_OOP; - return PreRuntimeDispatch::arraycopy(src_obj, src_offset_in_bytes, src_raw, - dst_obj, dst_offset_in_bytes, dst_raw, - length); + PreRuntimeDispatch::arraycopy(src_obj, src_offset_in_bytes, src_raw, + dst_obj, dst_offset_in_bytes, dst_raw, + length); } template - inline bool arraycopy_reduce_types(arrayOop src_obj, size_t src_offset_in_bytes, narrowOop* src_raw, + inline void arraycopy_reduce_types(arrayOop src_obj, size_t src_offset_in_bytes, narrowOop* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, narrowOop* dst_raw, size_t length) { const DecoratorSet expanded_decorators = decorators | INTERNAL_CONVERT_COMPRESSED_OOP | INTERNAL_RT_USE_COMPRESSED_OOPS; - return PreRuntimeDispatch::arraycopy(src_obj, src_offset_in_bytes, src_raw, - dst_obj, dst_offset_in_bytes, dst_raw, - length); + PreRuntimeDispatch::arraycopy(src_obj, src_offset_in_bytes, src_raw, + dst_obj, dst_offset_in_bytes, dst_raw, + length); } // Step 1: Set default decorators. This step remembers if a type was volatile @@ -1161,7 +1209,7 @@ namespace AccessInternal { } template - inline bool arraycopy(arrayOop src_obj, size_t src_offset_in_bytes, const T* src_raw, + inline void arraycopy(arrayOop src_obj, size_t src_offset_in_bytes, const T* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, T* dst_raw, size_t length) { STATIC_ASSERT((HasDecorator::value || @@ -1169,9 +1217,9 @@ namespace AccessInternal { std::is_floating_point::value)); // arraycopy allows type erased void elements using DecayedT = std::decay_t; const DecoratorSet expanded_decorators = DecoratorFixup::value; - return arraycopy_reduce_types(src_obj, src_offset_in_bytes, const_cast(src_raw), - dst_obj, dst_offset_in_bytes, const_cast(dst_raw), - length); + arraycopy_reduce_types(src_obj, src_offset_in_bytes, const_cast(src_raw), + dst_obj, dst_offset_in_bytes, const_cast(dst_raw), + length); } template @@ -1180,6 +1228,12 @@ namespace AccessInternal { PreRuntimeDispatch::clone(src, dst, size); } + template + inline void value_copy(void* src, void* dst, InlineKlass* md, LayoutKind lk) { + const DecoratorSet expanded_decorators = DecoratorFixup::value; + PreRuntimeDispatch::value_copy(src, dst, md, lk); + } + // Infer the type that should be returned from an Access::oop_load. template class OopLoadProxy: public StackObj { diff --git a/src/hotspot/share/oops/accessBackend.inline.hpp b/src/hotspot/share/oops/accessBackend.inline.hpp index e0d4f5aca92..a2900a14b70 100644 --- a/src/hotspot/share/oops/accessBackend.inline.hpp +++ b/src/hotspot/share/oops/accessBackend.inline.hpp @@ -30,6 +30,7 @@ #include "oops/access.hpp" #include "oops/arrayOop.hpp" #include "oops/compressedOops.inline.hpp" +#include "oops/inlineKlass.hpp" #include "oops/oopsHierarchy.hpp" #include "runtime/atomic.hpp" #include "runtime/orderAccess.hpp" @@ -124,12 +125,12 @@ inline T RawAccessBarrier::oop_atomic_xchg_at(oop base, ptrdiff_t of template template -inline bool RawAccessBarrier::oop_arraycopy(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, +inline void RawAccessBarrier::oop_arraycopy(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, T* dst_raw, size_t length) { - return arraycopy(src_obj, src_offset_in_bytes, src_raw, - dst_obj, dst_offset_in_bytes, dst_raw, - length); + arraycopy(src_obj, src_offset_in_bytes, src_raw, + dst_obj, dst_offset_in_bytes, dst_raw, + length); } template @@ -302,13 +303,12 @@ template<> struct RawAccessBarrierArrayCopy::IsHeapWordSized: public std:: template template -inline bool RawAccessBarrier::arraycopy(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, +inline void RawAccessBarrier::arraycopy(arrayOop src_obj, size_t src_offset_in_bytes, T* src_raw, arrayOop dst_obj, size_t dst_offset_in_bytes, T* dst_raw, size_t length) { RawAccessBarrierArrayCopy::arraycopy(src_obj, src_offset_in_bytes, src_raw, dst_obj, dst_offset_in_bytes, dst_raw, length); - return true; } template @@ -331,4 +331,9 @@ inline void RawAccessBarrier::clone(oop src, oop dst, size_t size) { dst->init_mark(); } +template +inline void RawAccessBarrier::value_copy(void* src, void* dst, InlineKlass* md, LayoutKind lk) { + assert(is_aligned(src, md->layout_alignment(lk)) && is_aligned(dst, md->layout_alignment(lk)), "Unaligned value_copy"); + AccessInternal::value_copy_internal(src, dst, static_cast(md->layout_size_in_bytes(lk))); +} #endif // SHARE_OOPS_ACCESSBACKEND_INLINE_HPP diff --git a/src/hotspot/share/oops/accessDecorators.hpp b/src/hotspot/share/oops/accessDecorators.hpp index 3d89b5c4334..0da68ab8004 100644 --- a/src/hotspot/share/oops/accessDecorators.hpp +++ b/src/hotspot/share/oops/accessDecorators.hpp @@ -192,17 +192,20 @@ const DecoratorSet IS_NOT_NULL = UCONST64(1) << 22; // are not guaranteed to be subclasses of the class of the destination array. This requires // a check-cast barrier during the copying operation. If this is not set, it is assumed // that the array is covariant: (the source array type is-a destination array type) +// * ARRAYCOPY_NOTNULL: This property means that the source array may contain null elements +// but the destination does not allow null elements (i.e. throw NPE) // * ARRAYCOPY_DISJOINT: This property means that it is known that the two array ranges // are disjoint. // * ARRAYCOPY_ARRAYOF: The copy is in the arrayof form. // * ARRAYCOPY_ATOMIC: The accesses have to be atomic over the size of its elements. // * ARRAYCOPY_ALIGNED: The accesses have to be aligned on a HeapWord. const DecoratorSet ARRAYCOPY_CHECKCAST = UCONST64(1) << 23; -const DecoratorSet ARRAYCOPY_DISJOINT = UCONST64(1) << 24; -const DecoratorSet ARRAYCOPY_ARRAYOF = UCONST64(1) << 25; -const DecoratorSet ARRAYCOPY_ATOMIC = UCONST64(1) << 26; -const DecoratorSet ARRAYCOPY_ALIGNED = UCONST64(1) << 27; -const DecoratorSet ARRAYCOPY_DECORATOR_MASK = ARRAYCOPY_CHECKCAST | ARRAYCOPY_DISJOINT | +const DecoratorSet ARRAYCOPY_NOTNULL = UCONST64(1) << 24; +const DecoratorSet ARRAYCOPY_DISJOINT = UCONST64(1) << 25; +const DecoratorSet ARRAYCOPY_ARRAYOF = UCONST64(1) << 26; +const DecoratorSet ARRAYCOPY_ATOMIC = UCONST64(1) << 27; +const DecoratorSet ARRAYCOPY_ALIGNED = UCONST64(1) << 28; +const DecoratorSet ARRAYCOPY_DECORATOR_MASK = ARRAYCOPY_CHECKCAST | ARRAYCOPY_NOTNULL | ARRAYCOPY_DISJOINT | ARRAYCOPY_ARRAYOF | ARRAYCOPY_ATOMIC | ARRAYCOPY_ALIGNED; @@ -210,11 +213,11 @@ const DecoratorSet ARRAYCOPY_DECORATOR_MASK = ARRAYCOPY_CHECKCAST | ARRAYC // * ACCESS_READ: Indicate that the resolved object is accessed read-only. This allows the GC // backend to use weaker and more efficient barriers. // * ACCESS_WRITE: Indicate that the resolved object is used for write access. -const DecoratorSet ACCESS_READ = UCONST64(1) << 28; -const DecoratorSet ACCESS_WRITE = UCONST64(1) << 29; +const DecoratorSet ACCESS_READ = UCONST64(1) << 29; +const DecoratorSet ACCESS_WRITE = UCONST64(1) << 30; // Keep track of the last decorator. -const DecoratorSet DECORATOR_LAST = UCONST64(1) << 29; +const DecoratorSet DECORATOR_LAST = UCONST64(1) << 30; namespace AccessInternal { // This class adds implied decorators that follow according to decorator rules. diff --git a/src/hotspot/share/oops/arrayKlass.cpp b/src/hotspot/share/oops/arrayKlass.cpp index 198ff1ef75e..54991cd65e0 100644 --- a/src/hotspot/share/oops/arrayKlass.cpp +++ b/src/hotspot/share/oops/arrayKlass.cpp @@ -26,6 +26,7 @@ #include "cds/metaspaceShared.hpp" #include "classfile/javaClasses.hpp" #include "classfile/moduleEntry.hpp" +#include "classfile/symbolTable.hpp" #include "classfile/vmClasses.hpp" #include "classfile/vmSymbols.hpp" #include "gc/shared/collectedHeap.inline.hpp" @@ -37,8 +38,10 @@ #include "oops/arrayOop.hpp" #include "oops/instanceKlass.hpp" #include "oops/klass.inline.hpp" +#include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.hpp" #include "oops/oop.inline.hpp" +#include "oops/refArrayKlass.hpp" #include "runtime/handles.inline.hpp" void* ArrayKlass::operator new(size_t size, ClassLoaderData* loader_data, size_t word_size, TRAPS) throw() { @@ -92,9 +95,10 @@ Method* ArrayKlass::uncached_lookup_method(const Symbol* name, return super()->uncached_lookup_method(name, signature, OverpassLookupMode::skip, private_mode); } -ArrayKlass::ArrayKlass(Symbol* name, KlassKind kind) : - Klass(kind), +ArrayKlass::ArrayKlass(Symbol* name, KlassKind kind, ArrayProperties props, markWord prototype_header) : +Klass(kind, prototype_header), _dimension(1), + _properties(props), _higher_dimension(nullptr), _lower_dimension(nullptr) { // Arrays don't add any new methods, so their vtable is the same size as @@ -108,6 +112,25 @@ ArrayKlass::ArrayKlass(Symbol* name, KlassKind kind) : log_array_class_load(this); } +Symbol* ArrayKlass::create_element_klass_array_name(Klass* element_klass, TRAPS) { + ResourceMark rm(THREAD); + Symbol* name = nullptr; + char *name_str = element_klass->name()->as_C_string(); + int len = element_klass->name()->utf8_length(); + char *new_str = NEW_RESOURCE_ARRAY(char, len + 4); + int idx = 0; + new_str[idx++] = JVM_SIGNATURE_ARRAY; + if (element_klass->is_instance_klass()) { // it could be an array or simple type + new_str[idx++] = JVM_SIGNATURE_CLASS; + } + memcpy(&new_str[idx], name_str, len * sizeof(char)); + idx += len; + if (element_klass->is_instance_klass()) { + new_str[idx++] = JVM_SIGNATURE_ENDCLASS; + } + new_str[idx++] = '\0'; + return SymbolTable::new_symbol(new_str); +} // Initialization of vtables and mirror object is done separately from base_create_array_klass, // since a GC can happen. At this point all instance variables of the ArrayKlass must be setup. @@ -121,7 +144,16 @@ void ArrayKlass::complete_create_array_klass(ArrayKlass* k, Klass* super_klass, assert((module_entry != nullptr) || ((module_entry == nullptr) && !ModuleEntryTable::javabase_defined()), "module entry not available post " JAVA_BASE_NAME " definition"); oop module_oop = (module_entry != nullptr) ? module_entry->module_oop() : (oop)nullptr; - java_lang_Class::create_mirror(k, Handle(THREAD, k->class_loader()), Handle(THREAD, module_oop), Handle(), Handle(), CHECK); + + if (k->is_refArray_klass() || k->is_flatArray_klass()) { + assert(super_klass != nullptr, "Must be"); + assert(k->super() != nullptr, "Must be"); + assert(k->super() == super_klass, "Must be"); + Handle mirror(THREAD, super_klass->java_mirror()); + k->set_java_mirror(mirror); + } else { + java_lang_Class::create_mirror(k, Handle(THREAD, k->class_loader()), Handle(THREAD, module_oop), Handle(), Handle(), CHECK); + } } ArrayKlass* ArrayKlass::array_klass(int n, TRAPS) { @@ -138,8 +170,7 @@ ArrayKlass* ArrayKlass::array_klass(int n, TRAPS) { if (higher_dimension() == nullptr) { // Create multi-dim klass object and link them together - ObjArrayKlass* ak = - ObjArrayKlass::allocate_objArray_klass(class_loader_data(), dim + 1, this, CHECK_NULL); + ObjArrayKlass* ak = RefArrayKlass::allocate_objArray_klass(class_loader_data(), dim + 1, this, CHECK_NULL); // use 'release' to pair with lock-free load release_set_higher_dimension(ak); assert(ak->lower_dimension() == this, "lower dimension mismatch"); @@ -187,14 +218,8 @@ GrowableArray* ArrayKlass::compute_secondary_supers(int num_extra_slots, return nullptr; } -objArrayOop ArrayKlass::allocate_arrayArray(int n, int length, TRAPS) { - check_array_allocation_length(length, arrayOopDesc::max_array_length(T_ARRAY), CHECK_NULL); - size_t size = objArrayOopDesc::object_size(length); - ArrayKlass* ak = array_klass(n + dimension(), CHECK_NULL); - objArrayOop o = (objArrayOop)Universe::heap()->array_allocate(ak, size, length, - /* do_zero */ true, CHECK_NULL); - // initialization to null not necessary, area already cleared - return o; +oop ArrayKlass::component_mirror() const { + return java_lang_Class::component_mirror(java_mirror()); } // JVMTI support @@ -236,7 +261,7 @@ void ArrayKlass::restore_unshareable_info(ClassLoaderData* loader_data, Handle p // Klass recreates the component mirror also if (_higher_dimension != nullptr) { - ArrayKlass *ak = higher_dimension(); + ObjArrayKlass *ak = higher_dimension(); log_array_class_load(ak); ak->restore_unshareable_info(loader_data, protection_domain, CHECK); } diff --git a/src/hotspot/share/oops/arrayKlass.hpp b/src/hotspot/share/oops/arrayKlass.hpp index 5bfe46573d3..7ed037d5d64 100644 --- a/src/hotspot/share/oops/arrayKlass.hpp +++ b/src/hotspot/share/oops/arrayKlass.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,10 +35,26 @@ class ObjArrayKlass; class ArrayKlass: public Klass { friend class VMStructs; + + public: + enum ArrayProperties : uint32_t { + DEFAULT = 0, + NULL_RESTRICTED = 1 << 0, + NON_ATOMIC = 1 << 1, + // FINAL = 1 << 2, + // VOLATILE = 1 << 3 + INVALID = 1 << 4, + DUMMY = 1 << 5 // Just to transition the code, to be removed ASAP + }; + + static bool is_null_restricted(ArrayProperties props) { return (props & NULL_RESTRICTED) != 0; } + static bool is_non_atomic(ArrayProperties props) { return (props & NON_ATOMIC) != 0; } + private: // If you add a new field that points to any metaspace object, you // must add this field to ArrayKlass::metaspace_pointers_do(). int _dimension; // This is n'th-dimensional array. + ArrayProperties _properties; ObjArrayKlass* volatile _higher_dimension; // Refers the (n+1)'th-dimensional array (if present). ArrayKlass* volatile _lower_dimension; // Refers the (n-1)'th-dimensional array (if present). @@ -46,12 +62,16 @@ class ArrayKlass: public Klass { // Constructors // The constructor with the Symbol argument does the real array // initialization, the other is a dummy - ArrayKlass(Symbol* name, KlassKind kind); + ArrayKlass(Symbol* name, KlassKind kind, ArrayProperties props, markWord prototype_header = markWord::prototype()); ArrayKlass(); + // Create array_name for element klass + static Symbol* create_element_klass_array_name(Klass* element_klass, TRAPS); + void* operator new(size_t size, ClassLoaderData* loader_data, size_t word_size, TRAPS) throw(); public: + // Testing operation DEBUG_ONLY(bool is_array_klass_slow() const { return true; }) @@ -67,6 +87,10 @@ class ArrayKlass: public Klass { int dimension() const { return _dimension; } void set_dimension(int dimension) { _dimension = dimension; } + ArrayProperties properties() const { return _properties; } + void set_properties(ArrayProperties props) { _properties = props; } + static ByteSize properties_offset() { return byte_offset_of(ArrayKlass, _properties); } + ObjArrayKlass* higher_dimension() const { return _higher_dimension; } inline ObjArrayKlass* higher_dimension_acquire() const; // load with acquire semantics void set_higher_dimension(ObjArrayKlass* k) { _higher_dimension = k; } @@ -87,7 +111,6 @@ class ArrayKlass: public Klass { // Sizes points to the first dimension of the array, subsequent dimensions // are always in higher memory. The callers of these set that up. virtual oop multi_allocate(int rank, jint* sizes, TRAPS); - objArrayOop allocate_arrayArray(int n, int length, TRAPS); // find field according to JVM spec 5.4.3.2, returns the klass in which the field is defined Klass* find_field(Symbol* name, Symbol* sig, fieldDescriptor* fd) const; @@ -110,6 +133,8 @@ class ArrayKlass: public Klass { GrowableArray* compute_secondary_supers(int num_extra_slots, Array* transitive_interfaces); + oop component_mirror() const; + // Sizing static int static_size(int header_size); @@ -142,4 +167,12 @@ class ArrayKlass: public Klass { void oop_verify_on(oop obj, outputStream* st); }; +class ArrayDescription : public StackObj { + public: + Klass::KlassKind _kind; + ArrayKlass::ArrayProperties _properties; + LayoutKind _layout_kind; + ArrayDescription(Klass::KlassKind k, ArrayKlass::ArrayProperties p, LayoutKind lk) { _kind = k; _properties = p; _layout_kind = lk; } + }; + #endif // SHARE_OOPS_ARRAYKLASS_HPP diff --git a/src/hotspot/share/oops/arrayOop.hpp b/src/hotspot/share/oops/arrayOop.hpp index f0c476a2486..5e2c9397005 100644 --- a/src/hotspot/share/oops/arrayOop.hpp +++ b/src/hotspot/share/oops/arrayOop.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,7 @@ #define SHARE_OOPS_ARRAYOOP_HPP #include "oops/oop.hpp" +#include "runtime/globals.hpp" #include "utilities/align.hpp" #include "utilities/globalDefinitions.hpp" @@ -55,6 +56,9 @@ class arrayOopDesc : public oopDesc { // Given a type, return true if elements of that type must be aligned to 64-bit. static bool element_type_should_be_aligned(BasicType type) { + if (EnableValhalla && type == T_FLAT_ELEMENT) { + return true; //CMH: tighten the alignment when removing T_FLAT_ELEMENT + } #ifdef _LP64 if (type == T_OBJECT || type == T_ARRAY) { return !UseCompressedOops; @@ -73,7 +77,7 @@ class arrayOopDesc : public oopDesc { // make sure it isn't called before UseCompressedOops is initialized. static int arrayoopdesc_hs = 0; if (arrayoopdesc_hs == 0) arrayoopdesc_hs = hs; - assert(arrayoopdesc_hs == hs, "header size can't change"); + // assert(arrayoopdesc_hs == hs, "header size can't change"); #endif // ASSERT return (int)hs; } @@ -132,6 +136,7 @@ class arrayOopDesc : public oopDesc { // 32 bit platforms when we convert it to a byte size. static int32_t max_array_length(BasicType type) { assert(type < T_CONFLICT, "wrong type"); + assert(type != T_FLAT_ELEMENT, "wrong type"); assert(type2aelembytes(type) != 0, "wrong type"); int hdr_size_in_bytes = base_offset_in_bytes(type); diff --git a/src/hotspot/share/oops/constMethodFlags.hpp b/src/hotspot/share/oops/constMethodFlags.hpp index 03d82cd5c74..c3fbd056ad4 100644 --- a/src/hotspot/share/oops/constMethodFlags.hpp +++ b/src/hotspot/share/oops/constMethodFlags.hpp @@ -60,7 +60,12 @@ class ConstMethodFlags { flag(jvmti_mount_transition , 1 << 18) \ flag(deprecated , 1 << 19) \ flag(deprecated_for_removal , 1 << 20) \ - flag(jvmti_hide_events , 1 << 21) \ + flag(has_scalarized_args , 1 << 21) \ + flag(has_scalarized_return , 1 << 22) \ + flag(c1_needs_stack_repair , 1 << 23) \ + flag(c2_needs_stack_repair , 1 << 24) \ + flag(mismatch , 1 << 25) \ + flag(jvmti_hide_events , 1 << 26) \ /* end of list */ #define CM_FLAGS_ENUM_NAME(name, value) _misc_##name = value, diff --git a/src/hotspot/share/oops/constantPool.cpp b/src/hotspot/share/oops/constantPool.cpp index 735968f7a95..c1baa149002 100644 --- a/src/hotspot/share/oops/constantPool.cpp +++ b/src/hotspot/share/oops/constantPool.cpp @@ -53,11 +53,13 @@ #include "oops/constantPool.inline.hpp" #include "oops/cpCache.inline.hpp" #include "oops/fieldStreams.inline.hpp" +#include "oops/flatArrayKlass.hpp" #include "oops/instanceKlass.hpp" #include "oops/klass.inline.hpp" #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" +#include "oops/refArrayOop.hpp" #include "oops/typeArrayOop.inline.hpp" #include "prims/jvmtiExport.hpp" #include "runtime/atomic.hpp" @@ -191,7 +193,7 @@ oop ConstantPool::resolved_reference_at(int index) const { // Use a CAS for multithreaded access oop ConstantPool::set_resolved_reference_at(int index, oop new_result) { assert(oopDesc::is_oop_or_null(new_result), "Must be oop"); - return resolved_references()->replace_if_null(index, new_result); + return refArrayOopDesc::cast(resolved_references())->replace_if_null(index, new_result); } // Create resolved_references array and mapping array for original cp indexes @@ -262,7 +264,7 @@ void ConstantPool::initialize_unresolved_klasses(ClassLoaderData* loader_data, T case JVM_CONSTANT_Class: case JVM_CONSTANT_UnresolvedClass: case JVM_CONSTANT_UnresolvedClassInError: - // All of these should have been reverted back to ClassIndex before calling + // All of these should have been reverted back to Unresolved before calling // this function. ShouldNotReachHere(); #endif @@ -478,6 +480,7 @@ static const char* get_type(Klass* k) { if (src_k->is_objArray_klass()) { src_k = ObjArrayKlass::cast(src_k)->bottom_klass(); assert(!src_k->is_objArray_klass(), "sanity"); + assert(src_k->is_instance_klass() || src_k->is_typeArray_klass(), "Sanity check"); } if (src_k->is_typeArray_klass()) { @@ -619,6 +622,12 @@ void ConstantPool::trace_class_resolution(const constantPoolHandle& this_cp, Kla } } +void check_is_inline_type(Klass* k, TRAPS) { + if (!k->is_inline_klass()) { + THROW(vmSymbols::java_lang_IncompatibleClassChangeError()); + } +} + Klass* ConstantPool::klass_at_impl(const constantPoolHandle& this_cp, int cp_index, TRAPS) { JavaThread* javaThread = THREAD; @@ -656,6 +665,7 @@ Klass* ConstantPool::klass_at_impl(const constantPoolHandle& this_cp, int cp_ind HandleMark hm(THREAD); Handle mirror_handle; Symbol* name = this_cp->symbol_at(name_index); + bool inline_type_signature = false; Handle loader (THREAD, this_cp->pool_holder()->class_loader()); Klass* k; @@ -664,6 +674,9 @@ Klass* ConstantPool::klass_at_impl(const constantPoolHandle& this_cp, int cp_ind JvmtiHideSingleStepping jhss(javaThread); k = SystemDictionary::resolve_or_fail(name, loader, true, THREAD); } // JvmtiHideSingleStepping jhss(javaThread); + if (inline_type_signature) { + name->decrement_refcount(); + } if (!HAS_PENDING_EXCEPTION) { // preserve the resolved klass from unloading @@ -672,6 +685,22 @@ Klass* ConstantPool::klass_at_impl(const constantPoolHandle& this_cp, int cp_ind verify_constant_pool_resolve(this_cp, k, THREAD); } + if (!HAS_PENDING_EXCEPTION && inline_type_signature) { + check_is_inline_type(k, THREAD); + } + + if (!HAS_PENDING_EXCEPTION) { + Klass* bottom_klass = nullptr; + if (k->is_objArray_klass()) { + bottom_klass = ObjArrayKlass::cast(k)->bottom_klass(); + assert(bottom_klass != nullptr, "Should be set"); + assert(bottom_klass->is_instance_klass() || bottom_klass->is_typeArray_klass(), "Sanity check"); + } else if (k->is_flatArray_klass()) { + bottom_klass = FlatArrayKlass::cast(k)->element_klass(); + assert(bottom_klass != nullptr, "Should be set"); + } + } + // Failed to resolve class. We must record the errors so that subsequent attempts // to resolve this constant pool entry fail with the same error (JVMS 5.4.3). if (HAS_PENDING_EXCEPTION) { diff --git a/src/hotspot/share/oops/constantPool.hpp b/src/hotspot/share/oops/constantPool.hpp index 9cbeb1245be..69e225a1984 100644 --- a/src/hotspot/share/oops/constantPool.hpp +++ b/src/hotspot/share/oops/constantPool.hpp @@ -303,7 +303,7 @@ class ConstantPool : public Metadata { // Storing constants - // For temporary use while constructing constant pool + // For temporary use while constructing constant pool. Used during a retransform/class redefinition as well. void klass_index_at_put(int cp_index, int name_index) { tag_at_put(cp_index, JVM_CONSTANT_ClassIndex); *int_at_addr(cp_index) = name_index; diff --git a/src/hotspot/share/oops/cpCache.cpp b/src/hotspot/share/oops/cpCache.cpp index 8944f85af0d..5a6275d8f74 100644 --- a/src/hotspot/share/oops/cpCache.cpp +++ b/src/hotspot/share/oops/cpCache.cpp @@ -210,6 +210,7 @@ void ConstantPoolCache::set_direct_or_vtable_call(Bytecodes::Code invoke_code, // // We set bytecode_2() to _invokevirtual. // See also interpreterRuntime.cpp. (8/25/2000) + invoke_code = Bytecodes::_invokevirtual; } else { assert(invoke_code == Bytecodes::_invokevirtual || (invoke_code == Bytecodes::_invokeinterface && @@ -225,7 +226,7 @@ void ConstantPoolCache::set_direct_or_vtable_call(Bytecodes::Code invoke_code, } } // set up for invokevirtual, even if linking for invokeinterface also: - method_entry->set_bytecode2(Bytecodes::_invokevirtual); + method_entry->set_bytecode2(invoke_code); } else { ShouldNotReachHere(); } diff --git a/src/hotspot/share/oops/cpCache.inline.hpp b/src/hotspot/share/oops/cpCache.inline.hpp index 8dec47a3c73..b6ce7f958a0 100644 --- a/src/hotspot/share/oops/cpCache.inline.hpp +++ b/src/hotspot/share/oops/cpCache.inline.hpp @@ -48,7 +48,7 @@ inline ConstantPoolCache::ConstantPoolCache(const intStack& invokedynamic_refere inline objArrayOop ConstantPoolCache::resolved_references() { oop obj = _resolved_references.resolve(); - assert(obj == nullptr || obj->is_objArray(), "should be objArray"); + assert(obj == nullptr || obj->is_refArray(), "should be refArray"); return (objArrayOop)obj; } diff --git a/src/hotspot/share/oops/fieldInfo.cpp b/src/hotspot/share/oops/fieldInfo.cpp index 8c1a9e46d40..c0fce4e7f7c 100644 --- a/src/hotspot/share/oops/fieldInfo.cpp +++ b/src/hotspot/share/oops/fieldInfo.cpp @@ -115,6 +115,12 @@ Array* FieldInfoStream::create_FieldInfoStream(GrowableArray* fie if (fi_ref->field_flags().is_contended()) { assert(fi_ref->contended_group() == fi.contended_group(), "Must be"); } + if (fi_ref->field_flags().is_flat()) { + assert(fi_ref->layout_kind() == fi.layout_kind(), "Must be"); + } + if (fi_ref->field_flags().has_null_marker()) { + assert(fi_ref->null_marker_offset() == fi.null_marker_offset(), "Must be"); + } } #endif // ASSERT diff --git a/src/hotspot/share/oops/fieldInfo.hpp b/src/hotspot/share/oops/fieldInfo.hpp index a98895da9cf..4fac76255cb 100644 --- a/src/hotspot/share/oops/fieldInfo.hpp +++ b/src/hotspot/share/oops/fieldInfo.hpp @@ -26,6 +26,7 @@ #define SHARE_OOPS_FIELDINFO_HPP #include "memory/allocation.hpp" +#include "oops/layoutKind.hpp" #include "oops/typeArrayOop.hpp" #include "utilities/unsigned5.hpp" #include "utilities/vmEnums.hpp" @@ -34,7 +35,6 @@ static constexpr u4 flag_mask(int pos) { return (u4)1 << pos; } - // Helper class for access to the underlying Array used to // store the compressed stream of FieldInfo template @@ -66,16 +66,20 @@ class FieldInfo { class FieldFlags { friend class VMStructs; friend class JVMCIVMStructs; + friend class FieldDesc; // The ordering of this enum is totally internal. More frequent // flags should come earlier than less frequent ones, because // earlier ones compress better. enum FieldFlagBitPosition { + _ff_null_free_inline_type, // field's type is an inline type and the field is null free + _ff_flat, // field is a flat field, optional section includes a layout kind + _ff_null_marker, // field has a null marker, optional section includes the null marker offset _ff_initialized, // has ConstantValue initializer attribute _ff_injected, // internal field injected by the JVM _ff_generic, // has a generic signature _ff_stable, // trust as stable b/c declared as @Stable - _ff_contended, // is contended, may have contention-group + _ff_contended // is contended, may have contention-group }; // Some but not all of the flag bits signal the presence of an @@ -83,7 +87,9 @@ class FieldInfo { static const u4 _optional_item_bit_mask = flag_mask((int)_ff_initialized) | flag_mask((int)_ff_generic) | - flag_mask((int)_ff_contended); + flag_mask((int)_ff_contended) | + flag_mask((int)_ff_flat) | + flag_mask((int)_ff_null_marker); // boilerplate: u4 _flags; @@ -106,16 +112,22 @@ class FieldInfo { } bool is_initialized() const { return test_flag(_ff_initialized); } + bool is_null_free_inline_type() const { return test_flag(_ff_null_free_inline_type); } + bool is_flat() const { return test_flag(_ff_flat); } bool is_injected() const { return test_flag(_ff_injected); } bool is_generic() const { return test_flag(_ff_generic); } bool is_stable() const { return test_flag(_ff_stable); } bool is_contended() const { return test_flag(_ff_contended); } + bool has_null_marker() const { return test_flag(_ff_null_marker); } void update_initialized(bool z) { update_flag(_ff_initialized, z); } + void update_null_free_inline_type(bool z) { update_flag(_ff_null_free_inline_type, z); } + void update_flat(bool z) { update_flag(_ff_flat, z); } void update_injected(bool z) { update_flag(_ff_injected, z); } void update_generic(bool z) { update_flag(_ff_generic, z); } void update_stable(bool z) { update_flag(_ff_stable, z); } void update_contended(bool z) { update_flag(_ff_contended, z); } + void update_null_marker(bool z) { update_flag(_ff_null_marker, z); } }; private: @@ -131,6 +143,8 @@ class FieldInfo { u4 _offset; // offset in object layout AccessFlags _access_flags; // access flags (JVM spec) FieldFlags _field_flags; // VM defined flags (not JVM spec) + LayoutKind _layout_kind; // LayoutKind if the field is flat + u4 _null_marker_offset; // null marker offset for this field in the object layout u2 _initializer_index; // index from ConstantValue attr (or 0) u2 _generic_signature_index; // index from GenericSignature attr (or 0) u2 _contention_group; // index from @Contended group item (or 0) @@ -143,6 +157,8 @@ class FieldInfo { _offset(0), _access_flags(AccessFlags(0)), _field_flags(FieldFlags(0)), + _layout_kind(LayoutKind::UNKNOWN), + _null_marker_offset(0), _initializer_index(0), _generic_signature_index(0), _contention_group(0) { } @@ -154,6 +170,8 @@ class FieldInfo { _offset(0), _access_flags(access_flags), _field_flags(fflags), + _layout_kind(LayoutKind::UNKNOWN), + _null_marker_offset(0), _initializer_index(initval_index), _generic_signature_index(0), _contention_group(0) { @@ -173,6 +191,16 @@ class FieldInfo { AccessFlags access_flags() const { return _access_flags; } FieldFlags field_flags() const { return _field_flags; } FieldFlags* field_flags_addr() { return &_field_flags; } + LayoutKind layout_kind() const { return _layout_kind; } + void set_layout_kind(LayoutKind lk) { + assert(_field_flags.is_flat(), "Must be"); + _layout_kind = lk; + } + u4 null_marker_offset() const { return _null_marker_offset; } + void set_null_marker_offset(u4 offset) { + _field_flags.update_null_marker(true); + _null_marker_offset = offset; + } u2 initializer_index() const { return _initializer_index; } void set_initializer_index(u2 index) { _initializer_index = index; } u2 generic_signature_index() const { return _generic_signature_index; } @@ -296,6 +324,8 @@ class FieldStatus { enum FieldStatusBitPosition { _fs_access_watched, // field access is watched by JVMTI _fs_modification_watched, // field modification is watched by JVMTI + _fs_strict_static_unset, // JVM_ACC_STRICT static field has not yet been set + _fs_strict_static_unread, // SS field has not yet been read _initialized_final_update // (static) final field updated outside (class) initializer }; @@ -314,12 +344,16 @@ class FieldStatus { FieldStatus(u1 flags) { _flags = flags; } u1 as_uint() { return _flags; } - bool is_access_watched() { return test_flag(_fs_access_watched); } - bool is_modification_watched() { return test_flag(_fs_modification_watched); } + bool is_access_watched() { return test_flag(_fs_access_watched); } + bool is_modification_watched() { return test_flag(_fs_modification_watched); } + bool is_strict_static_unset() { return test_flag(_fs_strict_static_unset); } + bool is_strict_static_unread() { return test_flag(_fs_strict_static_unread); } bool is_initialized_final_update() { return test_flag(_initialized_final_update); } void update_access_watched(bool z); void update_modification_watched(bool z); + void update_strict_static_unset(bool z); + void update_strict_static_unread(bool z); void update_initialized_final_update(bool z); }; diff --git a/src/hotspot/share/oops/fieldInfo.inline.hpp b/src/hotspot/share/oops/fieldInfo.inline.hpp index 842393729b2..b3eee00f342 100644 --- a/src/hotspot/share/oops/fieldInfo.inline.hpp +++ b/src/hotspot/share/oops/fieldInfo.inline.hpp @@ -29,6 +29,7 @@ #include "memory/metadataFactory.hpp" #include "oops/constantPool.hpp" +#include "oops/instanceKlass.hpp" #include "oops/symbol.hpp" #include "runtime/atomic.hpp" #include "utilities/checkedCast.hpp" @@ -96,10 +97,18 @@ inline void Mapper::map_field_info(const FieldInfo& fi) { if (fi.field_flags().is_contended()) { _consumer->accept_uint(fi.contention_group()); } + if (fi.field_flags().is_flat()) { + assert(fi.layout_kind() != LayoutKind::UNKNOWN, "Must be set"); + _consumer->accept_uint((uint32_t)fi.layout_kind()); + } + if (fi.field_flags().has_null_marker()) { + _consumer->accept_uint(fi.null_marker_offset()); + } } else { assert(fi.initializer_index() == 0, ""); assert(fi.generic_signature_index() == 0, ""); assert(fi.contention_group() == 0, ""); + assert(fi.null_marker_offset() == 0, ""); } } @@ -139,6 +148,14 @@ inline void FieldInfoReader::read_field_info(FieldInfo& fi) { } else { fi._contention_group = 0; } + if (fi._field_flags.is_flat()) { + fi._layout_kind = static_cast(next_uint()); + } + if (fi._field_flags.has_null_marker()) { + fi._null_marker_offset = next_uint(); + } else { + fi._null_marker_offset = 0; + } } inline FieldInfoReader& FieldInfoReader::skip_field_info() { @@ -149,7 +166,9 @@ inline FieldInfoReader& FieldInfoReader::skip_field_info() { if (ff.has_any_optionals()) { const int init_gen_cont = (ff.is_initialized() + ff.is_generic() + - ff.is_contended()); + ff.is_contended() + + ff.is_flat() + + ff.has_null_marker()); skip(init_gen_cont); // up to three items } return *this; @@ -185,8 +204,10 @@ inline void FieldStatus::update_flag(FieldStatusBitPosition pos, bool z) { else atomic_clear_bits(_flags, flag_mask(pos)); } -inline void FieldStatus::update_access_watched(bool z) { update_flag(_fs_access_watched, z); } -inline void FieldStatus::update_modification_watched(bool z) { update_flag(_fs_modification_watched, z); } +inline void FieldStatus::update_access_watched(bool z) { update_flag(_fs_access_watched, z); } +inline void FieldStatus::update_modification_watched(bool z) { update_flag(_fs_modification_watched, z); } +inline void FieldStatus::update_strict_static_unset(bool z) { update_flag(_fs_strict_static_unset, z); } +inline void FieldStatus::update_strict_static_unread(bool z) { update_flag(_fs_strict_static_unread, z); } inline void FieldStatus::update_initialized_final_update(bool z) { update_flag(_initialized_final_update, z); } #endif // SHARE_OOPS_FIELDINFO_INLINE_HPP diff --git a/src/hotspot/share/oops/fieldStreams.hpp b/src/hotspot/share/oops/fieldStreams.hpp index 08b04409a97..96703d62fce 100644 --- a/src/hotspot/share/oops/fieldStreams.hpp +++ b/src/hotspot/share/oops/fieldStreams.hpp @@ -39,6 +39,7 @@ // cases. // HierarchicalFieldStream allows to also iterate over fields of supertypes. class FieldStreamBase : public StackObj { + protected: const Array* _fieldinfo_stream; FieldInfoReader _reader; @@ -116,6 +117,14 @@ class FieldStreamBase : public StackObj { return field()->offset(); } + bool is_null_free_inline_type() { + return field()->field_flags().is_null_free_inline_type(); + } + + bool is_flat() const { + return field()->field_flags().is_flat(); + } + bool is_contended() const { return field()->is_contended(); } @@ -124,6 +133,10 @@ class FieldStreamBase : public StackObj { return field()->contended_group(); } + int null_marker_offset() const { + return field()->null_marker_offset(); + } + // Convenient methods const FieldInfo& to_FieldInfo() const { @@ -191,9 +204,87 @@ class AllFieldStream : public FieldStreamBase { AllFieldStream(const InstanceKlass* k): FieldStreamBase(k->fieldinfo_stream(), k->constants()) {} }; -// Iterate over fields including the ones declared in supertypes +/* Very generally, a base class for a stream adapter, a derived class just implements + * current_stream that returns a FieldStreamType, and this adapter takes care of providing + * the methods of FieldStreamBase. + * + * In practice, this is used to provide a stream over the fields of a class and its superclasses + * and interfaces. The derived class of HierarchicalFieldStreamBase decides in which order we iterate + * on the superclasses (and interfaces), and the template parameter FieldStreamType is the underlying + * stream we use to iterate over the fields each class. Methods such as done and next are still up to + * the derived classes, allowing them to iterate over the class hierarchy, but also skip elements that + * the underlying FieldStreamType would otherwise include. + */ +template +class HierarchicalFieldStreamBase : public StackObj { + virtual FieldStreamType& current_stream() = 0; + virtual const FieldStreamType& current_stream() const = 0; + +public: + // bridge functions from FieldStreamBase + int index() const { + return current_stream().index(); + } + + AccessFlags access_flags() const { + return current_stream().access_flags(); + } + + FieldInfo::FieldFlags field_flags() const { + return current_stream().field_flags(); + } + + Symbol* name() const { + return current_stream().name(); + } + + Symbol* signature() const { + return current_stream().signature(); + } + + Symbol* generic_signature() const { + return current_stream().generic_signature(); + } + + int offset() const { + return current_stream().offset(); + } + + bool is_contended() const { + return current_stream().is_contended(); + } + + int contended_group() const { + return current_stream().contended_group(); + } + + FieldInfo to_FieldInfo() { + return current_stream().to_FieldInfo(); + } + + fieldDescriptor& field_descriptor() const { + return current_stream().field_descriptor(); + } + + bool is_flat() const { + return current_stream().is_flat(); + } + + bool is_null_free_inline_type() { + return current_stream().is_null_free_inline_type(); + } + + int null_marker_offset() { + return current_stream().null_marker_offset(); + } +}; + +/* Iterate over fields including the ones declared in supertypes. + * Derived classes are traversed before base classes, and interfaces + * at the end. + */ template -class HierarchicalFieldStream : public StackObj { +class HierarchicalFieldStream final : public HierarchicalFieldStreamBase { private: const Array* _interfaces; InstanceKlass* _next_klass; // null indicates no more type to visit @@ -231,8 +322,11 @@ class HierarchicalFieldStream : public StackObj { } } + FieldStreamType& current_stream() override { return _current_stream; } + const FieldStreamType& current_stream() const override { return _current_stream; } + public: - HierarchicalFieldStream(InstanceKlass* klass) : + explicit HierarchicalFieldStream(InstanceKlass* klass) : _interfaces(klass->transitive_interfaces()), _next_klass(klass), _current_stream(FieldStreamType(klass)), @@ -246,49 +340,67 @@ class HierarchicalFieldStream : public StackObj { } bool done() const { return _next_klass == nullptr && _current_stream.done(); } +}; - // bridge functions from FieldStreamBase - - AccessFlags access_flags() const { - return _current_stream.access_flags(); - } - - FieldInfo::FieldFlags field_flags() const { - return _current_stream.field_flags(); - } - - Symbol* name() const { - return _current_stream.name(); - } - - Symbol* signature() const { - return _current_stream.signature(); +/* Iterates on the fields of a class and its super-class top-down (java.lang.Object first) + * Doesn't traverse interfaces for now, because it's not clear which order would make sense + * Let's decide when or if the need arises. Since we are not traversing interfaces, we + * wouldn't get all the static fields, and since the current use-case of this stream does not + * care about static fields, we restrict it to regular non-static fields. + */ +class TopDownHierarchicalNonStaticFieldStreamBase final : public HierarchicalFieldStreamBase { + GrowableArray* _super_types; // Self and super type, bottom up + int _current_stream_index; + JavaFieldStream _current_stream; + + void next_stream_if_needed() { + precond(_current_stream_index >= 0); + while (_current_stream.done()) { + _current_stream_index--; + if (_current_stream_index < 0) { + return; + } + _current_stream = JavaFieldStream(_super_types->at(_current_stream_index)); + } } - Symbol* generic_signature() const { - return _current_stream.generic_signature(); + GrowableArray* get_super_types(InstanceKlass* klass) { + auto super_types = new GrowableArray(); + do { + super_types->push(klass); + } while ((klass = klass->java_super()) != nullptr); + return super_types; } - int offset() const { - return _current_stream.offset(); + void raw_next() { + _current_stream.next(); + next_stream_if_needed(); } - bool is_contended() const { - return _current_stream.is_contended(); + void closest_non_static() { + while (!done() && access_flags().is_static()) { + raw_next(); + } } - int contended_group() const { - return _current_stream.contended_group(); - } + JavaFieldStream& current_stream() override { return _current_stream; } + const JavaFieldStream& current_stream() const override { return _current_stream; } - FieldInfo to_FieldInfo() { - return _current_stream.to_FieldInfo(); + public: + explicit TopDownHierarchicalNonStaticFieldStreamBase(InstanceKlass* klass) : + _super_types(get_super_types(klass)), + _current_stream_index(_super_types->length() - 1), + _current_stream(JavaFieldStream(_super_types->at(_current_stream_index))) { + next_stream_if_needed(); + closest_non_static(); } - fieldDescriptor& field_descriptor() const { - return _current_stream.field_descriptor(); + void next() { + raw_next(); + closest_non_static(); } + bool done() const { return _current_stream_index < 0; } }; #endif // SHARE_OOPS_FIELDSTREAMS_HPP diff --git a/src/hotspot/share/oops/flatArrayKlass.cpp b/src/hotspot/share/oops/flatArrayKlass.cpp new file mode 100644 index 00000000000..f77bf7914e9 --- /dev/null +++ b/src/hotspot/share/oops/flatArrayKlass.cpp @@ -0,0 +1,469 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "classfile/moduleEntry.hpp" +#include "classfile/packageEntry.hpp" +#include "classfile/symbolTable.hpp" +#include "classfile/systemDictionary.hpp" +#include "classfile/vmSymbols.hpp" +#include "gc/shared/collectedHeap.inline.hpp" +#include "memory/iterator.inline.hpp" +#include "memory/metadataFactory.hpp" +#include "memory/metaspaceClosure.hpp" +#include "memory/oopFactory.hpp" +#include "memory/resourceArea.hpp" +#include "memory/universe.hpp" +#include "oops/arrayKlass.inline.hpp" +#include "oops/arrayOop.hpp" +#include "oops/flatArrayKlass.hpp" +#include "oops/flatArrayOop.hpp" +#include "oops/flatArrayOop.inline.hpp" +#include "oops/inlineKlass.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/klass.inline.hpp" +#include "oops/objArrayKlass.hpp" +#include "oops/objArrayOop.inline.hpp" +#include "oops/oop.inline.hpp" +#include "oops/verifyOopClosure.hpp" +#include "runtime/arguments.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/mutexLocker.hpp" +#include "utilities/copy.hpp" +#include "utilities/macros.hpp" + +// Allocation... + +FlatArrayKlass::FlatArrayKlass(Klass* element_klass, Symbol* name, ArrayProperties props, LayoutKind lk) : + ObjArrayKlass(1, element_klass, name, Kind, props, markWord::flat_array_prototype(lk)) { + assert(element_klass->is_inline_klass(), "Expected Inline"); + assert(lk == LayoutKind::NON_ATOMIC_FLAT || lk == LayoutKind::ATOMIC_FLAT || lk == LayoutKind::NULLABLE_ATOMIC_FLAT, "Must be a flat layout"); + + set_element_klass(InlineKlass::cast(element_klass)); + set_class_loader_data(element_klass->class_loader_data()); + set_layout_kind(lk); + + set_layout_helper(array_layout_helper(InlineKlass::cast(element_klass), lk)); + assert(is_array_klass(), "sanity"); + assert(is_flatArray_klass(), "sanity"); + +#ifdef ASSERT + assert(layout_helper_is_array(layout_helper()), "Must be"); + assert(layout_helper_is_flatArray(layout_helper()), "Must be"); + assert(layout_helper_element_type(layout_helper()) == T_FLAT_ELEMENT, "Must be"); + assert(prototype_header().is_flat_array(), "Must be"); + switch(lk) { + case LayoutKind::NON_ATOMIC_FLAT: + case LayoutKind::ATOMIC_FLAT: + assert(layout_helper_is_null_free(layout_helper()), "Must be"); + assert(prototype_header().is_null_free_array(), "Must be"); + break; + case LayoutKind::NULLABLE_ATOMIC_FLAT: + assert(!layout_helper_is_null_free(layout_helper()), "Must be"); + assert(!prototype_header().is_null_free_array(), "Must be"); + break; + default: + ShouldNotReachHere(); + break; + } +#endif // ASSERT + +#ifndef PRODUCT + if (PrintFlatArrayLayout) { + print(); + } +#endif +} + +FlatArrayKlass* FlatArrayKlass::allocate_klass(Klass* eklass, ArrayProperties props, LayoutKind lk, TRAPS) { + guarantee((!Universe::is_bootstrapping() || vmClasses::Object_klass_loaded()), "Really ?!"); + assert(UseArrayFlattening, "Flatten array required"); + assert(MultiArray_lock->holds_lock(THREAD), "must hold lock after bootstrapping"); + + InlineKlass* element_klass = InlineKlass::cast(eklass); + assert(element_klass->must_be_atomic() || (!AlwaysAtomicAccesses), "Atomic by-default"); + + // Eagerly allocate the direct array supertype. + Klass* super_klass = nullptr; + Klass* element_super = element_klass->super(); + if (element_super != nullptr) { + // The element type has a direct super. E.g., String[] has direct super of Object[]. + super_klass = element_klass->array_klass(CHECK_NULL); + } + + Symbol* name = ArrayKlass::create_element_klass_array_name(element_klass, CHECK_NULL); + ClassLoaderData* loader_data = element_klass->class_loader_data(); + int size = ArrayKlass::static_size(FlatArrayKlass::header_size()); + FlatArrayKlass* vak = new (loader_data, size, THREAD) FlatArrayKlass(element_klass, name, props, lk); + + ModuleEntry* module = vak->module(); + assert(module != nullptr, "No module entry for array"); + complete_create_array_klass(vak, super_klass, module, CHECK_NULL); + + loader_data->add_class(vak); + + return vak; +} + +void FlatArrayKlass::initialize(TRAPS) { + element_klass()->initialize(THREAD); +} + +void FlatArrayKlass::metaspace_pointers_do(MetaspaceClosure* it) { + ObjArrayKlass::metaspace_pointers_do(it); +} + +// Oops allocation... +objArrayOop FlatArrayKlass::allocate_instance(int length, ArrayProperties props, TRAPS) { + assert(UseArrayFlattening, "Must be enabled"); + check_array_allocation_length(length, max_elements(), CHECK_NULL); + int size = flatArrayOopDesc::object_size(layout_helper(), length); + flatArrayOop array = (flatArrayOop) Universe::heap()->array_allocate(this, size, length, true, CHECK_NULL); + return array; +} + +oop FlatArrayKlass::multi_allocate(int rank, jint* last_size, TRAPS) { + // FlatArrays only have one dimension + ShouldNotReachHere(); +} + +jint FlatArrayKlass::array_layout_helper(InlineKlass* vk, LayoutKind lk) { + BasicType etype = T_FLAT_ELEMENT; + int esize = log2i_exact(round_up_power_of_2(vk->layout_size_in_bytes(lk))); + int hsize = arrayOopDesc::base_offset_in_bytes(etype); + bool null_free = lk != LayoutKind::NULLABLE_ATOMIC_FLAT; + int lh = Klass::array_layout_helper(_lh_array_tag_flat_value, null_free, hsize, etype, esize); + + assert(lh < (int)_lh_neutral_value, "must look like an array layout"); + assert(layout_helper_is_array(lh), "correct kind"); + assert(layout_helper_is_flatArray(lh), "correct kind"); + assert(!layout_helper_is_typeArray(lh), "correct kind"); + assert(layout_helper_is_null_free(lh) == null_free, "correct kind"); + assert(layout_helper_header_size(lh) == hsize, "correct decode"); + assert(layout_helper_element_type(lh) == etype, "correct decode"); + assert(layout_helper_log2_element_size(lh) == esize, "correct decode"); + assert((1 << esize) < BytesPerLong || is_aligned(hsize, HeapWordsPerLong), "unaligned base"); + + return lh; +} + +size_t FlatArrayKlass::oop_size(oop obj) const { + // In this assert, we cannot safely access the Klass* with compact headers, + // because size_given_klass() calls oop_size() on objects that might be + // concurrently forwarded, which would overwrite the Klass*. + // Also, why we need to pass this layout_helper() to flatArrayOop::object_size. + assert(UseCompactObjectHeaders || obj->is_flatArray(),"must be an flat array"); + flatArrayOop array = flatArrayOop(obj); + return array->object_size(layout_helper()); +} + +// For now return the maximum number of array elements that will not exceed: +// nof bytes = "max_jint * HeapWord" since the "oopDesc::oop_iterate_size" +// returns "int" HeapWords, need fix for JDK-4718400 and JDK-8233189 +jint FlatArrayKlass::max_elements() const { + // Check the max number of heap words limit first (because of int32_t in oopDesc_oop_size() etc) + size_t max_size = max_jint; + max_size -= (arrayOopDesc::base_offset_in_bytes(T_FLAT_ELEMENT) >> LogHeapWordSize); + max_size = align_down(max_size, MinObjAlignment); + max_size <<= LogHeapWordSize; // convert to max payload size in bytes + max_size >>= layout_helper_log2_element_size(_layout_helper); // divide by element size (in bytes) = max elements + // Within int32_t heap words, still can't exceed Java array element limit + if (max_size > max_jint) { + max_size = max_jint; + } + assert((max_size >> LogHeapWordSize) <= max_jint, "Overflow"); + return (jint) max_size; +} + +oop FlatArrayKlass::protection_domain() const { + return element_klass()->protection_domain(); +} + +// Temp hack having this here: need to move towards Access API +static bool needs_backwards_copy(arrayOop s, int src_pos, + arrayOop d, int dst_pos, int length) { + return (s == d) && (dst_pos > src_pos) && (dst_pos - src_pos) < length; +} + +void FlatArrayKlass::copy_array(arrayOop s, int src_pos, + arrayOop d, int dst_pos, int length, TRAPS) { + + assert(s->is_objArray() || s->is_flatArray(), "must be obj or flat array"); + + // Check destination + if ((!d->is_flatArray()) && (!d->is_objArray())) { + THROW(vmSymbols::java_lang_ArrayStoreException()); + } + + // Check if all offsets and lengths are non negative + if (src_pos < 0 || dst_pos < 0 || length < 0) { + THROW(vmSymbols::java_lang_ArrayIndexOutOfBoundsException()); + } + // Check if the ranges are valid + if ( (((unsigned int) length + (unsigned int) src_pos) > (unsigned int) s->length()) + || (((unsigned int) length + (unsigned int) dst_pos) > (unsigned int) d->length()) ) { + THROW(vmSymbols::java_lang_ArrayIndexOutOfBoundsException()); + } + // Check zero copy + if (length == 0) + return; + + ObjArrayKlass* sk = ObjArrayKlass::cast(s->klass()); + ObjArrayKlass* dk = ObjArrayKlass::cast(d->klass()); + Klass* d_elem_klass = dk->element_klass(); + Klass* s_elem_klass = sk->element_klass(); + /**** CMH: compare and contrast impl, re-factor once we find edge cases... ****/ + + if (sk->is_flatArray_klass()) { + assert(sk == this, "Unexpected call to copy_array"); + FlatArrayKlass* fsk = FlatArrayKlass::cast(sk); + // Check subtype, all src homogeneous, so just once + if (!s_elem_klass->is_subtype_of(d_elem_klass)) { + THROW(vmSymbols::java_lang_ArrayStoreException()); + } + + flatArrayOop sa = flatArrayOop(s); + InlineKlass* s_elem_vklass = element_klass(); + + // flatArray-to-flatArray + if (dk->is_flatArray_klass()) { + // element types MUST be exact, subtype check would be dangerous + if (d_elem_klass != this->element_klass()) { + THROW(vmSymbols::java_lang_ArrayStoreException()); + } + + FlatArrayKlass* fdk = FlatArrayKlass::cast(dk); + InlineKlass* vk = InlineKlass::cast(s_elem_klass); + flatArrayOop da = flatArrayOop(d); + int src_incr = fsk->element_byte_size(); + int dst_incr = fdk->element_byte_size(); + + if (fsk->layout_kind() == fdk->layout_kind()) { + assert(src_incr == dst_incr, "Must be"); + if (needs_backwards_copy(sa, src_pos, da, dst_pos, length)) { + address dst = (address) da->value_at_addr(dst_pos + length - 1, fdk->layout_helper()); + address src = (address) sa->value_at_addr(src_pos + length - 1, fsk->layout_helper()); + for (int i = 0; i < length; i++) { + // because source and destination have the same layout, bypassing the InlineKlass copy methods + // and call AccessAPI directly + HeapAccess<>::value_copy(src, dst, vk, fsk->layout_kind()); + dst -= dst_incr; + src -= src_incr; + } + } else { + // source and destination share same layout, direct copy from array to array is possible + address dst = (address) da->value_at_addr(dst_pos, fdk->layout_helper()); + address src = (address) sa->value_at_addr(src_pos, fsk->layout_helper()); + for (int i = 0; i < length; i++) { + // because source and destination have the same layout, bypassing the InlineKlass copy methods + // and call AccessAPI directly + HeapAccess<>::value_copy(src, dst, vk, fsk->layout_kind()); + dst += dst_incr; + src += src_incr; + } + } + } else { + flatArrayHandle hd(THREAD, da); + flatArrayHandle hs(THREAD, sa); + // source and destination layouts mismatch, simpler solution is to copy through an intermediate buffer (heap instance) + bool need_null_check = fsk->layout_kind() == LayoutKind::NULLABLE_ATOMIC_FLAT && fdk->layout_kind() != LayoutKind::NULLABLE_ATOMIC_FLAT; + oop buffer = vk->allocate_instance(CHECK); + address dst = (address) hd->value_at_addr(dst_pos, fdk->layout_helper()); + address src = (address) hs->value_at_addr(src_pos, fsk->layout_helper()); + for (int i = 0; i < length; i++) { + if (need_null_check) { + if (vk->is_payload_marked_as_null(src)) { + THROW(vmSymbols::java_lang_NullPointerException()); + } + } + vk->copy_payload_to_addr(src, vk->payload_addr(buffer), fsk->layout_kind(), true); + if (vk->has_nullable_atomic_layout()) { + // Setting null marker to not zero for non-nullable source layouts + vk->mark_payload_as_non_null(vk->payload_addr(buffer)); + } + vk->copy_payload_to_addr(vk->payload_addr(buffer), dst, fdk->layout_kind(), true); + dst += dst_incr; + src += src_incr; + } + } + } else { // flatArray-to-objArray + assert(dk->is_refArray_klass(), "Expected objArray here"); + // Need to allocate each new src elem payload -> dst oop + objArrayHandle dh(THREAD, (objArrayOop)d); + flatArrayHandle sh(THREAD, sa); + InlineKlass* vk = InlineKlass::cast(s_elem_klass); + for (int i = 0; i < length; i++) { + oop o = sh->obj_at(src_pos + i, CHECK); + dh->obj_at_put(dst_pos + i, o); + } + } + } else { + assert(s->is_objArray(), "Expected objArray"); + objArrayOop sa = objArrayOop(s); + assert(d->is_flatArray(), "Expected flatArray"); // objArray-to-flatArray + InlineKlass* d_elem_vklass = InlineKlass::cast(d_elem_klass); + flatArrayOop da = flatArrayOop(d); + FlatArrayKlass* fdk = FlatArrayKlass::cast(da->klass()); + InlineKlass* vk = InlineKlass::cast(d_elem_klass); + + for (int i = 0; i < length; i++) { + da->obj_at_put( dst_pos + i, sa->obj_at(src_pos + i), CHECK); + } + } +} + +ModuleEntry* FlatArrayKlass::module() const { + assert(element_klass() != nullptr, "FlatArrayKlass returned unexpected nullptr bottom_klass"); + // The array is defined in the module of its bottom class + return element_klass()->module(); +} + +PackageEntry* FlatArrayKlass::package() const { + assert(element_klass() != nullptr, "FlatArrayKlass returned unexpected nullptr bottom_klass"); + return element_klass()->package(); +} + +bool FlatArrayKlass::can_be_primary_super_slow() const { + return true; +} + +GrowableArray* FlatArrayKlass::compute_secondary_supers(int num_extra_slots, + Array* transitive_interfaces) { + assert(transitive_interfaces == nullptr, "sanity"); + // interfaces = { cloneable_klass, serializable_klass, elemSuper[], ... }; + Array* elem_supers = element_klass()->secondary_supers(); + int num_elem_supers = elem_supers == nullptr ? 0 : elem_supers->length(); + int num_secondaries = num_extra_slots + 2 + num_elem_supers; + GrowableArray* secondaries = new GrowableArray(num_elem_supers+2); + + secondaries->push(vmClasses::Cloneable_klass()); + secondaries->push(vmClasses::Serializable_klass()); + for (int i = 0; i < num_elem_supers; i++) { + Klass* elem_super = (Klass*) elem_supers->at(i); + Klass* array_super = elem_super->array_klass_or_null(); + assert(array_super != nullptr, "must already have been created"); + secondaries->push(array_super); + } + return secondaries; +} + +u2 FlatArrayKlass::compute_modifier_flags() const { + // The modifier for an flatArray is the same as its element + // With the addition of ACC_IDENTITY + u2 element_flags = element_klass()->compute_modifier_flags(); + + u2 identity_flag = (Arguments::enable_preview()) ? JVM_ACC_IDENTITY : 0; + + return (element_flags & (JVM_ACC_PUBLIC | JVM_ACC_PRIVATE | JVM_ACC_PROTECTED)) + | (identity_flag | JVM_ACC_ABSTRACT | JVM_ACC_FINAL); +} + +void FlatArrayKlass::print_on(outputStream* st) const { +#ifndef PRODUCT + assert(!is_refArray_klass(), "Unimplemented"); + + st->print("Flat Type Array: "); + Klass::print_on(st); + + st->print(" - element klass: "); + element_klass()->print_value_on(st); + st->cr(); + + int elem_size = element_byte_size(); + st->print(" - element size %i ", elem_size); + st->print("aligned layout size %i", 1 << layout_helper_log2_element_size(layout_helper())); + st->cr(); +#endif //PRODUCT +} + +void FlatArrayKlass::print_value_on(outputStream* st) const { + assert(is_klass(), "must be klass"); + + element_klass()->print_value_on(st); + st->print("[]"); +} + + +#ifndef PRODUCT +void FlatArrayKlass::oop_print_on(oop obj, outputStream* st) { + ArrayKlass::oop_print_on(obj, st); + flatArrayOop va = flatArrayOop(obj); + InlineKlass* vk = element_klass(); + int print_len = MIN2(va->length(), MaxElementPrintSize); + for(int index = 0; index < print_len; index++) { + int off = (address) va->value_at_addr(index, layout_helper()) - cast_from_oop
(obj); + st->print_cr(" - Index %3d offset %3d: ", index, off); + oop obj = cast_to_oop((address)va->value_at_addr(index, layout_helper()) - vk->payload_offset()); + FieldPrinter print_field(st, obj); + vk->do_nonstatic_fields(&print_field); + st->cr(); + } + int remaining = va->length() - print_len; + if (remaining > 0) { + st->print_cr(" - <%d more elements, increase MaxElementPrintSize to print>", remaining); + } +} +#endif //PRODUCT + +void FlatArrayKlass::oop_print_value_on(oop obj, outputStream* st) { + assert(obj->is_flatArray(), "must be flatArray"); + st->print("a "); + element_klass()->print_value_on(st); + int len = flatArrayOop(obj)->length(); + st->print("[%d] ", len); + obj->print_address_on(st); + if (PrintMiscellaneous && (WizardMode || Verbose)) { + int lh = layout_helper(); + st->print("{"); + for (int i = 0; i < len; i++) { + if (i > 4) { + st->print("..."); break; + } + st->print(" " INTPTR_FORMAT, (intptr_t)(void*)flatArrayOop(obj)->value_at_addr(i , lh)); + } + st->print(" }"); + } +} + +// Verification +class VerifyElementClosure: public BasicOopIterateClosure { + public: + virtual void do_oop(oop* p) { VerifyOopClosure::verify_oop.do_oop(p); } + virtual void do_oop(narrowOop* p) { VerifyOopClosure::verify_oop.do_oop(p); } +}; + +void FlatArrayKlass::oop_verify_on(oop obj, outputStream* st) { + ArrayKlass::oop_verify_on(obj, st); + guarantee(obj->is_flatArray(), "must be flatArray"); + + if (contains_oops()) { + flatArrayOop va = flatArrayOop(obj); + VerifyElementClosure ec; + va->oop_iterate(&ec); + } +} + +void FlatArrayKlass::verify_on(outputStream* st) { + ArrayKlass::verify_on(st); + guarantee(element_klass()->is_inline_klass(), "should be inline type klass"); +} diff --git a/src/hotspot/share/oops/flatArrayKlass.hpp b/src/hotspot/share/oops/flatArrayKlass.hpp new file mode 100644 index 00000000000..1292445cbd3 --- /dev/null +++ b/src/hotspot/share/oops/flatArrayKlass.hpp @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_OOPS_FLATARRAYKLASS_HPP +#define SHARE_VM_OOPS_FLATARRAYKLASS_HPP + +#include "classfile/classLoaderData.hpp" +#include "oops/arrayKlass.hpp" +#include "oops/inlineKlass.hpp" +#include "oops/objArrayKlass.hpp" +#include "utilities/macros.hpp" + +/** + * Array of inline types, gives a layout of typeArrayOop, but needs oops iterators + */ +class FlatArrayKlass : public ObjArrayKlass { + friend class Deoptimization; + friend class oopFactory; + friend class VMStructs; + + public: + static const KlassKind Kind = FlatArrayKlassKind; + + private: + // Constructor + FlatArrayKlass(Klass* element_klass, Symbol* name, ArrayProperties props, LayoutKind lk); + + LayoutKind _layout_kind; + + public: + + FlatArrayKlass() {} // used by CppVtableCloner::initialize() + + InlineKlass* element_klass() const { return InlineKlass::cast(_element_klass); } + void set_element_klass(Klass* k) { assert(k->is_inline_klass(), "Must be"); _element_klass = k; } + + LayoutKind layout_kind() const { return _layout_kind; } + void set_layout_kind(LayoutKind lk) { _layout_kind = lk; } + static ByteSize layout_kind_offset() { return in_ByteSize(offset_of(FlatArrayKlass, _layout_kind)); } + + // Casting from Klass* + static FlatArrayKlass* cast(Klass* k) { + assert(k->is_flatArray_klass(), "cast to FlatArrayKlass"); + return (FlatArrayKlass*) k; + } + + // klass allocation + static FlatArrayKlass* allocate_klass(Klass* element_klass, ArrayProperties props, LayoutKind lk, TRAPS); + + void initialize(TRAPS); + + ModuleEntry* module() const; + PackageEntry* package() const; + + bool can_be_primary_super_slow() const; + GrowableArray* compute_secondary_supers(int num_extra_slots, + Array* transitive_interfaces); + + int element_byte_size() const { return 1 << layout_helper_log2_element_size(_layout_helper); } + + bool is_flatArray_klass_slow() const { return true; } + + bool contains_oops() { + return element_klass()->contains_oops(); + } + + oop protection_domain() const; + + virtual void metaspace_pointers_do(MetaspaceClosure* iter); + + static jint array_layout_helper(InlineKlass* vklass, LayoutKind lk); // layout helper for values + + // sizing + static int header_size() { return sizeof(FlatArrayKlass)/HeapWordSize; } + int size() const { return ArrayKlass::static_size(header_size()); } + + jint max_elements() const; + + size_t oop_size(oop obj) const; + + // Oop Allocation + private: + objArrayOop allocate_instance(int length, ArrayProperties props, TRAPS); + public: + oop multi_allocate(int rank, jint* sizes, TRAPS); + + // Naming + const char* internal_name() const { return external_name(); } + + // Copying + void copy_array(arrayOop s, int src_pos, arrayOop d, int dst_pos, int length, TRAPS); + + // GC specific object visitors + template + inline void oop_oop_iterate(oop obj, OopClosureType* closure); + + template + inline void oop_oop_iterate_reverse(oop obj, OopClosureType* closure); + + template + inline void oop_oop_iterate_bounded(oop obj, OopClosureType* closure, MemRegion mr); + + template + inline void oop_oop_iterate_elements(flatArrayOop a, OopClosureType* closure); + +private: + template + inline void oop_oop_iterate_elements_specialized(flatArrayOop a, OopClosureType* closure); + + template + inline void oop_oop_iterate_elements_bounded(flatArrayOop a, OopClosureType* closure, MemRegion mr); + + template + inline void oop_oop_iterate_elements_specialized_bounded(flatArrayOop a, OopClosureType* closure, void* low, void* high); + + public: + u2 compute_modifier_flags() const; + + // Printing + void print_on(outputStream* st) const; + void print_value_on(outputStream* st) const; + + void oop_print_value_on(oop obj, outputStream* st); +#ifndef PRODUCT + void oop_print_on(oop obj, outputStream* st); +#endif + + // Verification + void verify_on(outputStream* st); + void oop_verify_on(oop obj, outputStream* st); +}; + +#endif diff --git a/src/hotspot/share/oops/flatArrayKlass.inline.hpp b/src/hotspot/share/oops/flatArrayKlass.inline.hpp new file mode 100644 index 00000000000..6f7c0639d93 --- /dev/null +++ b/src/hotspot/share/oops/flatArrayKlass.inline.hpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +#ifndef SHARE_VM_OOPS_FLATARRAYKLASS_INLINE_HPP +#define SHARE_VM_OOPS_FLATARRAYKLASS_INLINE_HPP + +#include "oops/flatArrayKlass.hpp" + +#include "memory/iterator.hpp" +#include "memory/memRegion.hpp" +#include "oops/arrayKlass.hpp" +#include "oops/flatArrayOop.hpp" +#include "oops/flatArrayOop.inline.hpp" +#include "oops/inlineKlass.hpp" +#include "oops/inlineKlass.inline.hpp" +#include "oops/klass.hpp" +#include "oops/oop.inline.hpp" +#include "utilities/devirtualizer.inline.hpp" +#include "utilities/macros.hpp" + +/* + * Warning incomplete: requires embedded oops, not yet enabled, so consider this a "sketch-up" of oop iterators + */ + +template +void FlatArrayKlass::oop_oop_iterate_elements_specialized(flatArrayOop a, + OopClosureType* closure) { + assert(contains_oops(), "Nothing to iterate"); + + const int shift = Klass::layout_helper_log2_element_size(layout_helper()); + const int addr_incr = 1 << shift; + uintptr_t elem_addr = (uintptr_t) a->base(); + const uintptr_t stop_addr = elem_addr + ((uintptr_t)a->length() << shift); + const int oop_offset = element_klass()->payload_offset(); + + while (elem_addr < stop_addr) { + element_klass()->oop_iterate_specialized((address)(elem_addr - oop_offset), closure); + elem_addr += addr_incr; + } +} + +template +void FlatArrayKlass::oop_oop_iterate_elements_specialized_bounded(flatArrayOop a, + OopClosureType* closure, + void* lo, void* hi) { + assert(contains_oops(), "Nothing to iterate"); + + const int shift = Klass::layout_helper_log2_element_size(layout_helper()); + const int addr_incr = 1 << shift; + uintptr_t elem_addr = (uintptr_t)a->base(); + uintptr_t stop_addr = elem_addr + ((uintptr_t)a->length() << shift); + const int oop_offset = element_klass()->payload_offset(); + + if (elem_addr < (uintptr_t) lo) { + uintptr_t diff = ((uintptr_t) lo) - elem_addr; + elem_addr += (diff >> shift) << shift; + } + if (stop_addr > (uintptr_t) hi) { + uintptr_t diff = stop_addr - ((uintptr_t) hi); + stop_addr -= (diff >> shift) << shift; + } + + const uintptr_t end = stop_addr; + while (elem_addr < end) { + element_klass()->oop_iterate_specialized_bounded((address)(elem_addr - oop_offset), closure, lo, hi); + elem_addr += addr_incr; + } +} + +template +void FlatArrayKlass::oop_oop_iterate_elements(flatArrayOop a, OopClosureType* closure) { + if (contains_oops()) { + oop_oop_iterate_elements_specialized(a, closure); + } +} + +template +void FlatArrayKlass::oop_oop_iterate(oop obj, OopClosureType* closure) { + assert(obj->klass()->is_flatArray_klass(),"must be a flat array"); + flatArrayOop a = flatArrayOop(obj); + + if (Devirtualizer::do_metadata(closure)) { + Devirtualizer::do_klass(closure, obj->klass()); + Devirtualizer::do_klass(closure, FlatArrayKlass::cast(obj->klass())->element_klass()); + } + + oop_oop_iterate_elements(a, closure); +} + +template +void FlatArrayKlass::oop_oop_iterate_reverse(oop obj, OopClosureType* closure) { + // TODO + oop_oop_iterate(obj, closure); +} + +template +void FlatArrayKlass::oop_oop_iterate_elements_bounded(flatArrayOop a, OopClosureType* closure, MemRegion mr) { + if (contains_oops()) { + oop_oop_iterate_elements_specialized_bounded(a, closure, mr.start(), mr.end()); + } +} + + +template +void FlatArrayKlass::oop_oop_iterate_bounded(oop obj, OopClosureType* closure, MemRegion mr) { + flatArrayOop a = flatArrayOop(obj); + if (Devirtualizer::do_metadata(closure)) { + Devirtualizer::do_klass(closure, a->klass()); + Devirtualizer::do_klass(closure, FlatArrayKlass::cast(obj->klass())->element_klass()); + } + oop_oop_iterate_elements_bounded(a, closure, mr); +} + +#endif // SHARE_VM_OOPS_FLATARRAYKLASS_INLINE_HPP diff --git a/src/hotspot/share/oops/flatArrayOop.cpp b/src/hotspot/share/oops/flatArrayOop.cpp new file mode 100644 index 00000000000..519d37d92ef --- /dev/null +++ b/src/hotspot/share/oops/flatArrayOop.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + + +#include "flatArrayKlass.inline.hpp" +#include "flatArrayOop.hpp" + diff --git a/src/hotspot/share/oops/flatArrayOop.hpp b/src/hotspot/share/oops/flatArrayOop.hpp new file mode 100644 index 00000000000..5c86397e706 --- /dev/null +++ b/src/hotspot/share/oops/flatArrayOop.hpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_OOPS_FLATARRAYOOP_HPP +#define SHARE_VM_OOPS_FLATARRAYOOP_HPP + +#include "oops/arrayOop.hpp" +#include "oops/inlineKlass.hpp" +#include "oops/klass.hpp" +#include "oops/objArrayOop.hpp" +#include "runtime/handles.hpp" + +// A flatArrayOop points to a flat array containing inline types (no indirection). +// It may include embedded oops in its elements. + +class flatArrayOopDesc : public objArrayOopDesc { + + public: + void* base() const; + void* value_at_addr(int index, jint lh) const; + + inline oop obj_at(int index) const; + inline oop obj_at(int index, TRAPS) const; + inline void obj_at_put(int index, oop value); + inline void obj_at_put(int index, oop value, TRAPS); + + // Sizing + static size_t element_size(int lh, int nof_elements) { + size_t sz = (size_t) nof_elements; + return sz << Klass::layout_helper_log2_element_size(lh); + } + + static int object_size(int lh, int length) { + julong size_in_bytes = arrayOopDesc::base_offset_in_bytes(Klass::layout_helper_element_type(lh)); + size_in_bytes += element_size(lh, length); + julong size_in_words = ((size_in_bytes + (HeapWordSize-1)) >> LogHeapWordSize); + assert(size_in_words <= (julong)max_jint, "no overflow"); + return align_object_size((intptr_t)size_in_words); + } + + int object_size(int lh) const; + +}; + +// See similar requirement for oopDesc. +static_assert(std::is_trivially_default_constructible::value, "required"); + +#endif // SHARE_VM_OOPS_FLATARRAYOOP_HPP diff --git a/src/hotspot/share/oops/flatArrayOop.inline.hpp b/src/hotspot/share/oops/flatArrayOop.inline.hpp new file mode 100644 index 00000000000..67b95dabab0 --- /dev/null +++ b/src/hotspot/share/oops/flatArrayOop.inline.hpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_OOPS_FLATARRAYOOP_INLINE_HPP +#define SHARE_VM_OOPS_FLATARRAYOOP_INLINE_HPP + +#include "oops/flatArrayOop.hpp" + +#include "classfile/vmSymbols.hpp" +#include "oops/access.inline.hpp" +#include "oops/inlineKlass.inline.hpp" +#include "oops/oop.inline.hpp" +#include "runtime/globals.hpp" + +inline void* flatArrayOopDesc::base() const { return arrayOopDesc::base(T_FLAT_ELEMENT); } + +inline void* flatArrayOopDesc::value_at_addr(int index, jint lh) const { + assert(is_within_bounds(index), "index out of bounds"); + + address addr = (address) base(); + addr += (index << Klass::layout_helper_log2_element_size(lh)); + return (void*) addr; +} + +inline int flatArrayOopDesc::object_size(int lh) const { + return object_size(lh, length()); +} + +inline oop flatArrayOopDesc::obj_at(int index) const { + EXCEPTION_MARK; + return obj_at(index, THREAD); +} + +inline oop flatArrayOopDesc::obj_at(int index, TRAPS) const { + assert(is_within_bounds(index), "index %d out of bounds %d", index, length()); + FlatArrayKlass* faklass = FlatArrayKlass::cast(klass()); + InlineKlass* vk = InlineKlass::cast(faklass->element_klass()); + int offset = ((char*)value_at_addr(index, faklass->layout_helper())) - ((char*)(oopDesc*)this); + oop res = vk->read_payload_from_addr((oopDesc*)this, offset, faklass->layout_kind(), CHECK_NULL); + return res; +} + +inline void flatArrayOopDesc::obj_at_put(int index, oop value) { + EXCEPTION_MARK; // What if the caller is not a Java Thread? + obj_at_put(index, value, THREAD); +} + +inline void flatArrayOopDesc::obj_at_put(int index, oop value, TRAPS) { + assert(is_within_bounds(index), "index %d out of bounds %d", index, length()); + FlatArrayKlass* faklass = FlatArrayKlass::cast(klass()); + InlineKlass* vk = InlineKlass::cast(faklass->element_klass()); + if (value != nullptr) { + if (value->klass() != vk) { + THROW(vmSymbols::java_lang_ArrayStoreException()); + } + } else if(is_null_free_array()) { + THROW_MSG(vmSymbols::java_lang_NullPointerException(), "Cannot store null in a null-restricted array"); + } + vk->write_value_to_addr(value, value_at_addr(index, faklass->layout_helper()), faklass->layout_kind(), true, CHECK); +} + +#endif // SHARE_VM_OOPS_FLATARRAYOOP_INLINE_HPP diff --git a/src/hotspot/share/oops/generateOopMap.cpp b/src/hotspot/share/oops/generateOopMap.cpp index 97d8bf3d526..970a48f3d10 100644 --- a/src/hotspot/share/oops/generateOopMap.cpp +++ b/src/hotspot/share/oops/generateOopMap.cpp @@ -138,8 +138,9 @@ class ComputeCallStack : public SignatureIterator { _idx = 0; _effect = effect; - if (!is_static) + if (!is_static) { effect[_idx++] = CellTypeState::ref; + } do_parameters_on(this); @@ -1600,11 +1601,11 @@ void GenerateOopMap::interp1(BytecodeStream *itr) { case Bytecodes::_getfield: do_field(true, false, itr->get_index_u2(), itr->bci(), itr->code()); break; case Bytecodes::_putfield: do_field(false, false, itr->get_index_u2(), itr->bci(), itr->code()); break; + case Bytecodes::_invokeinterface: case Bytecodes::_invokevirtual: - case Bytecodes::_invokespecial: do_method(false, false, itr->get_index_u2(), itr->bci(), itr->code()); break; - case Bytecodes::_invokestatic: do_method(true, false, itr->get_index_u2(), itr->bci(), itr->code()); break; - case Bytecodes::_invokedynamic: do_method(true, false, itr->get_index_u4(), itr->bci(), itr->code()); break; - case Bytecodes::_invokeinterface: do_method(false, true, itr->get_index_u2(), itr->bci(), itr->code()); break; + case Bytecodes::_invokespecial: do_method(false, itr->get_index_u2(), itr->bci(), itr->code()); break; + case Bytecodes::_invokestatic: do_method(true , itr->get_index_u2(), itr->bci(), itr->code()); break; + case Bytecodes::_invokedynamic: do_method(true , itr->get_index_u4(), itr->bci(), itr->code()); break; case Bytecodes::_newarray: case Bytecodes::_anewarray: pp_new_ref(vCTS, itr->bci()); break; case Bytecodes::_checkcast: do_checkcast(); break; @@ -1624,6 +1625,7 @@ void GenerateOopMap::interp1(BytecodeStream *itr) { case Bytecodes::_areturn: do_return_monitor_check(); ppop1(refCTS); break; + case Bytecodes::_ifnull: case Bytecodes::_ifnonnull: ppop1(refCTS); break; case Bytecodes::_multianewarray: do_multianewarray(*(itr->bcp()+3), itr->bci()); break; @@ -1949,13 +1951,15 @@ void GenerateOopMap::do_field(int is_get, int is_static, int idx, int bci, Bytec out = epsilonCTS; i = copy_cts(in, eff); } - if (!is_static) in[i++] = CellTypeState::ref; + if (!is_static) { + in[i++] = CellTypeState::ref; + } in[i] = CellTypeState::bottom; assert(i<=3, "sanity check"); pp(in, out); } -void GenerateOopMap::do_method(int is_static, int is_interface, int idx, int bci, Bytecodes::Code bc) { +void GenerateOopMap::do_method(int is_static, int idx, int bci, Bytecodes::Code bc) { // Dig up signature for field in constant pool ConstantPool* cp = _method->constants(); Symbol* signature = cp->signature_ref_at(idx, bc); diff --git a/src/hotspot/share/oops/generateOopMap.hpp b/src/hotspot/share/oops/generateOopMap.hpp index 0da3779d463..06836290fe7 100644 --- a/src/hotspot/share/oops/generateOopMap.hpp +++ b/src/hotspot/share/oops/generateOopMap.hpp @@ -91,7 +91,7 @@ class CellTypeState { unsigned int _state; // Masks for separating the BITS and INFO portions of a CellTypeState - enum { info_mask = right_n_bits(28), + enum { info_mask = right_n_bits(27), bits_mask = (int)(~info_mask) }; // These constant are used for manipulating the BITS portion of a @@ -104,18 +104,23 @@ class CellTypeState { // These constants are used for manipulating the INFO portion of a // CellTypeState - enum { top_info_bit = nth_bit(27), - not_bottom_info_bit = nth_bit(26), - info_data_mask = right_n_bits(26), + enum { top_info_bit = nth_bit(26), + not_bottom_info_bit = nth_bit(25), + info_data_mask = right_n_bits(25), info_conflict = info_mask }; // Within the INFO data, these values are used to distinguish different // kinds of references. - enum { ref_not_lock_bit = nth_bit(25), // 0 if this reference is locked as a monitor - ref_slot_bit = nth_bit(24), // 1 if this reference is a "slot" reference, + enum { ref_not_lock_bit = nth_bit(24), // 0 if this reference is locked as a monitor + ref_slot_bit = nth_bit(23), // 1 if this reference is a "slot" reference, // 0 if it is a "line" reference. - ref_data_mask = right_n_bits(24) }; + ref_data_mask = right_n_bits(23) }; + // Within the INFO data, these values are used to distinguish different + // kinds of value types. + enum { valuetype_slot_bit = nth_bit(24), // 1 if this reference is a "slot" value type, + // 0 if it is a "line" value type. + valuetype_data_mask = right_n_bits(24) }; // These values are used to initialize commonly used CellTypeState // constants. @@ -397,7 +402,7 @@ class GenerateOopMap { void do_astore (int idx); void do_jsr (int delta); void do_field (int is_get, int is_static, int idx, int bci, Bytecodes::Code bc); - void do_method (int is_static, int is_interface, int idx, int bci, Bytecodes::Code bc); + void do_method (int is_static, int idx, int bci, Bytecodes::Code bc); void do_multianewarray (int dims, int bci); void do_monitorenter (int bci); void do_monitorexit (int bci); diff --git a/src/hotspot/share/oops/inlineKlass.cpp b/src/hotspot/share/oops/inlineKlass.cpp new file mode 100644 index 00000000000..e2f64c39a70 --- /dev/null +++ b/src/hotspot/share/oops/inlineKlass.cpp @@ -0,0 +1,676 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "cds/archiveUtils.hpp" +#include "cds/cdsConfig.hpp" +#include "classfile/vmSymbols.hpp" +#include "code/codeCache.hpp" +#include "gc/shared/barrierSet.hpp" +#include "gc/shared/collectedHeap.inline.hpp" +#include "gc/shared/gcLocker.inline.hpp" +#include "interpreter/interpreter.hpp" +#include "logging/log.hpp" +#include "memory/metadataFactory.hpp" +#include "memory/metaspaceClosure.hpp" +#include "oops/access.hpp" +#include "oops/arrayKlass.hpp" +#include "oops/compressedOops.inline.hpp" +#include "oops/fieldStreams.inline.hpp" +#include "oops/flatArrayKlass.hpp" +#include "oops/inlineKlass.inline.hpp" +#include "oops/instanceKlass.inline.hpp" +#include "oops/method.hpp" +#include "oops/objArrayKlass.hpp" +#include "oops/oop.inline.hpp" +#include "oops/refArrayKlass.hpp" +#include "runtime/fieldDescriptor.inline.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/safepointVerifiers.hpp" +#include "runtime/sharedRuntime.hpp" +#include "runtime/signature.hpp" +#include "runtime/thread.inline.hpp" +#include "utilities/copy.hpp" +#include "utilities/stringUtils.hpp" + + // Constructor +InlineKlass::InlineKlass(const ClassFileParser& parser) + : InstanceKlass(parser, InlineKlass::Kind, markWord::inline_type_prototype()) { + assert(is_inline_klass(), "sanity"); + assert(prototype_header().is_inline_type(), "sanity"); +} + +InlineKlass::InlineKlass() { + assert(CDSConfig::is_dumping_archive() || UseSharedSpaces, "only for CDS"); +} + +void InlineKlass::init_fixed_block() { + _adr_inlineklass_fixed_block = inlineklass_static_block(); + // Addresses used for inline type calling convention + *((Array**)adr_extended_sig()) = nullptr; + *((Array**)adr_return_regs()) = nullptr; + *((address*)adr_pack_handler()) = nullptr; + *((address*)adr_pack_handler_jobject()) = nullptr; + *((address*)adr_unpack_handler()) = nullptr; + assert(pack_handler() == nullptr, "pack handler not null"); + set_null_reset_value_offset(0); + set_payload_offset(-1); + set_payload_size_in_bytes(-1); + set_payload_alignment(-1); + set_non_atomic_size_in_bytes(-1); + set_non_atomic_alignment(-1); + set_atomic_size_in_bytes(-1); + set_nullable_size_in_bytes(-1); + set_null_marker_offset(-1); +} + +void InlineKlass::set_null_reset_value(oop val) { + assert(val != nullptr, "Sanity check"); + assert(oopDesc::is_oop(val), "Sanity check"); + assert(val->is_inline_type(), "Sanity check"); + assert(val->klass() == this, "sanity check"); + java_mirror()->obj_field_put(null_reset_value_offset(), val); +} + +instanceOop InlineKlass::allocate_instance(TRAPS) { + int size = size_helper(); // Query before forming handle. + + instanceOop oop = (instanceOop)Universe::heap()->obj_allocate(this, size, CHECK_NULL); + assert(oop->mark().is_inline_type(), "Expected inline type"); + return oop; +} + +instanceOop InlineKlass::allocate_instance_buffer(TRAPS) { + int size = size_helper(); // Query before forming handle. + + instanceOop oop = (instanceOop)Universe::heap()->obj_buffer_allocate(this, size, CHECK_NULL); + assert(oop->mark().is_inline_type(), "Expected inline type"); + return oop; +} + +int InlineKlass::nonstatic_oop_count() { + int oops = 0; + int map_count = nonstatic_oop_map_count(); + OopMapBlock* block = start_of_nonstatic_oop_maps(); + OopMapBlock* end = block + map_count; + while (block != end) { + oops += block->count(); + block++; + } + return oops; +} + +int InlineKlass::layout_size_in_bytes(LayoutKind kind) const { + switch(kind) { + case LayoutKind::NON_ATOMIC_FLAT: + assert(has_non_atomic_layout(), "Layout not available"); + return non_atomic_size_in_bytes(); + break; + case LayoutKind::ATOMIC_FLAT: + assert(has_atomic_layout(), "Layout not available"); + return atomic_size_in_bytes(); + break; + case LayoutKind::NULLABLE_ATOMIC_FLAT: + assert(has_nullable_atomic_layout(), "Layout not available"); + return nullable_atomic_size_in_bytes(); + break; + case LayoutKind::BUFFERED: + return payload_size_in_bytes(); + break; + default: + ShouldNotReachHere(); + } +} + +int InlineKlass::layout_alignment(LayoutKind kind) const { + switch(kind) { + case LayoutKind::NON_ATOMIC_FLAT: + assert(has_non_atomic_layout(), "Layout not available"); + return non_atomic_alignment(); + break; + case LayoutKind::ATOMIC_FLAT: + assert(has_atomic_layout(), "Layout not available"); + return atomic_size_in_bytes(); + break; + case LayoutKind::NULLABLE_ATOMIC_FLAT: + assert(has_nullable_atomic_layout(), "Layout not available"); + return nullable_atomic_size_in_bytes(); + break; + case LayoutKind::BUFFERED: + return payload_alignment(); + break; + default: + ShouldNotReachHere(); + } +} + +bool InlineKlass::is_layout_supported(LayoutKind lk) { + switch(lk) { + case LayoutKind::NON_ATOMIC_FLAT: + return has_non_atomic_layout(); + break; + case LayoutKind::ATOMIC_FLAT: + return has_atomic_layout(); + break; + case LayoutKind::NULLABLE_ATOMIC_FLAT: + return has_nullable_atomic_layout(); + break; + case LayoutKind::BUFFERED: + return true; + break; + default: + ShouldNotReachHere(); + } +} + +void InlineKlass::copy_payload_to_addr(void* src, void* dst, LayoutKind lk, bool dest_is_initialized) { + assert(is_layout_supported(lk), "Unsupported layout"); + assert(lk != LayoutKind::REFERENCE && lk != LayoutKind::UNKNOWN, "Sanity check"); + switch(lk) { + case LayoutKind::NULLABLE_ATOMIC_FLAT: { + if (is_payload_marked_as_null((address)src)) { + if (!contains_oops()) { + mark_payload_as_null((address)dst); + return; + } + // copy null_reset value to dest + if (dest_is_initialized) { + HeapAccess<>::value_copy(payload_addr(null_reset_value()), dst, this, lk); + } else { + HeapAccess::value_copy(payload_addr(null_reset_value()), dst, this, lk); + } + } else { + // Copy has to be performed, even if this is an empty value, because of the null marker + mark_payload_as_non_null((address)src); + if (dest_is_initialized) { + HeapAccess<>::value_copy(src, dst, this, lk); + } else { + HeapAccess::value_copy(src, dst, this, lk); + } + } + } + break; + case LayoutKind::BUFFERED: + case LayoutKind::ATOMIC_FLAT: + case LayoutKind::NON_ATOMIC_FLAT: { + if (is_empty_inline_type()) return; // nothing to do + if (dest_is_initialized) { + HeapAccess<>::value_copy(src, dst, this, lk); + } else { + HeapAccess::value_copy(src, dst, this, lk); + } + } + break; + default: + ShouldNotReachHere(); + } +} + +oop InlineKlass::read_payload_from_addr(const oop src, int offset, LayoutKind lk, TRAPS) { + assert(src != nullptr, "Must be"); + assert(is_layout_supported(lk), "Unsupported layout"); + switch(lk) { + case LayoutKind::NULLABLE_ATOMIC_FLAT: { + if (is_payload_marked_as_null((address)((char*)(oopDesc*)src + offset))) { + return nullptr; + } + } // Fallthrough + case LayoutKind::BUFFERED: + case LayoutKind::ATOMIC_FLAT: + case LayoutKind::NON_ATOMIC_FLAT: { + Handle obj_h(THREAD, src); + oop res = allocate_instance_buffer(CHECK_NULL); + copy_payload_to_addr((void*)(cast_from_oop(obj_h()) + offset), payload_addr(res), lk, false); + if (lk == LayoutKind::NULLABLE_ATOMIC_FLAT) { + if(is_payload_marked_as_null(payload_addr(res))) { + return nullptr; + } + } + return res; + } + break; + default: + ShouldNotReachHere(); + } +} + +void InlineKlass::write_value_to_addr(oop src, void* dst, LayoutKind lk, bool dest_is_initialized, TRAPS) { + void* src_addr = nullptr; + if (src == nullptr) { + if (lk != LayoutKind::NULLABLE_ATOMIC_FLAT) { + THROW_MSG(vmSymbols::java_lang_NullPointerException(), "Value is null"); + } + // Writing null to a nullable flat field/element is usually done by writing + // the whole pre-allocated null_reset_value at the payload address to ensure + // that the null marker and all potential oops are reset to "zeros". + // However, the null_reset_value is allocated during class initialization. + // If the current value of the field is null, it is possible that the class + // of the field has not been initialized yet and thus the null_reset_value + // might not be available yet. + // Writing null over an already null value should not trigger class initialization. + // The solution is to detect null being written over null cases and return immediately + // (writing null over null is a no-op from a field modification point of view) + if (is_payload_marked_as_null((address)dst)) return; + src_addr = payload_addr(null_reset_value()); + } else { + src_addr = payload_addr(src); + if (lk == LayoutKind::NULLABLE_ATOMIC_FLAT) { + mark_payload_as_non_null((address)src_addr); + } + } + copy_payload_to_addr(src_addr, dst, lk, dest_is_initialized); +} + +// Arrays of... + +bool InlineKlass::maybe_flat_in_array() { + if (!UseArrayFlattening) { + return false; + } + // Too many embedded oops + if ((FlatArrayElementMaxOops >= 0) && (nonstatic_oop_count() > FlatArrayElementMaxOops)) { + return false; + } + // No flat layout? + if (!has_nullable_atomic_layout() && !has_atomic_layout() && !has_non_atomic_layout()) { + return false; + } + return true; +} + +// Inline type arguments are not passed by reference, instead each +// field of the inline type is passed as an argument. This helper +// function collects the flat field (recursively) +// in a list. Included with the field's type is +// the offset of each field in the inline type: i2c and c2i adapters +// need that to load or store fields. Finally, the list of fields is +// sorted in order of increasing offsets: the adapters and the +// compiled code need to agree upon the order of fields. +// +// The list of basic types that is returned starts with a T_METADATA +// and ends with an extra T_VOID. T_METADATA/T_VOID pairs are used as +// delimiters. Every entry between the two is a field of the inline +// type. If there's an embedded inline type in the list, it also starts +// with a T_METADATA and ends with a T_VOID. This is so we can +// generate a unique fingerprint for the method's adapters and we can +// generate the list of basic types from the interpreter point of view +// (inline types passed as reference: iterate on the list until a +// T_METADATA, drop everything until and including the closing +// T_VOID) or the compiler point of view (each field of the inline +// types is an argument: drop all T_METADATA/T_VOID from the list). +// +// Value classes could also have fields in abstract super value classes. +// Use a HierarchicalFieldStream to get them as well. +int InlineKlass::collect_fields(GrowableArray* sig, int base_off, int null_marker_offset) { + int count = 0; + SigEntry::add_entry(sig, T_METADATA, name(), base_off); + for (TopDownHierarchicalNonStaticFieldStreamBase fs(this); !fs.done(); fs.next()) { + assert(!fs.access_flags().is_static(), "TopDownHierarchicalNonStaticFieldStreamBase should not let static fields pass."); + int offset = base_off + fs.offset() - (base_off > 0 ? payload_offset() : 0); + InstanceKlass* field_holder = fs.field_descriptor().field_holder(); + // TODO 8284443 Use different heuristic to decide what should be scalarized in the calling convention + if (fs.is_flat()) { + // Resolve klass of flat field and recursively collect fields + int field_null_marker_offset = -1; + if (!fs.is_null_free_inline_type()) { + field_null_marker_offset = base_off + fs.null_marker_offset() - (base_off > 0 ? payload_offset() : 0); + } + Klass* vk = field_holder->get_inline_type_field_klass(fs.index()); + count += InlineKlass::cast(vk)->collect_fields(sig, offset, field_null_marker_offset); + } else { + BasicType bt = Signature::basic_type(fs.signature()); + SigEntry::add_entry(sig, bt, fs.name(), offset); + count += type2size[bt]; + } + if (field_holder != this) { + // Inherited field, add an empty wrapper to this to distinguish it from a "local" field + // with a different offset and avoid false adapter sharing. TODO 8348547 Is this sufficient? + SigEntry::add_entry(sig, T_METADATA, name(), base_off); + SigEntry::add_entry(sig, T_VOID, name(), offset); + } + } + int offset = base_off + size_helper()*HeapWordSize - (base_off > 0 ? payload_offset() : 0); + // Null markers are no real fields, add them manually at the end (C2 relies on this) of the flat fields + if (null_marker_offset != -1) { + SigEntry::add_null_marker(sig, name(), null_marker_offset); + count++; + } + SigEntry::add_entry(sig, T_VOID, name(), offset); + assert(sig->at(0)._bt == T_METADATA && sig->at(sig->length()-1)._bt == T_VOID, "broken structure"); + return count; +} + +void InlineKlass::initialize_calling_convention(TRAPS) { + // Because the pack and unpack handler addresses need to be loadable from generated code, + // they are stored at a fixed offset in the klass metadata. Since inline type klasses do + // not have a vtable, the vtable offset is used to store these addresses. + if (InlineTypeReturnedAsFields || InlineTypePassFieldsAsArgs) { + ResourceMark rm; + GrowableArray sig_vk; + int nb_fields = collect_fields(&sig_vk); + if (*PrintInlineKlassFields != '\0') { + const char* class_name_str = _name->as_C_string(); + if (StringUtils::class_list_match(PrintInlineKlassFields, class_name_str)) { + ttyLocker ttyl; + tty->print_cr("Fields of InlineKlass: %s", class_name_str); + for (const SigEntry& entry : sig_vk) { + tty->print(" %s: %s+%d", entry._name->as_C_string(), type2name(entry._bt), entry._offset); + if (entry._null_marker) { + tty->print(" (null marker)"); + } + tty->print_cr(""); + } + } + } + Array* extended_sig = MetadataFactory::new_array(class_loader_data(), sig_vk.length(), CHECK); + *((Array**)adr_extended_sig()) = extended_sig; + for (int i = 0; i < sig_vk.length(); i++) { + extended_sig->at_put(i, sig_vk.at(i)); + } + if (can_be_returned_as_fields(/* init= */ true)) { + nb_fields++; + BasicType* sig_bt = NEW_RESOURCE_ARRAY(BasicType, nb_fields); + sig_bt[0] = T_METADATA; + SigEntry::fill_sig_bt(&sig_vk, sig_bt+1); + VMRegPair* regs = NEW_RESOURCE_ARRAY(VMRegPair, nb_fields); + int total = SharedRuntime::java_return_convention(sig_bt, regs, nb_fields); + + if (total > 0) { + Array* return_regs = MetadataFactory::new_array(class_loader_data(), nb_fields, CHECK); + *((Array**)adr_return_regs()) = return_regs; + for (int i = 0; i < nb_fields; i++) { + return_regs->at_put(i, regs[i]); + } + + BufferedInlineTypeBlob* buffered_blob = SharedRuntime::generate_buffered_inline_type_adapter(this); + if (buffered_blob == nullptr) { + THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(), "Out of space in CodeCache for adapters"); + } + *((address*)adr_pack_handler()) = buffered_blob->pack_fields(); + *((address*)adr_pack_handler_jobject()) = buffered_blob->pack_fields_jobject(); + *((address*)adr_unpack_handler()) = buffered_blob->unpack_fields(); + assert(CodeCache::find_blob(pack_handler()) == buffered_blob, "lost track of blob"); + assert(can_be_returned_as_fields(), "sanity"); + } + } + if (!can_be_returned_as_fields() && !can_be_passed_as_fields()) { + MetadataFactory::free_array(class_loader_data(), extended_sig); + assert(return_regs() == nullptr, "sanity"); + } + } +} + +void InlineKlass::deallocate_contents(ClassLoaderData* loader_data) { + if (extended_sig() != nullptr) { + MetadataFactory::free_array(loader_data, extended_sig()); + *((Array**)adr_extended_sig()) = nullptr; + } + if (return_regs() != nullptr) { + MetadataFactory::free_array(loader_data, return_regs()); + *((Array**)adr_return_regs()) = nullptr; + } + cleanup_blobs(); + InstanceKlass::deallocate_contents(loader_data); +} + +void InlineKlass::cleanup(InlineKlass* ik) { + ik->cleanup_blobs(); +} + +void InlineKlass::cleanup_blobs() { + if (pack_handler() != nullptr) { + CodeBlob* buffered_blob = CodeCache::find_blob(pack_handler()); + assert(buffered_blob->is_buffered_inline_type_blob(), "bad blob type"); + BufferBlob::free((BufferBlob*)buffered_blob); + *((address*)adr_pack_handler()) = nullptr; + *((address*)adr_pack_handler_jobject()) = nullptr; + *((address*)adr_unpack_handler()) = nullptr; + } +} + +// Can this inline type be passed as multiple values? +bool InlineKlass::can_be_passed_as_fields() const { + return InlineTypePassFieldsAsArgs; +} + +// Can this inline type be returned as multiple values? +bool InlineKlass::can_be_returned_as_fields(bool init) const { + return InlineTypeReturnedAsFields && (init || return_regs() != nullptr); +} + +// Create handles for all oop fields returned in registers that are going to be live across a safepoint +void InlineKlass::save_oop_fields(const RegisterMap& reg_map, GrowableArray& handles) const { + Thread* thread = Thread::current(); + const Array* sig_vk = extended_sig(); + const Array* regs = return_regs(); + int j = 1; + + for (int i = 0; i < sig_vk->length(); i++) { + BasicType bt = sig_vk->at(i)._bt; + if (bt == T_OBJECT || bt == T_ARRAY) { + VMRegPair pair = regs->at(j); + address loc = reg_map.location(pair.first(), nullptr); + oop o = *(oop*)loc; + assert(oopDesc::is_oop_or_null(o), "Bad oop value: " PTR_FORMAT, p2i(o)); + handles.push(Handle(thread, o)); + } + if (bt == T_METADATA) { + continue; + } + if (bt == T_VOID && + sig_vk->at(i-1)._bt != T_LONG && + sig_vk->at(i-1)._bt != T_DOUBLE) { + continue; + } + j++; + } + assert(j == regs->length(), "missed a field?"); +} + +// Update oop fields in registers from handles after a safepoint +void InlineKlass::restore_oop_results(RegisterMap& reg_map, GrowableArray& handles) const { + assert(InlineTypeReturnedAsFields, "Inline types should never be returned as fields"); + const Array* sig_vk = extended_sig(); + const Array* regs = return_regs(); + assert(regs != nullptr, "inconsistent"); + + int j = 1; + int k = 0; + for (int i = 0; i < sig_vk->length(); i++) { + BasicType bt = sig_vk->at(i)._bt; + if (bt == T_OBJECT || bt == T_ARRAY) { + VMRegPair pair = regs->at(j); + address loc = reg_map.location(pair.first(), nullptr); + *(oop*)loc = handles.at(k++)(); + } + if (bt == T_METADATA) { + continue; + } + if (bt == T_VOID && + sig_vk->at(i-1)._bt != T_LONG && + sig_vk->at(i-1)._bt != T_DOUBLE) { + continue; + } + j++; + } + assert(k == handles.length(), "missed a handle?"); + assert(j == regs->length(), "missed a field?"); +} + +// Fields are in registers. Create an instance of the inline type and +// initialize it with the values of the fields. +oop InlineKlass::realloc_result(const RegisterMap& reg_map, const GrowableArray& handles, TRAPS) { + oop new_vt = allocate_instance(CHECK_NULL); + const Array* sig_vk = extended_sig(); + const Array* regs = return_regs(); + + int j = 1; + int k = 0; + for (int i = 0; i < sig_vk->length(); i++) { + BasicType bt = sig_vk->at(i)._bt; + if (bt == T_METADATA) { + continue; + } + if (bt == T_VOID) { + if (sig_vk->at(i-1)._bt == T_LONG || + sig_vk->at(i-1)._bt == T_DOUBLE) { + j++; + } + continue; + } + int off = sig_vk->at(i)._offset; + assert(off > 0, "offset in object should be positive"); + VMRegPair pair = regs->at(j); + address loc = reg_map.location(pair.first(), nullptr); + switch(bt) { + case T_BOOLEAN: { + new_vt->bool_field_put(off, *(jboolean*)loc); + break; + } + case T_CHAR: { + new_vt->char_field_put(off, *(jchar*)loc); + break; + } + case T_BYTE: { + new_vt->byte_field_put(off, *(jbyte*)loc); + break; + } + case T_SHORT: { + new_vt->short_field_put(off, *(jshort*)loc); + break; + } + case T_INT: { + new_vt->int_field_put(off, *(jint*)loc); + break; + } + case T_LONG: { +#ifdef _LP64 + new_vt->double_field_put(off, *(jdouble*)loc); +#else + Unimplemented(); +#endif + break; + } + case T_OBJECT: + case T_ARRAY: { + Handle handle = handles.at(k++); + new_vt->obj_field_put(off, handle()); + break; + } + case T_FLOAT: { + new_vt->float_field_put(off, *(jfloat*)loc); + break; + } + case T_DOUBLE: { + new_vt->double_field_put(off, *(jdouble*)loc); + break; + } + default: + ShouldNotReachHere(); + } + *(intptr_t*)loc = 0xDEAD; + j++; + } + assert(j == regs->length(), "missed a field?"); + assert(k == handles.length(), "missed an oop?"); + return new_vt; +} + +// Check if we return an inline type in scalarized form, i.e. check if either +// - The return value is a tagged InlineKlass pointer, or +// - The return value is an inline type oop that is also returned in scalarized form +InlineKlass* InlineKlass::returned_inline_klass(const RegisterMap& map, bool* return_oop, Method* method) { + BasicType bt = T_METADATA; + VMRegPair pair; + int nb = SharedRuntime::java_return_convention(&bt, &pair, 1); + assert(nb == 1, "broken"); + + address loc = map.location(pair.first(), nullptr); + intptr_t ptr = *(intptr_t*)loc; + if (is_set_nth_bit(ptr, 0)) { + // Return value is tagged, must be an InlineKlass pointer + clear_nth_bit(ptr, 0); + assert(Metaspace::contains((void*)ptr), "should be klass"); + InlineKlass* vk = (InlineKlass*)ptr; + assert(vk->can_be_returned_as_fields(), "must be able to return as fields"); + if (return_oop != nullptr) { + // Not returning an oop + *return_oop = false; + } + return vk; + } + // Return value is not tagged, must be a valid oop + oop o = cast_to_oop(ptr); + assert(oopDesc::is_oop_or_null(o, true), "Bad oop return: " PTR_FORMAT, ptr); + if (return_oop != nullptr && o != nullptr && o->is_inline_type()) { + // Check if inline type is also returned in scalarized form + InlineKlass* vk_val = InlineKlass::cast(o->klass()); + InlineKlass* vk_sig = method->returns_inline_type(); + if (vk_val->can_be_returned_as_fields() && vk_sig != nullptr) { + assert(vk_val == vk_sig, "Unexpected return value"); + return vk_val; + } + } + return nullptr; +} + +// CDS support +#if INCLUDE_CDS +void InlineKlass::metaspace_pointers_do(MetaspaceClosure* it) { + InstanceKlass::metaspace_pointers_do(it); +} + +void InlineKlass::remove_unshareable_info() { + InstanceKlass::remove_unshareable_info(); + + // update it to point to the "buffered" copy of this class. + _adr_inlineklass_fixed_block = inlineklass_static_block(); + ArchivePtrMarker::mark_pointer((address*)&_adr_inlineklass_fixed_block); + + *((Array**)adr_extended_sig()) = nullptr; + *((Array**)adr_return_regs()) = nullptr; + *((address*)adr_pack_handler()) = nullptr; + *((address*)adr_pack_handler_jobject()) = nullptr; + *((address*)adr_unpack_handler()) = nullptr; + assert(pack_handler() == nullptr, "pack handler not null"); +} + +void InlineKlass::remove_java_mirror() { + InstanceKlass::remove_java_mirror(); +} + +void InlineKlass::restore_unshareable_info(ClassLoaderData* loader_data, Handle protection_domain, PackageEntry* pkg_entry, TRAPS) { + InstanceKlass::restore_unshareable_info(loader_data, protection_domain, pkg_entry, CHECK); +} +#endif // CDS +// oop verify + +void InlineKlass::verify_on(outputStream* st) { + InstanceKlass::verify_on(st); + guarantee(prototype_header().is_inline_type(), "Prototype header is not inline type"); +} + +void InlineKlass::oop_verify_on(oop obj, outputStream* st) { + InstanceKlass::oop_verify_on(obj, st); + guarantee(obj->mark().is_inline_type(), "Header is not inline type"); +} diff --git a/src/hotspot/share/oops/inlineKlass.hpp b/src/hotspot/share/oops/inlineKlass.hpp new file mode 100644 index 00000000000..bdb2849acb8 --- /dev/null +++ b/src/hotspot/share/oops/inlineKlass.hpp @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_OOPS_INLINEKLASS_HPP +#define SHARE_VM_OOPS_INLINEKLASS_HPP + +#include "classfile/classFileParser.hpp" +#include "classfile/javaClasses.hpp" +#include "oops/arrayKlass.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/method.hpp" +#include "runtime/registerMap.hpp" + +// An InlineKlass is a specialized InstanceKlass for concrete value classes +// (abstract value classes are represented by InstanceKlass) + + +class InlineKlass: public InstanceKlass { + friend class VMStructs; + friend class InstanceKlass; + friend class ClassFileParser; + + public: + static const KlassKind Kind = InlineKlassKind; + + InlineKlass(); + + private: + + // Constructor + InlineKlass(const ClassFileParser& parser); + + void init_fixed_block(); + inline InlineKlassFixedBlock* inlineklass_static_block() const; + inline address adr_return_regs() const; + + address adr_extended_sig() const { + assert(_adr_inlineklass_fixed_block != nullptr, "Should have been initialized"); + return ((address)_adr_inlineklass_fixed_block) + in_bytes(byte_offset_of(InlineKlassFixedBlock, _extended_sig)); + } + + // pack and unpack handlers for inline types return + address adr_pack_handler() const { + assert(_adr_inlineklass_fixed_block != nullptr, "Should have been initialized"); + return ((address)_adr_inlineklass_fixed_block) + in_bytes(byte_offset_of(InlineKlassFixedBlock, _pack_handler)); + } + + address adr_pack_handler_jobject() const { + assert(_adr_inlineklass_fixed_block != nullptr, "Should have been initialized"); + return ((address)_adr_inlineklass_fixed_block) + in_bytes(byte_offset_of(InlineKlassFixedBlock, _pack_handler_jobject)); + } + + address adr_unpack_handler() const { + assert(_adr_inlineklass_fixed_block != nullptr, "Should have been initialized"); + return ((address)_adr_inlineklass_fixed_block) + in_bytes(byte_offset_of(InlineKlassFixedBlock, _unpack_handler)); + } + + address adr_null_reset_value_offset() const { + assert(_adr_inlineklass_fixed_block != nullptr, "Should have been initialized"); + return ((address)_adr_inlineklass_fixed_block) + in_bytes(null_reset_value_offset_offset()); + } + + address adr_payload_offset() const { + assert(_adr_inlineklass_fixed_block != nullptr, "Should have been initialized"); + return ((address)_adr_inlineklass_fixed_block) + in_bytes(byte_offset_of(InlineKlassFixedBlock, _payload_offset)); + } + + address adr_payload_size_in_bytes() const { + assert(_adr_inlineklass_fixed_block != nullptr, "Should have been initialized"); + return ((address)_adr_inlineklass_fixed_block) + in_bytes(byte_offset_of(InlineKlassFixedBlock, _payload_size_in_bytes)); + } + + address adr_payload_alignment() const { + assert(_adr_inlineklass_fixed_block != nullptr, "Should have been initialized"); + return ((address)_adr_inlineklass_fixed_block) + in_bytes(byte_offset_of(InlineKlassFixedBlock, _payload_alignment)); + } + + address adr_non_atomic_size_in_bytes() const { + assert(_adr_inlineklass_fixed_block != nullptr, "Should have been initialized"); + return ((address)_adr_inlineklass_fixed_block) + in_bytes(byte_offset_of(InlineKlassFixedBlock, _non_atomic_size_in_bytes)); + } + + address adr_non_atomic_alignment() const { + assert(_adr_inlineklass_fixed_block != nullptr, "Should have been initialized"); + return ((address)_adr_inlineklass_fixed_block) + in_bytes(byte_offset_of(InlineKlassFixedBlock, _non_atomic_alignment)); + } + + address adr_atomic_size_in_bytes() const { + assert(_adr_inlineklass_fixed_block != nullptr, "Should have been initialized"); + return ((address)_adr_inlineklass_fixed_block) + in_bytes(byte_offset_of(InlineKlassFixedBlock, _atomic_size_in_bytes)); + } + + address adr_nullable_atomic_size_in_bytes() const { + assert(_adr_inlineklass_fixed_block != nullptr, "Should have been initialized"); + return ((address)_adr_inlineklass_fixed_block) + in_bytes(byte_offset_of(InlineKlassFixedBlock, _nullable_size_in_bytes)); + } + + address adr_null_marker_offset() const { + assert(_adr_inlineklass_fixed_block != nullptr, "Should have been initialized"); + return ((address)_adr_inlineklass_fixed_block) + in_bytes(byte_offset_of(InlineKlassFixedBlock, _null_marker_offset)); + } + + public: + + bool is_empty_inline_type() const { return _misc_flags.is_empty_inline_type(); } + void set_is_empty_inline_type() { _misc_flags.set_is_empty_inline_type(true); } + + int payload_offset() const { + int offset = *(int*)adr_payload_offset(); + assert(offset != 0, "Must be initialized before use"); + return offset; + } + + void set_payload_offset(int offset) { *(int*)adr_payload_offset() = offset; } + + int payload_size_in_bytes() const { return *(int*)adr_payload_size_in_bytes(); } + void set_payload_size_in_bytes(int payload_size) { *(int*)adr_payload_size_in_bytes() = payload_size; } + + int payload_alignment() const { return *(int*)adr_payload_alignment(); } + void set_payload_alignment(int alignment) { *(int*)adr_payload_alignment() = alignment; } + + bool has_non_atomic_layout() const { return non_atomic_size_in_bytes() != -1; } + int non_atomic_size_in_bytes() const { return *(int*)adr_non_atomic_size_in_bytes(); } + void set_non_atomic_size_in_bytes(int size) { *(int*)adr_non_atomic_size_in_bytes() = size; } + int non_atomic_alignment() const { return *(int*)adr_non_atomic_alignment(); } + void set_non_atomic_alignment(int alignment) { *(int*)adr_non_atomic_alignment() = alignment; } + + bool has_atomic_layout() const { return atomic_size_in_bytes() != -1; } + int atomic_size_in_bytes() const { return *(int*)adr_atomic_size_in_bytes(); } + void set_atomic_size_in_bytes(int size) { *(int*)adr_atomic_size_in_bytes() = size; } + + bool has_nullable_atomic_layout() const { return nullable_atomic_size_in_bytes() != -1; } + int nullable_atomic_size_in_bytes() const { return *(int*)adr_nullable_atomic_size_in_bytes(); } + void set_nullable_size_in_bytes(int size) { *(int*)adr_nullable_atomic_size_in_bytes() = size; } + int null_marker_offset() const { return *(int*)adr_null_marker_offset(); } + int null_marker_offset_in_payload() const { return null_marker_offset() - payload_offset(); } + void set_null_marker_offset(int offset) { *(int*)adr_null_marker_offset() = offset; } + + bool is_payload_marked_as_null(address payload) { + assert(has_nullable_atomic_layout(), " Must have"); + return *((jbyte*)payload + null_marker_offset_in_payload()) == 0; + } + + void mark_payload_as_non_null(address payload) { + assert(has_nullable_atomic_layout(), " Must have"); + *((jbyte*)payload + null_marker_offset_in_payload()) = 1; + } + + void mark_payload_as_null(address payload) { + assert(has_nullable_atomic_layout(), " Must have"); + *((jbyte*)payload + null_marker_offset_in_payload()) = 0; + } + + bool is_layout_supported(LayoutKind lk); + + int layout_alignment(LayoutKind kind) const; + int layout_size_in_bytes(LayoutKind kind) const; + +#if INCLUDE_CDS + virtual void remove_unshareable_info(); + virtual void remove_java_mirror(); + virtual void restore_unshareable_info(ClassLoaderData* loader_data, Handle protection_domain, PackageEntry* pkg_entry, TRAPS); + virtual void metaspace_pointers_do(MetaspaceClosure* it); +#endif + + private: + int collect_fields(GrowableArray* sig, int base_off = 0, int null_marker_offset = -1); + + void cleanup_blobs(); + + public: + // Type testing + bool is_inline_klass_slow() const { return true; } + + // Casting from Klass* + + static InlineKlass* cast(Klass* k) { + return const_cast(cast(const_cast(k))); + } + + static const InlineKlass* cast(const Klass* k) { + assert(k != nullptr, "k should not be null"); + assert(k->is_inline_klass(), "cast to InlineKlass"); + return static_cast(k); + } + + // Use this to return the size of an instance in heap words. + // Note that this size only applies to heap allocated stand-alone instances. + virtual int size_helper() const { + return layout_helper_to_size_helper(layout_helper()); + } + + // allocate_instance() allocates a stand alone value in the Java heap + // initialized to default value (cleared memory) + instanceOop allocate_instance(TRAPS); + // allocates a stand alone inline buffer in the Java heap + // DOES NOT have memory cleared, user MUST initialize payload before + // returning to Java (i.e.: inline_copy) + instanceOop allocate_instance_buffer(TRAPS); + + address payload_addr(oop o) const; + + bool maybe_flat_in_array(); + + bool contains_oops() const { return nonstatic_oop_map_count() > 0; } + int nonstatic_oop_count(); + + // Methods to copy payload between containers + // Methods taking a LayoutKind argument expect that both the source and the destination + // layouts are compatible with the one specified in argument (alignment, size, presence + // of a null marker). Reminder: the BUFFERED layout, used in values buffered in heap, + // is compatible with all the other layouts. + + void write_value_to_addr(oop src, void* dst, LayoutKind lk, bool dest_is_initialized, TRAPS); + oop read_payload_from_addr(const oop src, int offset, LayoutKind lk, TRAPS); + void copy_payload_to_addr(void* src, void* dst, LayoutKind lk, bool dest_is_initialized); + + // oop iterate raw inline type data pointer (where oop_addr may not be an oop, but backing/array-element) + template + inline void oop_iterate_specialized(const address oop_addr, OopClosureType* closure); + + template + inline void oop_iterate_specialized_bounded(const address oop_addr, OopClosureType* closure, void* lo, void* hi); + + // calling convention support + void initialize_calling_convention(TRAPS); + Array* extended_sig() const { + return *((Array**)adr_extended_sig()); + } + inline Array* return_regs() const; + bool can_be_passed_as_fields() const; + bool can_be_returned_as_fields(bool init = false) const; + void save_oop_fields(const RegisterMap& map, GrowableArray& handles) const; + void restore_oop_results(RegisterMap& map, GrowableArray& handles) const; + oop realloc_result(const RegisterMap& reg_map, const GrowableArray& handles, TRAPS); + static InlineKlass* returned_inline_klass(const RegisterMap& reg_map, bool* return_oop = nullptr, Method* method = nullptr); + + address pack_handler() const { + return *(address*)adr_pack_handler(); + } + + address unpack_handler() const { + return *(address*)adr_unpack_handler(); + } + + // pack and unpack handlers. Need to be loadable from generated code + // so at a fixed offset from the base of the klass pointer. + static ByteSize pack_handler_offset() { + return byte_offset_of(InlineKlassFixedBlock, _pack_handler); + } + + static ByteSize pack_handler_jobject_offset() { + return byte_offset_of(InlineKlassFixedBlock, _pack_handler_jobject); + } + + static ByteSize unpack_handler_offset() { + return byte_offset_of(InlineKlassFixedBlock, _unpack_handler); + } + + static ByteSize null_reset_value_offset_offset() { + return byte_offset_of(InlineKlassFixedBlock, _null_reset_value_offset); + } + + static ByteSize payload_offset_offset() { + return byte_offset_of(InlineKlassFixedBlock, _payload_offset); + } + + static ByteSize null_marker_offset_offset() { + return byte_offset_of(InlineKlassFixedBlock, _null_marker_offset); + } + + void set_null_reset_value_offset(int offset) { + *((int*)adr_null_reset_value_offset()) = offset; + } + + int null_reset_value_offset() { + int offset = *((int*)adr_null_reset_value_offset()); + assert(offset != 0, "must not be called if not initialized"); + return offset; + } + + void set_null_reset_value(oop val); + + oop null_reset_value() { + assert(is_initialized() || is_being_initialized() || is_in_error_state(), "null reset value is set at the beginning of initialization"); + oop val = java_mirror()->obj_field_acquire(null_reset_value_offset()); + assert(val != nullptr, "Sanity check"); + return val; + } + + void deallocate_contents(ClassLoaderData* loader_data); + static void cleanup(InlineKlass* ik) ; + + // Verification + void verify_on(outputStream* st); + void oop_verify_on(oop obj, outputStream* st); + +}; + +#endif /* SHARE_VM_OOPS_INLINEKLASS_HPP */ diff --git a/src/hotspot/share/oops/inlineKlass.inline.hpp b/src/hotspot/share/oops/inlineKlass.inline.hpp new file mode 100644 index 00000000000..feb1cb001bc --- /dev/null +++ b/src/hotspot/share/oops/inlineKlass.inline.hpp @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +#ifndef SHARE_VM_OOPS_INLINEKLASS_INLINE_HPP +#define SHARE_VM_OOPS_INLINEKLASS_INLINE_HPP + +#include "oops/inlineKlass.hpp" + +#include "memory/iterator.hpp" +#include "oops/flatArrayKlass.hpp" +#include "oops/instanceKlass.inline.hpp" +#include "oops/oop.inline.hpp" +#include "utilities/devirtualizer.inline.hpp" +#include "utilities/macros.hpp" + +inline InlineKlassFixedBlock* InlineKlass::inlineklass_static_block() const { + + InstanceKlass* volatile* adr_impl = adr_implementor(); + if (adr_impl != nullptr) { + return (InlineKlassFixedBlock*)(adr_impl + 1); + } + + return (InlineKlassFixedBlock*)end_of_nonstatic_oop_maps(); +} + +inline address InlineKlass::adr_return_regs() const { + InlineKlassFixedBlock* vkst = inlineklass_static_block(); + return ((address)_adr_inlineklass_fixed_block) + in_bytes(byte_offset_of(InlineKlassFixedBlock, _return_regs)); +} + +inline Array* InlineKlass::return_regs() const { + return *((Array**)adr_return_regs()); +} + +inline address InlineKlass::payload_addr(oop o) const { + return ((address) (void*) o) + payload_offset(); +} + +template +void InlineKlass::oop_iterate_specialized(const address oop_addr, OopClosureType* closure) { + OopMapBlock* map = start_of_nonstatic_oop_maps(); + OopMapBlock* const end_map = map + nonstatic_oop_map_count(); + + for (; map < end_map; map++) { + T* p = (T*) (oop_addr + map->offset()); + T* const end = p + map->count(); + for (; p < end; ++p) { + Devirtualizer::do_oop(closure, p); + } + } +} + +template +inline void InlineKlass::oop_iterate_specialized_bounded(const address oop_addr, OopClosureType* closure, void* lo, void* hi) { + OopMapBlock* map = start_of_nonstatic_oop_maps(); + OopMapBlock* const end_map = map + nonstatic_oop_map_count(); + + T* const l = (T*) lo; + T* const h = (T*) hi; + + for (; map < end_map; map++) { + T* p = (T*) (oop_addr + map->offset()); + T* end = p + map->count(); + if (p < l) { + p = l; + } + if (end > h) { + end = h; + } + for (; p < end; ++p) { + Devirtualizer::do_oop(closure, p); + } + } +} + + +#endif // SHARE_VM_OOPS_INLINEKLASS_INLINE_HPP diff --git a/src/hotspot/share/oops/instanceClassLoaderKlass.cpp b/src/hotspot/share/oops/instanceClassLoaderKlass.cpp index 982a715c324..fe3b272d30f 100644 --- a/src/hotspot/share/oops/instanceClassLoaderKlass.cpp +++ b/src/hotspot/share/oops/instanceClassLoaderKlass.cpp @@ -24,6 +24,7 @@ #include "cds/cdsConfig.hpp" #include "oops/instanceClassLoaderKlass.hpp" +#include "oops/instanceKlass.inline.hpp" InstanceClassLoaderKlass::InstanceClassLoaderKlass() { assert(CDSConfig::is_dumping_static_archive() || CDSConfig::is_using_archive(), "only for CDS"); diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index 568ccd72176..613c7bdde9b 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -63,15 +63,18 @@ #include "memory/universe.hpp" #include "oops/constantPool.hpp" #include "oops/fieldStreams.inline.hpp" +#include "oops/inlineKlass.hpp" #include "oops/instanceClassLoaderKlass.hpp" #include "oops/instanceKlass.inline.hpp" #include "oops/instanceMirrorKlass.hpp" #include "oops/instanceOop.hpp" #include "oops/instanceStackChunkKlass.hpp" #include "oops/klass.inline.hpp" +#include "oops/markWord.hpp" #include "oops/method.hpp" #include "oops/oop.inline.hpp" #include "oops/recordComponent.hpp" +#include "oops/refArrayKlass.hpp" #include "oops/symbol.hpp" #include "prims/jvmtiExport.hpp" #include "prims/jvmtiRedefineClasses.hpp" @@ -149,6 +152,11 @@ #endif // ndef DTRACE_ENABLED +void InlineLayoutInfo::metaspace_pointers_do(MetaspaceClosure* it) { + log_trace(cds)("Iter(InlineFieldInfo): %p", this); + it->push(&_klass); +} + bool InstanceKlass::_finalization_enabled = true; static inline bool is_class_loader(const Symbol* class_name, @@ -170,6 +178,19 @@ static inline bool is_class_loader(const Symbol* class_name, return false; } +bool InstanceKlass::field_is_null_free_inline_type(int index) const { + return field(index).field_flags().is_null_free_inline_type(); +} + +bool InstanceKlass::is_class_in_loadable_descriptors_attribute(Symbol* name) const { + if (_loadable_descriptors == nullptr) return false; + for (int i = 0; i < _loadable_descriptors->length(); i++) { + Symbol* class_name = _constants->symbol_at(_loadable_descriptors->at(i)); + if (class_name == name) return true; + } + return false; +} + static inline bool is_stack_chunk_class(const Symbol* class_name, const ClassLoaderData* loader_data) { return (class_name == vmSymbols::jdk_internal_vm_StackChunk() && @@ -464,7 +485,8 @@ InstanceKlass* InstanceKlass::allocate_instance_klass(const ClassFileParser& par const int size = InstanceKlass::size(parser.vtable_size(), parser.itable_size(), nonstatic_oop_map_size(parser.total_oop_map_count()), - parser.is_interface()); + parser.is_interface(), + parser.is_inline_type()); const Symbol* const class_name = parser.class_name(); assert(class_name != nullptr, "invariant"); @@ -487,6 +509,9 @@ InstanceKlass* InstanceKlass::allocate_instance_klass(const ClassFileParser& par } else if (is_class_loader(class_name, parser)) { // class loader - java.lang.ClassLoader ik = new (loader_data, size, use_class_space, THREAD) InstanceClassLoaderKlass(parser); + } else if (parser.is_inline_type()) { + // inline type + ik = new (loader_data, size, use_class_space, THREAD) InlineKlass(parser); } else { // normal ik = new (loader_data, size, use_class_space, THREAD) InstanceKlass(parser); @@ -503,9 +528,38 @@ InstanceKlass* InstanceKlass::allocate_instance_klass(const ClassFileParser& par return nullptr; } +#ifdef ASSERT + ik->bounds_check((address) ik->start_of_vtable(), false, size); + ik->bounds_check((address) ik->start_of_itable(), false, size); + ik->bounds_check((address) ik->end_of_itable(), true, size); + ik->bounds_check((address) ik->end_of_nonstatic_oop_maps(), true, size); +#endif //ASSERT return ik; } +#ifndef PRODUCT +bool InstanceKlass::bounds_check(address addr, bool edge_ok, intptr_t size_in_bytes) const { + const char* bad = nullptr; + address end = nullptr; + if (addr < (address)this) { + bad = "before"; + } else if (addr == (address)this) { + if (edge_ok) return true; + bad = "just before"; + } else if (addr == (end = (address)this + sizeof(intptr_t) * (size_in_bytes < 0 ? size() : size_in_bytes))) { + if (edge_ok) return true; + bad = "just after"; + } else if (addr > end) { + bad = "after"; + } else { + return true; + } + tty->print_cr("%s object bounds: " INTPTR_FORMAT " [" INTPTR_FORMAT ".." INTPTR_FORMAT "]", + bad, (intptr_t)addr, (intptr_t)this, (intptr_t)end); + Verbose = WizardMode = true; this->print(); //@@ + return false; +} +#endif //PRODUCT // copy method ordering from resource area to Metaspace void InstanceKlass::copy_method_ordering(const intArray* m, TRAPS) { @@ -533,8 +587,8 @@ InstanceKlass::InstanceKlass() { assert(CDSConfig::is_dumping_static_archive() || CDSConfig::is_using_archive(), "only for CDS"); } -InstanceKlass::InstanceKlass(const ClassFileParser& parser, KlassKind kind, ReferenceType reference_type) : - Klass(kind), +InstanceKlass::InstanceKlass(const ClassFileParser& parser, KlassKind kind, markWord prototype_header, ReferenceType reference_type) : + Klass(kind, prototype_header), _nest_members(nullptr), _nest_host(nullptr), _permitted_subclasses(nullptr), @@ -545,13 +599,19 @@ InstanceKlass::InstanceKlass(const ClassFileParser& parser, KlassKind kind, Refe _nest_host_index(0), _init_state(allocated), _reference_type(reference_type), - _init_thread(nullptr) + _init_thread(nullptr), + _inline_layout_info_array(nullptr), + _loadable_descriptors(nullptr), + _adr_inlineklass_fixed_block(nullptr) { set_vtable_length(parser.vtable_size()); set_access_flags(parser.access_flags()); if (parser.is_hidden()) set_is_hidden(); set_layout_helper(Klass::instance_layout_helper(parser.layout_size(), false)); + if (parser.has_inline_fields()) { + set_has_inline_type_fields(); + } assert(nullptr == _methods, "underlying memory not zeroed?"); assert(is_instance_klass(), "is layout incorrect?"); @@ -696,6 +756,11 @@ void InstanceKlass::deallocate_contents(ClassLoaderData* loader_data) { } set_fields_status(nullptr); + if (inline_layout_info_array() != nullptr) { + MetadataFactory::free_array(loader_data, inline_layout_info_array()); + } + set_inline_layout_info_array(nullptr); + // If a method from a redefined class is using this constant pool, don't // delete it, yet. The new class's previous version will point to this. if (constants() != nullptr) { @@ -730,6 +795,13 @@ void InstanceKlass::deallocate_contents(ClassLoaderData* loader_data) { } set_permitted_subclasses(nullptr); + if (loadable_descriptors() != nullptr && + loadable_descriptors() != Universe::the_empty_short_array() && + !loadable_descriptors()->in_aot_cache()) { + MetadataFactory::free_array(loader_data, loadable_descriptors()); + } + set_loadable_descriptors(nullptr); + // We should deallocate the Annotations instance if it's not in shared spaces. if (annotations() != nullptr && !annotations()->in_aot_cache()) { MetadataFactory::free_metadata(loader_data, annotations()); @@ -898,6 +970,44 @@ bool InstanceKlass::verify_code(TRAPS) { return Verifier::verify(this, should_verify_class(), THREAD); } +static void load_classes_from_loadable_descriptors_attribute(InstanceKlass *ik, TRAPS) { + ResourceMark rm(THREAD); + if (ik->loadable_descriptors() != nullptr && PreloadClasses) { + HandleMark hm(THREAD); + for (int i = 0; i < ik->loadable_descriptors()->length(); i++) { + Symbol* sig = ik->constants()->symbol_at(ik->loadable_descriptors()->at(i)); + if (!Signature::has_envelope(sig)) continue; + TempNewSymbol class_name = Signature::strip_envelope(sig); + if (class_name == ik->name()) continue; + log_info(class, preload)("Preloading of class %s during linking of class %s " + "because of the class is listed in the LoadableDescriptors attribute", + sig->as_C_string(), ik->name()->as_C_string()); + oop loader = ik->class_loader(); + Klass* klass = SystemDictionary::resolve_or_null(class_name, + Handle(THREAD, loader), THREAD); + if (HAS_PENDING_EXCEPTION) { + CLEAR_PENDING_EXCEPTION; + } + if (klass != nullptr) { + log_info(class, preload)("Preloading of class %s during linking of class %s " + "(cause: LoadableDescriptors attribute) succeeded", + class_name->as_C_string(), ik->name()->as_C_string()); + if (!klass->is_inline_klass()) { + // Non value class are allowed by the current spec, but it could be an indication + // of an issue so let's log a warning + log_warning(class, preload)("Preloading of class %s during linking of class %s " + "(cause: LoadableDescriptors attribute) but loaded class is not a value class", + class_name->as_C_string(), ik->name()->as_C_string()); + } + } else { + log_warning(class, preload)("Preloading of class %s during linking of class %s " + "(cause: LoadableDescriptors attribute) failed", + class_name->as_C_string(), ik->name()->as_C_string()); + } + } + } +} + void InstanceKlass::link_class(TRAPS) { assert(is_loaded(), "must be loaded"); if (!is_linked()) { @@ -968,6 +1078,13 @@ bool InstanceKlass::link_class_impl(TRAPS) { interk->link_class_impl(CHECK_false); } + if (EnableValhalla) { + // Aggressively preloading all classes from the LoadableDescriptors attribute + // so inline classes can be scalarized in the calling conventions computed below + load_classes_from_loadable_descriptors_attribute(this, THREAD); + assert(!HAS_PENDING_EXCEPTION, "Shouldn't have pending exceptions from call above"); + } + // in case the class is linked in the process of linking its superclasses if (is_linked()) { return true; @@ -1272,6 +1389,27 @@ void InstanceKlass::initialize_impl(TRAPS) { } } + // Pre-allocating an all-zero value to be used to reset nullable flat storages + if (is_inline_klass()) { + InlineKlass* vk = InlineKlass::cast(this); + if (vk->has_nullable_atomic_layout()) { + oop val = vk->allocate_instance(THREAD); + if (HAS_PENDING_EXCEPTION) { + Handle e(THREAD, PENDING_EXCEPTION); + CLEAR_PENDING_EXCEPTION; + { + EXCEPTION_MARK; + add_initialization_error(THREAD, e); + // Locks object, set state, and notify all waiting threads + set_initialization_state_and_notify(initialization_error, THREAD); + CLEAR_PENDING_EXCEPTION; + } + THROW_OOP(e()); + } + vk->set_null_reset_value(val); + } + } + // Step 7 // Next, if C is a class rather than an interface, initialize it's super class and super // interfaces. @@ -1304,7 +1442,6 @@ void InstanceKlass::initialize_impl(TRAPS) { } } - // Step 8 { DTRACE_CLASSINIT_PROBE_WAIT(clinit, -1, wait); @@ -1325,6 +1462,36 @@ void InstanceKlass::initialize_impl(TRAPS) { } call_class_initializer(THREAD); } + + if (has_strict_static_fields() && !HAS_PENDING_EXCEPTION) { + // Step 9 also verifies that strict static fields have been initialized. + // Status bits were set in ClassFileParser::post_process_parsed_stream. + // After , bits must all be clear, or else we must throw an error. + // This is an extremely fast check, so we won't bother with a timer. + assert(fields_status() != nullptr, ""); + Symbol* bad_strict_static = nullptr; + for (int index = 0; index < fields_status()->length(); index++) { + // Very fast loop over single byte array looking for a set bit. + if (fields_status()->adr_at(index)->is_strict_static_unset()) { + // This strict static field has not been set by the class initializer. + // Note that in the common no-error case, we read no field metadata. + // We only unpack it when we need to report an error. + FieldInfo fi = field(index); + bad_strict_static = fi.name(constants()); + if (debug_logging_enabled) { + ResourceMark rm(jt); + const char* msg = format_strict_static_message(bad_strict_static); + log_debug(class, init)("%s", msg); + } else { + // If we are not logging, do not bother to look for a second offense. + break; + } + } + } + if (bad_strict_static != nullptr) { + throw_strict_static_exception(bad_strict_static, "is unset after initialization of", THREAD); + } + } } // Step 9 @@ -1378,6 +1545,74 @@ void InstanceKlass::set_initialization_state_and_notify(ClassState state, TRAPS) } } +void InstanceKlass::notify_strict_static_access(int field_index, bool is_writing, TRAPS) { + guarantee(field_index >= 0 && field_index < fields_status()->length(), "valid field index"); + DEBUG_ONLY(FieldInfo debugfi = field(field_index)); + assert(debugfi.access_flags().is_strict(), ""); + assert(debugfi.access_flags().is_static(), ""); + FieldStatus& fs = *fields_status()->adr_at(field_index); + LogTarget(Trace, class, init) lt; + if (lt.is_enabled()) { + ResourceMark rm(THREAD); + LogStream ls(lt); + FieldInfo fi = field(field_index); + ls.print("notify %s %s %s%s ", + external_name(), is_writing? "Write" : "Read", + fs.is_strict_static_unset() ? "Unset" : "(set)", + fs.is_strict_static_unread() ? "+Unread" : ""); + fi.print(&ls, constants()); + } + if (fs.is_strict_static_unset()) { + assert(fs.is_strict_static_unread(), "ClassFileParser resp."); + // If it is not set, there are only two reasonable things we can do here: + // - mark it set if this is putstatic + // - throw an error (Read-Before-Write) if this is getstatic + + // The unset state is (or should be) transient, and observable only in one + // thread during the execution of . Something is wrong here as this + // should not be possible + guarantee(is_reentrant_initialization(THREAD), "unscoped access to strict static"); + if (is_writing) { + // clear the "unset" bit, since the field is actually going to be written + fs.update_strict_static_unset(false); + } else { + // throw an IllegalStateException, since we are reading before writing + // see also InstanceKlass::initialize_impl, Step 8 (at end) + Symbol* bad_strict_static = field(field_index).name(constants()); + throw_strict_static_exception(bad_strict_static, "is unset before first read in", CHECK); + } + } else { + // Ensure no write after read for final strict statics + FieldInfo fi = field(field_index); + bool is_final = fi.access_flags().is_final(); + if (is_final) { + // no final write after read, so observing a constant freezes it, as if ended early + // (maybe we could trust the constant a little earlier, before ends) + if (is_writing && !fs.is_strict_static_unread()) { + Symbol* bad_strict_static = fi.name(constants()); + throw_strict_static_exception(bad_strict_static, "is set after read (as final) in", CHECK); + } else if (!is_writing && fs.is_strict_static_unread()) { + fs.update_strict_static_unread(false); + } + } + } +} + +void InstanceKlass::throw_strict_static_exception(Symbol* field_name, const char* when, TRAPS) { + ResourceMark rm(THREAD); + const char* msg = format_strict_static_message(field_name, when); + THROW_MSG(vmSymbols::java_lang_IllegalStateException(), msg); +} + +const char* InstanceKlass::format_strict_static_message(Symbol* field_name, const char* when) { + stringStream ss; + ss.print("Strict static \"%s\" %s %s", + field_name->as_C_string(), + when == nullptr ? "is unset in" : when, + external_name()); + return ss.as_string(); +} + // Update hierarchy. This is done before the new klass has been added to the SystemDictionary. The Compile_lock // is grabbed, to ensure that the compiler is not using the class hierarchy. void InstanceKlass::add_to_hierarchy(JavaThread* current) { @@ -1561,15 +1796,6 @@ bool InstanceKlass::is_same_or_direct_interface(Klass *k) const { return false; } -objArrayOop InstanceKlass::allocate_objArray(int n, int length, TRAPS) { - check_array_allocation_length(length, arrayOopDesc::max_array_length(T_OBJECT), CHECK_NULL); - size_t size = objArrayOopDesc::object_size(length); - ArrayKlass* ak = array_klass(n, CHECK_NULL); - objArrayOop o = (objArrayOop)Universe::heap()->array_allocate(ak, size, length, - /* do_zero */ true, CHECK_NULL); - return o; -} - instanceOop InstanceKlass::register_finalizer(instanceOop i, TRAPS) { if (TraceFinalizerRegistration) { tty->print("Registered "); @@ -1637,18 +1863,18 @@ ArrayKlass* InstanceKlass::array_klass(int n, TRAPS) { } // array_klasses() will always be set at this point - ObjArrayKlass* ak = array_klasses(); + ArrayKlass* ak = array_klasses(); assert(ak != nullptr, "should be set"); return ak->array_klass(n, THREAD); } ArrayKlass* InstanceKlass::array_klass_or_null(int n) { // Need load-acquire for lock-free read - ObjArrayKlass* oak = array_klasses_acquire(); - if (oak == nullptr) { + ArrayKlass* ak = array_klasses_acquire(); + if (ak == nullptr) { return nullptr; } else { - return oak->array_klass_or_null(n); + return ak->array_klass_or_null(n); } } @@ -1665,7 +1891,7 @@ static int call_class_initializer_counter = 0; // for debugging Method* InstanceKlass::class_initializer() const { Method* clinit = find_method( vmSymbols::class_initializer_name(), vmSymbols::void_method_signature()); - if (clinit != nullptr && clinit->has_valid_initializer_flags()) { + if (clinit != nullptr && clinit->is_class_initializer()) { return clinit; } return nullptr; @@ -1774,10 +2000,6 @@ void InstanceKlass::mask_for(const methodHandle& method, int bci, oop_map_cache->lookup(method, bci, entry_for); } -bool InstanceKlass::contains_field_offset(int offset) { - fieldDescriptor fd; - return find_field_from_offset(offset, false, &fd); -} FieldInfo InstanceKlass::field(int index) const { for (AllFieldStream fs(this); !fs.done(); fs.next()) { @@ -1858,6 +2080,15 @@ Klass* InstanceKlass::find_field(Symbol* name, Symbol* sig, bool is_static, fiel return nullptr; } +bool InstanceKlass::contains_field_offset(int offset) { + if (this->is_inline_klass()) { + InlineKlass* vk = InlineKlass::cast(this); + return offset >= vk->payload_offset() && offset < (vk->payload_offset() + vk->payload_size_in_bytes()); + } else { + fieldDescriptor fd; + return find_field_from_offset(offset, false, &fd); + } +} bool InstanceKlass::find_local_field_from_offset(int offset, bool is_static, fieldDescriptor* fd) const { for (JavaFieldStream fs(this); !fs.done(); fs.next()) { @@ -2241,6 +2472,9 @@ Method* InstanceKlass::uncached_lookup_method(const Symbol* name, if (method != nullptr) { return method; } + if (name == vmSymbols::object_initializer_name()) { + break; // is never inherited + } klass = klass->super(); overpass_local_mode = OverpassLookupMode::skip; // Always ignore overpass methods in superclasses } @@ -2653,7 +2887,9 @@ void InstanceKlass::metaspace_pointers_do(MetaspaceClosure* it) { it->push(&_nest_host); it->push(&_nest_members); it->push(&_permitted_subclasses); + it->push(&_loadable_descriptors); it->push(&_record_components); + it->push(&_inline_layout_info_array, MetaspaceClosure::_writable); } #if INCLUDE_CDS @@ -2701,7 +2937,7 @@ void InstanceKlass::remove_unshareable_info() { array_klasses()->remove_unshareable_info(); } - // These are not allocated from metaspace. They are safe to set to null. + // These are not allocated from metaspace. They are safe to set to nullptr. _source_debug_extension = nullptr; _dep_context = nullptr; _osr_nmethods_head = nullptr; @@ -2792,6 +3028,10 @@ void InstanceKlass::restore_unshareable_info(ClassLoaderData* loader_data, Handl set_package(loader_data, pkg_entry, CHECK); Klass::restore_unshareable_info(loader_data, protection_domain, CHECK); + if (is_inline_klass()) { + InlineKlass::cast(this)->initialize_calling_convention(CHECK); + } + Array* methods = this->methods(); int num_methods = methods->length(); for (int index = 0; index < num_methods; ++index) { @@ -2825,9 +3065,15 @@ void InstanceKlass::restore_unshareable_info(ClassLoaderData* loader_data, Handl // To get a consistent list of classes we need MultiArray_lock to ensure // array classes aren't observed while they are being restored. RecursiveLocker rl(MultiArray_lock, THREAD); - assert(this == array_klasses()->bottom_klass(), "sanity"); + assert(this == ObjArrayKlass::cast(array_klasses())->bottom_klass(), "sanity"); // Array classes have null protection domain. // --> see ArrayKlass::complete_create_array_klass() + if (class_loader_data() == nullptr) { + ResourceMark rm(THREAD); + log_debug(cds)(" loader_data %s ", loader_data == nullptr ? "nullptr" : "non null"); + log_debug(cds)(" this %s array_klasses %s ", this->name()->as_C_string(), array_klasses()->name()->as_C_string()); + } + assert(!array_klasses()->is_refined_objArray_klass(), "must be non-refined objarrayklass"); array_klasses()->restore_unshareable_info(class_loader_data(), Handle(), CHECK); } @@ -2981,6 +3227,10 @@ void InstanceKlass::set_minor_version(u2 minor_version) { _constants->set_minor_ u2 InstanceKlass::major_version() const { return _constants->major_version(); } void InstanceKlass::set_major_version(u2 major_version) { _constants->set_major_version(major_version); } +bool InstanceKlass::supports_inline_types() const { + return major_version() >= Verifier::VALUE_TYPES_MAJOR_VERSION && minor_version() == Verifier::JAVA_PREVIEW_MINOR_VERSION; +} + const InstanceKlass* InstanceKlass::get_klass_version(int version) const { for (const InstanceKlass* ik = this; ik != nullptr; ik = ik->previous_versions()) { if (ik->constants()->version() == version) { @@ -3013,16 +3263,19 @@ u2 InstanceKlass::generic_signature_index() const { return _con void InstanceKlass::set_generic_signature_index(u2 sig_index) { _constants->set_generic_signature_index(sig_index); } const char* InstanceKlass::signature_name() const { + return signature_name_of_carrier(JVM_SIGNATURE_CLASS); +} +const char* InstanceKlass::signature_name_of_carrier(char c) const { // Get the internal name as a c string const char* src = (const char*) (name()->as_C_string()); const int src_length = (int)strlen(src); char* dest = NEW_RESOURCE_ARRAY(char, src_length + 3); - // Add L as type indicator + // Add L or Q as type indicator int dest_index = 0; - dest[dest_index++] = JVM_SIGNATURE_CLASS; + dest[dest_index++] = c; // Add the actual class name for (int src_index = 0; src_index < src_length; ) { @@ -3303,6 +3556,25 @@ bool InstanceKlass::find_inner_classes_attr(int* ooff, int* noff, TRAPS) const { return false; } +void InstanceKlass::check_can_be_annotated_with_NullRestricted(InstanceKlass* type, Symbol* container_klass_name, TRAPS) { + assert(type->is_instance_klass(), "Sanity check"); + if (type->is_identity_class()) { + ResourceMark rm(THREAD); + THROW_MSG(vmSymbols::java_lang_IncompatibleClassChangeError(), + err_msg("Class %s expects class %s to be a value class, but it is an identity class", + container_klass_name->as_C_string(), + type->external_name())); + } + + if (type->is_abstract()) { + ResourceMark rm(THREAD); + THROW_MSG(vmSymbols::java_lang_IncompatibleClassChangeError(), + err_msg("Class %s expects class %s to be concrete value type, but it is an abstract class", + container_klass_name->as_C_string(), + type->external_name())); + } +} + InstanceKlass* InstanceKlass::compute_enclosing_class(bool* inner_is_member, TRAPS) const { InstanceKlass* outer_klass = nullptr; *inner_is_member = false; @@ -3369,8 +3641,7 @@ u2 InstanceKlass::compute_modifier_flags() const { break; } } - // Remember to strip ACC_SUPER bit - return (access & (~JVM_ACC_SUPER)); + return access; } jint InstanceKlass::jvmti_class_status() const { @@ -3624,20 +3895,55 @@ static const char* state_names[] = { "allocated", "loaded", "linked", "being_initialized", "fully_initialized", "initialization_error" }; -static void print_vtable(intptr_t* start, int len, outputStream* st) { +static void print_vtable(address self, intptr_t* start, int len, outputStream* st) { + ResourceMark rm; + int* forward_refs = NEW_RESOURCE_ARRAY(int, len); + for (int i = 0; i < len; i++) forward_refs[i] = 0; for (int i = 0; i < len; i++) { intptr_t e = start[i]; st->print("%d : " INTPTR_FORMAT, i, e); + if (forward_refs[i] != 0) { + int from = forward_refs[i]; + int off = (int) start[from]; + st->print(" (offset %d <= [%d])", off, from); + } if (MetaspaceObj::is_valid((Metadata*)e)) { st->print(" "); ((Metadata*)e)->print_value_on(st); + } else if (self != nullptr && e > 0 && e < 0x10000) { + address location = self + e; + int index = (int)((intptr_t*)location - start); + st->print(" (offset %d => [%d])", (int)e, index); + if (index >= 0 && index < len) + forward_refs[index] = i; } st->cr(); } } static void print_vtable(vtableEntry* start, int len, outputStream* st) { - return print_vtable(reinterpret_cast(start), len, st); + return print_vtable(nullptr, reinterpret_cast(start), len, st); +} + +template + static void print_array_on(outputStream* st, Array* array) { + if (array == nullptr) { st->print_cr("nullptr"); return; } + array->print_value_on(st); st->cr(); + if (Verbose || WizardMode) { + for (int i = 0; i < array->length(); i++) { + st->print("%d : ", i); array->at(i)->print_value_on(st); st->cr(); + } + } + } + +static void print_array_on(outputStream* st, Array* array) { + if (array == nullptr) { st->print_cr("nullptr"); return; } + array->print_value_on(st); st->cr(); + if (Verbose || WizardMode) { + for (int i = 0; i < array->length(); i++) { + st->print("%d : %d", i, array->at(i)); st->cr(); + } + } } const char* InstanceKlass::init_state_name() const { @@ -3678,22 +3984,10 @@ void InstanceKlass::print_on(outputStream* st) const { } st->print(BULLET"arrays: "); Metadata::print_value_on_maybe_null(st, array_klasses()); st->cr(); - st->print(BULLET"methods: "); methods()->print_value_on(st); st->cr(); - if (Verbose || WizardMode) { - Array* method_array = methods(); - for (int i = 0; i < method_array->length(); i++) { - st->print("%d : ", i); method_array->at(i)->print_value(); st->cr(); - } - } - st->print(BULLET"method ordering: "); method_ordering()->print_value_on(st); st->cr(); + st->print(BULLET"methods: "); print_array_on(st, methods()); + st->print(BULLET"method ordering: "); print_array_on(st, method_ordering()); if (default_methods() != nullptr) { - st->print(BULLET"default_methods: "); default_methods()->print_value_on(st); st->cr(); - if (Verbose) { - Array* method_array = default_methods(); - for (int i = 0; i < method_array->length(); i++) { - st->print("%d : ", i); method_array->at(i)->print_value(); st->cr(); - } - } + st->print(BULLET"default_methods: "); print_array_on(st, default_methods()); } print_on_maybe_null(st, BULLET"default vtable indices: ", default_vtable_indices()); st->print(BULLET"local interfaces: "); local_interfaces()->print_value_on(st); st->cr(); @@ -3753,6 +4047,7 @@ void InstanceKlass::print_on(outputStream* st) const { st->print(BULLET"nest members: "); nest_members()->print_value_on(st); st->cr(); print_on_maybe_null(st, BULLET"record components: ", record_components()); st->print(BULLET"permitted subclasses: "); permitted_subclasses()->print_value_on(st); st->cr(); + st->print(BULLET"loadable descriptors: "); loadable_descriptors()->print_value_on(st); st->cr(); if (java_mirror() != nullptr) { st->print(BULLET"java mirror: "); java_mirror()->print_value_on(st); @@ -3763,7 +4058,7 @@ void InstanceKlass::print_on(outputStream* st) const { st->print(BULLET"vtable length %d (start addr: " PTR_FORMAT ")", vtable_length(), p2i(start_of_vtable())); st->cr(); if (vtable_length() > 0 && (Verbose || WizardMode)) print_vtable(start_of_vtable(), vtable_length(), st); st->print(BULLET"itable length %d (start addr: " PTR_FORMAT ")", itable_length(), p2i(start_of_itable())); st->cr(); - if (itable_length() > 0 && (Verbose || WizardMode)) print_vtable(start_of_itable(), itable_length(), st); + if (itable_length() > 0 && (Verbose || WizardMode)) print_vtable(nullptr, start_of_itable(), itable_length(), st); st->print_cr(BULLET"---- static fields (%d words):", static_field_size()); FieldPrinter print_static_field(st); @@ -3795,18 +4090,19 @@ void InstanceKlass::print_value_on(outputStream* st) const { } void FieldPrinter::do_field(fieldDescriptor* fd) { + for (int i = 0; i < _indent; i++) _st->print(" "); _st->print(BULLET); if (_obj == nullptr) { - fd->print_on(_st); + fd->print_on(_st, _base_offset); _st->cr(); } else { - fd->print_on_for(_st, _obj); - _st->cr(); + fd->print_on_for(_st, _obj, _indent, _base_offset); + if (!fd->field_flags().is_flat()) _st->cr(); } } -void InstanceKlass::oop_print_on(oop obj, outputStream* st) { +void InstanceKlass::oop_print_on(oop obj, outputStream* st, int indent, int base_offset) { Klass::oop_print_on(obj, st); if (this == vmClasses::String_klass()) { @@ -3822,7 +4118,7 @@ void InstanceKlass::oop_print_on(oop obj, outputStream* st) { } st->print_cr(BULLET"---- fields (total size %zu words):", oop_size(obj)); - FieldPrinter print_field(st, obj); + FieldPrinter print_field(st, obj, indent, base_offset); print_nonstatic_fields(&print_field); if (this == vmClasses::Class_klass()) { diff --git a/src/hotspot/share/oops/instanceKlass.hpp b/src/hotspot/share/oops/instanceKlass.hpp index 3338b5cd446..f7f8be1aa0d 100644 --- a/src/hotspot/share/oops/instanceKlass.hpp +++ b/src/hotspot/share/oops/instanceKlass.hpp @@ -25,6 +25,7 @@ #ifndef SHARE_OOPS_INSTANCEKLASS_HPP #define SHARE_OOPS_INSTANCEKLASS_HPP +#include "code/vmreg.hpp" #include "memory/allocation.hpp" #include "memory/referenceType.hpp" #include "oops/annotations.hpp" @@ -32,6 +33,7 @@ #include "oops/fieldInfo.hpp" #include "oops/instanceKlassFlags.hpp" #include "oops/instanceOop.hpp" +#include "oops/refArrayKlass.hpp" #include "runtime/handles.hpp" #include "runtime/javaThread.hpp" #include "utilities/accessFlags.hpp" @@ -56,6 +58,7 @@ class RecordComponent; // The embedded nonstatic oop-map blocks are short pairs (offset, length) // indicating where oops are located in instances of this klass. // [EMBEDDED implementor of the interface] only exist for interface +// [EMBEDDED InlineKlassFixedBlock] only if is an InlineKlass instance // forward declaration for class -- see below for definition @@ -71,6 +74,7 @@ class JNIid; class JvmtiCachedClassFieldMap; class nmethodBucket; class OopMapCache; +class BufferedInlineTypeBlob; class InterpreterOopMap; class PackageEntry; class ModuleEntry; @@ -86,8 +90,11 @@ class FieldClosure: public StackObj { class FieldPrinter: public FieldClosure { oop _obj; outputStream* _st; + int _indent; + int _base_offset; public: - FieldPrinter(outputStream* st, oop obj = nullptr) : _obj(obj), _st(st) {} + FieldPrinter(outputStream* st, oop obj = nullptr, int indent = 0, int base_offset = 0) : + _obj(obj), _st(st), _indent(indent), _base_offset(base_offset) {} void do_field(fieldDescriptor* fd); }; @@ -131,17 +138,72 @@ class OopMapBlock { struct JvmtiCachedClassFileData; +class SigEntry; + +class InlineKlassFixedBlock { + Array** _extended_sig; + Array** _return_regs; + address* _pack_handler; + address* _pack_handler_jobject; + address* _unpack_handler; + int* _null_reset_value_offset; + int _payload_offset; // offset of the begining of the payload in a heap buffered instance + int _payload_size_in_bytes; // size of payload layout + int _payload_alignment; // alignment required for payload + int _non_atomic_size_in_bytes; // size of null-free non-atomic flat layout + int _non_atomic_alignment; // alignment requirement for null-free non-atomic layout + int _atomic_size_in_bytes; // size and alignment requirement for a null-free atomic layout, -1 if no atomic flat layout is possible + int _nullable_size_in_bytes; // size and alignment requirement for a nullable layout (always atomic), -1 if no nullable flat layout is possible + int _null_marker_offset; // expressed as an offset from the beginning of the object for a heap buffered value + // payload_offset must be subtracted to get the offset from the beginning of the payload + + friend class InlineKlass; +}; + +class InlineLayoutInfo : public MetaspaceObj { + InlineKlass* _klass; + LayoutKind _kind; + int _null_marker_offset; // null marker offset for this field, relative to the beginning of the current container + + public: + InlineLayoutInfo(): _klass(nullptr), _kind(LayoutKind::UNKNOWN), _null_marker_offset(-1) {} + InlineLayoutInfo(InlineKlass* ik, LayoutKind kind, int size, int nm_offset): + _klass(ik), _kind(kind), _null_marker_offset(nm_offset) {} + + InlineKlass* klass() const { return _klass; } + void set_klass(InlineKlass* k) { _klass = k; } + + LayoutKind kind() const { + assert(_kind != LayoutKind::UNKNOWN, "Not set"); + return _kind; + } + void set_kind(LayoutKind lk) { _kind = lk; } + + int null_marker_offset() const { + assert(_null_marker_offset != -1, "Not set"); + return _null_marker_offset; + } + void set_null_marker_offset(int o) { _null_marker_offset = o; } + + void metaspace_pointers_do(MetaspaceClosure* it); + MetaspaceObj::Type type() const { return InlineLayoutInfoType; } + + static ByteSize klass_offset() { return in_ByteSize(offset_of(InlineLayoutInfo, _klass)); } + static ByteSize null_marker_offset_offset() { return in_ByteSize(offset_of(InlineLayoutInfo, _null_marker_offset)); } +}; + class InstanceKlass: public Klass { friend class VMStructs; friend class JVMCIVMStructs; friend class ClassFileParser; friend class CompileReplay; + friend class TemplateTable; public: static const KlassKind Kind = InstanceKlassKind; protected: - InstanceKlass(const ClassFileParser& parser, KlassKind kind = Kind, ReferenceType reference_type = REF_NONE); + InstanceKlass(const ClassFileParser& parser, KlassKind kind = Kind, markWord prototype = markWord::prototype(), ReferenceType reference_type = REF_NONE); void* operator new(size_t size, ClassLoaderData* loader_data, size_t word_size, bool use_class_space, TRAPS) throw(); @@ -279,6 +341,10 @@ class InstanceKlass: public Klass { Array* _fieldinfo_search_table; Array* _fields_status; + Array* _inline_layout_info_array; + Array* _loadable_descriptors; + const InlineKlassFixedBlock* _adr_inlineklass_fixed_block; + // embedded Java vtable follows here // embedded Java itables follows here // embedded static fields follows here @@ -327,6 +393,21 @@ class InstanceKlass: public Klass { bool has_localvariable_table() const { return _misc_flags.has_localvariable_table(); } void set_has_localvariable_table(bool b) { _misc_flags.set_has_localvariable_table(b); } + bool has_inline_type_fields() const { return _misc_flags.has_inline_type_fields(); } + void set_has_inline_type_fields() { _misc_flags.set_has_inline_type_fields(true); } + + bool is_naturally_atomic() const { return _misc_flags.is_naturally_atomic(); } + void set_is_naturally_atomic() { _misc_flags.set_is_naturally_atomic(true); } + + // Query if this class has atomicity requirements (default is yes) + // This bit can occur anywhere, but is only significant + // for inline classes *and* their super types. + // It inherits from supers. + // Its value depends on the ForceNonTearable VM option, the LooselyConsistentValue annotation + // and the presence of flat fields with atomicity requirements + bool must_be_atomic() const { return _misc_flags.must_be_atomic(); } + void set_must_be_atomic() { _misc_flags.set_must_be_atomic(true); } + // field sizes int nonstatic_field_size() const { return _nonstatic_field_size; } void set_nonstatic_field_size(int size) { _nonstatic_field_size = size; } @@ -391,6 +472,12 @@ class InstanceKlass: public Klass { FieldStatus field_status(int index) const { return fields_status()->at(index); } inline Symbol* field_name (int index) const; inline Symbol* field_signature (int index) const; + bool field_is_flat(int index) const { return field_flags(index).is_flat(); } + bool field_has_null_marker(int index) const { return field_flags(index).has_null_marker(); } + bool field_is_null_free_inline_type(int index) const; + bool is_class_in_loadable_descriptors_attribute(Symbol* name) const; + + int null_marker_offset(int index) const { return inline_layout_info(index).null_marker_offset(); } // Number of Java declared fields int java_fields_count() const; @@ -405,6 +492,9 @@ class InstanceKlass: public Klass { Array* fields_status() const {return _fields_status; } void set_fields_status(Array* array) { _fields_status = array; } + Array* loadable_descriptors() const { return _loadable_descriptors; } + void set_loadable_descriptors(Array* c) { _loadable_descriptors = c; } + // inner classes Array* inner_classes() const { return _inner_classes; } void set_inner_classes(Array* f) { _inner_classes = f; } @@ -500,6 +590,9 @@ class InstanceKlass: public Klass { // Find InnerClasses attribute and return outer_class_info_index & inner_name_index. bool find_inner_classes_attr(int* ooff, int* noff, TRAPS) const; + // Check if this klass can be null-free + static void check_can_be_annotated_with_NullRestricted(InstanceKlass* type, Symbol* container_klass_name, TRAPS); + private: // Check prohibited package ("java/" only loadable by boot or platform loaders) static void check_prohibited_package(Symbol* class_name, @@ -535,6 +628,9 @@ class InstanceKlass: public Klass { bool is_marked_dependent() const { return _misc_flags.is_marked_dependent(); } void set_is_marked_dependent(bool value) { _misc_flags.set_is_marked_dependent(value); } + static ByteSize kind_offset() { return in_ByteSize(offset_of(InstanceKlass, _kind)); } + static ByteSize misc_flags_offset() { return in_ByteSize(offset_of(InstanceKlass, _misc_flags)); } + // initialization (virtuals from Klass) bool should_be_initialized() const; // means that initialize should be called void initialize_with_aot_initialized_mirror(TRAPS); @@ -662,6 +758,8 @@ class InstanceKlass: public Klass { u2 major_version() const; void set_major_version(u2 major_version); + bool supports_inline_types() const; + // source debug extension const char* source_debug_extension() const { return _source_debug_extension; } void set_source_debug_extension(const char* array, int length); @@ -776,6 +874,13 @@ class InstanceKlass: public Klass { inline u2 next_method_idnum(); void set_initial_method_idnum(u2 value) { _idnum_allocated_count = value; } + // runtime support for strict statics + bool has_strict_static_fields() const { return _misc_flags.has_strict_static_fields(); } + void set_has_strict_static_fields(bool b) { _misc_flags.set_has_strict_static_fields(b); } + void notify_strict_static_access(int field_index, bool is_writing, TRAPS); + const char* format_strict_static_message(Symbol* field_name, const char* doing_what = nullptr); + void throw_strict_static_exception(Symbol* field_name, const char* when, TRAPS); + // generics support Symbol* generic_signature() const; u2 generic_signature_index() const; @@ -820,7 +925,6 @@ class InstanceKlass: public Klass { // additional member function to return a handle instanceHandle allocate_instance_handle(TRAPS); - objArrayOop allocate_objArray(int n, int length, TRAPS); // Helper function static instanceOop register_finalizer(instanceOop i, TRAPS); @@ -870,6 +974,9 @@ class InstanceKlass: public Klass { JFR_ONLY(DEFINE_KLASS_TRACE_ID_OFFSET;) static ByteSize init_thread_offset() { return byte_offset_of(InstanceKlass, _init_thread); } + static ByteSize inline_layout_info_array_offset() { return in_ByteSize(offset_of(InstanceKlass, _inline_layout_info_array)); } + static ByteSize adr_inlineklass_fixed_block_offset() { return in_ByteSize(offset_of(InstanceKlass, _adr_inlineklass_fixed_block)); } + // subclass/subinterface checks bool implements_interface(Klass* k) const; bool is_same_or_direct_interface(Klass* k) const; @@ -933,32 +1040,54 @@ class InstanceKlass: public Klass { static int size(int vtable_length, int itable_length, int nonstatic_oop_map_size, - bool is_interface) { + bool is_interface, + bool is_inline_type) { return align_metadata_size(header_size() + vtable_length + itable_length + nonstatic_oop_map_size + - (is_interface ? (int)sizeof(Klass*)/wordSize : 0)); + (is_interface ? (int)sizeof(Klass*)/wordSize : 0) + + (is_inline_type ? (int)sizeof(InlineKlassFixedBlock) : 0)); } int size() const { return size(vtable_length(), itable_length(), nonstatic_oop_map_size(), - is_interface()); + is_interface(), + is_inline_klass()); } inline intptr_t* start_of_itable() const; inline intptr_t* end_of_itable() const; inline oop static_field_base_raw(); + bool bounds_check(address addr, bool edge_ok = false, intptr_t size_in_bytes = -1) const PRODUCT_RETURN0; inline OopMapBlock* start_of_nonstatic_oop_maps() const; inline Klass** end_of_nonstatic_oop_maps() const; inline InstanceKlass* volatile* adr_implementor() const; + void set_inline_layout_info_array(Array* array) { _inline_layout_info_array = array; } + Array* inline_layout_info_array() const { return _inline_layout_info_array; } + void set_inline_layout_info(int index, InlineLayoutInfo *info) { + assert(_inline_layout_info_array != nullptr ,"Array not created"); + _inline_layout_info_array->at_put(index, *info); + } + InlineLayoutInfo inline_layout_info(int index) const { + assert(_inline_layout_info_array != nullptr ,"Array not created"); + return _inline_layout_info_array->at(index); + } + InlineLayoutInfo* inline_layout_info_adr(int index) { + assert(_inline_layout_info_array != nullptr ,"Array not created"); + return _inline_layout_info_array->adr_at(index); + } + + inline InlineKlass* get_inline_type_field_klass(int idx) const ; + inline InlineKlass* get_inline_type_field_klass_or_null(int idx) const; + // Use this to return the size of an instance in heap words: - int size_helper() const { + virtual int size_helper() const { return layout_helper_to_size_helper(layout_helper()); } @@ -1009,6 +1138,7 @@ class InstanceKlass: public Klass { // Naming const char* signature_name() const; + const char* signature_name_of_carrier(char c) const; // Oop fields (and metadata) iterators // @@ -1134,7 +1264,7 @@ class InstanceKlass: public Klass { virtual void remove_unshareable_info(); void remove_unshareable_flags(); virtual void remove_java_mirror(); - void restore_unshareable_info(ClassLoaderData* loader_data, Handle protection_domain, PackageEntry* pkg_entry, TRAPS); + virtual void restore_unshareable_info(ClassLoaderData* loader_data, Handle protection_domain, PackageEntry* pkg_entry, TRAPS); void init_shared_package_entry(); bool can_be_verified_at_dumptime() const; void compute_has_loops_flag_for_methods(); @@ -1161,7 +1291,8 @@ class InstanceKlass: public Klass { void oop_print_value_on(oop obj, outputStream* st); - void oop_print_on (oop obj, outputStream* st); + void oop_print_on (oop obj, outputStream* st) { oop_print_on(obj, st, 0, 0); } + void oop_print_on (oop obj, outputStream* st, int indent = 0, int base_offset = 0); #ifndef PRODUCT void print_dependent_nmethods(bool verbose = false); diff --git a/src/hotspot/share/oops/instanceKlass.inline.hpp b/src/hotspot/share/oops/instanceKlass.inline.hpp index f9db34f4884..70380d11d35 100644 --- a/src/hotspot/share/oops/instanceKlass.inline.hpp +++ b/src/hotspot/share/oops/instanceKlass.inline.hpp @@ -63,6 +63,21 @@ inline InstanceKlass* volatile* InstanceKlass::adr_implementor() const { } } +inline InlineKlass* InstanceKlass::get_inline_type_field_klass(int idx) const { + assert(has_inline_type_fields(), "Sanity checking"); + assert(idx < java_fields_count(), "IOOB"); + InlineKlass* k = inline_layout_info(idx).klass(); + assert(k != nullptr, "Should always be set before being read"); + return k; +} + +inline InlineKlass* InstanceKlass::get_inline_type_field_klass_or_null(int idx) const { + assert(has_inline_type_fields(), "Sanity checking"); + assert(idx < java_fields_count(), "IOOB"); + InlineKlass* k = inline_layout_info(idx).klass(); + return k; +} + inline ObjArrayKlass* InstanceKlass::array_klasses_acquire() const { return Atomic::load_acquire(&_array_klasses); } diff --git a/src/hotspot/share/oops/instanceKlassFlags.hpp b/src/hotspot/share/oops/instanceKlassFlags.hpp index 1872c3bc998..3c0b89e333e 100644 --- a/src/hotspot/share/oops/instanceKlassFlags.hpp +++ b/src/hotspot/share/oops/instanceKlassFlags.hpp @@ -56,8 +56,19 @@ class InstanceKlassFlags { flag(has_final_method , 1 << 13) /* True if klass has final method */ \ flag(has_aot_safe_initializer , 1 << 14) /* has @AOTSafeClassInitializer annotation */ \ flag(is_runtime_setup_required , 1 << 15) /* has a runtimeSetup method to be called */ \ + flag(has_inline_type_fields , 1 << 16) /* has inline fields and related embedded section is not empty */ \ + flag(is_empty_inline_type , 1 << 17) /* empty inline type (*) */ \ + flag(is_naturally_atomic , 1 << 18) /* loaded/stored in one instruction*/ \ + flag(must_be_atomic , 1 << 19) /* doesn't allow tearing */ \ + flag(has_loosely_consistent_annotation , 1 << 20) /* the class has the LooselyConsistentValue annotation WARNING: it doesn't automatically mean that the class allows tearing */ \ + flag(has_strict_static_fields , 1 << 21) /* True if strict static fields declared */ \ /* end of list */ + /* (*) An inline type is considered empty if it contains no non-static fields or + if it contains only empty inline fields. Note that JITs have a slightly different + definition: empty inline fields must be flat otherwise the container won't + be considered empty */ + #define IK_FLAGS_ENUM_NAME(name, value) _misc_##name = value, enum { IK_FLAGS_DO(IK_FLAGS_ENUM_NAME) @@ -84,7 +95,7 @@ class InstanceKlassFlags { } // These flags are write-once before the class is published and then read-only so don't require atomic updates. - u2 _flags; + u4 _flags; // These flags are written during execution so require atomic stores u1 _status; @@ -109,6 +120,13 @@ class InstanceKlassFlags { void set_class_loader_type(const ClassLoaderData* cld); + + u4 flags() const { return _flags; } + + static u4 is_empty_inline_type_value() { + return _misc_is_empty_inline_type; + } + void assert_is_safe(bool set) NOT_DEBUG_RETURN; // Create getters and setters for the status values. diff --git a/src/hotspot/share/oops/instanceRefKlass.cpp b/src/hotspot/share/oops/instanceRefKlass.cpp index b8327492493..d0c217f1ef1 100644 --- a/src/hotspot/share/oops/instanceRefKlass.cpp +++ b/src/hotspot/share/oops/instanceRefKlass.cpp @@ -62,7 +62,7 @@ static ReferenceType determine_reference_type(const ClassFileParser& parser) { } InstanceRefKlass::InstanceRefKlass(const ClassFileParser& parser) - : InstanceKlass(parser, Kind, determine_reference_type(parser)) {} + : InstanceKlass(parser, Kind, markWord::prototype(), determine_reference_type(parser)) {} void InstanceRefKlass::update_nonstatic_oop_maps(Klass* k) { // Clear the nonstatic oop-map entries corresponding to referent diff --git a/src/hotspot/share/oops/klass.cpp b/src/hotspot/share/oops/klass.cpp index f4ae128aef7..418cd81292e 100644 --- a/src/hotspot/share/oops/klass.cpp +++ b/src/hotspot/share/oops/klass.cpp @@ -274,23 +274,6 @@ Method* Klass::uncached_lookup_method(const Symbol* name, const Symbol* signatur return nullptr; } -static markWord make_prototype(const Klass* kls) { - markWord prototype = markWord::prototype(); -#ifdef _LP64 - if (UseCompactObjectHeaders) { - // With compact object headers, the narrow Klass ID is part of the mark word. - // We therfore seed the mark word with the narrow Klass ID. - // Note that only those Klass that can be instantiated have a narrow Klass ID. - // For those who don't, we leave the klass bits empty and assert if someone - // tries to use those. - const narrowKlass nk = CompressedKlassPointers::is_encodable(kls) ? - CompressedKlassPointers::encode(const_cast(kls)) : 0; - prototype = prototype.set_narrow_klass(nk); - } -#endif - return prototype; -} - Klass::Klass() : _kind(UnknownKlassKind) { assert(CDSConfig::is_dumping_static_archive() || CDSConfig::is_using_archive(), "only for cds"); } @@ -299,9 +282,9 @@ Klass::Klass() : _kind(UnknownKlassKind) { // which zeros out memory - calloc equivalent. // The constructor is also used from CppVtableCloner, // which doesn't zero out the memory before calling the constructor. -Klass::Klass(KlassKind kind) : _kind(kind), - _prototype_header(make_prototype(this)), +Klass::Klass(KlassKind kind, markWord prototype_header) : _kind(kind), _shared_class_path_index(-1) { + set_prototype_header(make_prototype_header(this, prototype_header)); CDS_ONLY(_shared_class_flags = 0;) CDS_JAVA_HEAP_ONLY(_archived_mirror_index = -1;) _primary_supers[0] = this; @@ -314,12 +297,12 @@ jint Klass::array_layout_helper(BasicType etype) { int hsize = arrayOopDesc::base_offset_in_bytes(etype); int esize = type2aelembytes(etype); bool isobj = (etype == T_OBJECT); - int tag = isobj ? _lh_array_tag_obj_value : _lh_array_tag_type_value; - int lh = array_layout_helper(tag, hsize, etype, exact_log2(esize)); + int tag = isobj ? _lh_array_tag_ref_value : _lh_array_tag_type_value; + int lh = array_layout_helper(tag, false, hsize, etype, exact_log2(esize)); assert(lh < (int)_lh_neutral_value, "must look like an array layout"); assert(layout_helper_is_array(lh), "correct kind"); - assert(layout_helper_is_objArray(lh) == isobj, "correct kind"); + assert(layout_helper_is_refArray(lh) == isobj, "correct kind"); assert(layout_helper_is_typeArray(lh) == !isobj, "correct kind"); assert(layout_helper_header_size(lh) == hsize, "correct decode"); assert(layout_helper_element_type(lh) == etype, "correct decode"); @@ -1033,10 +1016,8 @@ void Klass::oop_print_on(oop obj, outputStream* st) { // print header obj->mark().print_on(st); st->cr(); - if (UseCompactObjectHeaders) { - st->print(BULLET"prototype_header: " INTPTR_FORMAT, _prototype_header.value()); - st->cr(); - } + st->print(BULLET"prototype_header: " INTPTR_FORMAT, _prototype_header.value()); + st->cr(); } // print class diff --git a/src/hotspot/share/oops/klass.hpp b/src/hotspot/share/oops/klass.hpp index d62f3f21ee2..aeb0ec9e901 100644 --- a/src/hotspot/share/oops/klass.hpp +++ b/src/hotspot/share/oops/klass.hpp @@ -66,18 +66,22 @@ class Klass : public Metadata { friend class JVMCIVMStructs; public: // Klass Kinds for all subclasses of Klass - enum KlassKind : u2 { - InstanceKlassKind, - InstanceRefKlassKind, - InstanceMirrorKlassKind, - InstanceClassLoaderKlassKind, - InstanceStackChunkKlassKind, - TypeArrayKlassKind, - ObjArrayKlassKind, - UnknownKlassKind - }; - - static const uint KLASS_KIND_COUNT = ObjArrayKlassKind + 1; + enum KlassKind : u2 + { + InstanceKlassKind, + InlineKlassKind, + InstanceRefKlassKind, + InstanceMirrorKlassKind, + InstanceClassLoaderKlassKind, + InstanceStackChunkKlassKind, + TypeArrayKlassKind, + ObjArrayKlassKind, + RefArrayKlassKind, + FlatArrayKlassKind, + UnknownKlassKind + }; + + static const uint KLASS_KIND_COUNT = FlatArrayKlassKind + 1; protected: // If you add a new field that points to any metaspace object, you @@ -99,7 +103,7 @@ class Klass : public Metadata { // distinct bytes, as follows: // MSB:[tag, hsz, ebt, log2(esz)]:LSB // where: - // tag is 0x80 if the elements are oops, 0xC0 if non-oops + // tag is 0x80 if the elements are oops, 0xC0 if non-oops, 0xA0 if value types // hsz is array header size in bytes (i.e., offset of first element) // ebt is the BasicType of the elements // esz is the element size in bytes @@ -202,7 +206,7 @@ class Klass : public Metadata { protected: - Klass(KlassKind kind); + Klass(KlassKind kind, markWord prototype_header = markWord::prototype()); Klass(); public: @@ -450,11 +454,18 @@ class Klass : public Metadata { static const int _lh_element_type_mask = right_n_bits(BitsPerByte); // shifted mask static const int _lh_header_size_shift = BitsPerByte*2; static const int _lh_header_size_mask = right_n_bits(BitsPerByte); // shifted mask - static const int _lh_array_tag_bits = 2; + static const int _lh_array_tag_bits = 4; static const int _lh_array_tag_shift = BitsPerInt - _lh_array_tag_bits; - static const int _lh_array_tag_obj_value = ~0x01; // 0x80000000 >> 30 - static const unsigned int _lh_array_tag_type_value = 0Xffffffff; // ~0x00, // 0xC0000000 >> 30 + static const unsigned int _lh_array_tag_type_value = 0Xfffffffc; + static const unsigned int _lh_array_tag_flat_value = 0Xfffffffa; + static const unsigned int _lh_array_tag_ref_value = 0Xfffffff8; + + // null-free array flag bit under the array tag bits, shift one more to get array tag value + static const int _lh_null_free_shift = _lh_array_tag_shift - 1; + static const int _lh_null_free_mask = 1; + + static const jint _lh_array_tag_flat_value_bit_inplace = (jint) (1 << (_lh_array_tag_shift + 1)); static int layout_helper_size_in_bytes(jint lh) { assert(lh > (jint)_lh_neutral_value, "must be instance"); @@ -471,12 +482,22 @@ class Klass : public Metadata { return (jint)lh < (jint)_lh_neutral_value; } static bool layout_helper_is_typeArray(jint lh) { - // _lh_array_tag_type_value == (lh >> _lh_array_tag_shift); - return (juint)lh >= (juint)(_lh_array_tag_type_value << _lh_array_tag_shift); + return (juint) _lh_array_tag_type_value == (juint)(lh >> _lh_array_tag_shift); } - static bool layout_helper_is_objArray(jint lh) { - // _lh_array_tag_obj_value == (lh >> _lh_array_tag_shift); - return (jint)lh < (jint)(_lh_array_tag_type_value << _lh_array_tag_shift); + static bool layout_helper_is_refArray(jint lh) { + return (juint)_lh_array_tag_ref_value == (juint)(lh >> _lh_array_tag_shift); + } + static bool layout_helper_is_flatArray(jint lh) { + return (juint)_lh_array_tag_flat_value == (juint)(lh >> _lh_array_tag_shift); + } + static bool layout_helper_is_null_free(jint lh) { + assert(layout_helper_is_flatArray(lh) || layout_helper_is_refArray(lh), "must be array of inline types"); + return ((lh >> _lh_null_free_shift) & _lh_null_free_mask); + } + static jint layout_helper_set_null_free(jint lh) { + lh |= (_lh_null_free_mask << _lh_null_free_shift); + assert(layout_helper_is_null_free(lh), "Bad encoding"); + return lh; } static int layout_helper_header_size(jint lh) { assert(lh < (jint)_lh_neutral_value, "must be array"); @@ -487,7 +508,7 @@ class Klass : public Metadata { static BasicType layout_helper_element_type(jint lh) { assert(lh < (jint)_lh_neutral_value, "must be array"); int btvalue = (lh >> _lh_element_type_shift) & _lh_element_type_mask; - assert(btvalue >= T_BOOLEAN && btvalue <= T_OBJECT, "sanity"); + assert((btvalue >= T_BOOLEAN && btvalue <= T_OBJECT) || btvalue == T_FLAT_ELEMENT, "sanity"); return (BasicType) btvalue; } @@ -508,12 +529,13 @@ class Klass : public Metadata { static int layout_helper_log2_element_size(jint lh) { assert(lh < (jint)_lh_neutral_value, "must be array"); int l2esz = (lh >> _lh_log2_element_size_shift) & _lh_log2_element_size_mask; - assert(l2esz <= LogBytesPerLong, + assert(layout_helper_element_type(lh) == T_FLAT_ELEMENT || l2esz <= LogBytesPerLong, "sanity. l2esz: 0x%x for lh: 0x%x", (uint)l2esz, (uint)lh); return l2esz; } - static jint array_layout_helper(jint tag, int hsize, BasicType etype, int log2_esize) { + static jint array_layout_helper(jint tag, bool null_free, int hsize, BasicType etype, int log2_esize) { return (tag << _lh_array_tag_shift) + | ((null_free ? 1 : 0) << _lh_null_free_shift) | (hsize << _lh_header_size_shift) | ((int)etype << _lh_element_type_shift) | (log2_esize << _lh_log2_element_size_shift); @@ -656,8 +678,12 @@ class Klass : public Metadata { virtual bool is_instance_klass_slow() const { return false; } virtual bool is_array_klass_slow() const { return false; } virtual bool is_objArray_klass_slow() const { return false; } + virtual bool is_refArray_klass_slow() const { return false; } virtual bool is_typeArray_klass_slow() const { return false; } + virtual bool is_flatArray_klass_slow() const { return false; } #endif // ASSERT + // current implementation uses this method even in non debug builds + virtual bool is_inline_klass_slow() const { return false; } public: // Fast non-virtual versions @@ -673,17 +699,21 @@ class Klass : public Metadata { #endif bool is_instance_klass() const { return assert_same_query(_kind <= InstanceStackChunkKlassKind, is_instance_klass_slow()); } - // Other is anything that is not one of the more specialized kinds of InstanceKlass. - bool is_other_instance_klass() const { return _kind == InstanceKlassKind; } + bool is_inline_klass() const { return assert_same_query(_kind == InlineKlassKind, is_inline_klass_slow()); } bool is_reference_instance_klass() const { return _kind == InstanceRefKlassKind; } bool is_mirror_instance_klass() const { return _kind == InstanceMirrorKlassKind; } bool is_class_loader_instance_klass() const { return _kind == InstanceClassLoaderKlassKind; } bool is_array_klass() const { return assert_same_query( _kind >= TypeArrayKlassKind, is_array_klass_slow()); } bool is_stack_chunk_instance_klass() const { return _kind == InstanceStackChunkKlassKind; } - bool is_objArray_klass() const { return assert_same_query( _kind == ObjArrayKlassKind, is_objArray_klass_slow()); } + bool is_flatArray_klass() const { return assert_same_query( _kind == FlatArrayKlassKind, is_flatArray_klass_slow()); } + bool is_objArray_klass() const { return assert_same_query( _kind == ObjArrayKlassKind || _kind == RefArrayKlassKind || _kind == FlatArrayKlassKind, is_objArray_klass_slow()); } + bool is_refArray_klass() const { return assert_same_query( _kind == RefArrayKlassKind, is_refArray_klass_slow()); } bool is_typeArray_klass() const { return assert_same_query( _kind == TypeArrayKlassKind, is_typeArray_klass_slow()); } + bool is_refined_objArray_klass() const { return is_refArray_klass() || is_flatArray_klass(); } #undef assert_same_query + inline bool is_null_free_array_klass() const { return !is_typeArray_klass() && layout_helper_is_null_free(layout_helper()); } + // Access flags AccessFlags access_flags() const { return _access_flags; } void set_access_flags(AccessFlags flags) { _access_flags = flags; } @@ -692,8 +722,8 @@ class Klass : public Metadata { bool is_final() const { return _access_flags.is_final(); } bool is_interface() const { return _access_flags.is_interface(); } bool is_abstract() const { return _access_flags.is_abstract(); } - bool is_super() const { return _access_flags.is_super(); } bool is_synthetic() const { return _access_flags.is_synthetic(); } + bool is_identity_class() const { assert(is_instance_klass(), "only for instanceKlass"); return _access_flags.is_identity_class(); } void set_is_synthetic() { _access_flags.set_is_synthetic(); } bool has_finalizer() const { return _misc_flags.has_finalizer(); } void set_has_finalizer() { _misc_flags.set_has_finalizer(true); } @@ -709,9 +739,11 @@ class Klass : public Metadata { bool is_cloneable() const; void set_is_cloneable(); + static inline markWord make_prototype_header(const Klass* kls, markWord prototype = markWord::prototype()); inline markWord prototype_header() const; inline void set_prototype_header(markWord header); static ByteSize prototype_header_offset() { return in_ByteSize(offset_of(Klass, _prototype_header)); } + inline void set_prototype_header_klass(narrowKlass klass); JFR_ONLY(DEFINE_TRACE_ID_METHODS;) diff --git a/src/hotspot/share/oops/klass.inline.hpp b/src/hotspot/share/oops/klass.inline.hpp index 19d4954ccad..51e6933eb14 100644 --- a/src/hotspot/share/oops/klass.inline.hpp +++ b/src/hotspot/share/oops/klass.inline.hpp @@ -59,27 +59,40 @@ inline bool Klass::is_loader_alive() const { return class_loader_data()->is_alive(); } +inline markWord Klass::make_prototype_header(const Klass* kls, markWord prototype) { + if (UseCompactObjectHeaders) { + // With compact object headers, the narrow Klass ID is part of the mark word. + // We therefore seed the mark word with the narrow Klass ID. + // Note that only those Klass that can be instantiated have a narrow Klass ID. + // For those who don't, we leave the klass bits empty and assert if someone + // tries to use those. + const narrowKlass nk = CompressedKlassPointers::is_encodable(kls) ? + CompressedKlassPointers::encode(const_cast(kls)) : 0; + prototype = prototype.set_narrow_klass(nk); + } + return prototype; +} + +inline void Klass::set_prototype_header(markWord header) { + _prototype_header = header; +} + inline bool Klass::is_loader_present_and_alive() const { ClassLoaderData* cld = class_loader_data(); return (cld != nullptr) ? cld->is_alive() : false; } inline markWord Klass::prototype_header() const { - assert(UseCompactObjectHeaders, "only use with compact object headers"); -#ifdef _LP64 // You only need prototypes for allocating objects. If the class is not instantiable, it won't live in // class space and have no narrow Klass ID. But in that case we should not need the prototype. - assert(_prototype_header.narrow_klass() > 0, "Klass " PTR_FORMAT ": invalid prototype (" PTR_FORMAT ")", + assert(!UseCompactObjectHeaders || _prototype_header.narrow_klass() > 0, "Klass " PTR_FORMAT ": invalid prototype (" PTR_FORMAT ")", p2i(this), _prototype_header.value()); -#endif return _prototype_header; } -// This is only used when dumping the archive. In other cases, -// the _prototype_header is already initialized to the right thing. -inline void Klass::set_prototype_header(markWord header) { - assert(UseCompactObjectHeaders, "only with compact headers"); - _prototype_header = header; +inline void Klass::set_prototype_header_klass(narrowKlass klass) { + // Merge narrowKlass in existing prototype header. + _prototype_header = _prototype_header.set_narrow_klass(klass); } // Loading the java_mirror does not keep its holder alive. See Klass::keep_alive(). diff --git a/src/hotspot/share/oops/klassVtable.cpp b/src/hotspot/share/oops/klassVtable.cpp index ce4e322930f..eef73d2cbb3 100644 --- a/src/hotspot/share/oops/klassVtable.cpp +++ b/src/hotspot/share/oops/klassVtable.cpp @@ -1230,10 +1230,10 @@ void klassItable::initialize_itable_and_check_constraints(TRAPS) { } inline bool interface_method_needs_itable_index(Method* m) { - if (m->is_static()) return false; // e.g., Stream.empty - if (m->is_object_initializer()) return false; // - if (m->is_static_initializer()) return false; // - if (m->is_private()) return false; // uses direct call + if (m->is_static()) return false; // e.g., Stream.empty + if (m->is_private()) return false; // uses direct call + if (m->is_object_constructor()) return false; // (...)V + if (m->is_class_initializer()) return false; // ()V // If an interface redeclares a method from java.lang.Object, // it should already have a vtable index, don't touch it. // e.g., CharSequence.toString (from initialize_vtable) @@ -1440,6 +1440,18 @@ class InterfaceVisiterClosure : public StackObj { virtual void doit(InstanceKlass* intf, int method_count) = 0; }; +int count_interface_methods_needing_itable_index(Array* methods) { + int method_count = 0; + if (methods->length() > 0) { + for (int i = methods->length(); --i >= 0; ) { + if (interface_method_needs_itable_index(methods->at(i))) { + method_count++; + } + } + } + return method_count; +} + // Visit all interfaces with at least one itable method static void visit_all_interfaces(Array* transitive_intf, InterfaceVisiterClosure *blk) { // Handle array argument diff --git a/src/hotspot/share/oops/klassVtable.hpp b/src/hotspot/share/oops/klassVtable.hpp index b8635e7f14b..d644c51b707 100644 --- a/src/hotspot/share/oops/klassVtable.hpp +++ b/src/hotspot/share/oops/klassVtable.hpp @@ -298,7 +298,10 @@ class klassItable { itableMethodEntry* method_entry(int i) { assert(0 <= i && i <= _size_method_table, "index out of bounds"); return &((itableMethodEntry*)method_start())[i]; } - int size_offset_table() { return _size_offset_table; } + InstanceKlass* klass() const { return _klass; } + int table_offset() const { return _table_offset; } + int size_offset_table() const { return _size_offset_table; } + int size_method_table() const { return _size_method_table; } // Initialization void initialize_itable_and_check_constraints(TRAPS); diff --git a/src/hotspot/share/oops/layoutKind.hpp b/src/hotspot/share/oops/layoutKind.hpp new file mode 100644 index 00000000000..b58c51436e2 --- /dev/null +++ b/src/hotspot/share/oops/layoutKind.hpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_OOPS_LAYOUTKIND_HPP +#define SHARE_OOPS_LAYOUTKIND_HPP + +#include "utilities/globalDefinitions.hpp" + +// LayoutKind is an enum used to indicate which layout has been used for a given value field. +// Each layout has its own properties and its own access protocol that is detailed below. +// +// REFERENCE : this layout uses a pointer to a heap allocated instance (no flattening). +// When used, field_flags().is_flat() is false . The field can be nullable or +// null-restricted, in the later case, field_flags().is_null_free_inline_type() is true. +// In case of a null-restricted field, putfield and putstatic must perform a null-check +// before writing a new value. Still for null-restricted fields, if getfield reads a null pointer +// from the receiver, it means that the field was not initialized yet, and getfield must substitute +// the null reference with the default value of the field's class. +// NON_ATOMIC_FLAT : this layout is the simplest form of flattening. Any field embedded inside the flat field +// can be accessed independently. The field is null-restricted, meaning putfield must perform a +// null-check before performing a field update. +// ATOMIC_FLAT : this flat layout is designed for atomic updates, with size and alignment that make use of +// atomic instructions possible. All accesses, reads and writes, must be performed atomically. +// The field is null-restricted, meaning putfield must perform a null-check before performing a +// field update. +// NULLABLE_ATOMIC_FLAT : this is the flat layout designed for JEP 401. It is designed for atomic updates, +// with size and alignment that make use of atomic instructions possible. All accesses, reads and +// writes, must be performed atomically. The layout includes a null marker which indicates if the +// field's value must be considered as null or not. The null marker is a byte, with the value zero +// meaning the field's value is null, and a non-zero value meaning the field's value is not null. +// A getfield must check the value of the null marker before returning a value. If the null marker +// is zero, getfield must return the null reference, otherwise it returns the field's value read +// from the receiver. When a putfield writes a non-null value to such field, the update, including +// the field's value and the null marker, must be performed in a single atomic operation. If the +// source of the value is a heap allocated instance of the field's class, it is allowed to set the +// null marker to non-zero in the heap allocated instance before copying the value to the receiver +// (the BUFFERED layout used in heap allocated values guarantees that the space for the null marker +// is included, but has no meaning for the heap allocated instance which is always non-null, and that +// the whole payload is correctly aligned for atomic operations). When a putfield writes null to such +// field, the null marker must be set to zero. However, if the field contains oops, those oops must be +// cleared too in order to prevent memory leaks. In order to simplify such operation, value classes +// supporting a NULLABLE_ATOMIC_FLAT layout have a pre-allocated reset value instance, filled with +// zeros, which can be used to simply overwrite the whole flat field and reset everything (oops and +// null marker). The reset value instance is needed because the VM needs an instance guaranteed to +// always be filled with zeros, and the default value could have its null marker set to non-zero if +// it is used as a source to update a NULLABLE_ATOMIC_FLAT field. +// BUFFERED: this layout is only used in heap buffered instances of a value class. It is computed to be compatible +// to be compatible in size and alignment with all other flat layouts supported by the value class. +// +// +// IMPORTANT: The REFERENCE layout must always be associated with the numerical value zero, because the implementation +// of the lava.lang.invoke.MemberName class relies on this property. + +enum class LayoutKind : uint32_t { + REFERENCE = 0, // indirection to a heap allocated instance + BUFFERED = 1, // layout used in heap allocated standalone instances + NON_ATOMIC_FLAT = 2, // flat, no guarantee of atomic updates, no null marker + ATOMIC_FLAT = 3, // flat, size compatible with atomic updates, alignment requirement is equal to the size + NULLABLE_ATOMIC_FLAT = 4, // flat, include a null marker, plus same properties as ATOMIC layout + UNKNOWN = 5 // used for uninitialized fields of type LayoutKind +}; + +#endif // SHARE_OOPS_LAYOUTKIND_HPP diff --git a/src/hotspot/share/oops/markWord.cpp b/src/hotspot/share/oops/markWord.cpp index 2ba57cddc67..42145747b91 100644 --- a/src/hotspot/share/oops/markWord.cpp +++ b/src/hotspot/share/oops/markWord.cpp @@ -72,6 +72,7 @@ void markWord::print_on(outputStream* st, bool print_monitor_info) const { st->print(" marked(" INTPTR_FORMAT ")", value()); } else if (has_monitor()) { // last bits = 10 // have to check has_monitor() before is_locked() + // Valhalla: inline types/arrays can't be monitored st->print(" monitor(" INTPTR_FORMAT ")=", value()); if (print_monitor_info && !UseObjectMonitorTable) { ObjectMonitor* mon = monitor(); @@ -83,19 +84,52 @@ void markWord::print_on(outputStream* st, bool print_monitor_info) const { } } else if (is_locked()) { // last bits != 01 => 00 // thin locked + // Valhalla: inline types can not possess an object monitor st->print(" locked(" INTPTR_FORMAT ")", value()); } else { st->print(" mark("); if (is_unlocked()) { // last bits = 01 st->print("is_unlocked"); + if (is_inline_type()) { + st->print(" inline_type"); + if (is_larval_state()) { + st->print("=larval"); + } + } if (has_no_hash()) { st->print(" no_hash"); } else { st->print(" hash=" INTPTR_FORMAT, hash()); } +#ifdef _LP64 // 64 bit encodings have array information + // flat or null-free do not imply each other + bool flat = is_flat_array(); + bool null_free = is_null_free_array(); + if (flat && !null_free) { + st->print(" flat_array"); + } else if (!flat && null_free) { + st->print(" null_free_array"); + } else if (flat && null_free) { + st->print(" flat_null_free_array"); + } +#endif } else { st->print("??"); } st->print(" age=%d)", age()); } } + +markWord markWord::flat_array_prototype(LayoutKind lk) { + switch(lk) { + case LayoutKind::ATOMIC_FLAT: + case LayoutKind::NON_ATOMIC_FLAT: + return markWord(null_free_flat_array_pattern); + break; + case LayoutKind::NULLABLE_ATOMIC_FLAT: + return markWord(nullable_flat_array_pattern); + break; + default: + ShouldNotReachHere(); + } +} diff --git a/src/hotspot/share/oops/markWord.hpp b/src/hotspot/share/oops/markWord.hpp index 9cb46f4697d..b4c3b87511c 100644 --- a/src/hotspot/share/oops/markWord.hpp +++ b/src/hotspot/share/oops/markWord.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,10 +25,12 @@ #ifndef SHARE_OOPS_MARKWORD_HPP #define SHARE_OOPS_MARKWORD_HPP +#include "layoutKind.hpp" #include "metaprogramming/primitiveConversions.hpp" #include "oops/compressedKlass.hpp" #include "oops/oopsHierarchy.hpp" #include "runtime/globals.hpp" +#include "utilities/vmEnums.hpp" #include @@ -65,9 +67,81 @@ // // We assume that stack/thread pointers have the lowest two bits cleared. // +// // - INFLATING() is a distinguished markword value of all zeros that is // used when inflating an existing stack-lock into an ObjectMonitor. // See below for is_being_inflated() and INFLATING(). +// +// +// +// Valhalla +// +// +// +// Project Valhalla has mark word encoding requirements for the following oops: +// +// * inline types: have alternative bytecode behavior, e.g. can not be locked +// - "larval state": mutable state, but only during object init, observable +// by only by a single thread (generally do not mutate markWord) +// +// * flat arrays: load/decode of klass layout helper is expensive for aaload +// +// * "null free" arrays: load/decode of klass layout helper again for aaload +// +// EnableValhalla +// +// Formerly known as "biased lock bit", "unused_gap" is free to use: using this +// bit to indicate inline type, combined with "unlocked" lock bits, means we +// will not interfere with lock encodings (displaced, inflating, and monitor), +// since inline types can't be locked. +// +// Further state encoding +// +// 32 bit plaforms currently have no further room for encoding. No room for +// "denormalized layout helper bits", these fast mark word tests can only be made on +// 64 bit platforms. 32-bit platforms need to load the klass->_layout_helper. This +// said, the larval state bit is still required for operation, stealing from the hash +// code is simplest mechanism. +// +// Valhalla specific encodings +// +// Revised Bit-format of an object header (most significant first, big endian layout below): +// +// 32 bits: +// -------- +// hash:24 ------------>| larval:1 age:4 inline_type:1 lock:2 +// +// 64 bits: +// -------- +// unused:1 | <-- hash:31 -->| unused:22 larval:1 age:4 flat_array:1 null_free_array:1 inline_type:1 lock:2 +// klass:22 hash:31 -->| larval:1 age:4 flat_array:1 null_free_array:1 inline_type:1 self-fwd:1 lock:2 (normal object) +// +// The "fast" static type bits (flat_array, null_free_array, and inline_type) +// are placed lowest next to lock bits to more easily decode forwarding pointers. +// G1 for example, implicitly clears age bits ("G1FullGCCompactionPoint::forward()") +// using "oopDesc->forwardee()", so it necessary for "markWord::decode_pointer()" +// to return a non-nullptr for this case, but not confuse the static type bits for +// a pointer. +// +// Note the position of 'self-fwd' is not by accident. When forwarding an +// object to a new heap position, HeapWord alignment guarantees the lower +// bits, including 'self-fwd' are 0. "is_self_forwarded()" will be correctly +// set to false. Otherwise encode_pointer_as_mark() may have 'self-fwd' set. +// +// +// Static types bits are recorded in the "klass->prototype_header()", displaced +// mark should simply use the prototype header as "slow path", rather chasing +// monitor or stack lock races. +// +// Lock patterns (note inline types can't be locked/monitor/inflating)... +// +// [ptr | 000] locked ptr points to real header on stack +// [header | ?01] unlocked regular object header +// [ptr | 010] monitor inflated lock (header is wapped out) +// [ptr | ?11] marked used to mark an object +// [0 ............ | 000] inflating inflation in progress +// +// class BasicLock; class ObjectMonitor; @@ -106,25 +180,48 @@ class markWord { // Conversion uintptr_t value() const { return _value; } - // Constants - static const int age_bits = 4; + // Constants, in least significant bit order static const int lock_bits = 2; static const int self_fwd_bits = 1; - static const int max_hash_bits = BitsPerWord - age_bits - lock_bits - self_fwd_bits; + // EnableValhalla: static prototype header bits (fast path instead of klass layout_helper) + static const int inline_type_bits = 1; + static const int null_free_array_bits = LP64_ONLY(1) NOT_LP64(0); + static const int flat_array_bits = LP64_ONLY(1) NOT_LP64(0); + // instance state + static const int age_bits = 4; + static const int larval_bits = 1; + static const int max_hash_bits = BitsPerWord - age_bits - lock_bits - inline_type_bits - larval_bits - flat_array_bits - null_free_array_bits - self_fwd_bits; static const int hash_bits = max_hash_bits > 31 ? 31 : max_hash_bits; - static const int unused_gap_bits = LP64_ONLY(4) NOT_LP64(0); // Reserved for Valhalla. static const int lock_shift = 0; - static const int self_fwd_shift = lock_shift + lock_bits; - static const int age_shift = self_fwd_shift + self_fwd_bits; - static const int hash_shift = age_shift + age_bits + unused_gap_bits; + static const int self_fwd_shift = lock_bits ; + static const int inline_type_shift = self_fwd_shift + self_fwd_bits; + static const int null_free_array_shift = inline_type_shift + inline_type_bits; + static const int flat_array_shift = null_free_array_shift + null_free_array_bits; + static const int age_shift = flat_array_shift + flat_array_bits; + static const int larval_shift = age_shift + age_bits; + static const int hash_shift = larval_shift + larval_bits; static const uintptr_t lock_mask = right_n_bits(lock_bits); static const uintptr_t lock_mask_in_place = lock_mask << lock_shift; static const uintptr_t self_fwd_mask = right_n_bits(self_fwd_bits); static const uintptr_t self_fwd_mask_in_place = self_fwd_mask << self_fwd_shift; + static const uintptr_t inline_type_bit_in_place = 1 << inline_type_shift; + static const uintptr_t inline_type_mask = inline_type_bit_in_place + lock_mask; + static const uintptr_t inline_type_mask_in_place = inline_type_mask << lock_shift; + static const uintptr_t null_free_array_mask = right_n_bits(null_free_array_bits); + static const uintptr_t null_free_array_mask_in_place = (null_free_array_mask << null_free_array_shift) | lock_mask_in_place; + static const uintptr_t null_free_array_bit_in_place = (1 << null_free_array_shift); + static const uintptr_t flat_array_mask = right_n_bits(flat_array_bits); + static const uintptr_t flat_array_mask_in_place = (flat_array_mask << flat_array_shift) | null_free_array_mask_in_place | lock_mask_in_place; + static const uintptr_t flat_array_bit_in_place = (1 << flat_array_shift); static const uintptr_t age_mask = right_n_bits(age_bits); static const uintptr_t age_mask_in_place = age_mask << age_shift; + + static const uintptr_t larval_mask = right_n_bits(larval_bits); + static const uintptr_t larval_mask_in_place = (larval_mask << larval_shift) | inline_type_mask_in_place; + static const uintptr_t larval_bit_in_place = (1 << larval_shift); + static const uintptr_t hash_mask = right_n_bits(hash_bits); static const uintptr_t hash_mask_in_place = hash_mask << hash_shift; @@ -147,6 +244,18 @@ class markWord { static const uintptr_t monitor_value = 2; static const uintptr_t marked_value = 3; + static const uintptr_t inline_type_pattern = inline_type_bit_in_place | unlocked_value; + static const uintptr_t null_free_array_pattern = null_free_array_bit_in_place | unlocked_value; + static const uintptr_t null_free_flat_array_pattern = flat_array_bit_in_place | null_free_array_pattern; + static const uintptr_t nullable_flat_array_pattern = flat_array_bit_in_place | unlocked_value; + + // Has static klass prototype, used for decode/encode pointer + static const uintptr_t static_prototype_mask = LP64_ONLY(right_n_bits(inline_type_bits + flat_array_bits + null_free_array_bits)) NOT_LP64(right_n_bits(inline_type_bits)); + static const uintptr_t static_prototype_mask_in_place = static_prototype_mask << lock_bits; + static const uintptr_t static_prototype_value_max = (1 << age_shift) - 1; + + static const uintptr_t larval_pattern = larval_bit_in_place | inline_type_pattern; + static const uintptr_t no_hash = 0 ; // no hash value assigned static const uintptr_t no_hash_in_place = (uintptr_t)no_hash << hash_shift; static const uintptr_t no_lock_in_place = unlocked_value; @@ -156,6 +265,10 @@ class markWord { // Creates a markWord with all bits set to zero. static markWord zero() { return markWord(uintptr_t(0)); } + bool is_inline_type() const { + return (mask_bits(value(), inline_type_mask_in_place) == inline_type_pattern); + } + // lock accessors (note that these assume lock_shift == 0) bool is_locked() const { return (mask_bits(value(), lock_mask_in_place) != unlocked_value); @@ -166,13 +279,17 @@ class markWord { bool is_marked() const { return (mask_bits(value(), lock_mask_in_place) == marked_value); } + + // is unlocked and not an inline type (which cannot be involved in locking, displacement or inflation) + // i.e. test both lock bits and the inline type bit together + bool is_neutral() const { // Not locked, or marked - a "clean" neutral state + return (mask_bits(value(), inline_type_mask_in_place) == unlocked_value); + } + bool is_forwarded() const { // Returns true for normal forwarded (0b011) and self-forwarded (0b1xx). return mask_bits(value(), lock_mask_in_place | self_fwd_mask_in_place) >= static_cast(marked_value); } - bool is_neutral() const { // Not locked, or marked - a "clean" neutral state - return (mask_bits(value(), lock_mask_in_place) == unlocked_value); - } // Special temporary state of the markWord while being inflated. // Code that looks at mark outside a lock need to take this into account. @@ -189,7 +306,8 @@ class markWord { // Should this header be preserved during GC? bool must_be_preserved() const { - return (!is_unlocked() || !has_no_hash()); + return (!is_unlocked() || !has_no_hash() || + (EnableValhalla && (is_larval_state() || is_inline_type() || is_flat_array() || is_null_free_array()))); } // WARNING: The following routines are used EXCLUSIVELY by @@ -283,6 +401,34 @@ class markWord { return hash() == no_hash; } + // private buffered value operations + markWord enter_larval_state() const { + return markWord(value() | larval_bit_in_place); + } + markWord exit_larval_state() const { + return markWord(value() & ~larval_bit_in_place); + } + bool is_larval_state() const { + return (mask_bits(value(), larval_mask_in_place) == larval_pattern); + } + + bool is_flat_array() const { +#ifdef _LP64 // 64 bit encodings only + return (mask_bits(value(), flat_array_mask_in_place) == null_free_flat_array_pattern) + || (mask_bits(value(), flat_array_mask_in_place) == nullable_flat_array_pattern); +#else + return false; +#endif + } + + bool is_null_free_array() const { +#ifdef _LP64 // 64 bit encodings only + return (mask_bits(value(), null_free_array_mask_in_place) == null_free_array_pattern); +#else + return false; +#endif + } + inline Klass* klass() const; inline Klass* klass_or_null() const; inline Klass* klass_without_asserts() const; @@ -294,6 +440,18 @@ class markWord { return markWord( no_hash_in_place | no_lock_in_place ); } + static markWord inline_type_prototype() { + return markWord(inline_type_pattern); + } + +#ifdef _LP64 // 64 bit encodings only + static markWord flat_array_prototype(LayoutKind lk); + + static markWord null_free_array_prototype() { + return markWord(null_free_array_pattern); + } +#endif + // Debugging void print_on(outputStream* st, bool print_monitor_info = true) const; @@ -301,7 +459,10 @@ class markWord { inline static markWord encode_pointer_as_mark(void* p) { return from_pointer(p).set_marked(); } // Recover address of oop from encoded form used in mark - inline void* decode_pointer() const { return (void*)clear_lock_bits().value(); } + inline void* decode_pointer() const { + return (EnableValhalla && _value < static_prototype_value_max) ? nullptr : + (void*) (clear_lock_bits().value()); + } inline bool is_self_forwarded() const { NOT_LP64(assert(LockingMode != LM_LEGACY, "incorrect with LM_LEGACY on 32 bit");) diff --git a/src/hotspot/share/oops/method.cpp b/src/hotspot/share/oops/method.cpp index 03330aee209..baec9e6cc6c 100644 --- a/src/hotspot/share/oops/method.cpp +++ b/src/hotspot/share/oops/method.cpp @@ -53,6 +53,7 @@ #include "nmt/memTracker.hpp" #include "oops/constantPool.hpp" #include "oops/constMethod.hpp" +#include "oops/inlineKlass.inline.hpp" #include "oops/jmethodIDTable.hpp" #include "oops/klass.inline.hpp" #include "oops/method.inline.hpp" @@ -122,7 +123,6 @@ Method::Method(ConstMethod* xconst, AccessFlags access_flags, Symbol* name) { clear_native_function(); set_signature_handler(nullptr); } - NOT_PRODUCT(set_compiled_invocation_count(0);) // Name is very useful for debugging. NOT_PRODUCT(_name = name;) @@ -167,11 +167,21 @@ address Method::get_c2i_entry() { return adapter()->get_c2i_entry(); } +address Method::get_c2i_inline_entry() { + assert(adapter() != nullptr, "must have"); + return adapter()->get_c2i_inline_entry(); +} + address Method::get_c2i_unverified_entry() { assert(adapter() != nullptr, "must have"); return adapter()->get_c2i_unverified_entry(); } +address Method::get_c2i_unverified_inline_entry() { + assert(adapter() != nullptr, "must have"); + return adapter()->get_c2i_unverified_inline_entry(); +} + address Method::get_c2i_no_clinit_check_entry() { assert(VM_Version::supports_fast_class_init_checks(), ""); assert(adapter() != nullptr, "must have"); @@ -397,7 +407,7 @@ Symbol* Method::klass_name() const { void Method::metaspace_pointers_do(MetaspaceClosure* it) { log_trace(aot)("Iter(Method): %p", this); - if (!method_holder()->is_rewritten()) { + if (!method_holder()->is_rewritten() || CDSConfig::is_valhalla_preview()) { it->push(&_constMethod, MetaspaceClosure::_writable); } else { it->push(&_constMethod); @@ -440,6 +450,8 @@ void Method::restore_unshareable_info(TRAPS) { if (_adapter != nullptr) { assert(_adapter->is_linked(), "must be"); _from_compiled_entry = _adapter->get_c2i_entry(); + _from_compiled_inline_entry = _adapter->get_c2i_inline_entry(); + _from_compiled_inline_ro_entry = _adapter->get_c2i_inline_ro_entry(); } assert(!queued_for_compilation(), "method's queued_for_compilation flag should not be set"); } @@ -729,6 +741,20 @@ int Method::extra_stack_words() { return extra_stack_entries() * Interpreter::stackElementSize; } +// InlineKlass the method is declared to return. This must not +// safepoint as it is called with references live on the stack at +// locations the GC is unaware of. +InlineKlass* Method::returns_inline_type() const { + assert(InlineTypeReturnedAsFields, "Inline types should never be returned as fields"); + if (is_native()) { + return nullptr; + } + NoSafepointVerifier nsv; + SignatureStream ss(signature()); + ss.skip_to_return_type(); + return ss.as_inline_klass(method_holder()); +} + bool Method::compute_has_loops_flag() { BytecodeStream bcs(methodHandle(Thread::current(), this)); Bytecodes::Code bc; @@ -877,6 +903,11 @@ bool Method::is_getter() const { default: return false; } + if (has_scalarized_return()) { + // Don't treat this as (trivial) getter method because the + // inline type should be returned in a scalarized form. + return false; + } return true; } @@ -898,6 +929,11 @@ bool Method::is_setter() const { } if (java_code_at(2) != Bytecodes::_putfield) return false; if (java_code_at(5) != Bytecodes::_return) return false; + if (has_scalarized_args()) { + // Don't treat this as (trivial) setter method because the + // inline type argument should be passed in a scalarized form. + return false; + } return true; } @@ -908,24 +944,22 @@ bool Method::is_constant_getter() const { return (2 <= code_size() && code_size() <= 4 && Bytecodes::is_const(java_code_at(0)) && Bytecodes::length_for(java_code_at(0)) == last_index && - Bytecodes::is_return(java_code_at(last_index))); -} - -bool Method::has_valid_initializer_flags() const { - return (is_static() || - method_holder()->major_version() < 51); + Bytecodes::is_return(java_code_at(last_index)) && + !has_scalarized_args()); } -bool Method::is_static_initializer() const { +bool Method::is_class_initializer() const { // For classfiles version 51 or greater, ensure that the clinit method is // static. Non-static methods with the name "" are not static // initializers. (older classfiles exempted for backward compatibility) - return name() == vmSymbols::class_initializer_name() && - has_valid_initializer_flags(); + return (name() == vmSymbols::class_initializer_name() && + (is_static() || + method_holder()->major_version() < 51)); } -bool Method::is_object_initializer() const { - return name() == vmSymbols::object_initializer_name(); +// A method named , is a classic object constructor. +bool Method::is_object_constructor() const { + return name() == vmSymbols::object_initializer_name(); } bool Method::needs_clinit_barrier() const { @@ -988,7 +1022,7 @@ int Method::line_number_from_bci(int bci) const { bool Method::is_klass_loaded_by_klass_index(int klass_index) const { - if( constants()->tag_at(klass_index).is_unresolved_klass() ) { + if( constants()->tag_at(klass_index).is_unresolved_klass()) { Thread *thread = Thread::current(); Symbol* klass_name = constants()->klass_name_at(klass_index); Handle loader(thread, method_holder()->class_loader()); @@ -1003,7 +1037,9 @@ bool Method::is_klass_loaded(int refinfo_index, Bytecodes::Code bc, bool must_be int klass_index = constants()->klass_ref_index_at(refinfo_index, bc); if (must_be_resolved) { // Make sure klass is resolved in constantpool. - if (constants()->tag_at(klass_index).is_unresolved_klass()) return false; + if (constants()->tag_at(klass_index).is_unresolved_klass()) { + return false; + } } return is_klass_loaded_by_klass_index(klass_index); } @@ -1171,9 +1207,13 @@ void Method::clear_code() { // this may be null if c2i adapters have not been made yet // Only should happen at allocate time. if (adapter() == nullptr) { - _from_compiled_entry = nullptr; + _from_compiled_entry = nullptr; + _from_compiled_inline_entry = nullptr; + _from_compiled_inline_ro_entry = nullptr; } else { - _from_compiled_entry = adapter()->get_c2i_entry(); + _from_compiled_entry = adapter()->get_c2i_entry(); + _from_compiled_inline_entry = adapter()->get_c2i_inline_entry(); + _from_compiled_inline_ro_entry = adapter()->get_c2i_inline_ro_entry(); } OrderAccess::storestore(); _from_interpreted_entry = _i2i_entry; @@ -1207,6 +1247,8 @@ void Method::unlink_method() { } _i2i_entry = nullptr; _from_compiled_entry = nullptr; + _from_compiled_inline_entry = nullptr; + _from_compiled_inline_ro_entry = nullptr; _from_interpreted_entry = nullptr; if (is_native()) { @@ -1238,6 +1280,8 @@ void Method::remove_unshareable_flags() { set_is_not_c1_compilable(false); set_is_not_c2_osr_compilable(false); set_on_stack_flag(false); + set_has_scalarized_args(false); + set_has_scalarized_return(false); } #endif @@ -1274,6 +1318,9 @@ void Method::link_method(const methodHandle& h_method, TRAPS) { SharedRuntime::native_method_throw_unsatisfied_link_error_entry(), !native_bind_event_is_interesting); } + if (InlineTypeReturnedAsFields && returns_inline_type() && !has_scalarized_return()) { + set_has_scalarized_return(); + } // Setup compiler entrypoint. This is made eagerly, so we do not need // special handling of vtables. An alternative is to make adapters more @@ -1284,11 +1331,16 @@ void Method::link_method(const methodHandle& h_method, TRAPS) { // later. Ditto for mega-morphic itable calls. If this proves to be a // problem we'll make these lazily later. if (is_abstract()) { - h_method->_from_compiled_entry = SharedRuntime::get_handle_wrong_method_abstract_stub(); + address wrong_method_abstract = SharedRuntime::get_handle_wrong_method_abstract_stub(); + h_method->_from_compiled_entry = wrong_method_abstract; + h_method->_from_compiled_inline_entry = wrong_method_abstract; + h_method->_from_compiled_inline_ro_entry = wrong_method_abstract; } else if (_adapter == nullptr) { (void) make_adapters(h_method, CHECK); assert(adapter()->is_linked(), "Adapter must have been linked"); h_method->_from_compiled_entry = adapter()->get_c2i_entry(); + h_method->_from_compiled_inline_entry = adapter()->get_c2i_inline_entry(); + h_method->_from_compiled_inline_ro_entry = adapter()->get_c2i_inline_ro_entry(); } // ONLY USE the h_method now as make_adapter may have blocked @@ -1344,6 +1396,18 @@ address Method::verified_code_entry() { return _from_compiled_entry; } +address Method::verified_inline_code_entry() { + DEBUG_ONLY(NoSafepointVerifier nsv;) + assert(_from_compiled_inline_entry != nullptr, "must be set"); + return _from_compiled_inline_entry; +} + +address Method::verified_inline_ro_code_entry() { + DEBUG_ONLY(NoSafepointVerifier nsv;) + assert(_from_compiled_inline_ro_entry != nullptr, "must be set"); + return _from_compiled_inline_ro_entry; +} + // Check that if an nmethod ref exists, it has a backlink to this or no backlink at all // (could be racing a deopt). // Not inline to avoid circular ref. @@ -1375,6 +1439,8 @@ void Method::set_code(const methodHandle& mh, nmethod *code) { OrderAccess::storestore(); mh->_from_compiled_entry = code->verified_entry_point(); + mh->_from_compiled_inline_entry = code->verified_inline_entry_point(); + mh->_from_compiled_inline_ro_entry = code->verified_inline_ro_entry_point(); OrderAccess::storestore(); if (mh->is_continuation_native_intrinsic()) { @@ -1567,6 +1633,8 @@ methodHandle Method::make_method_handle_intrinsic(vmIntrinsics::ID iid, void Method::restore_archived_method_handle_intrinsic(methodHandle m, TRAPS) { if (m->adapter() != nullptr) { m->set_from_compiled_entry(m->adapter()->get_c2i_entry()); + m->set_from_compiled_inline_entry(m->adapter()->get_c2i_inline_entry()); + m->set_from_compiled_inline_ro_entry(m->adapter()->get_c2i_inline_ro_entry()); } m->link_method(m, CHECK); @@ -2184,6 +2252,31 @@ bool Method::is_valid_method(const Method* m) { } } +bool Method::is_scalarized_arg(int idx) const { + if (!has_scalarized_args()) { + return false; + } + // Search through signature and check if argument is wrapped in T_METADATA/T_VOID + int depth = 0; + const GrowableArray* sig = adapter()->get_sig_cc(); + for (int i = 0; i < sig->length(); i++) { + BasicType bt = sig->at(i)._bt; + if (bt == T_METADATA) { + depth++; + } + if (idx == 0) { + break; // Argument found + } + if (bt == T_VOID && (sig->at(i-1)._bt != T_LONG && sig->at(i-1)._bt != T_DOUBLE)) { + depth--; + } + if (depth == 0 && bt != T_LONG && bt != T_DOUBLE) { + idx--; // Advance to next argument + } + } + return depth != 0; +} + // Printing #ifndef PRODUCT @@ -2209,6 +2302,10 @@ void Method::print_on(outputStream* st) const { if (highest_comp_level() != CompLevel_none) st->print_cr(" - highest level: %d", highest_comp_level()); st->print_cr(" - vtable index: %d", _vtable_index); +#ifdef ASSERT + if (valid_itable_index()) + st->print_cr(" - itable index: %d", itable_index()); +#endif st->print_cr(" - i2i entry: " PTR_FORMAT, p2i(interpreter_entry())); st->print( " - adapters: "); AdapterHandlerEntry* a = ((Method*)this)->adapter(); @@ -2216,7 +2313,9 @@ void Method::print_on(outputStream* st) const { st->print_cr(PTR_FORMAT, p2i(a)); else a->print_adapter_on(st); - st->print_cr(" - compiled entry " PTR_FORMAT, p2i(from_compiled_entry())); + st->print_cr(" - compiled entry " PTR_FORMAT, p2i(from_compiled_entry())); + st->print_cr(" - compiled inline entry " PTR_FORMAT, p2i(from_compiled_inline_entry())); + st->print_cr(" - compiled inline ro entry " PTR_FORMAT, p2i(from_compiled_inline_ro_entry())); st->print_cr(" - code size: %d", code_size()); if (code_size() != 0) { st->print_cr(" - code start: " PTR_FORMAT, p2i(code_base())); @@ -2286,6 +2385,7 @@ void Method::print_value_on(outputStream* st) const { st->print("%s", internal_name()); print_address_on(st); st->print(" "); + if (WizardMode) access_flags().print_on(st); name()->print_value_on(st); st->print(" "); signature()->print_value_on(st); diff --git a/src/hotspot/share/oops/method.hpp b/src/hotspot/share/oops/method.hpp index 4592cb8a8c0..842ee11cebf 100644 --- a/src/hotspot/share/oops/method.hpp +++ b/src/hotspot/share/oops/method.hpp @@ -93,7 +93,9 @@ class Method : public Metadata { address _i2i_entry; // All-args-on-stack calling convention // Entry point for calling from compiled code, to compiled code if it exists // or else the interpreter. - volatile address _from_compiled_entry; // Cache of: _code ? _code->entry_point() : _adapter->c2i_entry() + volatile address _from_compiled_entry; // Cache of: _code ? _code->entry_point() : _adapter->c2i_entry() + volatile address _from_compiled_inline_ro_entry; // Cache of: _code ? _code->verified_inline_ro_entry_point() : _adapter->c2i_inline_ro_entry() + volatile address _from_compiled_inline_entry; // Cache of: _code ? _code->verified_inline_entry_point() : _adapter->c2i_inline_entry() // The entry point for calling both from and to compiled code is // "_code->entry_point()". Because of tiered compilation and de-opt, this // field can come and go. It can transition from null to not-null at any @@ -133,6 +135,8 @@ class Method : public Metadata { static address make_adapters(const methodHandle& mh, TRAPS); address from_compiled_entry() const; + address from_compiled_inline_ro_entry() const; + address from_compiled_inline_entry() const; address from_interpreted_entry() const; // access flag @@ -361,6 +365,8 @@ class Method : public Metadata { // nmethod/verified compiler entry address verified_code_entry(); + address verified_inline_code_entry(); + address verified_inline_ro_code_entry(); bool check_code() const; // Not inline to avoid circular ref nmethod* code() const; @@ -383,12 +389,20 @@ class Method : public Metadata { _adapter = adapter; } void set_from_compiled_entry(address entry) { - _from_compiled_entry = entry; + _from_compiled_entry = entry; + } + void set_from_compiled_inline_ro_entry(address entry) { + _from_compiled_inline_ro_entry = entry; + } + void set_from_compiled_inline_entry(address entry) { + _from_compiled_inline_entry = entry; } address get_i2c_entry(); address get_c2i_entry(); + address get_c2i_inline_entry(); address get_c2i_unverified_entry(); + address get_c2i_unverified_inline_entry(); address get_c2i_no_clinit_check_entry(); AdapterHandlerEntry* adapter() const { return _adapter; @@ -502,7 +516,7 @@ class Method : public Metadata { Symbol* klass_name() const; // returns the name of the method holder BasicType result_type() const { return constMethod()->result_type(); } bool is_returning_oop() const { BasicType r = result_type(); return is_reference_type(r); } - bool is_returning_fp() const { BasicType r = result_type(); return (r == T_FLOAT || r == T_DOUBLE); } + InlineKlass* returns_inline_type() const; // Checked exceptions thrown by this method (resolved to mirrors) objArrayHandle resolved_checked_exceptions(TRAPS) { return resolved_checked_exceptions_impl(this, THREAD); } @@ -581,15 +595,12 @@ class Method : public Metadata { // returns true if the method does nothing but return a constant of primitive type bool is_constant_getter() const; - // returns true if the method is static OR if the classfile version < 51 - bool has_valid_initializer_flags() const; - // returns true if the method name is and the method has // valid static initializer flags. - bool is_static_initializer() const; + bool is_class_initializer() const; // returns true if the method name is - bool is_object_initializer() const; + bool is_object_constructor() const; // returns true if the method name is wait0 bool is_object_wait0() const; @@ -614,7 +625,10 @@ class Method : public Metadata { static ByteSize const_offset() { return byte_offset_of(Method, _constMethod ); } static ByteSize access_flags_offset() { return byte_offset_of(Method, _access_flags ); } static ByteSize from_compiled_offset() { return byte_offset_of(Method, _from_compiled_entry); } + static ByteSize from_compiled_inline_offset() { return byte_offset_of(Method, _from_compiled_inline_entry); } + static ByteSize from_compiled_inline_ro_offset(){ return byte_offset_of(Method, _from_compiled_inline_ro_entry); } static ByteSize code_offset() { return byte_offset_of(Method, _code); } + static ByteSize flags_offset() { return byte_offset_of(Method, _flags); } static ByteSize method_counters_offset() { return byte_offset_of(Method, _method_counters); @@ -766,6 +780,17 @@ class Method : public Metadata { bool has_reserved_stack_access() const { return constMethod()->reserved_stack_access(); } void set_has_reserved_stack_access() { constMethod()->set_reserved_stack_access(); } + bool is_scalarized_arg(int idx) const; + + bool c1_needs_stack_repair() const { return constMethod()->c1_needs_stack_repair(); } + void set_c1_needs_stack_repair() { constMethod()->set_c1_needs_stack_repair(); } + + bool c2_needs_stack_repair() const { return constMethod()->c2_needs_stack_repair(); } + void set_c2_needs_stack_repair() { constMethod()->set_c2_needs_stack_repair(); } + + bool mismatch() const { return constMethod()->mismatch(); } + void set_mismatch() { constMethod()->set_mismatch(); } + JFR_ONLY(DEFINE_TRACE_FLAG_ACCESSOR;) ConstMethod::MethodType method_type() const { diff --git a/src/hotspot/share/oops/method.inline.hpp b/src/hotspot/share/oops/method.inline.hpp index 18fca354b6b..592c12d873c 100644 --- a/src/hotspot/share/oops/method.inline.hpp +++ b/src/hotspot/share/oops/method.inline.hpp @@ -37,6 +37,14 @@ inline address Method::from_compiled_entry() const { return Atomic::load_acquire(&_from_compiled_entry); } +inline address Method::from_compiled_inline_ro_entry() const { + return Atomic::load_acquire(&_from_compiled_inline_ro_entry); +} + +inline address Method::from_compiled_inline_entry() const { + return Atomic::load_acquire(&_from_compiled_inline_entry); +} + inline address Method::from_interpreted_entry() const { return Atomic::load_acquire(&_from_interpreted_entry); } diff --git a/src/hotspot/share/oops/methodData.cpp b/src/hotspot/share/oops/methodData.cpp index 4c027e0839a..736e3fa528b 100644 --- a/src/hotspot/share/oops/methodData.cpp +++ b/src/hotspot/share/oops/methodData.cpp @@ -148,7 +148,7 @@ void ProfileData::print_shared(outputStream* st, const char* name, const char* e } int flags = data()->flags(); if (flags != 0) { - st->print("flags(%d) ", flags); + st->print("flags(%d) %p/%d", flags, data(), in_bytes(DataLayout::flags_offset())); } } @@ -218,7 +218,7 @@ int TypeStackSlotEntries::compute_cell_count(Symbol* signature, bool include_rec int TypeEntriesAtCall::compute_cell_count(BytecodeStream* stream) { assert(Bytecodes::is_invoke(stream->code()), "should be invoke"); - assert(TypeStackSlotEntries::per_arg_count() > ReturnTypeEntry::static_cell_count(), "code to test for arguments/results broken"); + assert(TypeStackSlotEntries::per_arg_count() > SingleTypeEntry::static_cell_count(), "code to test for arguments/results broken"); const methodHandle m = stream->method(); int bci = stream->bci(); Bytecode_invoke inv(m, bci); @@ -228,7 +228,7 @@ int TypeEntriesAtCall::compute_cell_count(BytecodeStream* stream) { } int ret_cell = 0; if (MethodData::profile_return_for_invoke(m, bci) && is_reference_type(inv.result_type())) { - ret_cell = ReturnTypeEntry::static_cell_count(); + ret_cell = SingleTypeEntry::static_cell_count(); } int header_cell = 0; if (args_cell + ret_cell > 0) { @@ -363,7 +363,7 @@ void TypeStackSlotEntries::metaspace_pointers_do(MetaspaceClosure* it) { } } -void ReturnTypeEntry::clean_weak_klass_links(bool always_clean) { +void SingleTypeEntry::clean_weak_klass_links(bool always_clean) { intptr_t p = type(); Klass* k = (Klass*)klass_part(p); if (k != nullptr) { @@ -376,7 +376,7 @@ void ReturnTypeEntry::clean_weak_klass_links(bool always_clean) { } } -void ReturnTypeEntry::metaspace_pointers_do(MetaspaceClosure* it) { +void SingleTypeEntry::metaspace_pointers_do(MetaspaceClosure* it) { Klass** k = (Klass**)type_adr(); // tagged it->push(k); } @@ -411,7 +411,7 @@ void TypeStackSlotEntries::print_data_on(outputStream* st) const { } } -void ReturnTypeEntry::print_data_on(outputStream* st) const { +void SingleTypeEntry::print_data_on(outputStream* st) const { _pd->tab(st); print_klass(st, type()); st->cr(); @@ -584,6 +584,10 @@ void BranchData::post_initialize(BytecodeStream* stream, MethodData* mdo) { void BranchData::print_data_on(outputStream* st, const char* extra) const { print_shared(st, "BranchData", extra); + if (data()->flags()) { + st->cr(); + tab(st); + } st->print_cr("taken(%u) displacement(%d)", taken(), displacement()); tab(st); @@ -714,6 +718,42 @@ void SpeculativeTrapData::print_data_on(outputStream* st, const char* extra) con st->cr(); } +void ArrayStoreData::print_data_on(outputStream* st, const char* extra) const { + print_shared(st, "ArrayStore", extra); + st->cr(); + tab(st, true); + st->print("array"); + _array.print_data_on(st); + tab(st, true); + st->print("element"); + if (null_seen()) { + st->print(" (null seen)"); + } + tab(st); + print_receiver_data_on(st); +} + +void ArrayLoadData::print_data_on(outputStream* st, const char* extra) const { + print_shared(st, "ArrayLoad", extra); + st->cr(); + tab(st, true); + st->print("array"); + _array.print_data_on(st); + tab(st, true); + st->print("element"); + _element.print_data_on(st); +} + +void ACmpData::print_data_on(outputStream* st, const char* extra) const { + BranchData::print_data_on(st, extra); + tab(st, true); + st->print("left"); + _left.print_data_on(st); + tab(st, true); + st->print("right"); + _right.print_data_on(st); +} + // ================================================================== // MethodData* // @@ -732,12 +772,15 @@ int MethodData::bytecode_cell_count(Bytecodes::Code code) { switch (code) { case Bytecodes::_checkcast: case Bytecodes::_instanceof: - case Bytecodes::_aastore: if (TypeProfileCasts) { return ReceiverTypeData::static_cell_count(); } else { return BitData::static_cell_count(); } + case Bytecodes::_aaload: + return ArrayLoadData::static_cell_count(); + case Bytecodes::_aastore: + return ArrayStoreData::static_cell_count(); case Bytecodes::_invokespecial: case Bytecodes::_invokestatic: if (MethodData::profile_arguments() || MethodData::profile_return()) { @@ -777,11 +820,12 @@ int MethodData::bytecode_cell_count(Bytecodes::Code code) { case Bytecodes::_if_icmpge: case Bytecodes::_if_icmpgt: case Bytecodes::_if_icmple: - case Bytecodes::_if_acmpeq: - case Bytecodes::_if_acmpne: case Bytecodes::_ifnull: case Bytecodes::_ifnonnull: return BranchData::static_cell_count(); + case Bytecodes::_if_acmpne: + case Bytecodes::_if_acmpeq: + return ACmpData::static_cell_count(); case Bytecodes::_lookupswitch: case Bytecodes::_tableswitch: return variable_cell_count; @@ -840,6 +884,7 @@ bool MethodData::is_speculative_trap_bytecode(Bytecodes::Code code) { switch (code) { case Bytecodes::_checkcast: case Bytecodes::_instanceof: + case Bytecodes::_aaload: case Bytecodes::_aastore: case Bytecodes::_invokevirtual: case Bytecodes::_invokeinterface: @@ -1056,7 +1101,6 @@ int MethodData::initialize_data(BytecodeStream* stream, switch (c) { case Bytecodes::_checkcast: case Bytecodes::_instanceof: - case Bytecodes::_aastore: if (TypeProfileCasts) { cell_count = ReceiverTypeData::static_cell_count(); tag = DataLayout::receiver_type_data_tag; @@ -1065,6 +1109,14 @@ int MethodData::initialize_data(BytecodeStream* stream, tag = DataLayout::bit_data_tag; } break; + case Bytecodes::_aaload: + cell_count = ArrayLoadData::static_cell_count(); + tag = DataLayout::array_load_data_tag; + break; + case Bytecodes::_aastore: + cell_count = ArrayStoreData::static_cell_count(); + tag = DataLayout::array_store_data_tag; + break; case Bytecodes::_invokespecial: case Bytecodes::_invokestatic: { int counter_data_cell_count = CounterData::static_cell_count(); @@ -1136,13 +1188,16 @@ int MethodData::initialize_data(BytecodeStream* stream, case Bytecodes::_if_icmpge: case Bytecodes::_if_icmpgt: case Bytecodes::_if_icmple: - case Bytecodes::_if_acmpeq: - case Bytecodes::_if_acmpne: case Bytecodes::_ifnull: case Bytecodes::_ifnonnull: cell_count = BranchData::static_cell_count(); tag = DataLayout::branch_data_tag; break; + case Bytecodes::_if_acmpeq: + case Bytecodes::_if_acmpne: + cell_count = ACmpData::static_cell_count(); + tag = DataLayout::acmp_data_tag; + break; case Bytecodes::_lookupswitch: case Bytecodes::_tableswitch: cell_count = MultiBranchData::compute_cell_count(stream); @@ -1210,6 +1265,12 @@ int DataLayout::cell_count() { return ((new ParametersTypeData(this))->cell_count()); case DataLayout::speculative_trap_data_tag: return SpeculativeTrapData::static_cell_count(); + case DataLayout::array_store_data_tag: + return ((new ArrayStoreData(this))->cell_count()); + case DataLayout::array_load_data_tag: + return ((new ArrayLoadData(this))->cell_count()); + case DataLayout::acmp_data_tag: + return ((new ACmpData(this))->cell_count()); } } ProfileData* DataLayout::data_in() { @@ -1244,6 +1305,12 @@ ProfileData* DataLayout::data_in() { return new ParametersTypeData(this); case DataLayout::speculative_trap_data_tag: return new SpeculativeTrapData(this); + case DataLayout::array_store_data_tag: + return new ArrayStoreData(this); + case DataLayout::array_load_data_tag: + return new ArrayLoadData(this); + case DataLayout::acmp_data_tag: + return new ACmpData(this); } } diff --git a/src/hotspot/share/oops/methodData.hpp b/src/hotspot/share/oops/methodData.hpp index 61137d9fb7a..e387d5ed074 100644 --- a/src/hotspot/share/oops/methodData.hpp +++ b/src/hotspot/share/oops/methodData.hpp @@ -127,7 +127,10 @@ class DataLayout { call_type_data_tag, virtual_call_type_data_tag, parameters_type_data_tag, - speculative_trap_data_tag + speculative_trap_data_tag, + array_store_data_tag, + array_load_data_tag, + acmp_data_tag }; enum { @@ -287,15 +290,18 @@ class CounterData; class ReceiverTypeData; class VirtualCallData; class VirtualCallTypeData; +class ArrayStoreData; class RetData; class CallTypeData; class JumpData; class BranchData; +class ACmpData; class ArrayData; class MultiBranchData; class ArgInfoData; class ParametersTypeData; class SpeculativeTrapData; +class ArrayLoadData; // ProfileData // @@ -303,7 +309,7 @@ class SpeculativeTrapData; // data in a structured way. class ProfileData : public ResourceObj { friend class TypeEntries; - friend class ReturnTypeEntry; + friend class SingleTypeEntry; friend class TypeStackSlotEntries; private: enum { @@ -422,6 +428,9 @@ class ProfileData : public ResourceObj { virtual bool is_VirtualCallTypeData()const { return false; } virtual bool is_ParametersTypeData() const { return false; } virtual bool is_SpeculativeTrapData()const { return false; } + virtual bool is_ArrayStoreData() const { return false; } + virtual bool is_ArrayLoadData() const { return false; } + virtual bool is_ACmpData() const { return false; } BitData* as_BitData() const { @@ -480,6 +489,18 @@ class ProfileData : public ResourceObj { assert(is_SpeculativeTrapData(), "wrong type"); return is_SpeculativeTrapData() ? (SpeculativeTrapData*)this : nullptr; } + ArrayStoreData* as_ArrayStoreData() const { + assert(is_ArrayStoreData(), "wrong type"); + return is_ArrayStoreData() ? (ArrayStoreData*)this : nullptr; + } + ArrayLoadData* as_ArrayLoadData() const { + assert(is_ArrayLoadData(), "wrong type"); + return is_ArrayLoadData() ? (ArrayLoadData*)this : nullptr; + } + ACmpData* as_ACmpData() const { + assert(is_ACmpData(), "wrong type"); + return is_ACmpData() ? (ACmpData*)this : nullptr; + } // Subclass specific initialization @@ -525,6 +546,7 @@ class BitData : public ProfileData { // bytecode threw any exception , exception_seen_flag = deprecated_method_callsite_flag + 1 #endif + , last_bit_data_flag }; enum { bit_cell_count = 0 }; // no additional data fields needed. public: @@ -545,7 +567,7 @@ class BitData : public ProfileData { // The null_seen flag bit is specially known to the interpreter. // Consulting it allows the compiler to avoid setting up null_check traps. - bool null_seen() { return flag_at(null_seen_flag); } + bool null_seen() const { return flag_at(null_seen_flag); } void set_null_seen() { set_flag_at(null_seen_flag); } bool deprecated_method_call_site() const { return flag_at(deprecated_method_callsite_flag); } bool set_deprecated_method_call_site() { return data()->set_flag_at(deprecated_method_callsite_flag); } @@ -646,7 +668,8 @@ class JumpData : public ProfileData { public: JumpData(DataLayout* layout) : ProfileData(layout) { assert(layout->tag() == DataLayout::jump_data_tag || - layout->tag() == DataLayout::branch_data_tag, "wrong type"); + layout->tag() == DataLayout::branch_data_tag || + layout->tag() == DataLayout::acmp_data_tag, "wrong type"); } virtual bool is_JumpData() const { return true; } @@ -890,7 +913,7 @@ class TypeStackSlotEntries : public TypeEntries { // Type entry used for return from a call. A single cell to record the // type. -class ReturnTypeEntry : public TypeEntries { +class SingleTypeEntry : public TypeEntries { private: enum { @@ -898,7 +921,7 @@ class ReturnTypeEntry : public TypeEntries { }; public: - ReturnTypeEntry(int base_off) + SingleTypeEntry(int base_off) : TypeEntries(base_off) {} void post_initialize() { @@ -939,7 +962,7 @@ class ReturnTypeEntry : public TypeEntries { }; // Entries to collect type information at a call: contains arguments -// (TypeStackSlotEntries), a return type (ReturnTypeEntry) and a +// (TypeStackSlotEntries), a return type (SingleTypeEntry) and a // number of cells. Because the number of cells for the return type is // smaller than the number of cells for the type of an arguments, the // number of cells is used to tell how many arguments are profiled and @@ -993,7 +1016,7 @@ class TypeEntriesAtCall { } static ByteSize return_only_size() { - return ReturnTypeEntry::size() + in_ByteSize(header_cell_count() * DataLayout::cell_size); + return SingleTypeEntry::size() + in_ByteSize(header_cell_count() * DataLayout::cell_size); } }; @@ -1008,7 +1031,7 @@ class CallTypeData : public CounterData { // entries for arguments if any TypeStackSlotEntries _args; // entry for return type if any - ReturnTypeEntry _ret; + SingleTypeEntry _ret; int cell_count_global_offset() const { return CounterData::static_cell_count() + TypeEntriesAtCall::cell_count_local_offset(); @@ -1027,7 +1050,7 @@ class CallTypeData : public CounterData { CallTypeData(DataLayout* layout) : CounterData(layout), _args(CounterData::static_cell_count()+TypeEntriesAtCall::header_cell_count(), number_of_arguments()), - _ret(cell_count() - ReturnTypeEntry::static_cell_count()) + _ret(cell_count() - SingleTypeEntry::static_cell_count()) { assert(layout->tag() == DataLayout::call_type_data_tag, "wrong type"); // Some compilers (VC++) don't want this passed in member initialization list @@ -1040,7 +1063,7 @@ class CallTypeData : public CounterData { return &_args; } - const ReturnTypeEntry* ret() const { + const SingleTypeEntry* ret() const { assert(has_return(), "no profiling of return value"); return &_ret; } @@ -1162,7 +1185,8 @@ class ReceiverTypeData : public CounterData { ReceiverTypeData(DataLayout* layout) : CounterData(layout) { assert(layout->tag() == DataLayout::receiver_type_data_tag || layout->tag() == DataLayout::virtual_call_data_tag || - layout->tag() == DataLayout::virtual_call_type_data_tag, "wrong type"); + layout->tag() == DataLayout::virtual_call_type_data_tag || + layout->tag() == DataLayout::array_store_data_tag, "wrong type"); } virtual bool is_ReceiverTypeData() const { return true; } @@ -1295,7 +1319,7 @@ class VirtualCallTypeData : public VirtualCallData { // entries for arguments if any TypeStackSlotEntries _args; // entry for return type if any - ReturnTypeEntry _ret; + SingleTypeEntry _ret; int cell_count_global_offset() const { return VirtualCallData::static_cell_count() + TypeEntriesAtCall::cell_count_local_offset(); @@ -1314,7 +1338,7 @@ class VirtualCallTypeData : public VirtualCallData { VirtualCallTypeData(DataLayout* layout) : VirtualCallData(layout), _args(VirtualCallData::static_cell_count()+TypeEntriesAtCall::header_cell_count(), number_of_arguments()), - _ret(cell_count() - ReturnTypeEntry::static_cell_count()) + _ret(cell_count() - SingleTypeEntry::static_cell_count()) { assert(layout->tag() == DataLayout::virtual_call_type_data_tag, "wrong type"); // Some compilers (VC++) don't want this passed in member initialization list @@ -1327,7 +1351,7 @@ class VirtualCallTypeData : public VirtualCallData { return &_args; } - const ReturnTypeEntry* ret() const { + const SingleTypeEntry* ret() const { assert(has_return(), "no profiling of return value"); return &_ret; } @@ -1540,7 +1564,7 @@ class BranchData : public JumpData { public: BranchData(DataLayout* layout) : JumpData(layout) { - assert(layout->tag() == DataLayout::branch_data_tag, "wrong type"); + assert(layout->tag() == DataLayout::branch_data_tag || layout->tag() == DataLayout::acmp_data_tag, "wrong type"); } virtual bool is_BranchData() const { return true; } @@ -1901,6 +1925,229 @@ class SpeculativeTrapData : public ProfileData { virtual void print_data_on(outputStream* st, const char* extra = nullptr) const; }; +class ArrayStoreData : public ReceiverTypeData { +private: + enum { + flat_array_flag = BitData::last_bit_data_flag, + null_free_array_flag = flat_array_flag + 1, + }; + + SingleTypeEntry _array; + +public: + ArrayStoreData(DataLayout* layout) : + ReceiverTypeData(layout), + _array(ReceiverTypeData::static_cell_count()) { + assert(layout->tag() == DataLayout::array_store_data_tag, "wrong type"); + _array.set_profile_data(this); + } + + const SingleTypeEntry* array() const { + return &_array; + } + + virtual bool is_ArrayStoreData() const { return true; } + + static int static_cell_count() { + return ReceiverTypeData::static_cell_count() + SingleTypeEntry::static_cell_count(); + } + + virtual int cell_count() const { + return static_cell_count(); + } + + void set_flat_array() { set_flag_at(flat_array_flag); } + bool flat_array() const { return flag_at(flat_array_flag); } + + void set_null_free_array() { set_flag_at(null_free_array_flag); } + bool null_free_array() const { return flag_at(null_free_array_flag); } + + // Code generation support + static int flat_array_byte_constant() { + return flag_number_to_constant(flat_array_flag); + } + + static int null_free_array_byte_constant() { + return flag_number_to_constant(null_free_array_flag); + } + + static ByteSize array_offset() { + return cell_offset(ReceiverTypeData::static_cell_count()); + } + + virtual void clean_weak_klass_links(bool always_clean) { + ReceiverTypeData::clean_weak_klass_links(always_clean); + _array.clean_weak_klass_links(always_clean); + } + + virtual void metaspace_pointers_do(MetaspaceClosure* it) { + ReceiverTypeData::metaspace_pointers_do(it); + _array.metaspace_pointers_do(it); + } + + static ByteSize array_store_data_size() { + return cell_offset(static_cell_count()); + } + + virtual void print_data_on(outputStream* st, const char* extra = nullptr) const; +}; + +class ArrayLoadData : public ProfileData { +private: + enum { + flat_array_flag = DataLayout::first_flag, + null_free_array_flag = flat_array_flag + 1, + }; + + SingleTypeEntry _array; + SingleTypeEntry _element; + +public: + ArrayLoadData(DataLayout* layout) : + ProfileData(layout), + _array(0), + _element(SingleTypeEntry::static_cell_count()) { + assert(layout->tag() == DataLayout::array_load_data_tag, "wrong type"); + _array.set_profile_data(this); + _element.set_profile_data(this); + } + + const SingleTypeEntry* array() const { + return &_array; + } + + const SingleTypeEntry* element() const { + return &_element; + } + + virtual bool is_ArrayLoadData() const { return true; } + + static int static_cell_count() { + return SingleTypeEntry::static_cell_count() * 2; + } + + virtual int cell_count() const { + return static_cell_count(); + } + + void set_flat_array() { set_flag_at(flat_array_flag); } + bool flat_array() const { return flag_at(flat_array_flag); } + + void set_null_free_array() { set_flag_at(null_free_array_flag); } + bool null_free_array() const { return flag_at(null_free_array_flag); } + + // Code generation support + static int flat_array_byte_constant() { + return flag_number_to_constant(flat_array_flag); + } + + static int null_free_array_byte_constant() { + return flag_number_to_constant(null_free_array_flag); + } + + static ByteSize array_offset() { + return cell_offset(0); + } + + static ByteSize element_offset() { + return cell_offset(SingleTypeEntry::static_cell_count()); + } + + virtual void clean_weak_klass_links(bool always_clean) { + _array.clean_weak_klass_links(always_clean); + _element.clean_weak_klass_links(always_clean); + } + + virtual void metaspace_pointers_do(MetaspaceClosure* it) { + _array.metaspace_pointers_do(it); + _element.metaspace_pointers_do(it); + } + + static ByteSize array_load_data_size() { + return cell_offset(static_cell_count()); + } + + virtual void print_data_on(outputStream* st, const char* extra = nullptr) const; +}; + +class ACmpData : public BranchData { +private: + enum { + left_inline_type_flag = DataLayout::first_flag, + right_inline_type_flag + }; + + SingleTypeEntry _left; + SingleTypeEntry _right; + +public: + ACmpData(DataLayout* layout) : + BranchData(layout), + _left(BranchData::static_cell_count()), + _right(BranchData::static_cell_count() + SingleTypeEntry::static_cell_count()) { + assert(layout->tag() == DataLayout::acmp_data_tag, "wrong type"); + _left.set_profile_data(this); + _right.set_profile_data(this); + } + + const SingleTypeEntry* left() const { + return &_left; + } + + const SingleTypeEntry* right() const { + return &_right; + } + + virtual bool is_ACmpData() const { return true; } + + static int static_cell_count() { + return BranchData::static_cell_count() + SingleTypeEntry::static_cell_count() * 2; + } + + virtual int cell_count() const { + return static_cell_count(); + } + + void set_left_inline_type() { set_flag_at(left_inline_type_flag); } + bool left_inline_type() const { return flag_at(left_inline_type_flag); } + + void set_right_inline_type() { set_flag_at(right_inline_type_flag); } + bool right_inline_type() const { return flag_at(right_inline_type_flag); } + + // Code generation support + static int left_inline_type_byte_constant() { + return flag_number_to_constant(left_inline_type_flag); + } + + static int right_inline_type_byte_constant() { + return flag_number_to_constant(right_inline_type_flag); + } + + static ByteSize left_offset() { + return cell_offset(BranchData::static_cell_count()); + } + + static ByteSize right_offset() { + return cell_offset(BranchData::static_cell_count() + SingleTypeEntry::static_cell_count()); + } + + virtual void clean_weak_klass_links(bool always_clean) { + _left.clean_weak_klass_links(always_clean); + _right.clean_weak_klass_links(always_clean); + } + + virtual void metaspace_pointers_do(MetaspaceClosure* it) { + _left.metaspace_pointers_do(it); + _right.metaspace_pointers_do(it); + } + + static ByteSize acmp_data_size() { + return cell_offset(static_cell_count()); + } + + virtual void print_data_on(outputStream* st, const char* extra = nullptr) const; +}; + // MethodData* // // A MethodData* holds information which has been collected about diff --git a/src/hotspot/share/oops/methodFlags.hpp b/src/hotspot/share/oops/methodFlags.hpp index 2ae1e002b65..994a2d43024 100644 --- a/src/hotspot/share/oops/methodFlags.hpp +++ b/src/hotspot/share/oops/methodFlags.hpp @@ -58,6 +58,8 @@ class MethodFlags { status(has_loops_flag , 1 << 13) /* Method has loops */ \ status(has_loops_flag_init , 1 << 14) /* The loop flag has been initialized */ \ status(on_stack_flag , 1 << 15) /* RedefineClasses support to keep Metadata from being cleaned */ \ + status(has_scalarized_args , 1 << 16) \ + status(has_scalarized_return , 1 << 17) \ /* end of list */ #define M_STATUS_ENUM_NAME(name, value) _misc_##name = value, @@ -86,6 +88,8 @@ class MethodFlags { M_STATUS_DO(M_STATUS_GET_SET) #undef M_STATUS_GET_SET + static u4 has_scalarized_return_flag() { return _misc_has_scalarized_return; } + int as_int() const { return _status; } void atomic_set_bits(u4 bits) { Atomic::fetch_then_or(&_status, bits); } void atomic_clear_bits(u4 bits) { Atomic::fetch_then_and(&_status, ~bits); } diff --git a/src/hotspot/share/oops/objArrayKlass.cpp b/src/hotspot/share/oops/objArrayKlass.cpp index 48d56ddc540..91990e3edac 100644 --- a/src/hotspot/share/oops/objArrayKlass.cpp +++ b/src/hotspot/share/oops/objArrayKlass.cpp @@ -22,6 +22,7 @@ * */ +#include "cds/cdsConfig.hpp" #include "classfile/moduleEntry.hpp" #include "classfile/packageEntry.hpp" #include "classfile/symbolTable.hpp" @@ -31,26 +32,33 @@ #include "memory/iterator.inline.hpp" #include "memory/metadataFactory.hpp" #include "memory/metaspaceClosure.hpp" +#include "memory/oopFactory.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "oops/arrayKlass.hpp" +#include "oops/flatArrayKlass.hpp" #include "oops/instanceKlass.hpp" #include "oops/klass.inline.hpp" +#include "oops/markWord.hpp" #include "oops/objArrayKlass.inline.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" +#include "oops/refArrayKlass.hpp" #include "oops/symbol.hpp" +#include "runtime/arguments.hpp" #include "runtime/handles.inline.hpp" #include "runtime/mutexLocker.hpp" #include "utilities/macros.hpp" -ObjArrayKlass* ObjArrayKlass::allocate_klass(ClassLoaderData* loader_data, int n, Klass* k, Symbol* name, TRAPS) { +ObjArrayKlass* ObjArrayKlass::allocate_klass(ClassLoaderData* loader_data, int n, + Klass* k, Symbol* name, ArrayKlass::ArrayProperties props, + TRAPS) { assert(ObjArrayKlass::header_size() <= InstanceKlass::header_size(), "array klasses must be same size as InstanceKlass"); int size = ArrayKlass::static_size(ObjArrayKlass::header_size()); - return new (loader_data, size, THREAD) ObjArrayKlass(n, k, name); + return new (loader_data, size, THREAD) ObjArrayKlass(n, k, name, Kind, props, ArrayKlass::is_null_restricted(props) ? markWord::null_free_array_prototype() : markWord::prototype()); } Symbol* ObjArrayKlass::create_element_klass_array_name(JavaThread* current, Klass* element_klass) { @@ -74,7 +82,7 @@ Symbol* ObjArrayKlass::create_element_klass_array_name(JavaThread* current, Klas ObjArrayKlass* ObjArrayKlass::allocate_objArray_klass(ClassLoaderData* loader_data, - int n, Klass* element_klass, TRAPS) { + int n, Klass* element_klass, TRAPS) { // Eagerly allocate the direct array supertype. Klass* super_klass = nullptr; @@ -102,7 +110,7 @@ ObjArrayKlass* ObjArrayKlass::allocate_objArray_klass(ClassLoaderData* loader_da Symbol* name = create_element_klass_array_name(THREAD, element_klass); // Initialize instance variables - ObjArrayKlass* oak = ObjArrayKlass::allocate_klass(loader_data, n, element_klass, name, CHECK_NULL); + ObjArrayKlass* oak = ObjArrayKlass::allocate_klass(loader_data, n, element_klass, name, ArrayProperties::INVALID, CHECK_NULL); ModuleEntry* module = oak->module(); assert(module != nullptr, "No module entry for array"); @@ -120,14 +128,18 @@ ObjArrayKlass* ObjArrayKlass::allocate_objArray_klass(ClassLoaderData* loader_da return oak; } -ObjArrayKlass::ObjArrayKlass(int n, Klass* element_klass, Symbol* name) : ArrayKlass(name, Kind) { +ObjArrayKlass::ObjArrayKlass(int n, Klass* element_klass, Symbol* name, KlassKind kind, ArrayKlass::ArrayProperties props, markWord mk) : +ArrayKlass(name, kind, props, mk) { set_dimension(n); set_element_klass(element_klass); + set_next_refined_klass_klass(nullptr); + set_properties(props); Klass* bk; if (element_klass->is_objArray_klass()) { bk = ObjArrayKlass::cast(element_klass)->bottom_klass(); } else { + assert(!element_klass->is_refArray_klass(), "Sanity"); bk = element_klass; } assert(bk != nullptr && (bk->is_instance_klass() || bk->is_typeArray_klass()), "invalid bottom klass"); @@ -138,7 +150,15 @@ ObjArrayKlass::ObjArrayKlass(int n, Klass* element_klass, Symbol* name) : ArrayK set_lower_dimension(ArrayKlass::cast(element_klass)); } - set_layout_helper(array_layout_helper(T_OBJECT)); + int lh = array_layout_helper(T_OBJECT); + if (ArrayKlass::is_null_restricted(props)) { + assert(n == 1, "Bytecode does not support null-free multi-dim"); + lh = layout_helper_set_null_free(lh); +#ifdef _LP64 + assert(prototype_header().is_null_free_array(), "sanity"); +#endif + } + set_layout_helper(lh); assert(is_array_klass(), "sanity"); assert(is_objArray_klass(), "sanity"); } @@ -148,26 +168,81 @@ size_t ObjArrayKlass::oop_size(oop obj) const { // because size_given_klass() calls oop_size() on objects that might be // concurrently forwarded, which would overwrite the Klass*. assert(UseCompactObjectHeaders || obj->is_objArray(), "must be object array"); - return objArrayOop(obj)->object_size(); + // return objArrayOop(obj)->object_size(); + return obj->is_flatArray() ? flatArrayOop(obj)->object_size(layout_helper()) : refArrayOop(obj)->object_size(); +} + +ArrayDescription ObjArrayKlass::array_layout_selection(Klass* element, ArrayProperties properties) { + // TODO FIXME: the layout selection should take the array size in consideration + // to avoid creation of arrays too big to be handled by the VM. See JDK-8233189 + if (!UseArrayFlattening || element->is_array_klass() || element->is_identity_class() || element->is_abstract()) { + return ArrayDescription(RefArrayKlassKind, properties, LayoutKind::REFERENCE); + } + assert(element->is_final(), "Flat layouts below require monomorphic elements"); + InlineKlass* vk = InlineKlass::cast(element); + if (is_null_restricted(properties)) { + if (is_non_atomic(properties)) { + // Null-restricted + non-atomic + if (vk->maybe_flat_in_array() && vk->has_non_atomic_layout()) { + return ArrayDescription(FlatArrayKlassKind, properties, LayoutKind::NON_ATOMIC_FLAT); + } else { + return ArrayDescription(RefArrayKlassKind, properties, LayoutKind::REFERENCE); + } + } else { + // Null-restricted + atomic + if (vk->maybe_flat_in_array() && vk->is_naturally_atomic() && vk->has_non_atomic_layout()) { + return ArrayDescription(FlatArrayKlassKind, properties, LayoutKind::NON_ATOMIC_FLAT); + } else if (vk->maybe_flat_in_array() && vk->has_atomic_layout()) { + return ArrayDescription(FlatArrayKlassKind, properties, LayoutKind::ATOMIC_FLAT); + } else { + return ArrayDescription(RefArrayKlassKind, properties, LayoutKind::REFERENCE); + } + } + } else { + // nullable implies atomic, so the non-atomic property is ignored + if (vk->maybe_flat_in_array() && vk->has_nullable_atomic_layout()) { + return ArrayDescription(FlatArrayKlassKind, properties, LayoutKind::NULLABLE_ATOMIC_FLAT); + } else { + return ArrayDescription(RefArrayKlassKind, properties, LayoutKind::REFERENCE); + } + } } -objArrayOop ObjArrayKlass::allocate_instance(int length, TRAPS) { +objArrayOop ObjArrayKlass::allocate_instance(int length, ArrayProperties props, TRAPS) { check_array_allocation_length(length, arrayOopDesc::max_array_length(T_OBJECT), CHECK_NULL); - size_t size = objArrayOopDesc::object_size(length); - return (objArrayOop)Universe::heap()->array_allocate(this, size, length, - /* do_zero */ true, THREAD); + ObjArrayKlass* ak = klass_with_properties(props, THREAD); + size_t size = 0; + switch(ak->kind()) { + case Klass::RefArrayKlassKind: + size = refArrayOopDesc::object_size(length); + break; + case Klass::FlatArrayKlassKind: + size = flatArrayOopDesc::object_size(ak->layout_helper(), length); + break; + default: + ShouldNotReachHere(); + } + assert(size != 0, "Sanity check"); + objArrayOop array = (objArrayOop)Universe::heap()->array_allocate( + ak, size, length, + /* do_zero */ true, CHECK_NULL); + assert(array->is_refArray() || array->is_flatArray(), "Must be"); + objArrayHandle array_h(THREAD, array); + return array_h(); } oop ObjArrayKlass::multi_allocate(int rank, jint* sizes, TRAPS) { int length = *sizes; ArrayKlass* ld_klass = lower_dimension(); // If length < 0 allocate will throw an exception. - objArrayOop array = allocate_instance(length, CHECK_NULL); + ObjArrayKlass* oak = klass_with_properties(ArrayProperties::DEFAULT, CHECK_NULL); + assert(oak->is_refArray_klass() || oak->is_flatArray_klass(), "Must be"); + objArrayOop array = oak->allocate_instance(length, ArrayProperties::DEFAULT, CHECK_NULL); objArrayHandle h_array (THREAD, array); if (rank > 1) { if (length != 0) { for (int index = 0; index < length; index++) { - oop sub_array = ld_klass->multi_allocate(rank - 1, &sizes[1], CHECK_NULL); + oop sub_array = ld_klass->multi_allocate(rank-1, &sizes[1], CHECK_NULL); h_array->obj_at_put(index, sub_array); } } else { @@ -185,114 +260,23 @@ oop ObjArrayKlass::multi_allocate(int rank, jint* sizes, TRAPS) { return h_array(); } -// Either oop or narrowOop depending on UseCompressedOops. -void ObjArrayKlass::do_copy(arrayOop s, size_t src_offset, - arrayOop d, size_t dst_offset, int length, TRAPS) { - if (s == d) { - // since source and destination are equal we do not need conversion checks. - assert(length > 0, "sanity check"); - ArrayAccess<>::oop_arraycopy(s, src_offset, d, dst_offset, length); - } else { - // We have to make sure all elements conform to the destination array - Klass* bound = ObjArrayKlass::cast(d->klass())->element_klass(); - Klass* stype = ObjArrayKlass::cast(s->klass())->element_klass(); - if (stype == bound || stype->is_subtype_of(bound)) { - // elements are guaranteed to be subtypes, so no check necessary - ArrayAccess::oop_arraycopy(s, src_offset, d, dst_offset, length); - } else { - // slow case: need individual subtype checks - // note: don't use obj_at_put below because it includes a redundant store check - if (!ArrayAccess::oop_arraycopy(s, src_offset, d, dst_offset, length)) { - ResourceMark rm(THREAD); - stringStream ss; - if (!bound->is_subtype_of(stype)) { - ss.print("arraycopy: type mismatch: can not copy %s[] into %s[]", - stype->external_name(), bound->external_name()); - } else { - // oop_arraycopy should return the index in the source array that - // contains the problematic oop. - ss.print("arraycopy: element type mismatch: can not cast one of the elements" - " of %s[] to the type of the destination array, %s", - stype->external_name(), bound->external_name()); - } - THROW_MSG(vmSymbols::java_lang_ArrayStoreException(), ss.as_string()); - } - } - } -} - void ObjArrayKlass::copy_array(arrayOop s, int src_pos, arrayOop d, int dst_pos, int length, TRAPS) { assert(s->is_objArray(), "must be obj array"); - if (!d->is_objArray()) { - ResourceMark rm(THREAD); - stringStream ss; - if (d->is_typeArray()) { - ss.print("arraycopy: type mismatch: can not copy object array[] into %s[]", - type2name_tab[ArrayKlass::cast(d->klass())->element_type()]); - } else { - ss.print("arraycopy: destination type %s is not an array", d->klass()->external_name()); - } - THROW_MSG(vmSymbols::java_lang_ArrayStoreException(), ss.as_string()); - } - - // Check is all offsets and lengths are non negative - if (src_pos < 0 || dst_pos < 0 || length < 0) { - // Pass specific exception reason. - ResourceMark rm(THREAD); - stringStream ss; - if (src_pos < 0) { - ss.print("arraycopy: source index %d out of bounds for object array[%d]", - src_pos, s->length()); - } else if (dst_pos < 0) { - ss.print("arraycopy: destination index %d out of bounds for object array[%d]", - dst_pos, d->length()); - } else { - ss.print("arraycopy: length %d is negative", length); + if (UseArrayFlattening) { + if (d->is_flatArray()) { + FlatArrayKlass::cast(d->klass())->copy_array(s, src_pos, d, dst_pos, length, THREAD); + return; } - THROW_MSG(vmSymbols::java_lang_ArrayIndexOutOfBoundsException(), ss.as_string()); - } - // Check if the ranges are valid - if ((((unsigned int) length + (unsigned int) src_pos) > (unsigned int) s->length()) || - (((unsigned int) length + (unsigned int) dst_pos) > (unsigned int) d->length())) { - // Pass specific exception reason. - ResourceMark rm(THREAD); - stringStream ss; - if (((unsigned int) length + (unsigned int) src_pos) > (unsigned int) s->length()) { - ss.print("arraycopy: last source index %u out of bounds for object array[%d]", - (unsigned int) length + (unsigned int) src_pos, s->length()); - } else { - ss.print("arraycopy: last destination index %u out of bounds for object array[%d]", - (unsigned int) length + (unsigned int) dst_pos, d->length()); + if (s->is_flatArray()) { + FlatArrayKlass::cast(s->klass())->copy_array(s, src_pos, d, dst_pos, length, THREAD); + return; } - THROW_MSG(vmSymbols::java_lang_ArrayIndexOutOfBoundsException(), ss.as_string()); } - // Special case. Boundary cases must be checked first - // This allows the following call: copy_array(s, s.length(), d.length(), 0). - // This is correct, since the position is supposed to be an 'in between point', i.e., s.length(), - // points to the right of the last element. - if (length==0) { - return; - } - if (UseCompressedOops) { - size_t src_offset = (size_t) objArrayOopDesc::obj_at_offset(src_pos); - size_t dst_offset = (size_t) objArrayOopDesc::obj_at_offset(dst_pos); - assert(arrayOopDesc::obj_offset_to_raw(s, src_offset, nullptr) == - objArrayOop(s)->obj_at_addr(src_pos), "sanity"); - assert(arrayOopDesc::obj_offset_to_raw(d, dst_offset, nullptr) == - objArrayOop(d)->obj_at_addr(dst_pos), "sanity"); - do_copy(s, src_offset, d, dst_offset, length, CHECK); - } else { - size_t src_offset = (size_t) objArrayOopDesc::obj_at_offset(src_pos); - size_t dst_offset = (size_t) objArrayOopDesc::obj_at_offset(dst_pos); - assert(arrayOopDesc::obj_offset_to_raw(s, src_offset, nullptr) == - objArrayOop(s)->obj_at_addr(src_pos), "sanity"); - assert(arrayOopDesc::obj_offset_to_raw(d, dst_offset, nullptr) == - objArrayOop(d)->obj_at_addr(dst_pos), "sanity"); - do_copy(s, src_offset, d, dst_offset, length, CHECK); - } + assert(s->is_refArray() && d->is_refArray(), "Must be"); + RefArrayKlass::cast(s->klass())->copy_array(s, src_pos, d, dst_pos, length, THREAD); } bool ObjArrayKlass::can_be_primary_super_slow() const { @@ -337,8 +321,36 @@ void ObjArrayKlass::metaspace_pointers_do(MetaspaceClosure* it) { ArrayKlass::metaspace_pointers_do(it); it->push(&_element_klass); it->push(&_bottom_klass); + if (_next_refined_array_klass != nullptr && !CDSConfig::is_dumping_dynamic_archive()) { + it->push(&_next_refined_array_klass); + } } +#if INCLUDE_CDS +void ObjArrayKlass::restore_unshareable_info(ClassLoaderData* loader_data, Handle protection_domain, TRAPS) { + ArrayKlass::restore_unshareable_info(loader_data, protection_domain, CHECK); + if (_next_refined_array_klass != nullptr) { + _next_refined_array_klass->restore_unshareable_info(loader_data, protection_domain, CHECK); + } +} + +void ObjArrayKlass::remove_unshareable_info() { + ArrayKlass::remove_unshareable_info(); + if (_next_refined_array_klass != nullptr && !CDSConfig::is_dumping_dynamic_archive()) { + _next_refined_array_klass->remove_unshareable_info(); + } else { + _next_refined_array_klass = nullptr; + } +} + +void ObjArrayKlass::remove_java_mirror() { + ArrayKlass::remove_java_mirror(); + if (_next_refined_array_klass != nullptr && !CDSConfig::is_dumping_dynamic_archive()) { + _next_refined_array_klass->remove_java_mirror(); + } +} +#endif // INCLUDE_CDS + u2 ObjArrayKlass::compute_modifier_flags() const { // The modifier for an objectArray is the same as its element assert (element_klass() != nullptr, "should be initialized"); @@ -346,8 +358,10 @@ u2 ObjArrayKlass::compute_modifier_flags() const { // Return the flags of the bottom element type. u2 element_flags = bottom_klass()->compute_modifier_flags(); + int identity_flag = (Arguments::enable_preview()) ? JVM_ACC_IDENTITY : 0; + return (element_flags & (JVM_ACC_PUBLIC | JVM_ACC_PRIVATE | JVM_ACC_PROTECTED)) - | (JVM_ACC_ABSTRACT | JVM_ACC_FINAL); + | (identity_flag | JVM_ACC_ABSTRACT | JVM_ACC_FINAL); } ModuleEntry* ObjArrayKlass::module() const { @@ -361,12 +375,51 @@ PackageEntry* ObjArrayKlass::package() const { return bottom_klass()->package(); } +ObjArrayKlass* ObjArrayKlass::klass_with_properties(ArrayKlass::ArrayProperties props, TRAPS) { + assert(props != ArrayProperties::INVALID, "Sanity check"); + + if (properties() == props) { + assert(is_refArray_klass() || is_flatArray_klass(), "Must be a concrete array klass"); + return this; + } + + ObjArrayKlass* ak = next_refined_array_klass_acquire(); + if (ak == nullptr) { + // Ensure atomic creation of refined array klasses + RecursiveLocker rl(MultiArray_lock, THREAD); + + if (next_refined_array_klass() == nullptr) { + ArrayDescription ad = ObjArrayKlass::array_layout_selection(element_klass(), props); + switch (ad._kind) { + case Klass::RefArrayKlassKind: { + ak = RefArrayKlass::allocate_refArray_klass(class_loader_data(), dimension(), element_klass(), props, CHECK_NULL); + break; + } + case Klass::FlatArrayKlassKind: { + assert(dimension() == 1, "Flat arrays can only be dimension 1 arrays"); + ak = FlatArrayKlass::allocate_klass(element_klass(), props, ad._layout_kind, CHECK_NULL); + break; + } + default: + ShouldNotReachHere(); + } + release_set_next_refined_klass(ak); + } + } + + ak = next_refined_array_klass(); + assert(ak != nullptr, "should be set"); + THREAD->check_possible_safepoint(); + return ak->klass_with_properties(props, THREAD); // why not CHECK_NULL ? +} + + // Printing void ObjArrayKlass::print_on(outputStream* st) const { #ifndef PRODUCT Klass::print_on(st); - st->print(" - instance klass: "); + st->print(" - element klass: "); element_klass()->print_value_on(st); st->cr(); #endif //PRODUCT @@ -382,38 +435,13 @@ void ObjArrayKlass::print_value_on(outputStream* st) const { #ifndef PRODUCT void ObjArrayKlass::oop_print_on(oop obj, outputStream* st) { - ArrayKlass::oop_print_on(obj, st); - assert(obj->is_objArray(), "must be objArray"); - objArrayOop oa = objArrayOop(obj); - int print_len = MIN2(oa->length(), MaxElementPrintSize); - for(int index = 0; index < print_len; index++) { - st->print(" - %3d : ", index); - if (oa->obj_at(index) != nullptr) { - oa->obj_at(index)->print_value_on(st); - st->cr(); - } else { - st->print_cr("null"); - } - } - int remaining = oa->length() - print_len; - if (remaining > 0) { - st->print_cr(" - <%d more elements, increase MaxElementPrintSize to print>", remaining); - } + ShouldNotReachHere(); } #endif //PRODUCT void ObjArrayKlass::oop_print_value_on(oop obj, outputStream* st) { - assert(obj->is_objArray(), "must be objArray"); - st->print("a "); - element_klass()->print_value_on(st); - int len = objArrayOop(obj)->length(); - st->print("[%d] ", len); - if (obj != nullptr) { - obj->print_address_on(st); - } else { - st->print_cr("null"); - } + ShouldNotReachHere(); } const char* ObjArrayKlass::internal_name() const { @@ -428,12 +456,14 @@ void ObjArrayKlass::verify_on(outputStream* st) { guarantee(element_klass()->is_klass(), "should be klass"); guarantee(bottom_klass()->is_klass(), "should be klass"); Klass* bk = bottom_klass(); - guarantee(bk->is_instance_klass() || bk->is_typeArray_klass(), "invalid bottom klass"); + guarantee(bk->is_instance_klass() || bk->is_typeArray_klass() || bk->is_flatArray_klass(), + "invalid bottom klass"); } void ObjArrayKlass::oop_verify_on(oop obj, outputStream* st) { ArrayKlass::oop_verify_on(obj, st); guarantee(obj->is_objArray(), "must be objArray"); + guarantee(obj->is_null_free_array() || (!is_null_free_array_klass()), "null-free klass but not object"); objArrayOop oa = objArrayOop(obj); for(int index = 0; index < oa->length(); index++) { guarantee(oopDesc::is_oop_or_null(oa->obj_at(index)), "should be oop"); diff --git a/src/hotspot/share/oops/objArrayKlass.hpp b/src/hotspot/share/oops/objArrayKlass.hpp index 6db6630cff4..170475d7585 100644 --- a/src/hotspot/share/oops/objArrayKlass.hpp +++ b/src/hotspot/share/oops/objArrayKlass.hpp @@ -44,27 +44,38 @@ class ObjArrayKlass : public ArrayKlass { private: // If you add a new field that points to any metaspace object, you // must add this field to ObjArrayKlass::metaspace_pointers_do(). - Klass* _element_klass; // The klass of the elements of this array type Klass* _bottom_klass; // The one-dimensional type (InstanceKlass or TypeArrayKlass) + protected: + Klass* _element_klass; // The klass of the elements of this array type + ObjArrayKlass* _next_refined_array_klass; + protected: // Constructor - ObjArrayKlass(int n, Klass* element_klass, Symbol* name); - static ObjArrayKlass* allocate_klass(ClassLoaderData* loader_data, int n, Klass* k, Symbol* name, TRAPS); + ObjArrayKlass(int n, Klass* element_klass, Symbol* name, KlassKind kind, ArrayKlass::ArrayProperties props, markWord mw); + static ObjArrayKlass* allocate_klass(ClassLoaderData* loader_data, int n, Klass* k, Symbol* name, ArrayKlass::ArrayProperties props, TRAPS); - objArrayOop allocate_instance(int length, TRAPS); + static ArrayDescription array_layout_selection(Klass* element, ArrayProperties properties); + virtual objArrayOop allocate_instance(int length, ArrayProperties props, TRAPS); - protected: - // Create array_name for element klass + // Create array_name for element klass static Symbol* create_element_klass_array_name(JavaThread* current, Klass* element_klass); public: // For dummy objects ObjArrayKlass() {} - // Instance variables - Klass* element_klass() const { return _element_klass; } - void set_element_klass(Klass* k) { _element_klass = k; } - Klass** element_klass_addr() { return &_element_klass; } + virtual Klass* element_klass() const { return _element_klass; } + virtual void set_element_klass(Klass* k) { _element_klass = k; } + + // Compiler/Interpreter offset + static ByteSize element_klass_offset() { return in_ByteSize(offset_of(ObjArrayKlass, _element_klass)); } + + ObjArrayKlass* next_refined_array_klass() const { return _next_refined_array_klass; } + inline ObjArrayKlass* next_refined_array_klass_acquire() const; + void set_next_refined_klass_klass(ObjArrayKlass* ak) { _next_refined_array_klass = ak; } + inline void release_set_next_refined_klass(ObjArrayKlass* ak); + ObjArrayKlass* klass_with_properties(ArrayKlass::ArrayProperties properties, TRAPS); + static ByteSize next_refined_array_klass_offset() { return byte_offset_of(ObjArrayKlass, _next_refined_array_klass); } Klass* bottom_klass() const { return _bottom_klass; } void set_bottom_klass(Klass* k) { _bottom_klass = k; } @@ -73,9 +84,6 @@ class ObjArrayKlass : public ArrayKlass { ModuleEntry* module() const; PackageEntry* package() const; - // Compiler/Interpreter offset - static ByteSize element_klass_offset() { return byte_offset_of(ObjArrayKlass, _element_klass); } - // Dispatched operation bool can_be_primary_super_slow() const; GrowableArray* compute_secondary_supers(int num_extra_slots, @@ -97,12 +105,12 @@ class ObjArrayKlass : public ArrayKlass { virtual void metaspace_pointers_do(MetaspaceClosure* iter); - private: - // Either oop or narrowOop depending on UseCompressedOops. - // must be called from within ObjArrayKlass.cpp - void do_copy(arrayOop s, size_t src_offset, - arrayOop d, size_t dst_offset, - int length, TRAPS); +#if INCLUDE_CDS + virtual void remove_unshareable_info(); + virtual void remove_java_mirror(); + void restore_unshareable_info(ClassLoaderData* loader_data, Handle protection_domain, TRAPS); +#endif + public: static ObjArrayKlass* cast(Klass* k) { return const_cast(cast(const_cast(k))); @@ -158,9 +166,9 @@ class ObjArrayKlass : public ArrayKlass { void print_on(outputStream* st) const; void print_value_on(outputStream* st) const; - void oop_print_value_on(oop obj, outputStream* st); + virtual void oop_print_value_on(oop obj, outputStream* st); #ifndef PRODUCT - void oop_print_on (oop obj, outputStream* st); + virtual void oop_print_on (oop obj, outputStream* st); #endif //PRODUCT const char* internal_name() const; diff --git a/src/hotspot/share/oops/objArrayKlass.inline.hpp b/src/hotspot/share/oops/objArrayKlass.inline.hpp index a92c42d21a8..d74104928be 100644 --- a/src/hotspot/share/oops/objArrayKlass.inline.hpp +++ b/src/hotspot/share/oops/objArrayKlass.inline.hpp @@ -33,89 +33,57 @@ #include "oops/klass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" +#include "runtime/atomic.hpp" #include "utilities/devirtualizer.inline.hpp" #include "utilities/macros.hpp" + +inline ObjArrayKlass* ObjArrayKlass::next_refined_array_klass_acquire() const { + return Atomic::load_acquire(&_next_refined_array_klass); +} + +inline void ObjArrayKlass::release_set_next_refined_klass(ObjArrayKlass* k) { + Atomic::release_store(&_next_refined_array_klass, k); +} + template void ObjArrayKlass::oop_oop_iterate_elements(objArrayOop a, OopClosureType* closure) { - T* p = (T*)a->base(); - T* const end = p + a->length(); - - for (;p < end; p++) { - Devirtualizer::do_oop(closure, p); - } + ShouldNotReachHere(); } template void ObjArrayKlass::oop_oop_iterate_elements_bounded( objArrayOop a, OopClosureType* closure, void* low, void* high) { + ShouldNotReachHere(); - T* const l = (T*)low; - T* const h = (T*)high; - - T* p = (T*)a->base(); - T* end = p + a->length(); - - if (p < l) { - p = l; - } - if (end > h) { - end = h; - } - - for (;p < end; ++p) { - Devirtualizer::do_oop(closure, p); - } } template void ObjArrayKlass::oop_oop_iterate(oop obj, OopClosureType* closure) { - assert(obj->is_array(), "obj must be array"); - objArrayOop a = objArrayOop(obj); - - if (Devirtualizer::do_metadata(closure)) { - Devirtualizer::do_klass(closure, obj->klass()); - } - - oop_oop_iterate_elements(a, closure); + ShouldNotReachHere(); } template void ObjArrayKlass::oop_oop_iterate_reverse(oop obj, OopClosureType* closure) { - // No reverse implementation ATM. - oop_oop_iterate(obj, closure); + ShouldNotReachHere(); } template void ObjArrayKlass::oop_oop_iterate_bounded(oop obj, OopClosureType* closure, MemRegion mr) { - assert(obj->is_array(), "obj must be array"); - objArrayOop a = objArrayOop(obj); - - if (Devirtualizer::do_metadata(closure)) { - Devirtualizer::do_klass(closure, a->klass()); - } - - oop_oop_iterate_elements_bounded(a, closure, mr.start(), mr.end()); + ShouldNotReachHere(); } // Like oop_oop_iterate but only iterates over a specified range and only used // for objArrayOops. template void ObjArrayKlass::oop_oop_iterate_range(objArrayOop a, OopClosureType* closure, int start, int end) { - T* low = (T*)a->base() + start; - T* high = (T*)a->base() + end; - - oop_oop_iterate_elements_bounded(a, closure, low, high); + ShouldNotReachHere(); } // Placed here to resolve include cycle between objArrayKlass.inline.hpp and objArrayOop.inline.hpp template void objArrayOopDesc::oop_iterate_range(OopClosureType* blk, int start, int end) { - if (UseCompressedOops) { - ((ObjArrayKlass*)klass())->oop_oop_iterate_range(this, blk, start, end); - } else { - ((ObjArrayKlass*)klass())->oop_oop_iterate_range(this, blk, start, end); - } + ShouldNotReachHere(); } #endif // SHARE_OOPS_OBJARRAYKLASS_INLINE_HPP diff --git a/src/hotspot/share/oops/objArrayOop.cpp b/src/hotspot/share/oops/objArrayOop.cpp index 363f6710da8..cb42bc1a784 100644 --- a/src/hotspot/share/oops/objArrayOop.cpp +++ b/src/hotspot/share/oops/objArrayOop.cpp @@ -27,16 +27,6 @@ #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" -oop objArrayOopDesc::replace_if_null(int index, oop exchange_value) { - ptrdiff_t offs; - if (UseCompressedOops) { - offs = objArrayOopDesc::obj_at_offset(index); - } else { - offs = objArrayOopDesc::obj_at_offset(index); - } - return HeapAccess::oop_atomic_cmpxchg_at(as_oop(), offs, (oop)nullptr, exchange_value); -} - Klass* objArrayOopDesc::element_klass() { return ObjArrayKlass::cast(klass())->element_klass(); } diff --git a/src/hotspot/share/oops/objArrayOop.hpp b/src/hotspot/share/oops/objArrayOop.hpp index 0539171f1ab..a2d4349aa7d 100644 --- a/src/hotspot/share/oops/objArrayOop.hpp +++ b/src/hotspot/share/oops/objArrayOop.hpp @@ -59,27 +59,17 @@ class objArrayOopDesc : public arrayOopDesc { return arrayOopDesc::base_offset_in_bytes(T_OBJECT); } + inline static objArrayOop cast(oop o); + // base is the address following the header. HeapWord* base() const; // Accessing oop obj_at(int index) const; + oop obj_at(int index, TRAPS) const; void obj_at_put(int index, oop value); - - oop replace_if_null(int index, oop exchange_value); - - // Sizing - size_t object_size() { return object_size(length()); } - - static size_t object_size(int length) { - // This returns the object size in HeapWords. - size_t asz = (size_t)length * heapOopSize; - size_t size_words = heap_word_size(base_offset_in_bytes() + asz); - size_t osz = align_object_size(size_words); - assert(osz < max_jint, "no overflow"); - return osz; - } + void obj_at_put(int index, oop value, TRAPS); Klass* element_klass(); diff --git a/src/hotspot/share/oops/objArrayOop.inline.hpp b/src/hotspot/share/oops/objArrayOop.inline.hpp index 21f95b756de..0ee1e5dcb12 100644 --- a/src/hotspot/share/oops/objArrayOop.inline.hpp +++ b/src/hotspot/share/oops/objArrayOop.inline.hpp @@ -29,11 +29,18 @@ #include "oops/access.hpp" #include "oops/arrayOop.hpp" +#include "oops/flatArrayOop.inline.hpp" #include "oops/oop.inline.hpp" +#include "oops/refArrayKlass.inline.hpp" #include "runtime/globals.hpp" inline HeapWord* objArrayOopDesc::base() const { return (HeapWord*) arrayOopDesc::base(T_OBJECT); } +inline objArrayOop objArrayOopDesc::cast(oop o) { + assert(o->is_objArray(), "Must be a refArray"); + return (objArrayOop)o; +} + template T* objArrayOopDesc::obj_at_addr(int index) const { assert(is_within_bounds(index), "index %d out of bounds %d", index, length()); return &((T*)base())[index]; @@ -41,14 +48,38 @@ template T* objArrayOopDesc::obj_at_addr(int index) const { inline oop objArrayOopDesc::obj_at(int index) const { assert(is_within_bounds(index), "index %d out of bounds %d", index, length()); - ptrdiff_t offset = UseCompressedOops ? obj_at_offset(index) : obj_at_offset(index); - return HeapAccess::oop_load_at(as_oop(), offset); + if (is_flatArray()) { + return ((const flatArrayOopDesc* )this)->obj_at(index); + } else { + return ((const refArrayOopDesc* )this)->obj_at(index); + } +} + +inline oop objArrayOopDesc::obj_at(int index, TRAPS) const { + assert(is_within_bounds(index), "index %d out of bounds %d", index, length()); + if (is_flatArray()) { + return ((const flatArrayOopDesc* )this)->obj_at(index, CHECK_NULL); + } else { + return ((const refArrayOopDesc* )this)->obj_at(index, CHECK_NULL); + } } inline void objArrayOopDesc::obj_at_put(int index, oop value) { assert(is_within_bounds(index), "index %d out of bounds %d", index, length()); - ptrdiff_t offset = UseCompressedOops ? obj_at_offset(index) : obj_at_offset(index); - HeapAccess::oop_store_at(as_oop(), offset, value); + if (is_flatArray()) { + ((flatArrayOopDesc* )this)->obj_at_put(index, value); + } else { + ((refArrayOopDesc* )this)->obj_at_put(index, value); + } +} + +inline void objArrayOopDesc::obj_at_put(int index, oop value, TRAPS) { + assert(is_within_bounds(index), "index %d out of bounds %d", index, length()); + if (is_flatArray()) { + ((flatArrayOopDesc* )this)->obj_at_put(index, value, CHECK); + } else { + ((refArrayOopDesc* )this)->obj_at_put(index, value, CHECK); + } } #endif // SHARE_OOPS_OBJARRAYOOP_INLINE_HPP diff --git a/src/hotspot/share/oops/oop.cpp b/src/hotspot/share/oops/oop.cpp index 7f0068f7473..185e72c165f 100644 --- a/src/hotspot/share/oops/oop.cpp +++ b/src/hotspot/share/oops/oop.cpp @@ -144,12 +144,15 @@ void VerifyOopClosure::do_oop(oop* p) { VerifyOopClosure::do_oop_work(p); void VerifyOopClosure::do_oop(narrowOop* p) { VerifyOopClosure::do_oop_work(p); } // type test operations that doesn't require inclusion of oop.inline.hpp. -bool oopDesc::is_instance_noinline() const { return is_instance(); } -bool oopDesc::is_instanceRef_noinline() const { return is_instanceRef(); } -bool oopDesc::is_stackChunk_noinline() const { return is_stackChunk(); } -bool oopDesc::is_array_noinline() const { return is_array(); } -bool oopDesc::is_objArray_noinline() const { return is_objArray(); } -bool oopDesc::is_typeArray_noinline() const { return is_typeArray(); } +bool oopDesc::is_instance_noinline() const { return is_instance(); } +bool oopDesc::is_instanceRef_noinline() const { return is_instanceRef(); } +bool oopDesc::is_stackChunk_noinline() const { return is_stackChunk(); } +bool oopDesc::is_array_noinline() const { return is_array(); } +bool oopDesc::is_objArray_noinline() const { return is_objArray(); } +bool oopDesc::is_refArray_noinline() const { return is_refArray(); } +bool oopDesc::is_typeArray_noinline() const { return is_typeArray(); } +bool oopDesc::is_flatArray_noinline() const { return is_flatArray(); } +bool oopDesc::is_null_free_array_noinline() const { return is_null_free_array(); } #if INCLUDE_CDS_JAVA_HEAP void oopDesc::set_narrow_klass(narrowKlass nk) { diff --git a/src/hotspot/share/oops/oop.hpp b/src/hotspot/share/oops/oop.hpp index 3ec0ce5764a..8ca4551b9ec 100644 --- a/src/hotspot/share/oops/oop.hpp +++ b/src/hotspot/share/oops/oop.hpp @@ -44,6 +44,16 @@ // (see oopHierarchy for complete oop class hierarchy) // // no virtual functions allowed +// +// oopDesc::_mark - the "oop mark word" encoding to be found separately in markWord.hpp +// +// oopDesc::_metadata - encodes the object's klass pointer, as a raw pointer in "_klass" +// or compressed pointer in "_compressed_klass" +// +// The overall size of the _metadata field is dependent on "UseCompressedClassPointers", +// hence the terms "narrow" (32 bits) vs "wide" (64 bits). +// + class oopDesc { friend class VMStructs; @@ -85,6 +95,7 @@ class oopDesc { // Used only to re-initialize the mark word (e.g., of promoted // objects during a GC) -- requires a valid klass pointer inline void init_mark(); + inline void reinit_mark(); // special for parallelGC inline Klass* klass() const; inline Klass* klass_or_null() const; @@ -120,20 +131,28 @@ class oopDesc { inline size_t size_given_klass(Klass* klass); // type test operations (inlined in oop.inline.hpp) - inline bool is_instance() const; - inline bool is_instanceRef() const; - inline bool is_stackChunk() const; - inline bool is_array() const; - inline bool is_objArray() const; - inline bool is_typeArray() const; + inline bool is_instance() const; + inline bool is_inline_type() const; + inline bool is_instanceRef() const; + inline bool is_stackChunk() const; + inline bool is_array() const; + inline bool is_objArray() const; + inline bool is_typeArray() const; + inline bool is_flatArray() const; + inline bool is_refArray() const; + inline bool is_null_free_array() const; + inline bool is_refined_objArray() const; // type test operations that don't require inclusion of oop.inline.hpp. - bool is_instance_noinline() const; - bool is_instanceRef_noinline() const; - bool is_stackChunk_noinline() const; - bool is_array_noinline() const; - bool is_objArray_noinline() const; - bool is_typeArray_noinline() const; + bool is_instance_noinline() const; + bool is_instanceRef_noinline() const; + bool is_stackChunk_noinline() const; + bool is_array_noinline() const; + bool is_objArray_noinline() const; + bool is_refArray_noinline() const; + bool is_typeArray_noinline() const; + bool is_flatArray_noinline() const; + bool is_null_free_array_noinline() const; protected: inline oop as_oop() const { return const_cast(this); } diff --git a/src/hotspot/share/oops/oop.inline.hpp b/src/hotspot/share/oops/oop.inline.hpp index 16c444a43f8..34532a50145 100644 --- a/src/hotspot/share/oops/oop.inline.hpp +++ b/src/hotspot/share/oops/oop.inline.hpp @@ -83,7 +83,7 @@ markWord oopDesc::cas_set_mark(markWord new_mark, markWord old_mark, atomic_memo } markWord oopDesc::prototype_mark() const { - if (UseCompactObjectHeaders) { + if (UseCompactObjectHeaders || EnableValhalla) { return klass()->prototype_header(); } else { return markWord::prototype(); @@ -94,6 +94,17 @@ void oopDesc::init_mark() { set_mark(prototype_mark()); } +// This is for parallel gc, which doesn't always have the klass. +// markWord::must_be_preserved preserves the original prototype header bits for EnableValhalla, +// I don't know why serial gc doesn't work the same. +void oopDesc::reinit_mark() { + if (UseCompactObjectHeaders) { + set_mark(klass()->prototype_header()); + } else { + set_mark(markWord::prototype()); + } +} + Klass* oopDesc::klass() const { switch (ObjLayout::klass_mode()) { case ObjLayout::Compact: @@ -240,7 +251,24 @@ bool oopDesc::is_instanceRef() const { return klass()->is_reference_instance_kla bool oopDesc::is_stackChunk() const { return klass()->is_stack_chunk_instance_klass(); } bool oopDesc::is_array() const { return klass()->is_array_klass(); } bool oopDesc::is_objArray() const { return klass()->is_objArray_klass(); } +bool oopDesc::is_refArray() const { return klass()->is_refArray_klass(); } bool oopDesc::is_typeArray() const { return klass()->is_typeArray_klass(); } +bool oopDesc::is_refined_objArray() const { return klass()->is_refined_objArray_klass(); } + +bool oopDesc::is_inline_type() const { return mark().is_inline_type(); } +#ifdef _LP64 +bool oopDesc::is_flatArray() const { + markWord mrk = mark(); + return (mrk.is_unlocked()) ? mrk.is_flat_array() : klass()->is_flatArray_klass(); +} +bool oopDesc::is_null_free_array() const { + markWord mrk = mark(); + return (mrk.is_unlocked()) ? mrk.is_null_free_array() : klass()->is_null_free_array_klass(); +} +#else +bool oopDesc::is_flatArray() const { return klass()->is_flatArray_klass(); } +bool oopDesc::is_null_free_array() const { return klass()->is_null_free_array_klass(); } +#endif template T* oopDesc::field_addr(int offset) const { return reinterpret_cast(cast_from_oop(as_oop()) + offset); } diff --git a/src/hotspot/share/oops/oopsHierarchy.hpp b/src/hotspot/share/oops/oopsHierarchy.hpp index 8458788f44f..ded8583c1ae 100644 --- a/src/hotspot/share/oops/oopsHierarchy.hpp +++ b/src/hotspot/share/oops/oopsHierarchy.hpp @@ -47,6 +47,8 @@ typedef class stackChunkOopDesc* stackChunkOop; typedef class arrayOopDesc* arrayOop; typedef class objArrayOopDesc* objArrayOop; typedef class typeArrayOopDesc* typeArrayOop; +typedef class flatArrayOopDesc* flatArrayOop; +typedef class refArrayOopDesc* refArrayOop; #else @@ -155,6 +157,8 @@ DEF_OOP(stackChunk); DEF_OOP(array); DEF_OOP(objArray); DEF_OOP(typeArray); +DEF_OOP(flatArray); +DEF_OOP(refArray); #endif // CHECK_UNHANDLED_OOPS @@ -184,6 +188,7 @@ class ConstantPool; class Klass; class InstanceKlass; +class InlineKlass; class InstanceMirrorKlass; class InstanceClassLoaderKlass; class InstanceRefKlass; @@ -191,5 +196,6 @@ class InstanceStackChunkKlass; class ArrayKlass; class ObjArrayKlass; class TypeArrayKlass; +class FlatArrayKlass; #endif // SHARE_OOPS_OOPSHIERARCHY_HPP diff --git a/src/hotspot/share/oops/refArrayKlass.cpp b/src/hotspot/share/oops/refArrayKlass.cpp new file mode 100644 index 00000000000..3880ba031f1 --- /dev/null +++ b/src/hotspot/share/oops/refArrayKlass.cpp @@ -0,0 +1,376 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "classfile/moduleEntry.hpp" +#include "classfile/packageEntry.hpp" +#include "classfile/symbolTable.hpp" +#include "classfile/vmClasses.hpp" +#include "classfile/vmSymbols.hpp" +#include "gc/shared/collectedHeap.inline.hpp" +#include "memory/iterator.inline.hpp" +#include "memory/metadataFactory.hpp" +#include "memory/metaspaceClosure.hpp" +#include "memory/oopFactory.hpp" +#include "memory/resourceArea.hpp" +#include "memory/universe.hpp" +#include "oops/arrayKlass.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/klass.inline.hpp" +#include "oops/markWord.hpp" +#include "oops/oop.inline.hpp" +#include "oops/refArrayKlass.inline.hpp" +#include "oops/refArrayOop.inline.hpp" +#include "oops/symbol.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/mutexLocker.hpp" +#include "utilities/macros.hpp" + +RefArrayKlass *RefArrayKlass::allocate_klass(ClassLoaderData* loader_data, int n, + Klass* k, Symbol *name, ArrayKlass::ArrayProperties props, + TRAPS) { + assert(RefArrayKlass::header_size() <= InstanceKlass::header_size(), + "array klasses must be same size as InstanceKlass"); + + int size = ArrayKlass::static_size(RefArrayKlass::header_size()); + + return new (loader_data, size, THREAD) RefArrayKlass(n, k, name, props); +} + +RefArrayKlass* RefArrayKlass::allocate_refArray_klass(ClassLoaderData* loader_data, int n, + Klass* element_klass, ArrayKlass::ArrayProperties props, + TRAPS) { + assert(!ArrayKlass::is_null_restricted(props) || (n == 1 && element_klass->is_inline_klass()), + "null-free unsupported"); + + // Eagerly allocate the direct array supertype. + Klass* super_klass = nullptr; + if (!Universe::is_bootstrapping() || vmClasses::Object_klass_loaded()) { + assert(MultiArray_lock->holds_lock(THREAD), + "must hold lock after bootstrapping"); + Klass* element_super = element_klass->super(); + super_klass = element_klass->array_klass(CHECK_NULL); + } + + // Create type name for klass. + Symbol* name = ArrayKlass::create_element_klass_array_name(element_klass, CHECK_NULL); + + // Initialize instance variables + RefArrayKlass* oak = RefArrayKlass::allocate_klass(loader_data, n, element_klass, + name, props, CHECK_NULL); + + ModuleEntry* module = oak->module(); + assert(module != nullptr, "No module entry for array"); + + // Call complete_create_array_klass after all instance variables has been + // initialized. + ArrayKlass::complete_create_array_klass(oak, super_klass, module, CHECK_NULL); + + // Add all classes to our internal class loader list here, + // including classes in the bootstrap (null) class loader. + // Do this step after creating the mirror so that if the + // mirror creation fails, loaded_classes_do() doesn't find + // an array class without a mirror. + loader_data->add_class(oak); + + return oak; +} + +RefArrayKlass::RefArrayKlass(int n, Klass* element_klass, Symbol* name, + ArrayKlass::ArrayProperties props) + : ObjArrayKlass(n, element_klass, name, Kind, props, + ArrayKlass::is_null_restricted(props) ? markWord::null_free_array_prototype() : markWord::prototype()) { + set_dimension(n); + set_element_klass(element_klass); + + Klass* bk; + if (element_klass->is_objArray_klass()) { + bk = ObjArrayKlass::cast(element_klass)->bottom_klass(); + } else { + bk = element_klass; + } + assert(bk != nullptr && (bk->is_instance_klass() || bk->is_typeArray_klass()), + "invalid bottom klass"); + set_bottom_klass(bk); + set_class_loader_data(bk->class_loader_data()); + + if (element_klass->is_array_klass()) { + set_lower_dimension(ArrayKlass::cast(element_klass)); + } + + int lh = array_layout_helper(T_OBJECT); + if (ArrayKlass::is_null_restricted(props)) { + assert(n == 1, "Bytecode does not support null-free multi-dim"); + lh = layout_helper_set_null_free(lh); +#ifdef _LP64 + assert(prototype_header().is_null_free_array(), "sanity"); +#endif + } + set_layout_helper(lh); + assert(is_array_klass(), "sanity"); + assert(is_refArray_klass(), "sanity"); +} + +size_t RefArrayKlass::oop_size(oop obj) const { + // In this assert, we cannot safely access the Klass* with compact headers, + // because size_given_klass() calls oop_size() on objects that might be + // concurrently forwarded, which would overwrite the Klass*. + assert(UseCompactObjectHeaders || obj->is_refArray(), "must be a reference array"); + return refArrayOop(obj)->object_size(); +} + +objArrayOop RefArrayKlass::allocate_instance(int length, ArrayProperties props, TRAPS) { + check_array_allocation_length( + length, arrayOopDesc::max_array_length(T_OBJECT), CHECK_NULL); + size_t size = refArrayOopDesc::object_size(length); + objArrayOop array = (objArrayOop)Universe::heap()->array_allocate( + this, size, length, + /* do_zero */ true, CHECK_NULL); + assert(array->is_refArray(), "Must be"); + objArrayHandle array_h(THREAD, array); + return array_h(); +} + + +// Either oop or narrowOop depending on UseCompressedOops. +void RefArrayKlass::do_copy(arrayOop s, size_t src_offset, arrayOop d, + size_t dst_offset, int length, TRAPS) { + if (s == d) { + // since source and destination are equal we do not need conversion checks. + assert(length > 0, "sanity check"); + ArrayAccess<>::oop_arraycopy(s, src_offset, d, dst_offset, length); + } else { + // We have to make sure all elements conform to the destination array + Klass *bound = RefArrayKlass::cast(d->klass())->element_klass(); + Klass *stype = RefArrayKlass::cast(s->klass())->element_klass(); + // Perform null check if dst is null-free but src has no such guarantee + bool null_check = ((!s->klass()->is_null_free_array_klass()) && + d->klass()->is_null_free_array_klass()); + if (stype == bound || stype->is_subtype_of(bound)) { + if (null_check) { + ArrayAccess::oop_arraycopy( + s, src_offset, d, dst_offset, length); + } else { + ArrayAccess::oop_arraycopy(s, src_offset, d, + dst_offset, length); + } + } else { + if (null_check) { + ArrayAccess::oop_arraycopy(s, src_offset, d, + dst_offset, length); + } else { + ArrayAccess::oop_arraycopy( + s, src_offset, d, dst_offset, length); + } + } + } +} + +void RefArrayKlass::copy_array(arrayOop s, int src_pos, arrayOop d, int dst_pos, + int length, TRAPS) { + assert(s->is_refArray(), "must be a reference array"); + + if (UseArrayFlattening) { + if (d->is_flatArray()) { + FlatArrayKlass::cast(d->klass())->copy_array(s, src_pos, d, dst_pos, length, THREAD); + return; + } + if (s->is_flatArray()) { + FlatArrayKlass::cast(s->klass())->copy_array(s, src_pos, d, dst_pos, length, THREAD); + return; + } + } + + if (!d->is_refArray()) { + ResourceMark rm(THREAD); + stringStream ss; + if (d->is_typeArray()) { + ss.print( + "arraycopy: type mismatch: can not copy object array[] into %s[]", + type2name_tab[ArrayKlass::cast(d->klass())->element_type()]); + } else { + ss.print("arraycopy: destination type %s is not an array", + d->klass()->external_name()); + } + THROW_MSG(vmSymbols::java_lang_ArrayStoreException(), ss.as_string()); + } + + // Check is all offsets and lengths are non negative + if (src_pos < 0 || dst_pos < 0 || length < 0) { + // Pass specific exception reason. + ResourceMark rm(THREAD); + stringStream ss; + if (src_pos < 0) { + ss.print("arraycopy: source index %d out of bounds for object array[%d]", + src_pos, s->length()); + } else if (dst_pos < 0) { + ss.print( + "arraycopy: destination index %d out of bounds for object array[%d]", + dst_pos, d->length()); + } else { + ss.print("arraycopy: length %d is negative", length); + } + THROW_MSG(vmSymbols::java_lang_ArrayIndexOutOfBoundsException(), + ss.as_string()); + } + // Check if the ranges are valid + if ((((unsigned int)length + (unsigned int)src_pos) > + (unsigned int)s->length()) || + (((unsigned int)length + (unsigned int)dst_pos) > + (unsigned int)d->length())) { + // Pass specific exception reason. + ResourceMark rm(THREAD); + stringStream ss; + if (((unsigned int)length + (unsigned int)src_pos) > + (unsigned int)s->length()) { + ss.print( + "arraycopy: last source index %u out of bounds for object array[%d]", + (unsigned int)length + (unsigned int)src_pos, s->length()); + } else { + ss.print("arraycopy: last destination index %u out of bounds for object " + "array[%d]", + (unsigned int)length + (unsigned int)dst_pos, d->length()); + } + THROW_MSG(vmSymbols::java_lang_ArrayIndexOutOfBoundsException(), + ss.as_string()); + } + + // Special case. Boundary cases must be checked first + // This allows the following call: copy_array(s, s.length(), d.length(), 0). + // This is correct, since the position is supposed to be an 'in between + // point', i.e., s.length(), points to the right of the last element. + if (length == 0) { + return; + } + if (UseCompressedOops) { + size_t src_offset = + (size_t)refArrayOopDesc::obj_at_offset(src_pos); + size_t dst_offset = + (size_t)refArrayOopDesc::obj_at_offset(dst_pos); + assert(arrayOopDesc::obj_offset_to_raw(s, src_offset, nullptr) == + refArrayOop(s)->obj_at_addr(src_pos), + "sanity"); + assert(arrayOopDesc::obj_offset_to_raw(d, dst_offset, nullptr) == + refArrayOop(d)->obj_at_addr(dst_pos), + "sanity"); + do_copy(s, src_offset, d, dst_offset, length, CHECK); + } else { + size_t src_offset = (size_t)refArrayOopDesc::obj_at_offset(src_pos); + size_t dst_offset = (size_t)refArrayOopDesc::obj_at_offset(dst_pos); + assert(arrayOopDesc::obj_offset_to_raw(s, src_offset, nullptr) == + refArrayOop(s)->obj_at_addr(src_pos), + "sanity"); + assert(arrayOopDesc::obj_offset_to_raw(d, dst_offset, nullptr) == + refArrayOop(d)->obj_at_addr(dst_pos), + "sanity"); + do_copy(s, src_offset, d, dst_offset, length, CHECK); + } +} + +void RefArrayKlass::initialize(TRAPS) { + bottom_klass()->initialize(THREAD); // dispatches to either InstanceKlass or TypeArrayKlass +} + +void RefArrayKlass::metaspace_pointers_do(MetaspaceClosure *it) { + ObjArrayKlass::metaspace_pointers_do(it); +} + +// Printing + +void RefArrayKlass::print_on(outputStream* st) const { +#ifndef PRODUCT + Klass::print_on(st); + st->print(" - element klass: "); + element_klass()->print_value_on(st); + st->cr(); +#endif // PRODUCT +} + +void RefArrayKlass::print_value_on(outputStream* st) const { + assert(is_klass(), "must be klass"); + + element_klass()->print_value_on(st); + st->print("[]"); +} + +#ifndef PRODUCT + +void RefArrayKlass::oop_print_on(oop obj, outputStream* st) { + ArrayKlass::oop_print_on(obj, st); + assert(obj->is_refArray(), "must be refArray"); + refArrayOop oa = refArrayOop(obj); + int print_len = MIN2(oa->length(), MaxElementPrintSize); + for (int index = 0; index < print_len; index++) { + st->print(" - %3d : ", index); + if (oa->obj_at(index) != nullptr) { + oa->obj_at(index)->print_value_on(st); + st->cr(); + } else { + st->print_cr("null"); + } + } + int remaining = oa->length() - print_len; + if (remaining > 0) { + st->print_cr(" - <%d more elements, increase MaxElementPrintSize to print>", + remaining); + } +} + +#endif // PRODUCT + +void RefArrayKlass::oop_print_value_on(oop obj, outputStream* st) { + assert(obj->is_refArray(), "must be refArray"); + st->print("a "); + element_klass()->print_value_on(st); + int len = refArrayOop(obj)->length(); + st->print("[%d] ", len); + if (obj != nullptr) { + obj->print_address_on(st); + } else { + st->print_cr("null"); + } +} + +// Verification + +void RefArrayKlass::verify_on(outputStream* st) { + ArrayKlass::verify_on(st); + guarantee(element_klass()->is_klass(), "should be klass"); + guarantee(bottom_klass()->is_klass(), "should be klass"); + Klass *bk = bottom_klass(); + guarantee(bk->is_instance_klass() || bk->is_typeArray_klass() || + bk->is_flatArray_klass(), + "invalid bottom klass"); +} + +void RefArrayKlass::oop_verify_on(oop obj, outputStream* st) { + ArrayKlass::oop_verify_on(obj, st); + guarantee(obj->is_refArray(), "must be refArray"); + guarantee(obj->is_null_free_array() || (!is_null_free_array_klass()), + "null-free klass but not object"); + refArrayOop oa = refArrayOop(obj); + for (int index = 0; index < oa->length(); index++) { + guarantee(oopDesc::is_oop_or_null(oa->obj_at(index)), "should be oop"); + } +} diff --git a/src/hotspot/share/oops/refArrayKlass.hpp b/src/hotspot/share/oops/refArrayKlass.hpp new file mode 100644 index 00000000000..f83ec75b093 --- /dev/null +++ b/src/hotspot/share/oops/refArrayKlass.hpp @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_OOPS_REFARRAYKLASS_HPP +#define SHARE_OOPS_REFARRAYKLASS_HPP + +#include "oops/arrayKlass.hpp" +#include "oops/objArrayKlass.hpp" +#include "utilities/macros.hpp" + +class ClassLoaderData; + +// RefArrayKlass is the klass for arrays of references + +class RefArrayKlass : public ObjArrayKlass { + friend class Deoptimization; + friend class oopFactory; + friend class VMStructs; + friend class JVMCIVMStructs; + + public: + static const KlassKind Kind = RefArrayKlassKind; + + private: + // Constructor + RefArrayKlass(int n, Klass* element_klass, Symbol* name, ArrayKlass::ArrayProperties props); + static RefArrayKlass* allocate_klass(ClassLoaderData* loader_data, int n, Klass* k, Symbol* name, + ArrayKlass::ArrayProperties props, TRAPS); + + public: + // For dummy objects + RefArrayKlass() {} + + // Dispatched operation + DEBUG_ONLY(bool is_refArray_klass_slow() const { return true; }) + size_t oop_size(oop obj) const; // TODO FIXME make it virtual in objArrayKlass + + // Allocation + static RefArrayKlass* allocate_refArray_klass(ClassLoaderData* loader_data, + int n, Klass* element_klass, + ArrayKlass::ArrayProperties props, TRAPS); + + private: + objArrayOop allocate_instance(int length, ArrayProperties props, TRAPS); + + public: + // Copying TODO FIXME make copying method in objArrayKlass virtual and default implementation invalid (ShouldNotReachHere()) + void copy_array(arrayOop s, int src_pos, arrayOop d, int dst_pos, int length, TRAPS); + + virtual void metaspace_pointers_do(MetaspaceClosure* iter); + + private: + // Either oop or narrowOop depending on UseCompressedOops. + // must be called from within ObjArrayKlass.cpp + void do_copy(arrayOop s, size_t src_offset, + arrayOop d, size_t dst_offset, + int length, TRAPS); + + public: + static RefArrayKlass *cast(Klass* k) { + assert(k->is_refArray_klass(), "cast to RefArrayKlass"); + return const_cast(cast(const_cast(k))); + } + + static const RefArrayKlass *cast(const Klass* k) { + assert(k->is_refArray_klass(), "cast to RefArrayKlass"); + return static_cast(k); + } + + // Sizing + static int header_size() { return sizeof(RefArrayKlass) / wordSize; } + int size() const { return ArrayKlass::static_size(header_size()); } + + // Initialization (virtual from Klass) + void initialize(TRAPS); + + // Oop fields (and metadata) iterators + // + // The RefArrayKlass iterators also visits the Object's klass. + + // Iterate over oop elements and metadata. + template + inline void oop_oop_iterate(oop obj, OopClosureType* closure); + + // Iterate over oop elements and metadata. + template + inline void oop_oop_iterate_reverse(oop obj, OopClosureType* closure); + + // Iterate over oop elements within mr, and metadata. + template + inline void oop_oop_iterate_bounded(oop obj, OopClosureType* closure, MemRegion mr); + + // Iterate over oop elements within [start, end), and metadata. + template + inline void oop_oop_iterate_range(refArrayOop a, OopClosureType* closure, int start, int end); + + public: + // Iterate over all oop elements. + template + inline void oop_oop_iterate_elements(refArrayOop a, OopClosureType* closure); + + private: + // Iterate over all oop elements with indices within mr. + template + inline void oop_oop_iterate_elements_bounded(refArrayOop a, OopClosureType* closure, void* low, void* high); + + public: + // Printing + void print_on(outputStream* st) const; + void print_value_on(outputStream* st) const; + + void oop_print_value_on(oop obj, outputStream* st); +#ifndef PRODUCT + void oop_print_on(oop obj, outputStream* st); +#endif // PRODUCT + + // Verification + void verify_on(outputStream* st); + + void oop_verify_on(oop obj, outputStream* st); +}; + +#endif // SHARE_OOPS_REFARRAYKLASS_HPP diff --git a/src/hotspot/share/oops/refArrayKlass.inline.hpp b/src/hotspot/share/oops/refArrayKlass.inline.hpp new file mode 100644 index 00000000000..01a5dba6e38 --- /dev/null +++ b/src/hotspot/share/oops/refArrayKlass.inline.hpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_OOPS_REFARRAYKLASS_INLINE_HPP +#define SHARE_OOPS_REFARRAYKLASS_INLINE_HPP + +#include "oops/refArrayKlass.hpp" + +#include "memory/memRegion.hpp" +#include "oops/arrayKlass.hpp" +#include "oops/arrayOop.hpp" +#include "oops/klass.hpp" +#include "oops/oop.inline.hpp" +#include "oops/refArrayOop.inline.hpp" +#include "utilities/devirtualizer.inline.hpp" +#include "utilities/macros.hpp" + +template +void RefArrayKlass::oop_oop_iterate_elements(refArrayOop a, + OopClosureType *closure) { + T *p = (T *)a->base(); + T *const end = p + a->length(); + + for (; p < end; p++) { + Devirtualizer::do_oop(closure, p); + } +} + +template +void RefArrayKlass::oop_oop_iterate_elements_bounded(refArrayOop a, + OopClosureType *closure, + void *low, void *high) { + + T *const l = (T *)low; + T *const h = (T *)high; + + T *p = (T *)a->base(); + T *end = p + a->length(); + + if (p < l) { + p = l; + } + if (end > h) { + end = h; + } + + for (; p < end; ++p) { + Devirtualizer::do_oop(closure, p); + } +} + +template +void RefArrayKlass::oop_oop_iterate(oop obj, OopClosureType *closure) { + assert(obj->is_array(), "obj must be array"); + refArrayOop a = refArrayOop(obj); + + if (Devirtualizer::do_metadata(closure)) { + Devirtualizer::do_klass(closure, obj->klass()); + } + + oop_oop_iterate_elements(a, closure); +} + +template +void RefArrayKlass::oop_oop_iterate_reverse(oop obj, OopClosureType *closure) { + // No reverse implementation ATM. + oop_oop_iterate(obj, closure); +} + +template +void RefArrayKlass::oop_oop_iterate_bounded(oop obj, OopClosureType *closure, + MemRegion mr) { + assert(obj->is_array(), "obj must be array"); + refArrayOop a = refArrayOop(obj); + + if (Devirtualizer::do_metadata(closure)) { + Devirtualizer::do_klass(closure, a->klass()); + } + + oop_oop_iterate_elements_bounded(a, closure, mr.start(), mr.end()); +} + +// Like oop_oop_iterate but only iterates over a specified range and only used +// for objArrayOops. +template +void RefArrayKlass::oop_oop_iterate_range(refArrayOop a, + OopClosureType *closure, int start, + int end) { + T *low = (T *)a->base() + start; + T *high = (T *)a->base() + end; + + oop_oop_iterate_elements_bounded(a, closure, low, high); +} + +// Placed here to resolve include cycle between objArrayKlass.inline.hpp and +// objArrayOop.inline.hpp +template +void refArrayOopDesc::oop_iterate_range(OopClosureType *blk, int start, + int end) { + if (UseCompressedOops) { + ((RefArrayKlass *)klass()) + ->oop_oop_iterate_range(this, blk, start, end); + } else { + ((RefArrayKlass *)klass()) + ->oop_oop_iterate_range(this, blk, start, end); + } +} + +#endif // SHARE_OOPS_REFARRAYKLASS_INLINE_HPP diff --git a/src/hotspot/share/oops/refArrayOop.cpp b/src/hotspot/share/oops/refArrayOop.cpp new file mode 100644 index 00000000000..6de815d4704 --- /dev/null +++ b/src/hotspot/share/oops/refArrayOop.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "oops/access.inline.hpp" +#include "oops/oop.inline.hpp" +#include "oops/refArrayKlass.hpp" +#include "oops/refArrayOop.inline.hpp" + +oop refArrayOopDesc::replace_if_null(int index, oop exchange_value) { + ptrdiff_t offs; + if (UseCompressedOops) { + offs = refArrayOopDesc::obj_at_offset(index); + } else { + offs = refArrayOopDesc::obj_at_offset(index); + } + return HeapAccess::oop_atomic_cmpxchg_at(as_oop(), offs, (oop)nullptr, exchange_value); +} + +Klass* refArrayOopDesc::element_klass() { + return RefArrayKlass::cast(klass())->element_klass(); +} diff --git a/src/hotspot/share/oops/refArrayOop.hpp b/src/hotspot/share/oops/refArrayOop.hpp new file mode 100644 index 00000000000..f48cd3d47c8 --- /dev/null +++ b/src/hotspot/share/oops/refArrayOop.hpp @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_OOPS_REFARRAYOOP_HPP +#define SHARE_OOPS_REFARRAYOOP_HPP + +#include "oops/arrayOop.hpp" +#include "utilities/align.hpp" + +#include + +class Klass; + +// An refArrayOop is an array containing references (oops). +// Evaluating "String arg[10]" will create an refArrayOop. + +class refArrayOopDesc : public arrayOopDesc { + friend class ArchiveHeapWriter; + friend class RefArrayKlass; + friend class Runtime1; + friend class psPromotionManager; + friend class CSetMarkWordClosure; + friend class Continuation; + template + friend class RawOopWriter; + + template T* obj_at_addr(int index) const; + + template + static ptrdiff_t obj_at_offset(int index) { + return base_offset_in_bytes() + sizeof(T) * index; + } + + public: + // Returns the offset of the first element. + static int base_offset_in_bytes() { + return arrayOopDesc::base_offset_in_bytes(T_OBJECT); + } + + inline static refArrayOop cast(oop o); + + // base is the address following the header. + inline HeapWord* base() const; + + // Accessing + oop obj_at(int index) const; + oop obj_at(int index, TRAPS) const; + + void obj_at_put(int index, oop value); + void obj_at_put(int index, oop value, TRAPS); + + oop replace_if_null(int index, oop exchange_value); + + // Sizing + size_t object_size() { return object_size(length()); } + + static size_t object_size(int length) { + // This returns the object size in HeapWords. + size_t asz = (size_t)length * heapOopSize; + size_t size_words = heap_word_size(base_offset_in_bytes() + asz); + size_t osz = align_object_size(size_words); + assert(osz < max_jint, "no overflow"); + return osz; + } + + Klass* element_klass(); + +public: + // special iterators for index ranges, returns size of object + template + void oop_iterate_range(OopClosureType* blk, int start, int end); +}; + +// See similar requirement for oopDesc. +static_assert(std::is_trivially_default_constructible::value, "required"); + +#endif // SHARE_OOPS_REFARRAYOOP_HPP diff --git a/src/hotspot/share/oops/refArrayOop.inline.hpp b/src/hotspot/share/oops/refArrayOop.inline.hpp new file mode 100644 index 00000000000..f513edc0a2c --- /dev/null +++ b/src/hotspot/share/oops/refArrayOop.inline.hpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2025, and/or its affiliates. All rights reserved. + * 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_OOPS_REFARRAYOOP_INLINE_HPP +#define SHARE_OOPS_REFARRAYOOP_INLINE_HPP + +#include "oops/refArrayOop.hpp" + +#include "oops/access.hpp" +#include "oops/arrayOop.hpp" +#include "oops/oop.inline.hpp" +#include "runtime/globals.hpp" + +inline HeapWord *refArrayOopDesc::base() const { + return (HeapWord *)arrayOopDesc::base(T_OBJECT); +} + +inline refArrayOop refArrayOopDesc::cast(oop o) { + assert(o->is_refArray(), "Must be a refArray"); + return (refArrayOop)o; +} + +template T *refArrayOopDesc::obj_at_addr(int index) const { + assert(is_within_bounds(index), "index %d out of bounds %d", index, length()); + return &((T *)base())[index]; +} + +inline oop refArrayOopDesc::obj_at(int index) const { + assert(is_within_bounds(index), "index %d out of bounds %d", index, length()); + ptrdiff_t offset = UseCompressedOops ? obj_at_offset(index) + : obj_at_offset(index); + return HeapAccess::oop_load_at(as_oop(), offset); +} + +inline oop refArrayOopDesc::obj_at(int index, TRAPS) const { + return obj_at(index); +} + +inline void refArrayOopDesc::obj_at_put(int index, oop value) { + assert(is_within_bounds(index), "index %d out of bounds %d", index, length()); + assert(!is_null_free_array() || value != nullptr, "Trying to write null to a null free array"); + ptrdiff_t offset = UseCompressedOops ? obj_at_offset(index) + : obj_at_offset(index); + HeapAccess::oop_store_at(as_oop(), offset, value); +} + +inline void refArrayOopDesc::obj_at_put(int index, oop value, TRAPS) { + if (is_null_free_array() && value == nullptr) { + THROW_MSG(vmSymbols::java_lang_NullPointerException(), "Cannot store null in a null-restricted array"); + } + obj_at_put(index, value); +} + +#endif // SHARE_OOPS_REFARRAYOOP_INLINE_HPP diff --git a/src/hotspot/share/oops/resolvedFieldEntry.cpp b/src/hotspot/share/oops/resolvedFieldEntry.cpp index dd0a81ce0f3..b70b3551a5b 100644 --- a/src/hotspot/share/oops/resolvedFieldEntry.cpp +++ b/src/hotspot/share/oops/resolvedFieldEntry.cpp @@ -23,7 +23,10 @@ */ #include "cds/archiveBuilder.hpp" +#include "interpreter/bytecodes.hpp" +#include "oops/instanceOop.hpp" #include "oops/resolvedFieldEntry.hpp" +#include "utilities/globalDefinitions.hpp" void ResolvedFieldEntry::print_on(outputStream* st) const { st->print_cr("Field Entry:"); @@ -39,10 +42,22 @@ void ResolvedFieldEntry::print_on(outputStream* st) const { st->print_cr(" - TOS: %s", type2name(as_BasicType((TosState)tos_state()))); st->print_cr(" - Is Final: %d", is_final()); st->print_cr(" - Is Volatile: %d", is_volatile()); + st->print_cr(" - Is Flat: %d", is_flat()); + st->print_cr(" - Is Null Free Inline Type: %d", is_null_free_inline_type()); + st->print_cr(" - Has null marker: %d", has_null_marker()); st->print_cr(" - Get Bytecode: %s", Bytecodes::name((Bytecodes::Code)get_code())); st->print_cr(" - Put Bytecode: %s", Bytecodes::name((Bytecodes::Code)put_code())); } +bool ResolvedFieldEntry::is_valid() const { + return field_holder()->is_instance_klass() && + field_offset() >= instanceOopDesc::base_offset_in_bytes() && field_offset() < 0x7fffffff && + as_BasicType((TosState)tos_state()) != T_ILLEGAL && + _flags < (1 << (max_flag_shift + 1)) && + (get_code() == 0 || get_code() == Bytecodes::_getstatic || get_code() == Bytecodes::_getfield) && + (put_code() == 0 || put_code() == Bytecodes::_putstatic || put_code() == Bytecodes::_putfield); +} + #if INCLUDE_CDS void ResolvedFieldEntry::remove_unshareable_info() { u2 saved_cpool_index = _cpool_index; diff --git a/src/hotspot/share/oops/resolvedFieldEntry.hpp b/src/hotspot/share/oops/resolvedFieldEntry.hpp index 1e89d10ab0c..25cf807abe2 100644 --- a/src/hotspot/share/oops/resolvedFieldEntry.hpp +++ b/src/hotspot/share/oops/resolvedFieldEntry.hpp @@ -52,7 +52,7 @@ class ResolvedFieldEntry { u2 _field_index; // Index into field information in holder InstanceKlass u2 _cpool_index; // Constant pool index u1 _tos_state; // TOS state - u1 _flags; // Flags: [0000|00|is_final|is_volatile] + u1 _flags; // Flags: [000|has_null_marker|is_null_free_inline_type|is_flat|is_final|is_volatile] u1 _get_code, _put_code; // Get and Put bytecodes of the field void copy_from(const ResolvedFieldEntry& other) { @@ -122,6 +122,10 @@ class ResolvedFieldEntry { enum { is_volatile_shift = 0, is_final_shift = 1, // unused + is_flat_shift = 2, + is_null_free_inline_type_shift = 3, + has_null_marker_shift = 4, + max_flag_shift = has_null_marker_shift }; // Getters @@ -134,6 +138,9 @@ class ResolvedFieldEntry { u1 put_code() const { return Atomic::load_acquire(&_put_code); } bool is_final() const { return (_flags & (1 << is_final_shift)) != 0; } bool is_volatile () const { return (_flags & (1 << is_volatile_shift)) != 0; } + bool is_flat() const { return (_flags & (1 << is_flat_shift)) != 0; } + bool is_null_free_inline_type() const { return (_flags & (1 << is_null_free_inline_type_shift)) != 0; } + bool has_null_marker() const { return (_flags & (1 << has_null_marker_shift)) != 0; } bool is_resolved(Bytecodes::Code code) const { switch(code) { case Bytecodes::_getstatic: @@ -151,11 +158,18 @@ class ResolvedFieldEntry { // Printing void print_on(outputStream* st) const; - void set_flags(bool is_final_flag, bool is_volatile_flag) { - int new_flags = (is_final_flag << is_final_shift) | static_cast(is_volatile_flag); + void set_flags(bool is_final_flag, bool is_volatile_flag, bool is_flat_flag, bool is_null_free_inline_type_flag, + bool has_null_marker_flag) { + u1 new_flags = ((is_final_flag ? 1 : 0) << is_final_shift) | static_cast(is_volatile_flag) | + ((is_flat_flag ? 1 : 0) << is_flat_shift) | + ((is_null_free_inline_type_flag ? 1 : 0) << is_null_free_inline_type_shift) | + ((has_null_marker_flag ? 1 : 0) << has_null_marker_shift); _flags = checked_cast(new_flags); assert(is_final() == is_final_flag, "Must be"); assert(is_volatile() == is_volatile_flag, "Must be"); + assert(is_flat() == is_flat_flag, "Must be"); + assert(is_null_free_inline_type() == is_null_free_inline_type_flag, "Must be"); + assert(has_null_marker() == has_null_marker_flag, "Must be"); } inline void set_bytecode(u1* code, u1 new_code) { @@ -167,7 +181,7 @@ class ResolvedFieldEntry { Atomic::release_store(code, new_code); } - // Populate the strucutre with resolution information + // Populate the structure with resolution information void fill_in(InstanceKlass* klass, int offset, u2 index, u1 tos_state, u1 b1, u1 b2) { _field_holder = klass; _field_offset = offset; @@ -177,6 +191,7 @@ class ResolvedFieldEntry { // These must be set after the other fields set_bytecode(&_get_code, b1); set_bytecode(&_put_code, b2); + assert(is_valid(), "invalid"); } // CDS @@ -194,6 +209,8 @@ class ResolvedFieldEntry { static ByteSize type_offset() { return byte_offset_of(ResolvedFieldEntry, _tos_state); } static ByteSize flags_offset() { return byte_offset_of(ResolvedFieldEntry, _flags); } + // Debug help + bool is_valid() const; }; #endif //SHARE_OOPS_RESOLVEDFIELDENTRY_HPP diff --git a/src/hotspot/share/oops/symbol.cpp b/src/hotspot/share/oops/symbol.cpp index 0f2cd8e6e6b..a6ce17c8d63 100644 --- a/src/hotspot/share/oops/symbol.cpp +++ b/src/hotspot/share/oops/symbol.cpp @@ -85,6 +85,46 @@ void Symbol::set_permanent() { } #endif +Symbol* Symbol::fundamental_name(TRAPS) { + if (char_at(0) == JVM_SIGNATURE_CLASS && ends_with(JVM_SIGNATURE_ENDCLASS)) { + return SymbolTable::new_symbol(this, 1, utf8_length() - 1); + } else { + // reference count is incremented to be consistent with the behavior with + // the SymbolTable::new_symbol() call above + this->increment_refcount(); + return this; + } +} + +bool Symbol::is_same_fundamental_type(Symbol* s) const { + if (this == s) return true; + if (utf8_length() < 3) return false; + int offset1, offset2, len; + if (ends_with(JVM_SIGNATURE_ENDCLASS)) { + if (char_at(0) != JVM_SIGNATURE_CLASS) return false; + offset1 = 1; + len = utf8_length() - 2; + } else { + offset1 = 0; + len = utf8_length(); + } + if (ends_with(JVM_SIGNATURE_ENDCLASS)) { + if (s->char_at(0) != JVM_SIGNATURE_CLASS) return false; + offset2 = 1; + } else { + offset2 = 0; + } + if ((offset2 + len) > s->utf8_length()) return false; + if ((utf8_length() - offset1 * 2) != (s->utf8_length() - offset2 * 2)) + return false; + int l = len; + while (l-- > 0) { + if (char_at(offset1 + l) != s->char_at(offset2 + l)) + return false; + } + return true; +} + // ------------------------------------------------------------------ // Symbol::index_of // diff --git a/src/hotspot/share/oops/symbol.hpp b/src/hotspot/share/oops/symbol.hpp index 045332c7a84..e4355afe64a 100644 --- a/src/hotspot/share/oops/symbol.hpp +++ b/src/hotspot/share/oops/symbol.hpp @@ -240,6 +240,15 @@ class Symbol : public MetaspaceObj { return code_byte == char_at(position); } + // True if this is a descriptor for a method with void return. + // (Assumes it is a valid descriptor.) + bool is_void_method_signature() const { + return starts_with('(') && ends_with('V'); + } + + Symbol* fundamental_name(TRAPS); + bool is_same_fundamental_type(Symbol*) const; + // Test if the symbol has the give substring at or after the i-th char. int index_of_at(int i, const char* substr, int substr_len) const; diff --git a/src/hotspot/share/oops/typeArrayKlass.cpp b/src/hotspot/share/oops/typeArrayKlass.cpp index bdf37c7db49..baf479e906d 100644 --- a/src/hotspot/share/oops/typeArrayKlass.cpp +++ b/src/hotspot/share/oops/typeArrayKlass.cpp @@ -38,6 +38,7 @@ #include "oops/oop.inline.hpp" #include "oops/typeArrayKlass.inline.hpp" #include "oops/typeArrayOop.inline.hpp" +#include "runtime/arguments.hpp" #include "runtime/handles.inline.hpp" #include "utilities/macros.hpp" @@ -75,10 +76,13 @@ TypeArrayKlass* TypeArrayKlass::allocate_klass(ClassLoaderData* loader_data, Bas } u2 TypeArrayKlass::compute_modifier_flags() const { - return JVM_ACC_ABSTRACT | JVM_ACC_FINAL | JVM_ACC_PUBLIC; + u2 identity_flag = (Arguments::enable_preview()) ? JVM_ACC_IDENTITY : 0; + + return JVM_ACC_ABSTRACT | JVM_ACC_FINAL | JVM_ACC_PUBLIC + | identity_flag; } -TypeArrayKlass::TypeArrayKlass(BasicType type, Symbol* name) : ArrayKlass(name, Kind) { +TypeArrayKlass::TypeArrayKlass(BasicType type, Symbol* name) : ArrayKlass(name, Kind, ArrayKlass::ArrayProperties::DEFAULT) { set_layout_helper(array_layout_helper(type)); assert(is_array_klass(), "sanity"); assert(is_typeArray_klass(), "sanity"); @@ -98,7 +102,6 @@ typeArrayOop TypeArrayKlass::allocate_common(int length, bool do_zero, TRAPS) { } oop TypeArrayKlass::multi_allocate(int rank, jint* last_size, TRAPS) { - // For typeArrays this is only called for the last dimension assert(rank == 1, "just checking"); int length = *last_size; return allocate_instance(length, THREAD); diff --git a/src/hotspot/share/oops/typeArrayOop.hpp b/src/hotspot/share/oops/typeArrayOop.hpp index c96001e9363..ca19f2a6ce6 100644 --- a/src/hotspot/share/oops/typeArrayOop.hpp +++ b/src/hotspot/share/oops/typeArrayOop.hpp @@ -116,7 +116,6 @@ class typeArrayOopDesc : public arrayOopDesc { // Returns the number of words necessary to hold an array of "len" // elements each of the given "byte_size". - private: static size_t object_size(int lh, int length) { int instance_header_size = Klass::layout_helper_header_size(lh); int element_shift = Klass::layout_helper_log2_element_size(lh); diff --git a/src/hotspot/share/opto/addnode.cpp b/src/hotspot/share/opto/addnode.cpp index 92fb54a3a13..0464066061b 100644 --- a/src/hotspot/share/opto/addnode.cpp +++ b/src/hotspot/share/opto/addnode.cpp @@ -693,6 +693,12 @@ const Type *AddPNode::bottom_type() const { if (tx->is_con()) { // Left input is an add of a constant? txoffset = tx->get_con(); } + if (tp->isa_aryptr()) { + // In the case of a flat inline type array, each field has its + // own slice so we need to extract the field being accessed from + // the address computation + return tp->is_aryptr()->add_field_offset_and_offset(txoffset); + } return tp->add_offset(txoffset); } @@ -713,6 +719,12 @@ const Type* AddPNode::Value(PhaseGVN* phase) const { if (p2->is_con()) { // Left input is an add of a constant? p2offset = p2->get_con(); } + if (p1->isa_aryptr()) { + // In the case of a flat inline type array, each field has its + // own slice so we need to extract the field being accessed from + // the address computation + return p1->is_aryptr()->add_field_offset_and_offset(p2offset); + } return p1->add_offset(p2offset); } diff --git a/src/hotspot/share/opto/arraycopynode.cpp b/src/hotspot/share/opto/arraycopynode.cpp index 85b6bd21aec..359040e2b01 100644 --- a/src/hotspot/share/opto/arraycopynode.cpp +++ b/src/hotspot/share/opto/arraycopynode.cpp @@ -22,12 +22,14 @@ * */ +#include "ci/ciFlatArrayKlass.hpp" #include "gc/shared/barrierSet.hpp" #include "gc/shared/c2/barrierSetC2.hpp" #include "gc/shared/c2/cardTableBarrierSetC2.hpp" #include "gc/shared/gc_globals.hpp" #include "opto/arraycopynode.hpp" #include "opto/graphKit.hpp" +#include "opto/inlinetypenode.hpp" #include "runtime/sharedRuntime.hpp" #include "utilities/macros.hpp" #include "utilities/powerOfTwo.hpp" @@ -115,10 +117,14 @@ intptr_t ArrayCopyNode::get_length_if_constant(PhaseGVN *phase) const { } int ArrayCopyNode::get_count(PhaseGVN *phase) const { - Node* src = in(ArrayCopyNode::Src); - const Type* src_type = phase->type(src); - if (is_clonebasic()) { + Node* src = in(ArrayCopyNode::Src); + const Type* src_type = phase->type(src); + + if (src_type == Type::TOP) { + return -1; + } + if (src_type->isa_instptr()) { const TypeInstPtr* inst_src = src_type->is_instptr(); ciInstanceKlass* ik = inst_src->instance_klass(); @@ -141,6 +147,7 @@ int ArrayCopyNode::get_count(PhaseGVN *phase) const { // 3 or 4 elements) might lead to the same length input // (e.g. 2 double-words). assert(!ary_src->size()->is_con() || (get_length_if_constant(phase) >= 0) || + (UseArrayFlattening && ary_src->elem()->make_oopptr() != nullptr && ary_src->elem()->make_oopptr()->can_be_inline_type()) || phase->is_IterGVN() || phase->C->inlining_incrementally() || StressReflectiveCode, "inconsistent"); if (ary_src->size()->is_con()) { return ary_src->size()->get_con(); @@ -194,6 +201,7 @@ Node* ArrayCopyNode::try_clone_instance(PhaseGVN *phase, bool can_reshape, int c } MergeMemNode* mem = phase->transform(MergeMemNode::make(in_mem))->as_MergeMem(); + phase->record_for_igvn(mem); if (can_reshape) { phase->is_IterGVN()->_worklist.push(mem); } @@ -284,22 +292,31 @@ bool ArrayCopyNode::prepare_array_copy(PhaseGVN *phase, bool can_reshape, if (is_reference_type(src_elem, true)) src_elem = T_OBJECT; if (is_reference_type(dest_elem, true)) dest_elem = T_OBJECT; - if (src_elem != dest_elem || dest_elem == T_VOID) { + // TODO 8350865 What about atomicity? + if (src_elem != dest_elem || ary_src->is_null_free() != ary_dest->is_null_free() || ary_src->is_flat() != ary_dest->is_flat() || dest_elem == T_VOID) { // We don't know if arguments are arrays of the same type return false; } BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); - if (bs->array_copy_requires_gc_barriers(is_alloc_tightly_coupled(), dest_elem, false, false, BarrierSetC2::Optimization)) { - // It's an object array copy but we can't emit the card marking - // that is needed + if ((!ary_dest->is_flat() && bs->array_copy_requires_gc_barriers(is_alloc_tightly_coupled(), dest_elem, false, false, BarrierSetC2::Optimization)) || + (ary_dest->is_flat() && ary_src->elem()->inline_klass()->contains_oops() && + bs->array_copy_requires_gc_barriers(is_alloc_tightly_coupled(), T_OBJECT, false, false, BarrierSetC2::Optimization))) { + // It's an object array copy but we can't emit the card marking that is needed return false; } value_type = ary_src->elem(); uint shift = exact_log2(type2aelembytes(dest_elem)); - uint header = arrayOopDesc::base_offset_in_bytes(dest_elem); + if (ary_dest->is_flat()) { + assert(ary_src->is_flat(), "src and dest must be flat"); + shift = ary_src->flat_log_elem_size(); + src_elem = T_FLAT_ELEMENT; + dest_elem = T_FLAT_ELEMENT; + } + + const uint header = arrayOopDesc::base_offset_in_bytes(dest_elem); src_offset = Compile::conv_I2X_index(phase, src_offset, ary_src->size()); if (src_offset->is_top()) { @@ -338,13 +355,21 @@ bool ArrayCopyNode::prepare_array_copy(PhaseGVN *phase, bool can_reshape, disjoint_bases = true; + if (ary_src->elem()->make_oopptr() != nullptr && + ary_src->elem()->make_oopptr()->can_be_inline_type()) { + return false; + } + BasicType elem = ary_src->isa_aryptr()->elem()->array_element_basic_type(); if (is_reference_type(elem, true)) { elem = T_OBJECT; } BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); - if (bs->array_copy_requires_gc_barriers(true, elem, true, is_clone_inst(), BarrierSetC2::Optimization)) { + if ((!ary_src->is_flat() && bs->array_copy_requires_gc_barriers(true, elem, true, is_clone_inst(), BarrierSetC2::Optimization)) || + (ary_src->is_flat() && ary_src->elem()->inline_klass()->contains_oops() && + bs->array_copy_requires_gc_barriers(true, T_OBJECT, true, is_clone_inst(), BarrierSetC2::Optimization))) { + // It's an object array copy but we can't emit the card marking that is needed return false; } @@ -368,109 +393,124 @@ bool ArrayCopyNode::prepare_array_copy(PhaseGVN *phase, bool can_reshape, return true; } -const TypePtr* ArrayCopyNode::get_address_type(PhaseGVN* phase, const TypePtr* atp, Node* n) { +const TypeAryPtr* ArrayCopyNode::get_address_type(PhaseGVN* phase, const TypePtr* atp, Node* n) { if (atp == TypeOopPtr::BOTTOM) { atp = phase->type(n)->isa_ptr(); } // adjust atp to be the correct array element address type - return atp->add_offset(Type::OffsetBot); + return atp->add_offset(Type::OffsetBot)->is_aryptr(); } -void ArrayCopyNode::array_copy_test_overlap(PhaseGVN *phase, bool can_reshape, bool disjoint_bases, int count, Node*& forward_ctl, Node*& backward_ctl) { - Node* ctl = in(TypeFunc::Control); +void ArrayCopyNode::array_copy_test_overlap(GraphKit& kit, bool disjoint_bases, int count, Node*& backward_ctl) { + Node* ctl = kit.control(); if (!disjoint_bases && count > 1) { + PhaseGVN& gvn = kit.gvn(); Node* src_offset = in(ArrayCopyNode::SrcPos); Node* dest_offset = in(ArrayCopyNode::DestPos); assert(src_offset != nullptr && dest_offset != nullptr, "should be"); - Node* cmp = phase->transform(new CmpINode(src_offset, dest_offset)); - Node *bol = phase->transform(new BoolNode(cmp, BoolTest::lt)); + Node* cmp = gvn.transform(new CmpINode(src_offset, dest_offset)); + Node *bol = gvn.transform(new BoolNode(cmp, BoolTest::lt)); IfNode *iff = new IfNode(ctl, bol, PROB_FAIR, COUNT_UNKNOWN); - phase->transform(iff); + gvn.transform(iff); - forward_ctl = phase->transform(new IfFalseNode(iff)); - backward_ctl = phase->transform(new IfTrueNode(iff)); + kit.set_control(gvn.transform(new IfFalseNode(iff))); + backward_ctl = gvn.transform(new IfTrueNode(iff)); + } +} + +void ArrayCopyNode::copy(GraphKit& kit, + const TypeAryPtr* atp_src, + const TypeAryPtr* atp_dest, + int i, + Node* base_src, + Node* base_dest, + Node* adr_src, + Node* adr_dest, + BasicType copy_type, + const Type* value_type) { + BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); + Node* ctl = kit.control(); + if (atp_dest->is_flat()) { + ciInlineKlass* vk = atp_src->elem()->inline_klass(); + for (int j = 0; j < vk->nof_nonstatic_fields(); j++) { + ciField* field = vk->nonstatic_field_at(j); + int off_in_vt = field->offset_in_bytes() - vk->payload_offset(); + Node* off = kit.MakeConX(off_in_vt + i * atp_src->flat_elem_size()); + ciType* ft = field->type(); + BasicType bt = type2field[ft->basic_type()]; + assert(!field->is_flat(), "flat field encountered"); + const Type* rt = Type::get_const_type(ft); + const TypePtr* adr_type = atp_src->with_field_offset(off_in_vt)->add_offset(Type::OffsetBot); + assert(!bs->array_copy_requires_gc_barriers(is_alloc_tightly_coupled(), bt, false, false, BarrierSetC2::Optimization), "GC barriers required"); + Node* next_src = kit.gvn().transform(new AddPNode(base_src, adr_src, off)); + Node* next_dest = kit.gvn().transform(new AddPNode(base_dest, adr_dest, off)); + Node* v = load(bs, &kit.gvn(), ctl, kit.merged_memory(), next_src, adr_type, rt, bt); + store(bs, &kit.gvn(), ctl, kit.merged_memory(), next_dest, adr_type, v, rt, bt); + } } else { - forward_ctl = ctl; + Node* off = kit.MakeConX(type2aelembytes(copy_type) * i); + Node* next_src = kit.gvn().transform(new AddPNode(base_src, adr_src, off)); + Node* next_dest = kit.gvn().transform(new AddPNode(base_dest, adr_dest, off)); + Node* v = load(bs, &kit.gvn(), ctl, kit.merged_memory(), next_src, atp_src, value_type, copy_type); + store(bs, &kit.gvn(), ctl, kit.merged_memory(), next_dest, atp_dest, v, value_type, copy_type); } + kit.set_control(ctl); } -Node* ArrayCopyNode::array_copy_forward(PhaseGVN *phase, - bool can_reshape, - Node*& forward_ctl, - Node* mem, - const TypePtr* atp_src, - const TypePtr* atp_dest, - Node* adr_src, - Node* base_src, - Node* adr_dest, - Node* base_dest, - BasicType copy_type, - const Type* value_type, - int count) { - if (!forward_ctl->is_top()) { - // copy forward - MergeMemNode* mm = MergeMemNode::make(mem); +void ArrayCopyNode::array_copy_forward(GraphKit& kit, + bool can_reshape, + const TypeAryPtr* atp_src, + const TypeAryPtr* atp_dest, + Node* adr_src, + Node* base_src, + Node* adr_dest, + Node* base_dest, + BasicType copy_type, + const Type* value_type, + int count) { + if (!kit.stopped()) { + // copy forward if (count > 0) { - BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); - Node* v = load(bs, phase, forward_ctl, mm, adr_src, atp_src, value_type, copy_type); - store(bs, phase, forward_ctl, mm, adr_dest, atp_dest, v, value_type, copy_type); - for (int i = 1; i < count; i++) { - Node* off = phase->MakeConX(type2aelembytes(copy_type) * i); - Node* next_src = phase->transform(new AddPNode(base_src,adr_src,off)); - Node* next_dest = phase->transform(new AddPNode(base_dest,adr_dest,off)); - v = load(bs, phase, forward_ctl, mm, next_src, atp_src, value_type, copy_type); - store(bs, phase, forward_ctl, mm, next_dest, atp_dest, v, value_type, copy_type); + for (int i = 0; i < count; i++) { + copy(kit, atp_src, atp_dest, i, base_src, base_dest, adr_src, adr_dest, copy_type, value_type); } } else if (can_reshape) { - PhaseIterGVN* igvn = phase->is_IterGVN(); - igvn->_worklist.push(adr_src); - igvn->_worklist.push(adr_dest); + PhaseGVN& gvn = kit.gvn(); + assert(gvn.is_IterGVN(), ""); + gvn.record_for_igvn(adr_src); + gvn.record_for_igvn(adr_dest); } - return mm; } - return phase->C->top(); } -Node* ArrayCopyNode::array_copy_backward(PhaseGVN *phase, - bool can_reshape, - Node*& backward_ctl, - Node* mem, - const TypePtr* atp_src, - const TypePtr* atp_dest, - Node* adr_src, - Node* base_src, - Node* adr_dest, - Node* base_dest, - BasicType copy_type, - const Type* value_type, - int count) { - if (!backward_ctl->is_top()) { +void ArrayCopyNode::array_copy_backward(GraphKit& kit, + bool can_reshape, + const TypeAryPtr* atp_src, + const TypeAryPtr* atp_dest, + Node* adr_src, + Node* base_src, + Node* adr_dest, + Node* base_dest, + BasicType copy_type, + const Type* value_type, + int count) { + if (!kit.stopped()) { // copy backward - MergeMemNode* mm = MergeMemNode::make(mem); - - BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); - assert(copy_type != T_OBJECT || !bs->array_copy_requires_gc_barriers(false, T_OBJECT, false, false, BarrierSetC2::Optimization), "only tightly coupled allocations for object arrays"); + PhaseGVN& gvn = kit.gvn(); if (count > 0) { - for (int i = count-1; i >= 1; i--) { - Node* off = phase->MakeConX(type2aelembytes(copy_type) * i); - Node* next_src = phase->transform(new AddPNode(base_src,adr_src,off)); - Node* next_dest = phase->transform(new AddPNode(base_dest,adr_dest,off)); - Node* v = load(bs, phase, backward_ctl, mm, next_src, atp_src, value_type, copy_type); - store(bs, phase, backward_ctl, mm, next_dest, atp_dest, v, value_type, copy_type); + for (int i = count-1; i >= 0; i--) { + copy(kit, atp_src, atp_dest, i, base_src, base_dest, adr_src, adr_dest, copy_type, value_type); } - Node* v = load(bs, phase, backward_ctl, mm, adr_src, atp_src, value_type, copy_type); - store(bs, phase, backward_ctl, mm, adr_dest, atp_dest, v, value_type, copy_type); - } else if (can_reshape) { - PhaseIterGVN* igvn = phase->is_IterGVN(); - igvn->_worklist.push(adr_src); - igvn->_worklist.push(adr_dest); + } else if(can_reshape) { + PhaseGVN& gvn = kit.gvn(); + assert(gvn.is_IterGVN(), ""); + gvn.record_for_igvn(adr_src); + gvn.record_for_igvn(adr_dest); } - return phase->transform(mm); } - return phase->C->top(); } bool ArrayCopyNode::finish_transform(PhaseGVN *phase, bool can_reshape, @@ -495,17 +535,16 @@ bool ArrayCopyNode::finish_transform(PhaseGVN *phase, bool can_reshape, } else { // replace fallthrough projections of the ArrayCopyNode by the // new memory, control and the input IO. - CallProjections callprojs; - extract_projections(&callprojs, true, false); + CallProjections* callprojs = extract_projections(true, false); - if (callprojs.fallthrough_ioproj != nullptr) { - igvn->replace_node(callprojs.fallthrough_ioproj, in(TypeFunc::I_O)); + if (callprojs->fallthrough_ioproj != nullptr) { + igvn->replace_node(callprojs->fallthrough_ioproj, in(TypeFunc::I_O)); } - if (callprojs.fallthrough_memproj != nullptr) { - igvn->replace_node(callprojs.fallthrough_memproj, mem); + if (callprojs->fallthrough_memproj != nullptr) { + igvn->replace_node(callprojs->fallthrough_memproj, mem); } - if (callprojs.fallthrough_catchproj != nullptr) { - igvn->replace_node(callprojs.fallthrough_catchproj, ctl); + if (callprojs->fallthrough_catchproj != nullptr) { + igvn->replace_node(callprojs->fallthrough_catchproj, ctl); } // The ArrayCopyNode is not disconnected. It still has the @@ -530,7 +569,11 @@ bool ArrayCopyNode::finish_transform(PhaseGVN *phase, bool can_reshape, Node *ArrayCopyNode::Ideal(PhaseGVN *phase, bool can_reshape) { - if (remove_dead_region(phase, can_reshape)) return this; + // Perform any generic optimizations first + Node* result = SafePointNode::Ideal(phase, can_reshape); + if (result != nullptr) { + return result; + } if (StressArrayCopyMacroNode && !can_reshape) { phase->record_for_igvn(this); @@ -572,6 +615,17 @@ Node *ArrayCopyNode::Ideal(PhaseGVN *phase, bool can_reshape) { return nullptr; } + Node* src = in(ArrayCopyNode::Src); + Node* dest = in(ArrayCopyNode::Dest); + const Type* src_type = phase->type(src); + const Type* dest_type = phase->type(dest); + + if (src_type->isa_aryptr() && dest_type->isa_instptr()) { + // clone used for load of unknown inline type can't be optimized at + // this point + return nullptr; + } + Node* mem = try_clone_instance(phase, can_reshape, count); if (mem != nullptr) { return (mem == NodeSentinel) ? nullptr : mem; @@ -593,58 +647,77 @@ Node *ArrayCopyNode::Ideal(PhaseGVN *phase, bool can_reshape) { return nullptr; } - Node* src = in(ArrayCopyNode::Src); - Node* dest = in(ArrayCopyNode::Dest); - const TypePtr* atp_src = get_address_type(phase, _src_type, src); - const TypePtr* atp_dest = get_address_type(phase, _dest_type, dest); - Node* in_mem = in(TypeFunc::Memory); + JVMState* new_jvms = nullptr; + SafePointNode* new_map = nullptr; + if (!is_clonebasic()) { + new_jvms = jvms()->clone_shallow(phase->C); + new_map = new SafePointNode(req(), new_jvms); + for (uint i = TypeFunc::FramePtr; i < req(); i++) { + new_map->init_req(i, in(i)); + } + new_jvms->set_map(new_map); + } else { + new_jvms = new (phase->C) JVMState(0); + new_map = new SafePointNode(TypeFunc::Parms, new_jvms); + new_jvms->set_map(new_map); + } + new_map->set_control(in(TypeFunc::Control)); + new_map->set_memory(MergeMemNode::make(in(TypeFunc::Memory))); + new_map->set_i_o(in(TypeFunc::I_O)); + phase->record_for_igvn(new_map); + + const TypeAryPtr* atp_src = get_address_type(phase, _src_type, src); + const TypeAryPtr* atp_dest = get_address_type(phase, _dest_type, dest); if (can_reshape) { assert(!phase->is_IterGVN()->delay_transform(), "cannot delay transforms"); phase->is_IterGVN()->set_delay_transform(true); } + GraphKit kit(new_jvms, phase); + + SafePointNode* backward_map = nullptr; + SafePointNode* forward_map = nullptr; Node* backward_ctl = phase->C->top(); - Node* forward_ctl = phase->C->top(); - array_copy_test_overlap(phase, can_reshape, disjoint_bases, count, forward_ctl, backward_ctl); - - Node* forward_mem = array_copy_forward(phase, can_reshape, forward_ctl, - in_mem, - atp_src, atp_dest, - adr_src, base_src, adr_dest, base_dest, - copy_type, value_type, count); - - Node* backward_mem = array_copy_backward(phase, can_reshape, backward_ctl, - in_mem, - atp_src, atp_dest, - adr_src, base_src, adr_dest, base_dest, - copy_type, value_type, count); - - Node* ctl = nullptr; - if (!forward_ctl->is_top() && !backward_ctl->is_top()) { - ctl = new RegionNode(3); - ctl->init_req(1, forward_ctl); - ctl->init_req(2, backward_ctl); - ctl = phase->transform(ctl); - MergeMemNode* forward_mm = forward_mem->as_MergeMem(); - MergeMemNode* backward_mm = backward_mem->as_MergeMem(); - for (MergeMemStream mms(forward_mm, backward_mm); mms.next_non_empty2(); ) { - if (mms.memory() != mms.memory2()) { - Node* phi = new PhiNode(ctl, Type::MEMORY, phase->C->get_adr_type(mms.alias_idx())); - phi->init_req(1, mms.memory()); - phi->init_req(2, mms.memory2()); - phi = phase->transform(phi); - mms.set_memory(phi); - } - } - mem = forward_mem; - } else if (!forward_ctl->is_top()) { - ctl = forward_ctl; - mem = forward_mem; + + array_copy_test_overlap(kit, disjoint_bases, count, backward_ctl); + + { + PreserveJVMState pjvms(&kit); + + array_copy_forward(kit, can_reshape, + atp_src, atp_dest, + adr_src, base_src, adr_dest, base_dest, + copy_type, value_type, count); + + forward_map = kit.stop(); + } + + kit.set_control(backward_ctl); + array_copy_backward(kit, can_reshape, + atp_src, atp_dest, + adr_src, base_src, adr_dest, base_dest, + copy_type, value_type, count); + + backward_map = kit.stop(); + + if (!forward_map->control()->is_top() && !backward_map->control()->is_top()) { + assert(forward_map->i_o() == backward_map->i_o(), "need a phi on IO?"); + Node* ctl = new RegionNode(3); + Node* mem = new PhiNode(ctl, Type::MEMORY, TypePtr::BOTTOM); + kit.set_map(forward_map); + ctl->init_req(1, kit.control()); + mem->init_req(1, kit.reset_memory()); + kit.set_map(backward_map); + ctl->init_req(2, kit.control()); + mem->init_req(2, kit.reset_memory()); + kit.set_control(phase->transform(ctl)); + kit.set_all_memory(phase->transform(mem)); + } else if (!forward_map->control()->is_top()) { + kit.set_map(forward_map); } else { - assert(!backward_ctl->is_top(), "no copy?"); - ctl = backward_ctl; - mem = backward_mem; + assert(!backward_map->control()->is_top(), "no copy?"); + kit.set_map(backward_map); } if (can_reshape) { @@ -652,8 +725,11 @@ Node *ArrayCopyNode::Ideal(PhaseGVN *phase, bool can_reshape) { phase->is_IterGVN()->set_delay_transform(false); } - if (!finish_transform(phase, can_reshape, ctl, mem)) { - if (can_reshape) { + mem = kit.map()->memory(); + if (!finish_transform(phase, can_reshape, kit.control(), mem)) { + if (!can_reshape) { + phase->record_for_igvn(this); + } else { // put in worklist, so that if it happens to be dead it is removed phase->is_IterGVN()->_worklist.push(mem); } @@ -750,13 +826,20 @@ bool ArrayCopyNode::modifies(intptr_t offset_lo, intptr_t offset_hi, PhaseValues BasicType ary_elem = ary_t->isa_aryptr()->elem()->array_element_basic_type(); if (is_reference_type(ary_elem, true)) ary_elem = T_OBJECT; - uint header = arrayOopDesc::base_offset_in_bytes(ary_elem); - uint elemsize = type2aelembytes(ary_elem); + uint header; + uint elem_size; + if (ary_t->is_flat()) { + header = arrayOopDesc::base_offset_in_bytes(T_FLAT_ELEMENT); + elem_size = ary_t->flat_elem_size(); + } else { + header = arrayOopDesc::base_offset_in_bytes(ary_elem); + elem_size = type2aelembytes(ary_elem); + } - jlong dest_pos_plus_len_lo = (((jlong)dest_pos_t->_lo) + len_t->_lo) * elemsize + header; - jlong dest_pos_plus_len_hi = (((jlong)dest_pos_t->_hi) + len_t->_hi) * elemsize + header; - jlong dest_pos_lo = ((jlong)dest_pos_t->_lo) * elemsize + header; - jlong dest_pos_hi = ((jlong)dest_pos_t->_hi) * elemsize + header; + jlong dest_pos_plus_len_lo = (((jlong)dest_pos_t->_lo) + len_t->_lo) * elem_size + header; + jlong dest_pos_plus_len_hi = (((jlong)dest_pos_t->_hi) + len_t->_hi) * elem_size + header; + jlong dest_pos_lo = ((jlong)dest_pos_t->_lo) * elem_size + header; + jlong dest_pos_hi = ((jlong)dest_pos_t->_hi) * elem_size + header; if (must_modify) { if (offset_lo >= dest_pos_hi && offset_hi < dest_pos_plus_len_lo) { diff --git a/src/hotspot/share/opto/arraycopynode.hpp b/src/hotspot/share/opto/arraycopynode.hpp index 13e739fc2c7..3a65892b43e 100644 --- a/src/hotspot/share/opto/arraycopynode.hpp +++ b/src/hotspot/share/opto/arraycopynode.hpp @@ -100,27 +100,29 @@ class ArrayCopyNode : public CallNode { intptr_t get_length_if_constant(PhaseGVN *phase) const; int get_count(PhaseGVN *phase) const; - static const TypePtr* get_address_type(PhaseGVN* phase, const TypePtr* atp, Node* n); + static const TypeAryPtr* get_address_type(PhaseGVN* phase, const TypePtr* atp, Node* n); Node* try_clone_instance(PhaseGVN *phase, bool can_reshape, int count); bool prepare_array_copy(PhaseGVN *phase, bool can_reshape, Node*& adr_src, Node*& base_src, Node*& adr_dest, Node*& base_dest, BasicType& copy_type, const Type*& value_type, bool& disjoint_bases); - void array_copy_test_overlap(PhaseGVN *phase, bool can_reshape, + void array_copy_test_overlap(GraphKit& kit, bool disjoint_bases, int count, - Node*& forward_ctl, Node*& backward_ctl); - Node* array_copy_forward(PhaseGVN *phase, bool can_reshape, Node*& ctl, - Node* mem, - const TypePtr* atp_src, const TypePtr* atp_dest, + Node*& backward_ctl); + void array_copy_forward(GraphKit& kit, bool can_reshape, + const TypeAryPtr* atp_src, const TypeAryPtr* atp_dest, + Node* adr_src, Node* base_src, Node* adr_dest, Node* base_dest, + BasicType copy_type, const Type* value_type, int count); + void array_copy_backward(GraphKit& kit, bool can_reshape, + const TypeAryPtr* atp_src, const TypeAryPtr* atp_dest, Node* adr_src, Node* base_src, Node* adr_dest, Node* base_dest, BasicType copy_type, const Type* value_type, int count); - Node* array_copy_backward(PhaseGVN *phase, bool can_reshape, Node*& ctl, - Node* mem, - const TypePtr* atp_src, const TypePtr* atp_dest, - Node* adr_src, Node* base_src, Node* adr_dest, Node* base_dest, - BasicType copy_type, const Type* value_type, int count); bool finish_transform(PhaseGVN *phase, bool can_reshape, Node* ctl, Node *mem); + void copy(GraphKit& kit, const TypeAryPtr* atp_src, const TypeAryPtr* atp_dest, int i, + Node* base_src, Node* base_dest, Node* adr_src, Node* adr_dest, + BasicType copy_type, const Type* value_type); + static bool may_modify_helper(const TypeOopPtr* t_oop, Node* n, PhaseValues* phase, ArrayCopyNode*& ac); public: static Node* load(BarrierSetC2* bs, PhaseGVN *phase, Node*& ctl, MergeMemNode* mem, Node* addr, const TypePtr* adr_type, const Type *type, BasicType bt); diff --git a/src/hotspot/share/opto/buildOopMap.cpp b/src/hotspot/share/opto/buildOopMap.cpp index 675113163e8..b99f8ff66f0 100644 --- a/src/hotspot/share/opto/buildOopMap.cpp +++ b/src/hotspot/share/opto/buildOopMap.cpp @@ -264,12 +264,12 @@ OopMap *OopFlow::build_oop_map( Node *n, int max_reg, PhaseRegAlloc *regalloc, i regalloc->C->record_method_not_compilable(ss.as_string()); continue; } - if( t->is_ptr()->_offset == 0 ) { // Not derived? + if (t->is_ptr()->offset() == 0) { // Not derived? if( mcall ) { // Outgoing argument GC mask responsibility belongs to the callee, // not the caller. Inspect the inputs to the call, to see if // this live-range is one of them. - uint cnt = mcall->tf()->domain()->cnt(); + uint cnt = mcall->tf()->domain_cc()->cnt(); uint j; for( j = TypeFunc::Parms; j < cnt; j++) if( mcall->in(j) == def ) @@ -339,7 +339,7 @@ OopMap *OopFlow::build_oop_map( Node *n, int max_reg, PhaseRegAlloc *regalloc, i // Outgoing argument GC mask responsibility belongs to the callee, // not the caller. Inspect the inputs to the call, to see if // this live-range is one of them. - uint cnt = mcall->tf()->domain()->cnt(); + uint cnt = mcall->tf()->domain_cc()->cnt(); uint j; for( j = TypeFunc::Parms; j < cnt; j++) if( mcall->in(j) == def ) diff --git a/src/hotspot/share/opto/bytecodeInfo.cpp b/src/hotspot/share/opto/bytecodeInfo.cpp index 547cf2f6a38..5e0d0cf5eb0 100644 --- a/src/hotspot/share/opto/bytecodeInfo.cpp +++ b/src/hotspot/share/opto/bytecodeInfo.cpp @@ -85,10 +85,10 @@ static bool is_init_with_ea(ciMethod* callee_method, if (!C->do_escape_analysis() || !EliminateAllocations) { return false; // EA is off } - if (callee_method->is_object_initializer()) { + if (callee_method->is_object_constructor()) { return true; // constructor } - if (caller_method->is_object_initializer() && + if ((caller_method->is_object_constructor() || caller_method->is_class_initializer()) && caller_method != C->method() && caller_method->holder()->is_subclass_of(callee_method->holder())) { return true; // super constructor is called from inlined constructor diff --git a/src/hotspot/share/opto/c2_globals.hpp b/src/hotspot/share/opto/c2_globals.hpp index 0a4f231c49b..79e163ada18 100644 --- a/src/hotspot/share/opto/c2_globals.hpp +++ b/src/hotspot/share/opto/c2_globals.hpp @@ -846,6 +846,12 @@ "profiling data. " \ "Requires UseLoopPredicate to be turned on (default).") \ \ + product(bool, UseArrayLoadStoreProfile, true, \ + "Take advantage of profiling at array load/store") \ + \ + product(bool, UseACmpProfile, true, \ + "Take advantage of profiling at acmp") \ + \ develop(uintx, StressLongCountedLoop, 0, \ "if > 0, convert int counted loops to long counted loops" \ "to stress handling of long counted loops: run inner loop" \ diff --git a/src/hotspot/share/opto/c2compiler.cpp b/src/hotspot/share/opto/c2compiler.cpp index acc28964627..8b29cdb6465 100644 --- a/src/hotspot/share/opto/c2compiler.cpp +++ b/src/hotspot/share/opto/c2compiler.cpp @@ -23,6 +23,7 @@ */ #include "classfile/vmClasses.hpp" +#include "classfile/vmIntrinsics.hpp" #include "compiler/compilationMemoryStatistic.hpp" #include "compiler/compilerDefinitions.inline.hpp" #include "jfr/support/jfrIntrinsics.hpp" @@ -129,7 +130,8 @@ void C2Compiler::compile_method(ciEnv* env, ciMethod* target, int entry_bci, boo bool do_escape_analysis = DoEscapeAnalysis; bool do_iterative_escape_analysis = DoEscapeAnalysis; bool do_reduce_allocation_merges = ReduceAllocationMerges && EliminateAllocations; - bool eliminate_boxing = EliminateAutoBox; + // TODO 8328675 Re-enable + bool eliminate_boxing = false; // EliminateAutoBox; bool do_locks_coarsening = EliminateLocks; bool do_superword = UseSuperWord; @@ -658,6 +660,8 @@ bool C2Compiler::is_intrinsic_supported(vmIntrinsics::ID id) { case vmIntrinsics::_getCharsStringU: case vmIntrinsics::_getCharStringU: case vmIntrinsics::_putCharStringU: + case vmIntrinsics::_makePrivateBuffer: + case vmIntrinsics::_finishPrivateBuffer: case vmIntrinsics::_getReference: case vmIntrinsics::_getBoolean: case vmIntrinsics::_getByte: @@ -667,6 +671,8 @@ bool C2Compiler::is_intrinsic_supported(vmIntrinsics::ID id) { case vmIntrinsics::_getLong: case vmIntrinsics::_getFloat: case vmIntrinsics::_getDouble: + case vmIntrinsics::_getValue: + case vmIntrinsics::_getFlatValue: case vmIntrinsics::_putReference: case vmIntrinsics::_putBoolean: case vmIntrinsics::_putByte: @@ -676,6 +682,8 @@ bool C2Compiler::is_intrinsic_supported(vmIntrinsics::ID id) { case vmIntrinsics::_putLong: case vmIntrinsics::_putFloat: case vmIntrinsics::_putDouble: + case vmIntrinsics::_putValue: + case vmIntrinsics::_putFlatValue: case vmIntrinsics::_getReferenceVolatile: case vmIntrinsics::_getBooleanVolatile: case vmIntrinsics::_getByteVolatile: @@ -759,6 +767,9 @@ bool C2Compiler::is_intrinsic_supported(vmIntrinsics::ID id) { case vmIntrinsics::_allocateInstance: case vmIntrinsics::_allocateUninitializedArray: case vmIntrinsics::_newArray: + case vmIntrinsics::_newNullRestrictedNonAtomicArray: + case vmIntrinsics::_newNullRestrictedAtomicArray: + case vmIntrinsics::_newNullableAtomicArray: case vmIntrinsics::_getLength: case vmIntrinsics::_copyOf: case vmIntrinsics::_copyOfRange: diff --git a/src/hotspot/share/opto/callGenerator.cpp b/src/hotspot/share/opto/callGenerator.cpp index e09d8cabe2c..8033039e137 100644 --- a/src/hotspot/share/opto/callGenerator.cpp +++ b/src/hotspot/share/opto/callGenerator.cpp @@ -29,11 +29,13 @@ #include "ci/ciObjArray.hpp" #include "classfile/javaClasses.hpp" #include "compiler/compileLog.hpp" +#include "oops/accessDecorators.hpp" #include "opto/addnode.hpp" #include "opto/callGenerator.hpp" #include "opto/callnode.hpp" #include "opto/castnode.hpp" #include "opto/cfgnode.hpp" +#include "opto/inlinetypenode.hpp" #include "opto/parse.hpp" #include "opto/rootnode.hpp" #include "opto/runtime.hpp" @@ -118,7 +120,7 @@ class DirectCallGenerator : public CallGenerator { private: CallStaticJavaNode* _call_node; // Force separate memory and I/O projections for the exceptional - // paths to facilitate late inlinig. + // paths to facilitate late inlining. bool _separate_io_proj; protected: @@ -127,8 +129,17 @@ class DirectCallGenerator : public CallGenerator { public: DirectCallGenerator(ciMethod* method, bool separate_io_proj) : CallGenerator(method), + _call_node(nullptr), _separate_io_proj(separate_io_proj) { + if (InlineTypeReturnedAsFields && method->is_method_handle_intrinsic()) { + // If that call has not been optimized by the time optimizations are over, + // we'll need to add a call to create an inline type instance from the klass + // returned by the call (see PhaseMacroExpand::expand_mh_intrinsic_return). + // Separating memory and I/O projections for exceptions is required to + // perform that graph transformation. + _separate_io_proj = true; + } } virtual JVMState* generate(JVMState* jvms); @@ -142,6 +153,7 @@ class DirectCallGenerator : public CallGenerator { JVMState* DirectCallGenerator::generate(JVMState* jvms) { GraphKit kit(jvms); + PhaseGVN& gvn = kit.gvn(); bool is_static = method()->is_static(); address target = is_static ? SharedRuntime::get_resolve_static_call_stub() : SharedRuntime::get_resolve_opt_virtual_call_stub(); @@ -174,7 +186,10 @@ JVMState* DirectCallGenerator::generate(JVMState* jvms) { call->set_method_handle_invoke(true); } } - kit.set_arguments_for_java_call(call); + kit.set_arguments_for_java_call(call, is_late_inline()); + if (kit.stopped()) { + return kit.transfer_exceptions_into_jvms(); + } kit.set_edges_for_java_call(call, false, _separate_io_proj); Node* ret = kit.set_results_for_java_call(call, _separate_io_proj); kit.push_node(method()->return_type()->basic_type(), ret); @@ -215,7 +230,6 @@ class VirtualCallGenerator : public CallGenerator { JVMState* VirtualCallGenerator::generate(JVMState* jvms) { GraphKit kit(jvms); Node* receiver = kit.argument(0); - if (kit.C->log() != nullptr) { kit.C->log()->elem("virtual_call bci='%d'", jvms->bci()); } @@ -273,6 +287,9 @@ JVMState* VirtualCallGenerator::generate(JVMState* jvms) { _call_node = call; // Save the call node in case we need it later kit.set_arguments_for_java_call(call); + if (kit.stopped()) { + return kit.transfer_exceptions_into_jvms(); + } kit.set_edges_for_java_call(call, false /*must_throw*/, _separate_io_proj); Node* ret = kit.set_results_for_java_call(call, _separate_io_proj); kit.push_node(method()->return_type()->basic_type(), ret); @@ -357,6 +374,10 @@ class LateInlineCallGenerator : public DirectCallGenerator { return _unique_id; } + virtual CallGenerator* inline_cg() { + return _inline_cg; + } + virtual CallGenerator* with_call_node(CallNode* call) { LateInlineCallGenerator* cg = new LateInlineCallGenerator(method(), _inline_cg, _is_pure_call); cg->set_call_node(call->as_CallStaticJava()); @@ -418,6 +439,14 @@ bool LateInlineMHCallGenerator::do_late_inline_check(Compile* C, JVMState* jvms) assert(!input_not_const, "sanity"); // shouldn't have been scheduled for inlining in the first place if (cg != nullptr) { + // AlwaysIncrementalInline causes for_method_handle_inline() to + // return a LateInlineCallGenerator. Extract the + // InlineCallGenerator from it. + if (AlwaysIncrementalInline && cg->is_late_inline() && !cg->is_virtual_late_inline()) { + cg = cg->inline_cg(); + assert(cg != nullptr, "inline call generator expected"); + } + if (!allow_inline) { C->inline_printer()->record(cg->method(), call_node()->jvms(), InliningResult::FAILURE, "late method handle call resolution"); @@ -574,9 +603,9 @@ void CallGenerator::do_late_inline_helper() { return; } - const TypeTuple *r = call->tf()->domain(); - for (int i1 = 0; i1 < method()->arg_size(); i1++) { - if (call->in(TypeFunc::Parms + i1)->is_top() && r->field_at(TypeFunc::Parms + i1) != Type::HALF) { + const TypeTuple* r = call->tf()->domain_cc(); + for (uint i1 = TypeFunc::Parms; i1 < r->cnt(); i1++) { + if (call->in(i1)->is_top() && r->field_at(i1) != Type::HALF) { assert(Compile::current()->inlining_incrementally(), "shouldn't happen during parsing"); return; } @@ -594,19 +623,17 @@ void CallGenerator::do_late_inline_helper() { } // check for unreachable loop - CallProjections callprojs; // Similar to incremental inlining, don't assert that all call // projections are still there for post-parse call devirtualization. bool do_asserts = !is_mh_late_inline() && !is_virtual_late_inline(); - call->extract_projections(&callprojs, true, do_asserts); - if ((callprojs.fallthrough_catchproj == call->in(0)) || - (callprojs.catchall_catchproj == call->in(0)) || - (callprojs.fallthrough_memproj == call->in(TypeFunc::Memory)) || - (callprojs.catchall_memproj == call->in(TypeFunc::Memory)) || - (callprojs.fallthrough_ioproj == call->in(TypeFunc::I_O)) || - (callprojs.catchall_ioproj == call->in(TypeFunc::I_O)) || - (callprojs.resproj != nullptr && call->find_edge(callprojs.resproj) != -1) || - (callprojs.exobj != nullptr && call->find_edge(callprojs.exobj) != -1)) { + CallProjections* callprojs = call->extract_projections(true, do_asserts); + if ((callprojs->fallthrough_catchproj == call->in(0)) || + (callprojs->catchall_catchproj == call->in(0)) || + (callprojs->fallthrough_memproj == call->in(TypeFunc::Memory)) || + (callprojs->catchall_memproj == call->in(TypeFunc::Memory)) || + (callprojs->fallthrough_ioproj == call->in(TypeFunc::I_O)) || + (callprojs->catchall_ioproj == call->in(TypeFunc::I_O)) || + (callprojs->exobj != nullptr && call->find_edge(callprojs->exobj) != -1)) { return; } @@ -616,11 +643,22 @@ void CallGenerator::do_late_inline_helper() { C->remove_macro_node(call); } - // The call is marked as pure (no important side effects), but result isn't used. - // It's safe to remove the call. - bool result_not_used = (callprojs.resproj == nullptr || callprojs.resproj->outcnt() == 0); + + bool result_not_used = true; + for (uint i = 0; i < callprojs->nb_resproj; i++) { + if (callprojs->resproj[i] != nullptr) { + if (callprojs->resproj[i]->outcnt() != 0) { + result_not_used = false; + } + if (call->find_edge(callprojs->resproj[i]) != -1) { + return; + } + } + } if (is_pure_call() && result_not_used) { + // The call is marked as pure (no important side effects), but result isn't used. + // It's safe to remove the call. GraphKit kit(call->jvms()); kit.replace_call(call, C->top(), true, do_asserts); } else { @@ -633,26 +671,44 @@ void CallGenerator::do_late_inline_helper() { map->init_req(i1, call->in(i1)); } + PhaseGVN& gvn = *C->initial_gvn(); // Make sure the state is a MergeMem for parsing. if (!map->in(TypeFunc::Memory)->is_MergeMem()) { Node* mem = MergeMemNode::make(map->in(TypeFunc::Memory)); - C->initial_gvn()->set_type_bottom(mem); + gvn.set_type_bottom(mem); map->set_req(TypeFunc::Memory, mem); } - uint nargs = method()->arg_size(); // blow away old call arguments - Node* top = C->top(); - for (uint i1 = 0; i1 < nargs; i1++) { - map->set_req(TypeFunc::Parms + i1, top); + for (uint i1 = TypeFunc::Parms; i1 < r->cnt(); i1++) { + map->set_req(i1, C->top()); } jvms->set_map(map); // Make enough space in the expression stack to transfer // the incoming arguments and return value. map->ensure_stack(jvms, jvms->method()->max_stack()); + const TypeTuple* domain_sig = call->_tf->domain_sig(); + uint nargs = method()->arg_size(); + assert(domain_sig->cnt() - TypeFunc::Parms == nargs, "inconsistent signature"); + + uint j = TypeFunc::Parms; + int arg_num = 0; for (uint i1 = 0; i1 < nargs; i1++) { - map->set_argument(jvms, i1, call->in(TypeFunc::Parms + i1)); + const Type* t = domain_sig->field_at(TypeFunc::Parms + i1); + if (t->is_inlinetypeptr() && !method()->get_Method()->mismatch() && method()->is_scalarized_arg(arg_num)) { + // Inline type arguments are not passed by reference: we get an argument per + // field of the inline type. Build InlineTypeNodes from the inline type arguments. + GraphKit arg_kit(jvms, &gvn); + Node* vt = InlineTypeNode::make_from_multi(&arg_kit, call, t->inline_klass(), j, /* in= */ true, /* null_free= */ !t->maybe_null()); + map->set_control(arg_kit.control()); + map->set_argument(jvms, i1, vt); + } else { + map->set_argument(jvms, i1, call->in(j++)); + } + if (t != Type::HALF) { + arg_num++; + } } C->log_late_inline(this); @@ -663,6 +719,26 @@ void CallGenerator::do_late_inline_helper() { return; } + // Check if we are late inlining a method handle call that returns an inline type as fields. + Node* buffer_oop = nullptr; + ciMethod* inline_method = inline_cg()->method(); + ciType* return_type = inline_method->return_type(); + if (!call->tf()->returns_inline_type_as_fields() && + return_type->is_inlinetype() && return_type->as_inline_klass()->can_be_returned_as_fields()) { + // Allocate a buffer for the inline type returned as fields because the caller expects an oop return. + // Do this before the method handle call in case the buffer allocation triggers deoptimization and + // we need to "re-execute" the call in the interpreter (to make sure the call is only executed once). + GraphKit arg_kit(jvms, &gvn); + { + PreserveReexecuteState preexecs(&arg_kit); + arg_kit.jvms()->set_should_reexecute(true); + arg_kit.inc_sp(nargs); + Node* klass_node = arg_kit.makecon(TypeKlassPtr::make(return_type->as_inline_klass())); + buffer_oop = arg_kit.new_instance(klass_node, nullptr, nullptr, /* deoptimize_on_exception */ true); + } + jvms = arg_kit.transfer_exceptions_into_jvms(); + } + // Setup default node notes to be picked up by the inlining Node_Notes* old_nn = C->node_notes_at(call->_idx); if (old_nn != nullptr) { @@ -703,11 +779,65 @@ void CallGenerator::do_late_inline_helper() { } if (inline_cg()->is_inline()) { - C->set_has_loops(C->has_loops() || inline_cg()->method()->has_loops()); - C->env()->notice_inlined_method(inline_cg()->method()); + C->set_has_loops(C->has_loops() || inline_method->has_loops()); + C->env()->notice_inlined_method(inline_method); } C->set_inlining_progress(true); C->set_do_cleanup(kit.stopped()); // path is dead; needs cleanup + + // Handle inline type returns + InlineTypeNode* vt = result->isa_InlineType(); + if (vt != nullptr) { + if (call->tf()->returns_inline_type_as_fields()) { + vt->replace_call_results(&kit, call, C); + } else { + // Result might still be allocated (for example, if it has been stored to a non-flat field) + if (!vt->is_allocated(&kit.gvn())) { + assert(buffer_oop != nullptr, "should have allocated a buffer"); + RegionNode* region = new RegionNode(3); + + // Check if result is null + Node* null_ctl = kit.top(); + kit.null_check_common(vt->get_null_marker(), T_INT, false, &null_ctl); + region->init_req(1, null_ctl); + PhiNode* oop = PhiNode::make(region, kit.gvn().zerocon(T_OBJECT), TypeInstPtr::make(TypePtr::BotPTR, vt->type()->inline_klass())); + Node* init_mem = kit.reset_memory(); + PhiNode* mem = PhiNode::make(region, init_mem, Type::MEMORY, TypePtr::BOTTOM); + + // Not null, initialize the buffer + kit.set_all_memory(init_mem); + + Node* payload_ptr = kit.basic_plus_adr(buffer_oop, kit.gvn().type(vt)->inline_klass()->payload_offset()); + vt->store_flat(&kit, buffer_oop, payload_ptr, false, true, true, IN_HEAP | MO_UNORDERED); + // Do not let stores that initialize this buffer be reordered with a subsequent + // store that would make this buffer accessible by other threads. + AllocateNode* alloc = AllocateNode::Ideal_allocation(buffer_oop); + assert(alloc != nullptr, "must have an allocation node"); + kit.insert_mem_bar(Op_MemBarStoreStore, alloc->proj_out_or_null(AllocateNode::RawAddress)); + region->init_req(2, kit.control()); + oop->init_req(2, buffer_oop); + mem->init_req(2, kit.merged_memory()); + + // Update oop input to buffer + kit.gvn().hash_delete(vt); + vt->set_oop(kit.gvn(), kit.gvn().transform(oop)); + vt->set_is_buffered(kit.gvn()); + vt = kit.gvn().transform(vt)->as_InlineType(); + + kit.set_control(kit.gvn().transform(region)); + kit.set_all_memory(kit.gvn().transform(mem)); + kit.record_for_igvn(region); + kit.record_for_igvn(oop); + kit.record_for_igvn(mem); + } + result = vt; + } + DEBUG_ONLY(buffer_oop = nullptr); + } else { + assert(result->is_top() || !call->tf()->returns_inline_type_as_fields() || !call->as_CallJava()->method()->return_type()->is_loaded(), "Unexpected return value"); + } + assert(buffer_oop == nullptr, "unused buffer allocation"); + kit.replace_call(call, result, true, do_asserts); } } @@ -936,6 +1066,29 @@ JVMState* PredictedCallGenerator::generate(JVMState* jvms) { return kit.transfer_exceptions_into_jvms(); } + // Allocate inline types if they are merged with objects (similar to Parse::merge_common()) + uint tos = kit.jvms()->stkoff() + kit.sp(); + uint limit = slow_map->req(); + for (uint i = TypeFunc::Parms; i < limit; i++) { + Node* m = kit.map()->in(i); + Node* n = slow_map->in(i); + const Type* t = gvn.type(m)->meet_speculative(gvn.type(n)); + // TODO 8284443 still needed? + if (m->is_InlineType() && !t->is_inlinetypeptr()) { + // Allocate inline type in fast path + m = m->as_InlineType()->buffer(&kit); + kit.map()->set_req(i, m); + } + if (n->is_InlineType() && !t->is_inlinetypeptr()) { + // Allocate inline type in slow path + PreserveJVMState pjvms(&kit); + kit.set_map(slow_map); + n = n->as_InlineType()->buffer(&kit); + kit.map()->set_req(i, n); + slow_map = kit.stop(); + } + } + // There are 2 branches and the replaced nodes are only valid on // one: restore the replaced nodes to what they were before the // branch. @@ -959,8 +1112,6 @@ JVMState* PredictedCallGenerator::generate(JVMState* jvms) { mms.set_memory(gvn.transform(phi)); } } - uint tos = kit.jvms()->stkoff() + kit.sp(); - uint limit = slow_map->req(); for (uint i = TypeFunc::Parms; i < limit; i++) { // Skip unused stack slots; fast forward to monoff(); if (i == tos) { @@ -997,8 +1148,8 @@ CallGenerator* CallGenerator::for_method_handle_call(JVMState* jvms, ciMethod* c ciCallProfile profile = caller->call_profile_at_bci(bci); int call_site_count = caller->scale_count(profile.count()); - if (IncrementalInlineMH && call_site_count > 0 && - (should_delay || input_not_const || !C->inlining_incrementally() || C->over_inlining_cutoff())) { + if (IncrementalInlineMH && (AlwaysIncrementalInline || + (call_site_count > 0 && (should_delay || input_not_const || !C->inlining_incrementally() || C->over_inlining_cutoff())))) { return CallGenerator::for_mh_late_inline(caller, callee, input_not_const); } else { // Out-of-line call. @@ -1006,6 +1157,7 @@ CallGenerator* CallGenerator::for_method_handle_call(JVMState* jvms, ciMethod* c } } + CallGenerator* CallGenerator::for_method_handle_inline(JVMState* jvms, ciMethod* caller, ciMethod* callee, bool allow_inline, bool& input_not_const) { GraphKit kit(jvms); PhaseGVN& gvn = kit.gvn(); @@ -1053,8 +1205,9 @@ CallGenerator* CallGenerator::for_method_handle_inline(JVMState* jvms, ciMethod* case vmIntrinsics::_linkToSpecial: case vmIntrinsics::_linkToInterface: { + int nargs = callee->arg_size(); // Get MemberName argument: - Node* member_name = kit.argument(callee->arg_size() - 1); + Node* member_name = kit.argument(nargs - 1); if (member_name->Opcode() == Op_ConP) { input_not_const = false; const TypeOopPtr* oop_ptr = member_name->bottom_type()->is_oopptr(); @@ -1124,7 +1277,8 @@ CallGenerator* CallGenerator::for_method_handle_inline(JVMState* jvms, ciMethod* CallGenerator* cg = C->call_generator(target, vtable_index, call_does_dispatch, jvms, allow_inline, PROB_ALWAYS, - speculative_receiver_type); + speculative_receiver_type, + true); return cg; } else { print_inlining_failure(C, callee, jvms, "member_name not constant"); @@ -1196,7 +1350,7 @@ JVMState* PredicatedIntrinsicGenerator::generate(JVMState* jvms) { if (!method()->is_static()) { // We need an explicit receiver null_check before checking its type in predicate. // We share a map with the caller, so his JVMS gets adjusted. - Node* receiver = kit.null_check_receiver_before_call(method()); + kit.null_check_receiver_before_call(method()); if (kit.stopped()) { return kit.transfer_exceptions_into_jvms(); } diff --git a/src/hotspot/share/opto/callGenerator.hpp b/src/hotspot/share/opto/callGenerator.hpp index 82b195e0c76..d88c44c92c3 100644 --- a/src/hotspot/share/opto/callGenerator.hpp +++ b/src/hotspot/share/opto/callGenerator.hpp @@ -44,9 +44,8 @@ class CallGenerator : public ArenaObj { void do_late_inline_helper(); - virtual bool do_late_inline_check(Compile* C, JVMState* jvms) { ShouldNotReachHere(); return false; } - virtual CallGenerator* inline_cg() const { ShouldNotReachHere(); return nullptr;} - virtual bool is_pure_call() const { ShouldNotReachHere(); return false; } + virtual bool do_late_inline_check(Compile* C, JVMState* jvms) { ShouldNotReachHere(); return false; } + virtual bool is_pure_call() const { ShouldNotReachHere(); return false; } public: // Accessors @@ -88,6 +87,8 @@ class CallGenerator : public ArenaObj { virtual void set_unique_id(jlong id) { fatal("unique id only for late inlines"); }; virtual jlong unique_id() const { fatal("unique id only for late inlines"); return 0; }; + virtual CallGenerator* inline_cg() const { ShouldNotReachHere(); return nullptr; } + virtual void set_callee_method(ciMethod* callee) { ShouldNotReachHere(); } // Note: It is possible for a CG to be both inline and virtual. diff --git a/src/hotspot/share/opto/callnode.cpp b/src/hotspot/share/opto/callnode.cpp index 728093851b0..0de4f0f6de6 100644 --- a/src/hotspot/share/opto/callnode.cpp +++ b/src/hotspot/share/opto/callnode.cpp @@ -23,6 +23,7 @@ */ #include "ci/bcEscapeAnalyzer.hpp" +#include "ci/ciFlatArrayKlass.hpp" #include "code/vmreg.hpp" #include "compiler/compileLog.hpp" #include "compiler/oopMap.hpp" @@ -34,6 +35,7 @@ #include "opto/castnode.hpp" #include "opto/convertnode.hpp" #include "opto/escape.hpp" +#include "opto/inlinetypenode.hpp" #include "opto/locknode.hpp" #include "opto/machnode.hpp" #include "opto/matcher.hpp" @@ -43,6 +45,7 @@ #include "opto/rootnode.hpp" #include "opto/runtime.hpp" #include "runtime/sharedRuntime.hpp" +#include "runtime/stubRoutines.hpp" #include "utilities/powerOfTwo.hpp" // Portions of code courtesy of Clifford Click @@ -77,7 +80,7 @@ const RegMask &StartNode::in_RegMask(uint) const { //------------------------------match------------------------------------------ // Construct projections for incoming parameters, and their RegMask info -Node *StartNode::match( const ProjNode *proj, const Matcher *match ) { +Node *StartNode::match(const ProjNode *proj, const Matcher *match, const RegMask* mask) { switch (proj->_con) { case TypeFunc::Control: case TypeFunc::I_O: @@ -101,17 +104,6 @@ Node *StartNode::match( const ProjNode *proj, const Matcher *match ) { return nullptr; } -//------------------------------StartOSRNode---------------------------------- -// The method start node for an on stack replacement adapter - -//------------------------------osr_domain----------------------------- -const TypeTuple *StartOSRNode::osr_domain() { - const Type **fields = TypeTuple::fields(2); - fields[TypeFunc::Parms+0] = TypeRawPtr::BOTTOM; // address of osr buffer - - return TypeTuple::make(TypeFunc::Parms+1, fields); -} - //============================================================================= const char * const ParmNode::names[TypeFunc::Parms+1] = { "Control", "I_O", "Memory", "FramePtr", "ReturnAdr", "Parms" @@ -497,17 +489,30 @@ void JVMState::format(PhaseRegAlloc *regalloc, const Node *n, outputStream* st) while (ndim-- > 0) { st->print("[]"); } + } else if (cik->is_flat_array_klass()) { + ciKlass* cie = cik->as_flat_array_klass()->base_element_klass(); + cie->print_name_on(st); + st->print("[%d]", spobj->n_fields()); + int ndim = cik->as_array_klass()->dimension() - 1; + while (ndim-- > 0) { + st->print("[]"); + } } st->print("={"); uint nf = spobj->n_fields(); if (nf > 0) { uint first_ind = spobj->first_index(mcall->jvms()); + if (iklass != nullptr && iklass->is_inlinetype()) { + Node* null_marker = mcall->in(first_ind++); + if (!null_marker->is_top()) { + st->print(" [null marker"); + format_helper(regalloc, st, null_marker, ":", -1, nullptr); + } + } Node* fld_node = mcall->in(first_ind); - ciField* cifield; if (iklass != nullptr) { st->print(" ["); - cifield = iklass->nonstatic_field_at(0); - cifield->print_name_on(st); + iklass->nonstatic_field_at(0)->print_name_on(st); format_helper(regalloc, st, fld_node, ":", 0, &scobjs); } else { format_helper(regalloc, st, fld_node, "[", 0, &scobjs); @@ -516,8 +521,7 @@ void JVMState::format(PhaseRegAlloc *regalloc, const Node *n, outputStream* st) fld_node = mcall->in(first_ind+j); if (iklass != nullptr) { st->print(", ["); - cifield = iklass->nonstatic_field_at(j); - cifield->print_name_on(st); + iklass->nonstatic_field_at(j)->print_name_on(st); format_helper(regalloc, st, fld_node, ":", j, &scobjs); } else { format_helper(regalloc, st, fld_node, ", [", j, &scobjs); @@ -737,16 +741,23 @@ void AllocateNode::dump_spec(outputStream* st) const { } #endif -const Type *CallNode::bottom_type() const { return tf()->range(); } +const Type *CallNode::bottom_type() const { return tf()->range_cc(); } const Type* CallNode::Value(PhaseGVN* phase) const { if (in(0) == nullptr || phase->type(in(0)) == Type::TOP) { return Type::TOP; } - return tf()->range(); + return tf()->range_cc(); } //------------------------------calling_convention----------------------------- void CallNode::calling_convention(BasicType* sig_bt, VMRegPair *parm_regs, uint argcnt) const { + if (_entry_point == StubRoutines::store_inline_type_fields_to_buf()) { + // The call to that stub is a special case: its inputs are + // multiple values returned from a call and so it should follow + // the return convention. + SharedRuntime::java_return_convention(sig_bt, parm_regs, argcnt); + return; + } // Use the standard compiler calling convention SharedRuntime::java_calling_convention(sig_bt, parm_regs, argcnt); } @@ -755,43 +766,53 @@ void CallNode::calling_convention(BasicType* sig_bt, VMRegPair *parm_regs, uint //------------------------------match------------------------------------------ // Construct projections for control, I/O, memory-fields, ..., and // return result(s) along with their RegMask info -Node *CallNode::match( const ProjNode *proj, const Matcher *match ) { - switch (proj->_con) { - case TypeFunc::Control: - case TypeFunc::I_O: - case TypeFunc::Memory: - return new MachProjNode(this,proj->_con,RegMask::Empty,MachProjNode::unmatched_proj); - - case TypeFunc::Parms+1: // For LONG & DOUBLE returns - assert(tf()->range()->field_at(TypeFunc::Parms+1) == Type::HALF, ""); - // 2nd half of doubles and longs - return new MachProjNode(this,proj->_con, RegMask::Empty, (uint)OptoReg::Bad); - - case TypeFunc::Parms: { // Normal returns - uint ideal_reg = tf()->range()->field_at(TypeFunc::Parms)->ideal_reg(); - OptoRegPair regs = Opcode() == Op_CallLeafVector - ? match->vector_return_value(ideal_reg) // Calls into assembly vector routine - : is_CallRuntime() - ? match->c_return_value(ideal_reg) // Calls into C runtime - : match-> return_value(ideal_reg); // Calls into compiled Java code - RegMask rm = RegMask(regs.first()); - - if (Opcode() == Op_CallLeafVector) { - // If the return is in vector, compute appropriate regmask taking into account the whole range - if(ideal_reg >= Op_VecA && ideal_reg <= Op_VecZ) { - if(OptoReg::is_valid(regs.second())) { - for (OptoReg::Name r = regs.first(); r <= regs.second(); r = OptoReg::add(r, 1)) { - rm.Insert(r); +Node *CallNode::match(const ProjNode *proj, const Matcher *match, const RegMask* mask) { + uint con = proj->_con; + const TypeTuple* range_cc = tf()->range_cc(); + if (con >= TypeFunc::Parms) { + if (tf()->returns_inline_type_as_fields()) { + // The call returns multiple values (inline type fields): we + // create one projection per returned value. + assert(con <= TypeFunc::Parms+1 || InlineTypeReturnedAsFields, "only for multi value return"); + uint ideal_reg = range_cc->field_at(con)->ideal_reg(); + return new MachProjNode(this, con, mask[con-TypeFunc::Parms], ideal_reg); + } else { + if (con == TypeFunc::Parms) { + uint ideal_reg = range_cc->field_at(TypeFunc::Parms)->ideal_reg(); + OptoRegPair regs = Opcode() == Op_CallLeafVector + ? match->vector_return_value(ideal_reg) // Calls into assembly vector routine + : match->c_return_value(ideal_reg); + RegMask rm = RegMask(regs.first()); + + if (Opcode() == Op_CallLeafVector) { + // If the return is in vector, compute appropriate regmask taking into account the whole range + if(ideal_reg >= Op_VecA && ideal_reg <= Op_VecZ) { + if(OptoReg::is_valid(regs.second())) { + for (OptoReg::Name r = regs.first(); r <= regs.second(); r = OptoReg::add(r, 1)) { + rm.Insert(r); + } + } } } + + if (OptoReg::is_valid(regs.second())) { + rm.Insert(regs.second()); + } + return new MachProjNode(this,con,rm,ideal_reg); + } else { + assert(con == TypeFunc::Parms+1, "only one return value"); + assert(range_cc->field_at(TypeFunc::Parms+1) == Type::HALF, ""); + return new MachProjNode(this,con, RegMask::Empty, (uint)OptoReg::Bad); } } - - if( OptoReg::is_valid(regs.second()) ) - rm.Insert( regs.second() ); - return new MachProjNode(this,proj->_con,rm,ideal_reg); } + switch (con) { + case TypeFunc::Control: + case TypeFunc::I_O: + case TypeFunc::Memory: + return new MachProjNode(this,proj->_con,RegMask::Empty,MachProjNode::unmatched_proj); + case TypeFunc::ReturnAdr: case TypeFunc::FramePtr: default: @@ -812,7 +833,7 @@ uint CallNode::match_edge(uint idx) const { bool CallNode::may_modify(const TypeOopPtr* t_oop, PhaseValues* phase) { assert((t_oop != nullptr), "sanity"); if (is_call_to_arraycopystub() && strcmp(_name, "unsafe_arraycopy") != 0) { - const TypeTuple* args = _tf->domain(); + const TypeTuple* args = _tf->domain_sig(); Node* dest = nullptr; // Stubs that can be called once an ArrayCopyNode is expanded have // different signatures. Look for the second pointer argument, @@ -861,7 +882,7 @@ bool CallNode::may_modify(const TypeOopPtr* t_oop, PhaseValues* phase) { return true; } } - const TypeTuple* d = tf()->domain(); + const TypeTuple* d = tf()->domain_cc(); for (uint i = TypeFunc::Parms; i < d->cnt(); i++) { const TypeInstPtr* inst_t = d->field_at(i)->isa_instptr(); if ((inst_t != nullptr) && (!inst_t->klass_is_exact() || @@ -876,17 +897,27 @@ bool CallNode::may_modify(const TypeOopPtr* t_oop, PhaseValues* phase) { } // Does this call have a direct reference to n other than debug information? -bool CallNode::has_non_debug_use(Node *n) { - const TypeTuple * d = tf()->domain(); +bool CallNode::has_non_debug_use(Node* n) { + const TypeTuple* d = tf()->domain_cc(); for (uint i = TypeFunc::Parms; i < d->cnt(); i++) { - Node *arg = in(i); - if (arg == n) { + if (in(i) == n) { return true; } } return false; } +bool CallNode::has_debug_use(Node* n) { + if (jvms() != nullptr) { + for (uint i = jvms()->debug_start(); i < jvms()->debug_end(); i++) { + if (in(i) == n) { + return true; + } + } + } + return false; +} + // Returns the unique CheckCastPP of a call // or 'this' if there are several CheckCastPP or unexpected uses // or returns null if there is no one. @@ -918,16 +949,21 @@ Node *CallNode::result_cast() { } -void CallNode::extract_projections(CallProjections* projs, bool separate_io_proj, bool do_asserts) const { - projs->fallthrough_proj = nullptr; - projs->fallthrough_catchproj = nullptr; - projs->fallthrough_ioproj = nullptr; - projs->catchall_ioproj = nullptr; - projs->catchall_catchproj = nullptr; - projs->fallthrough_memproj = nullptr; - projs->catchall_memproj = nullptr; - projs->resproj = nullptr; - projs->exobj = nullptr; +CallProjections* CallNode::extract_projections(bool separate_io_proj, bool do_asserts) const { + uint max_res = TypeFunc::Parms-1; + for (DUIterator_Fast imax, i = fast_outs(imax); i < imax; i++) { + ProjNode *pn = fast_out(i)->as_Proj(); + max_res = MAX2(max_res, pn->_con); + } + + assert(max_res < _tf->range_cc()->cnt(), "result out of bounds"); + + uint projs_size = sizeof(CallProjections); + if (max_res > TypeFunc::Parms) { + projs_size += (max_res-TypeFunc::Parms)*sizeof(Node*); + } + char* projs_storage = resource_allocate_bytes(projs_size); + CallProjections* projs = new(projs_storage)CallProjections(max_res - TypeFunc::Parms + 1); for (DUIterator_Fast imax, i = fast_outs(imax); i < imax; i++) { ProjNode *pn = fast_out(i)->as_Proj(); @@ -973,18 +1009,20 @@ void CallNode::extract_projections(CallProjections* projs, bool separate_io_proj projs->fallthrough_memproj = pn; break; case TypeFunc::Parms: - projs->resproj = pn; + projs->resproj[0] = pn; break; default: - assert(false, "unexpected projection from allocation node."); + assert(pn->_con <= max_res, "unexpected projection from allocation node."); + projs->resproj[pn->_con-TypeFunc::Parms] = pn; + break; } } // The resproj may not exist because the result could be ignored // and the exception object may not exist if an exception handler // swallows the exception but all the other must exist and be found. - assert(projs->fallthrough_proj != nullptr, "must be found"); do_asserts = do_asserts && !Compile::current()->inlining_incrementally(); + assert(!do_asserts || projs->fallthrough_proj != nullptr, "must be found"); assert(!do_asserts || projs->fallthrough_catchproj != nullptr, "must be found"); assert(!do_asserts || projs->fallthrough_memproj != nullptr, "must be found"); assert(!do_asserts || projs->fallthrough_ioproj != nullptr, "must be found"); @@ -993,6 +1031,7 @@ void CallNode::extract_projections(CallProjections* projs, bool separate_io_proj assert(!do_asserts || projs->catchall_memproj != nullptr, "must be found"); assert(!do_asserts || projs->catchall_ioproj != nullptr, "must be found"); } + return projs; } Node* CallNode::Ideal(PhaseGVN* phase, bool can_reshape) { @@ -1024,8 +1063,8 @@ bool CallJavaNode::cmp( const Node &n ) const { void CallJavaNode::copy_call_debug_info(PhaseIterGVN* phase, SafePointNode* sfpt) { // Copy debug information and adjust JVMState information - uint old_dbg_start = sfpt->is_Call() ? sfpt->as_Call()->tf()->domain()->cnt() : (uint)TypeFunc::Parms+1; - uint new_dbg_start = tf()->domain()->cnt(); + uint old_dbg_start = sfpt->is_Call() ? sfpt->as_Call()->tf()->domain_sig()->cnt() : (uint)TypeFunc::Parms+1; + uint new_dbg_start = tf()->domain_sig()->cnt(); int jvms_adj = new_dbg_start - old_dbg_start; assert (new_dbg_start == req(), "argument count mismatch"); Compile* C = phase->C; @@ -1066,6 +1105,10 @@ bool CallJavaNode::validate_symbolic_info() const { if (method() == nullptr) { return true; // call into runtime or uncommon trap } + Bytecodes::Code bc = jvms()->method()->java_code_at_bci(jvms()->bci()); + if (EnableValhalla && (bc == Bytecodes::_if_acmpeq || bc == Bytecodes::_if_acmpne)) { + return true; + } ciMethod* symbolic_info = jvms()->method()->get_method_at_bci(jvms()->bci()); ciMethod* callee = method(); if (symbolic_info->is_method_handle_intrinsic() && !callee->is_method_handle_intrinsic()) { @@ -1108,6 +1151,16 @@ bool CallStaticJavaNode::cmp( const Node &n ) const { } Node* CallStaticJavaNode::Ideal(PhaseGVN* phase, bool can_reshape) { + if (can_reshape && uncommon_trap_request() != 0) { + PhaseIterGVN* igvn = phase->is_IterGVN(); + if (remove_unknown_flat_array_load(igvn, in(0), in(TypeFunc::Memory), in(TypeFunc::Parms))) { + if (!in(0)->is_Region()) { + igvn->replace_input_of(this, 0, phase->C->top()); + } + return this; + } + } + CallGenerator* cg = generator(); if (can_reshape && cg != nullptr) { if (cg->is_mh_late_inline()) { @@ -1169,6 +1222,128 @@ int CallStaticJavaNode::extract_uncommon_trap_request(const Node* call) { return call->in(TypeFunc::Parms)->bottom_type()->is_int()->get_con(); } +// Split if can cause the flat array branch of an array load with unknown type (see +// Parse::array_load) to end in an uncommon trap. In that case, the call to +// 'load_unknown_inline' is useless. Replace it with an uncommon trap with the same JVMState. +bool CallStaticJavaNode::remove_unknown_flat_array_load(PhaseIterGVN* igvn, Node* ctl, Node* mem, Node* unc_arg) { + if (ctl == nullptr || ctl->is_top() || mem == nullptr || mem->is_top() || !mem->is_MergeMem()) { + return false; + } + if (ctl->is_Region()) { + bool res = false; + for (uint i = 1; i < ctl->req(); i++) { + MergeMemNode* mm = mem->clone()->as_MergeMem(); + for (MergeMemStream mms(mm); mms.next_non_empty(); ) { + Node* m = mms.memory(); + if (m->is_Phi() && m->in(0) == ctl) { + mms.set_memory(m->in(i)); + } + } + if (remove_unknown_flat_array_load(igvn, ctl->in(i), mm, unc_arg)) { + res = true; + if (!ctl->in(i)->is_Region()) { + igvn->replace_input_of(ctl, i, igvn->C->top()); + } + } + igvn->remove_dead_node(mm); + } + return res; + } + // Verify the control flow is ok + Node* call = ctl; + MemBarNode* membar = nullptr; + for (;;) { + if (call == nullptr || call->is_top()) { + return false; + } + if (call->is_Proj() || call->is_Catch() || call->is_MemBar()) { + call = call->in(0); + } else if (call->Opcode() == Op_CallStaticJava && !call->in(0)->is_top() && + call->as_Call()->entry_point() == OptoRuntime::load_unknown_inline_Java()) { + assert(call->in(0)->is_Proj() && call->in(0)->in(0)->is_MemBar(), "missing membar"); + membar = call->in(0)->in(0)->as_MemBar(); + break; + } else { + return false; + } + } + + JVMState* jvms = call->jvms(); + if (igvn->C->too_many_traps(jvms->method(), jvms->bci(), Deoptimization::trap_request_reason(uncommon_trap_request()))) { + return false; + } + + Node* call_mem = call->in(TypeFunc::Memory); + if (call_mem == nullptr || call_mem->is_top()) { + return false; + } + if (!call_mem->is_MergeMem()) { + call_mem = MergeMemNode::make(call_mem); + igvn->register_new_node_with_optimizer(call_mem); + } + + // Verify that there's no unexpected side effect + for (MergeMemStream mms2(mem->as_MergeMem(), call_mem->as_MergeMem()); mms2.next_non_empty2(); ) { + Node* m1 = mms2.is_empty() ? mms2.base_memory() : mms2.memory(); + Node* m2 = mms2.memory2(); + + for (uint i = 0; i < 100; i++) { + if (m1 == m2) { + break; + } else if (m1->is_Proj()) { + m1 = m1->in(0); + } else if (m1->is_MemBar()) { + m1 = m1->in(TypeFunc::Memory); + } else if (m1->Opcode() == Op_CallStaticJava && + m1->as_Call()->entry_point() == OptoRuntime::load_unknown_inline_Java()) { + if (m1 != call) { + return false; + } + break; + } else if (m1->is_MergeMem()) { + MergeMemNode* mm = m1->as_MergeMem(); + int idx = mms2.alias_idx(); + if (idx == Compile::AliasIdxBot) { + m1 = mm->base_memory(); + } else { + m1 = mm->memory_at(idx); + } + } else { + return false; + } + } + } + if (call_mem->outcnt() == 0) { + igvn->remove_dead_node(call_mem); + } + + // Remove membar preceding the call + membar->remove(igvn); + + address call_addr = OptoRuntime::uncommon_trap_blob()->entry_point(); + CallNode* unc = new CallStaticJavaNode(OptoRuntime::uncommon_trap_Type(), call_addr, "uncommon_trap", nullptr); + unc->init_req(TypeFunc::Control, call->in(0)); + unc->init_req(TypeFunc::I_O, call->in(TypeFunc::I_O)); + unc->init_req(TypeFunc::Memory, call->in(TypeFunc::Memory)); + unc->init_req(TypeFunc::FramePtr, call->in(TypeFunc::FramePtr)); + unc->init_req(TypeFunc::ReturnAdr, call->in(TypeFunc::ReturnAdr)); + unc->init_req(TypeFunc::Parms+0, unc_arg); + unc->set_cnt(PROB_UNLIKELY_MAG(4)); + unc->copy_call_debug_info(igvn, call->as_CallStaticJava()); + + // Replace the call with an uncommon trap + igvn->replace_input_of(call, 0, igvn->C->top()); + + igvn->register_new_node_with_optimizer(unc); + + Node* ctrl = igvn->transform(new ProjNode(unc, TypeFunc::Control)); + Node* halt = igvn->transform(new HaltNode(ctrl, call->in(TypeFunc::FramePtr), "uncommon trap returned which should never happen")); + igvn->add_input_to(igvn->C->root(), halt); + + return true; +} + + #ifndef PRODUCT void CallStaticJavaNode::dump_spec(outputStream *st) const { st->print("# Static "); @@ -1280,14 +1455,21 @@ bool CallLeafVectorNode::cmp( const Node &n ) const { //------------------------------calling_convention----------------------------- void CallRuntimeNode::calling_convention(BasicType* sig_bt, VMRegPair *parm_regs, uint argcnt) const { + if (_entry_point == nullptr) { + // The call to that stub is a special case: its inputs are + // multiple values returned from a call and so it should follow + // the return convention. + SharedRuntime::java_return_convention(sig_bt, parm_regs, argcnt); + return; + } SharedRuntime::c_calling_convention(sig_bt, parm_regs, argcnt); } void CallLeafVectorNode::calling_convention( BasicType* sig_bt, VMRegPair *parm_regs, uint argcnt ) const { #ifdef ASSERT - assert(tf()->range()->field_at(TypeFunc::Parms)->is_vect()->length_in_bytes() * BitsPerByte == _num_bits, + assert(tf()->range_sig()->field_at(TypeFunc::Parms)->is_vect()->length_in_bytes() * BitsPerByte == _num_bits, "return vector size must match"); - const TypeTuple* d = tf()->domain(); + const TypeTuple* d = tf()->domain_sig(); for (uint i = TypeFunc::Parms; i < d->cnt(); i++) { Node* arg = in(i); assert(arg->bottom_type()->is_vect()->length_in_bytes() * BitsPerByte == _num_bits, @@ -1323,7 +1505,7 @@ bool CallLeafPureNode::is_dead() const { TupleNode* CallLeafPureNode::make_tuple_of_input_state_and_top_return_values(const Compile* C) const { // Transparently propagate input state but parameters TupleNode* tuple = TupleNode::make( - tf()->range(), + tf()->range_cc(), in(TypeFunc::Control), in(TypeFunc::I_O), in(TypeFunc::Memory), @@ -1331,7 +1513,7 @@ TupleNode* CallLeafPureNode::make_tuple_of_input_state_and_top_return_values(con in(TypeFunc::ReturnAdr)); // And add TOPs for the return values - for (uint i = TypeFunc::Parms; i < tf()->range()->cnt(); i++) { + for (uint i = TypeFunc::Parms; i < tf()->range_cc()->cnt(); i++) { tuple->set_req(i, C->top()); } @@ -1362,6 +1544,12 @@ void CallLeafNode::dump_spec(outputStream *st) const { } #endif +uint CallLeafNoFPNode::match_edge(uint idx) const { + // Null entry point is a special case for which the target is in a + // register. Need to match that edge. + return entry_point() == nullptr && idx == TypeFunc::Parms; +} + //============================================================================= void SafePointNode::set_local(JVMState* jvms, uint idx, Node *c) { @@ -1412,7 +1600,20 @@ SafePointNode* SafePointNode::next_exception() const { // Skip over any collapsed Regions Node *SafePointNode::Ideal(PhaseGVN *phase, bool can_reshape) { assert(_jvms == nullptr || ((uintptr_t)_jvms->map() & 1) || _jvms->map() == this, "inconsistent JVMState"); - return remove_dead_region(phase, can_reshape) ? this : nullptr; + if (remove_dead_region(phase, can_reshape)) { + return this; + } + // Scalarize inline types in safepoint debug info. + // Delay this until all inlining is over to avoid getting inconsistent debug info. + if (phase->C->scalarize_in_safepoints() && can_reshape && jvms() != nullptr) { + for (uint i = jvms()->debug_start(); i < jvms()->debug_end(); i++) { + Node* n = in(i)->uncast(); + if (n->is_InlineType()) { + n->as_InlineType()->make_scalar_in_safepoints(phase->is_IterGVN()); + } + } + } + return nullptr; } //------------------------------Identity--------------------------------------- @@ -1564,7 +1765,7 @@ SafePointScalarObjectNode::SafePointScalarObjectNode(const TypeOopPtr* tp, Node* _alloc(alloc) { #ifdef ASSERT - if (!alloc->is_Allocate() && !(alloc->Opcode() == Op_VectorBox)) { + if (alloc != nullptr && !alloc->is_Allocate() && !(alloc->Opcode() == Op_VectorBox)) { alloc->dump(); assert(false, "unexpected call node"); } @@ -1669,7 +1870,9 @@ uint AllocateNode::size_of() const { return sizeof(*this); } AllocateNode::AllocateNode(Compile* C, const TypeFunc *atype, Node *ctrl, Node *mem, Node *abio, - Node *size, Node *klass_node, Node *initial_test) + Node *size, Node *klass_node, + Node* initial_test, + InlineTypeNode* inline_type_node) : CallNode(atype, nullptr, TypeRawPtr::BOTTOM) { init_class_id(Class_Allocate); @@ -1677,6 +1880,7 @@ AllocateNode::AllocateNode(Compile* C, const TypeFunc *atype, _is_scalar_replaceable = false; _is_non_escaping = false; _is_allocation_MemBar_redundant = false; + _larval = false; Node *topnode = C->top(); init_req( TypeFunc::Control , ctrl ); @@ -1689,12 +1893,16 @@ AllocateNode::AllocateNode(Compile* C, const TypeFunc *atype, init_req( InitialTest , initial_test); init_req( ALength , topnode); init_req( ValidLengthTest , topnode); + init_req( InlineType , inline_type_node); + // DefaultValue defaults to nullptr + // RawDefaultValue defaults to nullptr C->add_macro_node(this); } void AllocateNode::compute_MemBar_redundancy(ciMethod* initializer) { - assert(initializer != nullptr && initializer->is_object_initializer(), + assert(initializer != nullptr && + (initializer->is_object_constructor() || initializer->is_class_initializer()), "unexpected initializer method"); BCEscapeAnalyzer* analyzer = initializer->get_bcea(); if (analyzer == nullptr) { @@ -1706,17 +1914,22 @@ void AllocateNode::compute_MemBar_redundancy(ciMethod* initializer) _is_allocation_MemBar_redundant = true; } } -Node *AllocateNode::make_ideal_mark(PhaseGVN *phase, Node* obj, Node* control, Node* mem) { + +Node* AllocateNode::make_ideal_mark(PhaseGVN* phase, Node* control, Node* mem) { Node* mark_node = nullptr; - if (UseCompactObjectHeaders) { + if (UseCompactObjectHeaders || EnableValhalla) { Node* klass_node = in(AllocateNode::KlassNode); Node* proto_adr = phase->transform(new AddPNode(klass_node, klass_node, phase->MakeConX(in_bytes(Klass::prototype_header_offset())))); mark_node = LoadNode::make(*phase, control, mem, proto_adr, TypeRawPtr::BOTTOM, TypeX_X, TypeX_X->basic_type(), MemNode::unordered); + if (EnableValhalla) { + mark_node = phase->transform(mark_node); + // Avoid returning a constant (old node) here because this method is used by LoadNode::Ideal + mark_node = new OrXNode(mark_node, phase->MakeConX(_larval ? markWord::larval_bit_in_place : 0)); + } + return mark_node; } else { - // For now only enable fast locking for non-array types - mark_node = phase->MakeConX(markWord::prototype().value()); + return phase->MakeConX(markWord::prototype().value()); } - return mark_node; } // Retrieve the length from the AllocateArrayNode. Narrow the type with a @@ -2112,7 +2325,8 @@ Node *LockNode::Ideal(PhaseGVN *phase, bool can_reshape) { // prevents macro expansion from expanding the lock. Since we don't // modify the graph, the value returned from this function is the // one computed above. - if (can_reshape && EliminateLocks && !is_non_esc_obj()) { + const Type* obj_type = phase->type(obj_node()); + if (can_reshape && EliminateLocks && !is_non_esc_obj() && !obj_type->is_inlinetypeptr()) { // // If we are locking an non-escaped object, the lock/unlock is unnecessary // @@ -2313,7 +2527,8 @@ Node *UnlockNode::Ideal(PhaseGVN *phase, bool can_reshape) { // modify the graph, the value returned from this function is the // one computed above. // Escape state is defined after Parse phase. - if (can_reshape && EliminateLocks && !is_non_esc_obj()) { + const Type* obj_type = phase->type(obj_node()); + if (can_reshape && EliminateLocks && !is_non_esc_obj() && !obj_type->is_inlinetypeptr()) { // // If we are unlocking an non-escaped object, the lock/unlock is unnecessary. // @@ -2393,7 +2608,8 @@ bool CallNode::may_modify_arraycopy_helper(const TypeOopPtr* dest_t, const TypeO return true; } - dest_t = dest_t->add_offset(Type::OffsetBot)->is_oopptr(); + dest_t = dest_t->is_aryptr()->with_field_offset(Type::OffsetBot)->add_offset(Type::OffsetBot)->is_oopptr(); + t_oop = t_oop->is_aryptr()->with_field_offset(Type::OffsetBot); uint dest_alias = phase->C->get_alias_index(dest_t); uint t_oop_alias = phase->C->get_alias_index(t_oop); diff --git a/src/hotspot/share/opto/callnode.hpp b/src/hotspot/share/opto/callnode.hpp index 96706683f51..ac979e7055b 100644 --- a/src/hotspot/share/opto/callnode.hpp +++ b/src/hotspot/share/opto/callnode.hpp @@ -76,7 +76,7 @@ class StartNode : public MultiNode { virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); virtual void calling_convention( BasicType* sig_bt, VMRegPair *parm_reg, uint length ) const; virtual const RegMask &in_RegMask(uint) const; - virtual Node *match( const ProjNode *proj, const Matcher *m ); + virtual Node *match(const ProjNode *proj, const Matcher *m, const RegMask* mask); virtual uint ideal_reg() const { return 0; } #ifndef PRODUCT virtual void dump_spec(outputStream *st) const; @@ -90,7 +90,6 @@ class StartOSRNode : public StartNode { public: StartOSRNode( Node *root, const TypeTuple *domain ) : StartNode(root, domain) {} virtual int Opcode() const; - static const TypeTuple *osr_domain(); }; @@ -652,7 +651,7 @@ class SafePointScalarMergeNode: public TypeNode { // Simple container for the outgoing projections of a call. Useful // for serious surgery on calls. -class CallProjections : public StackObj { +class CallProjections { public: Node* fallthrough_proj; Node* fallthrough_catchproj; @@ -661,8 +660,26 @@ class CallProjections : public StackObj { Node* catchall_catchproj; Node* catchall_memproj; Node* catchall_ioproj; - Node* resproj; Node* exobj; + uint nb_resproj; + Node* resproj[1]; // at least one projection + + CallProjections(uint nbres) { + fallthrough_proj = nullptr; + fallthrough_catchproj = nullptr; + fallthrough_memproj = nullptr; + fallthrough_ioproj = nullptr; + catchall_catchproj = nullptr; + catchall_memproj = nullptr; + catchall_ioproj = nullptr; + exobj = nullptr; + nb_resproj = nbres; + resproj[0] = nullptr; + for (uint i = 1; i < nb_resproj; i++) { + resproj[i] = nullptr; + } + } + }; class CallGenerator; @@ -683,7 +700,7 @@ class CallNode : public SafePointNode { const char* _name; // Printable name, if _method is null CallNode(const TypeFunc* tf, address addr, const TypePtr* adr_type, JVMState* jvms = nullptr) - : SafePointNode(tf->domain()->cnt(), jvms, adr_type), + : SafePointNode(tf->domain_cc()->cnt(), jvms, adr_type), _tf(tf), _entry_point(addr), _cnt(COUNT_UNKNOWN), @@ -710,7 +727,7 @@ class CallNode : public SafePointNode { virtual bool cmp(const Node &n) const; virtual uint size_of() const = 0; virtual void calling_convention(BasicType* sig_bt, VMRegPair* parm_regs, uint argcnt) const; - virtual Node* match(const ProjNode* proj, const Matcher* m); + virtual Node* match(const ProjNode* proj, const Matcher* m, const RegMask* mask); virtual uint ideal_reg() const { return NotAMachineReg; } // Are we guaranteed that this node is a safepoint? Not true for leaf calls and // for some macro nodes whose expansion does not have a safepoint on the fast path. @@ -724,21 +741,23 @@ class CallNode : public SafePointNode { virtual bool may_modify(const TypeOopPtr* t_oop, PhaseValues* phase); // Does this node have a use of n other than in debug information? bool has_non_debug_use(Node* n); + bool has_debug_use(Node* n); // Returns the unique CheckCastPP of a call // or result projection is there are several CheckCastPP // or returns null if there is no one. Node* result_cast(); // Does this node returns pointer? bool returns_pointer() const { - const TypeTuple* r = tf()->range(); - return (r->cnt() > TypeFunc::Parms && + const TypeTuple* r = tf()->range_sig(); + return (!tf()->returns_inline_type_as_fields() && + r->cnt() > TypeFunc::Parms && r->field_at(TypeFunc::Parms)->isa_ptr()); } // Collect all the interesting edges from a call for use in // replacing the call by something else. Used by macro expansion // and the late inlining support. - void extract_projections(CallProjections* projs, bool separate_io_proj, bool do_asserts = true) const; + CallProjections* extract_projections(bool separate_io_proj, bool do_asserts = true) const; virtual uint match_edge(uint idx) const; @@ -808,6 +827,9 @@ class CallJavaNode : public CallNode { class CallStaticJavaNode : public CallJavaNode { virtual bool cmp( const Node &n ) const; virtual uint size_of() const; // Size is bigger + + bool remove_unknown_flat_array_load(PhaseIterGVN* igvn, Node* ctl, Node* mem, Node* unc_arg); + public: CallStaticJavaNode(Compile* C, const TypeFunc* tf, address addr, ciMethod* method) : CallJavaNode(tf, addr, method) { @@ -816,6 +838,17 @@ class CallStaticJavaNode : public CallJavaNode { init_flags(Flag_is_macro); C->add_macro_node(this); } + const TypeTuple *r = tf->range_sig(); + if (InlineTypeReturnedAsFields && + method != nullptr && + method->is_method_handle_intrinsic() && + r->cnt() > TypeFunc::Parms && + r->field_at(TypeFunc::Parms)->isa_oopptr() && + r->field_at(TypeFunc::Parms)->is_oopptr()->can_be_inline_type()) { + // Make sure this call is processed by PhaseMacroExpand::expand_mh_intrinsic_return + init_flags(Flag_is_macro); + C->add_macro_node(this); + } } CallStaticJavaNode(const TypeFunc* tf, address addr, const char* name, const TypePtr* adr_type) : CallJavaNode(tf, addr, nullptr) { @@ -953,6 +986,7 @@ class CallLeafNoFPNode : public CallLeafNode { init_class_id(Class_CallLeafNoFP); } virtual int Opcode() const; + virtual uint match_edge(uint idx) const; }; //------------------------------CallLeafVectorNode------------------------------- @@ -995,6 +1029,9 @@ class AllocateNode : public CallNode { InitialTest, // slow-path test (may be constant) ALength, // array length (or TOP if none) ValidLengthTest, + InlineType, // InlineTypeNode if this is an inline type allocation + InitValue, // Init value for null-free inline type arrays + RawInitValue, // Same as above but as raw machine word ParmLimit }; @@ -1005,6 +1042,9 @@ class AllocateNode : public CallNode { fields[InitialTest] = TypeInt::BOOL; fields[ALength] = t; // length (can be a bad length) fields[ValidLengthTest] = TypeInt::BOOL; + fields[InlineType] = Type::BOTTOM; + fields[InitValue] = TypeInstPtr::NOTNULL; + fields[RawInitValue] = TypeX_X; const TypeTuple *domain = TypeTuple::make(ParmLimit, fields); @@ -1022,10 +1062,12 @@ class AllocateNode : public CallNode { bool _is_non_escaping; // True when MemBar for new is redundant with MemBar at initialzer exit bool _is_allocation_MemBar_redundant; + bool _larval; virtual uint size_of() const; // Size is bigger AllocateNode(Compile* C, const TypeFunc *atype, Node *ctrl, Node *mem, Node *abio, - Node *size, Node *klass_node, Node *initial_test); + Node *size, Node *klass_node, Node *initial_test, + InlineTypeNode* inline_type_node = nullptr); // Expansion modifies the JVMState, so we need to deep clone it virtual bool needs_deep_clone_jvms(Compile* C) { return true; } virtual int Opcode() const; @@ -1090,7 +1132,7 @@ class AllocateNode : public CallNode { void compute_MemBar_redundancy(ciMethod* initializer); bool is_allocation_MemBar_redundant() { return _is_allocation_MemBar_redundant; } - Node* make_ideal_mark(PhaseGVN *phase, Node* obj, Node* control, Node* mem); + Node* make_ideal_mark(PhaseGVN* phase, Node* control, Node* mem); NOT_PRODUCT(virtual void dump_spec(outputStream* st) const;) }; @@ -1102,14 +1144,18 @@ class AllocateNode : public CallNode { class AllocateArrayNode : public AllocateNode { public: AllocateArrayNode(Compile* C, const TypeFunc* atype, Node* ctrl, Node* mem, Node* abio, Node* size, Node* klass_node, - Node* initial_test, Node* count_val, Node* valid_length_test) + Node* initial_test, Node* count_val, Node* valid_length_test, + Node* init_value, Node* raw_init_value) : AllocateNode(C, atype, ctrl, mem, abio, size, klass_node, initial_test) { init_class_id(Class_AllocateArray); - set_req(AllocateNode::ALength, count_val); + set_req(AllocateNode::ALength, count_val); set_req(AllocateNode::ValidLengthTest, valid_length_test); + init_req(AllocateNode::InitValue, init_value); + init_req(AllocateNode::RawInitValue, raw_init_value); } + virtual uint size_of() const { return sizeof(*this); } virtual int Opcode() const; // Dig the length operand out of a array allocation site. diff --git a/src/hotspot/share/opto/castnode.cpp b/src/hotspot/share/opto/castnode.cpp index 6d899c1f950..e52bef9e7d5 100644 --- a/src/hotspot/share/opto/castnode.cpp +++ b/src/hotspot/share/opto/castnode.cpp @@ -28,9 +28,12 @@ #include "opto/castnode.hpp" #include "opto/cfgnode.hpp" #include "opto/connode.hpp" +#include "opto/graphKit.hpp" +#include "opto/inlinetypenode.hpp" #include "opto/loopnode.hpp" #include "opto/matcher.hpp" #include "opto/phaseX.hpp" +#include "opto/rootnode.hpp" #include "opto/subnode.hpp" #include "opto/type.hpp" #include "utilities/checkedCast.hpp" @@ -98,13 +101,28 @@ const Type* ConstraintCastNode::Value(PhaseGVN* phase) const { //------------------------------Ideal------------------------------------------ // Return a node which is more "ideal" than the current node. Strip out // control copies -Node* ConstraintCastNode::Ideal(PhaseGVN* phase, bool can_reshape) { +Node *ConstraintCastNode::Ideal(PhaseGVN *phase, bool can_reshape) { if (in(0) != nullptr && remove_dead_region(phase, can_reshape)) { return this; } + + // Push cast through InlineTypeNode + InlineTypeNode* vt = in(1)->isa_InlineType(); + if (vt != nullptr && phase->type(vt)->filter_speculative(_type) != Type::TOP) { + Node* cast = clone(); + cast->set_req(1, vt->get_oop()); + vt = vt->clone()->as_InlineType(); + if (!_type->maybe_null()) { + vt->as_InlineType()->set_null_marker(*phase); + } + vt->set_oop(*phase, phase->transform(cast)); + return vt; + } + if (in(1) != nullptr && phase->type(in(1)) != Type::TOP) { return TypeNode::Ideal(phase, can_reshape); } + return nullptr; } @@ -423,6 +441,16 @@ Node* CastLLNode::Ideal(PhaseGVN* phase, bool can_reshape) { return nullptr; } +//============================================================================= +//------------------------------Identity--------------------------------------- +// If input is already higher or equal to cast type, then this is an identity. +Node* CheckCastPPNode::Identity(PhaseGVN* phase) { + if (in(1)->is_InlineType() && _type->isa_instptr() && phase->type(in(1))->inline_klass()->is_subtype_of(_type->is_instptr()->instance_klass())) { + return in(1); + } + return ConstraintCastNode::Identity(phase); +} + //------------------------------Value------------------------------------------ // Take 'join' of input and cast-up type, unless working with an Interface const Type* CheckCastPPNode::Value(PhaseGVN* phase) const { @@ -439,11 +467,21 @@ const Type* CheckCastPPNode::Value(PhaseGVN* phase) const { const TypePtr *my_type = _type->isa_ptr(); const Type *result = _type; if (in_type != nullptr && my_type != nullptr) { + // TODO 8302672 + if (!StressReflectiveCode && my_type->isa_aryptr() && in_type->isa_aryptr()) { + // Propagate array properties (not flat/null-free) + // Don't do this when StressReflectiveCode is enabled because it might lead to + // a dying data path while the corresponding flat/null-free check is not folded. + my_type = my_type->is_aryptr()->update_properties(in_type->is_aryptr()); + if (my_type == nullptr) { + return Type::TOP; // Inconsistent properties + } + } TypePtr::PTR in_ptr = in_type->ptr(); if (in_ptr == TypePtr::Null) { result = in_type; } else if (in_ptr != TypePtr::Constant) { - result = my_type->cast_to_ptr_type(my_type->join_ptr(in_ptr)); + result = my_type->cast_to_ptr_type(my_type->join_ptr(in_ptr)); } } @@ -530,6 +568,22 @@ const Type* CastP2XNode::Value(PhaseGVN* phase) const { uintptr_t bits = (uintptr_t) t->is_rawptr()->get_con(); return TypeX::make(bits); } + + if (t->is_zero_type() || !t->maybe_null()) { + for (DUIterator_Fast imax, i = fast_outs(imax); i < imax; i++) { + Node* u = fast_out(i); + if (u->Opcode() == Op_OrL) { + for (DUIterator_Fast jmax, j = u->fast_outs(jmax); j < jmax; j++) { + Node* cmp = u->fast_out(j); + if (cmp->Opcode() == Op_CmpL) { + // Give CmpL a chance to get optimized + phase->record_for_igvn(cmp); + } + } + } + } + } + return CastP2XNode::bottom_type(); } diff --git a/src/hotspot/share/opto/castnode.hpp b/src/hotspot/share/opto/castnode.hpp index 3c6ade64aa8..4921e8e17f3 100644 --- a/src/hotspot/share/opto/castnode.hpp +++ b/src/hotspot/share/opto/castnode.hpp @@ -216,6 +216,7 @@ class CheckCastPPNode: public ConstraintCastNode { init_class_id(Class_CheckCastPP); } + virtual Node* Identity(PhaseGVN* phase); virtual const Type* Value(PhaseGVN* phase) const; virtual int Opcode() const; virtual uint ideal_reg() const { return Op_RegP; } @@ -236,6 +237,17 @@ class CastX2PNode : public Node { virtual const Type *bottom_type() const { return TypeRawPtr::BOTTOM; } }; +// Cast an integer to a narrow oop +class CastI2NNode : public TypeNode { + public: + CastI2NNode(Node* ctrl, Node* n, const Type* t) : TypeNode(t, 2) { + init_req(0, ctrl); + init_req(1, n); + } + virtual int Opcode() const; + virtual uint ideal_reg() const { return Op_RegN; } +}; + //------------------------------CastP2XNode------------------------------------- // Used in both 32-bit and 64-bit land. // Used for card-marks and unsafe pointer math. @@ -252,6 +264,4 @@ class CastP2XNode : public Node { virtual bool depends_only_on_test() const { return false; } }; - - #endif // SHARE_OPTO_CASTNODE_HPP diff --git a/src/hotspot/share/opto/cfgnode.cpp b/src/hotspot/share/opto/cfgnode.cpp index ef912ff471a..3a3f5b3bc21 100644 --- a/src/hotspot/share/opto/cfgnode.cpp +++ b/src/hotspot/share/opto/cfgnode.cpp @@ -32,6 +32,7 @@ #include "opto/cfgnode.hpp" #include "opto/connode.hpp" #include "opto/convertnode.hpp" +#include "opto/inlinetypenode.hpp" #include "opto/loopnode.hpp" #include "opto/machnode.hpp" #include "opto/movenode.hpp" @@ -520,6 +521,7 @@ bool RegionNode::is_diamond() const { } return true; } + //------------------------------Ideal------------------------------------------ // Return a node which is more "ideal" than the current node. Must preserve // the CFG, but we can still strip out dead paths. @@ -966,7 +968,8 @@ bool RegionNode::optimize_trichotomy(PhaseIterGVN* igvn) { cmp2->Opcode() == Op_CmpF || cmp2->Opcode() == Op_CmpD || cmp1->Opcode() == Op_CmpP || cmp1->Opcode() == Op_CmpN || cmp2->Opcode() == Op_CmpP || cmp2->Opcode() == Op_CmpN || - cmp1->is_SubTypeCheck() || cmp2->is_SubTypeCheck()) { + cmp1->is_SubTypeCheck() || cmp2->is_SubTypeCheck() || + cmp1->is_FlatArrayCheck() || cmp2->is_FlatArrayCheck()) { // Floats and pointers don't exactly obey trichotomy. To be on the safe side, don't transform their tests. // SubTypeCheck is not commutative return false; @@ -1045,7 +1048,7 @@ Node *Node::nonnull_req() const { //============================================================================= -// note that these functions assume that the _adr_type field is flattened +// note that these functions assume that the _adr_type field is flat uint PhiNode::hash() const { const Type* at = _adr_type; return TypeNode::hash() + (at ? at->hash() : 0); @@ -1063,7 +1066,7 @@ const TypePtr* flatten_phi_adr_type(const TypePtr* at) { // create a new phi with edges matching r and set (initially) to x PhiNode* PhiNode::make(Node* r, Node* x, const Type *t, const TypePtr* at) { uint preds = r->req(); // Number of predecessor paths - assert(t != Type::MEMORY || at == flatten_phi_adr_type(at), "flatten at"); + assert(t != Type::MEMORY || at == flatten_phi_adr_type(at) || (flatten_phi_adr_type(at) == TypeAryPtr::INLINES && Compile::current()->flat_accesses_share_alias()), "flatten at"); PhiNode* p = new PhiNode(r, t, at); for (uint j = 1; j < preds; j++) { // Fill in all inputs, except those which the region does not yet have @@ -1192,6 +1195,14 @@ void PhiNode::verify_adr_type(bool recursive) const { if (Node::in_dump()) return; // muzzle asserts when printing assert((_type == Type::MEMORY) == (_adr_type != nullptr), "adr_type for memory phis only"); + // Flat array element shouldn't get their own memory slice until flat_accesses_share_alias is cleared. + // It could be the graph has no loads/stores and flat_accesses_share_alias is never cleared. EA could still + // creates per element Phis but that wouldn't be a problem as there are no memory accesses for that array. + assert(_adr_type == nullptr || _adr_type->isa_aryptr() == nullptr || + _adr_type->is_aryptr()->is_known_instance() || + !_adr_type->is_aryptr()->is_flat() || + !Compile::current()->flat_accesses_share_alias() || + _adr_type == TypeAryPtr::INLINES, "flat array element shouldn't get its own slice yet"); if (!VerifyAliases) return; // verify thoroughly only if requested @@ -1410,6 +1421,7 @@ bool PhiNode::try_clean_memory_phi(PhaseIterGVN* igvn) { } return false; } + //----------------------------check_cmove_id----------------------------------- // Check for CMove'ing a constant after comparing against the constant. // Happens all the time now, since if we compare equality vs a constant in @@ -1464,6 +1476,10 @@ Node* PhiNode::Identity(PhaseGVN* phase) { if (uin != nullptr) { return uin; } + uin = unique_constant_input_recursive(phase); + if (uin != nullptr) { + return uin; + } int true_path = is_diamond_phi(); // Delay CMove'ing identity if Ideal has not had the chance to handle unsafe cases, yet. @@ -1563,6 +1579,42 @@ Node* PhiNode::unique_input(PhaseValues* phase, bool uncast) { return nullptr; } +// Find the unique input, try to look recursively through input Phis +Node* PhiNode::unique_constant_input_recursive(PhaseGVN* phase) { + if (!phase->is_IterGVN()) { + return nullptr; + } + + ResourceMark rm; + Node* unique = nullptr; + Unique_Node_List visited; + visited.push(this); + + for (uint visited_idx = 0; visited_idx < visited.size(); visited_idx++) { + Node* current = visited.at(visited_idx); + for (uint i = 1; i < current->req(); i++) { + Node* phi_in = current->in(i); + if (phi_in == nullptr) { + continue; + } + + if (phi_in->is_Phi()) { + visited.push(phi_in); + } else { + if (unique == nullptr) { + if (!phi_in->is_Con()) { + return nullptr; + } + unique = phi_in; + } else if (unique != phi_in) { + return nullptr; + } + } + } + } + return unique; +} + //------------------------------is_x2logic------------------------------------- // Check for simple convert-to-boolean pattern // If:(C Bool) Region:(IfF IfT) Phi:(Region 0 1) @@ -2030,6 +2082,58 @@ bool PhiNode::wait_for_region_igvn(PhaseGVN* phase) { return delay; } +// Push inline type input nodes (and null) down through the phi recursively (can handle data loops). +InlineTypeNode* PhiNode::push_inline_types_down(PhaseGVN* phase, bool can_reshape, ciInlineKlass* inline_klass) { + assert(inline_klass != nullptr, "must be"); + InlineTypeNode* vt = InlineTypeNode::make_null(*phase, inline_klass, /* transform = */ false)->clone_with_phis(phase, in(0), nullptr, !_type->maybe_null(), true); + if (can_reshape) { + // Replace phi right away to be able to use the inline + // type node when reaching the phi again through data loops. + PhaseIterGVN* igvn = phase->is_IterGVN(); + for (DUIterator_Fast imax, i = fast_outs(imax); i < imax; i++) { + Node* u = fast_out(i); + igvn->rehash_node_delayed(u); + imax -= u->replace_edge(this, vt); + --i; + } + igvn->rehash_node_delayed(this); + assert(outcnt() == 0, "should be dead now"); + } + ResourceMark rm; + Node_List casts; + for (uint i = 1; i < req(); ++i) { + Node* n = in(i); + while (n->is_ConstraintCast()) { + casts.push(n); + n = n->in(1); + } + if (phase->type(n)->is_zero_type()) { + n = InlineTypeNode::make_null(*phase, inline_klass); + } else if (n->is_Phi()) { + assert(can_reshape, "can only handle phis during IGVN"); + n = phase->transform(n->as_Phi()->push_inline_types_down(phase, can_reshape, inline_klass)); + } + while (casts.size() != 0) { + // Push the cast(s) through the InlineTypeNode + // TODO 8302217 Can we avoid cloning? See InlineTypeNode::clone_if_required + Node* cast = casts.pop()->clone(); + cast->set_req_X(1, n->as_InlineType()->get_oop(), phase); + n = n->clone(); + n->as_InlineType()->set_oop(*phase, phase->transform(cast)); + n = phase->transform(n); + if (n->is_top()) { + break; + } + } + bool transform = !can_reshape && (i == (req()-1)); // Transform phis on last merge + assert(n->is_top() || n->is_InlineType(), "Only InlineType or top at this point."); + if (n->is_InlineType()) { + vt->merge_with(phase, n->as_InlineType(), i, transform); + } // else nothing to do: phis above vt created by clone_with_phis are initialized to top already. + } + return vt; +} + // If the Phi's Region is in an irreducible loop, and the Region // has had an input removed, but not yet transformed, it could be // that the Region (and this Phi) are not reachable from Root. @@ -2430,6 +2534,8 @@ Node *PhiNode::Ideal(PhaseGVN *phase, bool can_reshape) { // This split breaks the circularity and consequently does not lead to // non-termination. uint merge_width = 0; + // TODO revisit this with JDK-8247216 + bool mergemem_only = true; bool split_always_terminates = false; // Is splitting guaranteed to terminate? for( uint i=1; ibase_memory() == this) { split_always_terminates = true; } + } else { + mergemem_only = false; } } @@ -2468,7 +2576,7 @@ Node *PhiNode::Ideal(PhaseGVN *phase, bool can_reshape) { // are two Phis involved. Repeatedly splitting the Phis through the // MergeMem leads to non-termination. We check for non-termination below. // Only check for non-termination if necessary. - if (!split_always_terminates && adr_type() == TypePtr::BOTTOM && + if (!mergemem_only && !split_always_terminates && adr_type() == TypePtr::BOTTOM && merge_width > Compile::AliasIdxRaw) { split_always_terminates = is_split_through_mergemem_terminating(); } @@ -2503,7 +2611,7 @@ Node *PhiNode::Ideal(PhaseGVN *phase, bool can_reshape) { } } } - } else if (split_always_terminates) { + } else if (mergemem_only || split_always_terminates) { // If all inputs reference this phi (directly or through data nodes) - // it is a dead loop. bool saw_safe_input = false; @@ -2539,6 +2647,11 @@ Node *PhiNode::Ideal(PhaseGVN *phase, bool can_reshape) { Node *ii = in(i); if (ii->is_MergeMem()) { MergeMemNode* n = ii->as_MergeMem(); + if (igvn) { + // TODO revisit this with JDK-8247216 + // Put 'n' on the worklist because it might be modified by MergeMemStream::iteration_setup + igvn->_worklist.push(n); + } for (MergeMemStream mms(result, n); mms.next_non_empty2(); ) { // If we have not seen this slice yet, make a phi for it. bool made_new_phi = false; @@ -2581,7 +2694,7 @@ Node *PhiNode::Ideal(PhaseGVN *phase, bool can_reshape) { Node *ii = in(i); Node *new_in = MemNode::optimize_memory_chain(ii, at, nullptr, phase); if (ii != new_in ) { - set_req(i, new_in); + set_req_X(i, new_in, phase->is_IterGVN()); progress = this; } } @@ -2649,6 +2762,11 @@ Node *PhiNode::Ideal(PhaseGVN *phase, bool can_reshape) { } #endif + Node* inline_type = try_push_inline_types_down(phase, can_reshape); + if (inline_type != this) { + return inline_type; + } + // Try to convert a Phi with two duplicated convert nodes into a phi of the pre-conversion type and the convert node // proceeding the phi, to de-duplicate the convert node and compact the IR. if (can_reshape && progress == nullptr) { @@ -2692,6 +2810,101 @@ Node *PhiNode::Ideal(PhaseGVN *phase, bool can_reshape) { return progress; // Return any progress } +// Check recursively if inputs are either an inline type, constant null +// or another Phi (including self references through data loops). If so, +// push the inline types down through the phis to enable folding of loads. +Node* PhiNode::try_push_inline_types_down(PhaseGVN* phase, const bool can_reshape) { + if (!can_be_inline_type()) { + return this; + } + + ciInlineKlass* inline_klass; + if (can_push_inline_types_down(phase, can_reshape, inline_klass)) { + assert(inline_klass != nullptr, "must be"); + return push_inline_types_down(phase, can_reshape, inline_klass); + } + return this; +} + +bool PhiNode::can_push_inline_types_down(PhaseGVN* phase, const bool can_reshape, ciInlineKlass*& inline_klass) { + if (req() <= 2) { + // Dead phi. + return false; + } + inline_klass = nullptr; + + // TODO 8302217 We need to prevent endless pushing through + bool only_phi = (outcnt() != 0); + for (DUIterator_Fast imax, i = fast_outs(imax); i < imax; i++) { + Node* n = fast_out(i); + if (n->is_InlineType() && n->in(1) == this) { + return false; + } + if (!n->is_Phi()) { + only_phi = false; + } + } + if (only_phi) { + return false; + } + + ResourceMark rm; + Unique_Node_List worklist; + worklist.push(this); + Node_List casts; + + for (uint next = 0; next < worklist.size(); next++) { + Node* phi = worklist.at(next); + for (uint i = 1; i < phi->req(); i++) { + Node* n = phi->in(i); + if (n == nullptr) { + return false; + } + while (n->is_ConstraintCast()) { + if (n->in(0) != nullptr && n->in(0)->is_top()) { + // Will die, don't optimize + return false; + } + casts.push(n); + n = n->in(1); + } + const Type* type = phase->type(n); + if (n->is_InlineType() && (inline_klass == nullptr || inline_klass == type->inline_klass())) { + inline_klass = type->inline_klass(); + } else if (n->is_Phi() && can_reshape && n->bottom_type()->isa_ptr()) { + worklist.push(n); + } else if (!type->is_zero_type()) { + return false; + } + } + } + if (inline_klass == nullptr) { + return false; + } + + // Check if cast nodes can be pushed through + const Type* t = Type::get_const_type(inline_klass); + while (casts.size() != 0 && t != nullptr) { + Node* cast = casts.pop(); + if (t->filter(cast->bottom_type()) == Type::TOP) { + return false; + } + } + + return true; +} + +#ifdef ASSERT +bool PhiNode::can_push_inline_types_down(PhaseGVN* phase) { + if (!can_be_inline_type()) { + return false; + } + + ciInlineKlass* inline_klass; + return can_push_inline_types_down(phase, true, inline_klass); +} +#endif // ASSERT + static int compare_types(const Type* const& e1, const Type* const& e2) { return (intptr_t)e1 - (intptr_t)e2; } @@ -3074,6 +3287,12 @@ Node* CreateExNode::Identity(PhaseGVN* phase) { // We only come from CatchProj, unless the CatchProj goes away. // If the CatchProj is optimized away, then we just carry the // exception oop through. + + // CheckCastPPNode::Ideal() for inline types reuses the exception + // paths of a call to perform an allocation: we can see a Phi here. + if (in(1)->is_Phi()) { + return this; + } CallNode *call = in(1)->in(0)->as_Call(); return (in(0)->is_CatchProj() && in(0)->in(0)->is_Catch() && diff --git a/src/hotspot/share/opto/cfgnode.hpp b/src/hotspot/share/opto/cfgnode.hpp index a0e780c0e57..22716a731e8 100644 --- a/src/hotspot/share/opto/cfgnode.hpp +++ b/src/hotspot/share/opto/cfgnode.hpp @@ -180,6 +180,9 @@ class PhiNode : public TypeNode { bool must_wait_for_region_in_irreducible_loop(PhaseGVN* phase) const; + bool can_push_inline_types_down(PhaseGVN* phase, bool can_reshape, ciInlineKlass*& inline_klass); + InlineTypeNode* push_inline_types_down(PhaseGVN* phase, bool can_reshape, ciInlineKlass* inline_klass); + bool is_split_through_mergemem_terminating() const; public: @@ -229,6 +232,7 @@ class PhiNode : public TypeNode { } return uin; } + Node* unique_constant_input_recursive(PhaseGVN* phase); // Check for a simple dead loop. enum LoopSafety { Safe = 0, Unsafe, UnsafeLoop }; @@ -255,6 +259,13 @@ class PhiNode : public TypeNode { type()->higher_equal(tp); } + bool can_be_inline_type() const { + return EnableValhalla && _type->isa_instptr() && _type->is_instptr()->can_be_inline_type(); + } + + Node* try_push_inline_types_down(PhaseGVN* phase, bool can_reshape); + DEBUG_ONLY(bool can_push_inline_types_down(PhaseGVN* phase);) + virtual const Type* Value(PhaseGVN* phase) const; virtual Node* Identity(PhaseGVN* phase); virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); @@ -448,6 +459,8 @@ class IfNode : public MultiBranchNode { // Returns null is it couldn't improve the type. static const TypeInt* filtered_int_type(PhaseGVN* phase, Node* val, Node* if_proj); + bool is_flat_array_check(PhaseTransform* phase, Node** array = nullptr); + AssertionPredicateType assertion_predicate_type() const { return _assertion_predicate_type; } @@ -731,6 +744,7 @@ class BlackholeNode : public MultiNode { public: BlackholeNode(Node* ctrl) : MultiNode(1) { init_req(TypeFunc::Control, ctrl); + init_class_id(Class_Blackhole); } virtual int Opcode() const; virtual uint ideal_reg() const { return 0; } // not matched in the AD file diff --git a/src/hotspot/share/opto/chaitin.cpp b/src/hotspot/share/opto/chaitin.cpp index 926a5176232..e5f8f032250 100644 --- a/src/hotspot/share/opto/chaitin.cpp +++ b/src/hotspot/share/opto/chaitin.cpp @@ -1855,10 +1855,10 @@ Node* PhaseChaitin::find_base_for_derived(Node** derived_base_map, Node* derived // can't happen at run-time but the optimizer cannot deduce it so // we have to handle it gracefully. assert(!derived->bottom_type()->isa_narrowoop() || - derived->bottom_type()->make_ptr()->is_ptr()->_offset == 0, "sanity"); + derived->bottom_type()->make_ptr()->is_ptr()->offset() == 0, "sanity"); const TypePtr *tj = derived->bottom_type()->isa_ptr(); // If its an OOP with a non-zero offset, then it is derived. - if( tj == nullptr || tj->_offset == 0 ) { + if (tj == nullptr || tj->offset() == 0) { derived_base_map[derived->_idx] = derived; return derived; } @@ -2024,9 +2024,9 @@ bool PhaseChaitin::stretch_base_pointer_live_ranges(ResourceArea *a) { Node *derived = lrgs(neighbor)._def; const TypePtr *tj = derived->bottom_type()->isa_ptr(); assert(!derived->bottom_type()->isa_narrowoop() || - derived->bottom_type()->make_ptr()->is_ptr()->_offset == 0, "sanity"); + derived->bottom_type()->make_ptr()->is_ptr()->offset() == 0, "sanity"); // If its an OOP with a non-zero offset, then it is derived. - if( tj && tj->_offset != 0 && tj->isa_oop_ptr() ) { + if (tj && tj->offset() != 0 && tj->isa_oop_ptr()) { Node *base = find_base_for_derived(derived_base_map, derived, maxlrg); assert(base->_idx < _lrg_map.size(), ""); // Add reaching DEFs of derived pointer and base pointer as a @@ -2316,7 +2316,7 @@ void PhaseChaitin::dump_for_spill_split_recycle() const { void PhaseChaitin::dump_frame() const { const char *fp = OptoReg::regname(OptoReg::c_frame_pointer); - const TypeTuple *domain = C->tf()->domain(); + const TypeTuple *domain = C->tf()->domain_cc(); const int argcnt = domain->cnt() - TypeFunc::Parms; // Incoming arguments in registers dump @@ -2525,11 +2525,11 @@ void PhaseChaitin::verify_base_ptrs(ResourceArea* a) const { worklist.push(check->in(m)); } } else if (check->is_Con()) { - if (is_derived && check->bottom_type()->is_ptr()->_offset != 0) { + if (is_derived && check->bottom_type()->is_ptr()->offset() != 0) { // Derived is null+non-zero offset, base must be null. assert(check->bottom_type()->is_ptr()->ptr() == TypePtr::Null, "Bad derived pointer"); } else { - assert(check->bottom_type()->is_ptr()->_offset == 0, "Bad base pointer"); + assert(check->bottom_type()->is_ptr()->offset() == 0, "Bad base pointer"); // Base either ConP(nullptr) or loadConP if (check->is_Mach()) { assert(check->as_Mach()->ideal_Opcode() == Op_ConP, "Bad base pointer"); @@ -2538,7 +2538,7 @@ void PhaseChaitin::verify_base_ptrs(ResourceArea* a) const { check->bottom_type()->is_ptr()->ptr() == TypePtr::Null, "Bad base pointer"); } } - } else if (check->bottom_type()->is_ptr()->_offset == 0) { + } else if (check->bottom_type()->is_ptr()->offset() == 0) { if (check->is_Proj() || (check->is_Mach() && (check->as_Mach()->ideal_Opcode() == Op_CreateEx || check->as_Mach()->ideal_Opcode() == Op_ThreadLocal || diff --git a/src/hotspot/share/opto/classes.cpp b/src/hotspot/share/opto/classes.cpp index b760a179b57..089ad2e5358 100644 --- a/src/hotspot/share/opto/classes.cpp +++ b/src/hotspot/share/opto/classes.cpp @@ -31,6 +31,7 @@ #include "opto/convertnode.hpp" #include "opto/countbitsnode.hpp" #include "opto/divnode.hpp" +#include "opto/inlinetypenode.hpp" #include "opto/intrinsicnode.hpp" #include "opto/locknode.hpp" #include "opto/loopnode.hpp" diff --git a/src/hotspot/share/opto/classes.hpp b/src/hotspot/share/opto/classes.hpp index 587d5fad8f2..e59a655afb2 100644 --- a/src/hotspot/share/opto/classes.hpp +++ b/src/hotspot/share/opto/classes.hpp @@ -72,6 +72,7 @@ macro(CastII) macro(CastLL) macro(CastVV) macro(CastX2P) +macro(CastI2N) macro(CastP2X) macro(CastPP) macro(Catch) @@ -187,6 +188,7 @@ macro(EncodeP) macro(EncodePKlass) macro(FastLock) macro(FastUnlock) +macro(FlatArrayCheck) macro(FmaD) macro(FmaF) macro(FmaHF) @@ -357,6 +359,7 @@ macro(StoreD) macro(StoreF) macro(StoreI) macro(StoreL) +macro(StoreLSpecial) macro(StoreP) macro(StoreN) macro(StoreNKlass) @@ -383,6 +386,7 @@ macro(URShiftI) macro(URShiftL) macro(XorI) macro(XorL) +macro(InlineType) macro(Vector) macro(AddVB) macro(AddVS) diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index cef7aa61219..c30fad20458 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -59,6 +59,7 @@ #include "opto/divnode.hpp" #include "opto/escape.hpp" #include "opto/idealGraphPrinter.hpp" +#include "opto/inlinetypenode.hpp" #include "opto/locknode.hpp" #include "opto/loopnode.hpp" #include "opto/machnode.hpp" @@ -66,6 +67,7 @@ #include "opto/matcher.hpp" #include "opto/mathexactnode.hpp" #include "opto/memnode.hpp" +#include "opto/movenode.hpp" #include "opto/mulnode.hpp" #include "opto/narrowptrnode.hpp" #include "opto/node.hpp" @@ -405,6 +407,9 @@ void Compile::remove_useless_node(Node* dead) { if (dead->for_post_loop_opts_igvn()) { remove_from_post_loop_opts_igvn(dead); } + if (dead->is_InlineType()) { + remove_inline_type(dead); + } if (dead->for_merge_stores_igvn()) { remove_from_merge_stores_igvn(dead); } @@ -452,6 +457,9 @@ void Compile::disconnect_useless_nodes(Unique_Node_List& useful, Unique_Node_Lis assert(useful.member(n->unique_out()), "do not push a useless node"); worklist.push(n->unique_out()); } + if (n->outcnt() == 0) { + worklist.push(n); + } } remove_useless_nodes(_macro_nodes, useful); // remove useless macro nodes @@ -460,6 +468,12 @@ void Compile::disconnect_useless_nodes(Unique_Node_List& useful, Unique_Node_Lis remove_useless_nodes(_template_assertion_predicate_opaques, useful); remove_useless_nodes(_expensive_nodes, useful); // remove useless expensive nodes remove_useless_nodes(_for_post_loop_igvn, useful); // remove useless node recorded for post loop opts IGVN pass + remove_useless_nodes(_inline_type_nodes, useful); // remove useless inline type nodes +#ifdef ASSERT + if (_modified_nodes != nullptr) { + _modified_nodes->remove_useless_nodes(useful.member_set()); + } +#endif remove_useless_nodes(_for_merge_stores_igvn, useful); // remove useless node recorded for merge stores IGVN pass remove_useless_unstable_if_traps(useful); // remove useless unstable_if traps remove_useless_coarsened_locks(useful); // remove useless coarsened locks nodes @@ -646,6 +660,7 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci, _inlining_incrementally(false), _do_cleanup(false), _has_reserved_stack_access(target->has_reserved_stack_access()), + _has_circular_inline_type(false), #ifndef PRODUCT _igv_idx(0), _trace_opto_output(directive->TraceOptoOutputOption), @@ -665,6 +680,7 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci, _template_assertion_predicate_opaques(comp_arena(), 8, 0, nullptr), _expensive_nodes(comp_arena(), 8, 0, nullptr), _for_post_loop_igvn(comp_arena(), 8, 0, nullptr), + _inline_type_nodes (comp_arena(), 8, 0, nullptr), _for_merge_stores_igvn(comp_arena(), 8, 0, nullptr), _unstable_if_traps(comp_arena(), 8, 0, nullptr), _coarsened_locks(comp_arena(), 8, 0, nullptr), @@ -771,17 +787,15 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci, // Set up tf(), start(), and find a CallGenerator. CallGenerator* cg = nullptr; if (is_osr_compilation()) { - const TypeTuple *domain = StartOSRNode::osr_domain(); - const TypeTuple *range = TypeTuple::make_range(method()->signature()); - init_tf(TypeFunc::make(domain, range)); - StartNode* s = new StartOSRNode(root(), domain); + init_tf(TypeFunc::make(method(), /* is_osr_compilation = */ true)); + StartNode* s = new StartOSRNode(root(), tf()->domain_sig()); initial_gvn()->set_type_bottom(s); verify_start(s); cg = CallGenerator::for_osr(method(), entry_bci()); } else { // Normal case. init_tf(TypeFunc::make(method())); - StartNode* s = new StartNode(root(), tf()->domain()); + StartNode* s = new StartNode(root(), tf()->domain_cc()); initial_gvn()->set_type_bottom(s); verify_start(s); float past_uses = method()->interpreter_invocation_count(); @@ -882,6 +896,16 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci, // Now that we know the size of all the monitors we can add a fixed slot // for the original deopt pc. int next_slot = fixed_slots() + (sizeof(address) / VMRegImpl::stack_slot_size); + if (needs_stack_repair()) { + // One extra slot for the special stack increment value + next_slot += 2; + } + // TODO 8284443 Only reserve extra slot if needed + if (InlineTypeReturnedAsFields) { + // One extra slot to hold the null marker for a nullable + // inline type return if we run out of registers. + next_slot += 2; + } set_fixed_slots(next_slot); // Compute when to use implicit null checks. Used by matching trap based @@ -919,6 +943,7 @@ Compile::Compile(ciEnv* ci_env, _inlining_progress(false), _inlining_incrementally(false), _has_reserved_stack_access(false), + _has_circular_inline_type(false), #ifndef PRODUCT _igv_idx(0), _trace_opto_output(directive->TraceOptoOutputOption), @@ -1072,6 +1097,10 @@ void Compile::Init(bool aliasing) { set_do_freq_based_layout(_directive->BlockLayoutByFrequencyOption); _loop_opts_cnt = LoopOptsCount; + _has_flat_accesses = false; + _flat_accesses_share_alias = true; + _scalarize_in_safepoints = false; + set_do_inlining(Inline); set_max_inline_size(MaxInlineSize); set_freq_inline_size(FreqInlineSize); @@ -1356,6 +1385,15 @@ const TypePtr *Compile::flatten_alias_type( const TypePtr *tj ) const { // Erase stability property for alias analysis. tj = ta = ta->cast_to_stable(false); } + if (ta && ta->is_not_flat()) { + // Erase not flat property for alias analysis. + tj = ta = ta->cast_to_not_flat(false); + } + if (ta && ta->is_not_null_free()) { + // Erase not null free property for alias analysis. + tj = ta = ta->cast_to_not_null_free(false); + } + if( ta && is_known_inst ) { if ( offset != Type::OffsetBot && offset > arrayOopDesc::length_offset_in_bytes() ) { @@ -1369,6 +1407,8 @@ const TypePtr *Compile::flatten_alias_type( const TypePtr *tj ) const { // For arrays indexed by constant indices, we flatten the alias // space to include all of the array body. Only the header, klass // and array length can be accessed un-aliased. + // For flat inline type array, each field has its own slice so + // we must include the field offset. if( offset != Type::OffsetBot ) { if( ta->const_oop() ) { // MethodData* or Method* offset = Type::OffsetBot; // Flatten constant access into array body @@ -1409,18 +1449,23 @@ const TypePtr *Compile::flatten_alias_type( const TypePtr *tj ) const { // Arrays of known objects become arrays of unknown objects. if (ta->elem()->isa_narrowoop() && ta->elem() != TypeNarrowOop::BOTTOM) { const TypeAry *tary = TypeAry::make(TypeNarrowOop::BOTTOM, ta->size()); - tj = ta = TypeAryPtr::make(ptr,ta->const_oop(),tary,nullptr,false,offset); + tj = ta = TypeAryPtr::make(ptr,ta->const_oop(),tary,nullptr,false,Type::Offset(offset), ta->field_offset()); } if (ta->elem()->isa_oopptr() && ta->elem() != TypeInstPtr::BOTTOM) { const TypeAry *tary = TypeAry::make(TypeInstPtr::BOTTOM, ta->size()); - tj = ta = TypeAryPtr::make(ptr,ta->const_oop(),tary,nullptr,false,offset); + tj = ta = TypeAryPtr::make(ptr,ta->const_oop(),tary,nullptr,false,Type::Offset(offset), ta->field_offset()); + } + // Initially all flattened array accesses share a single slice + if (ta->is_flat() && ta->elem() != TypeInstPtr::BOTTOM && _flat_accesses_share_alias) { + const TypeAry* tary = TypeAry::make(TypeInstPtr::BOTTOM, ta->size(), /* stable= */ false, /* flat= */ true); + tj = ta = TypeAryPtr::make(ptr,ta->const_oop(),tary,nullptr,false,Type::Offset(offset), Type::Offset(Type::OffsetBot)); } // Arrays of bytes and of booleans both use 'bastore' and 'baload' so // cannot be distinguished by bytecode alone. if (ta->elem() == TypeInt::BOOL) { const TypeAry *tary = TypeAry::make(TypeInt::BYTE, ta->size()); ciKlass* aklass = ciTypeArrayKlass::make(T_BYTE); - tj = ta = TypeAryPtr::make(ptr,ta->const_oop(),tary,aklass,false,offset); + tj = ta = TypeAryPtr::make(ptr,ta->const_oop(),tary,aklass,false,Type::Offset(offset), ta->field_offset()); } // During the 2nd round of IterGVN, NotNull castings are removed. // Make sure the Bottom and NotNull variants alias the same. @@ -1470,7 +1515,7 @@ const TypePtr *Compile::flatten_alias_type( const TypePtr *tj ) const { // First handle header references such as a LoadKlassNode, even if the // object's klass is unloaded at compile time (4965979). if (!is_known_inst) { // Do it only for non-instance types - tj = to = TypeInstPtr::make(TypePtr::BotPTR, env()->Object_klass(), false, nullptr, offset); + tj = to = TypeInstPtr::make(TypePtr::BotPTR, env()->Object_klass(), false, nullptr, Type::Offset(offset)); } } else if (offset < 0 || offset >= ik->layout_helper_size_in_bytes()) { // Static fields are in the space above the normal instance @@ -1491,10 +1536,10 @@ const TypePtr *Compile::flatten_alias_type( const TypePtr *tj ) const { // but if not exact, it may include extra interfaces: build new type from the holder class to make sure only // its interfaces are included. if (xk && ik->equals(canonical_holder)) { - assert(tj == TypeInstPtr::make(to->ptr(), canonical_holder, is_known_inst, nullptr, offset, instance_id), "exact type should be canonical type"); + assert(tj == TypeInstPtr::make(to->ptr(), canonical_holder, is_known_inst, nullptr, Type::Offset(offset), instance_id), "exact type should be canonical type"); } else { assert(xk || !is_known_inst, "Known instance should be exact type"); - tj = to = TypeInstPtr::make(to->ptr(), canonical_holder, is_known_inst, nullptr, offset, instance_id); + tj = to = TypeInstPtr::make(to->ptr(), canonical_holder, is_known_inst, nullptr, Type::Offset(offset), instance_id); } } } @@ -1509,18 +1554,17 @@ const TypePtr *Compile::flatten_alias_type( const TypePtr *tj ) const { if ( offset == Type::OffsetBot || (offset >= 0 && (size_t)offset < sizeof(Klass)) ) { tj = tk = TypeInstKlassPtr::make(TypePtr::NotNull, env()->Object_klass(), - offset); + Type::Offset(offset)); } if (tk->isa_aryklassptr() && tk->is_aryklassptr()->elem()->isa_klassptr()) { ciKlass* k = ciObjArrayKlass::make(env()->Object_klass()); if (!k || !k->is_loaded()) { // Only fails for some -Xcomp runs - tj = tk = TypeInstKlassPtr::make(TypePtr::NotNull, env()->Object_klass(), offset); + tj = tk = TypeInstKlassPtr::make(TypePtr::NotNull, env()->Object_klass(), Type::Offset(offset)); } else { - tj = tk = TypeAryKlassPtr::make(TypePtr::NotNull, tk->is_aryklassptr()->elem(), k, offset); + tj = tk = TypeAryKlassPtr::make(TypePtr::NotNull, tk->is_aryklassptr()->elem(), k, Type::Offset(offset), tk->is_not_flat(), tk->is_not_null_free(), tk->is_flat(), tk->is_null_free(), tk->is_atomic(), tk->is_aryklassptr()->is_vm_type()); } } - // Check for precise loads from the primary supertype array and force them // to the supertype cache alias index. Check for generic array loads from // the primary supertype array and also force them to the supertype cache @@ -1650,14 +1694,17 @@ void Compile::grow_alias_types() { //--------------------------------find_alias_type------------------------------ -Compile::AliasType* Compile::find_alias_type(const TypePtr* adr_type, bool no_create, ciField* original_field) { +Compile::AliasType* Compile::find_alias_type(const TypePtr* adr_type, bool no_create, ciField* original_field, bool uncached) { if (!do_aliasing()) { return alias_type(AliasIdxBot); } - AliasCacheEntry* ace = probe_alias_cache(adr_type); - if (ace->_adr_type == adr_type) { - return alias_type(ace->_index); + AliasCacheEntry* ace = nullptr; + if (!uncached) { + ace = probe_alias_cache(adr_type); + if (ace->_adr_type == adr_type) { + return alias_type(ace->_index); + } } // Handle special cases. @@ -1707,14 +1754,23 @@ Compile::AliasType* Compile::find_alias_type(const TypePtr* adr_type, bool no_cr && flat->is_instptr()->instance_klass() == env()->Class_klass()) alias_type(idx)->set_rewritable(false); } + ciField* field = nullptr; if (flat->isa_aryptr()) { #ifdef ASSERT const int header_size_min = arrayOopDesc::base_offset_in_bytes(T_BYTE); // (T_BYTE has the weakest alignment and size restrictions...) assert(flat->offset() < header_size_min, "array body reference must be OffsetBot"); #endif + const Type* elemtype = flat->is_aryptr()->elem(); if (flat->offset() == TypePtr::OffsetBot) { - alias_type(idx)->set_element(flat->is_aryptr()->elem()); + alias_type(idx)->set_element(elemtype); + } + int field_offset = flat->is_aryptr()->field_offset().get(); + if (flat->is_flat() && + field_offset != Type::OffsetBot) { + ciInlineKlass* vk = elemtype->inline_klass(); + field_offset += vk->payload_offset(); + field = vk->get_field_by_offset(field_offset, false); } } if (flat->isa_klassptr()) { @@ -1730,6 +1786,8 @@ Compile::AliasType* Compile::find_alias_type(const TypePtr* adr_type, bool no_cr alias_type(idx)->set_rewritable(false); if (flat->offset() == in_bytes(Klass::java_mirror_offset())) alias_type(idx)->set_rewritable(false); + if (flat->offset() == in_bytes(Klass::layout_helper_offset())) + alias_type(idx)->set_rewritable(false); if (flat->offset() == in_bytes(Klass::secondary_super_cache_offset())) alias_type(idx)->set_rewritable(false); } @@ -1740,38 +1798,50 @@ Compile::AliasType* Compile::find_alias_type(const TypePtr* adr_type, bool no_cr // Check for final fields. const TypeInstPtr* tinst = flat->isa_instptr(); if (tinst && tinst->offset() >= instanceOopDesc::base_offset_in_bytes()) { - ciField* field; if (tinst->const_oop() != nullptr && tinst->instance_klass() == ciEnv::current()->Class_klass() && tinst->offset() >= (tinst->instance_klass()->layout_helper_size_in_bytes())) { // static field ciInstanceKlass* k = tinst->const_oop()->as_instance()->java_lang_Class_klass()->as_instance_klass(); field = k->get_field_by_offset(tinst->offset(), true); + } else if (tinst->is_inlinetypeptr()) { + // Inline type field + ciInlineKlass* vk = tinst->inline_klass(); + field = vk->get_field_by_offset(tinst->offset(), false); } else { ciInstanceKlass *k = tinst->instance_klass(); field = k->get_field_by_offset(tinst->offset(), false); } - assert(field == nullptr || - original_field == nullptr || - (field->holder() == original_field->holder() && - field->offset_in_bytes() == original_field->offset_in_bytes() && - field->is_static() == original_field->is_static()), "wrong field?"); - // Set field() and is_rewritable() attributes. - if (field != nullptr) alias_type(idx)->set_field(field); + } + assert(field == nullptr || + original_field == nullptr || + (field->holder() == original_field->holder() && + field->offset_in_bytes() == original_field->offset_in_bytes() && + field->is_static() == original_field->is_static()), "wrong field?"); + // Set field() and is_rewritable() attributes. + if (field != nullptr) { + alias_type(idx)->set_field(field); + if (flat->isa_aryptr()) { + // Fields of flat arrays are rewritable although they are declared final + assert(flat->is_flat(), "must be a flat array"); + alias_type(idx)->set_rewritable(true); + } } } // Fill the cache for next time. - ace->_adr_type = adr_type; - ace->_index = idx; - assert(alias_type(adr_type) == alias_type(idx), "type must be installed"); + if (!uncached) { + ace->_adr_type = adr_type; + ace->_index = idx; + assert(alias_type(adr_type) == alias_type(idx), "type must be installed"); - // Might as well try to fill the cache for the flattened version, too. - AliasCacheEntry* face = probe_alias_cache(flat); - if (face->_adr_type == nullptr) { - face->_adr_type = flat; - face->_index = idx; - assert(alias_type(flat) == alias_type(idx), "flat type must work too"); + // Might as well try to fill the cache for the flattened version, too. + AliasCacheEntry* face = probe_alias_cache(flat); + if (face->_adr_type == nullptr) { + face->_adr_type = flat; + face->_index = idx; + assert(alias_type(flat) == alias_type(idx), "flat type must work too"); + } } return alias_type(idx); @@ -1893,6 +1963,404 @@ void Compile::process_for_post_loop_opts_igvn(PhaseIterGVN& igvn) { } } +void Compile::add_inline_type(Node* n) { + assert(n->is_InlineType(), "unexpected node"); + _inline_type_nodes.push(n); +} + +void Compile::remove_inline_type(Node* n) { + assert(n->is_InlineType(), "unexpected node"); + if (_inline_type_nodes.contains(n)) { + _inline_type_nodes.remove(n); + } +} + +// Does the return value keep otherwise useless inline type allocations alive? +static bool return_val_keeps_allocations_alive(Node* ret_val) { + ResourceMark rm; + Unique_Node_List wq; + wq.push(ret_val); + bool some_allocations = false; + for (uint i = 0; i < wq.size(); i++) { + Node* n = wq.at(i); + if (n->outcnt() > 1) { + // Some other use for the allocation + return false; + } else if (n->is_InlineType()) { + wq.push(n->in(1)); + } else if (n->is_Phi()) { + for (uint j = 1; j < n->req(); j++) { + wq.push(n->in(j)); + } + } else if (n->is_CheckCastPP() && + n->in(1)->is_Proj() && + n->in(1)->in(0)->is_Allocate()) { + some_allocations = true; + } else if (n->is_CheckCastPP()) { + wq.push(n->in(1)); + } + } + return some_allocations; +} + +void Compile::process_inline_types(PhaseIterGVN &igvn, bool remove) { + // Make sure that the return value does not keep an otherwise unused allocation alive + if (tf()->returns_inline_type_as_fields()) { + Node* ret = nullptr; + for (uint i = 1; i < root()->req(); i++) { + Node* in = root()->in(i); + if (in->Opcode() == Op_Return) { + assert(ret == nullptr, "only one return"); + ret = in; + } + } + if (ret != nullptr) { + Node* ret_val = ret->in(TypeFunc::Parms); + if (igvn.type(ret_val)->isa_oopptr() && + return_val_keeps_allocations_alive(ret_val)) { + igvn.replace_input_of(ret, TypeFunc::Parms, InlineTypeNode::tagged_klass(igvn.type(ret_val)->inline_klass(), igvn)); + assert(ret_val->outcnt() == 0, "should be dead now"); + igvn.remove_dead_node(ret_val); + } + } + } + if (_inline_type_nodes.length() == 0) { + return; + } + // Scalarize inline types in safepoint debug info. + // Delay this until all inlining is over to avoid getting inconsistent debug info. + set_scalarize_in_safepoints(true); + for (int i = _inline_type_nodes.length()-1; i >= 0; i--) { + InlineTypeNode* vt = _inline_type_nodes.at(i)->as_InlineType(); + vt->make_scalar_in_safepoints(&igvn); + igvn.record_for_igvn(vt); + } + if (remove) { + // Remove inline type nodes by replacing them with their oop input + while (_inline_type_nodes.length() > 0) { + InlineTypeNode* vt = _inline_type_nodes.pop()->as_InlineType(); + if (vt->outcnt() == 0) { + igvn.remove_dead_node(vt); + continue; + } + for (DUIterator i = vt->outs(); vt->has_out(i); i++) { + DEBUG_ONLY(bool must_be_buffered = false); + Node* u = vt->out(i); + // Check if any users are blackholes. If so, rewrite them to use either the + // allocated buffer, or individual components, instead of the inline type node + // that goes away. + if (u->is_Blackhole()) { + BlackholeNode* bh = u->as_Blackhole(); + + // Unlink the old input + int idx = bh->find_edge(vt); + assert(idx != -1, "The edge should be there"); + bh->del_req(idx); + --i; + + if (vt->is_allocated(&igvn)) { + // Already has the allocated instance, blackhole that + bh->add_req(vt->get_oop()); + } else { + // Not allocated yet, blackhole the components + for (uint c = 0; c < vt->field_count(); c++) { + bh->add_req(vt->field_value(c)); + } + } + + // Node modified, record for IGVN + igvn.record_for_igvn(bh); + } +#ifdef ASSERT + // Verify that inline type is buffered when replacing by oop + else if (u->is_InlineType()) { + // InlineType uses don't need buffering because they are about to be replaced as well + } else if (u->is_Phi()) { + // TODO 8302217 Remove this once InlineTypeNodes are reliably pushed through + } else { + must_be_buffered = true; + } + if (must_be_buffered && !vt->is_allocated(&igvn)) { + vt->dump(0); + u->dump(0); + assert(false, "Should have been buffered"); + } +#endif + } + igvn.replace_node(vt, vt->get_oop()); + } + } + igvn.optimize(); +} + +void Compile::adjust_flat_array_access_aliases(PhaseIterGVN& igvn) { + if (!_has_flat_accesses) { + return; + } + // Initially, all flat array accesses share the same slice to + // keep dependencies with Object[] array accesses (that could be + // to a flat array) correct. We're done with parsing so we + // now know all flat array accesses in this compile + // unit. Let's move flat array accesses to their own slice, + // one per element field. This should help memory access + // optimizations. + ResourceMark rm; + Unique_Node_List wq; + wq.push(root()); + + Node_List mergememnodes; + Node_List memnodes; + + // Alias index currently shared by all flat memory accesses + int index = get_alias_index(TypeAryPtr::INLINES); + + // Find MergeMem nodes and flat array accesses + for (uint i = 0; i < wq.size(); i++) { + Node* n = wq.at(i); + if (n->is_Mem()) { + const TypePtr* adr_type = nullptr; + adr_type = get_adr_type(get_alias_index(n->adr_type())); + if (adr_type == TypeAryPtr::INLINES) { + memnodes.push(n); + } + } else if (n->is_MergeMem()) { + MergeMemNode* mm = n->as_MergeMem(); + if (mm->memory_at(index) != mm->base_memory()) { + mergememnodes.push(n); + } + } + for (uint j = 0; j < n->req(); j++) { + Node* m = n->in(j); + if (m != nullptr) { + wq.push(m); + } + } + } + + if (memnodes.size() > 0) { + _flat_accesses_share_alias = false; + + // We are going to change the slice for the flat array + // accesses so we need to clear the cache entries that refer to + // them. + for (uint i = 0; i < AliasCacheSize; i++) { + AliasCacheEntry* ace = &_alias_cache[i]; + if (ace->_adr_type != nullptr && + ace->_adr_type->is_flat()) { + ace->_adr_type = nullptr; + ace->_index = (i != 0) ? 0 : AliasIdxTop; // Make sure the nullptr adr_type resolves to AliasIdxTop + } + } + + // Find what aliases we are going to add + int start_alias = num_alias_types()-1; + int stop_alias = 0; + + for (uint i = 0; i < memnodes.size(); i++) { + Node* m = memnodes.at(i); + const TypePtr* adr_type = nullptr; + adr_type = m->adr_type(); +#ifdef ASSERT + m->as_Mem()->set_adr_type(adr_type); +#endif + int idx = get_alias_index(adr_type); + start_alias = MIN2(start_alias, idx); + stop_alias = MAX2(stop_alias, idx); + } + + assert(stop_alias >= start_alias, "should have expanded aliases"); + + Node_Stack stack(0); +#ifdef ASSERT + VectorSet seen(Thread::current()->resource_area()); +#endif + // Now let's fix the memory graph so each flat array access + // is moved to the right slice. Start from the MergeMem nodes. + uint last = unique(); + for (uint i = 0; i < mergememnodes.size(); i++) { + MergeMemNode* current = mergememnodes.at(i)->as_MergeMem(); + Node* n = current->memory_at(index); + MergeMemNode* mm = nullptr; + do { + // Follow memory edges through memory accesses, phis and + // narrow membars and push nodes on the stack. Once we hit + // bottom memory, we pop element off the stack one at a + // time, in reverse order, and move them to the right slice + // by changing their memory edges. + if ((n->is_Phi() && n->adr_type() != TypePtr::BOTTOM) || n->is_Mem() || n->adr_type() == TypeAryPtr::INLINES) { + assert(!seen.test_set(n->_idx), ""); + // Uses (a load for instance) will need to be moved to the + // right slice as well and will get a new memory state + // that we don't know yet. The use could also be the + // backedge of a loop. We put a place holder node between + // the memory node and its uses. We replace that place + // holder with the correct memory state once we know it, + // i.e. when nodes are popped off the stack. Using the + // place holder make the logic work in the presence of + // loops. + if (n->outcnt() > 1) { + Node* place_holder = nullptr; + assert(!n->has_out_with(Op_Node), ""); + for (DUIterator k = n->outs(); n->has_out(k); k++) { + Node* u = n->out(k); + if (u != current && u->_idx < last) { + bool success = false; + for (uint l = 0; l < u->req(); l++) { + if (!stack.is_empty() && u == stack.node() && l == stack.index()) { + continue; + } + Node* in = u->in(l); + if (in == n) { + if (place_holder == nullptr) { + place_holder = new Node(1); + place_holder->init_req(0, n); + } + igvn.replace_input_of(u, l, place_holder); + success = true; + } + } + if (success) { + --k; + } + } + } + } + if (n->is_Phi()) { + stack.push(n, 1); + n = n->in(1); + } else if (n->is_Mem()) { + stack.push(n, n->req()); + n = n->in(MemNode::Memory); + } else { + assert(n->is_Proj() && n->in(0)->Opcode() == Op_MemBarCPUOrder, ""); + stack.push(n, n->req()); + n = n->in(0)->in(TypeFunc::Memory); + } + } else { + assert(n->adr_type() == TypePtr::BOTTOM || (n->Opcode() == Op_Node && n->_idx >= last) || (n->is_Proj() && n->in(0)->is_Initialize()), ""); + // Build a new MergeMem node to carry the new memory state + // as we build it. IGVN should fold extraneous MergeMem + // nodes. + mm = MergeMemNode::make(n); + igvn.register_new_node_with_optimizer(mm); + while (stack.size() > 0) { + Node* m = stack.node(); + uint idx = stack.index(); + if (m->is_Mem()) { + // Move memory node to its new slice + const TypePtr* adr_type = m->adr_type(); + int alias = get_alias_index(adr_type); + Node* prev = mm->memory_at(alias); + igvn.replace_input_of(m, MemNode::Memory, prev); + mm->set_memory_at(alias, m); + } else if (m->is_Phi()) { + // We need as many new phis as there are new aliases + igvn.replace_input_of(m, idx, mm); + if (idx == m->req()-1) { + Node* r = m->in(0); + for (uint j = (uint)start_alias; j <= (uint)stop_alias; j++) { + const TypePtr* adr_type = get_adr_type(j); + if (!adr_type->isa_aryptr() || !adr_type->is_flat() || j == (uint)index) { + continue; + } + Node* phi = new PhiNode(r, Type::MEMORY, get_adr_type(j)); + igvn.register_new_node_with_optimizer(phi); + for (uint k = 1; k < m->req(); k++) { + phi->init_req(k, m->in(k)->as_MergeMem()->memory_at(j)); + } + mm->set_memory_at(j, phi); + } + Node* base_phi = new PhiNode(r, Type::MEMORY, TypePtr::BOTTOM); + igvn.register_new_node_with_optimizer(base_phi); + for (uint k = 1; k < m->req(); k++) { + base_phi->init_req(k, m->in(k)->as_MergeMem()->base_memory()); + } + mm->set_base_memory(base_phi); + } + } else { + // This is a MemBarCPUOrder node from + // Parse::array_load()/Parse::array_store(), in the + // branch that handles flat arrays hidden under + // an Object[] array. We also need one new membar per + // new alias to keep the unknown access that the + // membars protect properly ordered with accesses to + // known flat array. + assert(m->is_Proj(), "projection expected"); + Node* ctrl = m->in(0)->in(TypeFunc::Control); + igvn.replace_input_of(m->in(0), TypeFunc::Control, top()); + for (uint j = (uint)start_alias; j <= (uint)stop_alias; j++) { + const TypePtr* adr_type = get_adr_type(j); + if (!adr_type->isa_aryptr() || !adr_type->is_flat() || j == (uint)index) { + continue; + } + MemBarNode* mb = new MemBarCPUOrderNode(this, j, nullptr); + igvn.register_new_node_with_optimizer(mb); + Node* mem = mm->memory_at(j); + mb->init_req(TypeFunc::Control, ctrl); + mb->init_req(TypeFunc::Memory, mem); + ctrl = new ProjNode(mb, TypeFunc::Control); + igvn.register_new_node_with_optimizer(ctrl); + mem = new ProjNode(mb, TypeFunc::Memory); + igvn.register_new_node_with_optimizer(mem); + mm->set_memory_at(j, mem); + } + igvn.replace_node(m->in(0)->as_Multi()->proj_out(TypeFunc::Control), ctrl); + } + if (idx < m->req()-1) { + idx += 1; + stack.set_index(idx); + n = m->in(idx); + break; + } + // Take care of place holder nodes + if (m->has_out_with(Op_Node)) { + Node* place_holder = m->find_out_with(Op_Node); + if (place_holder != nullptr) { + Node* mm_clone = mm->clone(); + igvn.register_new_node_with_optimizer(mm_clone); + Node* hook = new Node(1); + hook->init_req(0, mm); + igvn.replace_node(place_holder, mm_clone); + hook->destruct(&igvn); + } + assert(!m->has_out_with(Op_Node), "place holder should be gone now"); + } + stack.pop(); + } + } + } while(stack.size() > 0); + // Fix the memory state at the MergeMem we started from + igvn.rehash_node_delayed(current); + for (uint j = (uint)start_alias; j <= (uint)stop_alias; j++) { + const TypePtr* adr_type = get_adr_type(j); + if (!adr_type->isa_aryptr() || !adr_type->is_flat()) { + continue; + } + current->set_memory_at(j, mm); + } + current->set_memory_at(index, current->base_memory()); + } + igvn.optimize(); + } + print_method(PHASE_SPLIT_INLINES_ARRAY, 2); +#ifdef ASSERT + if (!_flat_accesses_share_alias) { + wq.clear(); + wq.push(root()); + for (uint i = 0; i < wq.size(); i++) { + Node* n = wq.at(i); + assert(n->adr_type() != TypeAryPtr::INLINES, "should have been removed from the graph"); + for (uint j = 0; j < n->req(); j++) { + Node* m = n->in(j); + if (m != nullptr) { + wq.push(m); + } + } + } + } +#endif +} + void Compile::record_for_merge_stores_igvn(Node* n) { if (!n->for_merge_stores_igvn()) { assert(!_for_merge_stores_igvn.contains(n), "duplicate"); @@ -2011,7 +2479,7 @@ void Compile::process_for_unstable_if_traps(PhaseIterGVN& igvn) { Node* local = unc->local(jvms, i); // kill local using the liveness of next_bci. // give up when the local looks like an operand to secure reexecution. - if (!live_locals.at(i) && !local->is_top() && local != lhs && local!= rhs) { + if (!live_locals.at(i) && !local->is_top() && local != lhs && local != rhs) { uint idx = jvms->locoff() + i; #ifdef ASSERT if (PrintOpto && Verbose) { @@ -2026,7 +2494,7 @@ void Compile::process_for_unstable_if_traps(PhaseIterGVN& igvn) { } } - // keep the mondified trap for late query + // keep the modified trap for late query if (modified) { trap->set_modified(); } else { @@ -2232,7 +2700,10 @@ void Compile::process_late_inline_calls_no_inline(PhaseIterGVN& igvn) { // Tracking and verification of modified nodes is disabled by setting "_modified_nodes == nullptr" // as if "inlining_incrementally() == true" were set. assert(inlining_incrementally() == false, "not allowed"); - assert(_modified_nodes == nullptr, "not allowed"); +#ifdef ASSERT + Unique_Node_List* modified_nodes = _modified_nodes; + _modified_nodes = nullptr; +#endif assert(_late_inlines.length() > 0, "sanity"); while (_late_inlines.length() > 0) { @@ -2245,6 +2716,7 @@ void Compile::process_late_inline_calls_no_inline(PhaseIterGVN& igvn) { inline_incrementally_cleanup(igvn); } + DEBUG_ONLY( _modified_nodes = modified_nodes; ) } bool Compile::optimize_loops(PhaseIterGVN& igvn, LoopOptsMode mode) { @@ -2388,8 +2860,24 @@ void Compile::Optimize() { // safepoints remove_root_to_sfpts_edges(igvn); + // Process inline type nodes now that all inlining is over + process_inline_types(igvn); + + adjust_flat_array_access_aliases(igvn); + if (failing()) return; + if (C->macro_count() > 0) { + // Eliminate some macro nodes before EA to reduce analysis pressure + PhaseMacroExpand mexp(igvn); + mexp.eliminate_macro_nodes(/* eliminate_locks= */ false); + if (failing()) { + return; + } + igvn.set_delay_transform(false); + print_method(PHASE_ITER_GVN_AFTER_ELIMINATION, 2); + } + if (has_loops()) { print_method(PHASE_BEFORE_LOOP_OPTS, 2); } @@ -2400,10 +2888,23 @@ void Compile::Optimize() { // Cleanup graph (remove dead nodes). TracePhase tp(_t_idealLoop); PhaseIdealLoop::optimize(igvn, LoopOptsMaxUnroll); - if (failing()) return; + if (failing()) { + return; + } + print_method(PHASE_PHASEIDEAL_BEFORE_EA, 2); + if (C->macro_count() > 0) { + // Eliminate some macro nodes before EA to reduce analysis pressure + PhaseMacroExpand mexp(igvn); + mexp.eliminate_macro_nodes(/* eliminate_locks= */ false); + if (failing()) { + return; + } + igvn.set_delay_transform(false); + print_method(PHASE_ITER_GVN_AFTER_ELIMINATION, 2); + } } + bool progress; - print_method(PHASE_PHASEIDEAL_BEFORE_EA, 2); do { ConnectionGraph::do_analysis(this, &igvn); @@ -2421,13 +2922,12 @@ void Compile::Optimize() { TracePhase tp(_t_macroEliminate); PhaseMacroExpand mexp(igvn); mexp.eliminate_macro_nodes(); - if (failing()) return; + if (failing()) { + return; + } print_method(PHASE_AFTER_MACRO_ELIMINATION, 2); igvn.set_delay_transform(false); - igvn.optimize(); - if (failing()) return; - print_method(PHASE_ITER_GVN_AFTER_ELIMINATION, 2); } @@ -2518,10 +3018,24 @@ void Compile::Optimize() { bs->verify_gc_barriers(this, BarrierSetC2::BeforeMacroExpand); #endif + assert(_late_inlines.length() == 0 || IncrementalInlineMH || IncrementalInlineVirtual, "not empty"); + + if (_late_inlines.length() > 0) { + // More opportunities to optimize virtual and MH calls. + // Though it's maybe too late to perform inlining, strength-reducing them to direct calls is still an option. + process_late_inline_calls_no_inline(igvn); + } + { TracePhase tp(_t_macroExpand); + PhaseMacroExpand mex(igvn); + // Last attempt to eliminate macro nodes. + mex.eliminate_macro_nodes(); + if (failing()) { + return; + } + print_method(PHASE_BEFORE_MACRO_EXPANSION, 3); - PhaseMacroExpand mex(igvn); // Do not allow new macro nodes once we start to eliminate and expand C->reset_allow_macro_nodes(); // Last attempt to eliminate macro nodes before expand @@ -2541,6 +3055,10 @@ void Compile::Optimize() { print_method(PHASE_AFTER_MACRO_EXPANSION, 2); } + // Process inline type nodes again and remove them. From here + // on we don't need to keep track of field values anymore. + process_inline_types(igvn, /* remove= */ true); + { TracePhase tp(_t_barrierExpand); if (bs->expand_barriers(this, igvn)) { @@ -2557,17 +3075,9 @@ void Compile::Optimize() { } DEBUG_ONLY( _modified_nodes = nullptr; ) + DEBUG_ONLY( _late_inlines.clear(); ) assert(igvn._worklist.size() == 0, "not empty"); - - assert(_late_inlines.length() == 0 || IncrementalInlineMH || IncrementalInlineVirtual, "not empty"); - - if (_late_inlines.length() > 0) { - // More opportunities to optimize virtual and MH calls. - // Though it's maybe too late to perform inlining, strength-reducing them to direct calls is still an option. - process_late_inline_calls_no_inline(igvn); - if (failing()) return; - } } // (End scope of igvn; run destructor if necessary for asserts.) check_no_dead_use(); @@ -3313,7 +3823,7 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f new_call->init_req(TypeFunc::Memory, C->top()); new_call->init_req(TypeFunc::ReturnAdr, C->top()); new_call->init_req(TypeFunc::FramePtr, C->top()); - for (unsigned int i = TypeFunc::Parms; i < call->tf()->domain()->cnt(); i++) { + for (unsigned int i = TypeFunc::Parms; i < call->tf()->domain_sig()->cnt(); i++) { new_call->init_req(i, call->in(i)); } n->subsume_by(new_call, this); @@ -3359,6 +3869,7 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f case Op_StoreC: case Op_StoreI: case Op_StoreL: + case Op_StoreLSpecial: case Op_CompareAndSwapB: case Op_CompareAndSwapS: case Op_CompareAndSwapI: @@ -3911,6 +4422,11 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f break; } #ifdef ASSERT + case Op_InlineType: { + n->dump(-1); + assert(false, "inline type node was not removed"); + break; + } case Op_ConNKlass: { const TypePtr* tp = n->as_Type()->type()->make_ptr(); ciKlass* klass = tp->is_klassptr()->exact_klass(); @@ -4286,8 +4802,8 @@ bool Compile::needs_clinit_barrier(ciInstanceKlass* holder, ciMethod* accessing_ // Access inside a class. The barrier can be elided when access happens in , // , or a static method. In all those cases, there was an initialization // barrier on the holder klass passed. - if (accessing_method->is_static_initializer() || - accessing_method->is_object_initializer() || + if (accessing_method->is_class_initializer() || + accessing_method->is_object_constructor() || accessing_method->is_static()) { return false; } @@ -4295,7 +4811,7 @@ bool Compile::needs_clinit_barrier(ciInstanceKlass* holder, ciMethod* accessing_ // Access from a subclass. The barrier can be elided only when access happens in . // In case of or a static method, the barrier is on the subclass is not enough: // child class can become fully initialized while its parent class is still being initialized. - if (accessing_method->is_static_initializer()) { + if (accessing_method->is_class_initializer()) { return false; } } @@ -4365,9 +4881,10 @@ void Compile::verify_bidirectional_edges(Unique_Node_List& visited, const Unique } else if (in == nullptr) { assert(i == 0 || i >= n->req() || n->is_Region() || n->is_Phi() || n->is_ArrayCopy() || + (n->is_Allocate() && i >= AllocateNode::InlineType) || (n->is_Unlock() && i == (n->req() - 1)) || (n->is_MemBar() && i == 5), // the precedence edge to a membar can be removed during macro node expansion - "only region, phi, arraycopy, unlock or membar nodes have null data edges"); + "only region, phi, arraycopy, allocate, unlock or membar nodes have null data edges"); } else { assert(in->is_top(), "sanity"); // Nothing to check. @@ -4518,6 +5035,13 @@ Compile::SubTypeCheckResult Compile::static_subtype_check(const TypeKlassPtr* su if (superk->isa_aryklassptr()) { int ignored; superelem = superk->is_aryklassptr()->base_element_type(ignored); + + // Do not fold the subtype check to an array klass pointer comparison for null-able inline type arrays + // because null-free [LMyValue <: null-able [LMyValue but the klasses are different. Perform a full test. + if (!superk->is_aryklassptr()->is_null_free() && superk->is_aryklassptr()->elem()->isa_instklassptr() && + superk->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->is_inlinetype()) { + return SSC_full_test; + } } if (superelem->isa_instklassptr()) { @@ -5001,6 +5525,27 @@ void Compile::remove_speculative_types(PhaseIterGVN &igvn) { } } +Node* Compile::optimize_acmp(PhaseGVN* phase, Node* a, Node* b) { + const TypeInstPtr* ta = phase->type(a)->isa_instptr(); + const TypeInstPtr* tb = phase->type(b)->isa_instptr(); + if (!EnableValhalla || ta == nullptr || tb == nullptr || + ta->is_zero_type() || tb->is_zero_type() || + !ta->can_be_inline_type() || !tb->can_be_inline_type()) { + // Use old acmp if one operand is null or not an inline type + return new CmpPNode(a, b); + } else if (ta->is_inlinetypeptr() || tb->is_inlinetypeptr()) { + // We know that one operand is an inline type. Therefore, + // new acmp will only return true if both operands are nullptr. + // Check if both operands are null by or'ing the oops. + a = phase->transform(new CastP2XNode(nullptr, a)); + b = phase->transform(new CastP2XNode(nullptr, b)); + a = phase->transform(new OrXNode(a, b)); + return new CmpXNode(a, phase->MakeConX(0)); + } + // Use new acmp + return nullptr; +} + // Auxiliary methods to support randomized stressing/fuzzing. void Compile::initialize_stress_seed(const DirectiveSet* directive) { @@ -5356,6 +5901,8 @@ Node* Compile::narrow_value(BasicType bt, Node* value, const Type* type, PhaseGV result = new AndINode(value, phase->intcon(0xFF)); } else if (bt == T_CHAR) { result = new AndINode(value,phase->intcon(0xFFFF)); + } else if (bt == T_FLOAT) { + result = new MoveI2FNode(value); } else { assert(bt == T_SHORT, "unexpected narrow type"); result = phase->transform(new LShiftINode(value, phase->intcon(16))); diff --git a/src/hotspot/share/opto/compile.hpp b/src/hotspot/share/opto/compile.hpp index 2cada9c04c9..e648927854f 100644 --- a/src/hotspot/share/opto/compile.hpp +++ b/src/hotspot/share/opto/compile.hpp @@ -53,6 +53,7 @@ class AddPNode; class Block; class Bundle; class CallGenerator; +class CallNode; class CallStaticJavaNode; class CloneMap; class CompilationFailureInfo; @@ -96,6 +97,7 @@ class TypeVect; class Type_Array; class Unique_Node_List; class UnstableIfTrap; +class InlineTypeNode; class nmethod; class Node_Stack; struct Final_Reshape_Counts; @@ -332,6 +334,7 @@ class Compile : public Phase { bool _has_stringbuilder; // True StringBuffers or StringBuilders are allocated bool _has_boxed_value; // True if a boxed object is allocated bool _has_reserved_stack_access; // True if the method or an inlined method is annotated with ReservedStackAccess + bool _has_circular_inline_type; // True if method loads an inline type with a circular, non-flat field uint _max_vector_size; // Maximum size of generated vectors bool _clear_upper_avx; // Clear upper bits of ymm registers using vzeroupper uint _trap_hist[trapHistLength]; // Cumulative traps @@ -360,6 +363,9 @@ class Compile : public Phase { bool _has_scoped_access; // For shared scope closure bool _clinit_barrier_on_entry; // True if clinit barrier is needed on nmethod entry int _loop_opts_cnt; // loop opts round + bool _has_flat_accesses; // Any known flat array accesses? + bool _flat_accesses_share_alias; // Initially all flat array share a single slice + bool _scalarize_in_safepoints; // Scalarize inline types in safepoint debug info uint _stress_seed; // Seed for stress testing // Compilation environment. @@ -378,6 +384,7 @@ class Compile : public Phase { GrowableArray _template_assertion_predicate_opaques; GrowableArray _expensive_nodes; // List of nodes that are expensive to compute and that we'd better not let the GVN freely common GrowableArray _for_post_loop_igvn; // List of nodes for IGVN after loop opts are over + GrowableArray _inline_type_nodes; // List of InlineType nodes GrowableArray _for_merge_stores_igvn; // List of nodes for IGVN merge stores GrowableArray _unstable_if_traps; // List of ifnodes after IGVN GrowableArray _coarsened_locks; // List of coarsened Lock and Unlock nodes @@ -605,6 +612,8 @@ class Compile : public Phase { void set_has_boxed_value(bool z) { _has_boxed_value = z; } bool has_reserved_stack_access() const { return _has_reserved_stack_access; } void set_has_reserved_stack_access(bool z) { _has_reserved_stack_access = z; } + bool has_circular_inline_type() const { return _has_circular_inline_type; } + void set_has_circular_inline_type(bool z) { _has_circular_inline_type = z; } uint max_vector_size() const { return _max_vector_size; } void set_max_vector_size(uint s) { _max_vector_size = s; } bool clear_upper_avx() const { return _clear_upper_avx; } @@ -637,6 +646,16 @@ class Compile : public Phase { void set_max_node_limit(uint n) { _max_node_limit = n; } bool clinit_barrier_on_entry() { return _clinit_barrier_on_entry; } void set_clinit_barrier_on_entry(bool z) { _clinit_barrier_on_entry = z; } + void set_flat_accesses() { _has_flat_accesses = true; } + bool flat_accesses_share_alias() const { return _flat_accesses_share_alias; } + void set_flat_accesses_share_alias(bool z) { _flat_accesses_share_alias = z; } + bool scalarize_in_safepoints() const { return _scalarize_in_safepoints; } + void set_scalarize_in_safepoints(bool z) { _scalarize_in_safepoints = z; } + + // Support for scalarized inline type calling convention + bool has_scalarized_args() const { return _method != nullptr && _method->has_scalarized_args(); } + bool needs_stack_repair() const { return _method != nullptr && _method->get_Method()->c2_needs_stack_repair(); } + bool has_monitors() const { return _has_monitors; } void set_has_monitors(bool v) { _has_monitors = v; } bool has_scoped_access() const { return _has_scoped_access; } @@ -772,6 +791,13 @@ class Compile : public Phase { void remove_from_post_loop_opts_igvn(Node* n); void process_for_post_loop_opts_igvn(PhaseIterGVN& igvn); + // Keep track of inline type nodes for later processing + void add_inline_type(Node* n); + void remove_inline_type(Node* n); + void process_inline_types(PhaseIterGVN &igvn, bool remove = false); + + void adjust_flat_array_access_aliases(PhaseIterGVN& igvn); + void record_unstable_if_trap(UnstableIfTrap* trap); bool remove_unstable_if_trap(CallStaticJavaNode* unc, bool yield); void remove_useless_unstable_if_traps(Unique_Node_List &useful); @@ -954,11 +980,11 @@ class Compile : public Phase { } AliasType* alias_type(int idx) { assert(idx < num_alias_types(), "oob"); return _alias_types[idx]; } - AliasType* alias_type(const TypePtr* adr_type, ciField* field = nullptr) { return find_alias_type(adr_type, false, field); } + AliasType* alias_type(const TypePtr* adr_type, ciField* field = nullptr, bool uncached = false) { return find_alias_type(adr_type, false, field, uncached); } bool have_alias_type(const TypePtr* adr_type); AliasType* alias_type(ciField* field); - int get_alias_index(const TypePtr* at) { return alias_type(at)->index(); } + int get_alias_index(const TypePtr* at, bool uncached = false) { return alias_type(at, nullptr, uncached)->index(); } const TypePtr* get_adr_type(uint aidx) { return alias_type(aidx)->adr_type(); } int get_general_index(uint aidx) { return alias_type(aidx)->general_index(); } @@ -1199,7 +1225,7 @@ class Compile : public Phase { void grow_alias_types(); AliasCacheEntry* probe_alias_cache(const TypePtr* adr_type); const TypePtr *flatten_alias_type(const TypePtr* adr_type) const; - AliasType* find_alias_type(const TypePtr* adr_type, bool no_create, ciField* field); + AliasType* find_alias_type(const TypePtr* adr_type, bool no_create, ciField* field, bool uncached = false); void verify_top(Node*) const PRODUCT_RETURN; @@ -1289,7 +1315,9 @@ class Compile : public Phase { // Convert integer value to a narrowed long type dependent on ctrl (for example, a range check) static Node* constrained_convI2L(PhaseGVN* phase, Node* value, const TypeInt* itype, Node* ctrl, bool carry_dependency = false); - // Auxiliary methods for randomized fuzzing/stressing + Node* optimize_acmp(PhaseGVN* phase, Node* a, Node* b); + + // Auxiliary method for randomized fuzzing/stressing int random(); bool randomized_select(int count); diff --git a/src/hotspot/share/opto/convertnode.cpp b/src/hotspot/share/opto/convertnode.cpp index a495814da61..5d95644df2a 100644 --- a/src/hotspot/share/opto/convertnode.cpp +++ b/src/hotspot/share/opto/convertnode.cpp @@ -27,6 +27,7 @@ #include "opto/connode.hpp" #include "opto/convertnode.hpp" #include "opto/divnode.hpp" +#include "opto/inlinetypenode.hpp" #include "opto/matcher.hpp" #include "opto/movenode.hpp" #include "opto/mulnode.hpp" @@ -65,7 +66,14 @@ const Type* Conv2BNode::Value(PhaseGVN* phase) const { return TypeInt::BOOL; } +//------------------------------Ideal------------------------------------------ Node* Conv2BNode::Ideal(PhaseGVN* phase, bool can_reshape) { + if (in(1)->is_InlineType()) { + // Null checking a scalarized but nullable inline type. Check the null marker + // input instead of the oop input to avoid keeping buffer allocations alive. + set_req_X(1, in(1)->as_InlineType()->get_null_marker(), phase); + return this; + } if (!Matcher::match_rule_supported(Op_Conv2B)) { if (phase->C->post_loop_opts_phase()) { // Get type of comparison to make diff --git a/src/hotspot/share/opto/divnode.cpp b/src/hotspot/share/opto/divnode.cpp index 0d1337909fb..4b417be015d 100644 --- a/src/hotspot/share/opto/divnode.cpp +++ b/src/hotspot/share/opto/divnode.cpp @@ -1605,7 +1605,7 @@ Node* ModFloatingNode::Ideal(PhaseGVN* phase, bool can_reshape) { TupleNode* ModFloatingNode::make_tuple_of_input_state_and_constant_result(PhaseIterGVN* phase, const Type* con) const { Node* con_node = phase->makecon(con); TupleNode* tuple = TupleNode::make( - tf()->range(), + tf()->range_cc(), in(TypeFunc::Control), in(TypeFunc::I_O), in(TypeFunc::Memory), @@ -1668,7 +1668,7 @@ DivModLNode* DivModLNode::make(Node* div_or_mod) { //------------------------------match------------------------------------------ // return result(s) along with their RegMask info -Node *DivModINode::match( const ProjNode *proj, const Matcher *match ) { +Node *DivModINode::match(const ProjNode *proj, const Matcher *match, const RegMask* mask) { uint ideal_reg = proj->ideal_reg(); RegMask rm; if (proj->_con == div_proj_num) { @@ -1683,7 +1683,7 @@ Node *DivModINode::match( const ProjNode *proj, const Matcher *match ) { //------------------------------match------------------------------------------ // return result(s) along with their RegMask info -Node *DivModLNode::match( const ProjNode *proj, const Matcher *match ) { +Node *DivModLNode::match(const ProjNode *proj, const Matcher *match, const RegMask* mask) { uint ideal_reg = proj->ideal_reg(); RegMask rm; if (proj->_con == div_proj_num) { @@ -1721,7 +1721,7 @@ UDivModLNode* UDivModLNode::make(Node* div_or_mod) { //------------------------------match------------------------------------------ // return result(s) along with their RegMask info -Node* UDivModINode::match( const ProjNode *proj, const Matcher *match ) { +Node* UDivModINode::match(const ProjNode* proj, const Matcher* match, const RegMask* mask) { uint ideal_reg = proj->ideal_reg(); RegMask rm; if (proj->_con == div_proj_num) { @@ -1736,7 +1736,7 @@ Node* UDivModINode::match( const ProjNode *proj, const Matcher *match ) { //------------------------------match------------------------------------------ // return result(s) along with their RegMask info -Node* UDivModLNode::match( const ProjNode *proj, const Matcher *match ) { +Node* UDivModLNode::match( const ProjNode* proj, const Matcher* match, const RegMask* mask) { uint ideal_reg = proj->ideal_reg(); RegMask rm; if (proj->_con == div_proj_num) { diff --git a/src/hotspot/share/opto/divnode.hpp b/src/hotspot/share/opto/divnode.hpp index b13460c89f5..6c9526c0d9c 100644 --- a/src/hotspot/share/opto/divnode.hpp +++ b/src/hotspot/share/opto/divnode.hpp @@ -252,7 +252,7 @@ class DivModINode : public DivModNode { DivModINode( Node *c, Node *dividend, Node *divisor ) : DivModNode(c, dividend, divisor) {} virtual int Opcode() const; virtual const Type *bottom_type() const { return TypeTuple::INT_PAIR; } - virtual Node *match( const ProjNode *proj, const Matcher *m ); + virtual Node *match(const ProjNode *proj, const Matcher *m, const RegMask* mask); // Make a divmod and associated projections from a div or mod. static DivModINode* make(Node* div_or_mod); @@ -265,7 +265,7 @@ class DivModLNode : public DivModNode { DivModLNode( Node *c, Node *dividend, Node *divisor ) : DivModNode(c, dividend, divisor) {} virtual int Opcode() const; virtual const Type *bottom_type() const { return TypeTuple::LONG_PAIR; } - virtual Node *match( const ProjNode *proj, const Matcher *m ); + virtual Node *match(const ProjNode *proj, const Matcher *m, const RegMask* mask); // Make a divmod and associated projections from a div or mod. static DivModLNode* make(Node* div_or_mod); @@ -279,7 +279,7 @@ class UDivModINode : public DivModNode { UDivModINode( Node *c, Node *dividend, Node *divisor ) : DivModNode(c, dividend, divisor) {} virtual int Opcode() const; virtual const Type *bottom_type() const { return TypeTuple::INT_PAIR; } - virtual Node *match( const ProjNode *proj, const Matcher *m ); + virtual Node* match(const ProjNode* proj, const Matcher* m, const RegMask* mask); // Make a divmod and associated projections from a div or mod. static UDivModINode* make(Node* div_or_mod); @@ -292,7 +292,7 @@ class UDivModLNode : public DivModNode { UDivModLNode( Node *c, Node *dividend, Node *divisor ) : DivModNode(c, dividend, divisor) {} virtual int Opcode() const; virtual const Type *bottom_type() const { return TypeTuple::LONG_PAIR; } - virtual Node *match( const ProjNode *proj, const Matcher *m ); + virtual Node* match(const ProjNode* proj, const Matcher* m, const RegMask* mask); // Make a divmod and associated projections from a div or mod. static UDivModLNode* make(Node* div_or_mod); diff --git a/src/hotspot/share/opto/doCall.cpp b/src/hotspot/share/opto/doCall.cpp index ad7b64f93f5..b7a6f7d8438 100644 --- a/src/hotspot/share/opto/doCall.cpp +++ b/src/hotspot/share/opto/doCall.cpp @@ -25,10 +25,12 @@ #include "ci/ciCallSite.hpp" #include "ci/ciMethodHandle.hpp" #include "ci/ciSymbols.hpp" +#include "classfile/vmIntrinsics.hpp" #include "classfile/vmSymbols.hpp" #include "compiler/compileBroker.hpp" #include "compiler/compileLog.hpp" #include "interpreter/linkResolver.hpp" +#include "jvm_io.h" #include "logging/log.hpp" #include "logging/logLevel.hpp" #include "logging/logMessage.hpp" @@ -37,6 +39,7 @@ #include "opto/callGenerator.hpp" #include "opto/castnode.hpp" #include "opto/cfgnode.hpp" +#include "opto/inlinetypenode.hpp" #include "opto/mulnode.hpp" #include "opto/parse.hpp" #include "opto/rootnode.hpp" @@ -86,6 +89,64 @@ static void trace_type_profile(Compile* C, ciMethod* method, JVMState* jvms, } } +static bool arg_can_be_larval(ciMethod* callee, int arg_idx) { + if (callee->is_object_constructor() && arg_idx == 0) { + return true; + } + + if (arg_idx != 1 || callee->intrinsic_id() == vmIntrinsicID::_none) { + return false; + } + + switch (callee->intrinsic_id()) { + case vmIntrinsicID::_finishPrivateBuffer: + case vmIntrinsicID::_putBoolean: + case vmIntrinsicID::_putBooleanOpaque: + case vmIntrinsicID::_putBooleanRelease: + case vmIntrinsicID::_putBooleanVolatile: + case vmIntrinsicID::_putByte: + case vmIntrinsicID::_putByteOpaque: + case vmIntrinsicID::_putByteRelease: + case vmIntrinsicID::_putByteVolatile: + case vmIntrinsicID::_putChar: + case vmIntrinsicID::_putCharOpaque: + case vmIntrinsicID::_putCharRelease: + case vmIntrinsicID::_putCharUnaligned: + case vmIntrinsicID::_putCharVolatile: + case vmIntrinsicID::_putShort: + case vmIntrinsicID::_putShortOpaque: + case vmIntrinsicID::_putShortRelease: + case vmIntrinsicID::_putShortUnaligned: + case vmIntrinsicID::_putShortVolatile: + case vmIntrinsicID::_putInt: + case vmIntrinsicID::_putIntOpaque: + case vmIntrinsicID::_putIntRelease: + case vmIntrinsicID::_putIntUnaligned: + case vmIntrinsicID::_putIntVolatile: + case vmIntrinsicID::_putLong: + case vmIntrinsicID::_putLongOpaque: + case vmIntrinsicID::_putLongRelease: + case vmIntrinsicID::_putLongUnaligned: + case vmIntrinsicID::_putLongVolatile: + case vmIntrinsicID::_putFloat: + case vmIntrinsicID::_putFloatOpaque: + case vmIntrinsicID::_putFloatRelease: + case vmIntrinsicID::_putFloatVolatile: + case vmIntrinsicID::_putDouble: + case vmIntrinsicID::_putDoubleOpaque: + case vmIntrinsicID::_putDoubleRelease: + case vmIntrinsicID::_putDoubleVolatile: + case vmIntrinsicID::_putReference: + case vmIntrinsicID::_putReferenceOpaque: + case vmIntrinsicID::_putReferenceRelease: + case vmIntrinsicID::_putReferenceVolatile: + case vmIntrinsicID::_putValue: + return true; + default: + return false; + } +} + CallGenerator* Compile::call_generator(ciMethod* callee, int vtable_index, bool call_does_dispatch, JVMState* jvms, bool allow_inline, float prof_factor, ciKlass* speculative_receiver_type, @@ -145,7 +206,21 @@ CallGenerator* Compile::call_generator(ciMethod* callee, int vtable_index, bool // methods. If these methods are replaced with specialized code, // then we return it as the inlined version of the call. CallGenerator* cg_intrinsic = nullptr; - if (allow_inline && allow_intrinsics) { + if (callee->intrinsic_id() == vmIntrinsics::_makePrivateBuffer || callee->intrinsic_id() == vmIntrinsics::_finishPrivateBuffer) { + // These methods must be inlined so that we don't have larval value objects crossing method + // boundaries + assert(!call_does_dispatch, "callee should not be virtual %s", callee->name()->as_utf8()); + CallGenerator* cg = find_intrinsic(callee, call_does_dispatch); + + if (cg == nullptr) { + // This is probably because the intrinsics is disabled from the command line + char reason[256]; + jio_snprintf(reason, sizeof(reason), "cannot find an intrinsics for %s", callee->name()->as_utf8()); + C->record_method_not_compilable(reason); + return nullptr; + } + return cg; + } else if (allow_inline && allow_intrinsics) { CallGenerator* cg = find_intrinsic(callee, call_does_dispatch); if (cg != nullptr) { if (cg->is_predicated()) { @@ -601,7 +676,7 @@ void Parse::do_call() { // Additional receiver subtype checks for interface calls via invokespecial or invokeinterface. ciKlass* receiver_constraint = nullptr; - if (iter().cur_bc_raw() == Bytecodes::_invokespecial && !orig_callee->is_object_initializer()) { + if (iter().cur_bc_raw() == Bytecodes::_invokespecial && !orig_callee->is_object_constructor()) { ciInstanceKlass* calling_klass = method()->holder(); ciInstanceKlass* sender_klass = calling_klass; if (sender_klass->is_interface()) { @@ -629,6 +704,15 @@ void Parse::do_call() { set_stack(sp() - nargs, casted_receiver); } + // Scalarize value objects passed into this invocation if we know that they are not larval + for (int arg_idx = 0; arg_idx < nargs; arg_idx++) { + if (arg_can_be_larval(callee, arg_idx)) { + continue; + } + + cast_to_non_larval(peek(nargs - 1 - arg_idx)); + } + // Note: It's OK to try to inline a virtual call. // The call generator will not attempt to inline a polymorphic call // unless it knows how to optimize the receiver dispatch. @@ -643,6 +727,10 @@ void Parse::do_call() { // This call checks with CHA, the interpreter profile, intrinsics table, etc. // It decides whether inlining is desirable or not. CallGenerator* cg = C->call_generator(callee, vtable_index, call_does_dispatch, jvms, try_inline, prof_factor(), speculative_receiver_type); + if (failing()) { + return; + } + assert(cg != nullptr, "must find a CallGenerator for callee %s", callee->name()->as_utf8()); // NOTE: Don't use orig_callee and callee after this point! Use cg->method() instead. orig_callee = callee = nullptr; @@ -729,7 +817,7 @@ void Parse::do_call() { BasicType rt = rtype->basic_type(); BasicType ct = ctype->basic_type(); if (ct == T_VOID) { - // It's OK for a method to return a value that is discarded. + // It's OK for a method to return a value that is discarded. // The discarding does not require any special action from the caller. // The Java code knows this, at VerifyType.isNullConversion. pop_node(rt); // whatever it was, pop it @@ -787,6 +875,27 @@ void Parse::do_call() { if (is_reference_type(ct)) { record_profiled_return_for_speculation(); } + + if (!rtype->is_void() && cg->method()->intrinsic_id() != vmIntrinsicID::_makePrivateBuffer) { + Node* retnode = peek(); + const Type* rettype = gvn().type(retnode); + if (rettype->is_inlinetypeptr() && !retnode->is_InlineType()) { + retnode = InlineTypeNode::make_from_oop(this, retnode, rettype->inline_klass()); + dec_sp(1); + push(retnode); + } + } + + if (cg->method()->is_object_constructor() && receiver != nullptr && gvn().type(receiver)->is_inlinetypeptr()) { + InlineTypeNode* non_larval = InlineTypeNode::make_from_oop(this, receiver, gvn().type(receiver)->inline_klass()); + // Relinquish the oop input, we will delay the allocation to the point it is needed, see the + // comments in InlineTypeNode::Ideal for more details + non_larval = non_larval->clone_if_required(&gvn(), nullptr); + non_larval->set_oop(gvn(), null()); + non_larval->set_is_buffered(gvn(), false); + non_larval = gvn().transform(non_larval)->as_InlineType(); + map()->replace_edge(receiver, non_larval); + } } // Restart record of parsing work after possible inlining of call diff --git a/src/hotspot/share/opto/escape.cpp b/src/hotspot/share/opto/escape.cpp index 1a5bddd332e..f11343e9e23 100644 --- a/src/hotspot/share/opto/escape.cpp +++ b/src/hotspot/share/opto/escape.cpp @@ -28,6 +28,7 @@ #include "gc/shared/c2/barrierSetC2.hpp" #include "libadt/vectset.hpp" #include "memory/allocation.hpp" +#include "memory/metaspace.hpp" #include "memory/resourceArea.hpp" #include "opto/arraycopynode.hpp" #include "opto/c2compiler.hpp" @@ -36,6 +37,7 @@ #include "opto/cfgnode.hpp" #include "opto/compile.hpp" #include "opto/escape.hpp" +#include "opto/inlinetypenode.hpp" #include "opto/locknode.hpp" #include "opto/macro.hpp" #include "opto/movenode.hpp" @@ -163,6 +165,16 @@ bool ConnectionGraph::compute_escape() { java_objects_worklist.append(phantom_obj); for( uint next = 0; next < ideal_nodes.size(); ++next ) { Node* n = ideal_nodes.at(next); + if ((n->Opcode() == Op_LoadX || n->Opcode() == Op_StoreX) && + !n->in(MemNode::Address)->is_AddP() && + _igvn->type(n->in(MemNode::Address))->isa_oopptr()) { + // Load/Store at mark work address is at offset 0 so has no AddP which confuses EA + Node* addp = new AddPNode(n->in(MemNode::Address), n->in(MemNode::Address), _igvn->MakeConX(0)); + _igvn->register_new_node_with_optimizer(addp); + _igvn->replace_input_of(n, MemNode::Address, addp); + ideal_nodes.push(addp); + _nodes.at_put_grow(addp->_idx, nullptr, nullptr); + } // Create PointsTo nodes and add them to Connection Graph. Called // only once per ideal node since ideal_nodes is Unique_Node list. add_node_to_connection_graph(n, &delayed_worklist); @@ -1252,8 +1264,17 @@ bool ConnectionGraph::reduce_phi_on_safepoints_helper(Node* ophi, Node* cast, No } AllocateNode* alloc = ptn->ideal_node()->as_Allocate(); - SafePointScalarObjectNode* sobj = mexp.create_scalarized_object_description(alloc, sfpt); + Unique_Node_List value_worklist; +#ifdef ASSERT + const Type* res_type = alloc->result_cast()->bottom_type(); + if (res_type->is_inlinetypeptr() && !Compile::current()->has_circular_inline_type()) { + PhiNode* phi = ophi->as_Phi(); + assert(!ophi->as_Phi()->can_push_inline_types_down(_igvn), "missed earlier scalarization opportunity"); + } +#endif + SafePointScalarObjectNode* sobj = mexp.create_scalarized_object_description(alloc, sfpt, &value_worklist); if (sobj == nullptr) { + _compile->record_failure(C2Compiler::retry_no_reduce_allocation_merges()); return false; } @@ -1264,6 +1285,15 @@ bool ConnectionGraph::reduce_phi_on_safepoints_helper(Node* ophi, Node* cast, No // Register the scalarized object as a candidate for reallocation smerge->add_req(sobj); + + // Scalarize inline types that were added to the safepoint. + // Don't allow linking a constant oop (if available) for flat array elements + // because Deoptimization::reassign_flat_array_elements needs field values. + const bool allow_oop = !merge_t->is_flat(); + for (uint j = 0; j < value_worklist.size(); ++j) { + InlineTypeNode* vt = value_worklist.at(j)->as_InlineType(); + vt->make_scalar_in_safepoints(_igvn, allow_oop); + } } // Replaces debug information references to "original_sfpt_parent" in "sfpt" with references to "smerge" @@ -1462,7 +1492,7 @@ bool ConnectionGraph::has_arg_escape(CallJavaNode* call) { // no arg escapes through uncommon traps if (strcmp(name, "uncommon_trap") != 0) { // process_call_arguments() assumes that all arguments escape globally - const TypeTuple* d = call->tf()->domain(); + const TypeTuple* d = call->tf()->domain_sig(); for (uint i = TypeFunc::Parms; i < d->cnt(); i++) { const Type* at = d->field_at(i); if (at->isa_oopptr() != nullptr) { @@ -1536,6 +1566,17 @@ void ConnectionGraph::add_node_to_connection_graph(Node *n, Unique_Node_List *de (n->is_CallStaticJava() && n->as_CallStaticJava()->is_boxing_method())) { add_call_node(n->as_Call()); + } else if (n->as_Call()->tf()->returns_inline_type_as_fields()) { + bool returns_oop = false; + for (DUIterator_Fast imax, i = n->fast_outs(imax); i < imax && !returns_oop; i++) { + ProjNode* pn = n->fast_out(i)->as_Proj(); + if (pn->_con >= TypeFunc::Parms && pn->bottom_type()->isa_ptr()) { + returns_oop = true; + } + } + if (returns_oop) { + add_call_node(n->as_Call()); + } } } return; @@ -1563,10 +1604,12 @@ void ConnectionGraph::add_node_to_connection_graph(Node *n, Unique_Node_List *de } break; } - case Op_CastX2P: { + case Op_CastX2P: + case Op_CastI2N: { map_ideal_node(n, phantom_obj); break; } + case Op_InlineType: case Op_CastPP: case Op_CheckCastPP: case Op_EncodeP: @@ -1638,8 +1681,10 @@ void ConnectionGraph::add_node_to_connection_graph(Node *n, Unique_Node_List *de } case Op_Proj: { // we are only interested in the oop result projection from a call - if (n->as_Proj()->_con == TypeFunc::Parms && n->in(0)->is_Call() && - n->in(0)->as_Call()->returns_pointer()) { + if (n->as_Proj()->_con >= TypeFunc::Parms && n->in(0)->is_Call() && + (n->in(0)->as_Call()->returns_pointer() || n->bottom_type()->isa_ptr())) { + assert((n->as_Proj()->_con == TypeFunc::Parms && n->in(0)->as_Call()->returns_pointer()) || + n->in(0)->as_Call()->tf()->returns_inline_type_as_fields(), "what kind of oop return is it?"); add_local_var_and_edge(n, PointsToNode::NoEscape, n->in(0), delayed_worklist); } break; @@ -1741,6 +1786,7 @@ void ConnectionGraph::add_final_edges(Node *n) { add_base(n_ptn->as_Field(), ptn_base); break; } + case Op_InlineType: case Op_CastPP: case Op_CheckCastPP: case Op_EncodeP: @@ -1795,8 +1841,8 @@ void ConnectionGraph::add_final_edges(Node *n) { } case Op_Proj: { // we are only interested in the oop result projection from a call - assert(n->as_Proj()->_con == TypeFunc::Parms && n->in(0)->is_Call() && - n->in(0)->as_Call()->returns_pointer(), "Unexpected node type"); + assert((n->as_Proj()->_con == TypeFunc::Parms && n->in(0)->as_Call()->returns_pointer()) || + n->in(0)->as_Call()->tf()->returns_inline_type_as_fields(), "what kind of oop return is it?"); add_local_var_and_edge(n, PointsToNode::NoEscape, n->in(0), nullptr); break; } @@ -1972,7 +2018,7 @@ bool ConnectionGraph::add_final_edges_unsafe_access(Node* n, uint opcode) { } void ConnectionGraph::add_call_node(CallNode* call) { - assert(call->returns_pointer(), "only for call which returns pointer"); + assert(call->returns_pointer() || call->tf()->returns_inline_type_as_fields(), "only for call which returns pointer"); uint call_idx = call->_idx; if (call->is_Allocate()) { Node* k = call->in(AllocateNode::KlassNode); @@ -2048,7 +2094,9 @@ void ConnectionGraph::add_call_node(CallNode* call) { ciMethod* meth = call->as_CallJava()->method(); if (meth == nullptr) { const char* name = call->as_CallStaticJava()->_name; - assert(strncmp(name, "C2 Runtime multianewarray", 25) == 0, "TODO: add failed case check"); + assert(strncmp(name, "C2 Runtime multianewarray", 25) == 0 || + strncmp(name, "C2 Runtime load_unknown_inline", 30) == 0 || + strncmp(name, "store_inline_type_fields_to_buf", 31) == 0, "TODO: add failed case check"); // Returns a newly allocated non-escaped object. add_java_object(call, PointsToNode::NoEscape); set_not_scalar_replaceable(ptnode_adr(call_idx) NOT_PRODUCT(COMMA "is result of multinewarray")); @@ -2079,7 +2127,7 @@ void ConnectionGraph::add_call_node(CallNode* call) { set_not_scalar_replaceable(ptnode_adr(call_idx) NOT_PRODUCT(COMMA "is result of call")); } else { // Determine whether any arguments are returned. - const TypeTuple* d = call->tf()->domain(); + const TypeTuple* d = call->tf()->domain_cc(); bool ret_arg = false; for (uint i = TypeFunc::Parms; i < d->cnt(); i++) { if (d->field_at(i)->isa_ptr() != nullptr && @@ -2127,7 +2175,7 @@ void ConnectionGraph::process_call_arguments(CallNode *call) { case Op_CallLeaf: { // Stub calls, objects do not escape but they are not scale replaceable. // Adjust escape state for outgoing arguments. - const TypeTuple * d = call->tf()->domain(); + const TypeTuple * d = call->tf()->domain_sig(); bool src_has_oops = false; for (uint i = TypeFunc::Parms; i < d->cnt(); i++) { const Type* at = d->field_at(i); @@ -2158,7 +2206,10 @@ void ConnectionGraph::process_call_arguments(CallNode *call) { aat->isa_ptr() != nullptr, "expecting an Ptr"); bool arg_has_oops = aat->isa_oopptr() && (aat->isa_instptr() || - (aat->isa_aryptr() && (aat->isa_aryptr()->elem() == Type::BOTTOM || aat->isa_aryptr()->elem()->make_oopptr() != nullptr))); + (aat->isa_aryptr() && (aat->isa_aryptr()->elem() == Type::BOTTOM || aat->isa_aryptr()->elem()->make_oopptr() != nullptr)) || + (aat->isa_aryptr() && aat->isa_aryptr()->elem() != nullptr && + aat->isa_aryptr()->is_flat() && + aat->isa_aryptr()->elem()->inline_klass()->contains_oops())); if (i == TypeFunc::Parms) { src_has_oops = arg_has_oops; } @@ -2222,6 +2273,10 @@ void ConnectionGraph::process_call_arguments(CallNode *call) { strcmp(call->as_CallLeaf()->_name, "mulAdd") == 0 || strcmp(call->as_CallLeaf()->_name, "montgomery_multiply") == 0 || strcmp(call->as_CallLeaf()->_name, "montgomery_square") == 0 || + strcmp(call->as_CallLeaf()->_name, "vectorizedMismatch") == 0 || + strcmp(call->as_CallLeaf()->_name, "load_unknown_inline") == 0 || + strcmp(call->as_CallLeaf()->_name, "store_unknown_inline") == 0 || + strcmp(call->as_CallLeaf()->_name, "store_inline_type_fields_to_buf") == 0 || strcmp(call->as_CallLeaf()->_name, "bigIntegerRightShiftWorker") == 0 || strcmp(call->as_CallLeaf()->_name, "bigIntegerLeftShiftWorker") == 0 || strcmp(call->as_CallLeaf()->_name, "vectorizedMismatch") == 0 || @@ -2284,7 +2339,7 @@ void ConnectionGraph::process_call_arguments(CallNode *call) { // fall-through if not a Java method or no analyzer information if (call_analyzer != nullptr) { PointsToNode* call_ptn = ptnode_adr(call->_idx); - const TypeTuple* d = call->tf()->domain(); + const TypeTuple* d = call->tf()->domain_cc(); for (uint i = TypeFunc::Parms; i < d->cnt(); i++) { const Type* at = d->field_at(i); int k = i - TypeFunc::Parms; @@ -2328,7 +2383,7 @@ void ConnectionGraph::process_call_arguments(CallNode *call) { // Fall-through here if not a Java method or no analyzer information // or some other type of call, assume the worst case: all arguments // globally escape. - const TypeTuple* d = call->tf()->domain(); + const TypeTuple* d = call->tf()->domain_cc(); for (uint i = TypeFunc::Parms; i < d->cnt(); i++) { const Type* at = d->field_at(i); if (at->isa_oopptr() != nullptr) { @@ -2741,18 +2796,28 @@ int ConnectionGraph::find_field_value(FieldNode* field) { // Find fields initializing values for allocations. int ConnectionGraph::find_init_values_phantom(JavaObjectNode* pta) { assert(pta->escape_state() == PointsToNode::NoEscape, "Not escaped Allocate nodes only"); + PointsToNode* init_val = phantom_obj; Node* alloc = pta->ideal_node(); // Do nothing for Allocate nodes since its fields values are // "known" unless they are initialized by arraycopy/clone. if (alloc->is_Allocate() && !pta->arraycopy_dst()) { - return 0; + if (alloc->as_Allocate()->in(AllocateNode::InitValue) != nullptr) { + // Null-free inline type arrays are initialized with an init value instead of null + init_val = ptnode_adr(alloc->as_Allocate()->in(AllocateNode::InitValue)->_idx); + assert(init_val != nullptr, "init value should be registered"); + } else { + return 0; + } } - assert(pta->arraycopy_dst() || alloc->as_CallStaticJava(), "sanity"); + // Non-escaped allocation returned from Java or runtime call has unknown values in fields. + assert(pta->arraycopy_dst() || alloc->is_CallStaticJava() || init_val != phantom_obj, "sanity"); #ifdef ASSERT - if (!pta->arraycopy_dst() && alloc->as_CallStaticJava()->method() == nullptr) { + if (alloc->is_CallStaticJava() && alloc->as_CallStaticJava()->method() == nullptr) { const char* name = alloc->as_CallStaticJava()->_name; - assert(strncmp(name, "C2 Runtime multianewarray", 25) == 0, "sanity"); + assert(strncmp(name, "C2 Runtime multianewarray", 25) == 0 || + strncmp(name, "C2 Runtime load_unknown_inline", 30) == 0 || + strncmp(name, "store_inline_type_fields_to_buf", 31) == 0, "sanity"); } #endif // Non-escaped allocation returned from Java or runtime call have unknown values in fields. @@ -2760,7 +2825,7 @@ int ConnectionGraph::find_init_values_phantom(JavaObjectNode* pta) { for (EdgeIterator i(pta); i.has_next(); i.next()) { PointsToNode* field = i.get(); if (field->is_Field() && field->as_Field()->is_oop()) { - if (add_edge(field, phantom_obj)) { + if (add_edge(field, init_val)) { // New edge was added new_edges++; add_field_uses_to_worklist(field->as_Field()); @@ -2775,7 +2840,7 @@ int ConnectionGraph::find_init_values_null(JavaObjectNode* pta, PhaseValues* pha assert(pta->escape_state() == PointsToNode::NoEscape, "Not escaped Allocate nodes only"); Node* alloc = pta->ideal_node(); // Do nothing for Call nodes since its fields values are unknown. - if (!alloc->is_Allocate()) { + if (!alloc->is_Allocate() || alloc->as_Allocate()->in(AllocateNode::InitValue) != nullptr) { return 0; } InitializeNode* ini = alloc->as_Allocate()->initialization(); @@ -2861,9 +2926,9 @@ int ConnectionGraph::find_init_values_null(JavaObjectNode* pta, PhaseValues* pha if (missed_obj != nullptr) { tty->print_cr("----------field---------------------------------"); field->dump(); - tty->print_cr("----------missed referernce to object-----------"); + tty->print_cr("----------missed reference to object------------"); missed_obj->dump(); - tty->print_cr("----------object referernced by init store -----"); + tty->print_cr("----------object referenced by init store-------"); store->dump(); val->dump(); assert(!field->points_to(missed_obj->as_JavaObject()), "missed JavaObject reference"); @@ -3219,7 +3284,8 @@ void ConnectionGraph::optimize_ideal_graph(GrowableArray& ptr_cmp_worklis if (n->is_AbstractLock()) { // Lock and Unlock nodes AbstractLockNode* alock = n->as_AbstractLock(); if (!alock->is_non_esc_obj()) { - if (can_eliminate_lock(alock)) { + const Type* obj_type = igvn->type(alock->obj_node()); + if (can_eliminate_lock(alock) && !obj_type->is_inlinetypeptr()) { assert(!alock->is_eliminated() || alock->is_coarsened(), "sanity"); // The lock could be marked eliminated by lock coarsening // code during first IGVN before EA. Replace coarsened flag @@ -3261,11 +3327,16 @@ void ConnectionGraph::optimize_ideal_graph(GrowableArray& ptr_cmp_worklis Node* storestore = storestore_worklist.at(i); Node* alloc = storestore->in(MemBarNode::Precedent)->in(0); if (alloc->is_Allocate() && not_global_escape(alloc)) { - MemBarNode* mb = MemBarNode::make(C, Op_MemBarCPUOrder, Compile::AliasIdxBot); - mb->init_req(TypeFunc::Memory, storestore->in(TypeFunc::Memory)); - mb->init_req(TypeFunc::Control, storestore->in(TypeFunc::Control)); - igvn->register_new_node_with_optimizer(mb); - igvn->replace_node(storestore, mb); + if (alloc->in(AllocateNode::InlineType) != nullptr) { + // Non-escaping inline type buffer allocations don't require a membar + storestore->as_MemBar()->remove(_igvn); + } else { + MemBarNode* mb = MemBarNode::make(C, Op_MemBarCPUOrder, Compile::AliasIdxBot); + mb->init_req(TypeFunc::Memory, storestore->in(TypeFunc::Memory)); + mb->init_req(TypeFunc::Control, storestore->in(TypeFunc::Control)); + igvn->register_new_node_with_optimizer(mb); + igvn->replace_node(storestore, mb); + } } } } @@ -3429,8 +3500,9 @@ void ConnectionGraph::add_arraycopy(Node *n, PointsToNode::EscapeState es, bool ConnectionGraph::is_oop_field(Node* n, int offset, bool* unsafe) { const Type* adr_type = n->as_AddP()->bottom_type(); + int field_offset = adr_type->isa_aryptr() ? adr_type->isa_aryptr()->field_offset().get() : Type::OffsetBot; BasicType bt = T_INT; - if (offset == Type::OffsetBot) { + if (offset == Type::OffsetBot && field_offset == Type::OffsetBot) { // Check only oop fields. if (!adr_type->isa_aryptr() || adr_type->isa_aryptr()->elem() == Type::BOTTOM || @@ -3442,7 +3514,7 @@ bool ConnectionGraph::is_oop_field(Node* n, int offset, bool* unsafe) { } } else if (offset != oopDesc::klass_offset_in_bytes()) { if (adr_type->isa_instptr()) { - ciField* field = _compile->alias_type(adr_type->isa_instptr())->field(); + ciField* field = _compile->alias_type(adr_type->is_ptr())->field(); if (field != nullptr) { bt = field->layout_type(); } else { @@ -3461,8 +3533,20 @@ bool ConnectionGraph::is_oop_field(Node* n, int offset, bool* unsafe) { } else if (find_second_addp(n, n->in(AddPNode::Base)) != nullptr) { // Ignore first AddP. } else { - const Type* elemtype = adr_type->isa_aryptr()->elem(); - bt = elemtype->array_element_basic_type(); + const Type* elemtype = adr_type->is_aryptr()->elem(); + if (adr_type->is_aryptr()->is_flat() && field_offset != Type::OffsetBot) { + ciInlineKlass* vk = elemtype->inline_klass(); + field_offset += vk->payload_offset(); + ciField* field = vk->get_field_by_offset(field_offset, false); + if (field != nullptr) { + bt = field->layout_type(); + } else { + assert(field_offset == vk->payload_offset() + vk->null_marker_offset_in_payload(), "no field or null marker of %s at offset %d", vk->name()->as_utf8(), field_offset); + bt = T_BOOLEAN; + } + } else { + bt = elemtype->array_element_basic_type(); + } } } else if (adr_type->isa_rawptr() || adr_type->isa_klassptr()) { // Allocation initialization, ThreadLocal field access, unsafe access @@ -3659,9 +3743,7 @@ int ConnectionGraph::address_offset(Node* adr, PhaseValues* phase) { "offset must be a constant or it is initialization of array"); return offs; } - const TypePtr *t_ptr = adr_type->isa_ptr(); - assert(t_ptr != nullptr, "must be a pointer type"); - return t_ptr->offset(); + return adr_type->is_ptr()->flat_offset(); } Node* ConnectionGraph::get_addp_base(Node *addp) { @@ -3815,9 +3897,16 @@ bool ConnectionGraph::split_AddP(Node *addp, Node *base) { assert(addp->in(AddPNode::Address)->is_Proj(), "base of raw address must be result projection from allocation"); intptr_t offs = (int)igvn->find_intptr_t_con(addp->in(AddPNode::Offset), Type::OffsetBot); assert(offs != Type::OffsetBot, "offset must be a constant"); - t = base_t->add_offset(offs)->is_oopptr(); + if (base_t->isa_aryptr() != nullptr) { + // In the case of a flat inline type array, each field has its + // own slice so we need to extract the field being accessed from + // the address computation + t = base_t->isa_aryptr()->add_field_offset_and_offset(offs)->is_oopptr(); + } else { + t = base_t->add_offset(offs)->is_oopptr(); + } } - int inst_id = base_t->instance_id(); + int inst_id = base_t->instance_id(); assert(!t->is_known_instance() || t->instance_id() == inst_id, "old type must be non-instance or match new type"); @@ -3831,7 +3920,7 @@ bool ConnectionGraph::split_AddP(Node *addp, Node *base) { // of the allocation type was not propagated to the subclass type check. // // Or the type 't' could be not related to 'base_t' at all. - // It could happened when CHA type is different from MDO type on a dead path + // It could happen when CHA type is different from MDO type on a dead path // (for example, from instanceof check) which is not collapsed during parsing. // // Do nothing for such AddP node and don't process its users since @@ -3841,7 +3930,18 @@ bool ConnectionGraph::split_AddP(Node *addp, Node *base) { !base_t->maybe_java_subtype_of(t)) { return false; // bail out } - const TypeOopPtr *tinst = base_t->add_offset(t->offset())->is_oopptr(); + const TypePtr* tinst = base_t->add_offset(t->offset()); + if (tinst->isa_aryptr() && t->isa_aryptr()) { + // In the case of a flat inline type array, each field has its + // own slice so we need to keep track of the field being accessed. + tinst = tinst->is_aryptr()->with_field_offset(t->is_aryptr()->field_offset().get()); + // Keep array properties (not flat/null-free) + tinst = tinst->is_aryptr()->update_properties(t->is_aryptr()); + if (tinst == nullptr) { + return false; // Skip dead path with inconsistent properties + } + } + // Do NOT remove the next line: ensure a new alias index is allocated // for the instance type. Note: C++ will not remove it since the call // has side effect. @@ -4547,6 +4647,13 @@ void ConnectionGraph::split_unique_types(GrowableArray &alloc_worklist, tn_t = tn_type->isa_oopptr(); } if (tn_t != nullptr && tinst->maybe_java_subtype_of(tn_t)) { + if (tn_t->isa_aryptr()) { + // Keep array properties (not flat/null-free) + tinst = tinst->is_aryptr()->update_properties(tn_t->is_aryptr()); + if (tinst == nullptr) { + continue; // Skip dead path with inconsistent properties + } + } if (tn_type->isa_narrowoop()) { tn_type = tinst->make_narrowoop(); } else { @@ -4572,7 +4679,7 @@ void ConnectionGraph::split_unique_types(GrowableArray &alloc_worklist, // push allocation's users on appropriate worklist for (DUIterator_Fast imax, i = n->fast_outs(imax); i < imax; i++) { Node *use = n->fast_out(i); - if(use->is_Mem() && use->in(MemNode::Address) == n) { + if (use->is_Mem() && use->in(MemNode::Address) == n) { // Load/store to instance's field memnode_worklist.append_if_missing(use); } else if (use->is_MemBar()) { @@ -4608,6 +4715,9 @@ void ConnectionGraph::split_unique_types(GrowableArray &alloc_worklist, // EncodeISOArray overwrites destination array memnode_worklist.append_if_missing(use); } + } else if (use->Opcode() == Op_Return) { + // Allocation is referenced by field of returned inline type + assert(_compile->tf()->returns_inline_type_as_fields(), "EA: unexpected reference by ReturnNode"); } else { uint op = use->Opcode(); if ((op == Op_StrCompressedCopy || op == Op_StrInflatedCopy) && @@ -4621,7 +4731,7 @@ void ConnectionGraph::split_unique_types(GrowableArray &alloc_worklist, op == Op_StrCompressedCopy || op == Op_StrInflatedCopy || op == Op_StrEquals || op == Op_VectorizedHashCode || op == Op_StrIndexOf || op == Op_StrIndexOfChar || - op == Op_SubTypeCheck || + op == Op_SubTypeCheck || op == Op_InlineType || op == Op_FlatArrayCheck || op == Op_ReinterpretS2HF || BarrierSet::barrier_set()->barrier_set_c2()->is_gc_barrier_node(use))) { n->dump(); @@ -4725,6 +4835,9 @@ void ConnectionGraph::split_unique_types(GrowableArray &alloc_worklist, // get the memory projection n = n->find_out_with(Op_SCMemProj); assert(n != nullptr && n->Opcode() == Op_SCMemProj, "memory projection required"); + } else if (n->is_CallLeaf() && n->as_CallLeaf()->_name != nullptr && + strcmp(n->as_CallLeaf()->_name, "store_unknown_inline") == 0) { + n = n->as_CallLeaf()->proj_out(TypeFunc::Memory); } else { #ifdef ASSERT if (!n->is_Mem()) { @@ -4769,7 +4882,7 @@ void ConnectionGraph::split_unique_types(GrowableArray &alloc_worklist, memnode_worklist.append_if_missing(use); } #ifdef ASSERT - } else if(use->is_Mem()) { + } else if (use->is_Mem()) { assert(use->in(MemNode::Memory) != n, "EA: missing memory path"); } else if (use->is_MergeMem()) { assert(mergemem_worklist.contains(use->as_MergeMem()), "EA: missing MergeMem node in the worklist"); @@ -4778,6 +4891,10 @@ void ConnectionGraph::split_unique_types(GrowableArray &alloc_worklist, // EncodeISOArray overwrites destination array memnode_worklist.append_if_missing(use); } + } else if (use->is_CallLeaf() && use->as_CallLeaf()->_name != nullptr && + strcmp(use->as_CallLeaf()->_name, "store_unknown_inline") == 0) { + // store_unknown_inline overwrites destination array + memnode_worklist.append_if_missing(use); } else { uint op = use->Opcode(); if ((use->in(MemNode::Memory) == n) && @@ -4787,7 +4904,7 @@ void ConnectionGraph::split_unique_types(GrowableArray &alloc_worklist, } else if (!(BarrierSet::barrier_set()->barrier_set_c2()->is_gc_barrier_node(use) || op == Op_AryEq || op == Op_StrComp || op == Op_CountPositives || op == Op_StrCompressedCopy || op == Op_StrInflatedCopy || op == Op_VectorizedHashCode || - op == Op_StrEquals || op == Op_StrIndexOf || op == Op_StrIndexOfChar)) { + op == Op_StrEquals || op == Op_StrIndexOf || op == Op_StrIndexOfChar || op == Op_FlatArrayCheck)) { n->dump(); use->dump(); assert(false, "EA: missing memory path"); @@ -4888,7 +5005,7 @@ void ConnectionGraph::split_unique_types(GrowableArray &alloc_worklist, // First update the inputs of any non-instance Phi's from // which we split out an instance Phi. Note we don't have // to recursively process Phi's encountered on the input memory - // chains as is done in split_memory_phi() since they will + // chains as is done in split_memory_phi() since they will // also be processed here. for (int j = 0; j < orig_phis.length(); j++) { PhiNode *phi = orig_phis.at(j); diff --git a/src/hotspot/share/opto/gcm.cpp b/src/hotspot/share/opto/gcm.cpp index 8859263d8b6..0a2804a6692 100644 --- a/src/hotspot/share/opto/gcm.cpp +++ b/src/hotspot/share/opto/gcm.cpp @@ -233,7 +233,7 @@ void PhaseCFG::schedule_pinned_nodes(VectorSet &visited) { } // process all inputs that are non null - for (int i = node->req()-1; i >= 0; --i) { + for (int i = node->len()-1; i >= 0; --i) { if (node->in(i) != nullptr) { spstack.push(node->in(i)); } @@ -1590,6 +1590,9 @@ void PhaseCFG::schedule_late(VectorSet &visited, Node_Stack &stack) { early->add_inst(self); continue; break; + case Op_CastI2N: + early->add_inst(self); + continue; case Op_CheckCastPP: { // Don't move CheckCastPP nodes away from their input, if the input // is a rawptr (5071820). diff --git a/src/hotspot/share/opto/generateOptoStub.cpp b/src/hotspot/share/opto/generateOptoStub.cpp index 77633857cdf..99c305009db 100644 --- a/src/hotspot/share/opto/generateOptoStub.cpp +++ b/src/hotspot/share/opto/generateOptoStub.cpp @@ -47,8 +47,8 @@ void GraphKit::gen_stub(address C_function, bool return_pc) { ResourceMark rm; - const TypeTuple *jdomain = C->tf()->domain(); - const TypeTuple *jrange = C->tf()->range(); + const TypeTuple *jdomain = C->tf()->domain_sig(); + const TypeTuple *jrange = C->tf()->range_sig(); // The procedure start StartNode* start = new StartNode(root(), jdomain); @@ -269,7 +269,7 @@ void GraphKit::gen_stub(address C_function, exit_memory, frameptr(), returnadr()); - if (C->tf()->range()->cnt() > TypeFunc::Parms) + if (C->tf()->range_sig()->cnt() > TypeFunc::Parms) ret->add_req( map()->in(TypeFunc::Parms) ); break; case 1: // This is a fancy tail-call jump. Jump to computed address. diff --git a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp index d4776d9d2f0..f173fa7f18c 100644 --- a/src/hotspot/share/opto/graphKit.cpp +++ b/src/hotspot/share/opto/graphKit.cpp @@ -23,6 +23,8 @@ */ #include "asm/register.hpp" +#include "ci/ciFlatArrayKlass.hpp" +#include "ci/ciInlineKlass.hpp" #include "ci/ciObjArray.hpp" #include "ci/ciUtilities.hpp" #include "classfile/javaClasses.hpp" @@ -31,14 +33,17 @@ #include "gc/shared/c2/barrierSetC2.hpp" #include "interpreter/interpreter.hpp" #include "memory/resourceArea.hpp" +#include "oops/flatArrayKlass.hpp" #include "opto/addnode.hpp" #include "opto/castnode.hpp" #include "opto/convertnode.hpp" #include "opto/graphKit.hpp" #include "opto/idealKit.hpp" +#include "opto/inlinetypenode.hpp" #include "opto/intrinsicnode.hpp" #include "opto/locknode.hpp" #include "opto/machnode.hpp" +#include "opto/narrowptrnode.hpp" #include "opto/opaquenode.hpp" #include "opto/parse.hpp" #include "opto/rootnode.hpp" @@ -46,21 +51,30 @@ #include "opto/subtypenode.hpp" #include "runtime/deoptimization.hpp" #include "runtime/sharedRuntime.hpp" +#include "runtime/stubRoutines.hpp" #include "utilities/bitMap.inline.hpp" #include "utilities/growableArray.hpp" #include "utilities/powerOfTwo.hpp" //----------------------------GraphKit----------------------------------------- // Main utility constructor. -GraphKit::GraphKit(JVMState* jvms) +GraphKit::GraphKit(JVMState* jvms, PhaseGVN* gvn) : Phase(Phase::Parser), _env(C->env()), - _gvn(*C->initial_gvn()), + _gvn((gvn != nullptr) ? *gvn : *C->initial_gvn()), _barrier_set(BarrierSet::barrier_set()->barrier_set_c2()) { + assert(gvn == nullptr || !gvn->is_IterGVN() || gvn->is_IterGVN()->delay_transform(), "delay transform should be enabled"); _exceptions = jvms->map()->next_exception(); if (_exceptions != nullptr) jvms->map()->set_next_exception(nullptr); set_jvms(jvms); +#ifdef ASSERT + if (_gvn.is_IterGVN() != nullptr) { + assert(_gvn.is_IterGVN()->delay_transform(), "Transformation must be delayed if IterGVN is used"); + // Save the initial size of _for_igvn worklist for verification (see ~GraphKit) + _worklist_size = _gvn.C->igvn_worklist()->size(); + } +#endif } // Private constructor for parser. @@ -345,7 +359,8 @@ void GraphKit::combine_exception_states(SafePointNode* ex_map, SafePointNode* ph JVMState* ex_jvms = ex_map->_jvms; assert(ex_jvms->same_calls_as(phi_map->_jvms), "consistent call chains"); assert(ex_jvms->stkoff() == phi_map->_jvms->stkoff(), "matching locals"); - assert(ex_jvms->sp() == phi_map->_jvms->sp(), "matching stack sizes"); + // TODO 8325632 Re-enable + // assert(ex_jvms->sp() == phi_map->_jvms->sp(), "matching stack sizes"); assert(ex_jvms->monoff() == phi_map->_jvms->monoff(), "matching JVMS"); assert(ex_jvms->scloff() == phi_map->_jvms->scloff(), "matching scalar replaced objects"); assert(ex_map->req() == phi_map->req(), "matching maps"); @@ -873,7 +888,7 @@ static bool should_reexecute_implied_by_bytecode(JVMState *jvms, bool is_anewarr if (cur_method != nullptr && cur_bci != InvocationEntryBci) { Bytecodes::Code code = cur_method->java_code_at_bci(cur_bci); return Interpreter::bytecode_should_reexecute(code) || - (is_anewarray && code == Bytecodes::_multianewarray); + (is_anewarray && (code == Bytecodes::_multianewarray)); // Reexecute _multianewarray bytecode which was replaced with // sequence of [a]newarray. See Parse::do_multianewarray(). // @@ -964,6 +979,8 @@ void GraphKit::add_safepoint_edges(SafePointNode* call, bool must_throw) { // Loop over the map input edges associated with jvms, add them // to the call node, & reset all offsets to match call node array. + + JVMState* callee_jvms = nullptr; for (JVMState* in_jvms = youngest_jvms; in_jvms != nullptr; ) { uint debug_end = debug_ptr; uint debug_start = debug_ptr - in_jvms->debug_size(); @@ -989,8 +1006,9 @@ void GraphKit::add_safepoint_edges(SafePointNode* call, bool must_throw) { l = in_jvms->loc_size(); out_jvms->set_locoff(p); if (!can_prune_locals) { - for (j = 0; j < l; j++) - call->set_req(p++, in_map->in(k+j)); + for (j = 0; j < l; j++) { + call->set_req(p++, in_map->in(k + j)); + } } else { p += l; // already set to top above by add_req_batch } @@ -1000,8 +1018,9 @@ void GraphKit::add_safepoint_edges(SafePointNode* call, bool must_throw) { l = in_jvms->sp(); out_jvms->set_stkoff(p); if (!can_prune_locals) { - for (j = 0; j < l; j++) - call->set_req(p++, in_map->in(k+j)); + for (j = 0; j < l; j++) { + call->set_req(p++, in_map->in(k + j)); + } } else if (can_prune_locals && stack_slots_not_pruned != 0) { // Divide stack into {S0,...,S1}, where S0 is set to top. uint s1 = stack_slots_not_pruned; @@ -1040,6 +1059,7 @@ void GraphKit::add_safepoint_edges(SafePointNode* call, bool must_throw) { assert(out_jvms->debug_size() == in_jvms->debug_size(), "size must match"); // Update the two tail pointers in parallel. + callee_jvms = out_jvms; out_jvms = out_jvms->caller(); in_jvms = in_jvms->caller(); } @@ -1215,7 +1235,7 @@ Node* GraphKit::load_object_klass(Node* obj) { Node* akls = AllocateNode::Ideal_klass(obj, &_gvn); if (akls != nullptr) return akls; Node* k_adr = basic_plus_adr(obj, oopDesc::klass_offset_in_bytes()); - return _gvn.transform(LoadKlassNode::make(_gvn, immutable_memory(), k_adr, TypeInstPtr::KLASS)); + return _gvn.transform(LoadKlassNode::make(_gvn, immutable_memory(), k_adr, TypeInstPtr::KLASS, TypeInstKlassPtr::OBJECT)); } //-------------------------load_array_length----------------------------------- @@ -1264,11 +1284,35 @@ Node* GraphKit::null_check_common(Node* value, BasicType type, // optional arguments for variations: bool assert_null, Node* *null_control, - bool speculative) { + bool speculative, + bool null_marker_check) { assert(!assert_null || null_control == nullptr, "not both at once"); if (stopped()) return top(); NOT_PRODUCT(explicit_null_checks_inserted++); + if (value->is_InlineType()) { + // Null checking a scalarized but nullable inline type. Check the null marker + // input instead of the oop input to avoid keeping buffer allocations alive. + InlineTypeNode* vtptr = value->as_InlineType(); + while (vtptr->get_oop()->is_InlineType()) { + vtptr = vtptr->get_oop()->as_InlineType(); + } + null_check_common(vtptr->get_null_marker(), T_INT, assert_null, null_control, speculative, true); + if (stopped()) { + return top(); + } + if (assert_null) { + // TODO 8284443 Scalarize here (this currently leads to compilation bailouts) + // vtptr = InlineTypeNode::make_null(_gvn, vtptr->type()->inline_klass()); + // replace_in_map(value, vtptr); + // return vtptr; + replace_in_map(value, null()); + return null(); + } + bool do_replace_in_map = (null_control == nullptr || (*null_control) == top()); + return cast_not_null(value, do_replace_in_map); + } + // Construct null check Node *chk = nullptr; switch(type) { @@ -1372,7 +1416,7 @@ Node* GraphKit::null_check_common(Node* value, BasicType type, Deoptimization::DeoptReason reason; if (assert_null) { reason = Deoptimization::reason_null_assert(speculative); - } else if (type == T_OBJECT) { + } else if (type == T_OBJECT || null_marker_check) { reason = Deoptimization::reason_null_check(speculative); } else { reason = Deoptimization::Reason_div0_check; @@ -1446,10 +1490,18 @@ Node* GraphKit::null_check_common(Node* value, BasicType type, return value; } - //------------------------------cast_not_null---------------------------------- // Cast obj to not-null on this path Node* GraphKit::cast_not_null(Node* obj, bool do_replace_in_map) { + if (obj->is_InlineType()) { + Node* vt = obj->isa_InlineType()->clone_if_required(&gvn(), map(), do_replace_in_map); + vt->as_InlineType()->set_null_marker(_gvn); + vt = _gvn.transform(vt); + if (do_replace_in_map) { + replace_in_map(obj, vt); + } + return vt; + } const Type *t = _gvn.type(obj); const Type *t_not_null = t->join_speculative(TypePtr::NOTNULL); // Object is already not-null? @@ -1466,6 +1518,17 @@ Node* GraphKit::cast_not_null(Node* obj, bool do_replace_in_map) { return cast; // Return casted value } +Node* GraphKit::cast_to_non_larval(Node* obj) { + const Type* obj_type = gvn().type(obj); + if (obj->is_InlineType() || !obj_type->is_inlinetypeptr()) { + return obj; + } + + Node* new_obj = InlineTypeNode::make_from_oop(this, obj, obj_type->inline_klass()); + replace_in_map(obj, new_obj); + return new_obj; +} + // Sometimes in intrinsics, we implicitly know an object is not null // (there's no actual null check) so we can cast it to not null. In // the course of optimizations, the input to the cast can become null. @@ -1578,6 +1641,7 @@ Node* GraphKit::make_load(Node* ctl, Node* adr, const Type* t, BasicType bt, Node* mem = memory(adr_idx); Node* ld = LoadNode::make(_gvn, ctl, mem, adr, adr_type, t, bt, mo, control_dependency, require_atomic_access, unaligned, mismatched, unsafe, barrier_data); ld = _gvn.transform(ld); + if (((bt == T_OBJECT) && C->do_escape_analysis()) || C->eliminate_boxing()) { // Improve graph before escape analysis and boxing elimination. record_for_igvn(ld); @@ -1631,7 +1695,9 @@ Node* GraphKit::access_store_at(Node* obj, Node* val, const Type* val_type, BasicType bt, - DecoratorSet decorators) { + DecoratorSet decorators, + bool safe_for_replace, + const InlineTypeNode* vt) { // Transformation of a value which could be null pointer (CastPP #null) // could be delayed during Parse (for example, in adjust_map_after_if()). // Execute transformation here to avoid barrier generation in such case. @@ -1644,10 +1710,17 @@ Node* GraphKit::access_store_at(Node* obj, } assert(val != nullptr, "not dead path"); + if (val->is_InlineType()) { + // Store to non-flat field. Buffer the inline type and make sure + // the store is re-executed if the allocation triggers deoptimization. + PreserveReexecuteState preexecs(this); + jvms()->set_should_reexecute(true); + val = val->as_InlineType()->buffer(this, safe_for_replace); + } C2AccessValuePtr addr(adr, adr_type); C2AccessValue value(val, val_type); - C2ParseAccess access(this, decorators | C2_WRITE_ACCESS, bt, obj, addr); + C2ParseAccess access(this, decorators | C2_WRITE_ACCESS, bt, obj, addr, nullptr, vt); if (access.is_raw()) { return _barrier_set->BarrierSetC2::store_at(access, value); } else { @@ -1660,13 +1733,14 @@ Node* GraphKit::access_load_at(Node* obj, // containing obj const TypePtr* adr_type, const Type* val_type, BasicType bt, - DecoratorSet decorators) { + DecoratorSet decorators, + Node* ctl) { if (stopped()) { return top(); // Dead path ? } C2AccessValuePtr addr(adr, adr_type); - C2ParseAccess access(this, decorators | C2_READ_ACCESS, bt, obj, addr); + C2ParseAccess access(this, decorators | C2_READ_ACCESS, bt, obj, addr, ctl); if (access.is_raw()) { return _barrier_set->BarrierSetC2::load_at(access, val_type); } else { @@ -1771,8 +1845,21 @@ void GraphKit::access_clone(Node* src, Node* dst, Node* size, bool is_array) { //-------------------------array_element_address------------------------- Node* GraphKit::array_element_address(Node* ary, Node* idx, BasicType elembt, const TypeInt* sizetype, Node* ctrl) { - uint shift = exact_log2(type2aelembytes(elembt)); - uint header = arrayOopDesc::base_offset_in_bytes(elembt); + const TypeAryPtr* arytype = _gvn.type(ary)->is_aryptr(); + uint shift; + uint header; + if (arytype->is_flat() && arytype->klass_is_exact()) { + // We can only determine the flat array layout statically if the klass is exact. Otherwise, we could have different + // value classes at runtime with a potentially different layout. The caller needs to fall back to call + // load/store_unknown_inline_Type() at runtime. We could return a sentinel node for the non-exact case but that + // might mess with other GVN transformations in between. Thus, we just continue in the else branch normally, even + // though we don't need the address node in this case and throw it away again. + shift = arytype->flat_log_elem_size(); + header = arrayOopDesc::base_offset_in_bytes(T_FLAT_ELEMENT); + } else { + shift = exact_log2(type2aelembytes(elembt)); + header = arrayOopDesc::base_offset_in_bytes(elembt); + } // short-circuit a common case (saves lots of confusing waste motion) jint idx_con = find_int_con(idx, -1); @@ -1788,6 +1875,34 @@ Node* GraphKit::array_element_address(Node* ary, Node* idx, BasicType elembt, return basic_plus_adr(ary, base, scale); } +Node* GraphKit::cast_to_flat_array(Node* array, ciInlineKlass* vk, bool is_null_free, bool is_not_null_free, bool is_atomic) { + assert(vk->maybe_flat_in_array(), "element of type %s cannot be flat in array", vk->name()->as_utf8()); + if (!vk->has_nullable_atomic_layout()) { + // Element does not have a nullable flat layout, cannot be nullable + is_null_free = true; + } + if (!vk->has_atomic_layout() && !vk->has_non_atomic_layout()) { + // Element does not have a null-free flat layout, cannot be null-free + is_not_null_free = true; + } + if (is_null_free) { + // TODO 8350865 Impossible type + is_not_null_free = false; + } + + bool is_exact = is_null_free || is_not_null_free; + ciArrayKlass* array_klass = ciArrayKlass::make(vk, is_null_free, is_atomic, true); + assert(array_klass->is_elem_null_free() == is_null_free, "inconsistency"); + assert(array_klass->is_elem_atomic() == is_atomic, "inconsistency"); + const TypeAryPtr* arytype = TypeOopPtr::make_from_klass(array_klass)->isa_aryptr(); + arytype = arytype->cast_to_exactness(is_exact); + arytype = arytype->cast_to_not_null_free(is_not_null_free); + assert(arytype->is_null_free() == is_null_free, "inconsistency"); + assert(arytype->is_not_null_free() == is_not_null_free, "inconsistency"); + assert(arytype->is_atomic() == is_atomic, "inconsistency"); + return _gvn.transform(new CastPPNode(control(), array, arytype, ConstraintCastNode::StrongDependency)); +} + //-------------------------load_array_element------------------------- Node* GraphKit::load_array_element(Node* ary, Node* idx, const TypeAryPtr* arytype, bool set_ctrl) { const Type* elemtype = arytype->elem(); @@ -1803,12 +1918,48 @@ Node* GraphKit::load_array_element(Node* ary, Node* idx, const TypeAryPtr* aryty //-------------------------set_arguments_for_java_call------------------------- // Arguments (pre-popped from the stack) are taken from the JVMS. -void GraphKit::set_arguments_for_java_call(CallJavaNode* call) { - // Add the call arguments: - uint nargs = call->method()->arg_size(); - for (uint i = 0; i < nargs; i++) { - Node* arg = argument(i); - call->init_req(i + TypeFunc::Parms, arg); +void GraphKit::set_arguments_for_java_call(CallJavaNode* call, bool is_late_inline) { + PreserveReexecuteState preexecs(this); + if (EnableValhalla) { + // Make sure the call is "re-executed", if buffering of inline type arguments triggers deoptimization. + // At this point, the call hasn't been executed yet, so we will only ever execute the call once. + jvms()->set_should_reexecute(true); + int arg_size = method()->get_declared_signature_at_bci(bci())->arg_size_for_bc(java_bc()); + inc_sp(arg_size); + } + // Add the call arguments + const TypeTuple* domain = call->tf()->domain_sig(); + uint nargs = domain->cnt(); + int arg_num = 0; + for (uint i = TypeFunc::Parms, idx = TypeFunc::Parms; i < nargs; i++) { + Node* arg = argument(i-TypeFunc::Parms); + const Type* t = domain->field_at(i); + // TODO 8284443 A static call to a mismatched method should still be scalarized + if (t->is_inlinetypeptr() && !call->method()->get_Method()->mismatch() && call->method()->is_scalarized_arg(arg_num)) { + // We don't pass inline type arguments by reference but instead pass each field of the inline type + if (!arg->is_InlineType()) { + assert(_gvn.type(arg)->is_zero_type() && !t->inline_klass()->is_null_free(), "Unexpected argument type"); + arg = InlineTypeNode::make_from_oop(this, arg, t->inline_klass()); + } + InlineTypeNode* vt = arg->as_InlineType(); + vt->pass_fields(this, call, idx, true, !t->maybe_null()); + // If an inline type argument is passed as fields, attach the Method* to the call site + // to be able to access the extended signature later via attached_method_before_pc(). + // For example, see CompiledMethod::preserve_callee_argument_oops(). + call->set_override_symbolic_info(true); + // Register an evol dependency on the callee method to make sure that this method is deoptimized and + // re-compiled with a non-scalarized calling convention if the callee method is later marked as mismatched. + C->dependencies()->assert_evol_method(call->method()); + arg_num++; + continue; + } else if (arg->is_InlineType()) { + // Pass inline type argument via oop to callee + arg = arg->as_InlineType()->buffer(this, true); + } + if (t != Type::HALF) { + arg_num++; + } + call->init_req(idx++, arg); } } @@ -1846,13 +1997,6 @@ void GraphKit::set_edges_for_java_call(CallJavaNode* call, bool must_throw, bool Node* GraphKit::set_results_for_java_call(CallJavaNode* call, bool separate_io_proj, bool deoptimize) { if (stopped()) return top(); // maybe the call folded up? - // Capture the return value, if any. - Node* ret; - if (call->method() == nullptr || - call->method()->return_type()->basic_type() == T_VOID) - ret = top(); - else ret = _gvn.transform(new ProjNode(call, TypeFunc::Parms)); - // Note: Since any out-of-line call can produce an exception, // we always insert an I_O projection from the call into the result. @@ -1865,6 +2009,78 @@ Node* GraphKit::set_results_for_java_call(CallJavaNode* call, bool separate_io_p set_i_o(_gvn.transform( new ProjNode(call, TypeFunc::I_O) )); set_all_memory(_gvn.transform( new ProjNode(call, TypeFunc::Memory) )); } + + // Capture the return value, if any. + Node* ret; + if (call->method() == nullptr || call->method()->return_type()->basic_type() == T_VOID) { + ret = top(); + } else if (call->tf()->returns_inline_type_as_fields()) { + // Return of multiple values (inline type fields): we create a + // InlineType node, each field is a projection from the call. + ciInlineKlass* vk = call->method()->return_type()->as_inline_klass(); + uint base_input = TypeFunc::Parms; + ret = InlineTypeNode::make_from_multi(this, call, vk, base_input, false, false); + } else { + ret = _gvn.transform(new ProjNode(call, TypeFunc::Parms)); + ciType* t = call->method()->return_type(); + if (!t->is_loaded() && InlineTypeReturnedAsFields) { + // The return type is unloaded but the callee might later be C2 compiled and then return + // in scalarized form when the return type is loaded. Handle this similar to what we do in + // PhaseMacroExpand::expand_mh_intrinsic_return by calling into the runtime to buffer. + // It's a bit unfortunate because we will deopt anyway but the interpreter needs an oop. + IdealKit ideal(this); + IdealVariable res(ideal); + ideal.declarations_done(); + ideal.if_then(ret, BoolTest::eq, ideal.makecon(TypePtr::NULL_PTR)); { + // Return value is null + ideal.set(res, ret); + } ideal.else_(); { + // Return value is non-null + sync_kit(ideal); + + // Change return type of call to scalarized return + const TypeFunc* tf = call->_tf; + const TypeTuple* domain = OptoRuntime::store_inline_type_fields_Type()->domain_cc(); + const TypeFunc* new_tf = TypeFunc::make(tf->domain_sig(), tf->domain_cc(), tf->range_sig(), domain); + call->_tf = new_tf; + _gvn.set_type(call, call->Value(&_gvn)); + _gvn.set_type(ret, ret->Value(&_gvn)); + + Node* store_to_buf_call = make_runtime_call(RC_NO_LEAF | RC_NO_IO, + OptoRuntime::store_inline_type_fields_Type(), + StubRoutines::store_inline_type_fields_to_buf(), + nullptr, TypePtr::BOTTOM, ret); + + // We don't know how many values are returned. This assumes the + // worst case, that all available registers are used. + for (uint i = TypeFunc::Parms+1; i < domain->cnt(); i++) { + if (domain->field_at(i) == Type::HALF) { + store_to_buf_call->init_req(i, top()); + continue; + } + Node* proj =_gvn.transform(new ProjNode(call, i)); + store_to_buf_call->init_req(i, proj); + } + make_slow_call_ex(store_to_buf_call, env()->Throwable_klass(), false); + + Node* buf = _gvn.transform(new ProjNode(store_to_buf_call, TypeFunc::Parms)); + const Type* buf_type = TypeOopPtr::make_from_klass(t->as_klass())->join_speculative(TypePtr::NOTNULL); + buf = _gvn.transform(new CheckCastPPNode(control(), buf, buf_type)); + + ideal.set(res, buf); + ideal.sync_kit(this); + } ideal.end_if(); + sync_kit(ideal); + ret = _gvn.transform(ideal.value(res)); + } + if (t->is_klass()) { + const Type* type = TypeOopPtr::make_from_klass(t->as_klass()); + if (type->is_inlinetypeptr()) { + ret = InlineTypeNode::make_from_oop(this, ret, type->inline_klass()); + } + } + } + return ret; } @@ -1966,8 +2182,7 @@ void GraphKit::replace_call(CallNode* call, Node* result, bool do_replaced_nodes SafePointNode* final_state = stop(); // Find all the needed outputs of this call - CallProjections callprojs; - call->extract_projections(&callprojs, true, do_asserts); + CallProjections* callprojs = call->extract_projections(true, do_asserts); Unique_Node_List wl; Node* init_mem = call->in(TypeFunc::Memory); @@ -1976,40 +2191,44 @@ void GraphKit::replace_call(CallNode* call, Node* result, bool do_replaced_nodes Node* final_io = final_state->in(TypeFunc::I_O); // Replace all the old call edges with the edges from the inlining result - if (callprojs.fallthrough_catchproj != nullptr) { - C->gvn_replace_by(callprojs.fallthrough_catchproj, final_ctl); + if (callprojs->fallthrough_catchproj != nullptr) { + C->gvn_replace_by(callprojs->fallthrough_catchproj, final_ctl); } - if (callprojs.fallthrough_memproj != nullptr) { + if (callprojs->fallthrough_memproj != nullptr) { if (final_mem->is_MergeMem()) { // Parser's exits MergeMem was not transformed but may be optimized final_mem = _gvn.transform(final_mem); } - C->gvn_replace_by(callprojs.fallthrough_memproj, final_mem); + C->gvn_replace_by(callprojs->fallthrough_memproj, final_mem); add_mergemem_users_to_worklist(wl, final_mem); } - if (callprojs.fallthrough_ioproj != nullptr) { - C->gvn_replace_by(callprojs.fallthrough_ioproj, final_io); + if (callprojs->fallthrough_ioproj != nullptr) { + C->gvn_replace_by(callprojs->fallthrough_ioproj, final_io); } // Replace the result with the new result if it exists and is used - if (callprojs.resproj != nullptr && result != nullptr) { - C->gvn_replace_by(callprojs.resproj, result); + if (callprojs->resproj[0] != nullptr && result != nullptr) { + // If the inlined code is dead, the result projections for an inline type returned as + // fields have not been replaced. They will go away once the call is replaced by TOP below. + assert(callprojs->nb_resproj == 1 || (call->tf()->returns_inline_type_as_fields() && stopped()), + "unexpected number of results"); + C->gvn_replace_by(callprojs->resproj[0], result); } if (ejvms == nullptr) { // No exception edges to simply kill off those paths - if (callprojs.catchall_catchproj != nullptr) { - C->gvn_replace_by(callprojs.catchall_catchproj, C->top()); + if (callprojs->catchall_catchproj != nullptr) { + C->gvn_replace_by(callprojs->catchall_catchproj, C->top()); } - if (callprojs.catchall_memproj != nullptr) { - C->gvn_replace_by(callprojs.catchall_memproj, C->top()); + if (callprojs->catchall_memproj != nullptr) { + C->gvn_replace_by(callprojs->catchall_memproj, C->top()); } - if (callprojs.catchall_ioproj != nullptr) { - C->gvn_replace_by(callprojs.catchall_ioproj, C->top()); + if (callprojs->catchall_ioproj != nullptr) { + C->gvn_replace_by(callprojs->catchall_ioproj, C->top()); } // Replace the old exception object with top - if (callprojs.exobj != nullptr) { - C->gvn_replace_by(callprojs.exobj, C->top()); + if (callprojs->exobj != nullptr) { + C->gvn_replace_by(callprojs->exobj, C->top()); } } else { GraphKit ekit(ejvms); @@ -2020,22 +2239,22 @@ void GraphKit::replace_call(CallNode* call, Node* result, bool do_replaced_nodes Node* ex_oop = ekit.use_exception_state(ex_map); - if (callprojs.catchall_catchproj != nullptr) { - C->gvn_replace_by(callprojs.catchall_catchproj, ekit.control()); + if (callprojs->catchall_catchproj != nullptr) { + C->gvn_replace_by(callprojs->catchall_catchproj, ekit.control()); ex_ctl = ekit.control(); } - if (callprojs.catchall_memproj != nullptr) { + if (callprojs->catchall_memproj != nullptr) { Node* ex_mem = ekit.reset_memory(); - C->gvn_replace_by(callprojs.catchall_memproj, ex_mem); + C->gvn_replace_by(callprojs->catchall_memproj, ex_mem); add_mergemem_users_to_worklist(wl, ex_mem); } - if (callprojs.catchall_ioproj != nullptr) { - C->gvn_replace_by(callprojs.catchall_ioproj, ekit.i_o()); + if (callprojs->catchall_ioproj != nullptr) { + C->gvn_replace_by(callprojs->catchall_ioproj, ekit.i_o()); } // Replace the old exception object with the newly created one - if (callprojs.exobj != nullptr) { - C->gvn_replace_by(callprojs.exobj, ex_oop); + if (callprojs->exobj != nullptr) { + C->gvn_replace_by(callprojs->exobj, ex_oop); } } @@ -2049,7 +2268,7 @@ void GraphKit::replace_call(CallNode* call, Node* result, bool do_replaced_nodes _gvn.transform(wl.pop()); } - if (callprojs.fallthrough_catchproj != nullptr && !final_ctl->is_top() && do_replaced_nodes) { + if (callprojs->fallthrough_catchproj != nullptr && !final_ctl->is_top() && do_replaced_nodes) { replaced_nodes.apply(C, final_ctl); } if (!ex_ctl->is_top() && do_replaced_nodes) { @@ -2249,7 +2468,7 @@ Node* GraphKit::record_profile_for_speculation(Node* n, ciKlass* exact_kls, Prof assert(xtype->klass_is_exact(), "Should be exact"); // Any reason to believe n is not null (from this profiling or a previous one)? assert(ptr_kind != ProfileAlwaysNull, "impossible here"); - const TypePtr* ptr = (ptr_kind == ProfileMaybeNull && current_type->speculative_maybe_null()) ? TypePtr::BOTTOM : TypePtr::NOTNULL; + const TypePtr* ptr = (ptr_kind != ProfileNeverNull && current_type->speculative_maybe_null()) ? TypePtr::BOTTOM : TypePtr::NOTNULL; // record the new speculative type's depth speculative = xtype->cast_to_ptr_type(ptr->ptr())->is_ptr(); speculative = speculative->with_inline_depth(jvms()->depth()); @@ -2272,7 +2491,7 @@ Node* GraphKit::record_profile_for_speculation(Node* n, ciKlass* exact_kls, Prof if (speculative != current_type->speculative()) { // Build a type with a speculative type (what we think we know // about the type but will need a guard when we use it) - const TypeOopPtr* spec_type = TypeOopPtr::make(TypePtr::BotPTR, Type::OffsetBot, TypeOopPtr::InstanceBot, speculative); + const TypeOopPtr* spec_type = TypeOopPtr::make(TypePtr::BotPTR, Type::Offset::bottom, TypeOopPtr::InstanceBot, speculative); // We're changing the type, we need a new CheckCast node to carry // the new type. The new type depends on the control: what // profiling tells us is only valid from here as far as we can @@ -2306,20 +2525,31 @@ Node* GraphKit::record_profiled_receiver_for_speculation(Node* n) { method()->method_data()->is_mature()) { ciProfileData* data = method()->method_data()->bci_to_data(bci()); if (data != nullptr) { - if (!data->as_BitData()->null_seen()) { - ptr_kind = ProfileNeverNull; + if (java_bc() == Bytecodes::_aastore) { + ciKlass* array_type = nullptr; + ciKlass* element_type = nullptr; + ProfilePtrKind element_ptr = ProfileMaybeNull; + bool flat_array = true; + bool null_free_array = true; + method()->array_access_profiled_type(bci(), array_type, element_type, element_ptr, flat_array, null_free_array); + exact_kls = element_type; + ptr_kind = element_ptr; } else { - if (TypeProfileCasts) { - assert(data->is_ReceiverTypeData(), "bad profile data type"); - ciReceiverTypeData* call = (ciReceiverTypeData*)data->as_ReceiverTypeData(); - uint i = 0; - for (; i < call->row_limit(); i++) { - ciKlass* receiver = call->receiver(i); - if (receiver != nullptr) { - break; + if (!data->as_BitData()->null_seen()) { + ptr_kind = ProfileNeverNull; + } else { + if (TypeProfileCasts) { + assert(data->is_ReceiverTypeData(), "bad profile data type"); + ciReceiverTypeData* call = (ciReceiverTypeData*)data->as_ReceiverTypeData(); + uint i = 0; + for (; i < call->row_limit(); i++) { + ciKlass* receiver = call->receiver(i); + if (receiver != nullptr) { + break; + } } + ptr_kind = (i == call->row_limit()) ? ProfileAlwaysNull : ProfileMaybeNull; } - ptr_kind = (i == call->row_limit()) ? ProfileAlwaysNull : ProfileMaybeNull; } } } @@ -2339,10 +2569,10 @@ void GraphKit::record_profiled_arguments_for_speculation(ciMethod* dest_method, return; } const TypeFunc* tf = TypeFunc::make(dest_method); - int nargs = tf->domain()->cnt() - TypeFunc::Parms; + int nargs = tf->domain_sig()->cnt() - TypeFunc::Parms; int skip = Bytecodes::has_receiver(bc) ? 1 : 0; for (int j = skip, i = 0; j < nargs && i < TypeProfileArgsLimit; j++) { - const Type *targ = tf->domain()->field_at(j + TypeFunc::Parms); + const Type *targ = tf->domain_sig()->field_at(j + TypeFunc::Parms); if (is_reference_type(targ->basic_type())) { ProfilePtrKind ptr_kind = ProfileMaybeNull; ciKlass* better_type = nullptr; @@ -2502,7 +2732,7 @@ Node* GraphKit::make_runtime_call(int flags, } else if (flags & RC_NO_FP) { call = new CallLeafNoFPNode(call_type, call_addr, call_name, adr_type); } else if (flags & RC_VECTOR){ - uint num_bits = call_type->range()->field_at(TypeFunc::Parms)->is_vect()->length_in_bytes() * BitsPerByte; + uint num_bits = call_type->range_sig()->field_at(TypeFunc::Parms)->is_vect()->length_in_bytes() * BitsPerByte; call = new CallLeafVectorNode(call_type, call_addr, call_name, adr_type, num_bits); } else if (flags & RC_PURE) { call = new CallLeafPureNode(call_type, call_addr, call_name, adr_type); @@ -2536,7 +2766,7 @@ Node* GraphKit::make_runtime_call(int flags, if (parm6 != nullptr) { call->init_req(TypeFunc::Parms+6, parm6); if (parm7 != nullptr) { call->init_req(TypeFunc::Parms+7, parm7); /* close each nested if ===> */ } } } } } } } } - assert(call->in(call->req()-1) != nullptr, "must initialize all parms"); + assert(call->in(call->req()-1) != nullptr || (call->req()-1) > (TypeFunc::Parms+7), "must initialize all parms"); if (!is_leaf) { // Non-leaves can block and take safepoints: @@ -2584,6 +2814,7 @@ Node* GraphKit::sign_extend_short(Node* in) { return _gvn.transform(new RShiftINode(tmp, _gvn.intcon(16))); } + //------------------------------merge_memory----------------------------------- // Merge memory from one path into the current memory state. void GraphKit::merge_memory(Node* new_mem, Node* region, int new_path) { @@ -2680,6 +2911,15 @@ Node* Phase::gen_subtype_check(Node* subklass, Node* superklass, Node** ctrl, No return C->top(); } + const TypeKlassPtr* klass_ptr_type = gvn.type(superklass)->is_klassptr(); + const TypeAryKlassPtr* ary_klass_t = klass_ptr_type->isa_aryklassptr(); + Node* vm_superklass = superklass; + // TODO 8366668 Compute the VM type here for when we do a direct pointer comparison + if (ary_klass_t && ary_klass_t->klass_is_exact() && ary_klass_t->exact_klass()->is_obj_array_klass()) { + ary_klass_t = ary_klass_t->get_vm_type(); + vm_superklass = gvn.makecon(ary_klass_t); + } + // Fast check for identical types, perhaps identical constants. // The types can even be identical non-constants, in cases // involving Array.newInstance, Object.clone, etc. @@ -2717,7 +2957,7 @@ Node* Phase::gen_subtype_check(Node* subklass, Node* superklass, Node** ctrl, No case Compile::SSC_easy_test: { // Just do a direct pointer compare and be done. - IfNode* iff = gen_subtype_check_compare(*ctrl, subklass, superklass, BoolTest::eq, PROB_STATIC_FREQUENT, gvn, T_ADDRESS); + IfNode* iff = gen_subtype_check_compare(*ctrl, subklass, vm_superklass, BoolTest::eq, PROB_STATIC_FREQUENT, gvn, T_ADDRESS); *ctrl = gvn.transform(new IfTrueNode(iff)); return gvn.transform(new IfFalseNode(iff)); } @@ -2739,7 +2979,8 @@ Node* Phase::gen_subtype_check(Node* subklass, Node* superklass, Node** ctrl, No int cacheoff_con = in_bytes(Klass::secondary_super_cache_offset()); const TypeInt* chk_off_t = chk_off->Value(&gvn)->isa_int(); int chk_off_con = (chk_off_t != nullptr && chk_off_t->is_con()) ? chk_off_t->get_con() : cacheoff_con; - bool might_be_cache = (chk_off_con == cacheoff_con); + // TODO 8366668 Re-enable. This breaks test/hotspot/jtreg/compiler/c2/irTests/ProfileAtTypeCheck.java + bool might_be_cache = true;//(chk_off_con == cacheoff_con); // Load from the sub-klass's super-class display list, or a 1-word cache of // the secondary superclass list, or a failing value with a sentinel offset @@ -2790,6 +3031,7 @@ Node* Phase::gen_subtype_check(Node* subklass, Node* superklass, Node** ctrl, No const TypeKlassPtr* superk = gvn.type(superklass)->is_klassptr(); for (int i = 0; profile.has_receiver(i); ++i) { ciKlass* klass = profile.receiver(i); + // TODO 8366668 Do we need adjustments here?? const TypeKlassPtr* klass_t = TypeKlassPtr::make(klass); Compile::SubTypeCheckResult result = C->static_subtype_check(superk, klass_t); if (result != Compile::SSC_always_true && result != Compile::SSC_always_false) { @@ -2840,15 +3082,17 @@ Node* Phase::gen_subtype_check(Node* subklass, Node* superklass, Node** ctrl, No // check-offset points into the subklass display list or the 1-element // cache. If it points to the display (and NOT the cache) and the display // missed then it's not a subtype. + // TODO 8366668 Re-enable +/* Node *cacheoff = gvn.intcon(cacheoff_con); IfNode *iff2 = gen_subtype_check_compare(*ctrl, chk_off, cacheoff, BoolTest::ne, PROB_LIKELY(0.63f), gvn, T_INT); r_not_subtype->init_req(1, gvn.transform(new IfTrueNode (iff2))); *ctrl = gvn.transform(new IfFalseNode(iff2)); - +*/ // Check for self. Very rare to get here, but it is taken 1/3 the time. // No performance impact (too rare) but allows sharing of secondary arrays // which has some footprint reduction. - IfNode *iff3 = gen_subtype_check_compare(*ctrl, subklass, superklass, BoolTest::eq, PROB_LIKELY(0.36f), gvn, T_ADDRESS); + IfNode *iff3 = gen_subtype_check_compare(*ctrl, subklass, vm_superklass, BoolTest::eq, PROB_LIKELY(0.36f), gvn, T_ADDRESS); r_ok_subtype->init_req(2, gvn.transform(new IfTrueNode(iff3))); *ctrl = gvn.transform(new IfFalseNode(iff3)); @@ -2886,12 +3130,17 @@ Node* Phase::gen_subtype_check(Node* subklass, Node* superklass, Node** ctrl, No } Node* GraphKit::gen_subtype_check(Node* obj_or_subklass, Node* superklass) { + const Type* sub_t = _gvn.type(obj_or_subklass); + if (sub_t->make_oopptr() != nullptr && sub_t->make_oopptr()->is_inlinetypeptr()) { + sub_t = TypeKlassPtr::make(sub_t->inline_klass()); + obj_or_subklass = makecon(sub_t); + } bool expand_subtype_check = C->post_loop_opts_phase(); // macro node expansion is over if (expand_subtype_check) { MergeMemNode* mem = merged_memory(); Node* ctrl = control(); Node* subklass = obj_or_subklass; - if (!_gvn.type(obj_or_subklass)->isa_klassptr()) { + if (!sub_t->isa_klassptr()) { subklass = load_object_klass(obj_or_subklass); } @@ -2909,29 +3158,44 @@ Node* GraphKit::gen_subtype_check(Node* obj_or_subklass, Node* superklass) { // Profile-driven exact type check: Node* GraphKit::type_check_receiver(Node* receiver, ciKlass* klass, - float prob, - Node* *casted_receiver) { + float prob, Node* *casted_receiver) { assert(!klass->is_interface(), "no exact type check on interfaces"); - + Node* fail = top(); + const Type* rec_t = _gvn.type(receiver); + if (rec_t->is_inlinetypeptr()) { + if (klass->equals(rec_t->inline_klass())) { + (*casted_receiver) = receiver; // Always passes + } else { + (*casted_receiver) = top(); // Always fails + fail = control(); + set_control(top()); + } + return fail; + } const TypeKlassPtr* tklass = TypeKlassPtr::make(klass, Type::trust_interfaces); + const TypeAryKlassPtr* ary_klass_t = tklass->isa_aryklassptr(); + // TODO 8366668 Compute the VM type + if (ary_klass_t && ary_klass_t->klass_is_exact() && ary_klass_t->exact_klass()->is_obj_array_klass()) { + tklass = ary_klass_t->get_vm_type(); + } Node* recv_klass = load_object_klass(receiver); - Node* want_klass = makecon(tklass); - Node* cmp = _gvn.transform(new CmpPNode(recv_klass, want_klass)); - Node* bol = _gvn.transform(new BoolNode(cmp, BoolTest::eq)); - IfNode* iff = create_and_xform_if(control(), bol, prob, COUNT_UNKNOWN); - set_control( _gvn.transform(new IfTrueNode (iff))); - Node* fail = _gvn.transform(new IfFalseNode(iff)); + fail = type_check(recv_klass, tklass, prob); if (!stopped()) { const TypeOopPtr* receiver_type = _gvn.type(receiver)->isa_oopptr(); - const TypeOopPtr* recvx_type = tklass->as_instance_type(); - assert(recvx_type->klass_is_exact(), ""); + const TypeOopPtr* recv_xtype = tklass->as_instance_type(); + assert(recv_xtype->klass_is_exact(), ""); - if (!receiver_type->higher_equal(recvx_type)) { // ignore redundant casts + if (!receiver_type->higher_equal(recv_xtype)) { // ignore redundant casts // Subsume downstream occurrences of receiver with a cast to // recv_xtype, since now we know what the type will be. - Node* cast = new CheckCastPPNode(control(), receiver, recvx_type); - (*casted_receiver) = _gvn.transform(cast); + Node* cast = new CheckCastPPNode(control(), receiver, recv_xtype); + Node* res = _gvn.transform(cast); + if (recv_xtype->is_inlinetypeptr()) { + assert(!gvn().type(res)->maybe_null(), "receiver should never be null"); + res = InlineTypeNode::make_from_oop(this, res, recv_xtype->inline_klass()); + } + (*casted_receiver) = res; assert(!(*casted_receiver)->is_top(), "that path should be unreachable"); // (User must make the replace_in_map call.) } @@ -2940,6 +3204,17 @@ Node* GraphKit::type_check_receiver(Node* receiver, ciKlass* klass, return fail; } +Node* GraphKit::type_check(Node* recv_klass, const TypeKlassPtr* tklass, + float prob) { + Node* want_klass = makecon(tklass); + Node* cmp = _gvn.transform(new CmpPNode(recv_klass, want_klass)); + Node* bol = _gvn.transform(new BoolNode(cmp, BoolTest::eq)); + IfNode* iff = create_and_xform_if(control(), bol, prob, COUNT_UNKNOWN); + set_control(_gvn.transform(new IfTrueNode (iff))); + Node* fail = _gvn.transform(new IfFalseNode(iff)); + return fail; +} + //------------------------------subtype_check_receiver------------------------- Node* GraphKit::subtype_check_receiver(Node* receiver, ciKlass* klass, Node** casted_receiver) { @@ -2952,9 +3227,12 @@ Node* GraphKit::subtype_check_receiver(Node* receiver, ciKlass* klass, if (!stopped() && !klass->is_interface()) { const TypeOopPtr* receiver_type = _gvn.type(receiver)->isa_oopptr(); const TypeOopPtr* recv_type = tklass->cast_to_exactness(false)->is_klassptr()->as_instance_type(); - if (!receiver_type->higher_equal(recv_type)) { // ignore redundant casts - Node* cast = new CheckCastPPNode(control(), receiver, recv_type); - (*casted_receiver) = _gvn.transform(cast); + if (receiver_type != nullptr && !receiver_type->higher_equal(recv_type)) { // ignore redundant casts + Node* cast = _gvn.transform(new CheckCastPPNode(control(), receiver, recv_type)); + if (recv_type->is_inlinetypeptr()) { + cast = InlineTypeNode::make_from_oop(this, cast, recv_type->inline_klass()); + } + (*casted_receiver) = cast; } } @@ -3063,7 +3341,20 @@ Node* GraphKit::maybe_cast_profiled_receiver(Node* not_null_obj, // to use the same ciMethod accessor to get the profile info...) // If we have a speculative type use it instead of profiling (which // may not help us) - ciKlass* exact_kls = spec_klass == nullptr ? profile_has_unique_klass() : spec_klass; + ciKlass* exact_kls = spec_klass; + if (exact_kls == nullptr) { + if (java_bc() == Bytecodes::_aastore) { + ciKlass* array_type = nullptr; + ciKlass* element_type = nullptr; + ProfilePtrKind element_ptr = ProfileMaybeNull; + bool flat_array = true; + bool null_free_array = true; + method()->array_access_profiled_type(bci(), array_type, element_type, element_ptr, flat_array, null_free_array); + exact_kls = element_type; + } else { + exact_kls = profile_has_unique_klass(); + } + } if (exact_kls != nullptr) {// no cast failures here if (require_klass == nullptr || C->static_subtype_check(require_klass, TypeKlassPtr::make(exact_kls, Type::trust_interfaces)) == Compile::SSC_always_true) { @@ -3193,7 +3484,7 @@ Node* GraphKit::gen_instanceof(Node* obj, Node* superklass, bool safe_for_replac if (_gvn.type(superklass)->singleton()) { const TypeKlassPtr* superk = _gvn.type(superklass)->is_klassptr(); const TypeKlassPtr* subk = _gvn.type(obj)->is_oopptr()->as_klass_type(); - if (subk->is_loaded()) { + if (subk != nullptr && subk->is_loaded()) { int static_res = C->static_subtype_check(superk, subk); known_statically = (static_res == Compile::SSC_always_true || static_res == Compile::SSC_always_false); } @@ -3249,12 +3540,29 @@ Node* GraphKit::gen_instanceof(Node* obj, Node* superklass, bool safe_for_replac // If failure_control is supplied and not null, it is filled in with // the control edge for the cast failure. Otherwise, an appropriate // uncommon trap or exception is thrown. -Node* GraphKit::gen_checkcast(Node *obj, Node* superklass, - Node* *failure_control) { +Node* GraphKit::gen_checkcast(Node* obj, Node* superklass, Node* *failure_control, bool null_free, bool maybe_larval) { kill_dead_locals(); // Benefit all the uncommon traps const TypeKlassPtr* klass_ptr_type = _gvn.type(superklass)->is_klassptr(); + const Type* obj_type = _gvn.type(obj); + if (obj_type->is_inlinetypeptr() && !obj_type->maybe_null() && klass_ptr_type->klass_is_exact() && obj_type->inline_klass() == klass_ptr_type->exact_klass(true)) { + // Special case: larval inline objects must not be scalarized. They are also generally not + // allowed to participate in most operations except as the first operand of putfield, or as an + // argument to a constructor invocation with it being a receiver, Unsafe::putXXX with it being + // the first argument, or Unsafe::finishPrivateBuffer. This allows us to aggressively scalarize + // value objects in all other places. This special case comes from the limitation of the Java + // language, Unsafe::makePrivateBuffer returns an Object that is checkcast-ed to the concrete + // value type. We must do this first because C->static_subtype_check may do nothing when + // StressReflectiveCode is set. + return obj; + } + + // Else it must be a non-larval object + obj = cast_to_non_larval(obj); + const TypeKlassPtr* improved_klass_ptr_type = klass_ptr_type->try_improve(); const TypeOopPtr* toop = improved_klass_ptr_type->cast_to_exactness(false)->as_instance_type(); + bool safe_for_replace = (failure_control == nullptr); + assert(!null_free || toop->can_be_inline_type(), "must be an inline type pointer"); // Fast cutout: Check the case that the cast is vacuously true. // This detects the common cases where the test will short-circuit @@ -3263,18 +3571,34 @@ Node* GraphKit::gen_checkcast(Node *obj, Node* superklass, // want a residual null check left around. (Causes a slowdown, // for example, in some objArray manipulations, such as a[i]=a[j].) if (improved_klass_ptr_type->singleton()) { - const TypeOopPtr* objtp = _gvn.type(obj)->isa_oopptr(); - if (objtp != nullptr) { - switch (C->static_subtype_check(improved_klass_ptr_type, objtp->as_klass_type())) { + const TypeKlassPtr* kptr = nullptr; + if (obj_type->isa_oop_ptr()) { + kptr = obj_type->is_oopptr()->as_klass_type(); + } else if (obj->is_InlineType()) { + ciInlineKlass* vk = obj_type->inline_klass(); + kptr = TypeInstKlassPtr::make(TypePtr::NotNull, vk, Type::Offset(0)); + } + + if (kptr != nullptr) { + switch (C->static_subtype_check(improved_klass_ptr_type, kptr)) { case Compile::SSC_always_true: // If we know the type check always succeed then we don't use // the profiling data at this bytecode. Don't lose it, feed it // to the type system as a speculative type. - return record_profiled_receiver_for_speculation(obj); + obj = record_profiled_receiver_for_speculation(obj); + if (null_free) { + assert(safe_for_replace, "must be"); + obj = null_check(obj); + } + assert(stopped() || !toop->is_inlinetypeptr() || obj->is_InlineType(), "should have been scalarized"); + return obj; case Compile::SSC_always_false: + if (null_free) { + assert(safe_for_replace, "must be"); + obj = null_check(obj); + } // It needs a null check because a null will *pass* the cast check. - // A non-null value will always produce an exception. - if (!objtp->maybe_null()) { + if (obj_type->isa_oopptr() != nullptr && !obj_type->is_oopptr()->maybe_null()) { bool is_aastore = (java_bc() == Bytecodes::_aastore); Deoptimization::DeoptReason reason = is_aastore ? Deoptimization::Reason_array_check : Deoptimization::Reason_class_check; @@ -3291,19 +3615,22 @@ Node* GraphKit::gen_checkcast(Node *obj, Node* superklass, } ciProfileData* data = nullptr; - bool safe_for_replace = false; if (failure_control == nullptr) { // use MDO in regular case only assert(java_bc() == Bytecodes::_aastore || java_bc() == Bytecodes::_checkcast, "interpreter profiles type checks only for these BCs"); - data = method()->method_data()->bci_to_data(bci()); - safe_for_replace = true; + if (method()->method_data()->is_mature()) { + data = method()->method_data()->bci_to_data(bci()); + } } // Make the merge point enum { _obj_path = 1, _null_path, PATH_LIMIT }; RegionNode* region = new RegionNode(PATH_LIMIT); Node* phi = new PhiNode(region, toop); + _gvn.set_type(region, Type::CONTROL); + _gvn.set_type(phi, toop); + C->set_has_split_ifs(true); // Has chance for split-if optimization // Use null-cast information if it is available @@ -3311,13 +3638,29 @@ Node* GraphKit::gen_checkcast(Node *obj, Node* superklass, bool never_see_null = ((failure_control == nullptr) // regular case only && seems_never_null(obj, data, speculative_not_null)); + if (obj->is_InlineType()) { + // Re-execute if buffering during triggers deoptimization + PreserveReexecuteState preexecs(this); + jvms()->set_should_reexecute(true); + obj = obj->as_InlineType()->buffer(this, safe_for_replace); + } + // Null check; get casted pointer; set region slot 3 Node* null_ctl = top(); - Node* not_null_obj = null_check_oop(obj, &null_ctl, never_see_null, safe_for_replace, speculative_not_null); + Node* not_null_obj = nullptr; + if (null_free) { + assert(safe_for_replace, "must be"); + not_null_obj = null_check(obj); + } else { + not_null_obj = null_check_oop(obj, &null_ctl, never_see_null, safe_for_replace, speculative_not_null); + } // If not_null_obj is dead, only null-path is taken if (stopped()) { // Doing instance-of on a null? set_control(null_ctl); + if (toop->is_inlinetypeptr()) { + return InlineTypeNode::make_null(_gvn, toop->inline_klass()); + } return null(); } region->init_req(_null_path, null_ctl); @@ -3355,10 +3698,12 @@ Node* GraphKit::gen_checkcast(Node *obj, Node* superklass, // Generate the subtype check Node* improved_superklass = superklass; if (improved_klass_ptr_type != klass_ptr_type && improved_klass_ptr_type->singleton()) { + // Only improve the super class for constants which allows subsequent sub type checks to possibly be commoned up. + // The other non-constant cases cannot be improved with a cast node here since they could be folded to top. + // Additionally, the benefit would only be minor in non-constant cases. improved_superklass = makecon(improved_klass_ptr_type); } Node* not_subtype_ctrl = gen_subtype_check(not_null_obj, improved_superklass); - // Plug in success path into the merge cast_obj = _gvn.transform(new CheckCastPPNode(control(), not_null_obj, toop)); // Failure path ends in uncommon trap (or may be dead - failure impossible) @@ -3396,7 +3741,171 @@ Node* GraphKit::gen_checkcast(Node *obj, Node* superklass, set_control( _gvn.transform(region) ); record_for_igvn(region); - return record_profiled_receiver_for_speculation(res); + bool not_inline = !toop->can_be_inline_type(); + bool not_flat_in_array = !UseArrayFlattening || not_inline || (toop->is_inlinetypeptr() && !toop->inline_klass()->maybe_flat_in_array()); + if (EnableValhalla && (not_inline || not_flat_in_array)) { + // Check if obj has been loaded from an array + obj = obj->isa_DecodeN() ? obj->in(1) : obj; + Node* array = nullptr; + if (obj->isa_Load()) { + Node* address = obj->in(MemNode::Address); + if (address->isa_AddP()) { + array = address->as_AddP()->in(AddPNode::Base); + } + } else if (obj->is_Phi()) { + Node* region = obj->in(0); + // TODO make this more robust (see JDK-8231346) + if (region->req() == 3 && region->in(2) != nullptr && region->in(2)->in(0) != nullptr) { + IfNode* iff = region->in(2)->in(0)->isa_If(); + if (iff != nullptr) { + iff->is_flat_array_check(&_gvn, &array); + } + } + } + if (array != nullptr) { + const TypeAryPtr* ary_t = _gvn.type(array)->isa_aryptr(); + if (ary_t != nullptr) { + if (!ary_t->is_not_null_free() && !ary_t->is_null_free() && not_inline) { + // Casting array element to a non-inline-type, mark array as not null-free. + Node* cast = _gvn.transform(new CheckCastPPNode(control(), array, ary_t->cast_to_not_null_free())); + replace_in_map(array, cast); + array = cast; + } + if (!ary_t->is_not_flat() && !ary_t->is_flat() && not_flat_in_array) { + // Casting array element to a non-flat-in-array type, mark array as not flat. + Node* cast = _gvn.transform(new CheckCastPPNode(control(), array, ary_t->cast_to_not_flat())); + replace_in_map(array, cast); + array = cast; + } + } + } + } + + if (!stopped() && !res->is_InlineType()) { + res = record_profiled_receiver_for_speculation(res); + if (toop->is_inlinetypeptr() && !maybe_larval) { + Node* vt = InlineTypeNode::make_from_oop(this, res, toop->inline_klass()); + res = vt; + if (safe_for_replace) { + replace_in_map(obj, vt); + replace_in_map(not_null_obj, vt); + replace_in_map(res, vt); + } + } + } + return res; +} + +Node* GraphKit::mark_word_test(Node* obj, uintptr_t mask_val, bool eq, bool check_lock) { + // Load markword + Node* mark_adr = basic_plus_adr(obj, oopDesc::mark_offset_in_bytes()); + Node* mark = make_load(nullptr, mark_adr, TypeX_X, TypeX_X->basic_type(), MemNode::unordered); + if (check_lock && !UseCompactObjectHeaders) { + // COH: Locking does not override the markword with a tagged pointer. We can directly read from the markword. + // Check if obj is locked + Node* locked_bit = MakeConX(markWord::unlocked_value); + locked_bit = _gvn.transform(new AndXNode(locked_bit, mark)); + Node* cmp = _gvn.transform(new CmpXNode(locked_bit, MakeConX(0))); + Node* is_unlocked = _gvn.transform(new BoolNode(cmp, BoolTest::ne)); + IfNode* iff = new IfNode(control(), is_unlocked, PROB_MAX, COUNT_UNKNOWN); + _gvn.transform(iff); + Node* locked_region = new RegionNode(3); + Node* mark_phi = new PhiNode(locked_region, TypeX_X); + + // Unlocked: Use bits from mark word + locked_region->init_req(1, _gvn.transform(new IfTrueNode(iff))); + mark_phi->init_req(1, mark); + + // Locked: Load prototype header from klass + set_control(_gvn.transform(new IfFalseNode(iff))); + // Make loads control dependent to make sure they are only executed if array is locked + Node* klass_adr = basic_plus_adr(obj, oopDesc::klass_offset_in_bytes()); + Node* klass = _gvn.transform(LoadKlassNode::make(_gvn, C->immutable_memory(), klass_adr, TypeInstPtr::KLASS, TypeInstKlassPtr::OBJECT)); + Node* proto_adr = basic_plus_adr(klass, in_bytes(Klass::prototype_header_offset())); + Node* proto = _gvn.transform(LoadNode::make(_gvn, control(), C->immutable_memory(), proto_adr, proto_adr->bottom_type()->is_ptr(), TypeX_X, TypeX_X->basic_type(), MemNode::unordered)); + + locked_region->init_req(2, control()); + mark_phi->init_req(2, proto); + set_control(_gvn.transform(locked_region)); + record_for_igvn(locked_region); + + mark = mark_phi; + } + + // Now check if mark word bits are set + Node* mask = MakeConX(mask_val); + Node* masked = _gvn.transform(new AndXNode(_gvn.transform(mark), mask)); + record_for_igvn(masked); // Give it a chance to be optimized out by IGVN + Node* cmp = _gvn.transform(new CmpXNode(masked, mask)); + return _gvn.transform(new BoolNode(cmp, eq ? BoolTest::eq : BoolTest::ne)); +} + +Node* GraphKit::inline_type_test(Node* obj, bool is_inline) { + return mark_word_test(obj, markWord::inline_type_pattern, is_inline, /* check_lock = */ false); +} + +Node* GraphKit::flat_array_test(Node* array_or_klass, bool flat) { + // We can't use immutable memory here because the mark word is mutable. + // PhaseIdealLoop::move_flat_array_check_out_of_loop will make sure the + // check is moved out of loops (mainly to enable loop unswitching). + Node* cmp = _gvn.transform(new FlatArrayCheckNode(C, memory(Compile::AliasIdxRaw), array_or_klass)); + record_for_igvn(cmp); // Give it a chance to be optimized out by IGVN + return _gvn.transform(new BoolNode(cmp, flat ? BoolTest::eq : BoolTest::ne)); +} + +Node* GraphKit::null_free_array_test(Node* array, bool null_free) { + return mark_word_test(array, markWord::null_free_array_bit_in_place, null_free); +} + +Node* GraphKit::null_free_atomic_array_test(Node* array, ciInlineKlass* vk) { + assert(vk->has_atomic_layout() || vk->has_non_atomic_layout(), "Can't be null-free and flat"); + + // TODO 8350865 Add a stress flag to always access atomic if layout exists? + if (!vk->has_non_atomic_layout()) { + return intcon(1); // Always atomic + } else if (!vk->has_atomic_layout()) { + return intcon(0); // Never atomic + } + + Node* array_klass = load_object_klass(array); + int layout_kind_offset = in_bytes(FlatArrayKlass::layout_kind_offset()); + Node* layout_kind_addr = basic_plus_adr(array_klass, array_klass, layout_kind_offset); + Node* layout_kind = make_load(nullptr, layout_kind_addr, TypeInt::INT, T_INT, MemNode::unordered); + Node* cmp = _gvn.transform(new CmpINode(layout_kind, intcon((int)LayoutKind::ATOMIC_FLAT))); + return _gvn.transform(new BoolNode(cmp, BoolTest::eq)); +} + +// Deoptimize if 'ary' is a null-free inline type array and 'val' is null +Node* GraphKit::inline_array_null_guard(Node* ary, Node* val, int nargs, bool safe_for_replace) { + RegionNode* region = new RegionNode(3); + Node* null_ctl = top(); + null_check_oop(val, &null_ctl); + if (null_ctl != top()) { + PreserveJVMState pjvms(this); + set_control(null_ctl); + { + // Deoptimize if null-free array + BuildCutout unless(this, null_free_array_test(ary, /* null_free = */ false), PROB_MAX); + inc_sp(nargs); + uncommon_trap(Deoptimization::Reason_null_check, + Deoptimization::Action_none); + } + region->init_req(1, control()); + } + region->init_req(2, control()); + set_control(_gvn.transform(region)); + record_for_igvn(region); + if (_gvn.type(val) == TypePtr::NULL_PTR) { + // Since we were just successfully storing null, the array can't be null free. + const TypeAryPtr* ary_t = _gvn.type(ary)->is_aryptr(); + ary_t = ary_t->cast_to_not_null_free(); + Node* cast = _gvn.transform(new CheckCastPPNode(control(), ary, ary_t)); + if (safe_for_replace) { + replace_in_map(ary, cast); + } + ary = cast; + } + return ary; } //------------------------------next_monitor----------------------------------- @@ -3528,6 +4037,7 @@ void GraphKit::shared_unlock(Node* box, Node* obj) { map()->pop_monitor(); // Kill monitor from debug info return; } + assert(!obj->is_InlineType(), "should not unlock on inline type"); // Memory barrier to avoid floating things down past the locked region insert_mem_bar(Op_MemBarReleaseLock); @@ -3568,10 +4078,19 @@ Node* GraphKit::get_layout_helper(Node* klass_node, jint& constant_value) { const TypeKlassPtr* klass_t = _gvn.type(klass_node)->isa_klassptr(); if (!StressReflectiveCode && klass_t != nullptr) { bool xklass = klass_t->klass_is_exact(); - if (xklass || (klass_t->isa_aryklassptr() && klass_t->is_aryklassptr()->elem() != Type::BOTTOM)) { + bool can_be_flat = false; + const TypeAryPtr* ary_type = klass_t->as_instance_type()->isa_aryptr(); + if (UseArrayFlattening && !xklass && ary_type != nullptr && !ary_type->is_null_free()) { + // Don't constant fold if the runtime type might be a flat array but the static type is not. + const TypeOopPtr* elem = ary_type->elem()->make_oopptr(); + can_be_flat = ary_type->can_be_inline_array() && (!elem->is_inlinetypeptr() || elem->inline_klass()->maybe_flat_in_array()); + } + if (!can_be_flat && (xklass || (klass_t->isa_aryklassptr() && klass_t->is_aryklassptr()->elem() != Type::BOTTOM))) { jint lhelper; - if (klass_t->isa_aryklassptr()) { - BasicType elem = klass_t->as_instance_type()->isa_aryptr()->elem()->array_element_basic_type(); + if (klass_t->is_flat()) { + lhelper = ary_type->flat_layout_helper(); + } else if (klass_t->isa_aryklassptr()) { + BasicType elem = ary_type->elem()->array_element_basic_type(); if (is_reference_type(elem, true)) { elem = T_OBJECT; } @@ -3600,7 +4119,9 @@ static void hook_memory_on_init(GraphKit& kit, int alias_idx, Node* prevmem = kit.memory(alias_idx); init_in_merge->set_memory_at(alias_idx, prevmem); - kit.set_memory(init_out_raw, alias_idx); + if (init_out_raw != nullptr) { + kit.set_memory(init_out_raw, alias_idx); + } } //---------------------------set_output_for_allocation------------------------- @@ -3639,6 +4160,7 @@ Node* GraphKit::set_output_for_allocation(AllocateNode* alloc, MergeMemNode* minit_in = MergeMemNode::make(malloc); init->set_req(InitializeNode::Memory, minit_in); record_for_igvn(minit_in); // fold it up later, if possible + _gvn.set_type(minit_in, Type::MEMORY); Node* minit_out = memory(rawidx); assert(minit_out->is_Proj() && minit_out->in(0) == init, ""); // Add an edge in the MergeMem for the header fields so an access @@ -3646,10 +4168,36 @@ Node* GraphKit::set_output_for_allocation(AllocateNode* alloc, set_memory(minit_out, C->get_alias_index(oop_type->add_offset(oopDesc::mark_offset_in_bytes()))); set_memory(minit_out, C->get_alias_index(oop_type->add_offset(oopDesc::klass_offset_in_bytes()))); if (oop_type->isa_aryptr()) { - const TypePtr* telemref = oop_type->add_offset(Type::OffsetBot); - int elemidx = C->get_alias_index(telemref); - hook_memory_on_init(*this, elemidx, minit_in, minit_out); + const TypeAryPtr* arytype = oop_type->is_aryptr(); + if (arytype->is_flat()) { + // Initially all flat array accesses share a single slice + // but that changes after parsing. Prepare the memory graph so + // it can optimize flat array accesses properly once they + // don't share a single slice. + assert(C->flat_accesses_share_alias(), "should be set at parse time"); + C->set_flat_accesses_share_alias(false); + ciInlineKlass* vk = arytype->elem()->inline_klass(); + for (int i = 0, len = vk->nof_nonstatic_fields(); i < len; i++) { + ciField* field = vk->nonstatic_field_at(i); + if (field->offset_in_bytes() >= TrackedInitializationLimit * HeapWordSize) + continue; // do not bother to track really large numbers of fields + int off_in_vt = field->offset_in_bytes() - vk->payload_offset(); + const TypePtr* adr_type = arytype->with_field_offset(off_in_vt)->add_offset(Type::OffsetBot); + int fieldidx = C->get_alias_index(adr_type, true); + // Pass nullptr for init_out. Having per flat array element field memory edges as uses of the Initialize node + // can result in per flat array field Phis to be created which confuses the logic of + // Compile::adjust_flat_array_access_aliases(). + hook_memory_on_init(*this, fieldidx, minit_in, nullptr); + } + C->set_flat_accesses_share_alias(true); + hook_memory_on_init(*this, C->get_alias_index(TypeAryPtr::INLINES), minit_in, minit_out); + } else { + const TypePtr* telemref = oop_type->add_offset(Type::OffsetBot); + int elemidx = C->get_alias_index(telemref); + hook_memory_on_init(*this, elemidx, minit_in, minit_out); + } } else if (oop_type->isa_instptr()) { + set_memory(minit_out, C->get_alias_index(oop_type)); // mark word ciInstanceKlass* ik = oop_type->is_instptr()->instance_klass(); for (int i = 0, len = ik->nof_nonstatic_fields(); i < len; i++) { ciField* field = ik->nonstatic_field_at(i); @@ -3700,14 +4248,15 @@ Node* GraphKit::set_output_for_allocation(AllocateNode* alloc, Node* GraphKit::new_instance(Node* klass_node, Node* extra_slow_test, Node* *return_size_val, - bool deoptimize_on_exception) { + bool deoptimize_on_exception, + InlineTypeNode* inline_type_node) { // Compute size in doublewords // The size is always an integral number of doublewords, represented // as a positive bytewise size stored in the klass's layout_helper. // The layout_helper also encodes (in a low bit) the need for a slow path. jint layout_con = Klass::_lh_neutral_value; Node* layout_val = get_layout_helper(klass_node, layout_con); - int layout_is_con = (layout_val == nullptr); + bool layout_is_con = (layout_val == nullptr); if (extra_slow_test == nullptr) extra_slow_test = intcon(0); // Generate the initial go-slow test. It's either ALWAYS (return a @@ -3758,20 +4307,20 @@ Node* GraphKit::new_instance(Node* klass_node, // Now generate allocation code // The entire memory state is needed for slow path of the allocation - // since GC and deoptimization can happened. + // since GC and deoptimization can happen. Node *mem = reset_memory(); set_all_memory(mem); // Create new memory state AllocateNode* alloc = new AllocateNode(C, AllocateNode::alloc_type(Type::TOP), control(), mem, i_o(), size, klass_node, - initial_slow_test); + initial_slow_test, inline_type_node); return set_output_for_allocation(alloc, oop_type, deoptimize_on_exception); } //-------------------------------new_array------------------------------------- -// helper for both newarray and anewarray +// helper for newarray and anewarray // The 'length' parameter is (obviously) the length of the array. // The optional arguments are for specialized use by intrinsics: // - If 'return_size_val', report the non-padded array size (sum of header size @@ -3781,10 +4330,11 @@ Node* GraphKit::new_array(Node* klass_node, // array klass (maybe variable) Node* length, // number of array elements int nargs, // number of arguments to push back for uncommon trap Node* *return_size_val, - bool deoptimize_on_exception) { + bool deoptimize_on_exception, + Node* init_val) { jint layout_con = Klass::_lh_neutral_value; Node* layout_val = get_layout_helper(klass_node, layout_con); - int layout_is_con = (layout_val == nullptr); + bool layout_is_con = (layout_val == nullptr); if (!layout_is_con && !StressReflectiveCode && !too_many_traps(Deoptimization::Reason_class_check)) { @@ -3814,9 +4364,7 @@ Node* GraphKit::new_array(Node* klass_node, // array klass (maybe variable) assert(!StressReflectiveCode, "stress mode does not use these paths"); // Increase the size limit if we have exact knowledge of array type. int log2_esize = Klass::layout_helper_log2_element_size(layout_con); - assert(fast_size_limit == 0 || count_leading_zeros(fast_size_limit) > static_cast(LogBytesPerLong - log2_esize), - "fast_size_limit (%d) overflow when shifted left by %d", fast_size_limit, LogBytesPerLong - log2_esize); - fast_size_limit <<= (LogBytesPerLong - log2_esize); + fast_size_limit <<= MAX2(LogBytesPerLong - log2_esize, 0); } Node* initial_slow_cmp = _gvn.transform( new CmpUNode( length, intcon( fast_size_limit ) ) ); @@ -3833,9 +4381,10 @@ Node* GraphKit::new_array(Node* klass_node, // array klass (maybe variable) if (layout_is_con) { int hsize = Klass::layout_helper_header_size(layout_con); int eshift = Klass::layout_helper_log2_element_size(layout_con); + bool is_flat_array = Klass::layout_helper_is_flatArray(layout_con); if ((round_mask & ~right_n_bits(eshift)) == 0) round_mask = 0; // strength-reduce it if it goes away completely - assert((hsize & right_n_bits(eshift)) == 0, "hsize is pre-rounded"); + assert(is_flat_array || (hsize & right_n_bits(eshift)) == 0, "hsize is pre-rounded"); int header_size_min = arrayOopDesc::base_offset_in_bytes(T_BYTE); assert(header_size_min <= hsize, "generic minimum is smallest"); header_size = intcon(hsize); @@ -3924,7 +4473,7 @@ Node* GraphKit::new_array(Node* klass_node, // array klass (maybe variable) // Now generate allocation code // The entire memory state is needed for slow path of the allocation - // since GC and deoptimization can happened. + // since GC and deoptimization can happen. Node *mem = reset_memory(); set_all_memory(mem); // Create new memory state @@ -3933,7 +4482,27 @@ Node* GraphKit::new_array(Node* klass_node, // array klass (maybe variable) initial_slow_test = initial_slow_test->as_Bool()->as_int_value(&_gvn); } - const TypeOopPtr* ary_type = _gvn.type(klass_node)->is_klassptr()->as_instance_type(); + const TypeKlassPtr* ary_klass = _gvn.type(klass_node)->isa_klassptr(); + const TypeOopPtr* ary_type = ary_klass->as_instance_type(); + + Node* raw_init_value = nullptr; + if (init_val != nullptr) { + // TODO 8350865 Fast non-zero init not implemented yet for flat, null-free arrays + if (ary_type->is_flat()) { + initial_slow_test = intcon(1); + } + + if (UseCompressedOops) { + // With compressed oops, the 64-bit init value is built from two 32-bit compressed oops + init_val = _gvn.transform(new EncodePNode(init_val, init_val->bottom_type()->make_narrowoop())); + Node* lower = _gvn.transform(new CastP2XNode(control(), init_val)); + Node* upper = _gvn.transform(new LShiftLNode(lower, intcon(32))); + raw_init_value = _gvn.transform(new OrLNode(lower, upper)); + } else { + raw_init_value = _gvn.transform(new CastP2XNode(control(), init_val)); + } + } + Node* valid_length_test = _gvn.intcon(1); if (ary_type->isa_aryptr()) { BasicType bt = ary_type->isa_aryptr()->elem()->array_element_basic_type(); @@ -3948,8 +4517,8 @@ Node* GraphKit::new_array(Node* klass_node, // array klass (maybe variable) control(), mem, i_o(), size, klass_node, initial_slow_test, - length, valid_length_test); - + length, valid_length_test, + init_val, raw_init_value); // Cast to correct type. Note that the klass_node may be constant or not, // and in the latter case the actual array type will be inexact also. // (This happens via a non-constant argument to inline_native_newArray.) @@ -4104,11 +4673,11 @@ Node* GraphKit::load_String_length(Node* str, bool set_ctrl) { Node* GraphKit::load_String_value(Node* str, bool set_ctrl) { int value_offset = java_lang_String::value_offset(); const TypeInstPtr* string_type = TypeInstPtr::make(TypePtr::NotNull, C->env()->String_klass(), - false, nullptr, 0); + false, nullptr, Type::Offset(0)); const TypePtr* value_field_type = string_type->add_offset(value_offset); const TypeAryPtr* value_type = TypeAryPtr::make(TypePtr::NotNull, - TypeAry::make(TypeInt::BYTE, TypeInt::POS), - ciTypeArrayKlass::make(T_BYTE), true, 0); + TypeAry::make(TypeInt::BYTE, TypeInt::POS, false, false, true, true), + ciTypeArrayKlass::make(T_BYTE), true, Type::Offset(0)); Node* p = basic_plus_adr(str, str, value_offset); Node* load = access_load_at(str, p, value_field_type, value_type, T_OBJECT, IN_HEAP | (set_ctrl ? C2_CONTROL_DEPENDENT_LOAD : 0) | MO_UNORDERED); @@ -4121,7 +4690,7 @@ Node* GraphKit::load_String_coder(Node* str, bool set_ctrl) { } int coder_offset = java_lang_String::coder_offset(); const TypeInstPtr* string_type = TypeInstPtr::make(TypePtr::NotNull, C->env()->String_klass(), - false, nullptr, 0); + false, nullptr, Type::Offset(0)); const TypePtr* coder_field_type = string_type->add_offset(coder_offset); Node* p = basic_plus_adr(str, str, coder_offset); @@ -4133,7 +4702,7 @@ Node* GraphKit::load_String_coder(Node* str, bool set_ctrl) { void GraphKit::store_String_value(Node* str, Node* value) { int value_offset = java_lang_String::value_offset(); const TypeInstPtr* string_type = TypeInstPtr::make(TypePtr::NotNull, C->env()->String_klass(), - false, nullptr, 0); + false, nullptr, Type::Offset(0)); const TypePtr* value_field_type = string_type->add_offset(value_offset); access_store_at(str, basic_plus_adr(str, value_offset), value_field_type, @@ -4143,7 +4712,7 @@ void GraphKit::store_String_value(Node* str, Node* value) { void GraphKit::store_String_coder(Node* str, Node* value) { int coder_offset = java_lang_String::coder_offset(); const TypeInstPtr* string_type = TypeInstPtr::make(TypePtr::NotNull, C->env()->String_klass(), - false, nullptr, 0); + false, nullptr, Type::Offset(0)); const TypePtr* coder_field_type = string_type->add_offset(coder_offset); access_store_at(str, basic_plus_adr(str, coder_offset), coder_field_type, @@ -4256,18 +4825,36 @@ Node* GraphKit::make_constant_from_field(ciField* field, Node* obj) { const Type* con_type = Type::make_constant_from_field(field, holder, field->layout_type(), /*is_unsigned_load=*/false); if (con_type != nullptr) { - return makecon(con_type); + Node* con = makecon(con_type); + if (field->type()->is_inlinetype()) { + con = InlineTypeNode::make_from_oop(this, con, field->type()->as_inline_klass()); + } else if (con_type->is_inlinetypeptr()) { + con = InlineTypeNode::make_from_oop(this, con, con_type->inline_klass()); + } + return con; } return nullptr; } +//---------------------------load_mirror_from_klass---------------------------- +// Given a klass oop, load its java mirror (a java.lang.Class oop). +Node* GraphKit::load_mirror_from_klass(Node* klass) { + Node* p = basic_plus_adr(klass, in_bytes(Klass::java_mirror_offset())); + Node* load = make_load(nullptr, p, TypeRawPtr::NOTNULL, T_ADDRESS, MemNode::unordered); + // mirror = ((OopHandle)mirror)->resolve(); + return access_load(load, TypeInstPtr::MIRROR, T_OBJECT, IN_NATIVE); +} + Node* GraphKit::maybe_narrow_object_type(Node* obj, ciKlass* type) { - const TypeOopPtr* obj_type = obj->bottom_type()->isa_oopptr(); + const Type* obj_type = obj->bottom_type(); const TypeOopPtr* sig_type = TypeOopPtr::make_from_klass(type); - if (obj_type != nullptr && sig_type->is_loaded() && !obj_type->higher_equal(sig_type)) { + if (obj_type->isa_oopptr() && sig_type->is_loaded() && !obj_type->higher_equal(sig_type)) { const Type* narrow_obj_type = obj_type->filter_speculative(sig_type); // keep speculative part Node* casted_obj = gvn().transform(new CheckCastPPNode(control(), obj, narrow_obj_type)); - return casted_obj; + obj = casted_obj; + } + if (sig_type->is_inlinetypeptr()) { + obj = InlineTypeNode::make_from_oop(this, obj, sig_type->inline_klass()); } return obj; } diff --git a/src/hotspot/share/opto/graphKit.hpp b/src/hotspot/share/opto/graphKit.hpp index 806a211d7e2..9d69401c8be 100644 --- a/src/hotspot/share/opto/graphKit.hpp +++ b/src/hotspot/share/opto/graphKit.hpp @@ -33,6 +33,7 @@ #include "opto/cfgnode.hpp" #include "opto/compile.hpp" #include "opto/divnode.hpp" +#include "opto/inlinetypenode.hpp" #include "opto/mulnode.hpp" #include "opto/phaseX.hpp" #include "opto/subnode.hpp" @@ -66,6 +67,9 @@ class GraphKit : public Phase { int _bci; // JVM Bytecode Pointer ciMethod* _method; // JVM Current Method BarrierSetC2* _barrier_set; +#ifdef ASSERT + uint _worklist_size; +#endif private: int _sp; // JVM Expression Stack Pointer; don't modify directly! @@ -78,12 +82,19 @@ class GraphKit : public Phase { public: GraphKit(); // empty constructor - GraphKit(JVMState* jvms); // the JVM state on which to operate + GraphKit(JVMState* jvms, PhaseGVN* gvn = nullptr); // the JVM state on which to operate #ifdef ASSERT ~GraphKit() { assert(failing_internal() || !has_exceptions(), "unless compilation failed, user must call transfer_exceptions_into_jvms"); +#if 0 + // During incremental inlining, the Node_Array of the C->for_igvn() worklist and the IGVN + // worklist are shared but the _in_worklist VectorSet is not. To avoid inconsistencies, + // we should not add nodes to the _for_igvn worklist when using IGVN for the GraphKit. + assert((_gvn.is_IterGVN() == nullptr) || (_gvn.C->for_igvn()->size() == _worklist_size), + "GraphKit should not modify _for_igvn worklist after parsing"); +#endif } #endif @@ -94,7 +105,7 @@ class GraphKit : public Phase { PhaseGVN& gvn() const { return _gvn; } void* barrier_set_state() const { return C->barrier_set_state(); } - void record_for_igvn(Node* n) const { C->record_for_igvn(n); } // delegate to Compile + void record_for_igvn(Node* n) const { _gvn.record_for_igvn(n); } void remove_for_igvn(Node* n) const { C->remove_for_igvn(n); } // Handy well-known nodes: @@ -372,12 +383,12 @@ class GraphKit : public Phase { Node* null_check_common(Node* value, BasicType type, bool assert_null = false, Node* *null_control = nullptr, - bool speculative = false); + bool speculative = false, + bool null_marker_check = false); Node* null_check(Node* value, BasicType type = T_OBJECT) { return null_check_common(value, type, false, nullptr, !_gvn.type(value)->speculative_maybe_null()); } Node* null_check_receiver() { - assert(argument(0)->bottom_type()->isa_ptr(), "must be"); return null_check(argument(0)); } Node* zero_check_int(Node* value) { @@ -449,6 +460,13 @@ class GraphKit : public Phase { // Cast obj to not-null on this path Node* cast_not_null(Node* obj, bool do_replace_in_map = true); + // If a larval object appears multiple times in the JVMS and we encounter a loop, they will + // become multiple Phis and we cannot change all of them to non-larval when we invoke the + // constructor on one. The other case is that we don't know whether a parameter of an OSR + // compilation is larval or not. If such a maybe-larval object is passed into an operation that + // does not permit larval objects, we can be sure that it is not larval and scalarize it if it + // is a value object. + Node* cast_to_non_larval(Node* obj); // Replace all occurrences of one node by another. void replace_in_map(Node* old, Node* neww); @@ -579,14 +597,17 @@ class GraphKit : public Phase { Node* val, const Type* val_type, BasicType bt, - DecoratorSet decorators); + DecoratorSet decorators, + bool safe_for_replace = true, + const InlineTypeNode* vt = nullptr); Node* access_load_at(Node* obj, // containing obj Node* adr, // actual address to load val at const TypePtr* adr_type, const Type* val_type, BasicType bt, - DecoratorSet decorators); + DecoratorSet decorators, + Node* ctl = nullptr); Node* access_load(Node* adr, // actual address to load val at const Type* val_type, @@ -639,6 +660,7 @@ class GraphKit : public Phase { const TypeInt* sizetype = nullptr, // Optional control dependency (for example, on range check) Node* ctrl = nullptr); + Node* cast_to_flat_array(Node* array, ciInlineKlass* elem_vk, bool is_null_free, bool is_not_null_free, bool is_atomic); // Return a load of array element at idx. Node* load_array_element(Node* ary, Node* idx, const TypeAryPtr* arytype, bool set_ctrl); @@ -678,7 +700,7 @@ class GraphKit : public Phase { // Fill in argument edges for the call from argument(0), argument(1), ... // (The next step is to call set_edges_for_java_call.) - void set_arguments_for_java_call(CallJavaNode* call); + void set_arguments_for_java_call(CallJavaNode* call, bool is_late_inline = false); // Fill in non-argument edges for the call. // Transform the call, and update the basics: control, i_o, memory. @@ -809,8 +831,15 @@ class GraphKit : public Phase { // Generate a check-cast idiom. Used by both the check-cast bytecode // and the array-store bytecode - Node* gen_checkcast( Node *subobj, Node* superkls, - Node* *failure_control = nullptr ); + Node* gen_checkcast(Node *subobj, Node* superkls, Node* *failure_control = nullptr, bool null_free = false, bool maybe_larval = false); + + // Inline types + Node* mark_word_test(Node* obj, uintptr_t mask_val, bool eq, bool check_lock = true); + Node* inline_type_test(Node* obj, bool is_inline = true); + Node* flat_array_test(Node* array_or_klass, bool flat = true); + Node* null_free_array_test(Node* array, bool null_free = true); + Node* null_free_atomic_array_test(Node* array, ciInlineKlass* vk); + Node* inline_array_null_guard(Node* ary, Node* val, int nargs, bool safe_for_replace = false); Node* gen_subtype_check(Node* obj, Node* superklass); @@ -819,6 +848,7 @@ class GraphKit : public Phase { // (Caller is responsible for doing replace_in_map.) Node* type_check_receiver(Node* receiver, ciKlass* klass, float prob, Node* *casted_receiver); + Node* type_check(Node* recv_klass, const TypeKlassPtr* tklass, float prob); // Inexact type check used for predicted calls. Node* subtype_check_receiver(Node* receiver, ciKlass* klass, @@ -832,10 +862,12 @@ class GraphKit : public Phase { Node* new_instance(Node* klass_node, Node* slow_test = nullptr, Node* *return_size_val = nullptr, - bool deoptimize_on_exception = false); + bool deoptimize_on_exception = false, + InlineTypeNode* inline_type_node = nullptr); Node* new_array(Node* klass_node, Node* count_val, int nargs, Node* *return_size_val = nullptr, - bool deoptimize_on_exception = false); + bool deoptimize_on_exception = false, + Node* init_val = nullptr); // java.lang.String helpers Node* load_String_length(Node* str, bool set_ctrl); @@ -869,6 +901,7 @@ class GraphKit : public Phase { void add_parse_predicate(Deoptimization::DeoptReason reason, int nargs); Node* make_constant_from_field(ciField* field, Node* obj); + Node* load_mirror_from_klass(Node* klass); // Vector API support (implemented in vectorIntrinsics.cpp) Node* box_vector(Node* in, const TypeInstPtr* vbox_type, BasicType elem_bt, int num_elem, bool deoptimize_on_exception = false); diff --git a/src/hotspot/share/opto/idealKit.cpp b/src/hotspot/share/opto/idealKit.cpp index dd7e9ae52b7..0f38cb50899 100644 --- a/src/hotspot/share/opto/idealKit.cpp +++ b/src/hotspot/share/opto/idealKit.cpp @@ -47,7 +47,6 @@ IdealKit::IdealKit(GraphKit* gkit, bool delay_all_transforms, bool has_declarati _cvstate = nullptr; // We can go memory state free or else we need the entire memory state assert(_initial_memory == nullptr || _initial_memory->Opcode() == Op_MergeMem, "memory must be pre-split"); - assert(!_gvn.is_IterGVN(), "IdealKit can't be used during Optimize phase"); int init_size = 5; _pending_cvstates = new (C->node_arena()) GrowableArray(C->node_arena(), init_size, 0, nullptr); DEBUG_ONLY(_state = new (C->node_arena()) GrowableArray(C->node_arena(), init_size, 0, 0)); @@ -79,10 +78,13 @@ void IdealKit::if_then(Node* left, BoolTest::mask relop, assert(left->bottom_type()->isa_long() != nullptr, "what else?"); bol = Bool(CmpL(left, right), relop); } - } else { bol = Bool(CmpP(left, right), relop); } + if_then(bol, prob, cnt, push_new_state); +} + +void IdealKit::if_then(Node* bol, float prob, float cnt, bool push_new_state) { // Delay gvn.transform on if-nodes until construction is finished // to prevent a constant bool input from discarding a control output. IfNode* iff = delay_transform(new IfNode(ctrl(), bol, prob, cnt))->as_If(); @@ -293,7 +295,7 @@ Node* IdealKit::transform(Node* n) { return delay_transform(n); } else { n = gvn().transform(n); - C->record_for_igvn(n); + gvn().record_for_igvn(n); return n; } } @@ -302,7 +304,7 @@ Node* IdealKit::transform(Node* n) { Node* IdealKit::delay_transform(Node* n) { // Delay transform until IterativeGVN gvn().set_type(n, n->bottom_type()); - C->record_for_igvn(n); + gvn().record_for_igvn(n); return n; } @@ -502,8 +504,8 @@ Node* IdealKit::make_leaf_call(const TypeFunc *slow_call_type, assert(C->alias_type(call->adr_type()) == C->alias_type(adr_type), "call node must be constructed correctly"); Node* res = nullptr; - if (slow_call_type->range()->cnt() > TypeFunc::Parms) { - assert(slow_call_type->range()->cnt() == TypeFunc::Parms+1, "only one return value"); + if (slow_call_type->range_sig()->cnt() > TypeFunc::Parms) { + assert(slow_call_type->range_sig()->cnt() == TypeFunc::Parms+1, "only one return value"); res = transform(new ProjNode(call, TypeFunc::Parms)); } return res; diff --git a/src/hotspot/share/opto/idealKit.hpp b/src/hotspot/share/opto/idealKit.hpp index fe07716dcff..0586a505f82 100644 --- a/src/hotspot/share/opto/idealKit.hpp +++ b/src/hotspot/share/opto/idealKit.hpp @@ -163,6 +163,7 @@ class IdealKit: public StackObj { void if_then(Node* left, BoolTest::mask relop, Node* right, float prob = PROB_FAIR, float cnt = COUNT_UNKNOWN, bool push_new_state = true); + void if_then(Node* bol, float prob = PROB_FAIR, float cnt = COUNT_UNKNOWN, bool push_new_state = true); void else_(); void end_if(); void loop(GraphKit* gkit, int nargs, IdealVariable& iv, Node* init, BoolTest::mask cmp, Node* limit, diff --git a/src/hotspot/share/opto/ifnode.cpp b/src/hotspot/share/opto/ifnode.cpp index b397c2c5852..93611630df1 100644 --- a/src/hotspot/share/opto/ifnode.cpp +++ b/src/hotspot/share/opto/ifnode.cpp @@ -1271,6 +1271,23 @@ bool IfNode::is_null_check(ProjNode* proj, PhaseIterGVN* igvn) { return false; } +// Returns true if this IfNode belongs to a flat array check +// and returns the corresponding array in the 'array' parameter. +bool IfNode::is_flat_array_check(PhaseTransform* phase, Node** array) { + Node* bol = in(1); + if (!bol->is_Bool()) { + return false; + } + Node* cmp = bol->in(1); + if (cmp->isa_FlatArrayCheck()) { + if (array != nullptr) { + *array = cmp->in(FlatArrayCheckNode::ArrayOrKlass); + } + return true; + } + return false; +} + // Check that the If that is in between the 2 integer comparisons has // no side effect bool IfNode::is_side_effect_free_test(ProjNode* proj, PhaseIterGVN* igvn) { diff --git a/src/hotspot/share/opto/inlinetypenode.cpp b/src/hotspot/share/opto/inlinetypenode.cpp new file mode 100644 index 00000000000..e9733559652 --- /dev/null +++ b/src/hotspot/share/opto/inlinetypenode.cpp @@ -0,0 +1,1658 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "ci/ciInlineKlass.hpp" +#include "gc/shared/barrierSet.hpp" +#include "gc/shared/c2/barrierSetC2.hpp" +#include "gc/shared/gc_globals.hpp" +#include "oops/accessDecorators.hpp" +#include "opto/addnode.hpp" +#include "opto/castnode.hpp" +#include "opto/convertnode.hpp" +#include "opto/graphKit.hpp" +#include "opto/inlinetypenode.hpp" +#include "opto/movenode.hpp" +#include "opto/narrowptrnode.hpp" +#include "opto/opcodes.hpp" +#include "opto/phaseX.hpp" +#include "opto/rootnode.hpp" +#include "opto/type.hpp" +#include "utilities/globalDefinitions.hpp" + +// Clones the inline type to handle control flow merges involving multiple inline types. +// The inputs are replaced by PhiNodes to represent the merged values for the given region. +// init_with_top: input of phis above the returned InlineTypeNode are initialized to top. +InlineTypeNode* InlineTypeNode::clone_with_phis(PhaseGVN* gvn, Node* region, SafePointNode* map, bool is_non_null, bool init_with_top) { + InlineTypeNode* vt = clone_if_required(gvn, map); + const Type* t = Type::get_const_type(inline_klass()); + gvn->set_type(vt, t); + vt->as_InlineType()->set_type(t); + + Node* const top = gvn->C->top(); + + // Create a PhiNode for merging the oop values + PhiNode* oop = PhiNode::make(region, init_with_top ? top : vt->get_oop(), t); + gvn->set_type(oop, t); + gvn->record_for_igvn(oop); + vt->set_oop(*gvn, oop); + + // Create a PhiNode for merging the is_buffered values + t = Type::get_const_basic_type(T_BOOLEAN); + Node* is_buffered_node = PhiNode::make(region, init_with_top ? top : vt->get_is_buffered(), t);; + gvn->set_type(is_buffered_node, t); + gvn->record_for_igvn(is_buffered_node); + vt->set_req(IsBuffered, is_buffered_node); + + // Create a PhiNode for merging the null_marker values + Node* null_marker_node; + if (is_non_null) { + null_marker_node = gvn->intcon(1); + } else { + t = Type::get_const_basic_type(T_BOOLEAN); + null_marker_node = PhiNode::make(region, init_with_top ? top : vt->get_null_marker(), t); + gvn->set_type(null_marker_node, t); + gvn->record_for_igvn(null_marker_node); + } + vt->set_req(NullMarker, null_marker_node); + + // Create a PhiNode each for merging the field values + for (uint i = 0; i < vt->field_count(); ++i) { + ciType* type = vt->field_type(i); + Node* value = vt->field_value(i); + // We limit scalarization for inline types with circular fields and can therefore observe nodes + // of the same type but with different scalarization depth during GVN. To avoid inconsistencies + // during merging, make sure that we only create Phis for fields that are guaranteed to be scalarized. + bool no_circularity = !gvn->C->has_circular_inline_type() || field_is_flat(i); + if (type->is_inlinetype() && no_circularity) { + // Handle inline type fields recursively + value = value->as_InlineType()->clone_with_phis(gvn, region, map); + } else { + t = Type::get_const_type(type); + value = PhiNode::make(region, init_with_top ? top : value, t); + gvn->set_type(value, t); + gvn->record_for_igvn(value); + } + vt->set_field_value(i, value); + } + gvn->record_for_igvn(vt); + return vt; +} + +// Checks if the inputs of the InlineTypeNode were replaced by PhiNodes +// for the given region (see InlineTypeNode::clone_with_phis). +bool InlineTypeNode::has_phi_inputs(Node* region) { + // Check oop input + bool result = get_oop()->is_Phi() && get_oop()->as_Phi()->region() == region; +#ifdef ASSERT + if (result) { + // Check all field value inputs for consistency + for (uint i = Values; i < field_count(); ++i) { + Node* n = in(i); + if (n->is_InlineType()) { + assert(n->as_InlineType()->has_phi_inputs(region), "inconsistent phi inputs"); + } else { + assert(n->is_Phi() && n->as_Phi()->region() == region, "inconsistent phi inputs"); + } + } + } +#endif + return result; +} + +// Merges 'this' with 'other' by updating the input PhiNodes added by 'clone_with_phis' +InlineTypeNode* InlineTypeNode::merge_with(PhaseGVN* gvn, const InlineTypeNode* other, int pnum, bool transform) { + assert(inline_klass() == other->inline_klass(), "Merging incompatible types"); + + // Merge oop inputs + PhiNode* phi = get_oop()->as_Phi(); + phi->set_req(pnum, other->get_oop()); + if (transform) { + set_oop(*gvn, gvn->transform(phi)); + } + + // Merge is_buffered inputs + phi = get_is_buffered()->as_Phi(); + phi->set_req(pnum, other->get_is_buffered()); + if (transform) { + set_req(IsBuffered, gvn->transform(phi)); + } + + // Merge null_marker inputs + Node* null_marker = get_null_marker(); + if (null_marker->is_Phi()) { + phi = null_marker->as_Phi(); + phi->set_req(pnum, other->get_null_marker()); + if (transform) { + set_req(NullMarker, gvn->transform(phi)); + } + } else { + assert(null_marker->find_int_con(0) == 1, "only with a non null inline type"); + } + + // Merge field values + for (uint i = 0; i < field_count(); ++i) { + Node* val1 = field_value(i); + Node* val2 = other->field_value(i); + if (val1->is_InlineType()) { + if (val2->is_Phi()) { + val2 = gvn->transform(val2); + } + val1->as_InlineType()->merge_with(gvn, val2->as_InlineType(), pnum, transform); + } else { + assert(val1->is_Phi(), "must be a phi node"); + val1->set_req(pnum, val2); + } + if (transform) { + set_field_value(i, gvn->transform(val1)); + } + } + return this; +} + +// Adds a new merge path to an inline type node with phi inputs +void InlineTypeNode::add_new_path(Node* region) { + assert(has_phi_inputs(region), "must have phi inputs"); + + PhiNode* phi = get_oop()->as_Phi(); + phi->add_req(nullptr); + assert(phi->req() == region->req(), "must be same size as region"); + + phi = get_is_buffered()->as_Phi(); + phi->add_req(nullptr); + assert(phi->req() == region->req(), "must be same size as region"); + + phi = get_null_marker()->as_Phi(); + phi->add_req(nullptr); + assert(phi->req() == region->req(), "must be same size as region"); + + for (uint i = 0; i < field_count(); ++i) { + Node* val = field_value(i); + if (val->is_InlineType()) { + val->as_InlineType()->add_new_path(region); + } else { + val->as_Phi()->add_req(nullptr); + assert(val->req() == region->req(), "must be same size as region"); + } + } +} + +Node* InlineTypeNode::field_value(uint index) const { + assert(index < field_count(), "index out of bounds"); + return in(Values + index); +} + +// Get the value of the field at the given offset. +// If 'recursive' is true, flat inline type fields will be resolved recursively. +Node* InlineTypeNode::field_value_by_offset(int offset, bool recursive) const { + // Find the declared field which contains the field we are looking for + int index = inline_klass()->field_index_by_offset(offset); + Node* value = field_value(index); + assert(value != nullptr, "field value not found"); + + if (!recursive || !field_is_flat(index) || value->is_top()) { + assert(offset == field_offset(index), "offset mismatch"); + return value; + } + + // Flat inline type field + InlineTypeNode* vt = value->as_InlineType(); + if (offset == field_null_marker_offset(index)) { + return vt->get_null_marker(); + } else { + int sub_offset = offset - field_offset(index); // Offset of the flattened field inside the declared field + sub_offset += vt->inline_klass()->payload_offset(); // Add header size + return vt->field_value_by_offset(sub_offset, recursive); + } +} + +void InlineTypeNode::set_field_value(uint index, Node* value) { + assert(index < field_count(), "index out of bounds"); + set_req(Values + index, value); +} + +void InlineTypeNode::set_field_value_by_offset(int offset, Node* value) { + set_field_value(field_index(offset), value); +} + +int InlineTypeNode::field_offset(uint index) const { + assert(index < field_count(), "index out of bounds"); + return inline_klass()->declared_nonstatic_field_at(index)->offset_in_bytes(); +} + +uint InlineTypeNode::field_index(int offset) const { + uint i = 0; + for (; i < field_count() && field_offset(i) != offset; i++) { } + assert(i < field_count(), "field not found"); + return i; +} + +ciType* InlineTypeNode::field_type(uint index) const { + assert(index < field_count(), "index out of bounds"); + return inline_klass()->declared_nonstatic_field_at(index)->type(); +} + +bool InlineTypeNode::field_is_flat(uint index) const { + assert(index < field_count(), "index out of bounds"); + ciField* field = inline_klass()->declared_nonstatic_field_at(index); + assert(!field->is_flat() || field->type()->is_inlinetype(), "must be an inline type"); + return field->is_flat(); +} + +bool InlineTypeNode::field_is_null_free(uint index) const { + assert(index < field_count(), "index out of bounds"); + ciField* field = inline_klass()->declared_nonstatic_field_at(index); + assert(!field->is_flat() || field->type()->is_inlinetype(), "must be an inline type"); + return field->is_null_free(); +} + +bool InlineTypeNode::field_is_volatile(uint index) const { + assert(index < field_count(), "index out of bounds"); + ciField* field = inline_klass()->declared_nonstatic_field_at(index); + assert(!field->is_flat() || field->type()->is_inlinetype(), "must be an inline type"); + return field->is_volatile(); +} + +int InlineTypeNode::field_null_marker_offset(uint index) const { + assert(index < field_count(), "index out of bounds"); + ciField* field = inline_klass()->declared_nonstatic_field_at(index); + assert(field->is_flat(), "must be an inline type"); + return field->null_marker_offset(); +} + +uint InlineTypeNode::add_fields_to_safepoint(Unique_Node_List& worklist, SafePointNode* sfpt) { + uint cnt = 0; + for (uint i = 0; i < field_count(); ++i) { + Node* value = field_value(i); + if (field_is_flat(i)) { + InlineTypeNode* vt = value->as_InlineType(); + cnt += vt->add_fields_to_safepoint(worklist, sfpt); + if (!field_is_null_free(i)) { + // The null marker of a flat field is added right after we scalarize that field + sfpt->add_req(vt->get_null_marker()); + cnt++; + } + continue; + } + if (value->is_InlineType()) { + // Add inline type to the worklist to process later + worklist.push(value); + } + sfpt->add_req(value); + cnt++; + } + return cnt; +} + +void InlineTypeNode::make_scalar_in_safepoint(PhaseIterGVN* igvn, Unique_Node_List& worklist, SafePointNode* sfpt) { + JVMState* jvms = sfpt->jvms(); + assert(jvms != nullptr, "missing JVMS"); + uint first_ind = (sfpt->req() - jvms->scloff()); + + // Iterate over the inline type fields in order of increasing offset and add the + // field values to the safepoint. Nullable inline types have an null marker field that + // needs to be checked before using the field values. + sfpt->add_req(get_null_marker()); + uint nfields = add_fields_to_safepoint(worklist, sfpt); + jvms->set_endoff(sfpt->req()); + // Replace safepoint edge by SafePointScalarObjectNode + SafePointScalarObjectNode* sobj = new SafePointScalarObjectNode(type()->isa_instptr(), + nullptr, + first_ind, + sfpt->jvms()->depth(), + nfields); + sobj->init_req(0, igvn->C->root()); + sobj = igvn->transform(sobj)->as_SafePointScalarObject(); + igvn->rehash_node_delayed(sfpt); + for (uint i = jvms->debug_start(); i < jvms->debug_end(); i++) { + Node* debug = sfpt->in(i); + if (debug != nullptr && debug->uncast() == this) { + sfpt->set_req(i, sobj); + } + } +} + +void InlineTypeNode::make_scalar_in_safepoints(PhaseIterGVN* igvn, bool allow_oop) { + // If the inline type has a constant or loaded oop, use the oop instead of scalarization + // in the safepoint to avoid keeping field loads live just for the debug info. + Node* oop = get_oop(); + bool use_oop = false; + if (allow_oop && is_allocated(igvn) && oop->is_Phi()) { + Unique_Node_List worklist; + VectorSet visited; + visited.set(oop->_idx); + worklist.push(oop); + use_oop = true; + while (worklist.size() > 0 && use_oop) { + Node* n = worklist.pop(); + for (uint i = 1; i < n->req(); i++) { + Node* in = n->in(i); + if (in->is_Phi() && !visited.test_set(in->_idx)) { + worklist.push(in); + } else if (!(in->is_Con() || in->is_Parm())) { + use_oop = false; + break; + } + } + } + } else { + use_oop = allow_oop && is_allocated(igvn) && + (oop->is_Con() || oop->is_Parm() || oop->is_Load() || (oop->isa_DecodeN() && oop->in(1)->is_Load())); + } + + ResourceMark rm; + Unique_Node_List safepoints; + Unique_Node_List vt_worklist; + Unique_Node_List worklist; + worklist.push(this); + while (worklist.size() > 0) { + Node* n = worklist.pop(); + for (DUIterator_Fast imax, i = n->fast_outs(imax); i < imax; i++) { + Node* use = n->fast_out(i); + if (use->is_SafePoint() && !use->is_CallLeaf() && (!use->is_Call() || use->as_Call()->has_debug_use(n))) { + safepoints.push(use); + } else if (use->is_ConstraintCast()) { + worklist.push(use); + } + } + } + + // Process all safepoint uses and scalarize inline type + while (safepoints.size() > 0) { + SafePointNode* sfpt = safepoints.pop()->as_SafePoint(); + if (use_oop) { + for (uint i = sfpt->jvms()->debug_start(); i < sfpt->jvms()->debug_end(); i++) { + Node* debug = sfpt->in(i); + if (debug != nullptr && debug->uncast() == this) { + sfpt->set_req(i, get_oop()); + } + } + igvn->rehash_node_delayed(sfpt); + } else { + make_scalar_in_safepoint(igvn, vt_worklist, sfpt); + } + } + // Now scalarize non-flat fields + for (uint i = 0; i < vt_worklist.size(); ++i) { + InlineTypeNode* vt = vt_worklist.at(i)->isa_InlineType(); + vt->make_scalar_in_safepoints(igvn); + } + if (outcnt() == 0) { + igvn->record_for_igvn(this); + } +} + +// We limit scalarization for inline types with circular fields and can therefore observe nodes +// of the same type but with different scalarization depth during GVN. This method adjusts the +// scalarization depth to avoid inconsistencies during merging. +InlineTypeNode* InlineTypeNode::adjust_scalarization_depth(GraphKit* kit) { + if (!kit->C->has_circular_inline_type()) { + return this; + } + GrowableArray visited; + visited.push(inline_klass()); + return adjust_scalarization_depth_impl(kit, visited); +} + +InlineTypeNode* InlineTypeNode::adjust_scalarization_depth_impl(GraphKit* kit, GrowableArray& visited) { + InlineTypeNode* val = this; + for (uint i = 0; i < field_count(); ++i) { + Node* value = field_value(i); + Node* new_value = value; + ciType* ft = field_type(i); + if (value->is_InlineType()) { + if (!field_is_flat(i) && visited.contains(ft)) { + new_value = value->as_InlineType()->buffer(kit)->get_oop(); + } else { + int old_len = visited.length(); + visited.push(ft); + new_value = value->as_InlineType()->adjust_scalarization_depth_impl(kit, visited); + visited.trunc_to(old_len); + } + } else if (ft->is_inlinetype() && !visited.contains(ft)) { + int old_len = visited.length(); + visited.push(ft); + new_value = make_from_oop_impl(kit, value, ft->as_inline_klass(), visited); + visited.trunc_to(old_len); + } + if (value != new_value) { + if (val == this) { + val = clone_if_required(&kit->gvn(), kit->map()); + } + val->set_field_value(i, new_value); + } + } + return (val == this) ? this : kit->gvn().transform(val)->as_InlineType(); +} + +void InlineTypeNode::load(GraphKit* kit, Node* base, Node* ptr, bool immutable_memory, bool trust_null_free_oop, DecoratorSet decorators, GrowableArray& visited) { + // Initialize the inline type by loading its field values from + // memory and adding the values as input edges to the node. + ciInlineKlass* vk = inline_klass(); + for (uint i = 0; i < field_count(); ++i) { + int field_off = field_offset(i) - vk->payload_offset(); + Node* field_ptr = kit->basic_plus_adr(base, ptr, field_off); + Node* value = nullptr; + ciType* ft = field_type(i); + bool field_null_free = field_is_null_free(i); + if (field_is_flat(i)) { + // Recursively load the flat inline type field + ciInlineKlass* fvk = ft->as_inline_klass(); + // Atomic if nullable or not LooselyConsistentValue + bool atomic = !field_null_free || fvk->must_be_atomic(); + + int old_len = visited.length(); + visited.push(ft); + value = make_from_flat_impl(kit, fvk, base, field_ptr, atomic, immutable_memory, + field_null_free, trust_null_free_oop && field_null_free, decorators, visited); + visited.trunc_to(old_len); + } else { + // Load field value from memory + BasicType bt = type2field[ft->basic_type()]; + assert(is_java_primitive(bt) || field_ptr->bottom_type()->is_ptr_to_narrowoop() == UseCompressedOops, "inconsistent"); + const Type* val_type = Type::get_const_type(ft); + if (trust_null_free_oop && field_null_free) { + val_type = val_type->join_speculative(TypePtr::NOTNULL); + } + const TypePtr* field_ptr_type = (decorators & C2_MISMATCHED) == 0 ? kit->gvn().type(field_ptr)->is_ptr() : TypeRawPtr::BOTTOM; + value = kit->access_load_at(base, field_ptr, field_ptr_type, val_type, bt, decorators); + // Loading a non-flattened inline type from memory + if (visited.contains(ft)) { + kit->C->set_has_circular_inline_type(true); + } else if (ft->is_inlinetype()) { + int old_len = visited.length(); + visited.push(ft); + value = make_from_oop_impl(kit, value, ft->as_inline_klass(), visited); + visited.trunc_to(old_len); + } + } + set_field_value(i, value); + } +} + +// Get a field value from the payload by shifting it according to the offset +static Node* get_payload_value(PhaseGVN* gvn, Node* payload, BasicType bt, BasicType val_bt, int offset) { + // Shift to the right position in the long value + assert((offset + type2aelembytes(val_bt)) <= type2aelembytes(bt), "Value does not fit into payload"); + Node* value = nullptr; + Node* shift_val = gvn->intcon(offset << LogBitsPerByte); + if (bt == T_LONG) { + value = gvn->transform(new URShiftLNode(payload, shift_val)); + value = gvn->transform(new ConvL2INode(value)); + } else { + value = gvn->transform(new URShiftINode(payload, shift_val)); + } + + if (val_bt == T_INT || val_bt == T_OBJECT || val_bt == T_ARRAY) { + return value; + } else { + // Make sure to zero unused bits in the 32-bit value + return Compile::narrow_value(val_bt, value, nullptr, gvn, true); + } +} + +// Convert a payload value to field values +void InlineTypeNode::convert_from_payload(GraphKit* kit, BasicType bt, Node* payload, int holder_offset, bool null_free, bool trust_null_free_oop) { + PhaseGVN* gvn = &kit->gvn(); + ciInlineKlass* vk = inline_klass(); + Node* value = nullptr; + if (!null_free) { + // Get the null marker + value = get_payload_value(gvn, payload, bt, T_BOOLEAN, holder_offset + vk->null_marker_offset_in_payload()); + set_req(NullMarker, value); + } + // Iterate over the fields and get their values from the payload + for (uint i = 0; i < field_count(); ++i) { + ciType* ft = field_type(i); + bool field_null_free = field_is_null_free(i); + int offset = holder_offset + field_offset(i) - vk->payload_offset(); + if (field_is_flat(i)) { + InlineTypeNode* vt = make_uninitialized(*gvn, ft->as_inline_klass(), field_null_free); + vt->convert_from_payload(kit, bt, payload, offset, field_null_free, trust_null_free_oop && field_null_free); + value = gvn->transform(vt); + } else { + value = get_payload_value(gvn, payload, bt, ft->basic_type(), offset); + if (!ft->is_primitive_type()) { + // Narrow oop field + assert(UseCompressedOops && bt == T_LONG, "Naturally atomic"); + const Type* val_type = Type::get_const_type(ft); + if (trust_null_free_oop && field_null_free) { + val_type = val_type->join_speculative(TypePtr::NOTNULL); + } + value = gvn->transform(new CastI2NNode(kit->control(), value, val_type->make_narrowoop())); + value = gvn->transform(new DecodeNNode(value, val_type->make_narrowoop())); + + // Similar to CheckCastPP nodes with raw input, CastI2N nodes require special handling in 'PhaseCFG::schedule_late' to ensure the + // register allocator does not move the CastI2N below a safepoint. This is necessary to avoid having the raw pointer span a safepoint, + // making it opaque to the GC. Unlike CheckCastPPs, which need extra handling in 'Scheduling::ComputeRegisterAntidependencies' due to + // scalarization, CastI2N nodes are always used by a load if scalarization happens which inherently keeps them pinned above the safepoint. + + if (ft->is_inlinetype()) { + GrowableArray visited; + value = make_from_oop_impl(kit, value, ft->as_inline_klass(), visited); + } + } + } + set_field_value(i, value); + } +} + +// Set a field value in the payload by shifting it according to the offset +static Node* set_payload_value(PhaseGVN* gvn, Node* payload, BasicType bt, Node* value, BasicType val_bt, int offset) { + assert((offset + type2aelembytes(val_bt)) <= type2aelembytes(bt), "Value does not fit into payload"); + + // Make sure to zero unused bits in the 32-bit value + if (val_bt == T_BYTE || val_bt == T_BOOLEAN) { + value = gvn->transform(new AndINode(value, gvn->intcon(0xFF))); + } else if (val_bt == T_CHAR || val_bt == T_SHORT) { + value = gvn->transform(new AndINode(value, gvn->intcon(0xFFFF))); + } else if (val_bt == T_FLOAT) { + value = gvn->transform(new MoveF2INode(value)); + } else { + assert(val_bt == T_INT, "Unsupported type: %s", type2name(val_bt)); + } + + Node* shift_val = gvn->intcon(offset << LogBitsPerByte); + if (bt == T_LONG) { + // Convert to long and remove the sign bit (the backend will fold this and emit a zero extend i2l) + value = gvn->transform(new ConvI2LNode(value)); + value = gvn->transform(new AndLNode(value, gvn->longcon(0xFFFFFFFF))); + + Node* shift_value = gvn->transform(new LShiftLNode(value, shift_val)); + payload = new OrLNode(shift_value, payload); + } else { + Node* shift_value = gvn->transform(new LShiftINode(value, shift_val)); + payload = new OrINode(shift_value, payload); + } + return gvn->transform(payload); +} + +// Convert the field values to a payload value of type 'bt' +Node* InlineTypeNode::convert_to_payload(GraphKit* kit, BasicType bt, Node* payload, int holder_offset, bool null_free, int null_marker_offset, int& oop_off_1, int& oop_off_2) const { + PhaseGVN* gvn = &kit->gvn(); + Node* value = nullptr; + if (!null_free) { + // Set the null marker + value = get_null_marker(); + payload = set_payload_value(gvn, payload, bt, value, T_BOOLEAN, null_marker_offset); + } + // Iterate over the fields and add their values to the payload + for (uint i = 0; i < field_count(); ++i) { + value = field_value(i); + int inner_offset = field_offset(i) - inline_klass()->payload_offset(); + int offset = holder_offset + inner_offset; + if (field_is_flat(i)) { + null_marker_offset = holder_offset + field_null_marker_offset(i) - inline_klass()->payload_offset(); + payload = value->as_InlineType()->convert_to_payload(kit, bt, payload, offset, field_is_null_free(i), null_marker_offset, oop_off_1, oop_off_2); + } else { + ciType* ft = field_type(i); + BasicType field_bt = ft->basic_type(); + if (!ft->is_primitive_type()) { + // Narrow oop field + assert(UseCompressedOops && bt == T_LONG, "Naturally atomic"); + assert(inner_offset != -1, "sanity"); + if (oop_off_1 == -1) { + oop_off_1 = inner_offset; + } else { + assert(oop_off_2 == -1, "already set"); + oop_off_2 = inner_offset; + } + const Type* val_type = Type::get_const_type(ft)->make_narrowoop(); + if (value->is_InlineType()) { + PreserveReexecuteState preexecs(kit); + kit->jvms()->set_should_reexecute(true); + value = value->as_InlineType()->buffer(kit, false); + } + value = gvn->transform(new EncodePNode(value, val_type)); + value = gvn->transform(new CastP2XNode(kit->control(), value)); + value = gvn->transform(new ConvL2INode(value)); + field_bt = T_INT; + } + payload = set_payload_value(gvn, payload, bt, value, field_bt, offset); + } + } + return payload; +} + +void InlineTypeNode::store_flat(GraphKit* kit, Node* base, Node* ptr, bool atomic, bool immutable_memory, bool null_free, DecoratorSet decorators) const { + ciInlineKlass* vk = inline_klass(); + bool do_atomic = atomic; + // With immutable memory, a non-atomic load and an atomic load are the same + if (immutable_memory) { + do_atomic = false; + } + // If there is only one flattened field, a non-atomic load and an atomic load are the same + if (vk->is_naturally_atomic(null_free)) { + do_atomic = false; + } + + if (!do_atomic) { + if (!null_free) { + int nm_offset = vk->null_marker_offset_in_payload(); + Node* nm_ptr = kit->basic_plus_adr(base, ptr, nm_offset); + const TypePtr* nm_ptr_type = (decorators & C2_MISMATCHED) == 0 ? kit->gvn().type(nm_ptr)->is_ptr() : TypeRawPtr::BOTTOM; + kit->access_store_at(base, nm_ptr, nm_ptr_type, get_null_marker(), TypeInt::BOOL, T_BOOLEAN, decorators); + } + store(kit, base, ptr, immutable_memory, decorators); + return; + } + + // Convert to a payload value <= 64-bit and write atomically. + // The payload might contain at most two oop fields that must be narrow because otherwise they would be 64-bit + // in size and would then be written by a "normal" oop store. If the payload contains oops, its size is always + // 64-bit because the next smaller (power-of-two) size would be 32-bit which could only hold one narrow oop that + // would then be written by a normal narrow oop store. These properties are asserted in 'convert_to_payload'. + assert(!immutable_memory, "immutable memory does not need explicit atomic access"); + BasicType store_bt = vk->atomic_size_to_basic_type(null_free); + Node* payload = (store_bt == T_LONG) ? kit->longcon(0) : kit->intcon(0); + int oop_off_1 = -1; + int oop_off_2 = -1; + payload = convert_to_payload(kit, store_bt, payload, 0, null_free, vk->null_marker_offset_in_payload(), oop_off_1, oop_off_2); + if (!UseG1GC || oop_off_1 == -1) { + // No oop fields or no late barrier expansion. Emit an atomic store of the payload and add GC barriers if needed. + assert(oop_off_2 == -1 || !UseG1GC, "sanity"); + // ZGC does not support compressed oops, so only one oop can be in the payload which is written by a "normal" oop store. + assert((oop_off_1 == -1 && oop_off_2 == -1) || !UseZGC, "ZGC does not support embedded oops in flat fields"); + const Type* val_type = Type::get_const_basic_type(store_bt); + kit->insert_mem_bar(Op_MemBarCPUOrder); + kit->access_store_at(base, ptr, TypeRawPtr::BOTTOM, payload, val_type, store_bt, decorators | C2_MISMATCHED, true, this); + kit->insert_mem_bar(Op_MemBarCPUOrder); + } else { + // Contains oops and requires late barrier expansion. Emit a special store node that allows to emit GC barriers in the backend. + assert(UseG1GC, "Unexpected GC"); + assert(store_bt == T_LONG, "Unexpected payload type"); + // If one oop, set the offset (if no offset is set, two oops are assumed by the backend) + Node* oop_offset = (oop_off_2 == -1) ? kit->intcon(oop_off_1) : nullptr; + kit->insert_mem_bar(Op_MemBarCPUOrder); + Node* mem = kit->reset_memory(); + kit->set_all_memory(mem); + Node* st = kit->gvn().transform(new StoreLSpecialNode(kit->control(), mem, ptr, TypeRawPtr::BOTTOM, payload, oop_offset, MemNode::unordered)); + kit->set_memory(st, TypeRawPtr::BOTTOM); + kit->insert_mem_bar(Op_MemBarCPUOrder); + } +} + +void InlineTypeNode::store_flat_array(GraphKit* kit, Node* base, Node* idx) const { + PhaseGVN& gvn = kit->gvn(); + DecoratorSet decorators = IN_HEAP | IS_ARRAY | MO_UNORDERED; + kit->C->set_flat_accesses(); + ciInlineKlass* vk = inline_klass(); + assert(vk->maybe_flat_in_array(), "element type %s cannot be flat in array", vk->name()->as_utf8()); + + RegionNode* region = new RegionNode(4); + gvn.set_type(region, Type::CONTROL); + kit->record_for_igvn(region); + + Node* input_memory_state = kit->reset_memory(); + kit->set_all_memory(input_memory_state); + + PhiNode* mem = PhiNode::make(region, input_memory_state, Type::MEMORY, TypePtr::BOTTOM); + gvn.set_type(mem, Type::MEMORY); + kit->record_for_igvn(mem); + + PhiNode* io = PhiNode::make(region, kit->i_o(), Type::ABIO); + gvn.set_type(io, Type::ABIO); + kit->record_for_igvn(io); + + Node* bol_null_free = kit->null_free_array_test(base); // Argument evaluation order is undefined in C++ and since this sets control, it needs to come first + IfNode* iff_null_free = kit->create_and_map_if(kit->control(), bol_null_free, PROB_FAIR, COUNT_UNKNOWN); + + // Nullable + kit->set_control(kit->IfFalse(iff_null_free)); + if (!kit->stopped()) { + assert(vk->has_nullable_atomic_layout(), "element type %s does not have a nullable flat layout", vk->name()->as_utf8()); + kit->set_all_memory(input_memory_state); + Node* cast = kit->cast_to_flat_array(base, vk, false, true, true); + Node* ptr = kit->array_element_address(cast, idx, T_FLAT_ELEMENT); + store_flat(kit, cast, ptr, true, false, false, decorators); + + region->init_req(1, kit->control()); + mem->set_req(1, kit->reset_memory()); + io->set_req(1, kit->i_o()); + } + + // Null-free + kit->set_control(kit->IfTrue(iff_null_free)); + if (!kit->stopped()) { + kit->set_all_memory(input_memory_state); + + Node* bol_atomic = kit->null_free_atomic_array_test(base, vk); + IfNode* iff_atomic = kit->create_and_map_if(kit->control(), bol_atomic, PROB_FAIR, COUNT_UNKNOWN); + + // Atomic + kit->set_control(kit->IfTrue(iff_atomic)); + if (!kit->stopped()) { + assert(vk->has_atomic_layout(), "element type %s does not have a null-free atomic flat layout", vk->name()->as_utf8()); + kit->set_all_memory(input_memory_state); + Node* cast = kit->cast_to_flat_array(base, vk, true, false, true); + Node* ptr = kit->array_element_address(cast, idx, T_FLAT_ELEMENT); + store_flat(kit, cast, ptr, true, false, true, decorators); + + region->init_req(2, kit->control()); + mem->set_req(2, kit->reset_memory()); + io->set_req(2, kit->i_o()); + } + + // Non-atomic + kit->set_control(kit->IfFalse(iff_atomic)); + if (!kit->stopped()) { + assert(vk->has_non_atomic_layout(), "element type %s does not have a null-free non-atomic flat layout", vk->name()->as_utf8()); + kit->set_all_memory(input_memory_state); + Node* cast = kit->cast_to_flat_array(base, vk, true, false, false); + Node* ptr = kit->array_element_address(cast, idx, T_FLAT_ELEMENT); + store_flat(kit, cast, ptr, false, false, true, decorators); + + region->init_req(3, kit->control()); + mem->set_req(3, kit->reset_memory()); + io->set_req(3, kit->i_o()); + } + } + + kit->set_control(gvn.transform(region)); + kit->set_all_memory(gvn.transform(mem)); + kit->set_i_o(gvn.transform(io)); +} + +void InlineTypeNode::store(GraphKit* kit, Node* base, Node* ptr, bool immutable_memory, DecoratorSet decorators) const { + // Write field values to memory + ciInlineKlass* vk = inline_klass(); + for (uint i = 0; i < field_count(); ++i) { + int field_off = field_offset(i) - vk->payload_offset(); + Node* field_val = field_value(i); + bool field_null_free = field_is_null_free(i); + ciType* ft = field_type(i); + Node* field_ptr = kit->basic_plus_adr(base, ptr, field_off); + if (field_is_flat(i)) { + // Recursively store the flat inline type field + ciInlineKlass* fvk = ft->as_inline_klass(); + // Atomic if nullable or not LooselyConsistentValue + bool atomic = !field_null_free || fvk->must_be_atomic(); + + field_val->as_InlineType()->store_flat(kit, base, field_ptr, atomic, immutable_memory, field_null_free, decorators); + } else { + // Store field value to memory + BasicType bt = type2field[ft->basic_type()]; + const TypePtr* field_ptr_type = (decorators & C2_MISMATCHED) == 0 ? kit->gvn().type(field_ptr)->is_ptr() : TypeRawPtr::BOTTOM; + const Type* val_type = Type::get_const_type(ft); + kit->access_store_at(base, field_ptr, field_ptr_type, field_val, val_type, bt, decorators); + } + } +} + +InlineTypeNode* InlineTypeNode::buffer(GraphKit* kit, bool safe_for_replace) { + if (kit->gvn().find_int_con(get_is_buffered(), 0) == 1) { + // Already buffered + return this; + } + + // Check if inline type is already buffered + Node* not_buffered_ctl = kit->top(); + Node* not_null_oop = kit->null_check_oop(get_oop(), ¬_buffered_ctl, /* never_see_null = */ false, safe_for_replace); + if (not_buffered_ctl->is_top()) { + // Already buffered + InlineTypeNode* vt = clone_if_required(&kit->gvn(), kit->map(), safe_for_replace); + vt->set_is_buffered(kit->gvn()); + vt = kit->gvn().transform(vt)->as_InlineType(); + if (safe_for_replace) { + kit->replace_in_map(this, vt); + } + return vt; + } + Node* buffered_ctl = kit->control(); + kit->set_control(not_buffered_ctl); + + // Inline type is not buffered, check if it is null. + Node* null_ctl = kit->top(); + kit->null_check_common(get_null_marker(), T_INT, false, &null_ctl); + bool null_free = null_ctl->is_top(); + + RegionNode* region = new RegionNode(4); + PhiNode* oop = PhiNode::make(region, not_null_oop, type()->join_speculative(null_free ? TypePtr::NOTNULL : TypePtr::BOTTOM)); + + // InlineType is already buffered + region->init_req(1, buffered_ctl); + oop->init_req(1, not_null_oop); + + // InlineType is null + region->init_req(2, null_ctl); + oop->init_req(2, kit->gvn().zerocon(T_OBJECT)); + + PhiNode* io = PhiNode::make(region, kit->i_o(), Type::ABIO); + PhiNode* mem = PhiNode::make(region, kit->merged_memory(), Type::MEMORY, TypePtr::BOTTOM); + + if (!kit->stopped()) { + assert(!is_allocated(&kit->gvn()), "already buffered"); + PreserveJVMState pjvms(kit); + ciInlineKlass* vk = inline_klass(); + // Allocate and initialize buffer, re-execute on deoptimization. + kit->jvms()->set_bci(kit->bci()); + kit->jvms()->set_should_reexecute(true); + kit->kill_dead_locals(); + Node* klass_node = kit->makecon(TypeKlassPtr::make(vk)); + Node* alloc_oop = kit->new_instance(klass_node, nullptr, nullptr, /* deoptimize_on_exception */ true, this); + Node* payload_alloc_oop = kit->basic_plus_adr(alloc_oop, vk->payload_offset()); + store(kit, alloc_oop, payload_alloc_oop, true, IN_HEAP | MO_UNORDERED | C2_TIGHTLY_COUPLED_ALLOC); + + // Do not let stores that initialize this buffer be reordered with a subsequent + // store that would make this buffer accessible by other threads. + AllocateNode* alloc = AllocateNode::Ideal_allocation(alloc_oop); + assert(alloc != nullptr, "must have an allocation node"); + kit->insert_mem_bar(Op_MemBarStoreStore, alloc->proj_out_or_null(AllocateNode::RawAddress)); + oop->init_req(3, alloc_oop); + region->init_req(3, kit->control()); + io ->init_req(3, kit->i_o()); + mem ->init_req(3, kit->merged_memory()); + } + + // Update GraphKit + kit->set_control(kit->gvn().transform(region)); + kit->set_i_o(kit->gvn().transform(io)); + kit->set_all_memory(kit->gvn().transform(mem)); + kit->record_for_igvn(region); + kit->record_for_igvn(oop); + kit->record_for_igvn(io); + kit->record_for_igvn(mem); + + // Use cloned InlineTypeNode to propagate oop from now on + Node* res_oop = kit->gvn().transform(oop); + InlineTypeNode* vt = clone_if_required(&kit->gvn(), kit->map(), safe_for_replace); + vt->set_oop(kit->gvn(), res_oop); + vt->set_is_buffered(kit->gvn()); + vt = kit->gvn().transform(vt)->as_InlineType(); + if (safe_for_replace) { + kit->replace_in_map(this, vt); + } + // InlineTypeNode::remove_redundant_allocations piggybacks on split if. + // Make sure it gets a chance to remove this allocation. + kit->C->set_has_split_ifs(true); + return vt; +} + +bool InlineTypeNode::is_allocated(PhaseGVN* phase) const { + if (phase->find_int_con(get_is_buffered(), 0) == 1) { + return true; + } + Node* oop = get_oop(); + const Type* oop_type = (phase != nullptr) ? phase->type(oop) : oop->bottom_type(); + return !oop_type->maybe_null(); +} + +static void replace_proj(Compile* C, CallNode* call, uint& proj_idx, Node* value, BasicType bt) { + ProjNode* pn = call->proj_out_or_null(proj_idx); + if (pn != nullptr) { + C->gvn_replace_by(pn, value); + C->initial_gvn()->hash_delete(pn); + pn->set_req(0, C->top()); + } + proj_idx += type2size[bt]; +} + +// When a call returns multiple values, it has several result +// projections, one per field. Replacing the result of the call by an +// inline type node (after late inlining) requires that for each result +// projection, we find the corresponding inline type field. +void InlineTypeNode::replace_call_results(GraphKit* kit, CallNode* call, Compile* C) { + uint proj_idx = TypeFunc::Parms; + // Replace oop projection + replace_proj(C, call, proj_idx, get_oop(), T_OBJECT); + // Replace field projections + replace_field_projs(C, call, proj_idx); + // Replace null_marker projection + replace_proj(C, call, proj_idx, get_null_marker(), T_BOOLEAN); + assert(proj_idx == call->tf()->range_cc()->cnt(), "missed a projection"); +} + +void InlineTypeNode::replace_field_projs(Compile* C, CallNode* call, uint& proj_idx) { + for (uint i = 0; i < field_count(); ++i) { + Node* value = field_value(i); + if (field_is_flat(i)) { + InlineTypeNode* vt = value->as_InlineType(); + // Replace field projections for flat field + vt->replace_field_projs(C, call, proj_idx); + if (!field_is_null_free(i)) { + // Replace null_marker projection for nullable field + replace_proj(C, call, proj_idx, vt->get_null_marker(), T_BOOLEAN); + } + continue; + } + // Replace projection for field value + replace_proj(C, call, proj_idx, value, field_type(i)->basic_type()); + } +} + +Node* InlineTypeNode::allocate_fields(GraphKit* kit) { + InlineTypeNode* vt = clone_if_required(&kit->gvn(), kit->map()); + for (uint i = 0; i < field_count(); i++) { + Node* value = field_value(i); + if (field_is_flat(i)) { + // Flat inline type field + vt->set_field_value(i, value->as_InlineType()->allocate_fields(kit)); + } else if (value->is_InlineType()) { + // Non-flat inline type field + vt->set_field_value(i, value->as_InlineType()->buffer(kit)); + } + } + vt = kit->gvn().transform(vt)->as_InlineType(); + kit->replace_in_map(this, vt); + return vt; +} + +// Replace a buffer allocation by a dominating allocation +static void replace_allocation(PhaseIterGVN* igvn, Node* res, Node* dom) { + // Remove initializing stores and GC barriers + for (DUIterator_Fast imax, i = res->fast_outs(imax); i < imax; i++) { + Node* use = res->fast_out(i); + if (use->is_AddP()) { + for (DUIterator_Fast jmax, j = use->fast_outs(jmax); j < jmax; j++) { + Node* store = use->fast_out(j)->isa_Store(); + if (store != nullptr) { + igvn->rehash_node_delayed(store); + igvn->replace_in_uses(store, store->in(MemNode::Memory)); + } + } + } else if (use->Opcode() == Op_CastP2X) { + if (UseG1GC && use->find_out_with(Op_XorX)->in(1) != use) { + // The G1 pre-barrier uses a CastP2X both for the pointer of the object + // we store into, as well as the value we are storing. Skip if this is a + // barrier for storing 'res' into another object. + continue; + } + BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); + bs->eliminate_gc_barrier(igvn, use); + --i; --imax; + } + } + igvn->replace_node(res, dom); +} + +Node* InlineTypeNode::Ideal(PhaseGVN* phase, bool can_reshape) { + Node* oop = get_oop(); + Node* is_buffered = get_is_buffered(); + + if (oop->isa_InlineType() && !phase->type(oop)->maybe_null()) { + InlineTypeNode* vtptr = oop->as_InlineType(); + set_oop(*phase, vtptr->get_oop()); + set_is_buffered(*phase); + set_null_marker(*phase); + for (uint i = Values; i < vtptr->req(); ++i) { + set_req(i, vtptr->in(i)); + } + return this; + } + + // Use base oop if fields are loaded from memory, don't do so if base is the CheckCastPP of an + // allocation because the only case we load from a naked CheckCastPP is when we exit a + // constructor of an inline type and we want to relinquish the larval oop there. This has a + // couple of benefits: + // - The allocation is likely to be elided earlier if it is not an input of an InlineTypeNode. + // - The InlineTypeNode without an allocation input is more likely to be GVN-ed. This may emerge + // when we try to clone a value object. + // - The buffering, if needed, is delayed until it is required. This new allocation, since it is + // created from an InlineTypeNode, is recognized as not having a unique identity and in the + // future, we can move them around more freely such as hoisting out of loops. This is not true + // for the old allocation since larval value objects do have unique identities. + Node* base = is_loaded(phase); + if (base != nullptr && !base->is_InlineType() && !phase->type(base)->maybe_null() && AllocateNode::Ideal_allocation(base) == nullptr) { + if (oop != base || phase->type(is_buffered) != TypeInt::ONE) { + set_oop(*phase, base); + set_is_buffered(*phase); + return this; + } + } + + if (can_reshape) { + PhaseIterGVN* igvn = phase->is_IterGVN(); + if (is_allocated(phase)) { + // Search for and remove re-allocations of this inline type. Ignore scalar replaceable ones, + // they will be removed anyway and changing the memory chain will confuse other optimizations. + // This can happen with late inlining when we first allocate an inline type argument + // but later decide to inline the call after the callee code also triggered allocation. + for (DUIterator_Fast imax, i = fast_outs(imax); i < imax; i++) { + AllocateNode* alloc = fast_out(i)->isa_Allocate(); + if (alloc != nullptr && alloc->in(AllocateNode::InlineType) == this && !alloc->_is_scalar_replaceable) { + // Found a re-allocation + Node* res = alloc->result_cast(); + if (res != nullptr && res->is_CheckCastPP()) { + // Replace allocation by oop and unlink AllocateNode + replace_allocation(igvn, res, oop); + igvn->replace_input_of(alloc, AllocateNode::InlineType, igvn->C->top()); + --i; --imax; + } + } + } + } + } + + return nullptr; +} + +InlineTypeNode* InlineTypeNode::make_uninitialized(PhaseGVN& gvn, ciInlineKlass* vk, bool null_free) { + // Create a new InlineTypeNode with uninitialized values and nullptr oop + InlineTypeNode* vt = new InlineTypeNode(vk, gvn.zerocon(T_OBJECT), null_free); + vt->set_is_buffered(gvn, false); + vt->set_null_marker(gvn); + return vt; +} + +InlineTypeNode* InlineTypeNode::make_all_zero(PhaseGVN& gvn, ciInlineKlass* vk) { + GrowableArray visited; + visited.push(vk); + return make_all_zero_impl(gvn, vk, visited); +} + +InlineTypeNode* InlineTypeNode::make_all_zero_impl(PhaseGVN& gvn, ciInlineKlass* vk, GrowableArray& visited) { + // Create a new InlineTypeNode initialized with all zero + InlineTypeNode* vt = new InlineTypeNode(vk, gvn.zerocon(T_OBJECT), /* null_free= */ true); + vt->set_is_buffered(gvn, false); + vt->set_null_marker(gvn); + for (uint i = 0; i < vt->field_count(); ++i) { + ciType* ft = vt->field_type(i); + Node* value = gvn.zerocon(ft->basic_type()); + if (!vt->field_is_flat(i) && visited.contains(ft)) { + gvn.C->set_has_circular_inline_type(true); + } else if (ft->is_inlinetype()) { + int old_len = visited.length(); + visited.push(ft); + ciInlineKlass* vk = ft->as_inline_klass(); + if (vt->field_is_null_free(i)) { + value = make_all_zero_impl(gvn, vk, visited); + } else { + value = make_null_impl(gvn, vk, visited); + } + visited.trunc_to(old_len); + } + vt->set_field_value(i, value); + } + vt = gvn.transform(vt)->as_InlineType(); + assert(vt->is_all_zero(&gvn), "must be the all-zero inline type"); + return vt; +} + +bool InlineTypeNode::is_all_zero(PhaseGVN* gvn, bool flat) const { + const TypeInt* tinit = gvn->type(get_null_marker())->isa_int(); + if (tinit == nullptr || !tinit->is_con(1)) { + return false; // May be null + } + for (uint i = 0; i < field_count(); ++i) { + Node* value = field_value(i); + if (field_is_null_free(i)) { + // Null-free value class field must have the all-zero value. If 'flat' is set, + // reject non-flat fields because they need to be initialized with an oop to a buffer. + if (!value->is_InlineType() || !value->as_InlineType()->is_all_zero(gvn) || (flat && !field_is_flat(i))) { + return false; + } + continue; + } else if (value->is_InlineType()) { + // Nullable value class field must be null + tinit = gvn->type(value->as_InlineType()->get_null_marker())->isa_int(); + if (tinit != nullptr && tinit->is_con(0)) { + continue; + } + return false; + } else if (!gvn->type(value)->is_zero_type()) { + return false; + } + } + return true; +} + +InlineTypeNode* InlineTypeNode::make_from_oop(GraphKit* kit, Node* oop, ciInlineKlass* vk) { + GrowableArray visited; + visited.push(vk); + return make_from_oop_impl(kit, oop, vk, visited); +} + +InlineTypeNode* InlineTypeNode::make_from_oop_impl(GraphKit* kit, Node* oop, ciInlineKlass* vk, GrowableArray& visited) { + PhaseGVN& gvn = kit->gvn(); + + // Create and initialize an InlineTypeNode by loading all field + // values from a heap-allocated version and also save the oop. + InlineTypeNode* vt = nullptr; + + if (oop->isa_InlineType()) { + return oop->as_InlineType(); + } + + if (gvn.type(oop)->maybe_null()) { + // Add a null check because the oop may be null + Node* null_ctl = kit->top(); + Node* not_null_oop = kit->null_check_oop(oop, &null_ctl); + if (kit->stopped()) { + // Constant null + kit->set_control(null_ctl); + vt = make_null_impl(gvn, vk, visited); + kit->record_for_igvn(vt); + return vt; + } + vt = new InlineTypeNode(vk, not_null_oop, /* null_free= */ false); + vt->set_is_buffered(gvn); + vt->set_null_marker(gvn); + Node* payload_ptr = kit->basic_plus_adr(not_null_oop, vk->payload_offset()); + vt->load(kit, not_null_oop, payload_ptr, true, true, IN_HEAP | MO_UNORDERED, visited); + + if (null_ctl != kit->top()) { + InlineTypeNode* null_vt = make_null_impl(gvn, vk, visited); + Node* region = new RegionNode(3); + region->init_req(1, kit->control()); + region->init_req(2, null_ctl); + vt = vt->clone_with_phis(&gvn, region, kit->map()); + vt->merge_with(&gvn, null_vt, 2, true); + vt->set_oop(gvn, oop); + kit->set_control(gvn.transform(region)); + } + } else { + // Oop can never be null + vt = new InlineTypeNode(vk, oop, /* null_free= */ true); + Node* init_ctl = kit->control(); + vt->set_is_buffered(gvn); + vt->set_null_marker(gvn); + Node* payload_ptr = kit->basic_plus_adr(oop, vk->payload_offset()); + vt->load(kit, oop, payload_ptr, true, true, IN_HEAP | MO_UNORDERED, visited); +// TODO 8284443 +// assert(!null_free || vt->as_InlineType()->is_all_zero(&gvn) || init_ctl != kit->control() || !gvn.type(oop)->is_inlinetypeptr() || oop->is_Con() || oop->Opcode() == Op_InlineType || +// AllocateNode::Ideal_allocation(oop, &gvn) != nullptr || vt->as_InlineType()->is_loaded(&gvn) == oop, "inline type should be loaded"); + } + assert(vt->is_allocated(&gvn), "inline type should be allocated"); + kit->record_for_igvn(vt); + return gvn.transform(vt)->as_InlineType(); +} + +InlineTypeNode* InlineTypeNode::make_from_flat(GraphKit* kit, ciInlineKlass* vk, Node* base, Node* ptr, + bool atomic, bool immutable_memory, bool null_free, DecoratorSet decorators) { + GrowableArray visited; + visited.push(vk); + return make_from_flat_impl(kit, vk, base, ptr, atomic, immutable_memory, null_free, null_free, decorators, visited); +} + +// GraphKit wrapper for the 'make_from_flat' method +InlineTypeNode* InlineTypeNode::make_from_flat_impl(GraphKit* kit, ciInlineKlass* vk, Node* base, Node* ptr, bool atomic, bool immutable_memory, + bool null_free, bool trust_null_free_oop, DecoratorSet decorators, GrowableArray& visited) { + assert(null_free || !trust_null_free_oop, "cannot trust null-free oop when the holder object is not null-free"); + PhaseGVN& gvn = kit->gvn(); + bool do_atomic = atomic; + // With immutable memory, a non-atomic load and an atomic load are the same + if (immutable_memory) { + do_atomic = false; + } + // If there is only one flattened field, a non-atomic load and an atomic load are the same + if (vk->is_naturally_atomic(null_free)) { + do_atomic = false; + } + + if (!do_atomic) { + InlineTypeNode* vt = make_uninitialized(kit->gvn(), vk, null_free); + if (!null_free) { + int nm_offset = vk->null_marker_offset_in_payload(); + Node* nm_ptr = kit->basic_plus_adr(base, ptr, nm_offset); + const TypePtr* nm_ptr_type = (decorators & C2_MISMATCHED) == 0 ? gvn.type(nm_ptr)->is_ptr() : TypeRawPtr::BOTTOM; + Node* nm_value = kit->access_load_at(base, nm_ptr, nm_ptr_type, TypeInt::BOOL, T_BOOLEAN, decorators); + vt->set_req(NullMarker, nm_value); + } + + vt->load(kit, base, ptr, immutable_memory, trust_null_free_oop, decorators, visited); + return gvn.transform(vt)->as_InlineType(); + } + + assert(!immutable_memory, "immutable memory does not need explicit atomic access"); + InlineTypeNode* vt = make_uninitialized(kit->gvn(), vk, null_free); + BasicType load_bt = vk->atomic_size_to_basic_type(null_free); + decorators |= C2_MISMATCHED | C2_CONTROL_DEPENDENT_LOAD; + const Type* val_type = Type::get_const_basic_type(load_bt); + kit->insert_mem_bar(Op_MemBarCPUOrder); + Node* payload = kit->access_load_at(base, ptr, TypeRawPtr::BOTTOM, val_type, load_bt, decorators, kit->control()); + kit->insert_mem_bar(Op_MemBarCPUOrder); + vt->convert_from_payload(kit, load_bt, kit->gvn().transform(payload), 0, null_free, trust_null_free_oop); + return gvn.transform(vt)->as_InlineType(); +} + +InlineTypeNode* InlineTypeNode::make_from_flat_array(GraphKit* kit, ciInlineKlass* vk, Node* base, Node* idx) { + assert(vk->maybe_flat_in_array(), "element type %s cannot be flat in array", vk->name()->as_utf8()); + PhaseGVN& gvn = kit->gvn(); + DecoratorSet decorators = IN_HEAP | IS_ARRAY | MO_UNORDERED | C2_CONTROL_DEPENDENT_LOAD; + kit->C->set_flat_accesses(); + InlineTypeNode* vt_nullable = nullptr; + InlineTypeNode* vt_null_free = nullptr; + InlineTypeNode* vt_non_atomic = nullptr; + + RegionNode* region = new RegionNode(4); + gvn.set_type(region, Type::CONTROL); + kit->record_for_igvn(region); + + Node* input_memory_state = kit->reset_memory(); + kit->set_all_memory(input_memory_state); + + PhiNode* mem = PhiNode::make(region, input_memory_state, Type::MEMORY, TypePtr::BOTTOM); + gvn.set_type(mem, Type::MEMORY); + kit->record_for_igvn(mem); + + PhiNode* io = PhiNode::make(region, kit->i_o(), Type::ABIO); + gvn.set_type(io, Type::ABIO); + kit->record_for_igvn(io); + + Node* bol_null_free = kit->null_free_array_test(base); // Argument evaluation order is undefined in C++ and since this sets control, it needs to come first + IfNode* iff_null_free = kit->create_and_map_if(kit->control(), bol_null_free, PROB_FAIR, COUNT_UNKNOWN); + + // Nullable + kit->set_control(kit->IfFalse(iff_null_free)); + if (!kit->stopped()) { + assert(vk->has_nullable_atomic_layout(), "element type %s does not have a nullable flat layout", vk->name()->as_utf8()); + kit->set_all_memory(input_memory_state); + Node* cast = kit->cast_to_flat_array(base, vk, false, true, true); + Node* ptr = kit->array_element_address(cast, idx, T_FLAT_ELEMENT); + vt_nullable = InlineTypeNode::make_from_flat(kit, vk, cast, ptr, true, false, false, decorators); + + region->init_req(1, kit->control()); + mem->set_req(1, kit->reset_memory()); + io->set_req(1, kit->i_o()); + } + + // Null-free + kit->set_control(kit->IfTrue(iff_null_free)); + if (!kit->stopped()) { + kit->set_all_memory(input_memory_state); + + Node* bol_atomic = kit->null_free_atomic_array_test(base, vk); + IfNode* iff_atomic = kit->create_and_map_if(kit->control(), bol_atomic, PROB_FAIR, COUNT_UNKNOWN); + + // Atomic + kit->set_control(kit->IfTrue(iff_atomic)); + if (!kit->stopped()) { + assert(vk->has_atomic_layout(), "element type %s does not have a null-free atomic flat layout", vk->name()->as_utf8()); + kit->set_all_memory(input_memory_state); + Node* cast = kit->cast_to_flat_array(base, vk, true, false, true); + Node* ptr = kit->array_element_address(cast, idx, T_FLAT_ELEMENT); + vt_null_free = InlineTypeNode::make_from_flat(kit, vk, cast, ptr, true, false, true, decorators); + + region->init_req(2, kit->control()); + mem->set_req(2, kit->reset_memory()); + io->set_req(2, kit->i_o()); + } + + // Non-Atomic + kit->set_control(kit->IfFalse(iff_atomic)); + if (!kit->stopped()) { + assert(vk->has_non_atomic_layout(), "element type %s does not have a null-free non-atomic flat layout", vk->name()->as_utf8()); + kit->set_all_memory(input_memory_state); + Node* cast = kit->cast_to_flat_array(base, vk, true, false, false); + Node* ptr = kit->array_element_address(cast, idx, T_FLAT_ELEMENT); + vt_non_atomic = InlineTypeNode::make_from_flat(kit, vk, cast, ptr, false, false, true, decorators); + + region->init_req(3, kit->control()); + mem->set_req(3, kit->reset_memory()); + io->set_req(3, kit->i_o()); + } + } + + InlineTypeNode* vt = nullptr; + if (vt_nullable == nullptr && vt_null_free == nullptr && vt_non_atomic == nullptr) { + // All paths are dead + vt = make_null(gvn, vk); + } else if (vt_nullable == nullptr && vt_null_free == nullptr) { + vt = vt_non_atomic; + } else if (vt_nullable == nullptr && vt_non_atomic == nullptr) { + vt = vt_null_free; + } else if (vt_null_free == nullptr && vt_non_atomic == nullptr) { + vt = vt_nullable; + } + if (vt != nullptr) { + kit->set_control(kit->gvn().transform(region)); + kit->set_all_memory(kit->gvn().transform(mem)); + kit->set_i_o(kit->gvn().transform(io)); + return vt; + } + + InlineTypeNode* zero = InlineTypeNode::make_null(gvn, vk); + vt = zero->clone_with_phis(&gvn, region); + if (vt_nullable != nullptr) { + vt = vt->merge_with(&gvn, vt_nullable, 1, false); + } + if (vt_null_free != nullptr) { + vt = vt->merge_with(&gvn, vt_null_free, 2, false); + } + if (vt_non_atomic != nullptr) { + vt = vt->merge_with(&gvn, vt_non_atomic, 3, false); + } + + kit->set_control(kit->gvn().transform(region)); + kit->set_all_memory(kit->gvn().transform(mem)); + kit->set_i_o(kit->gvn().transform(io)); + return gvn.transform(vt)->as_InlineType(); +} + +InlineTypeNode* InlineTypeNode::make_from_multi(GraphKit* kit, MultiNode* multi, ciInlineKlass* vk, uint& base_input, bool in, bool null_free) { + InlineTypeNode* vt = make_uninitialized(kit->gvn(), vk, null_free); + if (!in) { + // Keep track of the oop. The returned inline type might already be buffered. + Node* oop = kit->gvn().transform(new ProjNode(multi, base_input++)); + vt->set_oop(kit->gvn(), oop); + } + GrowableArray visited; + visited.push(vk); + vt->initialize_fields(kit, multi, base_input, in, null_free, nullptr, visited); + return kit->gvn().transform(vt)->as_InlineType(); +} + +Node* InlineTypeNode::is_loaded(PhaseGVN* phase, ciInlineKlass* vk, Node* base, int holder_offset) { + if (vk == nullptr) { + vk = inline_klass(); + } + for (uint i = 0; i < field_count(); ++i) { + int offset = holder_offset + field_offset(i); + Node* value = field_value(i); + if (value->is_InlineType()) { + InlineTypeNode* vt = value->as_InlineType(); + if (vt->type()->inline_klass()->is_empty()) { + continue; + } else if (field_is_flat(i) && vt->is_InlineType()) { + // Check inline type field load recursively + base = vt->as_InlineType()->is_loaded(phase, vk, base, offset - vt->type()->inline_klass()->payload_offset()); + if (base == nullptr) { + return nullptr; + } + continue; + } else { + value = vt->get_oop(); + if (value->Opcode() == Op_CastPP) { + // Skip CastPP + value = value->in(1); + } + } + } + if (value->isa_DecodeN()) { + // Skip DecodeN + value = value->in(1); + } + if (value->isa_Load()) { + // Check if base and offset of field load matches inline type layout + intptr_t loffset = 0; + Node* lbase = AddPNode::Ideal_base_and_offset(value->in(MemNode::Address), phase, loffset); + if (lbase == nullptr || (lbase != base && base != nullptr) || loffset != offset) { + return nullptr; + } else if (base == nullptr) { + // Set base and check if pointer type matches + base = lbase; + const TypeInstPtr* vtptr = phase->type(base)->isa_instptr(); + if (vtptr == nullptr || !vtptr->instance_klass()->equals(vk)) { + return nullptr; + } + } + } else { + return nullptr; + } + } + return base; +} + +Node* InlineTypeNode::tagged_klass(ciInlineKlass* vk, PhaseGVN& gvn) { + const TypeKlassPtr* tk = TypeKlassPtr::make(vk); + intptr_t bits = tk->get_con(); + set_nth_bit(bits, 0); + return gvn.longcon((jlong)bits); +} + +void InlineTypeNode::pass_fields(GraphKit* kit, Node* n, uint& base_input, bool in, bool null_free) { + if (!null_free && in) { + n->init_req(base_input++, get_null_marker()); + } + for (uint i = 0; i < field_count(); i++) { + Node* arg = field_value(i); + if (field_is_flat(i)) { + // Flat inline type field + arg->as_InlineType()->pass_fields(kit, n, base_input, in); + if (!field_is_null_free(i)) { + assert(field_null_marker_offset(i) != -1, "inconsistency"); + n->init_req(base_input++, arg->as_InlineType()->get_null_marker()); + } + } else { + if (arg->is_InlineType()) { + // Non-flat inline type field + InlineTypeNode* vt = arg->as_InlineType(); + assert(n->Opcode() != Op_Return || vt->is_allocated(&kit->gvn()), "inline type field should be allocated on return"); + arg = vt->buffer(kit); + } + // Initialize call/return arguments + n->init_req(base_input++, arg); + if (field_type(i)->size() == 2) { + n->init_req(base_input++, kit->top()); + } + } + } + // The last argument is used to pass the null marker to compiled code and not required here. + if (!null_free && !in) { + n->init_req(base_input++, kit->top()); + } +} + +void InlineTypeNode::initialize_fields(GraphKit* kit, MultiNode* multi, uint& base_input, bool in, bool null_free, Node* null_check_region, GrowableArray& visited) { + PhaseGVN& gvn = kit->gvn(); + Node* null_marker = nullptr; + if (!null_free) { + // Nullable inline type + if (in) { + // Set null marker + if (multi->is_Start()) { + null_marker = gvn.transform(new ParmNode(multi->as_Start(), base_input)); + } else { + null_marker = multi->as_Call()->in(base_input); + } + set_req(NullMarker, null_marker); + base_input++; + } + // Add a null check to make subsequent loads dependent on + assert(null_check_region == nullptr, "already set"); + if (null_marker == nullptr) { + // Will only be initialized below, use dummy node for now + null_marker = new Node(1); + null_marker->init_req(0, kit->control()); // Add an input to prevent dummy from being dead + gvn.set_type_bottom(null_marker); + } + Node* null_ctrl = kit->top(); + kit->null_check_common(null_marker, T_INT, false, &null_ctrl); + Node* non_null_ctrl = kit->control(); + null_check_region = new RegionNode(3); + null_check_region->init_req(1, non_null_ctrl); + null_check_region->init_req(2, null_ctrl); + null_check_region = gvn.transform(null_check_region); + kit->set_control(null_check_region); + } + + for (uint i = 0; i < field_count(); ++i) { + ciType* type = field_type(i); + Node* parm = nullptr; + if (field_is_flat(i)) { + // Flat inline type field + InlineTypeNode* vt = make_uninitialized(gvn, type->as_inline_klass(), field_is_null_free(i)); + vt->initialize_fields(kit, multi, base_input, in, true, null_check_region, visited); + if (!field_is_null_free(i)) { + assert(field_null_marker_offset(i) != -1, "inconsistency"); + Node* null_marker = nullptr; + if (multi->is_Start()) { + null_marker = gvn.transform(new ParmNode(multi->as_Start(), base_input)); + } else if (in) { + null_marker = multi->as_Call()->in(base_input); + } else { + null_marker = gvn.transform(new ProjNode(multi->as_Call(), base_input)); + } + vt->set_req(NullMarker, null_marker); + base_input++; + } + parm = gvn.transform(vt); + } else { + if (multi->is_Start()) { + assert(in, "return from start?"); + parm = gvn.transform(new ParmNode(multi->as_Start(), base_input)); + } else if (in) { + parm = multi->as_Call()->in(base_input); + } else { + parm = gvn.transform(new ProjNode(multi->as_Call(), base_input)); + } + bool null_free = field_is_null_free(i); + // Non-flat inline type field + if (type->is_inlinetype()) { + if (null_check_region != nullptr) { + // We limit scalarization for inline types with circular fields and can therefore observe nodes + // of the same type but with different scalarization depth during GVN. To avoid inconsistencies + // during merging, make sure that we only create Phis for fields that are guaranteed to be scalarized. + if (parm->is_InlineType() && kit->C->has_circular_inline_type()) { + parm = parm->as_InlineType()->get_oop(); + } + // Holder is nullable, set field to nullptr if holder is nullptr to avoid loading from uninitialized memory + parm = PhiNode::make(null_check_region, parm, TypeInstPtr::make(TypePtr::BotPTR, type->as_inline_klass())); + parm->set_req(2, kit->zerocon(T_OBJECT)); + parm = gvn.transform(parm); + null_free = false; + } + if (visited.contains(type)) { + kit->C->set_has_circular_inline_type(true); + } else if (!parm->is_InlineType()) { + int old_len = visited.length(); + visited.push(type); + if (null_free) { + parm = kit->cast_not_null(parm); + } + parm = make_from_oop_impl(kit, parm, type->as_inline_klass(), visited); + visited.trunc_to(old_len); + } + } + base_input += type->size(); + } + assert(parm != nullptr, "should never be null"); + assert(field_value(i) == nullptr, "already set"); + set_field_value(i, parm); + gvn.record_for_igvn(parm); + } + // The last argument is used to pass the null marker to compiled code + if (!null_free && !in) { + Node* cmp = null_marker->raw_out(0); + null_marker = gvn.transform(new ProjNode(multi->as_Call(), base_input)); + set_req(NullMarker, null_marker); + gvn.hash_delete(cmp); + cmp->set_req(1, null_marker); + gvn.hash_find_insert(cmp); + gvn.record_for_igvn(cmp); + base_input++; + } +} + +// Search for multiple allocations of this inline type and try to replace them by dominating allocations. +// Equivalent InlineTypeNodes are merged by GVN, so we just need to search for AllocateNode users to find redundant allocations. +void InlineTypeNode::remove_redundant_allocations(PhaseIdealLoop* phase) { + PhaseIterGVN* igvn = &phase->igvn(); + // Search for allocations of this inline type. Ignore scalar replaceable ones, they + // will be removed anyway and changing the memory chain will confuse other optimizations. + for (DUIterator_Fast imax, i = fast_outs(imax); i < imax; i++) { + AllocateNode* alloc = fast_out(i)->isa_Allocate(); + if (alloc != nullptr && alloc->in(AllocateNode::InlineType) == this && !alloc->_is_scalar_replaceable) { + Node* res = alloc->result_cast(); + if (res == nullptr || !res->is_CheckCastPP()) { + break; // No unique CheckCastPP + } + // Search for a dominating allocation of the same inline type + Node* res_dom = res; + for (DUIterator_Fast jmax, j = fast_outs(jmax); j < jmax; j++) { + AllocateNode* alloc_other = fast_out(j)->isa_Allocate(); + if (alloc_other != nullptr && alloc_other->in(AllocateNode::InlineType) == this && !alloc_other->_is_scalar_replaceable) { + Node* res_other = alloc_other->result_cast(); + if (res_other != nullptr && res_other->is_CheckCastPP() && res_other != res_dom && + phase->is_dominator(res_other->in(0), res_dom->in(0))) { + res_dom = res_other; + } + } + } + if (res_dom != res) { + // Replace allocation by dominating one. + replace_allocation(igvn, res, res_dom); + // The result of the dominated allocation is now unused and will be removed + // later in PhaseMacroExpand::eliminate_allocate_node to not confuse loop opts. + igvn->_worklist.push(alloc); + } + } + } +} + +InlineTypeNode* InlineTypeNode::make_null(PhaseGVN& gvn, ciInlineKlass* vk, bool transform) { + GrowableArray visited; + visited.push(vk); + return make_null_impl(gvn, vk, visited, transform); +} + +InlineTypeNode* InlineTypeNode::make_null_impl(PhaseGVN& gvn, ciInlineKlass* vk, GrowableArray& visited, bool transform) { + InlineTypeNode* vt = new InlineTypeNode(vk, gvn.zerocon(T_OBJECT), /* null_free= */ false); + vt->set_is_buffered(gvn); + vt->set_null_marker(gvn, gvn.intcon(0)); + for (uint i = 0; i < vt->field_count(); i++) { + ciType* ft = vt->field_type(i); + Node* value = gvn.zerocon(ft->basic_type()); + if (!vt->field_is_flat(i) && visited.contains(ft)) { + gvn.C->set_has_circular_inline_type(true); + } else if (ft->is_inlinetype()) { + int old_len = visited.length(); + visited.push(ft); + value = make_null_impl(gvn, ft->as_inline_klass(), visited); + visited.trunc_to(old_len); + } + vt->set_field_value(i, value); + } + return transform ? gvn.transform(vt)->as_InlineType() : vt; +} + +InlineTypeNode* InlineTypeNode::clone_if_required(PhaseGVN* gvn, SafePointNode* map, bool safe_for_replace) { + if (!safe_for_replace || (map == nullptr && outcnt() != 0)) { + return clone()->as_InlineType(); + } + for (DUIterator_Fast imax, i = fast_outs(imax); i < imax; i++) { + if (fast_out(i) != map) { + return clone()->as_InlineType(); + } + } + gvn->hash_delete(this); + return this; +} + +const Type* InlineTypeNode::Value(PhaseGVN* phase) const { + Node* oop = get_oop(); + const Type* toop = phase->type(oop); +#ifdef ASSERT + if (oop->is_Con() && toop->is_zero_type() && _type->isa_oopptr()->is_known_instance()) { + // We are not allocated (anymore) and should therefore not have an instance id + dump(1); + assert(false, "Unbuffered inline type should not have known instance id"); + } +#endif + if (toop == Type::TOP) { + return Type::TOP; + } + const Type* t = toop->filter_speculative(_type); + if (t->singleton()) { + // Don't replace InlineType by a constant + t = _type; + } + const Type* tinit = phase->type(in(NullMarker)); + if (tinit == Type::TOP) { + return Type::TOP; + } + if (tinit->isa_int() && tinit->is_int()->is_con(1)) { + t = t->join_speculative(TypePtr::NOTNULL); + } + return t; +} diff --git a/src/hotspot/share/opto/inlinetypenode.hpp b/src/hotspot/share/opto/inlinetypenode.hpp new file mode 100644 index 00000000000..a5f56aac4f8 --- /dev/null +++ b/src/hotspot/share/opto/inlinetypenode.hpp @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_OPTO_INLINETYPENODE_HPP +#define SHARE_VM_OPTO_INLINETYPENODE_HPP + +#include "ci/ciInlineKlass.hpp" +#include "gc/shared/c2/barrierSetC2.hpp" +#include "oops/accessDecorators.hpp" +#include "opto/connode.hpp" +#include "opto/loopnode.hpp" +#include "opto/node.hpp" + +class GraphKit; + +//------------------------------InlineTypeNode------------------------------------- +// Node representing an inline type in C2 IR +class InlineTypeNode : public TypeNode { +private: + InlineTypeNode(ciInlineKlass* vk, Node* oop, bool null_free) + : TypeNode(TypeInstPtr::make(null_free ? TypePtr::NotNull : TypePtr::BotPTR, vk), Values + vk->nof_declared_nonstatic_fields()) { + init_class_id(Class_InlineType); + init_req(Oop, oop); + Compile::current()->add_inline_type(this); + } + + enum { Control, // Control input. + Oop, // Oop to heap allocated buffer. + IsBuffered, // True if inline type is heap allocated (or nullptr), false otherwise. + NullMarker, // Needs to be checked before using the field values. + // 0 => InlineType is null + // 1 => InlineType is non-null + // Can be dynamic value, not necessarily statically known + Values // Nodes corresponding to values of the inline type's fields. + // Nodes are connected in increasing order of the index of the field they correspond to. + }; + + // Get the klass defining the field layout of the inline type + ciInlineKlass* inline_klass() const { return type()->inline_klass(); } + + void make_scalar_in_safepoint(PhaseIterGVN* igvn, Unique_Node_List& worklist, SafePointNode* sfpt); + uint add_fields_to_safepoint(Unique_Node_List& worklist, SafePointNode* sfpt); + + // Checks if the inline type is loaded from memory and if so returns the oop + Node* is_loaded(PhaseGVN* phase, ciInlineKlass* vk = nullptr, Node* base = nullptr, int holder_offset = 0); + + // Initialize the inline type fields with the inputs or outputs of a MultiNode + void initialize_fields(GraphKit* kit, MultiNode* multi, uint& base_input, bool in, bool null_free, Node* null_check_region, GrowableArray& visited); + // Initialize the inline type by loading its field values from memory + void load(GraphKit* kit, Node* base, Node* ptr, bool immutable_memory, bool trust_null_free_oop, DecoratorSet decorators, GrowableArray& visited); + // Store the field values to memory + void store(GraphKit* kit, Node* base, Node* ptr, bool immutable_memory, DecoratorSet decorators) const; + + InlineTypeNode* adjust_scalarization_depth_impl(GraphKit* kit, GrowableArray& visited); + + static InlineTypeNode* make_all_zero_impl(PhaseGVN& gvn, ciInlineKlass* vk, GrowableArray& visited); + static InlineTypeNode* make_from_oop_impl(GraphKit* kit, Node* oop, ciInlineKlass* vk, GrowableArray& visited); + static InlineTypeNode* make_null_impl(PhaseGVN& gvn, ciInlineKlass* vk, GrowableArray& visited, bool transform = true); + static InlineTypeNode* make_from_flat_impl(GraphKit* kit, ciInlineKlass* vk, Node* base, Node* ptr, bool atomic, bool immutable_memory, + bool null_free, bool trust_null_free_oop, DecoratorSet decorators, GrowableArray& visited); + + void convert_from_payload(GraphKit* kit, BasicType bt, Node* payload, int holder_offset, bool null_free, bool trust_null_free_oop); + Node* convert_to_payload(GraphKit* kit, BasicType bt, Node* payload, int holder_offset, bool null_free, int null_marker_offset, int& oop_off_1, int& oop_off_2) const; + +public: + // Create with all-zero field values + static InlineTypeNode* make_all_zero(PhaseGVN& gvn, ciInlineKlass* vk); + // Create uninitialized + static InlineTypeNode* make_uninitialized(PhaseGVN& gvn, ciInlineKlass* vk, bool null_free = true); + // Create and initialize by loading the field values from an oop + static InlineTypeNode* make_from_oop(GraphKit* kit, Node* oop, ciInlineKlass* vk); + // Create and initialize by loading the field values from a flat field or array + static InlineTypeNode* make_from_flat(GraphKit* kit, ciInlineKlass* vk, Node* base, Node* ptr, + bool atomic, bool immutable_memory, bool null_free, DecoratorSet decorators); + static InlineTypeNode* make_from_flat_array(GraphKit* kit, ciInlineKlass* vk, Node* base, Node* idx); + // Create and initialize with the inputs or outputs of a MultiNode (method entry or call) + static InlineTypeNode* make_from_multi(GraphKit* kit, MultiNode* multi, ciInlineKlass* vk, uint& base_input, bool in, bool null_free = true); + // Create with null field values + static InlineTypeNode* make_null(PhaseGVN& gvn, ciInlineKlass* vk, bool transform = true); + + // Support for control flow merges + bool has_phi_inputs(Node* region); + InlineTypeNode* clone_with_phis(PhaseGVN* gvn, Node* region, SafePointNode* map = nullptr, bool is_non_null = false, bool init_with_top = false); + InlineTypeNode* merge_with(PhaseGVN* gvn, const InlineTypeNode* other, int pnum, bool transform); + void add_new_path(Node* region); + + // Get oop for heap allocated inline type (may be TypePtr::NULL_PTR) + Node* get_oop() const { return in(Oop); } + void set_oop(PhaseGVN& gvn, Node* oop) { set_req_X(Oop, oop, &gvn); } + Node* get_null_marker() const { return in(NullMarker); } + void set_null_marker(PhaseGVN& gvn, Node* init) { set_req_X(NullMarker, init, &gvn); } + void set_null_marker(PhaseGVN& gvn) { set_null_marker(gvn, gvn.intcon(1)); } + Node* get_is_buffered() const { return in(IsBuffered); } + void set_is_buffered(PhaseGVN& gvn, bool buffered = true) { set_req_X(IsBuffered, gvn.intcon(buffered ? 1 : 0), &gvn); } + + // Checks if the inline type fields are all set to zero + bool is_all_zero(PhaseGVN* gvn, bool flat = false) const; + + // Inline type fields + uint field_count() const { return req() - Values; } + Node* field_value(uint index) const; + Node* field_value_by_offset(int offset, bool recursive) const; + void set_field_value(uint index, Node* value); + void set_field_value_by_offset(int offset, Node* value); + int field_offset(uint index) const; + uint field_index(int offset) const; + ciType* field_type(uint index) const; + bool field_is_flat(uint index) const; + bool field_is_null_free(uint index) const; + bool field_is_volatile(uint index) const; + int field_null_marker_offset(uint index) const; + + // Replace InlineTypeNodes in debug info at safepoints with SafePointScalarObjectNodes + void make_scalar_in_safepoints(PhaseIterGVN* igvn, bool allow_oop = true); + + // Store the inline type as a flat (headerless) representation + void store_flat(GraphKit* kit, Node* base, Node* ptr, bool atomic, bool immutable_memory, bool null_free, DecoratorSet decorators) const; + // Store the inline type as a flat (headerless) representation into an array + void store_flat_array(GraphKit* kit, Node* base, Node* idx) const; + // Make sure that inline type is fully scalarized + InlineTypeNode* adjust_scalarization_depth(GraphKit* kit); + + // Allocates the inline type (if not yet allocated) + InlineTypeNode* buffer(GraphKit* kit, bool safe_for_replace = true); + bool is_allocated(PhaseGVN* phase) const; + + void replace_call_results(GraphKit* kit, CallNode* call, Compile* C); + void replace_field_projs(Compile* C, CallNode* call, uint& proj_idx); + + // Allocate all non-flat inline type fields + Node* allocate_fields(GraphKit* kit); + + Node* tagged_klass(PhaseGVN& gvn) { + return tagged_klass(inline_klass(), gvn); + } + static Node* tagged_klass(ciInlineKlass* vk, PhaseGVN& gvn); + // Pass inline type as fields at a call or return + void pass_fields(GraphKit* kit, Node* n, uint& base_input, bool in, bool null_free = true); + + // Allocation optimizations + void remove_redundant_allocations(PhaseIdealLoop* phase); + + InlineTypeNode* clone_if_required(PhaseGVN* gvn, SafePointNode* map, bool safe_for_replace = true); + + virtual const Type* Value(PhaseGVN* phase) const; + + virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); + + virtual int Opcode() const; +}; + +#endif // SHARE_VM_OPTO_INLINETYPENODE_HPP diff --git a/src/hotspot/share/opto/lcm.cpp b/src/hotspot/share/opto/lcm.cpp index 66ef4b23e2d..4e4d9574961 100644 --- a/src/hotspot/share/opto/lcm.cpp +++ b/src/hotspot/share/opto/lcm.cpp @@ -226,6 +226,7 @@ void PhaseCFG::implicit_null_check(Block* block, Node *proj, Node *val, int allo case Op_StoreF: case Op_StoreI: case Op_StoreL: + case Op_StoreLSpecial: case Op_StoreP: case Op_StoreN: case Op_StoreNKlass: @@ -315,9 +316,9 @@ void PhaseCFG::implicit_null_check(Block* block, Node *proj, Node *val, int allo tptr = base->bottom_type()->is_ptr(); } // Give up if offset is not a compile-time constant. - if (offset == Type::OffsetBot || tptr->_offset == Type::OffsetBot) + if (offset == Type::OffsetBot || tptr->offset() == Type::OffsetBot) continue; - offset += tptr->_offset; // correct if base is offsetted + offset += tptr->offset(); // correct if base is offsetted // Give up if reference is beyond page size. if (MacroAssembler::needs_explicit_null_check(offset)) continue; @@ -362,7 +363,11 @@ void PhaseCFG::implicit_null_check(Block* block, Node *proj, Node *val, int allo continue; } // Block of memory-op input - Block *inb = get_block_for_node(mach->in(j)); + Block* inb = get_block_for_node(mach->in(j)); + if (mach->in(j)->is_Con() && mach->in(j)->req() == 1 && inb == get_block_for_node(mach)) { + // Ignore constant loads scheduled in the same block (we can simply hoist them as well) + continue; + } Block *b = block; // Start from nul check while( b != inb && b->_dom_depth > inb->_dom_depth ) b = b->_idom; // search upwards for input @@ -435,6 +440,27 @@ void PhaseCFG::implicit_null_check(Block* block, Node *proj, Node *val, int allo } } + // Hoist constant load inputs as well. + for (uint i = 1; i < best->req(); ++i) { + Node* n = best->in(i); + if (n->is_Con() && get_block_for_node(n) == get_block_for_node(best)) { + get_block_for_node(n)->find_remove(n); + block->add_inst(n); + map_node_to_block(n, block); + // Constant loads may kill flags (for example, when XORing a register). + // Check for flag-killing projections that also need to be hoisted. + for (DUIterator_Fast jmax, j = n->fast_outs(jmax); j < jmax; j++) { + Node* proj = n->fast_out(j); + if (proj->is_MachProj()) { + get_block_for_node(proj)->find_remove(proj); + block->add_inst(proj); + map_node_to_block(proj, block); + } + } + } + } + + // Move any MachTemp inputs to the end of the test block. for (uint i = 0; i < best->req(); i++) { Node* n = best->in(i); @@ -743,6 +769,7 @@ void PhaseCFG::adjust_register_pressure(Node* n, Block* block, intptr_t* recalc_ case Op_StoreF: case Op_StoreI: case Op_StoreL: + case Op_StoreLSpecial: case Op_StoreP: case Op_StoreN: case Op_StoreVector: @@ -909,7 +936,7 @@ uint PhaseCFG::sched_call(Block* block, uint node_cnt, Node_List& worklist, Grow regs.Insert(_matcher.c_frame_pointer()); // Set all registers killed and not already defined by the call. - uint r_cnt = mcall->tf()->range()->cnt(); + uint r_cnt = mcall->tf()->range_cc()->cnt(); int op = mcall->ideal_Opcode(); MachProjNode *proj = new MachProjNode( mcall, r_cnt+1, RegMask::Empty, MachProjNode::fat_proj ); map_node_to_block(proj, block); diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index da04d6b01ac..d3ba9fc7bff 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -23,15 +23,21 @@ */ #include "asm/macroAssembler.hpp" +#include "ci/ciArrayKlass.hpp" +#include "ci/ciFlatArrayKlass.hpp" +#include "ci/ciInstanceKlass.hpp" #include "ci/ciSymbols.hpp" #include "ci/ciUtilities.inline.hpp" #include "classfile/vmIntrinsics.hpp" #include "compiler/compileBroker.hpp" #include "compiler/compileLog.hpp" #include "gc/shared/barrierSet.hpp" +#include "gc/shared/c2/barrierSetC2.hpp" #include "jfr/support/jfrIntrinsics.hpp" #include "memory/resourceArea.hpp" +#include "oops/accessDecorators.hpp" #include "oops/klass.inline.hpp" +#include "oops/layoutKind.hpp" #include "oops/objArrayKlass.hpp" #include "opto/addnode.hpp" #include "opto/arraycopynode.hpp" @@ -40,16 +46,20 @@ #include "opto/cfgnode.hpp" #include "opto/convertnode.hpp" #include "opto/countbitsnode.hpp" +#include "opto/graphKit.hpp" #include "opto/idealKit.hpp" +#include "opto/inlinetypenode.hpp" #include "opto/library_call.hpp" #include "opto/mathexactnode.hpp" #include "opto/mulnode.hpp" #include "opto/narrowptrnode.hpp" #include "opto/opaquenode.hpp" +#include "opto/opcodes.hpp" #include "opto/parse.hpp" #include "opto/rootnode.hpp" #include "opto/runtime.hpp" #include "opto/subnode.hpp" +#include "opto/type.hpp" #include "opto/vectornode.hpp" #include "prims/jvmtiExport.hpp" #include "prims/jvmtiThreadState.hpp" @@ -58,6 +68,7 @@ #include "runtime/objectMonitor.hpp" #include "runtime/sharedRuntime.hpp" #include "runtime/stubRoutines.hpp" +#include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" #include "utilities/powerOfTwo.hpp" @@ -318,6 +329,8 @@ bool LibraryCallKit::try_to_inline(int predicate) { case vmIntrinsics::_inflateStringC: case vmIntrinsics::_inflateStringB: return inline_string_copy(!is_compress); + case vmIntrinsics::_makePrivateBuffer: return inline_unsafe_make_private_buffer(); + case vmIntrinsics::_finishPrivateBuffer: return inline_unsafe_finish_private_buffer(); case vmIntrinsics::_getReference: return inline_unsafe_access(!is_store, T_OBJECT, Relaxed, false); case vmIntrinsics::_getBoolean: return inline_unsafe_access(!is_store, T_BOOLEAN, Relaxed, false); case vmIntrinsics::_getByte: return inline_unsafe_access(!is_store, T_BYTE, Relaxed, false); @@ -327,6 +340,7 @@ bool LibraryCallKit::try_to_inline(int predicate) { case vmIntrinsics::_getLong: return inline_unsafe_access(!is_store, T_LONG, Relaxed, false); case vmIntrinsics::_getFloat: return inline_unsafe_access(!is_store, T_FLOAT, Relaxed, false); case vmIntrinsics::_getDouble: return inline_unsafe_access(!is_store, T_DOUBLE, Relaxed, false); + case vmIntrinsics::_getValue: return inline_unsafe_access(!is_store, T_OBJECT, Relaxed, false, true); case vmIntrinsics::_putReference: return inline_unsafe_access( is_store, T_OBJECT, Relaxed, false); case vmIntrinsics::_putBoolean: return inline_unsafe_access( is_store, T_BOOLEAN, Relaxed, false); @@ -337,6 +351,7 @@ bool LibraryCallKit::try_to_inline(int predicate) { case vmIntrinsics::_putLong: return inline_unsafe_access( is_store, T_LONG, Relaxed, false); case vmIntrinsics::_putFloat: return inline_unsafe_access( is_store, T_FLOAT, Relaxed, false); case vmIntrinsics::_putDouble: return inline_unsafe_access( is_store, T_DOUBLE, Relaxed, false); + case vmIntrinsics::_putValue: return inline_unsafe_access( is_store, T_OBJECT, Relaxed, false, true); case vmIntrinsics::_getReferenceVolatile: return inline_unsafe_access(!is_store, T_OBJECT, Volatile, false); case vmIntrinsics::_getBooleanVolatile: return inline_unsafe_access(!is_store, T_BOOLEAN, Volatile, false); @@ -408,6 +423,9 @@ bool LibraryCallKit::try_to_inline(int predicate) { case vmIntrinsics::_putFloatOpaque: return inline_unsafe_access( is_store, T_FLOAT, Opaque, false); case vmIntrinsics::_putDoubleOpaque: return inline_unsafe_access( is_store, T_DOUBLE, Opaque, false); + case vmIntrinsics::_getFlatValue: return inline_unsafe_flat_access(!is_store, Relaxed); + case vmIntrinsics::_putFlatValue: return inline_unsafe_flat_access( is_store, Relaxed); + case vmIntrinsics::_compareAndSetReference: return inline_unsafe_load_store(T_OBJECT, LS_cmp_swap, Volatile); case vmIntrinsics::_compareAndSetByte: return inline_unsafe_load_store(T_BYTE, LS_cmp_swap, Volatile); case vmIntrinsics::_compareAndSetShort: return inline_unsafe_load_store(T_SHORT, LS_cmp_swap, Volatile); @@ -515,6 +533,9 @@ bool LibraryCallKit::try_to_inline(int predicate) { case vmIntrinsics::_allocateUninitializedArray: return inline_unsafe_newArray(true); case vmIntrinsics::_newArray: return inline_unsafe_newArray(false); + case vmIntrinsics::_newNullRestrictedNonAtomicArray: return inline_newArray(/* null_free */ true, /* atomic */ false); + case vmIntrinsics::_newNullRestrictedAtomicArray: return inline_newArray(/* null_free */ true, /* atomic */ true); + case vmIntrinsics::_newNullableAtomicArray: return inline_newArray(/* null_free */ false, /* atomic */ true); case vmIntrinsics::_isAssignableFrom: return inline_native_subtype_check(); @@ -2332,19 +2353,22 @@ bool LibraryCallKit::inline_divmod_methods(vmIntrinsics::ID id) { const TypeOopPtr* LibraryCallKit::sharpen_unsafe_type(Compile::AliasType* alias_type, const TypePtr *adr_type) { // Attempt to infer a sharper value type from the offset and base type. ciKlass* sharpened_klass = nullptr; + bool null_free = false; // See if it is an instance field, with an object type. if (alias_type->field() != nullptr) { if (alias_type->field()->type()->is_klass()) { sharpened_klass = alias_type->field()->type()->as_klass(); + null_free = alias_type->field()->is_null_free(); } } const TypeOopPtr* result = nullptr; // See if it is a narrow oop array. if (adr_type->isa_aryptr()) { - if (adr_type->offset() >= objArrayOopDesc::base_offset_in_bytes()) { + if (adr_type->offset() >= refArrayOopDesc::base_offset_in_bytes()) { const TypeOopPtr* elem_type = adr_type->is_aryptr()->elem()->make_oopptr(); + null_free = adr_type->is_aryptr()->is_null_free(); if (elem_type != nullptr && elem_type->is_loaded()) { // Sharpen the value type. result = elem_type; @@ -2357,6 +2381,9 @@ const TypeOopPtr* LibraryCallKit::sharpen_unsafe_type(Compile::AliasType* alias_ if (result == nullptr && sharpened_klass != nullptr && sharpened_klass->is_loaded()) { // Sharpen the value type. result = TypeOopPtr::make_from_klass(sharpened_klass); + if (null_free) { + result = result->join_speculative(TypePtr::NOTNULL)->is_oopptr(); + } } if (result != nullptr) { #ifndef PRODUCT @@ -2428,7 +2455,7 @@ void LibraryCallKit::SavedState::discard() { _discarded = true; } -bool LibraryCallKit::inline_unsafe_access(bool is_store, const BasicType type, const AccessKind kind, const bool unaligned) { +bool LibraryCallKit::inline_unsafe_access(bool is_store, const BasicType type, const AccessKind kind, const bool unaligned, const bool is_flat) { if (callee()->is_static()) return false; // caller must have the capability! DecoratorSet decorators = C2_UNSAFE_ACCESS; guarantee(!is_store || kind != Acquire, "Acquire accesses can be produced only for loads"); @@ -2453,13 +2480,13 @@ bool LibraryCallKit::inline_unsafe_access(bool is_store, const BasicType type, c // Object getReference(Object base, int/long offset), etc. BasicType rtype = sig->return_type()->basic_type(); assert(rtype == type, "getter must return the expected value"); - assert(sig->count() == 2, "oop getter has 2 arguments"); + assert(sig->count() == 2 || (is_flat && sig->count() == 3), "oop getter has 2 or 3 arguments"); assert(sig->type_at(0)->basic_type() == T_OBJECT, "getter base is object"); assert(sig->type_at(1)->basic_type() == T_LONG, "getter offset is correct"); } else { // void putReference(Object base, int/long offset, Object x), etc. assert(sig->return_type()->basic_type() == T_VOID, "putter must not return a value"); - assert(sig->count() == 3, "oop putter has 3 arguments"); + assert(sig->count() == 3 || (is_flat && sig->count() == 4), "oop putter has 3 arguments"); assert(sig->type_at(0)->basic_type() == T_OBJECT, "putter base is object"); assert(sig->type_at(1)->basic_type() == T_LONG, "putter offset is correct"); BasicType vtype = sig->type_at(sig->count()-1)->basic_type(); @@ -2485,6 +2512,55 @@ bool LibraryCallKit::inline_unsafe_access(bool is_store, const BasicType type, c // by oopDesc::field_addr. assert(Unsafe_field_offset_to_byte_offset(11) == 11, "fieldOffset must be byte-scaled"); + + ciInlineKlass* inline_klass = nullptr; + if (is_flat) { + const TypeInstPtr* cls = _gvn.type(argument(4))->isa_instptr(); + if (cls == nullptr || cls->const_oop() == nullptr) { + return false; + } + ciType* mirror_type = cls->const_oop()->as_instance()->java_mirror_type(); + if (!mirror_type->is_inlinetype()) { + return false; + } + inline_klass = mirror_type->as_inline_klass(); + } + + if (base->is_InlineType()) { + assert(!is_store, "InlineTypeNodes are non-larval value objects"); + InlineTypeNode* vt = base->as_InlineType(); + if (offset->is_Con()) { + long off = find_long_con(offset, 0); + ciInlineKlass* vk = vt->type()->inline_klass(); + if ((long)(int)off != off || !vk->contains_field_offset(off)) { + return false; + } + + ciField* field = vk->get_non_flat_field_by_offset(off); + if (field != nullptr) { + BasicType bt = type2field[field->type()->basic_type()]; + if (bt == T_ARRAY || bt == T_NARROWOOP) { + bt = T_OBJECT; + } + if (bt == type && (!field->is_flat() || field->type() == inline_klass)) { + Node* value = vt->field_value_by_offset(off, false); + if (value->is_InlineType()) { + value = value->as_InlineType()->adjust_scalarization_depth(this); + } + set_result(value); + return true; + } + } + } + { + // Re-execute the unsafe access if allocation triggers deoptimization. + PreserveReexecuteState preexecs(this); + jvms()->set_should_reexecute(true); + vt = vt->buffer(this); + } + base = vt->get_oop(); + } + // 32-bit machines ignore the high half! offset = ConvL2X(offset); @@ -2495,7 +2571,7 @@ bool LibraryCallKit::inline_unsafe_access(bool is_store, const BasicType type, c assert(!stopped(), "Inlining of unsafe access failed: address construction stopped unexpectedly"); if (_gvn.type(base->uncast())->isa_ptr() == TypePtr::NULL_PTR) { - if (type != T_OBJECT) { + if (type != T_OBJECT && (inline_klass == nullptr || !inline_klass->has_object_fields())) { decorators |= IN_NATIVE; // off-heap primitive access } else { return false; // off-heap oop accesses are not supported @@ -2511,7 +2587,7 @@ bool LibraryCallKit::inline_unsafe_access(bool is_store, const BasicType type, c decorators |= IN_HEAP; } - Node* val = is_store ? argument(4) : nullptr; + Node* val = is_store ? argument(4 + (is_flat ? 1 : 0)) : nullptr; const TypePtr* adr_type = _gvn.type(adr)->isa_ptr(); if (adr_type == TypePtr::NULL_PTR) { @@ -2528,7 +2604,35 @@ bool LibraryCallKit::inline_unsafe_access(bool is_store, const BasicType type, c } bool mismatched = false; - BasicType bt = alias_type->basic_type(); + BasicType bt = T_ILLEGAL; + ciField* field = nullptr; + if (adr_type->isa_instptr()) { + const TypeInstPtr* instptr = adr_type->is_instptr(); + ciInstanceKlass* k = instptr->instance_klass(); + int off = instptr->offset(); + if (instptr->const_oop() != nullptr && + k == ciEnv::current()->Class_klass() && + instptr->offset() >= (k->size_helper() * wordSize)) { + k = instptr->const_oop()->as_instance()->java_lang_Class_klass()->as_instance_klass(); + field = k->get_field_by_offset(off, true); + } else { + field = k->get_non_flat_field_by_offset(off); + } + if (field != nullptr) { + bt = type2field[field->type()->basic_type()]; + } + if (bt != alias_type->basic_type()) { + // Type mismatch. Is it an access to a nested flat field? + field = k->get_field_by_offset(off, false); + if (field != nullptr) { + bt = type2field[field->type()->basic_type()]; + } + } + assert(bt == alias_type->basic_type() || is_flat, "should match"); + } else { + bt = alias_type->basic_type(); + } + if (bt != T_ILLEGAL) { assert(alias_type->adr_type()->is_oopptr(), "should be on-heap access"); if (bt == T_BYTE && adr_type->isa_aryptr()) { @@ -2549,6 +2653,27 @@ bool LibraryCallKit::inline_unsafe_access(bool is_store, const BasicType type, c mismatched = true; // conservatively mark all "wide" on-heap accesses as mismatched } + if (is_flat) { + if (adr_type->isa_instptr()) { + if (field == nullptr || field->type() != inline_klass) { + mismatched = true; + } + } else if (adr_type->isa_aryptr()) { + const Type* elem = adr_type->is_aryptr()->elem(); + if (!adr_type->is_flat() || elem->inline_klass() != inline_klass) { + mismatched = true; + } + } else { + mismatched = true; + } + if (is_store) { + const Type* val_t = _gvn.type(val); + if (!val_t->is_inlinetypeptr() || val_t->inline_klass() != inline_klass) { + return false; + } + } + } + old_state.discard(); assert(!mismatched || alias_type->adr_type()->is_oopptr(), "off-heap access can't be mismatched"); @@ -2562,10 +2687,12 @@ bool LibraryCallKit::inline_unsafe_access(bool is_store, const BasicType type, c // Figure out the memory ordering. decorators |= mo_decorator_for_access_kind(kind); - if (!is_store && type == T_OBJECT) { - const TypeOopPtr* tjp = sharpen_unsafe_type(alias_type, adr_type); - if (tjp != nullptr) { - value_type = tjp; + if (!is_store) { + if (type == T_OBJECT && !is_flat) { + const TypeOopPtr* tjp = sharpen_unsafe_type(alias_type, adr_type); + if (tjp != nullptr) { + value_type = tjp; + } } } @@ -2581,14 +2708,23 @@ bool LibraryCallKit::inline_unsafe_access(bool is_store, const BasicType type, c if (!is_store) { Node* p = nullptr; // Try to constant fold a load from a constant field - ciField* field = alias_type->field(); - if (heap_base_oop != top() && field != nullptr && field->is_constant() && !mismatched) { + + if (heap_base_oop != top() && field != nullptr && field->is_constant() && !field->is_flat() && !mismatched) { // final or stable field p = make_constant_from_field(field, heap_base_oop); } if (p == nullptr) { // Could not constant fold the load - p = access_load_at(heap_base_oop, adr, adr_type, value_type, type, decorators); + if (is_flat) { + p = InlineTypeNode::make_from_flat(this, inline_klass, base, adr, adr_type, false, false, true); + } else { + p = access_load_at(heap_base_oop, adr, adr_type, value_type, type, decorators); + const TypeOopPtr* ptr = value_type->make_oopptr(); + if (ptr != nullptr && ptr->is_inlinetypeptr()) { + // Load a non-flattened inline type from memory + p = InlineTypeNode::make_from_oop(this, p, ptr->inline_klass()); + } + } // Normalize the value returned by getBoolean in the following cases if (type == T_BOOLEAN && (mismatched || @@ -2626,9 +2762,248 @@ bool LibraryCallKit::inline_unsafe_access(bool is_store, const BasicType type, c val = ConvL2X(val); val = gvn().transform(new CastX2PNode(val)); } - access_store_at(heap_base_oop, adr, adr_type, val, value_type, type, decorators); + if (is_flat) { + val->as_InlineType()->store_flat(this, base, adr, false, false, true, decorators); + } else { + access_store_at(heap_base_oop, adr, adr_type, val, value_type, type, decorators); + } + } + + return true; +} + +bool LibraryCallKit::inline_unsafe_flat_access(bool is_store, AccessKind kind) { +#ifdef ASSERT + { + ResourceMark rm; + // Check the signatures. + ciSignature* sig = callee()->signature(); + assert(sig->type_at(0)->basic_type() == T_OBJECT, "base should be object, but is %s", type2name(sig->type_at(0)->basic_type())); + assert(sig->type_at(1)->basic_type() == T_LONG, "offset should be long, but is %s", type2name(sig->type_at(1)->basic_type())); + assert(sig->type_at(2)->basic_type() == T_INT, "layout kind should be int, but is %s", type2name(sig->type_at(3)->basic_type())); + assert(sig->type_at(3)->basic_type() == T_OBJECT, "value klass should be object, but is %s", type2name(sig->type_at(4)->basic_type())); + if (is_store) { + assert(sig->return_type()->basic_type() == T_VOID, "putter must not return a value, but returns %s", type2name(sig->return_type()->basic_type())); + assert(sig->count() == 5, "flat putter should have 5 arguments, but has %d", sig->count()); + assert(sig->type_at(4)->basic_type() == T_OBJECT, "put value should be object, but is %s", type2name(sig->type_at(5)->basic_type())); + } else { + assert(sig->return_type()->basic_type() == T_OBJECT, "getter must return an object, but returns %s", type2name(sig->return_type()->basic_type())); + assert(sig->count() == 4, "flat getter should have 4 arguments, but has %d", sig->count()); + } + } +#endif // ASSERT + + assert(kind == Relaxed, "Only plain accesses for now"); + if (callee()->is_static()) { + // caller must have the capability! + return false; + } + C->set_has_unsafe_access(true); + + const TypeInstPtr* value_klass_node = _gvn.type(argument(5))->isa_instptr(); + if (value_klass_node == nullptr || value_klass_node->const_oop() == nullptr) { + // parameter valueType is not a constant + return false; + } + ciType* mirror_type = value_klass_node->const_oop()->as_instance()->java_mirror_type(); + if (!mirror_type->is_inlinetype()) { + // Dead code + return false; + } + ciInlineKlass* value_klass = mirror_type->as_inline_klass(); + + const TypeInt* layout_type = _gvn.type(argument(4))->isa_int(); + if (layout_type == nullptr || !layout_type->is_con()) { + // parameter layoutKind is not a constant + return false; + } + assert(layout_type->get_con() >= static_cast(LayoutKind::REFERENCE) && + layout_type->get_con() <= static_cast(LayoutKind::UNKNOWN), + "invalid layoutKind %d", layout_type->get_con()); + LayoutKind layout = static_cast(layout_type->get_con()); + assert(layout == LayoutKind::REFERENCE || layout == LayoutKind::NON_ATOMIC_FLAT || + layout == LayoutKind::ATOMIC_FLAT || layout == LayoutKind::NULLABLE_ATOMIC_FLAT, + "unexpected layoutKind %d", layout_type->get_con()); + + null_check(argument(0)); + if (stopped()) { + return true; + } + + Node* base = must_be_not_null(argument(1), true); + Node* offset = argument(2); + const Type* base_type = _gvn.type(base); + + Node* ptr; + bool immutable_memory = false; + DecoratorSet decorators = C2_UNSAFE_ACCESS | IN_HEAP | MO_UNORDERED; + if (base_type->isa_instptr()) { + const TypeLong* offset_type = _gvn.type(offset)->isa_long(); + if (offset_type == nullptr || !offset_type->is_con()) { + // Offset into a non-array should be a constant + decorators |= C2_MISMATCHED; + } else { + int offset_con = checked_cast(offset_type->get_con()); + ciInstanceKlass* base_klass = base_type->is_instptr()->instance_klass(); + ciField* field = base_klass->get_non_flat_field_by_offset(offset_con); + if (field == nullptr) { + assert(!base_klass->is_final(), "non-existence field at offset %d of class %s", offset_con, base_klass->name()->as_utf8()); + decorators |= C2_MISMATCHED; + } else { + assert(field->type() == value_klass, "field at offset %d of %s is of type %s, but valueType is %s", + offset_con, base_klass->name()->as_utf8(), field->type()->name(), value_klass->name()->as_utf8()); + immutable_memory = field->is_strict() && field->is_final(); + + if (base->is_InlineType()) { + assert(!is_store, "Cannot store into a non-larval value object"); + set_result(base->as_InlineType()->field_value_by_offset(offset_con, false)); + return true; + } + } + } + + if (base->is_InlineType()) { + assert(!is_store, "Cannot store into a non-larval value object"); + base = base->as_InlineType()->buffer(this, true); + } + ptr = basic_plus_adr(base, ConvL2X(offset)); + } else if (base_type->isa_aryptr()) { + decorators |= IS_ARRAY; + if (layout == LayoutKind::REFERENCE) { + if (!base_type->is_aryptr()->is_not_flat()) { + const TypeAryPtr* array_type = base_type->is_aryptr()->cast_to_not_flat(); + Node* new_base = _gvn.transform(new CastPPNode(control(), base, array_type, ConstraintCastNode::StrongDependency)); + replace_in_map(base, new_base); + base = new_base; + } + ptr = basic_plus_adr(base, ConvL2X(offset)); + } else { + if (UseArrayFlattening) { + // Flat array must have an exact type + bool is_null_free = layout != LayoutKind::NULLABLE_ATOMIC_FLAT; + bool is_atomic = layout != LayoutKind::NON_ATOMIC_FLAT; + Node* new_base = cast_to_flat_array(base, value_klass, is_null_free, !is_null_free, is_atomic); + replace_in_map(base, new_base); + base = new_base; + ptr = basic_plus_adr(base, ConvL2X(offset)); + const TypeAryPtr* ptr_type = _gvn.type(ptr)->is_aryptr(); + if (ptr_type->field_offset().get() != 0) { + ptr = _gvn.transform(new CastPPNode(control(), ptr, ptr_type->with_field_offset(0), ConstraintCastNode::StrongDependency)); + } + } else { + uncommon_trap(Deoptimization::Reason_intrinsic, + Deoptimization::Action_none); + return true; + } + } + } else { + decorators |= C2_MISMATCHED; + ptr = basic_plus_adr(base, ConvL2X(offset)); + } + + if (is_store) { + Node* value = argument(6); + const Type* value_type = _gvn.type(value); + if (!value_type->is_inlinetypeptr()) { + value_type = Type::get_const_type(value_klass)->filter_speculative(value_type); + Node* new_value = _gvn.transform(new CastPPNode(control(), value, value_type, ConstraintCastNode::StrongDependency)); + new_value = InlineTypeNode::make_from_oop(this, new_value, value_klass); + replace_in_map(value, new_value); + value = new_value; + } + + assert(value_type->inline_klass() == value_klass, "value is of type %s while valueType is %s", value_type->inline_klass()->name()->as_utf8(), value_klass->name()->as_utf8()); + if (layout == LayoutKind::REFERENCE) { + const TypePtr* ptr_type = (decorators & C2_MISMATCHED) != 0 ? TypeRawPtr::BOTTOM : _gvn.type(ptr)->is_ptr(); + access_store_at(base, ptr, ptr_type, value, value_type, T_OBJECT, decorators); + } else { + bool atomic = layout != LayoutKind::NON_ATOMIC_FLAT; + bool null_free = layout != LayoutKind::NULLABLE_ATOMIC_FLAT; + value->as_InlineType()->store_flat(this, base, ptr, atomic, immutable_memory, null_free, decorators); + } + + return true; + } else { + decorators |= (C2_CONTROL_DEPENDENT_LOAD | C2_UNKNOWN_CONTROL_LOAD); + InlineTypeNode* result; + if (layout == LayoutKind::REFERENCE) { + const TypePtr* ptr_type = (decorators & C2_MISMATCHED) != 0 ? TypeRawPtr::BOTTOM : _gvn.type(ptr)->is_ptr(); + Node* oop = access_load_at(base, ptr, ptr_type, Type::get_const_type(value_klass), T_OBJECT, decorators); + result = InlineTypeNode::make_from_oop(this, oop, value_klass); + } else { + bool atomic = layout != LayoutKind::NON_ATOMIC_FLAT; + bool null_free = layout != LayoutKind::NULLABLE_ATOMIC_FLAT; + result = InlineTypeNode::make_from_flat(this, value_klass, base, ptr, atomic, immutable_memory, null_free, decorators); + } + + set_result(result); + return true; + } +} + +bool LibraryCallKit::inline_unsafe_make_private_buffer() { + Node* receiver = argument(0); + Node* value = argument(1); + + const Type* type = gvn().type(value); + if (!type->is_inlinetypeptr()) { + C->record_method_not_compilable("value passed to Unsafe::makePrivateBuffer is not of a constant value type"); + return false; + } + + null_check(receiver); + if (stopped()) { + return true; + } + + value = null_check(value); + if (stopped()) { + return true; + } + + ciInlineKlass* vk = type->inline_klass(); + Node* klass = makecon(TypeKlassPtr::make(vk)); + Node* obj = new_instance(klass); + AllocateNode::Ideal_allocation(obj)->_larval = true; + + assert(value->is_InlineType(), "must be an InlineTypeNode"); + Node* payload_ptr = basic_plus_adr(obj, vk->payload_offset()); + value->as_InlineType()->store_flat(this, obj, payload_ptr, false, true, true, IN_HEAP | MO_UNORDERED); + + set_result(obj); + return true; +} + +bool LibraryCallKit::inline_unsafe_finish_private_buffer() { + Node* receiver = argument(0); + Node* buffer = argument(1); + + const Type* type = gvn().type(buffer); + if (!type->is_inlinetypeptr()) { + C->record_method_not_compilable("value passed to Unsafe::finishPrivateBuffer is not of a constant value type"); + return false; } + AllocateNode* alloc = AllocateNode::Ideal_allocation(buffer); + if (alloc == nullptr) { + C->record_method_not_compilable("value passed to Unsafe::finishPrivateBuffer must be allocated by Unsafe::makePrivateBuffer"); + return false; + } + + null_check(receiver); + if (stopped()) { + return true; + } + + // Unset the larval bit in the object header + Node* old_header = make_load(control(), buffer, TypeX_X, TypeX_X->basic_type(), MemNode::unordered, LoadNode::Pinned); + Node* new_header = gvn().transform(new AndXNode(old_header, MakeConX(~markWord::larval_bit_in_place))); + access_store_at(buffer, buffer, type->is_ptr(), new_header, TypeX_X, TypeX_X->basic_type(), MO_UNORDERED | IN_HEAP); + + // We must ensure that the buffer is properly published + insert_mem_bar(Op_MemBarStoreStore, alloc->proj_out(AllocateNode::RawAddress)); + assert(!type->maybe_null(), "result of an allocation should not be null"); + set_result(InlineTypeNode::make_from_oop(this, buffer, type->inline_klass())); return true; } @@ -2834,6 +3209,19 @@ bool LibraryCallKit::inline_unsafe_load_store(const BasicType type, const LoadSt if (is_reference_type(type)) { decorators |= IN_HEAP | ON_UNKNOWN_OOP_REF; + if (oldval != nullptr && oldval->is_InlineType()) { + // Re-execute the unsafe access if allocation triggers deoptimization. + PreserveReexecuteState preexecs(this); + jvms()->set_should_reexecute(true); + oldval = oldval->as_InlineType()->buffer(this)->get_oop(); + } + if (newval != nullptr && newval->is_InlineType()) { + // Re-execute the unsafe access if allocation triggers deoptimization. + PreserveReexecuteState preexecs(this); + jvms()->set_should_reexecute(true); + newval = newval->as_InlineType()->buffer(this)->get_oop(); + } + // Transformation of a value which could be null pointer (CastPP #null) // could be delayed during Parse (for example, in adjust_map_after_if()). // Execute transformation here to avoid barrier generation in such case. @@ -3020,8 +3408,13 @@ bool LibraryCallKit::inline_unsafe_allocate() { test = _gvn.transform(new SubINode(inst, bits)); // The 'test' is non-zero if we need to take a slow path. } - - Node* obj = new_instance(kls, test); + Node* obj = nullptr; + const TypeInstKlassPtr* tkls = _gvn.type(kls)->isa_instklassptr(); + if (tkls != nullptr && tkls->instance_klass()->is_inlinetype()) { + obj = InlineTypeNode::make_all_zero(_gvn, tkls->instance_klass()->as_inline_klass())->buffer(this); + } else { + obj = new_instance(kls, test); + } set_result(obj); return true; } @@ -3802,12 +4195,12 @@ bool LibraryCallKit::inline_native_setCurrentThread() { const Type* LibraryCallKit::scopedValueCache_type() { ciKlass* objects_klass = ciObjArrayKlass::make(env()->Object_klass()); const TypeOopPtr* etype = TypeOopPtr::make_from_klass(env()->Object_klass()); - const TypeAry* arr0 = TypeAry::make(etype, TypeInt::POS); + const TypeAry* arr0 = TypeAry::make(etype, TypeInt::POS, /* stable= */ false, /* flat= */ false, /* not_flat= */ true, /* not_null_free= */ true); // Because we create the scopedValue cache lazily we have to make the // type of the result BotPTR. bool xk = etype->klass_is_exact(); - const Type* objects_type = TypeAryPtr::make(TypePtr::BotPTR, arr0, objects_klass, xk, 0); + const Type* objects_type = TypeAryPtr::make(TypePtr::BotPTR, arr0, objects_klass, xk, TypeAryPtr::Offset(0)); return objects_type; } @@ -3931,15 +4324,6 @@ bool LibraryCallKit::inline_native_Continuation_pinning(bool unpin) { return true; } -//---------------------------load_mirror_from_klass---------------------------- -// Given a klass oop, load its java mirror (a java.lang.Class oop). -Node* LibraryCallKit::load_mirror_from_klass(Node* klass) { - Node* p = basic_plus_adr(klass, in_bytes(Klass::java_mirror_offset())); - Node* load = make_load(nullptr, p, TypeRawPtr::NOTNULL, T_ADDRESS, MemNode::unordered); - // mirror = ((OopHandle)mirror)->resolve(); - return access_load(load, TypeInstPtr::MIRROR, T_OBJECT, IN_NATIVE); -} - //-----------------------load_klass_from_mirror_common------------------------- // Given a java mirror (a java.lang.Class oop), load its corresponding klass oop. // Test the klass oop for null (signifying a primitive Class like Integer.TYPE), @@ -3983,6 +4367,7 @@ Node* LibraryCallKit::generate_klass_flags_guard(Node* kls, int modifier_mask, i Node* bol = _gvn.transform(new BoolNode(cmp, BoolTest::ne)); return generate_fair_guard(bol, region); } + Node* LibraryCallKit::generate_interface_guard(Node* kls, RegionNode* region) { return generate_klass_flags_guard(kls, JVM_ACC_INTERFACE, 0, region, Klass::access_flags_offset(), TypeInt::CHAR, T_CHAR); @@ -4132,6 +4517,7 @@ bool LibraryCallKit::inline_native_Class_query(vmIntrinsics::ID id) { return true; } + //-------------------------inline_Class_cast------------------- bool LibraryCallKit::inline_Class_cast() { Node* mirror = argument(0); // Class @@ -4154,7 +4540,8 @@ bool LibraryCallKit::inline_Class_cast() { // Don't use intrinsic when class is not loaded. return false; } else { - int static_res = C->static_subtype_check(TypeKlassPtr::make(tm->as_klass(), Type::trust_interfaces), tp->as_klass_type()); + const TypeKlassPtr* tklass = TypeKlassPtr::make(tm->as_klass(), Type::trust_interfaces); + int static_res = C->static_subtype_check(tklass, tp->as_klass_type()); if (static_res == Compile::SSC_always_true) { // isInstance() is true - fold the code. set_result(obj); @@ -4183,8 +4570,8 @@ bool LibraryCallKit::inline_Class_cast() { return true; } - // Not-subtype or the mirror's klass ptr is null (in case it is a primitive). - enum { _bad_type_path = 1, _prim_path = 2, PATH_LIMIT }; + // Not-subtype or the mirror's klass ptr is nullptr (in case it is a primitive). + enum { _bad_type_path = 1, _prim_path = 2, _npe_path = 3, PATH_LIMIT }; RegionNode* region = new RegionNode(PATH_LIMIT); record_for_igvn(region); @@ -4194,17 +4581,24 @@ bool LibraryCallKit::inline_Class_cast() { Node* kls = load_klass_from_mirror(mirror, false, region, _prim_path); Node* res = top(); + Node* io = i_o(); + Node* mem = merged_memory(); if (!stopped()) { + Node* bad_type_ctrl = top(); // Do checkcast optimizations. res = gen_checkcast(obj, kls, &bad_type_ctrl); region->init_req(_bad_type_path, bad_type_ctrl); } if (region->in(_prim_path) != top() || - region->in(_bad_type_path) != top()) { + region->in(_bad_type_path) != top() || + region->in(_npe_path) != top()) { // Let Interpreter throw ClassCastException. PreserveJVMState pjvms(this); set_control(_gvn.transform(region)); + // Set IO and memory because gen_checkcast may override them when buffering inline types + set_i_o(io); + set_all_memory(mem); uncommon_trap(Deoptimization::Reason_intrinsic, Deoptimization::Action_maybe_recompile); } @@ -4238,8 +4632,10 @@ bool LibraryCallKit::inline_native_subtype_check() { }; RegionNode* region = new RegionNode(PATH_LIMIT); + RegionNode* prim_region = new RegionNode(2); Node* phi = new PhiNode(region, TypeInt::BOOL); record_for_igvn(region); + record_for_igvn(prim_region); const TypePtr* adr_type = TypeRawPtr::BOTTOM; // memory type of loads const TypeKlassPtr* kls_type = TypeInstKlassPtr::OBJECT_OR_NULL; @@ -4264,8 +4660,11 @@ bool LibraryCallKit::inline_native_subtype_check() { Node* kls = klasses[which_arg]; Node* null_ctl = top(); kls = null_check_oop(kls, &null_ctl, never_see_null); - int prim_path = (which_arg == 0 ? _prim_0_path : _prim_1_path); - region->init_req(prim_path, null_ctl); + if (which_arg == 0) { + prim_region->init_req(1, null_ctl); + } else { + region->init_req(_prim_1_path, null_ctl); + } if (stopped()) break; klasses[which_arg] = kls; } @@ -4275,19 +4674,19 @@ bool LibraryCallKit::inline_native_subtype_check() { Node* subk = klasses[1]; // the argument to isAssignableFrom Node* superk = klasses[0]; // the receiver region->set_req(_both_ref_path, gen_subtype_check(subk, superk)); - // now we have a successful reference subtype check region->set_req(_ref_subtype_path, control()); } // If both operands are primitive (both klasses null), then // we must return true when they are identical primitives. // It is convenient to test this after the first null klass check. - set_control(region->in(_prim_0_path)); // go back to first null check + // This path is also used if superc is a value mirror. + set_control(_gvn.transform(prim_region)); if (!stopped()) { // Since superc is primitive, make a guard for the superc==subc case. Node* cmp_eq = _gvn.transform(new CmpPNode(args[0], args[1])); Node* bol_eq = _gvn.transform(new BoolNode(cmp_eq, BoolTest::eq)); - generate_guard(bol_eq, region, PROB_FAIR); + generate_fair_guard(bol_eq, region); if (region->req() == PATH_LIMIT+1) { // A guard was added. If the added guard is taken, superc==subc. region->swap_edges(PATH_LIMIT, _prim_same_path); @@ -4318,30 +4717,27 @@ bool LibraryCallKit::inline_native_subtype_check() { } //---------------------generate_array_guard_common------------------------ -Node* LibraryCallKit::generate_array_guard_common(Node* kls, RegionNode* region, - bool obj_array, bool not_array, Node** obj) { +Node* LibraryCallKit::generate_array_guard_common(Node* kls, RegionNode* region, ArrayKind kind, Node** obj) { if (stopped()) { return nullptr; } - // If obj_array/non_array==false/false: - // Branch around if the given klass is in fact an array (either obj or prim). - // If obj_array/non_array==false/true: - // Branch around if the given klass is not an array klass of any kind. - // If obj_array/non_array==true/true: - // Branch around if the kls is not an oop array (kls is int[], String, etc.) - // If obj_array/non_array==true/false: - // Branch around if the kls is an oop array (Object[] or subtype) - // // Like generate_guard, adds a new path onto the region. jint layout_con = 0; Node* layout_val = get_layout_helper(kls, layout_con); if (layout_val == nullptr) { - bool query = (obj_array - ? Klass::layout_helper_is_objArray(layout_con) - : Klass::layout_helper_is_array(layout_con)); - if (query == not_array) { + bool query = 0; + switch(kind) { + case RefArray: query = Klass::layout_helper_is_refArray(layout_con); break; + case NonRefArray: query = !Klass::layout_helper_is_refArray(layout_con); break; + case TypeArray: query = Klass::layout_helper_is_typeArray(layout_con); break; + case AnyArray: query = Klass::layout_helper_is_array(layout_con); break; + case NonArray: query = !Klass::layout_helper_is_array(layout_con); break; + default: + ShouldNotReachHere(); + } + if (!query) { return nullptr; // never a branch } else { // always a branch Node* always_branch = control(); @@ -4351,18 +4747,33 @@ Node* LibraryCallKit::generate_array_guard_common(Node* kls, RegionNode* region, return always_branch; } } + unsigned int value = 0; + BoolTest::mask btest = BoolTest::illegal; + switch(kind) { + case RefArray: + case NonRefArray: { + value = Klass::_lh_array_tag_ref_value; + layout_val = _gvn.transform(new RShiftINode(layout_val, intcon(Klass::_lh_array_tag_shift))); + btest = (kind == RefArray) ? BoolTest::eq : BoolTest::ne; + break; + } + case TypeArray: { + value = Klass::_lh_array_tag_type_value; + layout_val = _gvn.transform(new RShiftINode(layout_val, intcon(Klass::_lh_array_tag_shift))); + btest = BoolTest::eq; + break; + } + case AnyArray: value = Klass::_lh_neutral_value; btest = BoolTest::lt; break; + case NonArray: value = Klass::_lh_neutral_value; btest = BoolTest::gt; break; + default: + ShouldNotReachHere(); + } // Now test the correct condition. - jint nval = (obj_array - ? (jint)(Klass::_lh_array_tag_type_value - << Klass::_lh_array_tag_shift) - : Klass::_lh_neutral_value); + jint nval = (jint)value; Node* cmp = _gvn.transform(new CmpINode(layout_val, intcon(nval))); - BoolTest::mask btest = BoolTest::lt; // correct for testing is_[obj]array - // invert the test if we are looking for a non-array - if (not_array) btest = BoolTest(btest).negate(); Node* bol = _gvn.transform(new BoolNode(cmp, btest)); Node* ctrl = generate_fair_guard(bol, region); - Node* is_array_ctrl = not_array ? control() : ctrl; + Node* is_array_ctrl = kind == NonArray ? control() : ctrl; if (obj != nullptr && is_array_ctrl != nullptr && is_array_ctrl != top()) { // Keep track of the fact that 'obj' is an array to prevent // array specific accesses from floating above the guard. @@ -4371,9 +4782,125 @@ Node* LibraryCallKit::generate_array_guard_common(Node* kls, RegionNode* region, return ctrl; } +// public static native Object[] newNullRestrictedAtomicArray(Class componentType, int length, Object initVal); +// public static native Object[] newNullRestrictedNonAtomicArray(Class componentType, int length, Object initVal); +// public static native Object[] newNullableAtomicArray(Class componentType, int length); +bool LibraryCallKit::inline_newArray(bool null_free, bool atomic) { + assert(null_free || atomic, "nullable implies atomic"); + Node* componentType = argument(0); + Node* length = argument(1); + Node* init_val = null_free ? argument(2) : nullptr; + + const TypeInstPtr* tp = _gvn.type(componentType)->isa_instptr(); + if (tp != nullptr) { + ciInstanceKlass* ik = tp->instance_klass(); + if (ik == C->env()->Class_klass()) { + ciType* t = tp->java_mirror_type(); + if (t != nullptr && t->is_inlinetype()) { + + ciArrayKlass* array_klass = ciArrayKlass::make(t, null_free, atomic, true); + assert(array_klass->is_elem_null_free() == null_free, "inconsistency"); + assert(array_klass->is_elem_atomic() == atomic, "inconsistency"); + + // TOOD 8350865 ZGC needs card marks on initializing oop stores + if (UseZGC && null_free && !array_klass->is_flat_array_klass()) { + return false; + } + + if (array_klass->is_loaded() && array_klass->element_klass()->as_inline_klass()->is_initialized()) { + const TypeAryKlassPtr* array_klass_type = TypeAryKlassPtr::make(array_klass, Type::trust_interfaces, true); + if (null_free) { + if (init_val->is_InlineType()) { + if (array_klass_type->is_flat() && init_val->as_InlineType()->is_all_zero(&gvn(), /* flat */ true)) { + // Zeroing is enough because the init value is the all-zero value + init_val = nullptr; + } else { + init_val = init_val->as_InlineType()->buffer(this); + } + } + // TODO 8350865 Should we add a check of the init_val type (maybe in debug only + halt)? + } + Node* obj = new_array(makecon(array_klass_type), length, 0, nullptr, false, init_val); + const TypeAryPtr* arytype = gvn().type(obj)->is_aryptr(); + assert(arytype->is_null_free() == null_free, "inconsistency"); + assert(arytype->is_not_null_free() == !null_free, "inconsistency"); + assert(arytype->is_atomic() == atomic, "inconsistency"); + set_result(obj); + return true; + } + } + } + } + return false; +} + +Node* LibraryCallKit::load_default_array_klass(Node* klass_node) { + // TODO 8366668 + // - Fred suggested that we could just have the first entry in the refined list point to the array with ArrayKlass::ArrayProperties::DEFAULT property + // For now, we just load from ObjArrayKlass::_next_refined_array_klass, which would always be the refKlass for non-values, and deopt if it's not + // - Convert this to an IGVN optimization, so it's also folded after parsing + // - The generate_typeArray_guard is not needed by all callers, double-check that it's folded + + const Type* klass_t = _gvn.type(klass_node); + const TypeAryKlassPtr* ary_klass_t = klass_t->isa_aryklassptr(); + if (ary_klass_t && ary_klass_t->klass_is_exact()) { + if (ary_klass_t->exact_klass()->is_obj_array_klass()) { + ary_klass_t = ary_klass_t->get_vm_type(false); + return makecon(ary_klass_t); + } else { + return klass_node; + } + } + + // Load next refined array klass if klass is an ObjArrayKlass + RegionNode* refined_region = new RegionNode(2); + Node* refined_phi = new PhiNode(refined_region, klass_t); + + generate_typeArray_guard(klass_node, refined_region); + if (refined_region->req() == 3) { + refined_phi->add_req(klass_node); + } + + Node* adr_refined_klass = basic_plus_adr(klass_node, in_bytes(ObjArrayKlass::next_refined_array_klass_offset())); + Node* refined_klass = _gvn.transform(LoadKlassNode::make(_gvn, immutable_memory(), adr_refined_klass, TypeRawPtr::BOTTOM, TypeInstKlassPtr::OBJECT_OR_NULL)); + + RegionNode* refined_region2 = new RegionNode(3); + Node* refined_phi2 = new PhiNode(refined_region2, klass_t); + + Node* null_ctl = top(); + Node* null_free_klass = null_check_common(refined_klass, T_OBJECT, false, &null_ctl); + refined_region2->init_req(1, null_ctl); + refined_phi2->init_req(1, klass_node); + + refined_region2->init_req(2, control()); + refined_phi2->init_req(2, null_free_klass); + + set_control(_gvn.transform(refined_region2)); + refined_klass = _gvn.transform(refined_phi2); + + Node* adr_properties = basic_plus_adr(refined_klass, in_bytes(ObjArrayKlass::properties_offset())); + + Node* properties = _gvn.transform(LoadNode::make(_gvn, control(), immutable_memory(), adr_properties, TypeRawPtr::BOTTOM, TypeInt::INT, T_INT, MemNode::unordered)); + Node* default_val = makecon(TypeInt::make(ArrayKlass::ArrayProperties::DEFAULT)); + Node* chk = _gvn.transform(new CmpINode(properties, default_val)); + Node* tst = _gvn.transform(new BoolNode(chk, BoolTest::eq)); + + { // Deoptimize if not the default property + BuildCutout unless(this, tst, PROB_MAX); + uncommon_trap_exact(Deoptimization::Reason_class_check, Deoptimization::Action_none); + } + + refined_region->init_req(1, control()); + refined_phi->init_req(1, refined_klass); + + set_control(_gvn.transform(refined_region)); + klass_node = _gvn.transform(refined_phi); + + return klass_node; +} //-----------------------inline_native_newArray-------------------------- -// private static native Object java.lang.reflect.newArray(Class componentType, int length); +// private static native Object java.lang.reflect.Array.newArray(Class componentType, int length); // private native Object Unsafe.allocateUninitializedArray0(Class cls, int size); bool LibraryCallKit::inline_unsafe_newArray(bool uninitialized) { Node* mirror; @@ -4431,6 +4958,9 @@ bool LibraryCallKit::inline_unsafe_newArray(bool uninitialized) { // Normal case: The array type has been cached in the java.lang.Class. // The following call works fine even if the array type is polymorphic. // It could be a dynamic mix of int[], boolean[], Object[], etc. + + klass_node = load_default_array_klass(klass_node); + Node* obj = new_array(klass_node, count_val, 0); // no arguments to push result_reg->init_req(_normal_path, control()); result_val->init_req(_normal_path, obj); @@ -4519,11 +5049,27 @@ bool LibraryCallKit::inline_array_copyOf(bool is_copyOfRange) { // Despite the generic type of Arrays.copyOf, the mirror might be int, int[], etc. // Bail out if that is so. - Node* not_objArray = generate_non_objArray_guard(klass_node, bailout); + // Inline type array may have object field that would require a + // write barrier. Conservatively, go to slow path. + // TODO 8251971: Optimize for the case when flat src/dst are later found + // to not contain oops (i.e., move this check to the macro expansion phase). + BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); + const TypeAryPtr* orig_t = _gvn.type(original)->isa_aryptr(); + const TypeKlassPtr* tklass = _gvn.type(klass_node)->is_klassptr(); + bool exclude_flat = UseArrayFlattening && bs->array_copy_requires_gc_barriers(true, T_OBJECT, false, false, BarrierSetC2::Parsing) && + // Can src array be flat and contain oops? + (orig_t == nullptr || (!orig_t->is_not_flat() && (!orig_t->is_flat() || orig_t->elem()->inline_klass()->contains_oops()))) && + // Can dest array be flat and contain oops? + tklass->can_be_inline_array() && (!tklass->is_flat() || tklass->is_aryklassptr()->elem()->is_instklassptr()->instance_klass()->as_inline_klass()->contains_oops()); + // TODO 8366668 generate_non_refArray_guard also passed for ref arrays?? + Node* not_objArray = exclude_flat ? generate_non_refArray_guard(klass_node, bailout) : generate_typeArray_guard(klass_node, bailout); + + klass_node = load_default_array_klass(klass_node); + if (not_objArray != nullptr) { // Improve the klass node's type from the new optimistic assumption: ciKlass* ak = ciArrayKlass::make(env()->Object_klass()); - const Type* akls = TypeKlassPtr::make(TypePtr::NotNull, ak, 0/*offset*/); + const Type* akls = TypeKlassPtr::make(TypePtr::NotNull, ak, Type::Offset(0)); Node* cast = new CastPPNode(control(), klass_node, akls); klass_node = _gvn.transform(cast); } @@ -4543,6 +5089,45 @@ bool LibraryCallKit::inline_array_copyOf(bool is_copyOfRange) { // should be thrown generate_negative_guard(length, bailout, &length); + // Handle inline type arrays + bool can_validate = !too_many_traps(Deoptimization::Reason_class_check); + if (!stopped()) { + // TODO JDK-8329224 + if (!orig_t->is_null_free()) { + // Not statically known to be null free, add a check + generate_fair_guard(null_free_array_test(original), bailout); + } + orig_t = _gvn.type(original)->isa_aryptr(); + if (orig_t != nullptr && orig_t->is_flat()) { + // Src is flat, check that dest is flat as well + if (exclude_flat) { + // Dest can't be flat, bail out + bailout->add_req(control()); + set_control(top()); + } else { + generate_fair_guard(flat_array_test(klass_node, /* flat = */ false), bailout); + } + // TODO 8350865 This is not correct anymore. Write tests and fix logic similar to arraycopy. + } else if (UseArrayFlattening && (orig_t == nullptr || !orig_t->is_not_flat()) && + // If dest is flat, src must be flat as well (guaranteed by src <: dest check if validated). + ((!tklass->is_flat() && tklass->can_be_inline_array()) || !can_validate)) { + // Src might be flat and dest might not be flat. Go to the slow path if src is flat. + // TODO 8251971: Optimize for the case when src/dest are later found to be both flat. + generate_fair_guard(flat_array_test(load_object_klass(original)), bailout); + if (orig_t != nullptr) { + orig_t = orig_t->cast_to_not_flat(); + original = _gvn.transform(new CheckCastPPNode(control(), original, orig_t)); + } + } + if (!can_validate) { + // No validation. The subtype check emitted at macro expansion time will not go to the slow + // path but call checkcast_arraycopy which can not handle flat/null-free inline type arrays. + // TODO 8251971: Optimize for the case when src/dest are later found to be both flat/null-free. + generate_fair_guard(flat_array_test(klass_node), bailout); + generate_fair_guard(null_free_array_test(original), bailout); + } + } + // Bail out if start is larger than the original length Node* orig_tail = _gvn.transform(new SubINode(orig_length, start)); generate_negative_guard(orig_tail, bailout, &orig_tail); @@ -4588,7 +5173,7 @@ bool LibraryCallKit::inline_array_copyOf(bool is_copyOfRange) { bool validated = false; // Reason_class_check rather than Reason_intrinsic because we // want to intrinsify even if this traps. - if (!too_many_traps(Deoptimization::Reason_class_check)) { + if (can_validate) { Node* not_subtype_ctrl = gen_subtype_check(original, klass_node); if (not_subtype_ctrl != top()) { @@ -4674,11 +5259,11 @@ LibraryCallKit::generate_method_call(vmIntrinsicID method_id, bool is_virtual, b const TypeFunc* tf = TypeFunc::make(method); if (res_not_null) { assert(tf->return_type() == T_OBJECT, ""); - const TypeTuple* range = tf->range(); + const TypeTuple* range = tf->range_cc(); const Type** fields = TypeTuple::fields(range->cnt()); fields[TypeFunc::Parms] = range->field_at(TypeFunc::Parms)->filter_speculative(TypePtr::NOTNULL); const TypeTuple* new_range = TypeTuple::make(range->cnt(), fields); - tf = TypeFunc::make(tf->domain(), new_range); + tf = TypeFunc::make(tf->domain_cc(), new_range); } CallJavaNode* slow_call; if (is_static) { @@ -4735,7 +5320,14 @@ bool LibraryCallKit::inline_native_hashcode(bool is_virtual, bool is_static) { PhiNode* result_val = new PhiNode(result_reg, TypeInt::INT); PhiNode* result_io = new PhiNode(result_reg, Type::ABIO); PhiNode* result_mem = new PhiNode(result_reg, Type::MEMORY, TypePtr::BOTTOM); - Node* obj = nullptr; + Node* obj = argument(0); + + // Don't intrinsify hashcode on inline types for now. + // The "is locked" runtime check below also serves as inline type check and goes to the slow path. + if (gvn().type(obj)->is_inlinetypeptr()) { + return false; + } + if (!is_static) { // Check for hashing null object obj = null_check_receiver(); @@ -4745,7 +5337,6 @@ bool LibraryCallKit::inline_native_hashcode(bool is_virtual, bool is_static) { } else { // Do a null check, and return zero if null. // System.identityHashCode(null) == 0 - obj = argument(0); Node* null_ctl = top(); obj = null_check_oop(obj, &null_ctl); result_reg->init_req(_null_path, null_ctl); @@ -4786,7 +5377,8 @@ bool LibraryCallKit::inline_native_hashcode(bool is_virtual, bool is_static) { if (!UseObjectMonitorTable) { // Test the header to see if it is safe to read w.r.t. locking. - Node *lock_mask = _gvn.MakeConX(markWord::lock_mask_in_place); + // This also serves as guard against inline types + Node *lock_mask = _gvn.MakeConX(markWord::inline_type_mask_in_place); Node *lmasked_header = _gvn.transform(new AndXNode(header, lock_mask)); if (LockingMode == LM_LIGHTWEIGHT) { Node *monitor_val = _gvn.MakeConX(markWord::monitor_value); @@ -4861,7 +5453,16 @@ bool LibraryCallKit::inline_native_hashcode(bool is_virtual, bool is_static) { // // Build special case code for calls to getClass on an object. bool LibraryCallKit::inline_native_getClass() { - Node* obj = null_check_receiver(); + Node* obj = argument(0); + if (obj->is_InlineType()) { + const Type* t = _gvn.type(obj); + if (t->maybe_null()) { + null_check(obj); + } + set_result(makecon(TypeInstPtr::make(t->inline_klass()->java_mirror()))); + return true; + } + obj = null_check_receiver(); if (stopped()) return true; set_result(load_mirror_from_klass(load_object_klass(obj))); return true; @@ -5283,17 +5884,25 @@ bool LibraryCallKit::inline_native_clone(bool is_virtual) { { PreserveReexecuteState preexecs(this); jvms()->set_should_reexecute(true); - Node* obj = null_check_receiver(); + Node* obj = argument(0); + obj = null_check_receiver(); if (stopped()) return true; const TypeOopPtr* obj_type = _gvn.type(obj)->is_oopptr(); + if (obj_type->is_inlinetypeptr()) { + // If the object to clone is an inline type, we can simply return it (i.e. a nop) since inline types have + // no identity. + set_result(obj); + return true; + } // If we are going to clone an instance, we need its exact type to // know the number and types of fields to convert the clone to // loads/stores. Maybe a speculative type can help us. if (!obj_type->klass_is_exact() && obj_type->speculative_type() != nullptr && - obj_type->speculative_type()->is_instance_klass()) { + obj_type->speculative_type()->is_instance_klass() && + !obj_type->speculative_type()->is_inlinetype()) { ciInstanceKlass* spec_ik = obj_type->speculative_type()->as_instance_klass(); if (spec_ik->nof_nonstatic_fields() <= ArrayCopyLoadStoreMaxElem && !spec_ik->has_injected_fields()) { @@ -5323,61 +5932,75 @@ bool LibraryCallKit::inline_native_clone(bool is_virtual) { record_for_igvn(result_reg); Node* obj_klass = load_object_klass(obj); + // We only go to the fast case code if we pass a number of guards. + // The paths which do not pass are accumulated in the slow_region. + RegionNode* slow_region = new RegionNode(1); + record_for_igvn(slow_region); + Node* array_obj = obj; Node* array_ctl = generate_array_guard(obj_klass, (RegionNode*)nullptr, &array_obj); if (array_ctl != nullptr) { // It's an array. PreserveJVMState pjvms(this); set_control(array_ctl); - Node* obj_length = load_array_length(array_obj); - Node* array_size = nullptr; // Size of the array without object alignment padding. - Node* alloc_obj = new_array(obj_klass, obj_length, 0, &array_size, /*deoptimize_on_exception=*/true); BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); - if (bs->array_copy_requires_gc_barriers(true, T_OBJECT, true, false, BarrierSetC2::Parsing)) { - // If it is an oop array, it requires very special treatment, - // because gc barriers are required when accessing the array. - Node* is_obja = generate_objArray_guard(obj_klass, (RegionNode*)nullptr); - if (is_obja != nullptr) { - PreserveJVMState pjvms2(this); - set_control(is_obja); - // Generate a direct call to the right arraycopy function(s). - // Clones are always tightly coupled. - ArrayCopyNode* ac = ArrayCopyNode::make(this, true, array_obj, intcon(0), alloc_obj, intcon(0), obj_length, true, false); - ac->set_clone_oop_array(); - Node* n = _gvn.transform(ac); - assert(n == ac, "cannot disappear"); - ac->connect_outputs(this, /*deoptimize_on_exception=*/true); - - result_reg->init_req(_objArray_path, control()); - result_val->init_req(_objArray_path, alloc_obj); - result_i_o ->set_req(_objArray_path, i_o()); - result_mem ->set_req(_objArray_path, reset_memory()); - } + const TypeAryPtr* ary_ptr = obj_type->isa_aryptr(); + if (UseArrayFlattening && bs->array_copy_requires_gc_barriers(true, T_OBJECT, true, false, BarrierSetC2::Expansion) && + obj_type->can_be_inline_array() && + (ary_ptr == nullptr || (!ary_ptr->is_not_flat() && (!ary_ptr->is_flat() || ary_ptr->elem()->inline_klass()->contains_oops())))) { + // Flat inline type array may have object field that would require a + // write barrier. Conservatively, go to slow path. + generate_fair_guard(flat_array_test(obj_klass), slow_region); } - // Otherwise, there are no barriers to worry about. - // (We can dispense with card marks if we know the allocation - // comes out of eden (TLAB)... In fact, ReduceInitialCardMarks - // causes the non-eden paths to take compensating steps to - // simulate a fresh allocation, so that no further - // card marks are required in compiled code to initialize - // the object.) if (!stopped()) { - copy_to_clone(array_obj, alloc_obj, array_size, true); - - // Present the results of the copy. - result_reg->init_req(_array_path, control()); - result_val->init_req(_array_path, alloc_obj); - result_i_o ->set_req(_array_path, i_o()); - result_mem ->set_req(_array_path, reset_memory()); + Node* obj_length = load_array_length(array_obj); + Node* array_size = nullptr; // Size of the array without object alignment padding. + Node* alloc_obj = new_array(obj_klass, obj_length, 0, &array_size, /*deoptimize_on_exception=*/true); + + BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); + if (bs->array_copy_requires_gc_barriers(true, T_OBJECT, true, false, BarrierSetC2::Parsing)) { + // If it is an oop array, it requires very special treatment, + // because gc barriers are required when accessing the array. + Node* is_obja = generate_refArray_guard(obj_klass, (RegionNode*)nullptr); + if (is_obja != nullptr) { + PreserveJVMState pjvms2(this); + set_control(is_obja); + // Generate a direct call to the right arraycopy function(s). + // Clones are always tightly coupled. + ArrayCopyNode* ac = ArrayCopyNode::make(this, true, array_obj, intcon(0), alloc_obj, intcon(0), obj_length, true, false); + ac->set_clone_oop_array(); + Node* n = _gvn.transform(ac); + assert(n == ac, "cannot disappear"); + ac->connect_outputs(this, /*deoptimize_on_exception=*/true); + + result_reg->init_req(_objArray_path, control()); + result_val->init_req(_objArray_path, alloc_obj); + result_i_o ->set_req(_objArray_path, i_o()); + result_mem ->set_req(_objArray_path, reset_memory()); + } + } + // Otherwise, there are no barriers to worry about. + // (We can dispense with card marks if we know the allocation + // comes out of eden (TLAB)... In fact, ReduceInitialCardMarks + // causes the non-eden paths to take compensating steps to + // simulate a fresh allocation, so that no further + // card marks are required in compiled code to initialize + // the object.) + + if (!stopped()) { + copy_to_clone(obj, alloc_obj, array_size, true); + + // Present the results of the copy. + result_reg->init_req(_array_path, control()); + result_val->init_req(_array_path, alloc_obj); + result_i_o ->set_req(_array_path, i_o()); + result_mem ->set_req(_array_path, reset_memory()); + } } } - // We only go to the instance fast case code if we pass a number of guards. - // The paths which do not pass are accumulated in the slow_region. - RegionNode* slow_region = new RegionNode(1); - record_for_igvn(slow_region); if (!stopped()) { // It's an instance (we did array above). Make the slow-path tests. // If this is a virtual call, we generate a funny guard. We grab @@ -5509,12 +6132,35 @@ SafePointNode* LibraryCallKit::create_safepoint_with_state_before_array_allocati for (uint i = 0; i < size; i++) { sfpt->init_req(i, alloc->in(i)); } + int adjustment = 1; + const TypeAryKlassPtr* ary_klass_ptr = alloc->in(AllocateNode::KlassNode)->bottom_type()->is_aryklassptr(); + if (ary_klass_ptr->is_null_free()) { + // A null-free, tightly coupled array allocation can only come from LibraryCallKit::inline_newArray which + // also requires the componentType and initVal on stack for re-execution. + // Re-create and push the componentType. + ciArrayKlass* klass = ary_klass_ptr->exact_klass()->as_array_klass(); + ciInstance* instance = klass->component_mirror_instance(); + const TypeInstPtr* t_instance = TypeInstPtr::make(instance); + sfpt->ins_req(old_jvms->stkoff() + old_jvms->sp(), makecon(t_instance)); + adjustment++; + } // re-push array length for deoptimization - sfpt->ins_req(old_jvms->stkoff() + old_jvms->sp(), alloc->in(AllocateNode::ALength)); - old_jvms->set_sp(old_jvms->sp()+1); - old_jvms->set_monoff(old_jvms->monoff()+1); - old_jvms->set_scloff(old_jvms->scloff()+1); - old_jvms->set_endoff(old_jvms->endoff()+1); + sfpt->ins_req(old_jvms->stkoff() + old_jvms->sp() + adjustment - 1, alloc->in(AllocateNode::ALength)); + if (ary_klass_ptr->is_null_free()) { + // Re-create and push the initVal. + Node* init_val = alloc->in(AllocateNode::InitValue); + if (init_val == nullptr) { + init_val = InlineTypeNode::make_all_zero(_gvn, ary_klass_ptr->elem()->is_instklassptr()->instance_klass()->as_inline_klass()); + } else if (UseCompressedOops) { + init_val = _gvn.transform(new DecodeNNode(init_val, init_val->bottom_type()->make_ptr())); + } + sfpt->ins_req(old_jvms->stkoff() + old_jvms->sp() + adjustment, init_val); + adjustment++; + } + old_jvms->set_sp(old_jvms->sp() + adjustment); + old_jvms->set_monoff(old_jvms->monoff() + adjustment); + old_jvms->set_scloff(old_jvms->scloff() + adjustment); + old_jvms->set_endoff(old_jvms->endoff() + adjustment); old_jvms->set_should_reexecute(true); sfpt->set_i_o(map()->i_o()); @@ -5547,11 +6193,10 @@ void LibraryCallKit::arraycopy_move_allocation_here(AllocateArrayNode* alloc, No _reexecute_sp = saved_reexecute_sp; // Remove the allocation from above the guards - CallProjections callprojs; - alloc->extract_projections(&callprojs, true); + CallProjections* callprojs = alloc->extract_projections(true); InitializeNode* init = alloc->initialization(); Node* alloc_mem = alloc->in(TypeFunc::Memory); - C->gvn_replace_by(callprojs.fallthrough_ioproj, alloc->in(TypeFunc::I_O)); + C->gvn_replace_by(callprojs->fallthrough_ioproj, alloc->in(TypeFunc::I_O)); C->gvn_replace_by(init->proj_out(TypeFunc::Memory), alloc_mem); // The CastIINode created in GraphKit::new_array (in AllocateArrayNode::make_ideal_length) must stay below @@ -5593,7 +6238,7 @@ void LibraryCallKit::arraycopy_move_allocation_here(AllocateArrayNode* alloc, No set_all_memory(mem); alloc->set_req(TypeFunc::Memory, mem); set_control(init->proj_out_or_null(TypeFunc::Control)); - set_i_o(callprojs.fallthrough_ioproj); + set_i_o(callprojs->fallthrough_ioproj); // Update memory as done in GraphKit::set_output_for_allocation() const TypeInt* length_type = _gvn.find_int_type(alloc->in(AllocateNode::ALength)); @@ -5931,7 +6576,7 @@ bool LibraryCallKit::inline_arraycopy() { if (is_reference_type(src_elem, true)) src_elem = T_OBJECT; if (is_reference_type(dest_elem, true)) dest_elem = T_OBJECT; - if (src_elem == dest_elem && src_elem == T_OBJECT) { + if (src_elem == dest_elem && top_src->is_flat() == top_dest->is_flat() && src_elem == T_OBJECT) { // If both arrays are object arrays then having the exact types // for both will remove the need for a subtype check at runtime // before the call and may make it possible to pick a faster copy @@ -5958,9 +6603,13 @@ bool LibraryCallKit::inline_arraycopy() { // If we can have both exact types, emit the missing guards if (could_have_src && !src_spec) { src = maybe_cast_profiled_obj(src, src_k, true); + src_type = _gvn.type(src); + top_src = src_type->isa_aryptr(); } if (could_have_dest && !dest_spec) { dest = maybe_cast_profiled_obj(dest, dest_k, true); + dest_type = _gvn.type(dest); + top_dest = dest_type->isa_aryptr(); } } } @@ -5976,8 +6625,7 @@ bool LibraryCallKit::inline_arraycopy() { bool negative_length_guard_generated = false; if (!C->too_many_traps(trap_method, trap_bci, Deoptimization::Reason_intrinsic) && - can_emit_guards && - !src->is_top() && !dest->is_top()) { + can_emit_guards && !src->is_top() && !dest->is_top()) { // validate arguments: enables transformation the ArrayCopyNode validated = true; @@ -6020,15 +6668,44 @@ bool LibraryCallKit::inline_arraycopy() { Node* dest_klass = load_object_klass(dest); if (src != dest) { Node* not_subtype_ctrl = gen_subtype_check(src, dest_klass); + slow_region->add_req(not_subtype_ctrl); + } - if (not_subtype_ctrl != top()) { - PreserveJVMState pjvms(this); - set_control(not_subtype_ctrl); - uncommon_trap(Deoptimization::Reason_intrinsic, - Deoptimization::Action_make_not_entrant); - assert(stopped(), "Should be stopped"); + // TODO 8350865 Fix below logic. Also handle atomicity. + generate_fair_guard(flat_array_test(src), slow_region); + generate_fair_guard(flat_array_test(dest), slow_region); + + const TypeKlassPtr* dest_klass_t = _gvn.type(dest_klass)->is_klassptr(); + const Type* toop = dest_klass_t->cast_to_exactness(false)->as_instance_type(); + src = _gvn.transform(new CheckCastPPNode(control(), src, toop)); + src_type = _gvn.type(src); + top_src = src_type->isa_aryptr(); + + // Handle flat inline type arrays (null-free arrays are handled by the subtype check above) + if (!stopped() && UseArrayFlattening) { + // If dest is flat, src must be flat as well (guaranteed by src <: dest check). Handle flat src here. + assert(top_dest == nullptr || !top_dest->is_flat() || top_src->is_flat(), "src array must be flat"); + if (top_src != nullptr && top_src->is_flat()) { + // Src is flat, check that dest is flat as well + if (top_dest != nullptr && !top_dest->is_flat()) { + generate_fair_guard(flat_array_test(dest_klass, /* flat = */ false), slow_region); + // Since dest is flat and src <: dest, dest must have the same type as src. + top_dest = top_src->cast_to_exactness(false); + assert(top_dest->is_flat(), "dest must be flat"); + dest = _gvn.transform(new CheckCastPPNode(control(), dest, top_dest)); + } + } else if (top_src == nullptr || !top_src->is_not_flat()) { + // Src might be flat and dest might not be flat. Go to the slow path if src is flat. + // TODO 8251971: Optimize for the case when src/dest are later found to be both flat. + assert(top_dest == nullptr || !top_dest->is_flat(), "dest array must not be flat"); + generate_fair_guard(flat_array_test(src), slow_region); + if (top_src != nullptr) { + top_src = top_src->cast_to_not_flat(); + src = _gvn.transform(new CheckCastPPNode(control(), src, top_src)); + } } } + { PreserveJVMState pjvms(this); set_control(_gvn.transform(slow_region)); @@ -6036,10 +6713,6 @@ bool LibraryCallKit::inline_arraycopy() { Deoptimization::Action_make_not_entrant); assert(stopped(), "Should be stopped"); } - - const TypeKlassPtr* dest_klass_t = _gvn.type(dest_klass)->is_klassptr(); - const Type *toop = dest_klass_t->cast_to_exactness(false)->as_instance_type(); - src = _gvn.transform(new CheckCastPPNode(control(), src, toop)); arraycopy_move_allocation_here(alloc, dest, saved_jvms_before_guards, saved_reexecute_sp, new_idx); } diff --git a/src/hotspot/share/opto/library_call.hpp b/src/hotspot/share/opto/library_call.hpp index fbac1363dae..19e37e50073 100644 --- a/src/hotspot/share/opto/library_call.hpp +++ b/src/hotspot/share/opto/library_call.hpp @@ -31,6 +31,7 @@ #include "opto/castnode.hpp" #include "opto/convertnode.hpp" #include "opto/graphKit.hpp" +#include "opto/inlinetypenode.hpp" #include "opto/intrinsicnode.hpp" #include "opto/movenode.hpp" @@ -108,13 +109,21 @@ class LibraryCallKit : public GraphKit { void push_result() { // Push the result onto the stack. - if (!stopped() && result() != nullptr) { - if (result()->is_top()) { + Node* res = result(); + if (!stopped() && res != nullptr) { + if (res->is_top()) { assert(false, "Can't determine return value."); C->record_method_not_compilable("Can't determine return value."); } - BasicType bt = result()->bottom_type()->basic_type(); - push_node(bt, result()); + BasicType bt = res->bottom_type()->basic_type(); + if (C->inlining_incrementally() && res->is_InlineType()) { + // The caller expects an oop when incrementally inlining an intrinsic that returns an + // inline type. Make sure the call is re-executed if the allocation triggers a deoptimization. + PreserveReexecuteState preexecs(this); + jvms()->set_should_reexecute(true); + res = res->as_InlineType()->buffer(this); + } + push_node(bt, res); } } @@ -169,7 +178,6 @@ class LibraryCallKit : public GraphKit { bool is_immutable); Node* generate_current_thread(Node* &tls_output); Node* generate_virtual_thread(Node* threadObj); - Node* load_mirror_from_klass(Node* klass); Node* load_klass_from_mirror_common(Node* mirror, bool never_see_null, RegionNode* region, int null_path, int offset); @@ -187,27 +195,41 @@ class LibraryCallKit : public GraphKit { region, null_path, offset); } + Node* load_default_array_klass(Node* klass_node); + Node* generate_klass_flags_guard(Node* kls, int modifier_mask, int modifier_bits, RegionNode* region, ByteSize offset, const Type* type, BasicType bt); Node* generate_misc_flags_guard(Node* kls, int modifier_mask, int modifier_bits, RegionNode* region); Node* generate_interface_guard(Node* kls, RegionNode* region); + + enum ArrayKind { + AnyArray, + NonArray, + RefArray, + NonRefArray, + TypeArray + }; + Node* generate_hidden_class_guard(Node* kls, RegionNode* region); + Node* generate_array_guard(Node* kls, RegionNode* region, Node** obj = nullptr) { - return generate_array_guard_common(kls, region, false, false, obj); + return generate_array_guard_common(kls, region, AnyArray, obj); } Node* generate_non_array_guard(Node* kls, RegionNode* region, Node** obj = nullptr) { - return generate_array_guard_common(kls, region, false, true, obj); + return generate_array_guard_common(kls, region, NonArray, obj); + } + Node* generate_refArray_guard(Node* kls, RegionNode* region, Node** obj = nullptr) { + return generate_array_guard_common(kls, region, RefArray, obj); } - Node* generate_objArray_guard(Node* kls, RegionNode* region, Node** obj = nullptr) { - return generate_array_guard_common(kls, region, true, false, obj); + Node* generate_non_refArray_guard(Node* kls, RegionNode* region, Node** obj = nullptr) { + return generate_array_guard_common(kls, region, NonRefArray, obj); } - Node* generate_non_objArray_guard(Node* kls, RegionNode* region, Node** obj = nullptr) { - return generate_array_guard_common(kls, region, true, true, obj); + Node* generate_typeArray_guard(Node* kls, RegionNode* region, Node** obj = nullptr) { + return generate_array_guard_common(kls, region, TypeArray, obj); } - Node* generate_array_guard_common(Node* kls, RegionNode* region, - bool obj_array, bool not_array, Node** obj = nullptr); + Node* generate_array_guard_common(Node* kls, RegionNode* region, ArrayKind kind, Node** obj = nullptr); Node* generate_virtual_guard(Node* obj_klass, RegionNode* slow_region); CallJavaNode* generate_method_call(vmIntrinsicID method_id, bool is_virtual, bool is_static, bool res_not_null); CallJavaNode* generate_method_call_static(vmIntrinsicID method_id, bool res_not_null) { @@ -255,13 +277,17 @@ class LibraryCallKit : public GraphKit { typedef enum { Relaxed, Opaque, Volatile, Acquire, Release } AccessKind; DecoratorSet mo_decorator_for_access_kind(AccessKind kind); - bool inline_unsafe_access(bool is_store, BasicType type, AccessKind kind, bool is_unaligned); + bool inline_unsafe_access(bool is_store, BasicType type, AccessKind kind, bool is_unaligned, bool is_flat = false); + bool inline_unsafe_flat_access(bool is_store, AccessKind kind); static bool klass_needs_init_guard(Node* kls); bool inline_unsafe_allocate(); bool inline_unsafe_newArray(bool uninitialized); + bool inline_newArray(bool null_free, bool atomic); bool inline_unsafe_writeback0(); bool inline_unsafe_writebackSync0(bool is_pre); bool inline_unsafe_copyMemory(); + bool inline_unsafe_make_private_buffer(); + bool inline_unsafe_finish_private_buffer(); bool inline_unsafe_setMemory(); bool inline_native_currentCarrierThread(); @@ -288,6 +314,7 @@ class LibraryCallKit : public GraphKit { void extend_setCurrentThread(Node* jt, Node* thread); #endif bool inline_native_Class_query(vmIntrinsics::ID id); + bool inline_primitive_Class_conversion(vmIntrinsics::ID id); bool inline_native_subtype_check(); bool inline_native_getLength(); bool inline_array_copyOf(bool is_copyOfRange); diff --git a/src/hotspot/share/opto/locknode.cpp b/src/hotspot/share/opto/locknode.cpp index 4587bfd4fd6..e152826751b 100644 --- a/src/hotspot/share/opto/locknode.cpp +++ b/src/hotspot/share/opto/locknode.cpp @@ -184,6 +184,18 @@ bool FastLockNode::cmp( const Node &n ) const { return (&n == this); // Always fail except on self } +const Type* FastLockNode::Value(PhaseGVN* phase) const { + const Type* in1_t = phase->type(in(1)); + if (in1_t == Type::TOP) { + return Type::TOP; + } + if (in1_t->is_inlinetypeptr()) { + // Locking on inline types always fails + return TypeInt::CC_GT; + } + return TypeInt::CC; +} + //============================================================================= //-----------------------------hash-------------------------------------------- uint FastUnlockNode::hash() const { return NO_HASH; } @@ -203,6 +215,12 @@ void Parse::do_monitor_enter() { // Check for locking null object if (stopped()) return; + { + // Synchronizing on an inline type is not allowed + BuildCutout unless(this, inline_type_test(obj, /* is_inline = */ false), PROB_MAX); + uncommon_trap_exact(Deoptimization::Reason_class_check, Deoptimization::Action_none); + } + // the monitor object is not part of debug info expression stack pop(); diff --git a/src/hotspot/share/opto/locknode.hpp b/src/hotspot/share/opto/locknode.hpp index 229dcb73292..d2dd7653bf1 100644 --- a/src/hotspot/share/opto/locknode.hpp +++ b/src/hotspot/share/opto/locknode.hpp @@ -93,7 +93,7 @@ class BoxLockNode : public Node { void set_local() { assert((_kind == Regular || _kind == Local || _kind == Coarsened), - "incorrect kind for Local transitioni: %s", _kind_name[(int)_kind]); + "incorrect kind for Local transition: %s", _kind_name[(int)_kind]); _kind = Local; } void set_nested() { @@ -145,7 +145,7 @@ class FastLockNode: public CmpNode { virtual uint size_of() const; virtual bool cmp( const Node &n ) const ; // Always fail, except on self virtual int Opcode() const; - virtual const Type* Value(PhaseGVN* phase) const { return TypeInt::CC; } + virtual const Type* Value(PhaseGVN* phase) const; const Type *sub(const Type *t1, const Type *t2) const { return TypeInt::CC;} }; diff --git a/src/hotspot/share/opto/loopUnswitch.cpp b/src/hotspot/share/opto/loopUnswitch.cpp index b40a0492df5..6687eea8873 100644 --- a/src/hotspot/share/opto/loopUnswitch.cpp +++ b/src/hotspot/share/opto/loopUnswitch.cpp @@ -125,7 +125,12 @@ bool IdealLoopTree::policy_unswitching(PhaseIdealLoop* phase) const { if (head->unswitch_count() + 1 > head->unswitch_max()) { return false; } - if (phase->find_unswitch_candidate(this) == nullptr) { + + if (head->is_flat_arrays()) { + return false; + } + + if (no_unswitch_candidate()) { return false; } @@ -133,9 +138,53 @@ bool IdealLoopTree::policy_unswitching(PhaseIdealLoop* phase) const { return phase->may_require_nodes(est_loop_clone_sz(2)); } +// Check the absence of any If node that can be used for Loop Unswitching. In that case, no Loop Unswitching can be done. +bool IdealLoopTree::no_unswitch_candidate() const { + ResourceMark rm; + Node_List dont_care; + return _phase->find_unswitch_candidates(this, dont_care) == nullptr; +} + // Find an invariant test in the loop body that does not exit the loop. If multiple tests are found, we pick the first -// one in the loop body. Return the "unswitch candidate" If to apply Loop Unswitching on. -IfNode* PhaseIdealLoop::find_unswitch_candidate(const IdealLoopTree* loop) const { +// one in the loop body as "unswitch candidate" to apply Loop Unswitching on. +// Depending on whether we find such a candidate and if we do, whether it's a flat array check, we do the following: +// (1) Candidate is not a flat array check: +// Return the unique unswitch candidate. +// (2) Candidate is a flat array check: +// Collect all remaining non-loop-exiting flat array checks in the loop body in the provided 'flat_array_checks' +// list in order to create an unswitched loop version without any flat array checks and a version with checks +// (i.e. same as original loop). Return the initially found candidate which could be unique if no further flat array +// checks are found. +// (3) No candidate is initially found: +// As in (2), we collect all non-loop-exiting flat array checks in the loop body in the provided 'flat_array_checks' +// list. Pick the first collected flat array check as unswitch candidate, which could be unique, and return it (a). +// If there are no flat array checks, we cannot apply Loop Unswitching (b). +// +// Note that for both (2) and (3a), if there are multiple flat array checks, then the candidate's FlatArrayCheckNode is +// later updated in Loop Unswitching to perform a flat array check on all collected flat array checks. +IfNode* PhaseIdealLoop::find_unswitch_candidates(const IdealLoopTree* loop, Node_List& flat_array_checks) const { + IfNode* unswitch_candidate = find_unswitch_candidate_from_idoms(loop); + if (unswitch_candidate != nullptr && !unswitch_candidate->is_flat_array_check(&_igvn)) { + // Case (1) + return unswitch_candidate; + } + + collect_flat_array_checks(loop, flat_array_checks); + if (unswitch_candidate != nullptr) { + // Case (2) + assert(unswitch_candidate->is_flat_array_check(&_igvn), "is a flat array check"); + return unswitch_candidate; + } else if (flat_array_checks.size() > 0) { + // Case (3a): Pick first one found as candidate (there could be multiple). + return flat_array_checks[0]->as_If(); + } + + // Case (3b): No suitable unswitch candidate found. + return nullptr; +} + +// Find an unswitch candidate by following the idom chain from the loop back edge. +IfNode* PhaseIdealLoop::find_unswitch_candidate_from_idoms(const IdealLoopTree* loop) const { LoopNode* head = loop->_head->as_Loop(); IfNode* unswitch_candidate = nullptr; Node* n = head->in(LoopNode::LoopBackControl); @@ -162,6 +211,150 @@ IfNode* PhaseIdealLoop::find_unswitch_candidate(const IdealLoopTree* loop) const return unswitch_candidate; } +// Collect all flat array checks in the provided 'flat_array_checks' list. +void PhaseIdealLoop::collect_flat_array_checks(const IdealLoopTree* loop, Node_List& flat_array_checks) const { + assert(flat_array_checks.size() == 0, "should be empty initially"); + for (uint i = 0; i < loop->_body.size(); i++) { + Node* next = loop->_body.at(i); + if (next->is_If() && next->as_If()->is_flat_array_check(&_igvn) && loop->is_invariant(next->in(1)) && + !loop->is_loop_exit(next)) { + flat_array_checks.push(next); + } + } +} + +// This class represents an "unswitch candidate" which is an If that can be used to perform Loop Unswitching on. If the +// candidate is a flat array check candidate, then we also collect all remaining non-loop-exiting flat array checks. +// These are candidates as well. We want to get rid of all these flat array checks in the true-path-loop for the +// following reason: +// +// FlatArrayCheckNodes are used with array accesses to switch between a flat and a non-flat array access. We want +// the performance impact on non-flat array accesses to be as small as possible. We therefore create the following +// loops in Loop Unswitching: +// - True-path-loop: We remove all non-loop-exiting flat array checks to get a loop with only non-flat array accesses +// (i.e. a fast path loop). +// - False-path-loop: We keep all flat array checks in this loop (i.e. a slow path loop). +class UnswitchCandidate : public StackObj { + PhaseIdealLoop* const _phase; + const Node_List& _old_new; + Node* const _original_loop_entry; + // If _candidate is a flat array check, this list contains all non-loop-exiting flat array checks in the loop body. + Node_List _flat_array_check_candidates; + IfNode* const _candidate; + + public: + UnswitchCandidate(IdealLoopTree* loop, const Node_List& old_new) + : _phase(loop->_phase), + _old_new(old_new), + _original_loop_entry(loop->_head->as_Loop()->skip_strip_mined()->in(LoopNode::EntryControl)), + _flat_array_check_candidates(), + _candidate(find_unswitch_candidate(loop)) {} + NONCOPYABLE(UnswitchCandidate); + + IfNode* find_unswitch_candidate(IdealLoopTree* loop) { + IfNode* unswitch_candidate = _phase->find_unswitch_candidates(loop, _flat_array_check_candidates); + assert(unswitch_candidate != nullptr, "guaranteed to exist by policy_unswitching"); + assert(_phase->is_member(loop, unswitch_candidate), "must be inside original loop"); + return unswitch_candidate; + } + + IfNode* candidate() const { + return _candidate; + } + + // Is the candidate a flat array check and are there other flat array checks as well? + bool has_multiple_flat_array_check_candidates() const { + return _flat_array_check_candidates.size() > 1; + } + + // Remove all candidates from the true-path-loop which are now dominated by the loop selector + // (i.e. 'true_path_loop_proj'). The removed candidates are folded in the next IGVN round. + void update_in_true_path_loop(IfTrueNode* true_path_loop_proj) const { + remove_from_loop(true_path_loop_proj, _candidate); + if (has_multiple_flat_array_check_candidates()) { + remove_flat_array_checks(true_path_loop_proj); + } + } + + // Remove a unique candidate from the false-path-loop which is now dominated by the loop selector + // (i.e. 'false_path_loop_proj'). The removed candidate is folded in the next IGVN round. If there are multiple + // candidates (i.e. flat array checks), then we leave them in the false-path-loop and only mark the loop such that it + // is not unswitched anymore in later loop opts rounds. + void update_in_false_path_loop(IfFalseNode* false_path_loop_proj, LoopNode* false_path_loop) const { + if (has_multiple_flat_array_check_candidates()) { + // Leave the flat array checks in the false-path-loop and prevent it from being unswitched again based on these + // checks. + false_path_loop->mark_flat_arrays(); + } else { + remove_from_loop(false_path_loop_proj, _old_new[_candidate->_idx]->as_If()); + } + } + + private: + void remove_from_loop(IfProjNode* dominating_proj, IfNode* candidate) const { + _phase->igvn().rehash_node_delayed(candidate); + _phase->dominated_by(dominating_proj, candidate); + } + + void remove_flat_array_checks(IfProjNode* dominating_proj) const { + for (uint i = 0; i < _flat_array_check_candidates.size(); i++) { + IfNode* flat_array_check = _flat_array_check_candidates.at(i)->as_If(); + _phase->igvn().rehash_node_delayed(flat_array_check); + _phase->dominated_by(dominating_proj, flat_array_check); + } + } + + public: + // Merge all flat array checks into a single new BoolNode and return it. + BoolNode* merge_flat_array_checks() const { + assert(has_multiple_flat_array_check_candidates(), "must have multiple flat array checks to merge"); + assert(_candidate->in(1)->as_Bool()->_test._test == BoolTest::ne, "IfTrue proj must point to flat array"); + BoolNode* merged_flat_array_check_bool = create_bool_node(); + create_flat_array_check_node(merged_flat_array_check_bool); + return merged_flat_array_check_bool; + } + + private: + BoolNode* create_bool_node() const { + BoolNode* merged_flat_array_check_bool = _candidate->in(1)->clone()->as_Bool(); + _phase->register_new_node(merged_flat_array_check_bool, _original_loop_entry); + return merged_flat_array_check_bool; + } + + void create_flat_array_check_node(BoolNode* merged_flat_array_check_bool) const { + FlatArrayCheckNode* cloned_flat_array_check = merged_flat_array_check_bool->in(1)->clone()->as_FlatArrayCheck(); + _phase->register_new_node(cloned_flat_array_check, _original_loop_entry); + merged_flat_array_check_bool->set_req(1, cloned_flat_array_check); + set_flat_array_check_inputs(cloned_flat_array_check); + } + + // Combine all checks into a single one that fails if one array is flat. + void set_flat_array_check_inputs(FlatArrayCheckNode* cloned_flat_array_check) const { + assert(cloned_flat_array_check->req() == 3, "unexpected number of inputs for FlatArrayCheck"); + cloned_flat_array_check->add_req_batch(_phase->C->top(), _flat_array_check_candidates.size() - 1); + for (uint i = 0; i < _flat_array_check_candidates.size(); i++) { + Node* array = _flat_array_check_candidates.at(i)->in(1)->in(1)->in(FlatArrayCheckNode::ArrayOrKlass); + cloned_flat_array_check->set_req(FlatArrayCheckNode::ArrayOrKlass + i, array); + } + } + + public: +#ifndef PRODUCT + void trace_flat_array_checks() const { + if (has_multiple_flat_array_check_candidates()) { + tty->print_cr("- Unswitched and Merged Flat Array Checks:"); + for (uint i = 0; i < _flat_array_check_candidates.size(); i++) { + Node* unswitch_iff = _flat_array_check_candidates.at(i); + Node* cloned_unswitch_iff = _old_new[unswitch_iff->_idx]; + assert(cloned_unswitch_iff != nullptr, "must exist"); + tty->print_cr(" - %d %s -> %d %s", unswitch_iff->_idx, unswitch_iff->Name(), + cloned_unswitch_iff->_idx, cloned_unswitch_iff->Name()); + } + } + } +#endif // NOT PRODUCT +}; + // LoopSelector is used for loop multiversioning and unswitching. This class creates an If node (i.e. loop selector) // that selects if the true-path-loop or the false-path-loop should be executed at runtime. class LoopSelector : public StackObj { @@ -176,7 +369,9 @@ class LoopSelector : public StackObj { IfTrueNode* const _true_path_loop_proj; IfFalseNode* const _false_path_loop_proj; - enum PathToLoop { TRUE_PATH, FALSE_PATH }; + enum PathToLoop { + TRUE_PATH, FALSE_PATH + }; public: // For multiversioning: create a new selector (multiversion_if) from a bol condition. @@ -192,7 +387,7 @@ class LoopSelector : public StackObj { // For unswitching: create an unswitching if before the loop, from a pre-existing // unswitching_candidate inside the loop. - LoopSelector(IdealLoopTree* loop, IfNode* unswitch_candidate) + LoopSelector(IdealLoopTree* loop, const UnswitchCandidate& unswitch_candidate) : _phase(loop->_phase), _outer_loop(loop->skip_strip_mined()->_parent), _original_loop_entry(loop->_head->as_Loop()->skip_strip_mined()->in(LoopNode::EntryControl)), @@ -203,6 +398,7 @@ class LoopSelector : public StackObj { } NONCOPYABLE(LoopSelector); + private: IfNode* create_multiversioning_if(Node* bol, float prob, float fcnt) { _phase->igvn().rehash_node_delayed(_original_loop_entry); IfNode* selector_if = new IfNode(_original_loop_entry, bol, prob, fcnt); @@ -210,16 +406,21 @@ class LoopSelector : public StackObj { return selector_if; } - IfNode* create_unswitching_if(IfNode* unswitch_candidate) { + IfNode* create_unswitching_if(const UnswitchCandidate& unswitch_candidate) { + const uint dom_depth = _phase->dom_depth(_original_loop_entry); _phase->igvn().rehash_node_delayed(_original_loop_entry); - BoolNode* unswitch_candidate_bool = unswitch_candidate->in(1)->as_Bool(); - IfNode* selector_if = IfNode::make_with_same_profile(unswitch_candidate, _original_loop_entry, - unswitch_candidate_bool); - _phase->register_node(selector_if, _outer_loop, _original_loop_entry, _dom_depth); + IfNode* unswitch_candidate_if = unswitch_candidate.candidate(); + BoolNode* selector_bool; + if (unswitch_candidate.has_multiple_flat_array_check_candidates()) { + selector_bool = unswitch_candidate.merge_flat_array_checks(); + } else { + selector_bool = unswitch_candidate_if->in(1)->as_Bool(); + } + IfNode* selector_if = IfNode::make_with_same_profile(unswitch_candidate_if, _original_loop_entry, selector_bool); + _phase->register_node(selector_if, _outer_loop, _original_loop_entry, dom_depth); return selector_if; } - private: IfProjNode* create_proj_to_loop(const PathToLoop path_to_loop) { IfProjNode* proj_to_loop; if (path_to_loop == TRUE_PATH) { @@ -249,26 +450,17 @@ class LoopSelector : public StackObj { // executed at runtime. This is done by finding an invariant and non-loop-exiting unswitch candidate If node (guaranteed // to exist at this point) to perform Loop Unswitching on. class UnswitchedLoopSelector : public StackObj { - IfNode* const _unswitch_candidate; + const UnswitchCandidate& _unswitch_candidate; const LoopSelector _loop_selector; public: - UnswitchedLoopSelector(IdealLoopTree* loop) - : _unswitch_candidate(find_unswitch_candidate(loop)), + UnswitchedLoopSelector(IdealLoopTree* loop, const UnswitchCandidate& unswitch_candidate) + : _unswitch_candidate(unswitch_candidate), _loop_selector(loop, _unswitch_candidate) {} NONCOPYABLE(UnswitchedLoopSelector); - private: - static IfNode* find_unswitch_candidate(IdealLoopTree* loop) { - IfNode* unswitch_candidate = loop->_phase->find_unswitch_candidate(loop); - assert(unswitch_candidate != nullptr, "guaranteed to exist by policy_unswitching"); - assert(loop->_phase->is_member(loop, unswitch_candidate), "must be inside original loop"); - return unswitch_candidate; - } - - public: - IfNode* unswitch_candidate() const { - return _unswitch_candidate; + IfNode* selector_if() const { + return _loop_selector.selector(); } const LoopSelector& loop_selector() const { @@ -298,7 +490,6 @@ class OriginalLoop : public StackObj { // Remove the unswitch candidate If from both unswitched loop versions which are now covered by the loop selector If. void unswitch(const UnswitchedLoopSelector& unswitched_loop_selector) { multiversion(unswitched_loop_selector.loop_selector()); - remove_unswitch_candidate_from_loops(unswitched_loop_selector); } // Multiversion the original loop. The loop selector if selects between the original loop (true-path-loop), and @@ -363,20 +554,6 @@ class OriginalLoop : public StackObj { Node* old_to_new(const Node* old) const { return _old_new[old->_idx]; } - - // Remove the unswitch candidate If nodes in both unswitched loop versions which are now dominated by the loop selector - // If node. Keep the true-path-path in the true-path-loop and the false-path-path in the false-path-loop by setting - // the bool input accordingly. The unswitch candidate If nodes are folded in the next IGVN round. - void remove_unswitch_candidate_from_loops(const UnswitchedLoopSelector& unswitched_loop_selector) { - const LoopSelector& loop_selector = unswitched_loop_selector.loop_selector();; - IfNode* unswitch_candidate = unswitched_loop_selector.unswitch_candidate(); - _phase->igvn().rehash_node_delayed(unswitch_candidate); - _phase->dominated_by(loop_selector.true_path_loop_proj(), unswitch_candidate); - - IfNode* unswitch_candidate_clone = _old_new[unswitch_candidate->_idx]->as_If(); - _phase->igvn().rehash_node_delayed(unswitch_candidate_clone); - _phase->dominated_by(loop_selector.false_path_loop_proj(), unswitch_candidate_clone); - } }; // See comments below file header for more information about Loop Unswitching. @@ -394,17 +571,21 @@ void PhaseIdealLoop::do_unswitching(IdealLoopTree* loop, Node_List& old_new) { revert_to_normal_loop(original_head); - const UnswitchedLoopSelector unswitched_loop_selector(loop); + const UnswitchCandidate unswitch_candidate(loop, old_new); + const UnswitchedLoopSelector unswitched_loop_selector(loop, unswitch_candidate); OriginalLoop original_loop(loop, old_new); original_loop.unswitch(unswitched_loop_selector); - hoist_invariant_check_casts(loop, old_new, unswitched_loop_selector); + unswitch_candidate.update_in_true_path_loop(unswitched_loop_selector.loop_selector().true_path_loop_proj()); + unswitch_candidate.update_in_false_path_loop(unswitched_loop_selector.loop_selector().false_path_loop_proj(), + old_new[original_head->_idx]->as_Loop()); + hoist_invariant_check_casts(loop, old_new, unswitch_candidate, unswitched_loop_selector.selector_if()); add_unswitched_loop_version_bodies_to_igvn(loop, old_new); LoopNode* new_head = old_new[original_head->_idx]->as_Loop(); increment_unswitch_counts(original_head, new_head); - NOT_PRODUCT(trace_loop_unswitching_result(unswitched_loop_selector, original_head, new_head);) + NOT_PRODUCT(trace_loop_unswitching_result(unswitched_loop_selector, unswitch_candidate, original_head, new_head);) C->print_method(PHASE_AFTER_LOOP_UNSWITCHING, 4, new_head); C->set_major_progress(); } @@ -608,15 +789,17 @@ void PhaseIdealLoop::trace_loop_unswitching_count(IdealLoopTree* loop, LoopNode* } void PhaseIdealLoop::trace_loop_unswitching_result(const UnswitchedLoopSelector& unswitched_loop_selector, + const UnswitchCandidate& unswitch_candidate, const LoopNode* original_head, const LoopNode* new_head) { if (TraceLoopUnswitching) { - IfNode* unswitch_candidate = unswitched_loop_selector.unswitch_candidate(); - IfNode* loop_selector = unswitched_loop_selector.loop_selector().selector(); + IfNode* unswitch_candidate_if = unswitch_candidate.candidate(); + IfNode* loop_selector = unswitched_loop_selector.selector_if(); tty->print_cr("Loop Unswitching:"); - tty->print_cr("- Unswitch-Candidate-If: %d %s", unswitch_candidate->_idx, unswitch_candidate->Name()); + tty->print_cr("- Unswitch-Candidate-If: %d %s", unswitch_candidate_if->_idx, unswitch_candidate_if->Name()); tty->print_cr("- Loop-Selector-If: %d %s", loop_selector->_idx, loop_selector->Name()); tty->print_cr("- True-Path-Loop (=Orig): %d %s", original_head->_idx, original_head->Name()); tty->print_cr("- False-Path-Loop (=Clone): %d %s", new_head->_idx, new_head->Name()); + unswitch_candidate.trace_flat_array_checks(); } } @@ -643,13 +826,13 @@ void PhaseIdealLoop::revert_to_normal_loop(const LoopNode* loop_head) { // Hoist invariant CheckCastPPNodes out of each unswitched loop version to the appropriate loop selector If projection. void PhaseIdealLoop::hoist_invariant_check_casts(const IdealLoopTree* loop, const Node_List& old_new, - const UnswitchedLoopSelector& unswitched_loop_selector) { - IfNode* unswitch_candidate = unswitched_loop_selector.unswitch_candidate(); - IfNode* loop_selector = unswitched_loop_selector.loop_selector().selector(); + const UnswitchCandidate& unswitch_candidate, + const IfNode* loop_selector) { ResourceMark rm; GrowableArray loop_invariant_check_casts; - for (DUIterator_Fast imax, i = unswitch_candidate->fast_outs(imax); i < imax; i++) { - IfProjNode* proj = unswitch_candidate->fast_out(i)->as_IfProj(); + const IfNode* unswitch_candidate_if = unswitch_candidate.candidate(); + for (DUIterator_Fast imax, i = unswitch_candidate_if->fast_outs(imax); i < imax; i++) { + IfProjNode* proj = unswitch_candidate_if->fast_out(i)->as_IfProj(); // Copy to a worklist for easier manipulation for (DUIterator_Fast jmax, j = proj->fast_outs(jmax); j < jmax; j++) { CheckCastPPNode* check_cast = proj->fast_out(j)->isa_CheckCastPP(); @@ -664,9 +847,12 @@ void PhaseIdealLoop::hoist_invariant_check_casts(const IdealLoopTree* loop, cons cast_clone->set_req(0, loop_selector_if_proj); _igvn.replace_input_of(cast, 1, cast_clone); register_new_node(cast_clone, loop_selector_if_proj); - // Same for the clone - Node* use_clone = old_new[cast->_idx]; - _igvn.replace_input_of(use_clone, 1, cast_clone); + // Same for the false-path-loop if there are not multiple flat array checks (in that case we leave the + // false-path-loop unchanged). + if (!unswitch_candidate.has_multiple_flat_array_check_candidates()) { + Node* use_clone = old_new[cast->_idx]; + _igvn.replace_input_of(use_clone, 1, cast_clone); + } } } } @@ -674,7 +860,7 @@ void PhaseIdealLoop::hoist_invariant_check_casts(const IdealLoopTree* loop, cons // Enable more optimizations possibilities in the next IGVN round. void PhaseIdealLoop::add_unswitched_loop_version_bodies_to_igvn(IdealLoopTree* loop, const Node_List& old_new) { loop->record_for_igvn(); - for(int i = loop->_body.size() - 1; i >= 0 ; i--) { + for (int i = loop->_body.size() - 1; i >= 0; i--) { Node* n = loop->_body[i]; Node* n_clone = old_new[n->_idx]; _igvn._worklist.push(n_clone); @@ -686,4 +872,3 @@ void PhaseIdealLoop::increment_unswitch_counts(LoopNode* original_head, LoopNode original_head->set_unswitch_count(unswitch_count); new_head->set_unswitch_count(unswitch_count); } - diff --git a/src/hotspot/share/opto/loopnode.hpp b/src/hotspot/share/opto/loopnode.hpp index 8863f37699a..88b47263327 100644 --- a/src/hotspot/share/opto/loopnode.hpp +++ b/src/hotspot/share/opto/loopnode.hpp @@ -43,6 +43,7 @@ class OuterStripMinedLoopEndNode; class PredicateBlock; class PathFrequency; class PhaseIdealLoop; +class UnswitchCandidate; class LoopSelector; class UnswitchedLoopSelector; class VectorSet; @@ -85,7 +86,7 @@ class LoopNode : public RegionNode { MultiversionSlowLoop = 2<<17, MultiversionDelayedSlowLoop = 3<<17, MultiversionFlagsMask = 3<<17, - }; + FlatArrays = 1<<18}; char _unswitch_count; enum { _unswitch_max=3 }; @@ -108,6 +109,7 @@ class LoopNode : public RegionNode { bool is_subword_loop() const { return _loop_flags & SubwordLoop; } bool is_loop_nest_inner_loop() const { return _loop_flags & LoopNestInnerLoop; } bool is_loop_nest_outer_loop() const { return _loop_flags & LoopNestLongOuterLoop; } + bool is_flat_arrays() const { return _loop_flags & FlatArrays; } void mark_partial_peel_failed() { _loop_flags |= PartialPeelFailed; } void mark_was_slp() { _loop_flags |= WasSlpAnalyzed; } @@ -121,6 +123,7 @@ class LoopNode : public RegionNode { void mark_subword_loop() { _loop_flags |= SubwordLoop; } void mark_loop_nest_inner_loop() { _loop_flags |= LoopNestInnerLoop; } void mark_loop_nest_outer_loop() { _loop_flags |= LoopNestLongOuterLoop; } + void mark_flat_arrays() { _loop_flags |= FlatArrays; } int unswitch_max() { return _unswitch_max; } int unswitch_count() { return _unswitch_count; } @@ -753,6 +756,7 @@ class IdealLoopTree : public ResourceObj { // Return TRUE or FALSE if the loop should be unswitched -- clone // loop with an invariant test bool policy_unswitching( PhaseIdealLoop *phase ) const; + bool no_unswitch_candidate() const; // Micro-benchmark spamming. Remove empty loops. bool do_remove_empty_loop( PhaseIdealLoop *phase ); @@ -1498,14 +1502,15 @@ class PhaseIdealLoop : public PhaseTransform { // execute. void do_unswitching(IdealLoopTree* loop, Node_List& old_new); - IfNode* find_unswitch_candidate(const IdealLoopTree* loop) const; + IfNode* find_unswitch_candidates(const IdealLoopTree* loop, Node_List& flat_array_checks) const; + IfNode* find_unswitch_candidate_from_idoms(const IdealLoopTree* loop) const; private: static bool has_control_dependencies_from_predicates(LoopNode* head); static void revert_to_normal_loop(const LoopNode* loop_head); void hoist_invariant_check_casts(const IdealLoopTree* loop, const Node_List& old_new, - const UnswitchedLoopSelector& unswitched_loop_selector); + const UnswitchCandidate& unswitch_candidate, const IfNode* loop_selector); void add_unswitched_loop_version_bodies_to_igvn(IdealLoopTree* loop, const Node_List& old_new); static void increment_unswitch_counts(LoopNode* original_head, LoopNode* new_head); void remove_unswitch_candidate_from_loops(const Node_List& old_new, const UnswitchedLoopSelector& unswitched_loop_selector); @@ -1513,6 +1518,7 @@ class PhaseIdealLoop : public PhaseTransform { static void trace_loop_unswitching_count(IdealLoopTree* loop, LoopNode* original_head); static void trace_loop_unswitching_impossible(const LoopNode* original_head); static void trace_loop_unswitching_result(const UnswitchedLoopSelector& unswitched_loop_selector, + const UnswitchCandidate& unswitch_candidate, const LoopNode* original_head, const LoopNode* new_head); static void trace_loop_multiversioning_result(const LoopSelector& loop_selector, const LoopNode* original_head, const LoopNode* new_head); @@ -1710,7 +1716,9 @@ class PhaseIdealLoop : public PhaseTransform { Node* place_outside_loop(Node* useblock, IdealLoopTree* loop) const; Node* try_move_store_before_loop(Node* n, Node *n_ctrl); void try_move_store_after_loop(Node* n); + void move_flat_array_check_out_of_loop(Node* n); bool identical_backtoback_ifs(Node *n); + bool flat_array_element_type_check(Node *n); bool can_split_if(Node *n_ctrl); bool cannot_split_division(const Node* n, const Node* region) const; static bool is_divisor_loop_phi(const Node* divisor, const Node* loop); @@ -1900,6 +1908,8 @@ class PhaseIdealLoop : public PhaseTransform { void pin_array_access_nodes_dependent_on(Node* ctrl); + void collect_flat_array_checks(const IdealLoopTree* loop, Node_List& flat_array_checks) const; + Node* ensure_node_and_inputs_are_above_pre_end(CountedLoopEndNode* pre_end, Node* node); bool try_make_short_running_loop(IdealLoopTree* loop, jint stride_con, const Node_List& range_checks, const uint iters_limit); diff --git a/src/hotspot/share/opto/loopopts.cpp b/src/hotspot/share/opto/loopopts.cpp index a09eef0bb81..0bf69ef1d6a 100644 --- a/src/hotspot/share/opto/loopopts.cpp +++ b/src/hotspot/share/opto/loopopts.cpp @@ -31,6 +31,7 @@ #include "opto/castnode.hpp" #include "opto/connode.hpp" #include "opto/divnode.hpp" +#include "opto/inlinetypenode.hpp" #include "opto/loopnode.hpp" #include "opto/matcher.hpp" #include "opto/movenode.hpp" @@ -63,6 +64,12 @@ Node* PhaseIdealLoop::split_thru_phi(Node* n, Node* region, int policy) { return nullptr; } + // Inline types should not be split through Phis because they cannot be merged + // through Phi nodes but each value input needs to be merged individually. + if (n->is_InlineType()) { + return nullptr; + } + if (cannot_split_division(n, region)) { return nullptr; } @@ -767,6 +774,10 @@ Node *PhaseIdealLoop::conditional_move( Node *region ) { for (uint j = 1; j < region->req(); j++) { Node *proj = region->in(j); Node *inp = phi->in(j); + if (inp->isa_InlineType()) { + // TODO 8302217 This prevents PhiNode::push_inline_types_through + return nullptr; + } if (get_ctrl(inp) == proj) { // Found local op cost++; // Check for a chain of dependent ops; these will all become @@ -1096,6 +1107,54 @@ void PhaseIdealLoop::try_move_store_after_loop(Node* n) { } } +// We can't use immutable memory for the flat array check because we are loading the mark word which is +// mutable. Although the bits we are interested in are immutable (we check for markWord::unlocked_value), +// we need to use raw memory to not break anti dependency analysis. Below code will attempt to still move +// flat array checks out of loops, mainly to enable loop unswitching. +void PhaseIdealLoop::move_flat_array_check_out_of_loop(Node* n) { + // Skip checks for more than one array + if (n->req() > 3) { + return; + } + Node* mem = n->in(FlatArrayCheckNode::Memory); + Node* array = n->in(FlatArrayCheckNode::ArrayOrKlass)->uncast(); + IdealLoopTree* check_loop = get_loop(get_ctrl(n)); + IdealLoopTree* ary_loop = get_loop(get_ctrl(array)); + + // Check if array is loop invariant + if (!check_loop->is_member(ary_loop)) { + // Walk up memory graph from the check until we leave the loop + VectorSet wq; + wq.set(mem->_idx); + while (check_loop->is_member(get_loop(ctrl_or_self(mem)))) { + if (mem->is_Phi()) { + mem = mem->in(1); + } else if (mem->is_MergeMem()) { + mem = mem->as_MergeMem()->memory_at(Compile::AliasIdxRaw); + } else if (mem->is_Proj()) { + mem = mem->in(0); + } else if (mem->is_MemBar() || mem->is_SafePoint()) { + mem = mem->in(TypeFunc::Memory); + } else if (mem->is_Store() || mem->is_LoadStore() || mem->is_ClearArray()) { + mem = mem->in(MemNode::Memory); + } else { +#ifdef ASSERT + mem->dump(); +#endif + ShouldNotReachHere(); + } + if (wq.test_set(mem->_idx)) { + return; + } + } + // Replace memory input and re-compute ctrl to move the check out of the loop + _igvn.replace_input_of(n, 1, mem); + set_ctrl_and_loop(n, get_early_ctrl(n)); + Node* bol = n->unique_out(); + set_ctrl_and_loop(bol, get_early_ctrl(bol)); + } +} + //------------------------------split_if_with_blocks_pre----------------------- // Do the real work in a non-recursive function. Data nodes want to be // cloned in the pre-order so they can feed each other nicely. @@ -1108,6 +1167,12 @@ Node *PhaseIdealLoop::split_if_with_blocks_pre( Node *n ) { if (n->is_Proj()) { return n; } + + if (n->isa_FlatArrayCheck()) { + move_flat_array_check_out_of_loop(n); + return n; + } + // Do not clone-up CmpFXXX variations, as these are always // followed by a CmpI if (n->is_Cmp()) { @@ -1383,12 +1448,114 @@ static Node* is_inner_of_stripmined_loop(const Node* out) { return out_le; } +bool PhaseIdealLoop::flat_array_element_type_check(Node *n) { + // If the CmpP is a subtype check for a value that has just been + // loaded from an array, the subtype check guarantees the value + // can't be stored in a flat array and the load of the value + // happens with a flat array check then: push the type check + // through the phi of the flat array check. This needs special + // logic because the subtype check's input is not a phi but a + // LoadKlass that must first be cloned through the phi. + if (n->Opcode() != Op_CmpP) { + return false; + } + + Node* klassptr = n->in(1); + Node* klasscon = n->in(2); + + if (klassptr->is_DecodeNarrowPtr()) { + klassptr = klassptr->in(1); + } + + if (klassptr->Opcode() != Op_LoadKlass && klassptr->Opcode() != Op_LoadNKlass) { + return false; + } + + if (!klasscon->is_Con()) { + return false; + } + + Node* addr = klassptr->in(MemNode::Address); + + if (!addr->is_AddP()) { + return false; + } + + intptr_t offset; + Node* obj = AddPNode::Ideal_base_and_offset(addr, &_igvn, offset); + + if (obj == nullptr) { + return false; + } + + assert(obj != nullptr && addr->in(AddPNode::Base) == addr->in(AddPNode::Address), "malformed AddP?"); + if (obj->Opcode() == Op_CastPP) { + obj = obj->in(1); + } + + if (!obj->is_Phi()) { + return false; + } + + Node* region = obj->in(0); + + Node* phi = PhiNode::make_blank(region, n->in(1)); + for (uint i = 1; i < region->req(); i++) { + Node* in = obj->in(i); + Node* ctrl = region->in(i); + if (addr->in(AddPNode::Base) != obj) { + Node* cast = addr->in(AddPNode::Base); + assert(cast->Opcode() == Op_CastPP && cast->in(0) != nullptr, "inconsistent subgraph"); + Node* cast_clone = cast->clone(); + cast_clone->set_req(0, ctrl); + cast_clone->set_req(1, in); + register_new_node(cast_clone, ctrl); + const Type* tcast = cast_clone->Value(&_igvn); + _igvn.set_type(cast_clone, tcast); + cast_clone->as_Type()->set_type(tcast); + in = cast_clone; + } + Node* addr_clone = addr->clone(); + addr_clone->set_req(AddPNode::Base, in); + addr_clone->set_req(AddPNode::Address, in); + register_new_node(addr_clone, ctrl); + _igvn.set_type(addr_clone, addr_clone->Value(&_igvn)); + Node* klassptr_clone = klassptr->clone(); + klassptr_clone->set_req(2, addr_clone); + register_new_node(klassptr_clone, ctrl); + _igvn.set_type(klassptr_clone, klassptr_clone->Value(&_igvn)); + if (klassptr != n->in(1)) { + Node* decode = n->in(1); + assert(decode->is_DecodeNarrowPtr(), "inconsistent subgraph"); + Node* decode_clone = decode->clone(); + decode_clone->set_req(1, klassptr_clone); + register_new_node(decode_clone, ctrl); + _igvn.set_type(decode_clone, decode_clone->Value(&_igvn)); + klassptr_clone = decode_clone; + } + phi->set_req(i, klassptr_clone); + } + register_new_node(phi, region); + Node* orig = n->in(1); + _igvn.replace_input_of(n, 1, phi); + split_if_with_blocks_post(n); + if (n->outcnt() != 0) { + _igvn.replace_input_of(n, 1, orig); + _igvn.remove_dead_node(phi); + } + return true; +} + //------------------------------split_if_with_blocks_post---------------------- // Do the real work in a non-recursive function. CFG hackery wants to be // in the post-order, so it can dirty the I-DOM info and not use the dirtied // info. void PhaseIdealLoop::split_if_with_blocks_post(Node *n) { + if (flat_array_element_type_check(n)) { + return; + } + // Cloning Cmp through Phi's involves the split-if transform. // FastLock is not used by an If if (n->is_Cmp() && !n->is_FastLock()) { @@ -1539,6 +1706,11 @@ void PhaseIdealLoop::split_if_with_blocks_post(Node *n) { } try_move_store_after_loop(n); + + // Remove multiple allocations of the same inline type + if (n->is_InlineType()) { + n->as_InlineType()->remove_redundant_allocations(this); + } } // Transform: @@ -2036,10 +2208,18 @@ Node* PhaseIdealLoop::clone_iff(PhiNode* phi) { } else { sample_bool = n; } - Node *sample_cmp = sample_bool->in(1); + Node* sample_cmp = sample_bool->in(1); + const Type* t = Type::TOP; + const TypePtr* at = nullptr; + if (sample_cmp->is_FlatArrayCheck()) { + // Left input of a FlatArrayCheckNode is memory, set the (adr) type of the phi accordingly + assert(sample_cmp->in(1)->bottom_type() == Type::MEMORY, "unexpected input type"); + t = Type::MEMORY; + at = TypeRawPtr::BOTTOM; + } // Make Phis to merge the Cmp's inputs. - PhiNode *phi1 = new PhiNode(phi->in(0), Type::TOP); + PhiNode *phi1 = new PhiNode(phi->in(0), t, at); PhiNode *phi2 = new PhiNode(phi->in(0), Type::TOP); for (i = 1; i < phi->req(); i++) { Node *n1 = sample_opaque == nullptr ? phi->in(i)->in(1)->in(1) : phi->in(i)->in(1)->in(1)->in(1); diff --git a/src/hotspot/share/opto/machnode.cpp b/src/hotspot/share/opto/machnode.cpp index 2ae2c5de381..88d973fd96c 100644 --- a/src/hotspot/share/opto/machnode.cpp +++ b/src/hotspot/share/opto/machnode.cpp @@ -416,6 +416,22 @@ const class TypePtr *MachNode::adr_type() const { } assert(tp->base() != Type::AnyPtr, "not a bare pointer"); + if (tp->isa_aryptr()) { + // In the case of a flat inline type array, each field has its + // own slice so we need to extract the field being accessed from + // the address computation + if (offset == Type::OffsetBot) { + Node* base; + Node* index; + const MachOper* oper = memory_inputs(base, index); + if (oper != (MachOper*)-1) { + offset = oper->constant_disp(); + return tp->is_aryptr()->add_field_offset_and_offset(offset)->add_offset(Type::OffsetBot); + } + } + return tp->is_aryptr()->add_field_offset_and_offset(offset); + } + return tp->add_offset(offset); } @@ -480,6 +496,11 @@ void MachNode::method_set( intptr_t addr ) { //------------------------------rematerialize---------------------------------- bool MachNode::rematerialize() const { + // Never rematerialize CastI2N because it might "hide" narrow oops from a safepoint + if (ideal_Opcode() == Op_CastI2N) { + return false; + } + // Temps are always rematerializable if (is_MachTemp()) return true; @@ -709,8 +730,8 @@ const RegMask &MachSafePointNode::in_RegMask( uint idx ) const { bool MachCallNode::cmp( const Node &n ) const { return _tf == ((MachCallNode&)n)._tf; } -const Type *MachCallNode::bottom_type() const { return tf()->range(); } -const Type* MachCallNode::Value(PhaseGVN* phase) const { return tf()->range(); } +const Type *MachCallNode::bottom_type() const { return tf()->range_cc(); } +const Type* MachCallNode::Value(PhaseGVN* phase) const { return tf()->range_cc(); } #ifndef PRODUCT void MachCallNode::dump_spec(outputStream *st) const { @@ -721,9 +742,8 @@ void MachCallNode::dump_spec(outputStream *st) const { } #endif -#ifndef _LP64 bool MachCallNode::return_value_is_used() const { - if (tf()->range()->cnt() == TypeFunc::Parms) { + if (tf()->range_sig()->cnt() == TypeFunc::Parms) { // void return return false; } @@ -738,22 +758,30 @@ bool MachCallNode::return_value_is_used() const { } return false; } -#endif // Similar to cousin class CallNode::returns_pointer // Because this is used in deoptimization, we want the type info, not the data // flow info; the interpreter will "use" things that are dead to the optimizer. bool MachCallNode::returns_pointer() const { - const TypeTuple *r = tf()->range(); + const TypeTuple *r = tf()->range_sig(); return (r->cnt() > TypeFunc::Parms && r->field_at(TypeFunc::Parms)->isa_ptr()); } +bool MachCallNode::returns_scalarized() const { + return tf()->returns_inline_type_as_fields(); +} + //------------------------------Registers-------------------------------------- const RegMask &MachCallNode::in_RegMask(uint idx) const { // Values in the domain use the users calling convention, embodied in the // _in_rms array of RegMasks. - if (idx < tf()->domain()->cnt()) { + if (entry_point() == nullptr && idx == TypeFunc::Parms) { + // Null entry point is a special cast where the target of the call + // is in a register. + return MachNode::in_RegMask(idx); + } + if (idx < tf()->domain_sig()->cnt()) { return _in_rms[idx]; } if (idx == mach_constant_base_node_input()) { @@ -786,7 +814,7 @@ void MachCallJavaNode::dump_spec(outputStream *st) const { const RegMask &MachCallJavaNode::in_RegMask(uint idx) const { // Values in the domain use the users calling convention, embodied in the // _in_rms array of RegMasks. - if (idx < tf()->domain()->cnt()) { + if (idx < tf()->domain_cc()->cnt()) { return _in_rms[idx]; } if (idx == mach_constant_base_node_input()) { diff --git a/src/hotspot/share/opto/machnode.hpp b/src/hotspot/share/opto/machnode.hpp index 3594806b91e..dc17684c7f0 100644 --- a/src/hotspot/share/opto/machnode.hpp +++ b/src/hotspot/share/opto/machnode.hpp @@ -51,6 +51,7 @@ class MachPrologNode; class MachReturnNode; class MachSafePointNode; class MachSpillCopyNode; +class MachVEPNode; class Matcher; class PhaseRegAlloc; class RegMask; @@ -509,13 +510,42 @@ class MachConstantNode : public MachTypeNode { virtual uint size_of() const { return sizeof(MachConstantNode); } }; +//------------------------------MachVEPNode----------------------------------- +// Machine Inline Type Entry Point Node +class MachVEPNode : public MachIdealNode { +public: + Label* _verified_entry; + + MachVEPNode(Label* verified_entry, bool verified, bool receiver_only) : + _verified_entry(verified_entry), + _verified(verified), + _receiver_only(receiver_only) { + init_class_id(Class_MachVEP); + } + virtual bool cmp(const Node &n) const { + return (_verified_entry == ((MachVEPNode&)n)._verified_entry) && + (_verified == ((MachVEPNode&)n)._verified) && + (_receiver_only == ((MachVEPNode&)n)._receiver_only) && + MachIdealNode::cmp(n); + } + virtual uint size_of() const { return sizeof(*this); } + virtual void emit(C2_MacroAssembler *masm, PhaseRegAlloc* ra_) const; + +#ifndef PRODUCT + virtual const char* Name() const { return "InlineType Entry-Point"; } + virtual void format(PhaseRegAlloc*, outputStream* st) const; +#endif +private: + bool _verified; + bool _receiver_only; +}; + //------------------------------MachUEPNode----------------------------------- // Machine Unvalidated Entry Point Node class MachUEPNode : public MachIdealNode { public: MachUEPNode( ) {} virtual void emit(C2_MacroAssembler *masm, PhaseRegAlloc *ra_) const; - virtual uint size(PhaseRegAlloc *ra_) const; #ifndef PRODUCT virtual const char *Name() const { return "Unvalidated-Entry-Point"; } @@ -527,9 +557,16 @@ class MachUEPNode : public MachIdealNode { // Machine function Prolog Node class MachPrologNode : public MachIdealNode { public: - MachPrologNode( ) {} + Label* _verified_entry; + + MachPrologNode(Label* verified_entry) : _verified_entry(verified_entry) { + init_class_id(Class_MachProlog); + } + virtual bool cmp(const Node &n) const { + return (_verified_entry == ((MachPrologNode&)n)._verified_entry) && MachIdealNode::cmp(n); + } + virtual uint size_of() const { return sizeof(*this); } virtual void emit(C2_MacroAssembler *masm, PhaseRegAlloc *ra_) const; - virtual uint size(PhaseRegAlloc *ra_) const; virtual int reloc() const; #ifndef PRODUCT @@ -546,7 +583,6 @@ class MachEpilogNode : public MachIdealNode { public: MachEpilogNode(bool do_poll = false) : _do_polling(do_poll) {} virtual void emit(C2_MacroAssembler *masm, PhaseRegAlloc *ra_) const; - virtual uint size(PhaseRegAlloc *ra_) const; virtual int reloc() const; virtual const Pipeline *pipeline() const; virtual uint size_of() const { return sizeof(MachEpilogNode); } @@ -935,10 +971,11 @@ class MachCallNode : public MachSafePointNode { virtual const RegMask &in_RegMask(uint) const; virtual int ret_addr_offset() { return 0; } - NOT_LP64(bool return_value_is_used() const;) + bool return_value_is_used() const; // Similar to cousin class CallNode::returns_pointer bool returns_pointer() const; + bool returns_scalarized() const; bool guaranteed_safepoint() const { return _guaranteed_safepoint; } diff --git a/src/hotspot/share/opto/macro.cpp b/src/hotspot/share/opto/macro.cpp index a0b52358bac..9f876ce30d9 100644 --- a/src/hotspot/share/opto/macro.cpp +++ b/src/hotspot/share/opto/macro.cpp @@ -22,6 +22,8 @@ * */ +#include "ci/ciFlatArrayKlass.hpp" +#include "ci/ciInstanceKlass.hpp" #include "compiler/compileLog.hpp" #include "gc/shared/collectedHeap.inline.hpp" #include "gc/shared/tlab_globals.hpp" @@ -35,6 +37,7 @@ #include "opto/compile.hpp" #include "opto/convertnode.hpp" #include "opto/graphKit.hpp" +#include "opto/inlinetypenode.hpp" #include "opto/intrinsicnode.hpp" #include "opto/locknode.hpp" #include "opto/loopnode.hpp" @@ -52,6 +55,8 @@ #include "prims/jvmtiExport.hpp" #include "runtime/continuation.hpp" #include "runtime/sharedRuntime.hpp" +#include "runtime/stubRoutines.hpp" +#include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" #include "utilities/powerOfTwo.hpp" #if INCLUDE_G1GC @@ -81,17 +86,6 @@ int PhaseMacroExpand::replace_input(Node *use, Node *oldref, Node *newref) { return nreplacements; } -void PhaseMacroExpand::migrate_outs(Node *old, Node *target) { - assert(old != nullptr, "sanity"); - for (DUIterator_Fast imax, i = old->fast_outs(imax); i < imax; i++) { - Node* use = old->fast_out(i); - _igvn.rehash_node_delayed(use); - imax -= replace_input(use, old, target); - // back up iterator - --i; - } - assert(old->outcnt() == 0, "all uses must be deleted"); -} Node* PhaseMacroExpand::opt_bits_test(Node* ctrl, Node* region, int edge, Node* word) { Node* cmp = word; @@ -144,7 +138,7 @@ CallNode* PhaseMacroExpand::make_slow_call(CallNode *oldcall, const TypeFunc* sl void PhaseMacroExpand::eliminate_gc_barrier(Node* p2x) { BarrierSetC2 *bs = BarrierSet::barrier_set()->barrier_set_c2(); - bs->eliminate_gc_barrier(this, p2x); + bs->eliminate_gc_barrier(&_igvn, p2x); #ifndef PRODUCT if (PrintOptoStatistics) { Atomic::inc(&PhaseMacroExpand::_GC_barriers_removed_counter); @@ -199,7 +193,7 @@ static Node *scan_mem_chain(Node *mem, int alias_idx, int offset, Node *start_me int adr_idx = phase->C->get_alias_index(atype); if (adr_idx == alias_idx) { assert(atype->isa_oopptr(), "address type must be oopptr"); - int adr_offset = atype->offset(); + int adr_offset = atype->flat_offset(); uint adr_iid = atype->is_oopptr()->instance_id(); // Array elements references have the same alias_idx // but different offset and different instance_id. @@ -244,7 +238,7 @@ static Node *scan_mem_chain(Node *mem, int alias_idx, int offset, Node *start_me return nullptr; } mem = mem->in(MemNode::Memory); - } else if (mem->Opcode() == Op_StrInflatedCopy) { + } else if (mem->Opcode() == Op_StrInflatedCopy) { Node* adr = mem->in(3); // Destination array const TypePtr* atype = adr->bottom_type()->is_ptr(); int adr_idx = phase->C->get_alias_index(atype); @@ -289,17 +283,26 @@ Node* PhaseMacroExpand::make_arraycopy_load(ArrayCopyNode* ac, intptr_t offset, const TypeInt* dest_pos_t = _igvn.type(dest_pos)->is_int(); Node* adr = nullptr; - const TypePtr* adr_type = nullptr; + Node* base = ac->in(ArrayCopyNode::Src); + const TypeAryPtr* adr_type = _igvn.type(base)->is_aryptr(); + if (adr_type->is_flat()) { + shift = adr_type->flat_log_elem_size(); + } if (src_pos_t->is_con() && dest_pos_t->is_con()) { intptr_t off = ((src_pos_t->get_con() - dest_pos_t->get_con()) << shift) + offset; - Node* base = ac->in(ArrayCopyNode::Src); adr = _igvn.transform(new AddPNode(base, base, _igvn.MakeConX(off))); - adr_type = _igvn.type(base)->is_ptr()->add_offset(off); + adr_type = _igvn.type(adr)->is_aryptr(); + assert(adr_type == _igvn.type(base)->is_aryptr()->add_field_offset_and_offset(off), "incorrect address type"); if (ac->in(ArrayCopyNode::Src) == ac->in(ArrayCopyNode::Dest)) { // Don't emit a new load from src if src == dst but try to get the value from memory instead - return value_from_mem(ac->in(TypeFunc::Memory), ctl, ft, ftype, adr_type->isa_oopptr(), alloc); + return value_from_mem(ac->in(TypeFunc::Memory), ctl, ft, ftype, adr_type, alloc); } } else { + if (ac->in(ArrayCopyNode::Src) == ac->in(ArrayCopyNode::Dest)) { + // Non constant offset in the array: we can't statically + // determine the value + return nullptr; + } Node* diff = _igvn.transform(new SubINode(ac->in(ArrayCopyNode::SrcPos), ac->in(ArrayCopyNode::DestPos))); #ifdef _LP64 diff = _igvn.transform(new ConvI2LNode(diff)); @@ -307,14 +310,12 @@ Node* PhaseMacroExpand::make_arraycopy_load(ArrayCopyNode* ac, intptr_t offset, diff = _igvn.transform(new LShiftXNode(diff, _igvn.intcon(shift))); Node* off = _igvn.transform(new AddXNode(_igvn.MakeConX(offset), diff)); - Node* base = ac->in(ArrayCopyNode::Src); adr = _igvn.transform(new AddPNode(base, base, off)); - adr_type = _igvn.type(base)->is_ptr()->add_offset(Type::OffsetBot); - if (ac->in(ArrayCopyNode::Src) == ac->in(ArrayCopyNode::Dest)) { - // Non constant offset in the array: we can't statically - // determine the value - return nullptr; - } + // In the case of a flat inline type array, each field has its + // own slice so we need to extract the field being accessed from + // the address computation + adr_type = adr_type->add_field_offset_and_offset(offset)->add_offset(Type::OffsetBot)->is_aryptr(); + adr = _igvn.transform(new CastPPNode(ctl, adr, adr_type)); } MergeMemNode* mergemen = _igvn.transform(MergeMemNode::make(mem))->as_MergeMem(); BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); @@ -324,6 +325,7 @@ Node* PhaseMacroExpand::make_arraycopy_load(ArrayCopyNode* ac, intptr_t offset, if (res != nullptr) { if (ftype->isa_narrowoop()) { // PhaseMacroExpand::scalar_replacement adds DecodeN nodes + assert(res->isa_DecodeN(), "should be narrow oop"); res = _igvn.transform(new EncodePNode(res, ftype)); } return res; @@ -339,7 +341,7 @@ Node* PhaseMacroExpand::make_arraycopy_load(ArrayCopyNode* ac, intptr_t offset, Node *PhaseMacroExpand::value_from_mem_phi(Node *mem, BasicType ft, const Type *phi_type, const TypeOopPtr *adr_t, AllocateNode *alloc, Node_Stack *value_phis, int level) { assert(mem->is_Phi(), "sanity"); int alias_idx = C->get_alias_index(adr_t); - int offset = adr_t->offset(); + int offset = adr_t->flat_offset(); int instance_id = adr_t->instance_id(); // Check if an appropriate value phi already exists. @@ -375,11 +377,23 @@ Node *PhaseMacroExpand::value_from_mem_phi(Node *mem, BasicType ft, const Type * Node *in = mem->in(j); if (in == nullptr || in->is_top()) { values.at_put(j, in); - } else { + } else { Node *val = scan_mem_chain(in, alias_idx, offset, start_mem, alloc, &_igvn); if (val == start_mem || val == alloc_mem) { - // hit a sentinel, return appropriate 0 value - values.at_put(j, _igvn.zerocon(ft)); + // hit a sentinel, return appropriate value + Node* init_value = alloc->in(AllocateNode::InitValue); + if (init_value != nullptr) { + if (val == start_mem) { + // TODO 8350865 Scalar replacement does not work well for flat arrays. + // Somehow we ended up with root mem and therefore walked past the alloc. Fix this. Triggered by TestGenerated::test15 + // Don't we need field_value_by_offset? + return nullptr; + } + values.at_put(j, init_value); + } else { + assert(alloc->in(AllocateNode::RawInitValue) == nullptr, "init value may not be null"); + values.at_put(j, _igvn.zerocon(ft)); + } continue; } if (val->is_Initialize()) { @@ -398,8 +412,16 @@ Node *PhaseMacroExpand::value_from_mem_phi(Node *mem, BasicType ft, const Type * n = Compile::narrow_value(ft, n, phi_type, &_igvn, true); } values.at_put(j, n); - } else if(val->is_Proj() && val->in(0) == alloc) { - values.at_put(j, _igvn.zerocon(ft)); + } else if (val->is_Proj() && val->in(0) == alloc) { + Node* init_value = alloc->in(AllocateNode::InitValue); + if (init_value != nullptr) { + // TODO 8350865 Scalar replacement does not work well for flat arrays. + // Is this correct for non-all-zero init values? Don't we need field_value_by_offset? + values.at_put(j, init_value); + } else { + assert(alloc->in(AllocateNode::RawInitValue) == nullptr, "init value may not be null"); + values.at_put(j, _igvn.zerocon(ft)); + } } else if (val->is_Phi()) { val = value_from_mem_phi(val, ft, phi_type, adr_t, alloc, value_phis, level-1); if (val == nullptr) { @@ -447,9 +469,8 @@ Node *PhaseMacroExpand::value_from_mem(Node *sfpt_mem, Node *sfpt_ctl, BasicType assert((uint)instance_id == alloc->_idx, "wrong allocation"); int alias_idx = C->get_alias_index(adr_t); - int offset = adr_t->offset(); + int offset = adr_t->flat_offset(); Node *start_mem = C->start()->proj_out_or_null(TypeFunc::Memory); - Node *alloc_ctrl = alloc->in(TypeFunc::Control); Node *alloc_mem = alloc->proj_out_or_null(TypeFunc::Memory, /*io_use:*/false); assert(alloc_mem != nullptr, "Allocation without a memory projection."); VectorSet visited; @@ -466,7 +487,7 @@ Node *PhaseMacroExpand::value_from_mem(Node *sfpt_mem, Node *sfpt_ctl, BasicType } else if (mem->is_Initialize()) { mem = mem->as_Initialize()->find_captured_store(offset, type2aelembytes(ft), &_igvn); if (mem == nullptr) { - done = true; // Something go wrong. + done = true; // Something went wrong. } else if (mem->is_Store()) { const TypePtr* atype = mem->as_Store()->adr_type(); assert(C->get_alias_index(atype) == Compile::AliasIdxRaw, "store is correct memory slice"); @@ -476,7 +497,7 @@ Node *PhaseMacroExpand::value_from_mem(Node *sfpt_mem, Node *sfpt_ctl, BasicType const TypeOopPtr* atype = mem->as_Store()->adr_type()->isa_oopptr(); assert(atype != nullptr, "address type must be oopptr"); assert(C->get_alias_index(atype) == alias_idx && - atype->is_known_instance_field() && atype->offset() == offset && + atype->is_known_instance_field() && atype->flat_offset() == offset && atype->instance_id() == instance_id, "store is correct memory slice"); done = true; } else if (mem->is_Phi()) { @@ -508,7 +529,23 @@ Node *PhaseMacroExpand::value_from_mem(Node *sfpt_mem, Node *sfpt_ctl, BasicType } if (mem != nullptr) { if (mem == start_mem || mem == alloc_mem) { - // hit a sentinel, return appropriate 0 value + // hit a sentinel, return appropriate value + Node* init_value = alloc->in(AllocateNode::InitValue); + if (init_value != nullptr) { + if (adr_t->is_flat()) { + if (init_value->is_EncodeP()) { + init_value = init_value->in(1); + } + assert(adr_t->is_aryptr()->field_offset().get() != Type::OffsetBot, "Unknown offset"); + offset = adr_t->is_aryptr()->field_offset().get() + init_value->bottom_type()->inline_klass()->payload_offset(); + init_value = init_value->as_InlineType()->field_value_by_offset(offset, true); + if (ft == T_NARROWOOP) { + init_value = transform_later(new EncodePNode(init_value, init_value->bottom_type()->make_ptr())); + } + } + return init_value; + } + assert(alloc->in(AllocateNode::RawInitValue) == nullptr, "init value may not be null"); return _igvn.zerocon(ft); } else if (mem->is_Store()) { Node* n = mem->in(MemNode::ValueIn); @@ -540,10 +577,79 @@ Node *PhaseMacroExpand::value_from_mem(Node *sfpt_mem, Node *sfpt_ctl, BasicType return make_arraycopy_load(mem->as_ArrayCopy(), offset, ctl, m, ft, ftype, alloc); } } - // Something go wrong. + // Something went wrong. return nullptr; } +// Search the last value stored into the inline type's fields (for flat arrays). +Node* PhaseMacroExpand::inline_type_from_mem(ciInlineKlass* vk, const TypeAryPtr* elem_adr_type, int elem_idx, int offset_in_element, bool null_free, AllocateNode* alloc, SafePointNode* sfpt) { + auto report_failure = [&](int field_offset_in_element) { +#ifndef PRODUCT + if (PrintEliminateAllocations) { + ciInlineKlass* elem_klass = elem_adr_type->elem()->inline_klass(); + int offset = field_offset_in_element + elem_klass->payload_offset(); + ciField* flattened_field = elem_klass->get_field_by_offset(offset, false); + assert(flattened_field != nullptr, "must have a field of type %s at offset %d", elem_klass->name()->as_utf8(), offset); + tty->print("=== At SafePoint node %d can't find value of field [%s] of array element [%d]", sfpt->_idx, flattened_field->name()->as_utf8(), elem_idx); + tty->print(", which prevents elimination of: "); + alloc->dump(); + } +#endif // PRODUCT + }; + + // Create a new InlineTypeNode and retrieve the field values from memory + InlineTypeNode* vt = InlineTypeNode::make_uninitialized(_igvn, vk, false); + transform_later(vt); + if (null_free) { + vt->set_null_marker(_igvn); + } else { + int nm_offset_in_element = offset_in_element + vk->null_marker_offset_in_payload(); + const TypeAryPtr* nm_adr_type = elem_adr_type->with_field_offset(nm_offset_in_element); + Node* nm_value = value_from_mem(sfpt->memory(), sfpt->control(), T_BOOLEAN, TypeInt::BOOL, nm_adr_type, alloc); + if (nm_value != nullptr) { + vt->set_null_marker(_igvn, nm_value); + } else { + report_failure(nm_offset_in_element); + return nullptr; + } + } + + for (int i = 0; i < vk->nof_declared_nonstatic_fields(); ++i) { + ciType* field_type = vt->field_type(i); + int field_offset_in_element = offset_in_element + vt->field_offset(i) - vk->payload_offset(); + Node* field_value = nullptr; + if (vt->field_is_flat(i)) { + field_value = inline_type_from_mem(field_type->as_inline_klass(), elem_adr_type, elem_idx, field_offset_in_element, vt->field_is_null_free(i), alloc, sfpt); + } else { + const Type* ft = Type::get_const_type(field_type); + BasicType bt = type2field[field_type->basic_type()]; + if (UseCompressedOops && !is_java_primitive(bt)) { + ft = ft->make_narrowoop(); + bt = T_NARROWOOP; + } + // Each inline type field has its own memory slice + const TypeAryPtr* field_adr_type = elem_adr_type->with_field_offset(field_offset_in_element); + field_value = value_from_mem(sfpt->memory(), sfpt->control(), bt, ft, field_adr_type, alloc); + if (field_value == nullptr) { + report_failure(field_offset_in_element); + } else if (ft->isa_narrowoop()) { + assert(UseCompressedOops, "unexpected narrow oop"); + if (field_value->is_EncodeP()) { + field_value = field_value->in(1); + } else if (!field_value->is_InlineType()) { + field_value = transform_later(new DecodeNNode(field_value, field_value->get_ptr_type())); + } + } + } + if (field_value != nullptr) { + vt->set_field_value(i, field_value); + } else { + return nullptr; + } + } + return vt; +} + // Check the possibility of scalar replacement. bool PhaseMacroExpand::can_eliminate_allocation(PhaseIterGVN* igvn, AllocateNode *alloc, GrowableArray * safepoints) { // Scan the uses of the allocation to check for anything that would @@ -553,6 +659,7 @@ bool PhaseMacroExpand::can_eliminate_allocation(PhaseIterGVN* igvn, AllocateNode bool can_eliminate = true; bool reduce_merge_precheck = (safepoints == nullptr); + Unique_Node_List worklist; Node* res = alloc->result_cast(); const TypeOopPtr* res_type = nullptr; if (res == nullptr) { @@ -561,6 +668,7 @@ bool PhaseMacroExpand::can_eliminate_allocation(PhaseIterGVN* igvn, AllocateNode NOT_PRODUCT(fail_eliminate = "Allocation does not have unique CheckCastPP";) can_eliminate = false; } else { + worklist.push(res); res_type = igvn->type(res)->isa_oopptr(); if (res_type == nullptr) { NOT_PRODUCT(fail_eliminate = "Neither instance or array allocation";) @@ -577,10 +685,10 @@ bool PhaseMacroExpand::can_eliminate_allocation(PhaseIterGVN* igvn, AllocateNode } } - if (can_eliminate && res != nullptr) { + while (can_eliminate && worklist.size() > 0) { BarrierSetC2 *bs = BarrierSet::barrier_set()->barrier_set_c2(); - for (DUIterator_Fast jmax, j = res->fast_outs(jmax); - j < jmax && can_eliminate; j++) { + res = worklist.pop(); + for (DUIterator_Fast jmax, j = res->fast_outs(jmax); j < jmax && can_eliminate; j++) { Node* use = res->fast_out(j); if (use->is_AddP()) { @@ -626,8 +734,32 @@ bool PhaseMacroExpand::can_eliminate_allocation(PhaseIterGVN* igvn, AllocateNode NOT_PRODUCT(fail_eliminate = "null or TOP memory";) can_eliminate = false; } else if (!reduce_merge_precheck) { + assert(!res->is_Phi() || !res->as_Phi()->can_be_inline_type(), "Inline type allocations should not have safepoint uses"); safepoints->append_if_missing(sfpt); } + } else if (use->is_InlineType() && use->as_InlineType()->get_oop() == res) { + // Look at uses + for (DUIterator_Fast kmax, k = use->fast_outs(kmax); k < kmax; k++) { + Node* u = use->fast_out(k); + if (u->is_InlineType()) { + // Use in flat field can be eliminated + InlineTypeNode* vt = u->as_InlineType(); + for (uint i = 0; i < vt->field_count(); ++i) { + if (vt->field_value(i) == use && !vt->field_is_flat(i)) { + can_eliminate = false; // Use in non-flat field + break; + } + } + } else { + // Add other uses to the worklist to process individually + worklist.push(use); + } + } + } else if (use->Opcode() == Op_StoreX && use->in(MemNode::Address) == res) { + // Store to mark word of inline type larval buffer + assert(res_type->is_inlinetypeptr(), "Unexpected store to mark word"); + } else if (res_type->is_inlinetypeptr() && (use->Opcode() == Op_MemBarRelease || use->Opcode() == Op_MemBarStoreStore)) { + // Inline type buffer allocations are followed by a membar } else if (reduce_merge_precheck && (use->is_Phi() || use->is_EncodeP() || use->Opcode() == Op_MemBarRelease || @@ -650,6 +782,9 @@ bool PhaseMacroExpand::can_eliminate_allocation(PhaseIterGVN* igvn, AllocateNode DEBUG_ONLY(disq_node = use;) } can_eliminate = false; + } else { + assert(use->Opcode() == Op_CastP2X, "should be"); + assert(!use->has_out_with(Op_OrL), "should have been removed because oop is never null"); } } } @@ -662,7 +797,7 @@ bool PhaseMacroExpand::can_eliminate_allocation(PhaseIterGVN* igvn, AllocateNode alloc->dump(); else res->dump(); - } else if (alloc->_is_scalar_replaceable) { + } else { tty->print("NotScalar (%s)", fail_eliminate); if (res == nullptr) alloc->dump(); @@ -732,22 +867,165 @@ void PhaseMacroExpand::undo_previous_scalarizations(GrowableArray isa_narrowoop()) { + // Enable "DecodeN(EncodeP(Allocate)) --> Allocate" transformation + // to be able scalar replace the allocation. + if (field_val->is_EncodeP()) { + field_val = field_val->in(1); + } else if (!field_val->is_InlineType()) { + field_val = transform_later(new DecodeNNode(field_val, field_val->get_ptr_type())); + } + } + + // Keep track of inline types to scalarize them later + if (field_val->is_InlineType()) { + value_worklist->push(field_val); + } else if (field_val->is_Phi()) { + PhiNode* phi = field_val->as_Phi(); + // Eagerly replace inline type phis now since we could be removing an inline type allocation where we must + // scalarize all its fields in safepoints. + field_val = phi->try_push_inline_types_down(&_igvn, true); + if (field_val->is_InlineType()) { + value_worklist->push(field_val); + } + } + sfpt->add_req(field_val); +} + +bool PhaseMacroExpand::add_array_elems_to_safepoint(AllocateNode* alloc, const TypeAryPtr* array_type, SafePointNode* sfpt, Unique_Node_List* value_worklist) { + const Type* elem_type = array_type->elem(); + BasicType basic_elem_type = elem_type->array_element_basic_type(); + + intptr_t elem_size; + uint header_size; + if (array_type->is_flat()) { + elem_size = array_type->flat_elem_size(); + header_size = arrayOopDesc::base_offset_in_bytes(T_FLAT_ELEMENT); + } else { + elem_size = type2aelembytes(basic_elem_type); + header_size = arrayOopDesc::base_offset_in_bytes(basic_elem_type); + } + + int n_elems = alloc->in(AllocateNode::ALength)->get_int(); + for (int elem_idx = 0; elem_idx < n_elems; elem_idx++) { + intptr_t elem_offset = header_size + elem_idx * elem_size; + const TypeAryPtr* elem_adr_type = array_type->with_offset(elem_offset); + Node* elem_val; + if (array_type->is_flat()) { + ciInlineKlass* elem_klass = elem_type->inline_klass(); + assert(elem_klass->maybe_flat_in_array(), "must be flat in array"); + elem_val = inline_type_from_mem(elem_klass, elem_adr_type, elem_idx, 0, array_type->is_null_free(), alloc, sfpt); + } else { + elem_val = value_from_mem(sfpt->memory(), sfpt->control(), basic_elem_type, elem_type, elem_adr_type, alloc); +#ifndef PRODUCT + if (PrintEliminateAllocations && elem_val == nullptr) { + tty->print("=== At SafePoint node %d can't find value of array element [%d]", sfpt->_idx, elem_idx); + tty->print(", which prevents elimination of: "); + alloc->dump(); + } +#endif // PRODUCT + } + if (elem_val == nullptr) { + return false; + } + + process_field_value_at_safepoint(elem_type, elem_val, sfpt, value_worklist); + } + + return true; +} + +// Recursively adds all flattened fields of a type 'iklass' inside 'base' to 'sfpt'. +// 'offset_minus_header' refers to the offset of the payload of 'iklass' inside 'base' minus the +// payload offset of 'iklass'. If 'base' is of type 'iklass' then 'offset_minus_header' == 0. +bool PhaseMacroExpand::add_inst_fields_to_safepoint(ciInstanceKlass* iklass, AllocateNode* alloc, Node* base, int offset_minus_header, SafePointNode* sfpt, Unique_Node_List* value_worklist) { + const TypeInstPtr* base_type = _igvn.type(base)->is_instptr(); + auto report_failure = [&](int offset) { +#ifndef PRODUCT + if (PrintEliminateAllocations) { + ciInstanceKlass* base_klass = base_type->instance_klass(); + ciField* flattened_field = base_klass->get_field_by_offset(offset, false); + assert(flattened_field != nullptr, "must have a field of type %s at offset %d", base_klass->name()->as_utf8(), offset); + tty->print("=== At SafePoint node %d can't find value of field: ", sfpt->_idx); + flattened_field->print(); + int field_idx = C->alias_type(flattened_field)->index(); + tty->print(" (alias_idx=%d)", field_idx); + tty->print(", which prevents elimination of: "); + base->dump(); + } +#endif // PRODUCT + }; + + for (int i = 0; i < iklass->nof_declared_nonstatic_fields(); i++) { + ciField* field = iklass->declared_nonstatic_field_at(i); + if (field->is_flat()) { + ciInlineKlass* fvk = field->type()->as_inline_klass(); + int field_offset_minus_header = offset_minus_header + field->offset_in_bytes() - fvk->payload_offset(); + bool success = add_inst_fields_to_safepoint(fvk, alloc, base, field_offset_minus_header, sfpt, value_worklist); + if (!success) { + return false; + } + + // The null marker of a field is added right after we scalarize that field + if (!field->is_null_free()) { + int nm_offset = offset_minus_header + field->null_marker_offset(); + Node* null_marker = value_from_mem(sfpt->memory(), sfpt->control(), T_BOOLEAN, TypeInt::BOOL, base_type->with_offset(nm_offset), alloc); + if (null_marker == nullptr) { + report_failure(nm_offset); + return false; + } + process_field_value_at_safepoint(TypeInt::BOOL, null_marker, sfpt, value_worklist); + } + + continue; + } + + int offset = offset_minus_header + field->offset_in_bytes(); + ciType* elem_type = field->type(); + BasicType basic_elem_type = field->layout_type(); + + const Type* field_type; + if (is_reference_type(basic_elem_type)) { + if (!elem_type->is_loaded()) { + field_type = TypeInstPtr::BOTTOM; + } else { + field_type = TypeOopPtr::make_from_klass(elem_type->as_klass()); + } + if (UseCompressedOops) { + field_type = field_type->make_narrowoop(); + basic_elem_type = T_NARROWOOP; + } + } else { + field_type = Type::get_const_basic_type(basic_elem_type); + } + + const TypeInstPtr* field_addr_type = base_type->add_offset(offset)->isa_instptr(); + Node* field_val = value_from_mem(sfpt->memory(), sfpt->control(), basic_elem_type, field_type, field_addr_type, alloc); + if (field_val == nullptr) { + report_failure(offset); + return false; + } + process_field_value_at_safepoint(field_type, field_val, sfpt, value_worklist); + } + + return true; +} + +SafePointScalarObjectNode* PhaseMacroExpand::create_scalarized_object_description(AllocateNode* alloc, SafePointNode* sfpt, + Unique_Node_List* value_worklist) { // Fields of scalar objs are referenced only at the end // of regular debuginfo at the last (youngest) JVMS. // Record relative start index. ciInstanceKlass* iklass = nullptr; - BasicType basic_elem_type = T_ILLEGAL; - const Type* field_type = nullptr; const TypeOopPtr* res_type = nullptr; int nfields = 0; - int array_base = 0; - int element_size = 0; uint first_ind = (sfpt->req() - sfpt->jvms()->scloff()); Node* res = alloc->result_cast(); assert(res == nullptr || res->is_CheckCastPP(), "unexpected AllocateNode result"); assert(sfpt->jvms() != nullptr, "missed JVMS"); + uint before_sfpt_req = sfpt->req(); if (res != nullptr) { // Could be null when there are no users res_type = _igvn.type(res)->isa_oopptr(); @@ -760,10 +1038,16 @@ SafePointScalarObjectNode* PhaseMacroExpand::create_scalarized_object_descriptio // find the array's elements which will be needed for safepoint debug information nfields = alloc->in(AllocateNode::ALength)->find_int_con(-1); assert(nfields >= 0, "must be an array klass."); - basic_elem_type = res_type->is_aryptr()->elem()->array_element_basic_type(); - array_base = arrayOopDesc::base_offset_in_bytes(basic_elem_type); - element_size = type2aelembytes(basic_elem_type); - field_type = res_type->is_aryptr()->elem(); + } + + if (res->bottom_type()->is_inlinetypeptr()) { + // Nullable inline types have a null marker field which is added to the safepoint when scalarizing them (see + // InlineTypeNode::make_scalar_in_safepoint()). When having circular inline types, we stop scalarizing at depth 1 + // to avoid an endless recursion. Therefore, we do not have a SafePointScalarObjectNode node here, yet. + // We are about to create a SafePointScalarObjectNode as if this is a normal object. Add an additional int input + // with value 1 which sets the null marker to true to indicate that the object is always non-null. This input is checked + // later in PhaseOutput::filLocArray() for inline types. + sfpt->add_req(_igvn.intcon(1)); } } @@ -771,88 +1055,28 @@ SafePointScalarObjectNode* PhaseMacroExpand::create_scalarized_object_descriptio sobj->init_req(0, C->root()); transform_later(sobj); - // Scan object's fields adding an input to the safepoint for each field. - for (int j = 0; j < nfields; j++) { - intptr_t offset; - ciField* field = nullptr; - if (iklass != nullptr) { - field = iklass->nonstatic_field_at(j); - offset = field->offset_in_bytes(); - ciType* elem_type = field->type(); - basic_elem_type = field->layout_type(); - - // The next code is taken from Parse::do_get_xxx(). - if (is_reference_type(basic_elem_type)) { - if (!elem_type->is_loaded()) { - field_type = TypeInstPtr::BOTTOM; - } else if (field != nullptr && field->is_static_constant()) { - ciObject* con = field->constant_value().as_object(); - // Do not "join" in the previous type; it doesn't add value, - // and may yield a vacuous result if the field is of interface type. - field_type = TypeOopPtr::make_from_constant(con)->isa_oopptr(); - assert(field_type != nullptr, "field singleton type must be consistent"); - } else { - field_type = TypeOopPtr::make_from_klass(elem_type->as_klass()); - } - if (UseCompressedOops) { - field_type = field_type->make_narrowoop(); - basic_elem_type = T_NARROWOOP; - } - } else { - field_type = Type::get_const_basic_type(basic_elem_type); - } - } else { - offset = array_base + j * (intptr_t)element_size; - } - - const TypeOopPtr *field_addr_type = res_type->add_offset(offset)->isa_oopptr(); - - Node *field_val = value_from_mem(sfpt->memory(), sfpt->control(), basic_elem_type, field_type, field_addr_type, alloc); - - // We weren't able to find a value for this field, - // give up on eliminating this allocation. - if (field_val == nullptr) { - uint last = sfpt->req() - 1; - for (int k = 0; k < j; k++) { - sfpt->del_req(last--); - } - _igvn._worklist.push(sfpt); - -#ifndef PRODUCT - if (PrintEliminateAllocations) { - if (field != nullptr) { - tty->print("=== At SafePoint node %d can't find value of field: ", sfpt->_idx); - field->print(); - int field_idx = C->get_alias_index(field_addr_type); - tty->print(" (alias_idx=%d)", field_idx); - } else { // Array's element - tty->print("=== At SafePoint node %d can't find value of array element [%d]", sfpt->_idx, j); - } - tty->print(", which prevents elimination of: "); - if (res == nullptr) - alloc->dump(); - else - res->dump(); - } -#endif + if (res == nullptr) { + sfpt->jvms()->set_endoff(sfpt->req()); + return sobj; + } - return nullptr; - } + bool success; + if (iklass == nullptr) { + success = add_array_elems_to_safepoint(alloc, res_type->is_aryptr(), sfpt, value_worklist); + } else { + success = add_inst_fields_to_safepoint(iklass, alloc, res, 0, sfpt, value_worklist); + } - if (UseCompressedOops && field_type->isa_narrowoop()) { - // Enable "DecodeN(EncodeP(Allocate)) --> Allocate" transformation - // to be able scalar replace the allocation. - if (field_val->is_EncodeP()) { - field_val = field_val->in(1); - } else { - field_val = transform_later(new DecodeNNode(field_val, field_val->get_ptr_type())); - } + // We weren't able to find a value for this field, remove all the fields added to the safepoint + if (!success) { + for (uint i = sfpt->req() - 1; i >= before_sfpt_req; i--) { + sfpt->del_req(i); } - sfpt->add_req(field_val); + _igvn._worklist.push(sfpt); + return nullptr; } sfpt->jvms()->set_endoff(sfpt->req()); - return sobj; } @@ -861,11 +1085,16 @@ bool PhaseMacroExpand::scalar_replacement(AllocateNode *alloc, GrowableArray safepoints_done; Node* res = alloc->result_cast(); assert(res == nullptr || res->is_CheckCastPP(), "unexpected AllocateNode result"); + const TypeOopPtr* res_type = nullptr; + if (res != nullptr) { // Could be null when there are no users + res_type = _igvn.type(res)->isa_oopptr(); + } // Process the safepoint uses + Unique_Node_List value_worklist; while (safepoints.length() > 0) { SafePointNode* sfpt = safepoints.pop(); - SafePointScalarObjectNode* sobj = create_scalarized_object_description(alloc, sfpt); + SafePointScalarObjectNode* sobj = create_scalarized_object_description(alloc, sfpt, &value_worklist); if (sobj == nullptr) { undo_previous_scalarizations(safepoints_done, alloc); @@ -881,7 +1110,14 @@ bool PhaseMacroExpand::scalar_replacement(AllocateNode *alloc, GrowableArray is_flat(); + for (uint i = 0; i < value_worklist.size(); ++i) { + InlineTypeNode* vt = value_worklist.at(i)->as_InlineType(); + vt->make_scalar_in_safepoints(&_igvn, allow_oop); + } return true; } @@ -897,9 +1133,14 @@ static void disconnect_projections(MultiNode* n, PhaseIterGVN& igvn) { } // Process users of eliminated allocation. -void PhaseMacroExpand::process_users_of_allocation(CallNode *alloc) { +void PhaseMacroExpand::process_users_of_allocation(CallNode *alloc, bool inline_alloc) { + Unique_Node_List worklist; Node* res = alloc->result_cast(); if (res != nullptr) { + worklist.push(res); + } + while (worklist.size() > 0) { + res = worklist.pop(); for (DUIterator_Last jmin, j = res->last_outs(jmin); j >= jmin; ) { Node *use = res->last_out(j); uint oc1 = res->outcnt(); @@ -909,18 +1150,14 @@ void PhaseMacroExpand::process_users_of_allocation(CallNode *alloc) { Node *n = use->last_out(k); uint oc2 = use->outcnt(); if (n->is_Store()) { -#ifdef ASSERT - // Verify that there is no dependent MemBarVolatile nodes, - // they should be removed during IGVN, see MemBarNode::Ideal(). - for (DUIterator_Fast pmax, p = n->fast_outs(pmax); - p < pmax; p++) { - Node* mb = n->fast_out(p); - assert(mb->is_Initialize() || !mb->is_MemBar() || - mb->req() <= MemBarNode::Precedent || - mb->in(MemBarNode::Precedent) != n, - "MemBarVolatile should be eliminated for non-escaping object"); + for (DUIterator_Fast pmax, p = n->fast_outs(pmax); p < pmax; p++) { + MemBarNode* mb = n->fast_out(p)->isa_MemBar(); + if (mb != nullptr && mb->req() <= MemBarNode::Precedent && mb->in(MemBarNode::Precedent) == n) { + // MemBarVolatiles should have been removed by MemBarNode::Ideal() for non-inline allocations + assert(inline_alloc, "MemBarVolatile should be eliminated for non-escaping object"); + mb->remove(&_igvn); + } } -#endif _igvn.replace_node(n, n->in(MemNode::Memory)); } else { eliminate_gc_barrier(n); @@ -944,12 +1181,11 @@ void PhaseMacroExpand::process_users_of_allocation(CallNode *alloc) { assert(ac->is_arraycopy_validated() || ac->is_copyof_validated() || ac->is_copyofrange_validated(), "unsupported"); - CallProjections callprojs; - ac->extract_projections(&callprojs, true); + CallProjections* callprojs = ac->extract_projections(true); - _igvn.replace_node(callprojs.fallthrough_ioproj, ac->in(TypeFunc::I_O)); - _igvn.replace_node(callprojs.fallthrough_memproj, ac->in(TypeFunc::Memory)); - _igvn.replace_node(callprojs.fallthrough_catchproj, ac->in(TypeFunc::Control)); + _igvn.replace_node(callprojs->fallthrough_ioproj, ac->in(TypeFunc::I_O)); + _igvn.replace_node(callprojs->fallthrough_memproj, ac->in(TypeFunc::Memory)); + _igvn.replace_node(callprojs->fallthrough_catchproj, ac->in(TypeFunc::Control)); // Set control to top. IGVN will remove the remaining projections ac->set_req(0, top()); @@ -966,6 +1202,29 @@ void PhaseMacroExpand::process_users_of_allocation(CallNode *alloc) { } } _igvn._worklist.push(ac); + } else if (use->is_InlineType()) { + assert(use->as_InlineType()->get_oop() == res, "unexpected inline type ptr use"); + // Cut off oop input and remove known instance id from type + _igvn.rehash_node_delayed(use); + use->as_InlineType()->set_oop(_igvn, _igvn.zerocon(T_OBJECT)); + const TypeOopPtr* toop = _igvn.type(use)->is_oopptr()->cast_to_instance_id(TypeOopPtr::InstanceBot); + _igvn.set_type(use, toop); + use->as_InlineType()->set_type(toop); + // Process users + for (DUIterator_Fast kmax, k = use->fast_outs(kmax); k < kmax; k++) { + Node* u = use->fast_out(k); + if (!u->is_InlineType()) { + worklist.push(u); + } + } + } else if (use->Opcode() == Op_StoreX && use->in(MemNode::Address) == res) { + // Store to mark word of inline type larval buffer + assert(inline_alloc, "Unexpected store to mark word"); + _igvn.replace_node(use, use->in(MemNode::Memory)); + } else if (use->Opcode() == Op_MemBarRelease || use->Opcode() == Op_MemBarStoreStore) { + // Inline type buffer allocations are followed by a membar + assert(inline_alloc, "Unexpected MemBarRelease"); + use->as_MemBar()->remove(&_igvn); } else { eliminate_gc_barrier(use); } @@ -978,21 +1237,21 @@ void PhaseMacroExpand::process_users_of_allocation(CallNode *alloc) { // // Process other users of allocation's projections // - if (_callprojs.resproj != nullptr && _callprojs.resproj->outcnt() != 0) { + if (_callprojs->resproj[0] != nullptr && _callprojs->resproj[0]->outcnt() != 0) { // First disconnect stores captured by Initialize node. // If Initialize node is eliminated first in the following code, // it will kill such stores and DUIterator_Last will assert. - for (DUIterator_Fast jmax, j = _callprojs.resproj->fast_outs(jmax); j < jmax; j++) { - Node* use = _callprojs.resproj->fast_out(j); + for (DUIterator_Fast jmax, j = _callprojs->resproj[0]->fast_outs(jmax); j < jmax; j++) { + Node* use = _callprojs->resproj[0]->fast_out(j); if (use->is_AddP()) { // raw memory addresses used only by the initialization _igvn.replace_node(use, C->top()); --j; --jmax; } } - for (DUIterator_Last jmin, j = _callprojs.resproj->last_outs(jmin); j >= jmin; ) { - Node* use = _callprojs.resproj->last_out(j); - uint oc1 = _callprojs.resproj->outcnt(); + for (DUIterator_Last jmin, j = _callprojs->resproj[0]->last_outs(jmin); j >= jmin; ) { + Node* use = _callprojs->resproj[0]->last_out(j); + uint oc1 = _callprojs->resproj[0]->outcnt(); if (use->is_Initialize()) { // Eliminate Initialize node. InitializeNode *init = use->as_Initialize(); @@ -1003,7 +1262,7 @@ void PhaseMacroExpand::process_users_of_allocation(CallNode *alloc) { #ifdef ASSERT // If the InitializeNode has no memory out, it will die, and tmp will become null Node* tmp = init->in(TypeFunc::Control); - assert(tmp == nullptr || tmp == _callprojs.fallthrough_catchproj, "allocation control projection"); + assert(tmp == nullptr || tmp == _callprojs->fallthrough_catchproj, "allocation control projection"); #endif } Node *mem_proj = init->proj_out_or_null(TypeFunc::Memory); @@ -1011,36 +1270,40 @@ void PhaseMacroExpand::process_users_of_allocation(CallNode *alloc) { Node *mem = init->in(TypeFunc::Memory); #ifdef ASSERT if (mem->is_MergeMem()) { - assert(mem->in(TypeFunc::Memory) == _callprojs.fallthrough_memproj, "allocation memory projection"); + assert(mem->in(TypeFunc::Memory) == _callprojs->fallthrough_memproj, "allocation memory projection"); } else { - assert(mem == _callprojs.fallthrough_memproj, "allocation memory projection"); + assert(mem == _callprojs->fallthrough_memproj, "allocation memory projection"); } #endif _igvn.replace_node(mem_proj, mem); } + } else if (use->Opcode() == Op_MemBarStoreStore) { + // Inline type buffer allocations are followed by a membar + assert(inline_alloc, "Unexpected MemBarStoreStore"); + use->as_MemBar()->remove(&_igvn); } else { assert(false, "only Initialize or AddP expected"); } - j -= (oc1 - _callprojs.resproj->outcnt()); + j -= (oc1 - _callprojs->resproj[0]->outcnt()); } } - if (_callprojs.fallthrough_catchproj != nullptr) { - _igvn.replace_node(_callprojs.fallthrough_catchproj, alloc->in(TypeFunc::Control)); + if (_callprojs->fallthrough_catchproj != nullptr) { + _igvn.replace_node(_callprojs->fallthrough_catchproj, alloc->in(TypeFunc::Control)); } - if (_callprojs.fallthrough_memproj != nullptr) { - _igvn.replace_node(_callprojs.fallthrough_memproj, alloc->in(TypeFunc::Memory)); + if (_callprojs->fallthrough_memproj != nullptr) { + _igvn.replace_node(_callprojs->fallthrough_memproj, alloc->in(TypeFunc::Memory)); } - if (_callprojs.catchall_memproj != nullptr) { - _igvn.replace_node(_callprojs.catchall_memproj, C->top()); + if (_callprojs->catchall_memproj != nullptr) { + _igvn.replace_node(_callprojs->catchall_memproj, C->top()); } - if (_callprojs.fallthrough_ioproj != nullptr) { - _igvn.replace_node(_callprojs.fallthrough_ioproj, alloc->in(TypeFunc::I_O)); + if (_callprojs->fallthrough_ioproj != nullptr) { + _igvn.replace_node(_callprojs->fallthrough_ioproj, alloc->in(TypeFunc::I_O)); } - if (_callprojs.catchall_ioproj != nullptr) { - _igvn.replace_node(_callprojs.catchall_ioproj, C->top()); + if (_callprojs->catchall_ioproj != nullptr) { + _igvn.replace_node(_callprojs->catchall_ioproj, C->top()); } - if (_callprojs.catchall_catchproj != nullptr) { - _igvn.replace_node(_callprojs.catchall_catchproj, C->top()); + if (_callprojs->catchall_catchproj != nullptr) { + _igvn.replace_node(_callprojs->catchall_catchproj, C->top()); } } @@ -1050,22 +1313,30 @@ bool PhaseMacroExpand::eliminate_allocate_node(AllocateNode *alloc) { // nice with JVMTI popframe. // We avoid this issue by eager reallocation when the popframe request // is received. - if (!EliminateAllocations || !alloc->_is_non_escaping) { + if (!EliminateAllocations) { return false; } Node* klass = alloc->in(AllocateNode::KlassNode); const TypeKlassPtr* tklass = _igvn.type(klass)->is_klassptr(); - Node* res = alloc->result_cast(); + + // Attempt to eliminate inline type buffer allocations + // regardless of usage and escape/replaceable status. + bool inline_alloc = tklass->isa_instklassptr() && + tklass->is_instklassptr()->instance_klass()->is_inlinetype(); + if (!alloc->_is_non_escaping && !inline_alloc) { + return false; + } // Eliminate boxing allocations which are not used // regardless scalar replaceable status. - bool boxing_alloc = C->eliminate_boxing() && + Node* res = alloc->result_cast(); + bool boxing_alloc = (res == nullptr) && C->eliminate_boxing() && tklass->isa_instklassptr() && tklass->is_instklassptr()->instance_klass()->is_box_klass(); - if (!alloc->_is_scalar_replaceable && (!boxing_alloc || (res != nullptr))) { + if (!alloc->_is_scalar_replaceable && !boxing_alloc && !inline_alloc) { return false; } - alloc->extract_projections(&_callprojs, false /*separate_io_proj*/, false /*do_asserts*/); + _callprojs = alloc->extract_projections(false /*separate_io_proj*/, false /*do_asserts*/); GrowableArray safepoints; if (!can_eliminate_allocation(&_igvn, alloc, &safepoints)) { @@ -1073,7 +1344,7 @@ bool PhaseMacroExpand::eliminate_allocate_node(AllocateNode *alloc) { } if (!alloc->_is_scalar_replaceable) { - assert(res == nullptr, "sanity"); + assert(res == nullptr || inline_alloc, "sanity"); // We can only eliminate allocation if all debug info references // are already replaced with SafePointScalarObject because // we can't search for a fields value without instance_id. @@ -1098,7 +1369,7 @@ bool PhaseMacroExpand::eliminate_allocate_node(AllocateNode *alloc) { log->tail("eliminate_allocation"); } - process_users_of_allocation(alloc); + process_users_of_allocation(alloc, inline_alloc); #ifndef PRODUCT if (PrintEliminateAllocations) { @@ -1120,9 +1391,9 @@ bool PhaseMacroExpand::eliminate_boxing_node(CallStaticJavaNode *boxing) { assert(boxing->result_cast() == nullptr, "unexpected boxing node result"); - boxing->extract_projections(&_callprojs, false /*separate_io_proj*/, false /*do_asserts*/); + _callprojs = boxing->extract_projections(false /*separate_io_proj*/, false /*do_asserts*/); - const TypeTuple* r = boxing->tf()->range(); + const TypeTuple* r = boxing->tf()->range_sig(); assert(r->cnt() > TypeFunc::Parms, "sanity"); const TypeInstPtr* t = r->field_at(TypeFunc::Parms)->isa_instptr(); assert(t != nullptr, "sanity"); @@ -1226,6 +1497,7 @@ Node* PhaseMacroExpand::make_store(Node* ctl, Node* mem, Node* base, int offset, void PhaseMacroExpand::expand_allocate_common( AllocateNode* alloc, // allocation node to be expanded Node* length, // array length for an array allocation + Node* init_val, // value to initialize the array with const TypeFunc* slow_call_type, // Type of slow call address slow_call_address, // Address of slow call Node* valid_length_test // whether length is valid or not @@ -1308,10 +1580,10 @@ void PhaseMacroExpand::expand_allocate_common( IfNode *toobig_iff = new IfNode(ctrl, initial_slow_test, PROB_MIN, COUNT_UNKNOWN); transform_later(toobig_iff); // Plug the failing-too-big test into the slow-path region - Node *toobig_true = new IfTrueNode( toobig_iff ); + Node* toobig_true = new IfTrueNode(toobig_iff); transform_later(toobig_true); slow_region ->init_req( too_big_or_final_path, toobig_true ); - toobig_false = new IfFalseNode( toobig_iff ); + toobig_false = new IfFalseNode(toobig_iff); transform_later(toobig_false); } else { // No initial test, just fall into next case @@ -1350,6 +1622,7 @@ void PhaseMacroExpand::expand_allocate_common( // Name successful fast-path variables Node* fast_oop_ctrl; Node* fast_oop_rawmem; + if (allocation_has_use) { Node* needgc_ctrl = nullptr; result_phi_rawoop = new PhiNode(result_region, TypeRawPtr::BOTTOM); @@ -1407,6 +1680,12 @@ void PhaseMacroExpand::expand_allocate_common( call->init_req(TypeFunc::Parms+0, klass_node); if (length != nullptr) { call->init_req(TypeFunc::Parms+1, length); + if (init_val != nullptr) { + call->init_req(TypeFunc::Parms+2, init_val); + } + } else { + // Let the runtime know if this is a larval allocation + call->init_req(TypeFunc::Parms+1, _igvn.intcon(alloc->_larval)); } // Copy debug information and adjust JVMState information, then replace @@ -1438,24 +1717,24 @@ void PhaseMacroExpand::expand_allocate_common( // // We are interested in the CatchProj nodes. // - call->extract_projections(&_callprojs, false /*separate_io_proj*/, false /*do_asserts*/); + _callprojs = call->extract_projections(false /*separate_io_proj*/, false /*do_asserts*/); // An allocate node has separate memory projections for the uses on // the control and i_o paths. Replace the control memory projection with // result_phi_rawmem (unless we are only generating a slow call when // both memory projections are combined) - if (expand_fast_path && _callprojs.fallthrough_memproj != nullptr) { - migrate_outs(_callprojs.fallthrough_memproj, result_phi_rawmem); + if (expand_fast_path && _callprojs->fallthrough_memproj != nullptr) { + _igvn.replace_in_uses(_callprojs->fallthrough_memproj, result_phi_rawmem); } // Now change uses of catchall_memproj to use fallthrough_memproj and delete // catchall_memproj so we end up with a call that has only 1 memory projection. - if (_callprojs.catchall_memproj != nullptr ) { - if (_callprojs.fallthrough_memproj == nullptr) { - _callprojs.fallthrough_memproj = new ProjNode(call, TypeFunc::Memory); - transform_later(_callprojs.fallthrough_memproj); + if (_callprojs->catchall_memproj != nullptr) { + if (_callprojs->fallthrough_memproj == nullptr) { + _callprojs->fallthrough_memproj = new ProjNode(call, TypeFunc::Memory); + transform_later(_callprojs->fallthrough_memproj); } - migrate_outs(_callprojs.catchall_memproj, _callprojs.fallthrough_memproj); - _igvn.remove_dead_node(_callprojs.catchall_memproj); + _igvn.replace_in_uses(_callprojs->catchall_memproj, _callprojs->fallthrough_memproj); + _igvn.remove_dead_node(_callprojs->catchall_memproj); } // An allocate node has separate i_o projections for the uses on the control @@ -1463,18 +1742,18 @@ void PhaseMacroExpand::expand_allocate_common( // otherwise incoming i_o become dead when only a slow call is generated // (it is different from memory projections where both projections are // combined in such case). - if (_callprojs.fallthrough_ioproj != nullptr) { - migrate_outs(_callprojs.fallthrough_ioproj, result_phi_i_o); + if (_callprojs->fallthrough_ioproj != nullptr) { + _igvn.replace_in_uses(_callprojs->fallthrough_ioproj, result_phi_i_o); } // Now change uses of catchall_ioproj to use fallthrough_ioproj and delete // catchall_ioproj so we end up with a call that has only 1 i_o projection. - if (_callprojs.catchall_ioproj != nullptr ) { - if (_callprojs.fallthrough_ioproj == nullptr) { - _callprojs.fallthrough_ioproj = new ProjNode(call, TypeFunc::I_O); - transform_later(_callprojs.fallthrough_ioproj); + if (_callprojs->catchall_ioproj != nullptr) { + if (_callprojs->fallthrough_ioproj == nullptr) { + _callprojs->fallthrough_ioproj = new ProjNode(call, TypeFunc::I_O); + transform_later(_callprojs->fallthrough_ioproj); } - migrate_outs(_callprojs.catchall_ioproj, _callprojs.fallthrough_ioproj); - _igvn.remove_dead_node(_callprojs.catchall_ioproj); + _igvn.replace_in_uses(_callprojs->catchall_ioproj, _callprojs->fallthrough_ioproj); + _igvn.remove_dead_node(_callprojs->catchall_ioproj); } // if we generated only a slow call, we are done @@ -1493,21 +1772,21 @@ void PhaseMacroExpand::expand_allocate_common( return; } - if (_callprojs.fallthrough_catchproj != nullptr) { - ctrl = _callprojs.fallthrough_catchproj->clone(); + if (_callprojs->fallthrough_catchproj != nullptr) { + ctrl = _callprojs->fallthrough_catchproj->clone(); transform_later(ctrl); - _igvn.replace_node(_callprojs.fallthrough_catchproj, result_region); + _igvn.replace_node(_callprojs->fallthrough_catchproj, result_region); } else { ctrl = top(); } Node *slow_result; - if (_callprojs.resproj == nullptr) { + if (_callprojs->resproj[0] == nullptr) { // no uses of the allocation result slow_result = top(); } else { - slow_result = _callprojs.resproj->clone(); + slow_result = _callprojs->resproj[0]->clone(); transform_later(slow_result); - _igvn.replace_node(_callprojs.resproj, result_phi_rawoop); + _igvn.replace_node(_callprojs->resproj[0], result_phi_rawoop); } // Plug slow-path into result merge point @@ -1517,7 +1796,7 @@ void PhaseMacroExpand::expand_allocate_common( result_phi_rawoop->init_req(slow_result_path, slow_result); transform_later(result_phi_rawoop); } - result_phi_rawmem->init_req(slow_result_path, _callprojs.fallthrough_memproj); + result_phi_rawmem->init_req(slow_result_path, _callprojs->fallthrough_memproj); transform_later(result_phi_rawmem); transform_later(result_phi_i_o); // This completes all paths into the result merge point @@ -1529,45 +1808,45 @@ void PhaseMacroExpand::yank_alloc_node(AllocateNode* alloc) { Node* mem = alloc->in(TypeFunc::Memory); Node* i_o = alloc->in(TypeFunc::I_O); - alloc->extract_projections(&_callprojs, false /*separate_io_proj*/, false /*do_asserts*/); - if (_callprojs.resproj != nullptr) { - for (DUIterator_Fast imax, i = _callprojs.resproj->fast_outs(imax); i < imax; i++) { - Node* use = _callprojs.resproj->fast_out(i); + _callprojs = alloc->extract_projections(false /*separate_io_proj*/, false /*do_asserts*/); + if (_callprojs->resproj[0] != nullptr) { + for (DUIterator_Fast imax, i = _callprojs->resproj[0]->fast_outs(imax); i < imax; i++) { + Node* use = _callprojs->resproj[0]->fast_out(i); use->isa_MemBar()->remove(&_igvn); --imax; --i; // back up iterator } - assert(_callprojs.resproj->outcnt() == 0, "all uses must be deleted"); - _igvn.remove_dead_node(_callprojs.resproj); + assert(_callprojs->resproj[0]->outcnt() == 0, "all uses must be deleted"); + _igvn.remove_dead_node(_callprojs->resproj[0]); } - if (_callprojs.fallthrough_catchproj != nullptr) { - migrate_outs(_callprojs.fallthrough_catchproj, ctrl); - _igvn.remove_dead_node(_callprojs.fallthrough_catchproj); + if (_callprojs->fallthrough_catchproj != nullptr) { + _igvn.replace_in_uses(_callprojs->fallthrough_catchproj, ctrl); + _igvn.remove_dead_node(_callprojs->fallthrough_catchproj); } - if (_callprojs.catchall_catchproj != nullptr) { - _igvn.rehash_node_delayed(_callprojs.catchall_catchproj); - _callprojs.catchall_catchproj->set_req(0, top()); + if (_callprojs->catchall_catchproj != nullptr) { + _igvn.rehash_node_delayed(_callprojs->catchall_catchproj); + _callprojs->catchall_catchproj->set_req(0, top()); } - if (_callprojs.fallthrough_proj != nullptr) { - Node* catchnode = _callprojs.fallthrough_proj->unique_ctrl_out(); + if (_callprojs->fallthrough_proj != nullptr) { + Node* catchnode = _callprojs->fallthrough_proj->unique_ctrl_out(); _igvn.remove_dead_node(catchnode); - _igvn.remove_dead_node(_callprojs.fallthrough_proj); + _igvn.remove_dead_node(_callprojs->fallthrough_proj); } - if (_callprojs.fallthrough_memproj != nullptr) { - migrate_outs(_callprojs.fallthrough_memproj, mem); - _igvn.remove_dead_node(_callprojs.fallthrough_memproj); + if (_callprojs->fallthrough_memproj != nullptr) { + _igvn.replace_in_uses(_callprojs->fallthrough_memproj, mem); + _igvn.remove_dead_node(_callprojs->fallthrough_memproj); } - if (_callprojs.fallthrough_ioproj != nullptr) { - migrate_outs(_callprojs.fallthrough_ioproj, i_o); - _igvn.remove_dead_node(_callprojs.fallthrough_ioproj); + if (_callprojs->fallthrough_ioproj != nullptr) { + _igvn.replace_in_uses(_callprojs->fallthrough_ioproj, i_o); + _igvn.remove_dead_node(_callprojs->fallthrough_ioproj); } - if (_callprojs.catchall_memproj != nullptr) { - _igvn.rehash_node_delayed(_callprojs.catchall_memproj); - _callprojs.catchall_memproj->set_req(0, top()); + if (_callprojs->catchall_memproj != nullptr) { + _igvn.rehash_node_delayed(_callprojs->catchall_memproj); + _callprojs->catchall_memproj->set_req(0, top()); } - if (_callprojs.catchall_ioproj != nullptr) { - _igvn.rehash_node_delayed(_callprojs.catchall_ioproj); - _callprojs.catchall_ioproj->set_req(0, top()); + if (_callprojs->catchall_ioproj != nullptr) { + _igvn.rehash_node_delayed(_callprojs->catchall_ioproj); + _callprojs->catchall_ioproj->set_req(0, top()); } #ifndef PRODUCT if (PrintEliminateAllocations) { @@ -1685,14 +1964,13 @@ void PhaseMacroExpand::expand_dtrace_alloc_probe(AllocateNode* alloc, Node* oop, // Helper for PhaseMacroExpand::expand_allocate_common. // Initializes the newly-allocated storage. -Node* -PhaseMacroExpand::initialize_object(AllocateNode* alloc, - Node* control, Node* rawmem, Node* object, - Node* klass_node, Node* length, - Node* size_in_bytes) { +Node* PhaseMacroExpand::initialize_object(AllocateNode* alloc, + Node* control, Node* rawmem, Node* object, + Node* klass_node, Node* length, + Node* size_in_bytes) { InitializeNode* init = alloc->initialization(); // Store the klass & mark bits - Node* mark_node = alloc->make_ideal_mark(&_igvn, object, control, rawmem); + Node* mark_node = alloc->make_ideal_mark(&_igvn, control, rawmem); if (!mark_node->is_Con()) { transform_later(mark_node); } @@ -1730,6 +2008,8 @@ PhaseMacroExpand::initialize_object(AllocateNode* alloc, // within an Allocate, and then (maybe or maybe not) clear some more later. if (!(UseTLAB && ZeroTLAB)) { rawmem = ClearArrayNode::clear_memory(control, rawmem, object, + alloc->in(AllocateNode::InitValue), + alloc->in(AllocateNode::RawInitValue), header_size, size_in_bytes, &_igvn); } @@ -1904,7 +2184,7 @@ Node* PhaseMacroExpand::prefetch_allocation(Node* i_o, Node*& needgc_false, void PhaseMacroExpand::expand_allocate(AllocateNode *alloc) { - expand_allocate_common(alloc, nullptr, + expand_allocate_common(alloc, nullptr, nullptr, OptoRuntime::new_instance_Type(), OptoRuntime::new_instance_Java(), nullptr); } @@ -1914,18 +2194,34 @@ void PhaseMacroExpand::expand_allocate_array(AllocateArrayNode *alloc) { Node* valid_length_test = alloc->in(AllocateNode::ValidLengthTest); InitializeNode* init = alloc->initialization(); Node* klass_node = alloc->in(AllocateNode::KlassNode); + Node* init_value = alloc->in(AllocateNode::InitValue); const TypeAryKlassPtr* ary_klass_t = _igvn.type(klass_node)->isa_aryklassptr(); + // TODO 8366668 Compute the VM type, is this even needed now that we set it earlier? Should we assert instead? + if (ary_klass_t && ary_klass_t->klass_is_exact() && ary_klass_t->exact_klass()->is_obj_array_klass()) { + ary_klass_t = ary_klass_t->get_vm_type(); + klass_node = makecon(ary_klass_t); + _igvn.replace_input_of(alloc, AllocateNode::KlassNode, klass_node); + } + const TypeFunc* slow_call_type; address slow_call_address; // Address of slow call if (init != nullptr && init->is_complete_with_arraycopy() && ary_klass_t && ary_klass_t->elem()->isa_klassptr() == nullptr) { // Don't zero type array during slow allocation in VM since // it will be initialized later by arraycopy in compiled code. slow_call_address = OptoRuntime::new_array_nozero_Java(); + slow_call_type = OptoRuntime::new_array_nozero_Type(); } else { slow_call_address = OptoRuntime::new_array_Java(); + slow_call_type = OptoRuntime::new_array_Type(); + + if (init_value == nullptr) { + init_value = _igvn.zerocon(T_OBJECT); + } else if (UseCompressedOops) { + init_value = transform_later(new DecodeNNode(init_value, init_value->bottom_type()->make_ptr())); + } } - expand_allocate_common(alloc, length, - OptoRuntime::new_array_Type(), + expand_allocate_common(alloc, length, init_value, + slow_call_type, slow_call_address, valid_length_test); } @@ -2137,16 +2433,16 @@ bool PhaseMacroExpand::eliminate_locking_node(AbstractLockNode *alock) { Node* ctrl = alock->in(TypeFunc::Control); guarantee(ctrl != nullptr, "missing control projection, cannot replace_node() with null"); - alock->extract_projections(&_callprojs, false /*separate_io_proj*/, false /*do_asserts*/); + _callprojs = alock->extract_projections(false /*separate_io_proj*/, false /*do_asserts*/); // There are 2 projections from the lock. The lock node will // be deleted when its last use is subsumed below. assert(alock->outcnt() == 2 && - _callprojs.fallthrough_proj != nullptr && - _callprojs.fallthrough_memproj != nullptr, + _callprojs->fallthrough_proj != nullptr && + _callprojs->fallthrough_memproj != nullptr, "Unexpected projections from Lock/Unlock"); - Node* fallthroughproj = _callprojs.fallthrough_proj; - Node* memproj_fallthrough = _callprojs.fallthrough_memproj; + Node* fallthroughproj = _callprojs->fallthrough_proj; + Node* memproj_fallthrough = _callprojs->fallthrough_memproj; // The memory projection from a lock/unlock is RawMem // The input to a Lock is merged memory, so extract its RawMem input @@ -2217,26 +2513,26 @@ void PhaseMacroExpand::expand_lock_node(LockNode *lock) { OptoRuntime::complete_monitor_locking_Java(), nullptr, slow_path, obj, box, nullptr); - call->extract_projections(&_callprojs, false /*separate_io_proj*/, false /*do_asserts*/); + _callprojs = call->extract_projections(false /*separate_io_proj*/, false /*do_asserts*/); // Slow path can only throw asynchronous exceptions, which are always // de-opted. So the compiler thinks the slow-call can never throw an // exception. If it DOES throw an exception we would need the debug // info removed first (since if it throws there is no monitor). - assert(_callprojs.fallthrough_ioproj == nullptr && _callprojs.catchall_ioproj == nullptr && - _callprojs.catchall_memproj == nullptr && _callprojs.catchall_catchproj == nullptr, "Unexpected projection from Lock"); + assert(_callprojs->fallthrough_ioproj == nullptr && _callprojs->catchall_ioproj == nullptr && + _callprojs->catchall_memproj == nullptr && _callprojs->catchall_catchproj == nullptr, "Unexpected projection from Lock"); // Capture slow path // disconnect fall-through projection from call and create a new one // hook up users of fall-through projection to region - Node *slow_ctrl = _callprojs.fallthrough_proj->clone(); + Node *slow_ctrl = _callprojs->fallthrough_proj->clone(); transform_later(slow_ctrl); - _igvn.hash_delete(_callprojs.fallthrough_proj); - _callprojs.fallthrough_proj->disconnect_inputs(C); + _igvn.hash_delete(_callprojs->fallthrough_proj); + _callprojs->fallthrough_proj->disconnect_inputs(C); region->init_req(1, slow_ctrl); // region inputs are now complete transform_later(region); - _igvn.replace_node(_callprojs.fallthrough_proj, region); + _igvn.replace_node(_callprojs->fallthrough_proj, region); Node *memproj = transform_later(new ProjNode(call, TypeFunc::Memory)); @@ -2244,7 +2540,7 @@ void PhaseMacroExpand::expand_lock_node(LockNode *lock) { transform_later(mem_phi); - _igvn.replace_node(_callprojs.fallthrough_memproj, mem_phi); + _igvn.replace_node(_callprojs->fallthrough_memproj, mem_phi); } //------------------------------expand_unlock_node---------------------- @@ -2277,31 +2573,253 @@ void PhaseMacroExpand::expand_unlock_node(UnlockNode *unlock) { CAST_FROM_FN_PTR(address, SharedRuntime::complete_monitor_unlocking_C), "complete_monitor_unlocking_C", slow_path, obj, box, thread); - call->extract_projections(&_callprojs, false /*separate_io_proj*/, false /*do_asserts*/); - assert(_callprojs.fallthrough_ioproj == nullptr && _callprojs.catchall_ioproj == nullptr && - _callprojs.catchall_memproj == nullptr && _callprojs.catchall_catchproj == nullptr, "Unexpected projection from Lock"); + _callprojs = call->extract_projections(false /*separate_io_proj*/, false /*do_asserts*/); + assert(_callprojs->fallthrough_ioproj == nullptr && _callprojs->catchall_ioproj == nullptr && + _callprojs->catchall_memproj == nullptr && _callprojs->catchall_catchproj == nullptr, "Unexpected projection from Lock"); // No exceptions for unlocking // Capture slow path // disconnect fall-through projection from call and create a new one // hook up users of fall-through projection to region - Node *slow_ctrl = _callprojs.fallthrough_proj->clone(); + Node *slow_ctrl = _callprojs->fallthrough_proj->clone(); transform_later(slow_ctrl); - _igvn.hash_delete(_callprojs.fallthrough_proj); - _callprojs.fallthrough_proj->disconnect_inputs(C); + _igvn.hash_delete(_callprojs->fallthrough_proj); + _callprojs->fallthrough_proj->disconnect_inputs(C); region->init_req(1, slow_ctrl); // region inputs are now complete transform_later(region); - _igvn.replace_node(_callprojs.fallthrough_proj, region); + _igvn.replace_node(_callprojs->fallthrough_proj, region); Node *memproj = transform_later(new ProjNode(call, TypeFunc::Memory) ); mem_phi->init_req(1, memproj ); mem_phi->init_req(2, mem); transform_later(mem_phi); - _igvn.replace_node(_callprojs.fallthrough_memproj, mem_phi); + _igvn.replace_node(_callprojs->fallthrough_memproj, mem_phi); } +// An inline type might be returned from the call but we don't know its +// type. Either we get a buffered inline type (and nothing needs to be done) +// or one of the values being returned is the klass of the inline type +// and we need to allocate an inline type instance of that type and +// initialize it with other values being returned. In that case, we +// first try a fast path allocation and initialize the value with the +// inline klass's pack handler or we fall back to a runtime call. +void PhaseMacroExpand::expand_mh_intrinsic_return(CallStaticJavaNode* call) { + assert(call->method()->is_method_handle_intrinsic(), "must be a method handle intrinsic call"); + Node* ret = call->proj_out_or_null(TypeFunc::Parms); + if (ret == nullptr) { + return; + } + const TypeFunc* tf = call->_tf; + const TypeTuple* domain = OptoRuntime::store_inline_type_fields_Type()->domain_cc(); + const TypeFunc* new_tf = TypeFunc::make(tf->domain_sig(), tf->domain_cc(), tf->range_sig(), domain); + call->_tf = new_tf; + // Make sure the change of type is applied before projections are processed by igvn + _igvn.set_type(call, call->Value(&_igvn)); + _igvn.set_type(ret, ret->Value(&_igvn)); + + // Before any new projection is added: + CallProjections* projs = call->extract_projections(true, true); + + // Create temporary hook nodes that will be replaced below. + // Add an input to prevent hook nodes from being dead. + Node* ctl = new Node(call); + Node* mem = new Node(ctl); + Node* io = new Node(ctl); + Node* ex_ctl = new Node(ctl); + Node* ex_mem = new Node(ctl); + Node* ex_io = new Node(ctl); + Node* res = new Node(ctl); + + // Allocate a new buffered inline type only if a new one is not returned + Node* cast = transform_later(new CastP2XNode(ctl, res)); + Node* mask = MakeConX(0x1); + Node* masked = transform_later(new AndXNode(cast, mask)); + Node* cmp = transform_later(new CmpXNode(masked, mask)); + Node* bol = transform_later(new BoolNode(cmp, BoolTest::eq)); + IfNode* allocation_iff = new IfNode(ctl, bol, PROB_MAX, COUNT_UNKNOWN); + transform_later(allocation_iff); + Node* allocation_ctl = transform_later(new IfTrueNode(allocation_iff)); + Node* no_allocation_ctl = transform_later(new IfFalseNode(allocation_iff)); + Node* no_allocation_res = transform_later(new CheckCastPPNode(no_allocation_ctl, res, TypeInstPtr::BOTTOM)); + + // Try to allocate a new buffered inline instance either from TLAB or eden space + Node* needgc_ctrl = nullptr; // needgc means slowcase, i.e. allocation failed + CallLeafNoFPNode* handler_call; + const bool alloc_in_place = UseTLAB; + if (alloc_in_place) { + Node* fast_oop_ctrl = nullptr; + Node* fast_oop_rawmem = nullptr; + Node* mask2 = MakeConX(-2); + Node* masked2 = transform_later(new AndXNode(cast, mask2)); + Node* rawklassptr = transform_later(new CastX2PNode(masked2)); + Node* klass_node = transform_later(new CheckCastPPNode(allocation_ctl, rawklassptr, TypeInstKlassPtr::OBJECT_OR_NULL)); + Node* layout_val = make_load(nullptr, mem, klass_node, in_bytes(Klass::layout_helper_offset()), TypeInt::INT, T_INT); + Node* size_in_bytes = ConvI2X(layout_val); + BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); + Node* fast_oop = bs->obj_allocate(this, mem, allocation_ctl, size_in_bytes, io, needgc_ctrl, + fast_oop_ctrl, fast_oop_rawmem, + AllocateInstancePrefetchLines); + // Allocation succeed, initialize buffered inline instance header firstly, + // and then initialize its fields with an inline class specific handler + Node* mark_word_node; + if (UseCompactObjectHeaders) { + // COH: We need to load the prototype from the klass at runtime since it encodes the klass pointer already. + mark_word_node = make_load(fast_oop_ctrl, fast_oop_rawmem, klass_node, in_bytes(Klass::prototype_header_offset()), TypeRawPtr::BOTTOM, T_ADDRESS); + } else { + // Otherwise, use the static prototype. + mark_word_node = makecon(TypeRawPtr::make((address)markWord::inline_type_prototype().value())); + } + + fast_oop_rawmem = make_store(fast_oop_ctrl, fast_oop_rawmem, fast_oop, oopDesc::mark_offset_in_bytes(), mark_word_node, T_ADDRESS); + if (!UseCompactObjectHeaders) { + // COH: Everything is encoded in the mark word, so nothing left to do. + fast_oop_rawmem = make_store(fast_oop_ctrl, fast_oop_rawmem, fast_oop, oopDesc::klass_offset_in_bytes(), klass_node, T_METADATA); + if (UseCompressedClassPointers) { + fast_oop_rawmem = make_store(fast_oop_ctrl, fast_oop_rawmem, fast_oop, oopDesc::klass_gap_offset_in_bytes(), intcon(0), T_INT); + } + } + Node* fixed_block = make_load(fast_oop_ctrl, fast_oop_rawmem, klass_node, in_bytes(InstanceKlass::adr_inlineklass_fixed_block_offset()), TypeRawPtr::BOTTOM, T_ADDRESS); + Node* pack_handler = make_load(fast_oop_ctrl, fast_oop_rawmem, fixed_block, in_bytes(InlineKlass::pack_handler_offset()), TypeRawPtr::BOTTOM, T_ADDRESS); + handler_call = new CallLeafNoFPNode(OptoRuntime::pack_inline_type_Type(), + nullptr, + "pack handler", + TypeRawPtr::BOTTOM); + handler_call->init_req(TypeFunc::Control, fast_oop_ctrl); + handler_call->init_req(TypeFunc::Memory, fast_oop_rawmem); + handler_call->init_req(TypeFunc::I_O, top()); + handler_call->init_req(TypeFunc::FramePtr, call->in(TypeFunc::FramePtr)); + handler_call->init_req(TypeFunc::ReturnAdr, top()); + handler_call->init_req(TypeFunc::Parms, pack_handler); + handler_call->init_req(TypeFunc::Parms+1, fast_oop); + } else { + needgc_ctrl = allocation_ctl; + } + + // Allocation failed, fall back to a runtime call + CallStaticJavaNode* slow_call = new CallStaticJavaNode(OptoRuntime::store_inline_type_fields_Type(), + StubRoutines::store_inline_type_fields_to_buf(), + "store_inline_type_fields", + TypePtr::BOTTOM); + slow_call->init_req(TypeFunc::Control, needgc_ctrl); + slow_call->init_req(TypeFunc::Memory, mem); + slow_call->init_req(TypeFunc::I_O, io); + slow_call->init_req(TypeFunc::FramePtr, call->in(TypeFunc::FramePtr)); + slow_call->init_req(TypeFunc::ReturnAdr, call->in(TypeFunc::ReturnAdr)); + slow_call->init_req(TypeFunc::Parms, res); + + Node* slow_ctl = transform_later(new ProjNode(slow_call, TypeFunc::Control)); + Node* slow_mem = transform_later(new ProjNode(slow_call, TypeFunc::Memory)); + Node* slow_io = transform_later(new ProjNode(slow_call, TypeFunc::I_O)); + Node* slow_res = transform_later(new ProjNode(slow_call, TypeFunc::Parms)); + Node* slow_catc = transform_later(new CatchNode(slow_ctl, slow_io, 2)); + Node* slow_norm = transform_later(new CatchProjNode(slow_catc, CatchProjNode::fall_through_index, CatchProjNode::no_handler_bci)); + Node* slow_excp = transform_later(new CatchProjNode(slow_catc, CatchProjNode::catch_all_index, CatchProjNode::no_handler_bci)); + + Node* ex_r = new RegionNode(3); + Node* ex_mem_phi = new PhiNode(ex_r, Type::MEMORY, TypePtr::BOTTOM); + Node* ex_io_phi = new PhiNode(ex_r, Type::ABIO); + ex_r->init_req(1, slow_excp); + ex_mem_phi->init_req(1, slow_mem); + ex_io_phi->init_req(1, slow_io); + ex_r->init_req(2, ex_ctl); + ex_mem_phi->init_req(2, ex_mem); + ex_io_phi->init_req(2, ex_io); + transform_later(ex_r); + transform_later(ex_mem_phi); + transform_later(ex_io_phi); + + // We don't know how many values are returned. This assumes the + // worst case, that all available registers are used. + for (uint i = TypeFunc::Parms+1; i < domain->cnt(); i++) { + if (domain->field_at(i) == Type::HALF) { + slow_call->init_req(i, top()); + if (alloc_in_place) { + handler_call->init_req(i+1, top()); + } + continue; + } + Node* proj = transform_later(new ProjNode(call, i)); + slow_call->init_req(i, proj); + if (alloc_in_place) { + handler_call->init_req(i+1, proj); + } + } + // We can safepoint at that new call + slow_call->copy_call_debug_info(&_igvn, call); + transform_later(slow_call); + if (alloc_in_place) { + transform_later(handler_call); + } + + Node* fast_ctl = nullptr; + Node* fast_res = nullptr; + MergeMemNode* fast_mem = nullptr; + if (alloc_in_place) { + fast_ctl = transform_later(new ProjNode(handler_call, TypeFunc::Control)); + Node* rawmem = transform_later(new ProjNode(handler_call, TypeFunc::Memory)); + fast_res = transform_later(new ProjNode(handler_call, TypeFunc::Parms)); + fast_mem = MergeMemNode::make(mem); + fast_mem->set_memory_at(Compile::AliasIdxRaw, rawmem); + transform_later(fast_mem); + } + + Node* r = new RegionNode(alloc_in_place ? 4 : 3); + Node* mem_phi = new PhiNode(r, Type::MEMORY, TypePtr::BOTTOM); + Node* io_phi = new PhiNode(r, Type::ABIO); + Node* res_phi = new PhiNode(r, TypeInstPtr::BOTTOM); + r->init_req(1, no_allocation_ctl); + mem_phi->init_req(1, mem); + io_phi->init_req(1, io); + res_phi->init_req(1, no_allocation_res); + r->init_req(2, slow_norm); + mem_phi->init_req(2, slow_mem); + io_phi->init_req(2, slow_io); + res_phi->init_req(2, slow_res); + if (alloc_in_place) { + r->init_req(3, fast_ctl); + mem_phi->init_req(3, fast_mem); + io_phi->init_req(3, io); + res_phi->init_req(3, fast_res); + } + transform_later(r); + transform_later(mem_phi); + transform_later(io_phi); + transform_later(res_phi); + + // Do not let stores that initialize this buffer be reordered with a subsequent + // store that would make this buffer accessible by other threads. + MemBarNode* mb = MemBarNode::make(C, Op_MemBarStoreStore, Compile::AliasIdxBot); + transform_later(mb); + mb->init_req(TypeFunc::Memory, mem_phi); + mb->init_req(TypeFunc::Control, r); + r = new ProjNode(mb, TypeFunc::Control); + transform_later(r); + mem_phi = new ProjNode(mb, TypeFunc::Memory); + transform_later(mem_phi); + + assert(projs->nb_resproj == 1, "unexpected number of results"); + _igvn.replace_in_uses(projs->fallthrough_catchproj, r); + _igvn.replace_in_uses(projs->fallthrough_memproj, mem_phi); + _igvn.replace_in_uses(projs->fallthrough_ioproj, io_phi); + _igvn.replace_in_uses(projs->resproj[0], res_phi); + _igvn.replace_in_uses(projs->catchall_catchproj, ex_r); + _igvn.replace_in_uses(projs->catchall_memproj, ex_mem_phi); + _igvn.replace_in_uses(projs->catchall_ioproj, ex_io_phi); + // The CatchNode should not use the ex_io_phi. Re-connect it to the catchall_ioproj. + Node* cn = projs->fallthrough_catchproj->in(0); + _igvn.replace_input_of(cn, 1, projs->catchall_ioproj); + + _igvn.replace_node(ctl, projs->fallthrough_catchproj); + _igvn.replace_node(mem, projs->fallthrough_memproj); + _igvn.replace_node(io, projs->fallthrough_ioproj); + _igvn.replace_node(res, projs->resproj[0]); + _igvn.replace_node(ex_ctl, projs->catchall_catchproj); + _igvn.replace_node(ex_mem, projs->catchall_memproj); + _igvn.replace_node(ex_io, projs->catchall_ioproj); + } + void PhaseMacroExpand::expand_subtypecheck_node(SubTypeCheckNode *check) { assert(check->in(SubTypeCheckNode::Control) == nullptr, "should be pinned"); Node* bol = check->unique_out(); @@ -2327,7 +2845,7 @@ void PhaseMacroExpand::expand_subtypecheck_node(SubTypeCheckNode *check) { subklass = obj_or_subklass; } else { Node* k_adr = basic_plus_adr(obj_or_subklass, oopDesc::klass_offset_in_bytes()); - subklass = _igvn.transform(LoadKlassNode::make(_igvn, C->immutable_memory(), k_adr, TypeInstPtr::KLASS)); + subklass = _igvn.transform(LoadKlassNode::make(_igvn, C->immutable_memory(), k_adr, TypeInstPtr::KLASS, TypeInstKlassPtr::OBJECT)); } Node* not_subtype_ctrl = Phase::gen_subtype_check(subklass, superklass, &ctrl, nullptr, _igvn, check->method(), check->bci()); @@ -2339,6 +2857,119 @@ void PhaseMacroExpand::expand_subtypecheck_node(SubTypeCheckNode *check) { _igvn.replace_node(check, C->top()); } +// FlatArrayCheckNode (array1 array2 ...) is expanded into: +// +// long mark = array1.mark | array2.mark | ...; +// long locked_bit = markWord::unlocked_value & array1.mark & array2.mark & ...; +// if (locked_bit == 0) { +// // One array is locked, load prototype header from the klass +// mark = array1.klass.proto | array2.klass.proto | ... +// } +// if ((mark & markWord::flat_array_bit_in_place) == 0) { +// ... +// } +void PhaseMacroExpand::expand_flatarraycheck_node(FlatArrayCheckNode* check) { + bool array_inputs = _igvn.type(check->in(FlatArrayCheckNode::ArrayOrKlass))->isa_oopptr() != nullptr; + if (array_inputs) { + Node* mark = MakeConX(0); + Node* locked_bit = MakeConX(markWord::unlocked_value); + Node* mem = check->in(FlatArrayCheckNode::Memory); + for (uint i = FlatArrayCheckNode::ArrayOrKlass; i < check->req(); ++i) { + Node* ary = check->in(i); + const TypeOopPtr* t = _igvn.type(ary)->isa_oopptr(); + assert(t != nullptr, "Mixing array and klass inputs"); + assert(!t->is_flat() && !t->is_not_flat(), "Should have been optimized out"); + Node* mark_adr = basic_plus_adr(ary, oopDesc::mark_offset_in_bytes()); + Node* mark_load = _igvn.transform(LoadNode::make(_igvn, nullptr, mem, mark_adr, mark_adr->bottom_type()->is_ptr(), TypeX_X, TypeX_X->basic_type(), MemNode::unordered)); + mark = _igvn.transform(new OrXNode(mark, mark_load)); + locked_bit = _igvn.transform(new AndXNode(locked_bit, mark_load)); + } + assert(!mark->is_Con(), "Should have been optimized out"); + Node* cmp = _igvn.transform(new CmpXNode(locked_bit, MakeConX(0))); + Node* is_unlocked = _igvn.transform(new BoolNode(cmp, BoolTest::ne)); + + // BoolNode might be shared, replace each if user + Node* old_bol = check->unique_out(); + assert(old_bol->is_Bool() && old_bol->as_Bool()->_test._test == BoolTest::ne, "unexpected condition"); + for (DUIterator_Last imin, i = old_bol->last_outs(imin); i >= imin; --i) { + IfNode* old_iff = old_bol->last_out(i)->as_If(); + Node* ctrl = old_iff->in(0); + RegionNode* region = new RegionNode(3); + Node* mark_phi = new PhiNode(region, TypeX_X); + + // Check if array is unlocked + IfNode* iff = _igvn.transform(new IfNode(ctrl, is_unlocked, PROB_MAX, COUNT_UNKNOWN))->as_If(); + + // Unlocked: Use bits from mark word + region->init_req(1, _igvn.transform(new IfTrueNode(iff))); + mark_phi->init_req(1, mark); + + // Locked: Load prototype header from klass + ctrl = _igvn.transform(new IfFalseNode(iff)); + Node* proto = MakeConX(0); + for (uint i = FlatArrayCheckNode::ArrayOrKlass; i < check->req(); ++i) { + Node* ary = check->in(i); + // Make loads control dependent to make sure they are only executed if array is locked + Node* klass_adr = basic_plus_adr(ary, oopDesc::klass_offset_in_bytes()); + Node* klass = _igvn.transform(LoadKlassNode::make(_igvn, C->immutable_memory(), klass_adr, TypeInstPtr::KLASS, TypeInstKlassPtr::OBJECT)); + Node* proto_adr = basic_plus_adr(klass, in_bytes(Klass::prototype_header_offset())); + Node* proto_load = _igvn.transform(LoadNode::make(_igvn, ctrl, C->immutable_memory(), proto_adr, proto_adr->bottom_type()->is_ptr(), TypeX_X, TypeX_X->basic_type(), MemNode::unordered)); + proto = _igvn.transform(new OrXNode(proto, proto_load)); + } + region->init_req(2, ctrl); + mark_phi->init_req(2, proto); + + // Check if flat array bits are set + Node* mask = MakeConX(markWord::flat_array_bit_in_place); + Node* masked = _igvn.transform(new AndXNode(_igvn.transform(mark_phi), mask)); + cmp = _igvn.transform(new CmpXNode(masked, MakeConX(0))); + Node* is_not_flat = _igvn.transform(new BoolNode(cmp, BoolTest::eq)); + + ctrl = _igvn.transform(region); + iff = _igvn.transform(new IfNode(ctrl, is_not_flat, PROB_MAX, COUNT_UNKNOWN))->as_If(); + _igvn.replace_node(old_iff, iff); + } + _igvn.replace_node(check, C->top()); + } else { + // Fall back to layout helper check + Node* lhs = intcon(0); + for (uint i = FlatArrayCheckNode::ArrayOrKlass; i < check->req(); ++i) { + Node* array_or_klass = check->in(i); + Node* klass = nullptr; + const TypePtr* t = _igvn.type(array_or_klass)->is_ptr(); + assert(!t->is_flat() && !t->is_not_flat(), "Should have been optimized out"); + if (t->isa_oopptr() != nullptr) { + Node* klass_adr = basic_plus_adr(array_or_klass, oopDesc::klass_offset_in_bytes()); + klass = transform_later(LoadKlassNode::make(_igvn, C->immutable_memory(), klass_adr, TypeInstPtr::KLASS, TypeInstKlassPtr::OBJECT)); + } else { + assert(t->isa_klassptr(), "Unexpected input type"); + klass = array_or_klass; + } + Node* lh_addr = basic_plus_adr(klass, in_bytes(Klass::layout_helper_offset())); + Node* lh_val = _igvn.transform(LoadNode::make(_igvn, nullptr, C->immutable_memory(), lh_addr, lh_addr->bottom_type()->is_ptr(), TypeInt::INT, T_INT, MemNode::unordered)); + lhs = _igvn.transform(new OrINode(lhs, lh_val)); + } + Node* masked = transform_later(new AndINode(lhs, intcon(Klass::_lh_array_tag_flat_value_bit_inplace))); + Node* cmp = transform_later(new CmpINode(masked, intcon(0))); + Node* bol = transform_later(new BoolNode(cmp, BoolTest::eq)); + Node* m2b = transform_later(new Conv2BNode(masked)); + // The matcher expects the input to If nodes to be produced by a Bool(CmpI..) + // pattern, but the input to other potential users (e.g. Phi) to be some + // other pattern (e.g. a Conv2B node, possibly idealized as a CMoveI). + Node* old_bol = check->unique_out(); + for (DUIterator_Last imin, i = old_bol->last_outs(imin); i >= imin; --i) { + Node* user = old_bol->last_out(i); + for (uint j = 0; j < user->req(); j++) { + Node* n = user->in(j); + if (n == old_bol) { + _igvn.replace_input_of(user, j, user->is_If() ? bol : m2b); + } + } + } + _igvn.replace_node(check, C->top()); + } +} + // Perform refining of strip mined loop nodes in the macro nodes list. void PhaseMacroExpand::refine_strip_mined_loop_macro_nodes() { for (int i = C->macro_count(); i > 0; i--) { @@ -2351,61 +2982,48 @@ void PhaseMacroExpand::refine_strip_mined_loop_macro_nodes() { //---------------------------eliminate_macro_nodes---------------------- // Eliminate scalar replaced allocations and associated locks. -void PhaseMacroExpand::eliminate_macro_nodes() { - if (C->macro_count() == 0) +void PhaseMacroExpand::eliminate_macro_nodes(bool eliminate_locks) { + if (C->macro_count() == 0) { return; + } if (StressMacroElimination) { C->shuffle_macro_nodes(); } NOT_PRODUCT(int membar_before = count_MemBar(C);) - // Before elimination may re-mark (change to Nested or NonEscObj) - // all associated (same box and obj) lock and unlock nodes. - int cnt = C->macro_count(); - for (int i=0; i < cnt; i++) { - Node *n = C->macro_node(i); - if (n->is_AbstractLock()) { // Lock and Unlock nodes - mark_eliminated_locking_nodes(n->as_AbstractLock()); + int iteration = 0; + while (C->macro_count() > 0) { + if (iteration++ > 100) { + assert(false, "Too slow convergence of macro elimination"); + break; } - } - // Re-marking may break consistency of Coarsened locks. - if (!C->coarsened_locks_consistent()) { - return; // recompile without Coarsened locks if broken - } else { - // After coarsened locks are eliminated locking regions - // become unbalanced. We should not execute any more - // locks elimination optimizations on them. - C->mark_unbalanced_boxes(); - } - // First, attempt to eliminate locks - bool progress = true; - while (progress) { - progress = false; - for (int i = C->macro_count(); i > 0; i = MIN2(i - 1, C->macro_count())) { // more than 1 element can be eliminated at once - Node* n = C->macro_node(i - 1); - bool success = false; - DEBUG_ONLY(int old_macro_count = C->macro_count();) - if (n->is_AbstractLock()) { - success = eliminate_locking_node(n->as_AbstractLock()); -#ifndef PRODUCT - if (success && PrintOptoStatistics) { - Atomic::inc(&PhaseMacroExpand::_monitor_objects_removed_counter); + // Postpone lock elimination to after EA when most allocations are eliminated + // because they might block lock elimination if their escape state isn't + // determined yet and we only got one chance at eliminating the lock. + if (eliminate_locks) { + // Before elimination may re-mark (change to Nested or NonEscObj) + // all associated (same box and obj) lock and unlock nodes. + int cnt = C->macro_count(); + for (int i=0; i < cnt; i++) { + Node *n = C->macro_node(i); + if (n->is_AbstractLock()) { // Lock and Unlock nodes + mark_eliminated_locking_nodes(n->as_AbstractLock()); } -#endif } - assert(success == (C->macro_count() < old_macro_count), "elimination reduces macro count"); - progress = progress || success; - if (success) { - C->print_method(PHASE_AFTER_MACRO_ELIMINATION_STEP, 5, n); + // Re-marking may break consistency of Coarsened locks. + if (!C->coarsened_locks_consistent()) { + return; // recompile without Coarsened locks if broken + } else { + // After coarsened locks are eliminated locking regions + // become unbalanced. We should not execute any more + // locks elimination optimizations on them. + C->mark_unbalanced_boxes(); } } - } - // Next, attempt to eliminate allocations - progress = true; - while (progress) { - progress = false; + + bool progress = false; for (int i = C->macro_count(); i > 0; i = MIN2(i - 1, C->macro_count())) { // more than 1 element can be eliminated at once Node* n = C->macro_node(i - 1); bool success = false; @@ -2420,12 +3038,23 @@ void PhaseMacroExpand::eliminate_macro_nodes() { } #endif break; - case Node::Class_CallStaticJava: - success = eliminate_boxing_node(n->as_CallStaticJava()); + case Node::Class_CallStaticJava: { + CallStaticJavaNode* call = n->as_CallStaticJava(); + if (!call->method()->is_method_handle_intrinsic()) { + success = eliminate_boxing_node(n->as_CallStaticJava()); + } break; + } case Node::Class_Lock: case Node::Class_Unlock: - assert(!n->as_AbstractLock()->is_eliminated(), "sanity"); + if (eliminate_locks) { + success = eliminate_locking_node(n->as_AbstractLock()); +#ifndef PRODUCT + if (success && PrintOptoStatistics) { + Atomic::inc(&PhaseMacroExpand::_monitor_objects_removed_counter); + } +#endif + } break; case Node::Class_ArrayCopy: break; @@ -2435,6 +3064,8 @@ void PhaseMacroExpand::eliminate_macro_nodes() { break; case Node::Class_Opaque1: break; + case Node::Class_FlatArrayCheck: + break; default: assert(n->Opcode() == Op_LoopLimit || n->Opcode() == Op_ModD || @@ -2452,6 +3083,22 @@ void PhaseMacroExpand::eliminate_macro_nodes() { C->print_method(PHASE_AFTER_MACRO_ELIMINATION_STEP, 5, n); } } + + // Ensure the graph after PhaseMacroExpand::eliminate_macro_nodes is canonical (no igvn + // transformation is pending). If an allocation is used only in safepoints, elimination of + // other macro nodes can remove all these safepoints, allowing the allocation to be removed. + // Hence after igvn we retry removing macro nodes if some progress that has been made in this + // iteration. + _igvn.set_delay_transform(false); + _igvn.optimize(); + if (C->failing()) { + return; + } + _igvn.set_delay_transform(true); + + if (!progress) { + break; + } } #ifndef PRODUCT if (PrintOptoStatistics) { @@ -2480,10 +3127,13 @@ void PhaseMacroExpand::eliminate_opaque_looplimit_macro_nodes() { _igvn._worklist.push(n); success = true; } else if (n->Opcode() == Op_CallStaticJava) { - // Remove it from macro list and put on IGVN worklist to optimize. - C->remove_macro_node(n); - _igvn._worklist.push(n); - success = true; + CallStaticJavaNode* call = n->as_CallStaticJava(); + if (!call->method()->is_method_handle_intrinsic()) { + // Remove it from macro list and put on IGVN worklist to optimize. + C->remove_macro_node(n); + _igvn._worklist.push(n); + success = true; + } } else if (n->is_Opaque1()) { _igvn.replace_node(n, n->in(1)); success = true; @@ -2592,6 +3242,13 @@ bool PhaseMacroExpand::expand_macro_nodes() { case Node::Class_SubTypeCheck: expand_subtypecheck_node(n->as_SubTypeCheck()); break; + case Node::Class_CallStaticJava: + expand_mh_intrinsic_return(n->as_CallStaticJava()); + C->remove_macro_node(n); + break; + case Node::Class_FlatArrayCheck: + expand_flatarraycheck_node(n->as_FlatArrayCheck()); + break; default: switch (n->Opcode()) { case Op_ModD: @@ -2604,7 +3261,7 @@ bool PhaseMacroExpand::expand_macro_nodes() { call->init_req(TypeFunc::Memory, C->top()); call->init_req(TypeFunc::ReturnAdr, C->top()); call->init_req(TypeFunc::FramePtr, C->top()); - for (unsigned int i = 0; i < mod_macro->tf()->domain()->cnt() - TypeFunc::Parms; i++) { + for (unsigned int i = 0; i < mod_macro->tf()->domain_cc()->cnt() - TypeFunc::Parms; i++) { call->init_req(TypeFunc::Parms + i, mod_macro->in(TypeFunc::Parms + i)); } _igvn.replace_node(mod_macro, call); diff --git a/src/hotspot/share/opto/macro.hpp b/src/hotspot/share/opto/macro.hpp index c899bed567c..319974bea14 100644 --- a/src/hotspot/share/opto/macro.hpp +++ b/src/hotspot/share/opto/macro.hpp @@ -25,6 +25,9 @@ #ifndef SHARE_OPTO_MACRO_HPP #define SHARE_OPTO_MACRO_HPP +#include "ci/ciInstanceKlass.hpp" +#include "opto/callnode.hpp" +#include "opto/node.hpp" #include "opto/phase.hpp" class AllocateNode; @@ -81,24 +84,30 @@ class PhaseMacroExpand : public Phase { private: // projections extracted from a call node - CallProjections _callprojs; + CallProjections* _callprojs; void expand_allocate(AllocateNode *alloc); void expand_allocate_array(AllocateArrayNode *alloc); void expand_allocate_common(AllocateNode* alloc, Node* length, + Node* init_val, const TypeFunc* slow_call_type, address slow_call_address, Node* valid_length_test); void yank_alloc_node(AllocateNode* alloc); - Node *value_from_mem(Node *mem, Node *ctl, BasicType ft, const Type *ftype, const TypeOopPtr *adr_t, AllocateNode *alloc); - Node *value_from_mem_phi(Node *mem, BasicType ft, const Type *ftype, const TypeOopPtr *adr_t, AllocateNode *alloc, Node_Stack *value_phis, int level); + + void process_field_value_at_safepoint(const Type* field_type, Node* field_val, SafePointNode* sfpt, Unique_Node_List* value_worklist); + bool add_array_elems_to_safepoint(AllocateNode* alloc, const TypeAryPtr* array_type, SafePointNode* sfpt, Unique_Node_List* value_worklist); + bool add_inst_fields_to_safepoint(ciInstanceKlass* iklass, AllocateNode* alloc, Node* base, int offset_minus_header, SafePointNode* sfpt, Unique_Node_List* value_worklist); + Node* value_from_mem(Node* mem, Node* ctl, BasicType ft, const Type* ftype, const TypeOopPtr* adr_t, AllocateNode* alloc); + Node* value_from_mem_phi(Node* mem, BasicType ft, const Type* ftype, const TypeOopPtr* adr_t, AllocateNode* alloc, Node_Stack* value_phis, int level); + Node* inline_type_from_mem(ciInlineKlass* vk, const TypeAryPtr* elem_adr_type, int elem_idx, int offset_in_element, bool null_free, AllocateNode* alloc, SafePointNode* sfpt); bool eliminate_boxing_node(CallStaticJavaNode *boxing); bool eliminate_allocate_node(AllocateNode *alloc); void undo_previous_scalarizations(GrowableArray safepoints_done, AllocateNode* alloc); bool scalar_replacement(AllocateNode *alloc, GrowableArray & safepoints); - void process_users_of_allocation(CallNode *alloc); + void process_users_of_allocation(CallNode *alloc, bool inline_alloc = false); void eliminate_gc_barrier(Node *p2x); void mark_eliminated_box(Node* box, Node* obj); @@ -106,6 +115,7 @@ class PhaseMacroExpand : public Phase { bool eliminate_locking_node(AbstractLockNode *alock); void expand_lock_node(LockNode *lock); void expand_unlock_node(UnlockNode *unlock); + void expand_mh_intrinsic_return(CallStaticJavaNode* call); // More helper methods modeled after GraphKit for array copy void insert_mem_bar(Node** ctrl, Node** mem, int opcode, int alias_idx, Node* precedent = nullptr); @@ -115,6 +125,7 @@ class PhaseMacroExpand : public Phase { // helper methods modeled after LibraryCallKit for array copy Node* generate_guard(Node** ctrl, Node* test, RegionNode* region, float true_prob); Node* generate_slow_guard(Node** ctrl, Node* test, RegionNode* region); + Node* generate_fair_guard(Node** ctrl, Node* test, RegionNode* region); void generate_partial_inlining_block(Node** ctrl, MergeMemNode** mem, const TypePtr* adr_type, RegionNode** exit_block, Node** result_memory, Node* length, @@ -125,6 +136,10 @@ class PhaseMacroExpand : public Phase { // More helper methods for array copy Node* generate_nonpositive_guard(Node** ctrl, Node* index, bool never_negative); + Node* mark_word_test(Node** ctrl, Node* obj, MergeMemNode* mem, uintptr_t mask_val, RegionNode* region); + Node* generate_flat_array_guard(Node** ctrl, Node* array, MergeMemNode* mem, RegionNode* region); + Node* generate_null_free_array_guard(Node** ctrl, Node* array, MergeMemNode* mem, RegionNode* region); + void finish_arraycopy_call(Node* call, Node** ctrl, MergeMemNode** mem, const TypePtr* adr_type); Node* generate_arraycopy(ArrayCopyNode *ac, AllocateArrayNode* alloc, @@ -134,12 +149,15 @@ class PhaseMacroExpand : public Phase { Node* src, Node* src_offset, Node* dest, Node* dest_offset, Node* copy_length, + Node* dest_length, bool disjoint_bases = false, bool length_never_negative = false, RegionNode* slow_region = nullptr); void generate_clear_array(Node* ctrl, MergeMemNode* merge_mem, const TypePtr* adr_type, Node* dest, + Node* val, + Node* raw_val, BasicType basic_elem_type, Node* slice_idx, Node* slice_len, @@ -175,11 +193,15 @@ class PhaseMacroExpand : public Phase { Node* src, Node* src_offset, Node* dest, Node* dest_offset, Node* copy_length, bool dest_uninitialized); - + const TypePtr* adjust_for_flat_array(const TypeAryPtr* top_dest, Node*& src_offset, + Node*& dest_offset, Node*& length, BasicType& dest_elem, + Node*& dest_length); void expand_arraycopy_node(ArrayCopyNode *ac); void expand_subtypecheck_node(SubTypeCheckNode *check); + void expand_flatarraycheck_node(FlatArrayCheckNode* check); + int replace_input(Node *use, Node *oldref, Node *newref); void migrate_outs(Node *old, Node *target); Node* opt_bits_test(Node* ctrl, Node* region, int edge, Node* word); @@ -201,11 +223,11 @@ class PhaseMacroExpand : public Phase { } void refine_strip_mined_loop_macro_nodes(); - void eliminate_macro_nodes(); + void eliminate_macro_nodes(bool eliminate_locks = true); bool expand_macro_nodes(); void eliminate_opaque_looplimit_macro_nodes(); - SafePointScalarObjectNode* create_scalarized_object_description(AllocateNode *alloc, SafePointNode* sfpt); + SafePointScalarObjectNode* create_scalarized_object_description(AllocateNode *alloc, SafePointNode* sfpt, Unique_Node_List* value_worklist); static bool can_eliminate_allocation(PhaseIterGVN *igvn, AllocateNode *alloc, GrowableArray *safepoints); diff --git a/src/hotspot/share/opto/macroArrayCopy.cpp b/src/hotspot/share/opto/macroArrayCopy.cpp index 10de940c0c2..be3b79237e7 100644 --- a/src/hotspot/share/opto/macroArrayCopy.cpp +++ b/src/hotspot/share/opto/macroArrayCopy.cpp @@ -22,6 +22,7 @@ * */ +#include "ci/ciFlatArrayKlass.hpp" #include "gc/shared/barrierSet.hpp" #include "gc/shared/tlab_globals.hpp" #include "oops/objArrayKlass.hpp" @@ -57,6 +58,11 @@ void PhaseMacroExpand::insert_mem_bar(Node** ctrl, Node** mem, int opcode, int a Node* PhaseMacroExpand::array_element_address(Node* ary, Node* idx, BasicType elembt) { uint shift = exact_log2(type2aelembytes(elembt)); + const TypeAryPtr* array_type = _igvn.type(ary)->isa_aryptr(); + if (array_type != nullptr && array_type->is_aryptr()->is_flat()) { + // Use T_FLAT_ELEMENT to get proper alignment with COH when fetching the array element address. + elembt = T_FLAT_ELEMENT; + } uint header = arrayOopDesc::base_offset_in_bytes(elembt); Node* base = basic_plus_adr(ary, header); #ifdef _LP64 @@ -145,10 +151,14 @@ Node* PhaseMacroExpand::generate_guard(Node** ctrl, Node* test, RegionNode* regi return if_slow; } -inline Node* PhaseMacroExpand::generate_slow_guard(Node** ctrl, Node* test, RegionNode* region) { +Node* PhaseMacroExpand::generate_slow_guard(Node** ctrl, Node* test, RegionNode* region) { return generate_guard(ctrl, test, region, PROB_UNLIKELY_MAG(3)); } +inline Node* PhaseMacroExpand::generate_fair_guard(Node** ctrl, Node* test, RegionNode* region) { + return generate_guard(ctrl, test, region, PROB_FAIR); +} + void PhaseMacroExpand::generate_negative_guard(Node** ctrl, Node* index, RegionNode* region) { if ((*ctrl)->is_top()) return; // already stopped @@ -289,6 +299,49 @@ Node* PhaseMacroExpand::generate_nonpositive_guard(Node** ctrl, Node* index, boo return is_notp; } +Node* PhaseMacroExpand::mark_word_test(Node** ctrl, Node* obj, MergeMemNode* mem, uintptr_t mask_val, RegionNode* region) { + // Load markword and check if obj is locked + Node* mark = make_load(nullptr, mem->memory_at(Compile::AliasIdxRaw), obj, oopDesc::mark_offset_in_bytes(), TypeX_X, TypeX_X->basic_type()); + Node* locked_bit = MakeConX(markWord::unlocked_value); + locked_bit = transform_later(new AndXNode(locked_bit, mark)); + Node* cmp = transform_later(new CmpXNode(locked_bit, MakeConX(0))); + Node* is_unlocked = transform_later(new BoolNode(cmp, BoolTest::ne)); + IfNode* iff = transform_later(new IfNode(*ctrl, is_unlocked, PROB_MAX, COUNT_UNKNOWN))->as_If(); + Node* locked_region = transform_later(new RegionNode(3)); + Node* mark_phi = transform_later(new PhiNode(locked_region, TypeX_X)); + + // Unlocked: Use bits from mark word + locked_region->init_req(1, transform_later(new IfTrueNode(iff))); + mark_phi->init_req(1, mark); + + // Locked: Load prototype header from klass + *ctrl = transform_later(new IfFalseNode(iff)); + // Make loads control dependent to make sure they are only executed if array is locked + Node* klass_adr = basic_plus_adr(obj, oopDesc::klass_offset_in_bytes()); + Node* klass = transform_later(LoadKlassNode::make(_igvn, C->immutable_memory(), klass_adr, TypeInstPtr::KLASS, TypeInstKlassPtr::OBJECT)); + Node* proto_adr = basic_plus_adr(klass, in_bytes(Klass::prototype_header_offset())); + Node* proto = transform_later(LoadNode::make(_igvn, *ctrl, C->immutable_memory(), proto_adr, proto_adr->bottom_type()->is_ptr(), TypeX_X, TypeX_X->basic_type(), MemNode::unordered)); + + locked_region->init_req(2, *ctrl); + mark_phi->init_req(2, proto); + *ctrl = locked_region; + + // Now check if mark word bits are set + Node* mask = MakeConX(mask_val); + Node* masked = transform_later(new AndXNode(mark_phi, mask)); + cmp = transform_later(new CmpXNode(masked, mask)); + Node* bol = transform_later(new BoolNode(cmp, BoolTest::eq)); + return generate_fair_guard(ctrl, bol, region); +} + +Node* PhaseMacroExpand::generate_flat_array_guard(Node** ctrl, Node* array, MergeMemNode* mem, RegionNode* region) { + return mark_word_test(ctrl, array, mem, markWord::flat_array_bit_in_place, region); +} + +Node* PhaseMacroExpand::generate_null_free_array_guard(Node** ctrl, Node* array, MergeMemNode* mem, RegionNode* region) { + return mark_word_test(ctrl, array, mem, markWord::null_free_array_bit_in_place, region); +} + void PhaseMacroExpand::finish_arraycopy_call(Node* call, Node** ctrl, MergeMemNode** mem, const TypePtr* adr_type) { transform_later(call); @@ -383,6 +436,7 @@ Node* PhaseMacroExpand::generate_arraycopy(ArrayCopyNode *ac, AllocateArrayNode* Node* src, Node* src_offset, Node* dest, Node* dest_offset, Node* copy_length, + Node* dest_length, bool disjoint_bases, bool length_never_negative, RegionNode* slow_region) { @@ -394,6 +448,8 @@ Node* PhaseMacroExpand::generate_arraycopy(ArrayCopyNode *ac, AllocateArrayNode* Node* original_dest = dest; bool dest_needs_zeroing = false; bool acopy_to_uninitialized = false; + Node* init_value = nullptr; + Node* raw_init_value = nullptr; // See if this is the initialization of a newly-allocated array. // If so, we will take responsibility here for initializing it to zero. @@ -424,6 +480,8 @@ Node* PhaseMacroExpand::generate_arraycopy(ArrayCopyNode *ac, AllocateArrayNode* // Also, if this flag is set we make sure that arraycopy interacts properly // with G1, eliding pre-barriers. See CR 6627983. dest_needs_zeroing = true; + init_value = alloc->in(AllocateNode::InitValue); + raw_init_value = alloc->in(AllocateNode::RawInitValue); } else { // dest_need_zeroing = false; } @@ -493,14 +551,15 @@ Node* PhaseMacroExpand::generate_arraycopy(ArrayCopyNode *ac, AllocateArrayNode* // copy_length is 0. if (dest_needs_zeroing) { assert(!local_ctrl->is_top(), "no ctrl?"); - Node* dest_length = alloc->in(AllocateNode::ALength); if (copy_length->eqv_uncast(dest_length) || _igvn.find_int_con(dest_length, 1) <= 0) { // There is no zeroing to do. No need for a secondary raw memory barrier. } else { // Clear the whole thing since there are no source elements to copy. generate_clear_array(local_ctrl, local_mem, - adr_type, dest, basic_elem_type, + adr_type, dest, + init_value, raw_init_value, + basic_elem_type, intcon(0), nullptr, alloc->in(AllocateNode::AllocSize)); // Use a secondary InitializeNode as raw memory barrier. @@ -531,13 +590,14 @@ Node* PhaseMacroExpand::generate_arraycopy(ArrayCopyNode *ac, AllocateArrayNode* // The copy destination is the slice dest[off..off+len]. The other slices // are dest_head = dest[0..off] and dest_tail = dest[off+len..dest.length]. Node* dest_size = alloc->in(AllocateNode::AllocSize); - Node* dest_length = alloc->in(AllocateNode::ALength); Node* dest_tail = transform_later( new AddINode(dest_offset, copy_length)); // If there is a head section that needs zeroing, do it now. if (_igvn.find_int_con(dest_offset, -1) != 0) { generate_clear_array(*ctrl, mem, - adr_type, dest, basic_elem_type, + adr_type, dest, + init_value, raw_init_value, + basic_elem_type, intcon(0), dest_offset, nullptr); } @@ -586,7 +646,9 @@ Node* PhaseMacroExpand::generate_arraycopy(ArrayCopyNode *ac, AllocateArrayNode* *ctrl = tail_ctl; if (notail_ctl == nullptr) { generate_clear_array(*ctrl, mem, - adr_type, dest, basic_elem_type, + adr_type, dest, + init_value, raw_init_value, + basic_elem_type, dest_tail, nullptr, dest_size); } else { @@ -596,7 +658,9 @@ Node* PhaseMacroExpand::generate_arraycopy(ArrayCopyNode *ac, AllocateArrayNode* done_ctl->init_req(1, notail_ctl); done_mem->init_req(1, mem->memory_at(alias_idx)); generate_clear_array(*ctrl, mem, - adr_type, dest, basic_elem_type, + adr_type, dest, + init_value, raw_init_value, + basic_elem_type, dest_tail, nullptr, dest_size); done_ctl->init_req(2, *ctrl); @@ -773,7 +837,9 @@ Node* PhaseMacroExpand::generate_arraycopy(ArrayCopyNode *ac, AllocateArrayNode* if (dest_needs_zeroing) { generate_clear_array(local_ctrl, local_mem, - adr_type, dest, basic_elem_type, + adr_type, dest, + init_value, raw_init_value, + basic_elem_type, intcon(0), nullptr, alloc->in(AllocateNode::AllocSize)); } @@ -842,11 +908,11 @@ Node* PhaseMacroExpand::generate_arraycopy(ArrayCopyNode *ac, AllocateArrayNode* assert((*ctrl)->in(0)->isa_MemBar(), "MemBar node"); (*ctrl)->in(0)->isa_MemBar()->set_trailing_expanded_array_copy(); - _igvn.replace_node(_callprojs.fallthrough_memproj, out_mem); - if (_callprojs.fallthrough_ioproj != nullptr) { - _igvn.replace_node(_callprojs.fallthrough_ioproj, *io); + _igvn.replace_node(_callprojs->fallthrough_memproj, out_mem); + if (_callprojs->fallthrough_ioproj != nullptr) { + _igvn.replace_node(_callprojs->fallthrough_ioproj, *io); } - _igvn.replace_node(_callprojs.fallthrough_catchproj, *ctrl); + _igvn.replace_node(_callprojs->fallthrough_catchproj, *ctrl); #ifdef ASSERT const TypeOopPtr* dest_t = _igvn.type(dest)->is_oopptr(); @@ -886,6 +952,8 @@ Node* PhaseMacroExpand::generate_arraycopy(ArrayCopyNode *ac, AllocateArrayNode* void PhaseMacroExpand::generate_clear_array(Node* ctrl, MergeMemNode* merge_mem, const TypePtr* adr_type, Node* dest, + Node* val, + Node* raw_val, BasicType basic_elem_type, Node* slice_idx, Node* slice_len, @@ -924,12 +992,12 @@ void PhaseMacroExpand::generate_clear_array(Node* ctrl, MergeMemNode* merge_mem, if (start_con >= 0 && end_con >= 0) { // Constant start and end. Simple. - mem = ClearArrayNode::clear_memory(ctrl, mem, dest, + mem = ClearArrayNode::clear_memory(ctrl, mem, dest, val, raw_val, start_con, end_con, &_igvn); } else if (start_con >= 0 && dest_size != top()) { // Constant start, pre-rounded end after the tail of the array. Node* end = dest_size; - mem = ClearArrayNode::clear_memory(ctrl, mem, dest, + mem = ClearArrayNode::clear_memory(ctrl, mem, dest, val, raw_val, start_con, end, &_igvn); } else if (start_con >= 0 && slice_len != top()) { // Constant start, non-constant end. End needs rounding up. @@ -942,7 +1010,7 @@ void PhaseMacroExpand::generate_clear_array(Node* ctrl, MergeMemNode* merge_mem, end_base += end_round; end = transform_later(new AddXNode(end, MakeConX(end_base)) ); end = transform_later(new AndXNode(end, MakeConX(~end_round)) ); - mem = ClearArrayNode::clear_memory(ctrl, mem, dest, + mem = ClearArrayNode::clear_memory(ctrl, mem, dest, val, raw_val, start_con, end, &_igvn); } else if (start_con < 0 && dest_size != top()) { // Non-constant start, pre-rounded end after the tail of the array. @@ -971,12 +1039,18 @@ void PhaseMacroExpand::generate_clear_array(Node* ctrl, MergeMemNode* merge_mem, // Store a zero to the immediately preceding jint: Node* x1 = transform_later(new AddXNode(start, MakeConX(-bump_bit)) ); Node* p1 = basic_plus_adr(dest, x1); - mem = StoreNode::make(_igvn, ctrl, mem, p1, adr_type, intcon(0), T_INT, MemNode::unordered); + if (val == nullptr) { + assert(raw_val == nullptr, "val may not be null"); + mem = StoreNode::make(_igvn, ctrl, mem, p1, adr_type, intcon(0), T_INT, MemNode::unordered); + } else { + assert(_igvn.type(val)->isa_narrowoop(), "should be narrow oop"); + mem = new StoreNNode(ctrl, mem, p1, adr_type, val, MemNode::unordered); + } mem = transform_later(mem); } } Node* end = dest_size; // pre-rounded - mem = ClearArrayNode::clear_memory(ctrl, mem, dest, + mem = ClearArrayNode::clear_memory(ctrl, mem, dest, raw_val, start, end, &_igvn); } else { // Non-constant start, unrounded non-constant end. @@ -1092,11 +1166,11 @@ MergeMemNode* PhaseMacroExpand::generate_slow_arraycopy(ArrayCopyNode *ac, _igvn.replace_node(ac, call); transform_later(call); - call->extract_projections(&_callprojs, false /*separate_io_proj*/, false /*do_asserts*/); - *ctrl = _callprojs.fallthrough_catchproj->clone(); + _callprojs = call->extract_projections(false /*separate_io_proj*/, false /*do_asserts*/); + *ctrl = _callprojs->fallthrough_catchproj->clone(); transform_later(*ctrl); - Node* m = _callprojs.fallthrough_memproj->clone(); + Node* m = _callprojs->fallthrough_memproj->clone(); transform_later(m); uint alias_idx = C->get_alias_index(adr_type); @@ -1110,9 +1184,9 @@ MergeMemNode* PhaseMacroExpand::generate_slow_arraycopy(ArrayCopyNode *ac, transform_later(out_mem); // When src is negative and arraycopy is before an infinite loop,_callprojs.fallthrough_ioproj - // could be null. Skip clone and update null fallthrough_ioproj. - if (_callprojs.fallthrough_ioproj != nullptr) { - *io = _callprojs.fallthrough_ioproj->clone(); + // could be nullptr. Skip clone and update nullptr fallthrough_ioproj. + if (_callprojs->fallthrough_ioproj != nullptr) { + *io = _callprojs->fallthrough_ioproj->clone(); transform_later(*io); } else { *io = nullptr; @@ -1244,6 +1318,42 @@ void PhaseMacroExpand::generate_unchecked_arraycopy(Node** ctrl, MergeMemNode** } } +const TypePtr* PhaseMacroExpand::adjust_for_flat_array(const TypeAryPtr* top_dest, Node*& src_offset, + Node*& dest_offset, Node*& length, BasicType& dest_elem, + Node*& dest_length) { +#ifdef ASSERT + BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); + bool needs_barriers = top_dest->elem()->inline_klass()->contains_oops() && + bs->array_copy_requires_gc_barriers(dest_length != nullptr, T_OBJECT, false, false, BarrierSetC2::Optimization); + assert(!needs_barriers || StressReflectiveCode, "Flat arracopy would require GC barriers"); +#endif + int elem_size = top_dest->flat_elem_size(); + if (elem_size >= 8) { + if (elem_size > 8) { + // treat as array of long but scale length, src offset and dest offset + assert((elem_size % 8) == 0, "not a power of 2?"); + int factor = elem_size / 8; + length = transform_later(new MulINode(length, intcon(factor))); + src_offset = transform_later(new MulINode(src_offset, intcon(factor))); + dest_offset = transform_later(new MulINode(dest_offset, intcon(factor))); + if (dest_length != nullptr) { + dest_length = transform_later(new MulINode(dest_length, intcon(factor))); + } + elem_size = 8; + } + dest_elem = T_LONG; + } else if (elem_size == 4) { + dest_elem = T_INT; + } else if (elem_size == 2) { + dest_elem = T_CHAR; + } else if (elem_size == 1) { + dest_elem = T_BYTE; + } else { + ShouldNotReachHere(); + } + return TypeRawPtr::BOTTOM; +} + #undef XTOP void PhaseMacroExpand::expand_arraycopy_node(ArrayCopyNode *ac) { @@ -1261,23 +1371,59 @@ void PhaseMacroExpand::expand_arraycopy_node(ArrayCopyNode *ac) { bs->clone_at_expansion(this, ac); return; } else if (ac->is_copyof() || ac->is_copyofrange() || ac->is_clone_oop_array()) { - Node* mem = ac->in(TypeFunc::Memory); - merge_mem = MergeMemNode::make(mem); - transform_later(merge_mem); + const Type* src_type = _igvn.type(src); + const Type* dest_type = _igvn.type(dest); + const TypeAryPtr* top_src = src_type->isa_aryptr(); + // Note: The destination could have type Object (i.e. non-array) when directly invoking the protected method + // Object::clone() with reflection on a declared Object that is an array at runtime. top_dest is then null. + const TypeAryPtr* top_dest = dest_type->isa_aryptr(); + BasicType dest_elem = T_OBJECT; + if (top_dest != nullptr && top_dest->elem() != Type::BOTTOM) { + dest_elem = top_dest->elem()->array_element_basic_type(); + } + if (is_reference_type(dest_elem, true)) dest_elem = T_OBJECT; + + if (top_src != nullptr && top_src->is_flat()) { + // If src is flat, dest is guaranteed to be flat as well + top_dest = top_src; + } AllocateArrayNode* alloc = nullptr; + Node* dest_length = nullptr; if (ac->is_alloc_tightly_coupled()) { alloc = AllocateArrayNode::Ideal_array_allocation(dest); assert(alloc != nullptr, "expect alloc"); + dest_length = alloc->in(AllocateNode::ALength); } - const TypePtr* adr_type = _igvn.type(dest)->is_oopptr()->add_offset(Type::OffsetBot); - if (ac->_dest_type != TypeOopPtr::BOTTOM) { - adr_type = ac->_dest_type->add_offset(Type::OffsetBot)->is_ptr(); + Node* mem = ac->in(TypeFunc::Memory); + const TypePtr* adr_type = nullptr; + if (top_dest != nullptr && top_dest->is_flat()) { + assert(dest_length != nullptr || StressReflectiveCode, "must be tightly coupled"); + // Copy to a flat array modifies multiple memory slices. Conservatively insert a barrier + // on all slices to prevent writes into the source from floating below the arraycopy. + int mem_bar_alias_idx = Compile::AliasIdxBot; + if (ac->_dest_type != TypeOopPtr::BOTTOM) { + mem_bar_alias_idx = C->get_alias_index(ac->_dest_type->add_offset(Type::OffsetBot)->is_ptr()); + } + insert_mem_bar(&ctrl, &mem, Op_MemBarCPUOrder, mem_bar_alias_idx); + adr_type = adjust_for_flat_array(top_dest, src_offset, dest_offset, length, dest_elem, dest_length); + } else { + adr_type = dest_type->is_oopptr()->add_offset(Type::OffsetBot); + if (ac->_dest_type != TypeOopPtr::BOTTOM) { + adr_type = ac->_dest_type->add_offset(Type::OffsetBot)->is_ptr(); + } + if (ac->_src_type != ac->_dest_type) { + adr_type = TypeRawPtr::BOTTOM; + } } + merge_mem = MergeMemNode::make(mem); + transform_later(merge_mem); + generate_arraycopy(ac, alloc, &ctrl, merge_mem, &io, - adr_type, T_OBJECT, + adr_type, dest_elem, src, src_offset, dest, dest_offset, length, + dest_length, true, ac->has_negative_length_guard()); return; @@ -1313,9 +1459,7 @@ void PhaseMacroExpand::expand_arraycopy_node(ArrayCopyNode *ac) { if (is_reference_type(src_elem, true)) src_elem = T_OBJECT; if (is_reference_type(dest_elem, true)) dest_elem = T_OBJECT; - if (ac->is_arraycopy_validated() && - dest_elem != T_CONFLICT && - src_elem == T_CONFLICT) { + if (ac->is_arraycopy_validated() && dest_elem != T_CONFLICT && src_elem == T_CONFLICT) { src_elem = dest_elem; } @@ -1331,12 +1475,13 @@ void PhaseMacroExpand::expand_arraycopy_node(ArrayCopyNode *ac) { } // Call StubRoutines::generic_arraycopy stub. - Node* mem = generate_arraycopy(ac, nullptr, &ctrl, merge_mem, &io, - TypeRawPtr::BOTTOM, T_CONFLICT, - src, src_offset, dest, dest_offset, length, - // If a negative length guard was generated for the ArrayCopyNode, - // the length of the array can never be negative. - false, ac->has_negative_length_guard()); + generate_arraycopy(ac, nullptr, &ctrl, merge_mem, &io, + TypeRawPtr::BOTTOM, T_CONFLICT, + src, src_offset, dest, dest_offset, length, + nullptr, + // If a negative length guard was generated for the ArrayCopyNode, + // the length of the array can never be negative. + false, ac->has_negative_length_guard()); return; } @@ -1344,7 +1489,14 @@ void PhaseMacroExpand::expand_arraycopy_node(ArrayCopyNode *ac) { // (2) src and dest arrays must have elements of the same BasicType // Figure out the size and type of the elements we will be copying. - if (src_elem != dest_elem || dest_elem == T_VOID) { + // + // We have no stub to copy flat inline type arrays with oop + // fields if we need to emit write barriers. + // + BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); + if (src_elem != dest_elem || top_src->is_flat() != top_dest->is_flat() || dest_elem == T_VOID || + (top_src->is_flat() && top_dest->elem()->inline_klass()->contains_oops() && + bs->array_copy_requires_gc_barriers(alloc != nullptr, T_OBJECT, false, false, BarrierSetC2::Optimization))) { // The component types are not the same or are not recognized. Punt. // (But, avoid the native method wrapper to JVM_ArrayCopy.) { @@ -1352,11 +1504,11 @@ void PhaseMacroExpand::expand_arraycopy_node(ArrayCopyNode *ac) { merge_mem = generate_slow_arraycopy(ac, &ctrl, mem, &io, TypePtr::BOTTOM, src, src_offset, dest, dest_offset, length, false); } - _igvn.replace_node(_callprojs.fallthrough_memproj, merge_mem); - if (_callprojs.fallthrough_ioproj != nullptr) { - _igvn.replace_node(_callprojs.fallthrough_ioproj, io); + _igvn.replace_node(_callprojs->fallthrough_memproj, merge_mem); + if (_callprojs->fallthrough_ioproj != nullptr) { + _igvn.replace_node(_callprojs->fallthrough_ioproj, io); } - _igvn.replace_node(_callprojs.fallthrough_catchproj, ctrl); + _igvn.replace_node(_callprojs->fallthrough_catchproj, ctrl); return; } @@ -1373,11 +1525,18 @@ void PhaseMacroExpand::expand_arraycopy_node(ArrayCopyNode *ac) { // (8) dest_offset + length must not exceed length of dest. // (9) each element of an oop array must be assignable - { - Node* mem = ac->in(TypeFunc::Memory); - merge_mem = MergeMemNode::make(mem); - transform_later(merge_mem); + Node* mem = ac->in(TypeFunc::Memory); + if (top_dest->is_flat()) { + // Copy to a flat array modifies multiple memory slices. Conservatively insert a barrier + // on all slices to prevent writes into the source from floating below the arraycopy. + int mem_bar_alias_idx = Compile::AliasIdxBot; + if (ac->_dest_type != TypeOopPtr::BOTTOM) { + mem_bar_alias_idx = C->get_alias_index(ac->_dest_type->add_offset(Type::OffsetBot)->is_ptr()); + } + insert_mem_bar(&ctrl, &mem, Op_MemBarCPUOrder, mem_bar_alias_idx); } + merge_mem = MergeMemNode::make(mem); + transform_later(merge_mem); RegionNode* slow_region = new RegionNode(1); transform_later(slow_region); @@ -1418,10 +1577,37 @@ void PhaseMacroExpand::expand_arraycopy_node(ArrayCopyNode *ac) { // (9) each element of an oop array must be assignable // The generate_arraycopy subroutine checks this. + + // TODO 8350865 Fix below logic. Also handle atomicity. + // We need to be careful here because 'adjust_for_flat_array' will adjust offsets/length etc. which then does not work anymore for the slow call to SharedRuntime::slow_arraycopy_C. + if (!(top_src->is_flat() && top_dest->is_flat())) { + generate_flat_array_guard(&ctrl, src, merge_mem, slow_region); + generate_flat_array_guard(&ctrl, dest, merge_mem, slow_region); + } + + // Handle inline type arrays + if (!top_src->is_flat()) { + if (UseArrayFlattening && !top_src->is_not_flat()) { + // Src might be flat and dest might not be flat. Go to the slow path if src is flat. + generate_flat_array_guard(&ctrl, src, merge_mem, slow_region); + } + if (EnableValhalla) { + // No validation. The subtype check emitted at macro expansion time will not go to the slow + // path but call checkcast_arraycopy which can not handle flat/null-free inline type arrays. + generate_null_free_array_guard(&ctrl, dest, merge_mem, slow_region); + } + } else { + assert(top_dest->is_flat(), "dest array must be flat"); + } } + // This is where the memory effects are placed: const TypePtr* adr_type = nullptr; - if (ac->_dest_type != TypeOopPtr::BOTTOM) { + Node* dest_length = (alloc != nullptr) ? alloc->in(AllocateNode::ALength) : nullptr; + + if (top_src->is_flat() && top_dest->is_flat()) { + adr_type = adjust_for_flat_array(top_dest, src_offset, dest_offset, length, dest_elem, dest_length); + } else if (ac->_dest_type != TypeOopPtr::BOTTOM) { adr_type = ac->_dest_type->add_offset(Type::OffsetBot)->is_ptr(); } else { adr_type = TypeAryPtr::get_array_body_type(dest_elem); @@ -1430,7 +1616,9 @@ void PhaseMacroExpand::expand_arraycopy_node(ArrayCopyNode *ac) { generate_arraycopy(ac, alloc, &ctrl, merge_mem, &io, adr_type, dest_elem, src, src_offset, dest, dest_offset, length, + dest_length, // If a negative length guard was generated for the ArrayCopyNode, // the length of the array can never be negative. - false, ac->has_negative_length_guard(), slow_region); + false, ac->has_negative_length_guard(), + slow_region); } diff --git a/src/hotspot/share/opto/matcher.cpp b/src/hotspot/share/opto/matcher.cpp index 5cb56019bc1..98d4d60e6cb 100644 --- a/src/hotspot/share/opto/matcher.cpp +++ b/src/hotspot/share/opto/matcher.cpp @@ -186,6 +186,50 @@ void Matcher::verify_new_nodes_only(Node* xroot) { } #endif +// Array of RegMask, one per returned values (inline type instances can +// be returned as multiple return values, one per field) +RegMask* Matcher::return_values_mask(const TypeFunc* tf) { + const TypeTuple* range = tf->range_cc(); + uint cnt = range->cnt() - TypeFunc::Parms; + if (cnt == 0) { + return nullptr; + } + RegMask* mask = NEW_RESOURCE_ARRAY(RegMask, cnt); + BasicType* sig_bt = NEW_RESOURCE_ARRAY(BasicType, cnt); + VMRegPair* vm_parm_regs = NEW_RESOURCE_ARRAY(VMRegPair, cnt); + for (uint i = 0; i < cnt; i++) { + sig_bt[i] = range->field_at(i+TypeFunc::Parms)->basic_type(); + } + + int regs = SharedRuntime::java_return_convention(sig_bt, vm_parm_regs, cnt); + if (regs <= 0) { + // We ran out of registers to store the null marker for a nullable inline type return. + // Since it is only set in the 'call_epilog', we can simply put it on the stack. + assert(tf->returns_inline_type_as_fields(), "should have been tested during graph construction"); + // TODO 8284443 Can we teach the register allocator to reserve a stack slot instead? + // mask[--cnt] = STACK_ONLY_mask does not work (test with -XX:+StressGCM) + int slot = C->fixed_slots() - 2; + if (C->needs_stack_repair()) { + slot -= 2; // Account for stack increment value + } + mask[--cnt].Clear(); + mask[cnt].Insert(OptoReg::stack2reg(slot)); + } + for (uint i = 0; i < cnt; i++) { + mask[i].Clear(); + + OptoReg::Name reg1 = OptoReg::as_OptoReg(vm_parm_regs[i].first()); + if (OptoReg::is_valid(reg1)) { + mask[i].Insert(reg1); + } + OptoReg::Name reg2 = OptoReg::as_OptoReg(vm_parm_regs[i].second()); + if (OptoReg::is_valid(reg2)) { + mask[i].Insert(reg2); + } + } + + return mask; +} //---------------------------match--------------------------------------------- void Matcher::match( ) { @@ -204,21 +248,9 @@ void Matcher::match( ) { _return_addr_mask.Insert(OptoReg::add(return_addr(),1)); #endif - // Map a Java-signature return type into return register-value - // machine registers for 0, 1 and 2 returned values. - const TypeTuple *range = C->tf()->range(); - if( range->cnt() > TypeFunc::Parms ) { // If not a void function - // Get ideal-register return type - uint ireg = range->field_at(TypeFunc::Parms)->ideal_reg(); - // Get machine return register - uint sop = C->start()->Opcode(); - OptoRegPair regs = return_value(ireg); - - // And mask for same - _return_value_mask = RegMask(regs.first()); - if( OptoReg::is_valid(regs.second()) ) - _return_value_mask.Insert(regs.second()); - } + // Map Java-signature return types into return register-value + // machine registers. + _return_values_mask = return_values_mask(C->tf()); // --------------- // Frame Layout @@ -226,7 +258,7 @@ void Matcher::match( ) { // Need the method signature to determine the incoming argument types, // because the types determine which registers the incoming arguments are // in, and this affects the matched code. - const TypeTuple *domain = C->tf()->domain(); + const TypeTuple *domain = C->tf()->domain_cc(); uint argcnt = domain->cnt() - TypeFunc::Parms; BasicType *sig_bt = NEW_RESOURCE_ARRAY( BasicType, argcnt ); VMRegPair *vm_parm_regs = NEW_RESOURCE_ARRAY( VMRegPair, argcnt ); @@ -549,6 +581,7 @@ void Matcher::init_first_stack_mask() { for (i = init_in; i < _in_arg_limit; i = OptoReg::add(i,1)) { C->FIRST_STACK_mask().Insert(i); } + // Add in all bits past the outgoing argument area guarantee(RegMask::can_represent_arg(OptoReg::add(_out_arg_limit,-1)), "must be able to represent all call arguments in reg mask"); @@ -807,12 +840,11 @@ void Matcher::Fixup_Save_On_Entry( ) { // Input RegMask array shared by all Returns. // The type for doubles and longs has a count of 2, but // there is only 1 returned value - uint ret_edge_cnt = TypeFunc::Parms + ((C->tf()->range()->cnt() == TypeFunc::Parms) ? 0 : 1); + uint ret_edge_cnt = C->tf()->range_cc()->cnt(); RegMask *ret_rms = init_input_masks( ret_edge_cnt + soe_cnt, _return_addr_mask, c_frame_ptr_mask ); - // Returns have 0 or 1 returned values depending on call signature. - // Return register is specified by return_value in the AD file. - if (ret_edge_cnt > TypeFunc::Parms) - ret_rms[TypeFunc::Parms+0] = _return_value_mask; + for (i = TypeFunc::Parms; i < ret_edge_cnt; i++) { + ret_rms[i] = _return_values_mask[i-TypeFunc::Parms]; + } // Input RegMask array shared by all ForwardExceptions uint forw_exc_edge_cnt = TypeFunc::Parms; @@ -884,7 +916,7 @@ void Matcher::Fixup_Save_On_Entry( ) { } // Next unused projection number from Start. - int proj_cnt = C->tf()->domain()->cnt(); + int proj_cnt = C->tf()->domain_cc()->cnt(); // Do all the save-on-entry registers. Make projections from Start for // them, and give them a use at the exit points. To the allocator, they @@ -1165,7 +1197,11 @@ Node *Matcher::xform( Node *n, int max_stack ) { } else { // Nothing the matcher cares about if (n->is_Proj() && n->in(0) != nullptr && n->in(0)->is_Multi()) { // Projections? // Convert to machine-dependent projection - m = n->in(0)->as_Multi()->match( n->as_Proj(), this ); + RegMask* mask = nullptr; + if (n->in(0)->is_Call() && n->in(0)->as_Call()->tf()->returns_inline_type_as_fields()) { + mask = return_values_mask(n->in(0)->as_Call()->tf()); + } + m = n->in(0)->as_Multi()->match(n->as_Proj(), this, mask); NOT_PRODUCT(record_new2old(m, n);) if (m->in(0) != nullptr) // m might be top collect_null_checks(m, n); @@ -1305,7 +1341,7 @@ MachNode *Matcher::match_sfpt( SafePointNode *sfpt ) { bool is_method_handle_invoke = false; // for special kill effects if( sfpt->is_Call() ) { call = sfpt->as_Call(); - domain = call->tf()->domain(); + domain = call->tf()->domain_cc(); cnt = domain->cnt(); // Match just the call, nothing else @@ -1384,13 +1420,16 @@ MachNode *Matcher::match_sfpt( SafePointNode *sfpt ) { // Do the normal argument list (parameters) register masks - int argcnt = cnt - TypeFunc::Parms; + // Null entry point is a special cast where the target of the call + // is in a register. + int adj = (call != nullptr && call->entry_point() == nullptr) ? 1 : 0; + int argcnt = cnt - TypeFunc::Parms - adj; if( argcnt > 0 ) { // Skip it all if we have no args BasicType *sig_bt = NEW_RESOURCE_ARRAY( BasicType, argcnt ); VMRegPair *parm_regs = NEW_RESOURCE_ARRAY( VMRegPair, argcnt ); int i; for( i = 0; i < argcnt; i++ ) { - sig_bt[i] = domain->field_at(i+TypeFunc::Parms)->basic_type(); + sig_bt[i] = domain->field_at(i+TypeFunc::Parms+adj)->basic_type(); } // V-call to pick proper calling convention call->calling_convention( sig_bt, parm_regs, argcnt ); @@ -1431,7 +1470,7 @@ MachNode *Matcher::match_sfpt( SafePointNode *sfpt ) { // and over the entire method. for( i = 0; i < argcnt; i++ ) { // Address of incoming argument mask to fill in - RegMask *rm = &mcall->_in_rms[i+TypeFunc::Parms]; + RegMask *rm = &mcall->_in_rms[i+TypeFunc::Parms+adj]; VMReg first = parm_regs[i].first(); VMReg second = parm_regs[i].second(); if(!first->is_valid() && @@ -1452,15 +1491,17 @@ MachNode *Matcher::match_sfpt( SafePointNode *sfpt ) { if (C->failing()) { return nullptr; } - if (OptoReg::is_valid(reg1)) + if (OptoReg::is_valid(reg1)) { rm->Insert( reg1 ); + } // Grab second register (if any), adjust stack slots and insert in mask. OptoReg::Name reg2 = warp_outgoing_stk_arg(second, begin_out_arg_area, out_arg_limit_per_call ); if (C->failing()) { return nullptr; } - if (OptoReg::is_valid(reg2)) + if (OptoReg::is_valid(reg2)) { rm->Insert( reg2 ); + } } // End of for all arguments } @@ -1476,7 +1517,7 @@ MachNode *Matcher::match_sfpt( SafePointNode *sfpt ) { // Since the max-per-method covers the max-per-call-site and debug info // is excluded on the max-per-method basis, debug info cannot land in // this killed area. - uint r_cnt = mcall->tf()->range()->cnt(); + uint r_cnt = mcall->tf()->range_sig()->cnt(); MachProjNode *proj = new MachProjNode( mcall, r_cnt+10000, RegMask::Empty, MachProjNode::fat_proj ); if (!RegMask::can_represent_arg(OptoReg::Name(out_arg_limit_per_call-1))) { // Bailout. We do not have space to represent all arguments. @@ -1498,7 +1539,7 @@ MachNode *Matcher::match_sfpt( SafePointNode *sfpt ) { // Debug inputs begin just after the last incoming parameter assert((mcall == nullptr) || (mcall->jvms() == nullptr) || - (mcall->jvms()->debug_start() + mcall->_jvmadj == mcall->tf()->domain()->cnt()), ""); + (mcall->jvms()->debug_start() + mcall->_jvmadj == mcall->tf()->domain_cc()->cnt()), ""); // Add additional edges. if (msfpt->mach_constant_base_node_input() != (uint)-1 && !msfpt->is_MachCallLeaf()) { @@ -2187,7 +2228,7 @@ void Matcher::find_shared(Node* n) { if (find_shared_visit(mstack, n, nop, mem_op, mem_addr_idx)) { continue; } - for (int i = n->req() - 1; i >= 0; --i) { // For my children + for (int i = n->len() - 1; i >= 0; --i) { // For my children Node* m = n->in(i); // Get ith input if (m == nullptr) { continue; // Ignore nulls @@ -2498,6 +2539,13 @@ void Matcher::find_shared_post_visit(Node* n, uint opcode) { n->del_req(3); break; } + case Op_ClearArray: { + Node* pair = new BinaryNode(n->in(2), n->in(3)); + n->set_req(2, pair); + n->set_req(3, n->in(4)); + n->del_req(4); + break; + } case Op_VectorCmpMasked: case Op_CopySignD: case Op_SignumVF: @@ -2547,6 +2595,14 @@ void Matcher::find_shared_post_visit(Node* n, uint opcode) { } break; } + case Op_StoreLSpecial: { + if (n->req() > (MemNode::ValueIn + 1) && n->in(MemNode::ValueIn + 1) != nullptr) { + Node* pair = new BinaryNode(n->in(MemNode::ValueIn), n->in(MemNode::ValueIn + 1)); + n->set_req(MemNode::ValueIn, pair); + n->del_req(MemNode::ValueIn + 1); + } + break; + } default: break; } diff --git a/src/hotspot/share/opto/matcher.hpp b/src/hotspot/share/opto/matcher.hpp index 2ee2ded17b6..5fcd2250068 100644 --- a/src/hotspot/share/opto/matcher.hpp +++ b/src/hotspot/share/opto/matcher.hpp @@ -270,6 +270,8 @@ class Matcher : public PhaseTransform { // Helper for match OptoReg::Name warp_incoming_stk_arg( VMReg reg ); + RegMask* return_values_mask(const TypeFunc* tf); + // Transform, then walk. Does implicit DCE while walking. // Name changed from "transform" to avoid it being virtual. Node *xform( Node *old_space_node, int Nodes ); @@ -407,7 +409,7 @@ class Matcher : public PhaseTransform { // Return value register. On Intel it is EAX. static OptoRegPair return_value(uint ideal_reg); static OptoRegPair c_return_value(uint ideal_reg); - RegMask _return_value_mask; + RegMask* _return_values_mask; // Inline Cache Register static OptoReg::Name inline_cache_reg(); static int inline_cache_reg_encode(); diff --git a/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp index f358729dfb2..6021b885e0c 100644 --- a/src/hotspot/share/opto/memnode.cpp +++ b/src/hotspot/share/opto/memnode.cpp @@ -23,7 +23,9 @@ * */ +#include "ci/ciFlatArrayKlass.hpp" #include "classfile/javaClasses.hpp" +#include "classfile/systemDictionary.hpp" #include "compiler/compileLog.hpp" #include "gc/shared/barrierSet.hpp" #include "gc/shared/c2/barrierSetC2.hpp" @@ -37,6 +39,7 @@ #include "opto/compile.hpp" #include "opto/connode.hpp" #include "opto/convertnode.hpp" +#include "opto/inlinetypenode.hpp" #include "opto/loopnode.hpp" #include "opto/machnode.hpp" #include "opto/matcher.hpp" @@ -140,31 +143,137 @@ extern void print_alias_types(); #endif -Node *MemNode::optimize_simple_memory_chain(Node *mchain, const TypeOopPtr *t_oop, Node *load, PhaseGVN *phase) { - assert((t_oop != nullptr), "sanity"); +// Find the memory output corresponding to the fall-through path of a call +static Node* find_call_fallthrough_mem_output(CallNode* call) { + ResourceMark rm; + CallProjections* projs = call->extract_projections(false, false); + Node* res = projs->fallthrough_memproj; + assert(res != nullptr, "must have a fallthrough mem output"); + return res; +} + +// Try to find a better memory input for a load from a strict final field +static Node* try_optimize_strict_final_load_memory(PhaseGVN* phase, Node* adr, ProjNode*& base_local) { + intptr_t offset = 0; + Node* base = AddPNode::Ideal_base_and_offset(adr, phase, offset); + if (base == nullptr) { + return nullptr; + } + + Node* base_uncasted = base->uncast(); + if (base_uncasted->is_Proj()) { + MultiNode* multi = base_uncasted->in(0)->as_Multi(); + if (multi->is_Allocate()) { + base_local = base_uncasted->as_Proj(); + return nullptr; + } else if (multi->is_Call()) { + // The oop is returned from a call, the memory can be the fallthrough output of the call + return find_call_fallthrough_mem_output(multi->as_Call()); + } else if (multi->is_Start()) { + // The oop is a parameter + if (phase->C->method()->is_object_constructor() && base_uncasted->as_Proj()->_con == TypeFunc::Parms) { + // The receiver of a constructor is similar to the result of an AllocateNode + base_local = base_uncasted->as_Proj(); + return nullptr; + } else { + // Use the start memory otherwise + return multi->proj_out(TypeFunc::Memory); + } + } + } + + return nullptr; +} + +// Whether a call can modify a strict final field, given that the object is allocated inside the +// current compilation unit, or is the first parameter when the compilation root is a constructor. +// This is equivalent to asking whether 'call' is a constructor invocation and the class declaring +// the target method is a subclass of the class declaring 'field'. +static bool call_can_modify_local_object(ciField* field, CallNode* call) { + if (!call->is_CallJava()) { + return false; + } + + ciMethod* target = call->as_CallJava()->method(); + if (target == nullptr || !target->is_object_constructor()) { + return false; + } + + // If 'field' is declared in a class that is a subclass of the one declaring the constructor, + // then the field is set inside the constructor, else the field must be set before the + // constructor invocation. E.g. A field Super.x will be set during the execution of Sub::, + // while a field Sub.y must be set before Super:: is invoked. + // We can try to be more heroic and decide if the receiver of the constructor invocation is the + // object from which we are loading from. This, however, may be problematic as deciding if 2 + // nodes are definitely different may not be trivial, especially if the graph is not canonical. + // As a result, it is made more conservative for now. + assert(call->req() > TypeFunc::Parms, "constructor must have at least 1 argument"); + return target->holder()->is_subclass_of(field->holder()); +} + +Node* MemNode::optimize_simple_memory_chain(Node* mchain, const TypeOopPtr* t_oop, Node* load, PhaseGVN* phase) { + assert(t_oop != nullptr, "sanity"); bool is_instance = t_oop->is_known_instance_field(); - bool is_boxed_value_load = t_oop->is_ptr_to_boxed_value() && - (load != nullptr) && load->is_Load() && - (phase->is_IterGVN() != nullptr); - if (!(is_instance || is_boxed_value_load)) - return mchain; // don't try to optimize non-instance types + + ciField* field = phase->C->alias_type(t_oop)->field(); + bool is_strict_final_load = false; + + // After macro expansion, an allocation may become a call, changing the memory input to the + // memory output of that call would be illegal. As a result, disallow this transformation after + // macro expansion. + if (phase->is_IterGVN() && phase->C->allow_macro_nodes() && load != nullptr && load->is_Load() && !load->as_Load()->is_mismatched_access()) { + if (EnableValhalla) { + if (field != nullptr && (field->holder()->is_inlinetype() || field->holder()->is_abstract_value_klass())) { + is_strict_final_load = true; + } +#ifdef ASSERT + if (t_oop->is_inlinetypeptr() && t_oop->inline_klass()->contains_field_offset(t_oop->offset())) { + assert(is_strict_final_load, "sanity check for basic cases"); + } +#endif + } else { + is_strict_final_load = field != nullptr && t_oop->is_ptr_to_boxed_value(); + } + } + + if (!is_instance && !is_strict_final_load) { + return mchain; + } + + Node* result = mchain; + ProjNode* base_local = nullptr; + + if (is_strict_final_load) { + Node* adr = load->in(MemNode::Address); + assert(phase->type(adr) == t_oop, "inconsistent type"); + Node* tmp = try_optimize_strict_final_load_memory(phase, adr, base_local); + if (tmp != nullptr) { + result = tmp; + } + } + uint instance_id = t_oop->instance_id(); - Node *start_mem = phase->C->start()->proj_out_or_null(TypeFunc::Memory); - Node *prev = nullptr; - Node *result = mchain; + Node* start_mem = phase->C->start()->proj_out_or_null(TypeFunc::Memory); + Node* prev = nullptr; while (prev != result) { prev = result; - if (result == start_mem) - break; // hit one of our sentinels + if (result == start_mem) { + // start_mem is the earliest memory possible + break; + } + // skip over a call which does not affect this memory slice if (result->is_Proj() && result->as_Proj()->_con == TypeFunc::Memory) { - Node *proj_in = result->in(0); + Node* proj_in = result->in(0); if (proj_in->is_Allocate() && proj_in->_idx == instance_id) { - break; // hit one of our sentinels + // This is the allocation that creates the object from which we are loading from + break; } else if (proj_in->is_Call()) { // ArrayCopyNodes processed here as well - CallNode *call = proj_in->as_Call(); - if (!call->may_modify(t_oop, phase)) { // returns false for instances + CallNode* call = proj_in->as_Call(); + if (!call->may_modify(t_oop, phase)) { + result = call->in(TypeFunc::Memory); + } else if (is_strict_final_load && base_local != nullptr && !call_can_modify_local_object(field, call)) { result = call->in(TypeFunc::Memory); } } else if (proj_in->is_Initialize()) { @@ -176,11 +285,15 @@ Node *MemNode::optimize_simple_memory_chain(Node *mchain, const TypeOopPtr *t_oo } if (is_instance) { result = proj_in->in(TypeFunc::Memory); - } else if (is_boxed_value_load) { + } else if (is_strict_final_load) { Node* klass = alloc->in(AllocateNode::KlassNode); const TypeKlassPtr* tklass = phase->type(klass)->is_klassptr(); if (tklass->klass_is_exact() && !tklass->exact_klass()->equals(t_oop->is_instptr()->exact_klass())) { - result = proj_in->in(TypeFunc::Memory); // not related allocation + // Allocation of another type, must be another object + result = proj_in->in(TypeFunc::Memory); + } else if (base_local != nullptr && (base_local->is_Parm() || base_local->in(0) != alloc)) { + // Allocation of another object + result = proj_in->in(TypeFunc::Memory); } } } else if (proj_in->is_MemBar()) { @@ -233,6 +346,8 @@ Node *MemNode::optimize_memory_chain(Node *mchain, const TypePtr *t_adr, Node *l mem_t = mem_t->is_aryptr() ->cast_to_stable(t_oop->is_aryptr()->is_stable()) ->cast_to_size(t_oop->is_aryptr()->size()) + ->cast_to_not_flat(t_oop->is_aryptr()->is_not_flat()) + ->cast_to_not_null_free(t_oop->is_aryptr()->is_not_null_free()) ->with_offset(t_oop->is_aryptr()->offset()) ->is_aryptr(); } @@ -259,7 +374,7 @@ static Node *step_through_mergemem(PhaseGVN *phase, MergeMemNode *mmem, const T phase->C->must_alias(adr_check, alias_idx ); // Sometimes dead array references collapse to a[-1], a[-2], or a[-3] if( !consistent && adr_check != nullptr && !adr_check->empty() && - tp->isa_aryptr() && tp->offset() == Type::OffsetBot && + tp->isa_aryptr() && tp->offset() == Type::OffsetBot && adr_check->isa_aryptr() && adr_check->offset() != Type::OffsetBot && ( adr_check->offset() == arrayOopDesc::length_offset_in_bytes() || adr_check->offset() == oopDesc::klass_offset_in_bytes() || @@ -1019,7 +1134,7 @@ static bool skip_through_membars(Compile::AliasType* atp, const TypeInstPtr* tp, (tp != nullptr) && (tp->isa_aryptr() != nullptr) && tp->isa_aryptr()->is_stable(); - return (eliminate_boxing && non_volatile) || is_stable_ary; + return (eliminate_boxing && non_volatile) || is_stable_ary || tp->is_inlinetypeptr(); } return false; @@ -1075,8 +1190,7 @@ Node* LoadNode::can_see_arraycopy_value(Node* st, PhaseGVN* phase) const { BasicType ary_elem = ary_t->isa_aryptr()->elem()->array_element_basic_type(); if (is_reference_type(ary_elem, true)) ary_elem = T_OBJECT; - uint header = arrayOopDesc::base_offset_in_bytes(ary_elem); - uint shift = exact_log2(type2aelembytes(ary_elem)); + uint shift = ary_t->is_flat() ? ary_t->flat_log_elem_size() : exact_log2(type2aelembytes(ary_elem)); Node* diff = phase->transform(new SubINode(ac->in(ArrayCopyNode::SrcPos), ac->in(ArrayCopyNode::DestPos))); #ifdef _LP64 @@ -1100,6 +1214,16 @@ Node* LoadNode::can_see_arraycopy_value(Node* st, PhaseGVN* phase) const { return nullptr; } +static Node* see_through_inline_type(PhaseValues* phase, const MemNode* load, Node* base, int offset) { + if (!load->is_mismatched_access() && base != nullptr && base->is_InlineType() && offset > oopDesc::klass_offset_in_bytes()) { + InlineTypeNode* vt = base->as_InlineType(); + Node* value = vt->field_value_by_offset(offset, true); + assert(value != nullptr, "must see some value"); + return value; + } + + return nullptr; +} //---------------------------can_see_stored_value------------------------------ // This routine exists to make sure this set of tests is done the same @@ -1112,6 +1236,15 @@ Node* MemNode::can_see_stored_value(Node* st, PhaseValues* phase) const { Node* ld_adr = in(MemNode::Address); intptr_t ld_off = 0; Node* ld_base = AddPNode::Ideal_base_and_offset(ld_adr, phase, ld_off); + // Try to see through an InlineTypeNode + // LoadN is special because the input is not compressed + if (Opcode() != Op_LoadN) { + Node* value = see_through_inline_type(phase, this, ld_base, ld_off); + if (value != nullptr) { + return value; + } + } + Node* ld_alloc = AllocateNode::Ideal_allocation(ld_base); const TypeInstPtr* tp = phase->type(ld_adr)->isa_instptr(); Compile::AliasType* atp = (tp != nullptr) ? phase->C->alias_type(tp) : nullptr; @@ -1195,7 +1328,7 @@ Node* MemNode::can_see_stored_value(Node* st, PhaseValues* phase) const { // LoadVector/StoreVector needs additional check to ensure the types match. if (st->is_StoreVector()) { const TypeVect* in_vt = st->as_StoreVector()->vect_type(); - const TypeVect* out_vt = as_LoadVector()->vect_type(); + const TypeVect* out_vt = is_Load() ? as_LoadVector()->vect_type() : as_StoreVector()->vect_type(); if (in_vt != out_vt) { return nullptr; } @@ -1213,6 +1346,13 @@ Node* MemNode::can_see_stored_value(Node* st, PhaseValues* phase) const { // (This is one of the few places where a generic PhaseTransform // can create new nodes. Think of it as lazily manifesting // virtually pre-existing constants.) + Node* init_value = ld_alloc->in(AllocateNode::InitValue); + if (init_value != nullptr) { + // TODO 8350865 Scalar replacement does not work well for flat arrays. + // Is this correct for non-all-zero init values? Don't we need field_value_by_offset? + return init_value; + } + assert(ld_alloc->in(AllocateNode::RawInitValue) == nullptr, "init value may not be null"); if (value_basic_type() != T_VOID) { if (ReduceBulkZeroing || find_array_copy_clone(ld_alloc, in(MemNode::Memory)) == nullptr) { // If ReduceBulkZeroing is disabled, we need to check if the allocation does not belong to an @@ -1873,6 +2013,7 @@ Node *LoadNode::Ideal(PhaseGVN *phase, bool can_reshape) { && phase->C->get_alias_index(phase->type(address)->is_ptr()) != Compile::AliasIdxRaw) { // Check for useless control edge in some common special cases if (in(MemNode::Control) != nullptr + && !(phase->type(address)->is_inlinetypeptr() && is_mismatched_access()) && can_remove_control() && phase->type(base)->higher_equal(TypePtr::NOTNULL) && all_controls_dominate(base, phase->C->start())) { @@ -1969,7 +2110,14 @@ Node *LoadNode::Ideal(PhaseGVN *phase, bool can_reshape) { } } - return progress ? this : nullptr; + if (progress) { + return this; + } + + if (!can_reshape) { + phase->record_for_igvn(this); + } + return nullptr; } // Helper to recognize certain Klass fields which are invariant across @@ -2069,6 +2217,7 @@ const Type* LoadNode::Value(PhaseGVN* phase) const { // expression (LShiftL quux 3) independently optimized to the constant 8. if ((t->isa_int() == nullptr) && (t->isa_long() == nullptr) && (_type->isa_vect() == nullptr) + && !ary->is_flat() && Opcode() != Op_LoadKlass && Opcode() != Op_LoadNKlass) { // t might actually be lower than _type, if _type is a unique // concrete subclass of abstract class t. @@ -2104,16 +2253,20 @@ const Type* LoadNode::Value(PhaseGVN* phase) const { // arrays can be cast to Objects !tp->isa_instptr() || tp->is_instptr()->instance_klass()->is_java_lang_Object() || + // Default value load + tp->is_instptr()->instance_klass() == ciEnv::current()->Class_klass() || // unsafe field access may not have a constant offset C->has_unsafe_access(), "Field accesses must be precise" ); // For oop loads, we expect the _type to be precise. - // Optimize loads from constant fields. const TypeInstPtr* tinst = tp->is_instptr(); + BasicType bt = value_basic_type(); + + // Optimize loads from constant fields. ciObject* const_oop = tinst->const_oop(); if (!is_mismatched_access() && off != Type::OffsetBot && const_oop != nullptr && const_oop->is_instance()) { - const Type* con_type = Type::make_constant_from_field(const_oop->as_instance(), off, is_unsigned(), value_basic_type()); + const Type* con_type = Type::make_constant_from_field(const_oop->as_instance(), off, is_unsigned(), bt); if (con_type != nullptr) { return con_type; } @@ -2160,7 +2313,7 @@ const Type* LoadNode::Value(PhaseGVN* phase) const { assert(Opcode() == Op_LoadI, "must load an int from _super_check_offset"); return TypeInt::make(klass->super_check_offset()); } - if (UseCompactObjectHeaders) { + if (UseCompactObjectHeaders) { // TODO: Should EnableValhalla also take this path ? if (tkls->offset() == in_bytes(Klass::prototype_header_offset())) { // The field is Klass::_prototype_header. Return its (constant) value. assert(this->Opcode() == Op_LoadX, "must load a proper type from _prototype_header"); @@ -2236,14 +2389,34 @@ const Type* LoadNode::Value(PhaseGVN* phase) const { Node *mem = in(MemNode::Memory); if (mem->is_Parm() && mem->in(0)->is_Start()) { assert(mem->as_Parm()->_con == TypeFunc::Memory, "must be memory Parm"); + // TODO 8350865 Scalar replacement does not work well for flat arrays. + // Escape Analysis assumes that arrays are always zeroed during allocation which is not true for null-free arrays + // ConnectionGraph::split_unique_types will re-wire the memory of loads from such arrays around the allocation + // TestArrays::test6 and test152 and TestBasicFunctionality::test20 are affected by this. + if (tp->isa_aryptr() && tp->is_aryptr()->is_flat() && tp->is_aryptr()->is_null_free()) { + intptr_t offset = 0; + Node* base = AddPNode::Ideal_base_and_offset(adr, phase, offset); + AllocateNode* alloc = AllocateNode::Ideal_allocation(base); + if (alloc != nullptr && alloc->is_AllocateArray() && alloc->in(AllocateNode::InitValue) != nullptr) { + return _type; + } + } return Type::get_zero_type(_type->basic_type()); } } - if (!UseCompactObjectHeaders) { Node* alloc = is_new_object_mark_load(); if (alloc != nullptr) { - return TypeX::make(markWord::prototype().value()); + if (EnableValhalla) { + // The mark word may contain property bits (inline, flat, null-free) + Node* klass_node = alloc->in(AllocateNode::KlassNode); + const TypeKlassPtr* tkls = phase->type(klass_node)->isa_klassptr(); + if (tkls != nullptr && tkls->is_loaded() && tkls->klass_is_exact()) { + return TypeX::make(tkls->exact_klass()->prototype_header()); + } + } else { + return TypeX::make(markWord::prototype().value()); + } } } @@ -2392,6 +2565,19 @@ const Type* LoadSNode::Value(PhaseGVN* phase) const { return LoadNode::Value(phase); } +Node* LoadNNode::Ideal(PhaseGVN* phase, bool can_reshape) { + // Loading from an InlineType, find the input and make an EncodeP + Node* addr = in(Address); + intptr_t offset; + Node* base = AddPNode::Ideal_base_and_offset(addr, phase, offset); + Node* value = see_through_inline_type(phase, this, base, offset); + if (value != nullptr) { + return new EncodePNode(value, type()); + } + + return LoadNode::Ideal(phase, can_reshape); +} + //============================================================================= //----------------------------LoadKlassNode::make------------------------------ // Polymorphic factory method: @@ -2465,7 +2651,7 @@ const Type* LoadNode::klass_value_common(PhaseGVN* phase) const { } // Check for loading klass from an array - const TypeAryPtr *tary = tp->isa_aryptr(); + const TypeAryPtr* tary = tp->isa_aryptr(); if (tary != nullptr && tary->offset() == oopDesc::klass_offset_in_bytes()) { return tary->as_klass_type(true); @@ -2556,8 +2742,9 @@ Node* LoadNode::klass_identity_common(PhaseGVN* phase) { if (base2->is_Load()) { /* direct load of a load which is the OopHandle */ Node* adr2 = base2->in(MemNode::Address); const TypeKlassPtr* tkls = phase->type(adr2)->isa_klassptr(); + // TODO 8366668 Re-enable this for arrays if (tkls != nullptr && !tkls->empty() - && (tkls->isa_instklassptr() || tkls->isa_aryklassptr()) + && ((tkls->isa_instklassptr() && !tkls->is_instklassptr()->might_be_an_array()) || (tkls->isa_aryklassptr() && false)) && adr2->is_AddP() ) { int mirror_field = in_bytes(Klass::java_mirror_offset()); @@ -3381,8 +3568,8 @@ Node *StoreNode::Ideal(PhaseGVN *phase, bool can_reshape) { Node* address = in(MemNode::Address); Node* value = in(MemNode::ValueIn); // Back-to-back stores to same address? Fold em up. Generally - // unsafe if I have intervening uses. - { + // unsafe if I have intervening uses... + if (phase->C->get_adr_type(phase->C->get_alias_index(adr_type())) != TypeAryPtr::INLINES) { Node* st = mem; // If Store 'st' has more than one use, we cannot fold 'st' away. // For example, 'st' might be the final state at a conditional @@ -3402,6 +3589,8 @@ Node *StoreNode::Ideal(PhaseGVN *phase, bool can_reshape) { phase->C->get_alias_index(adr_type()) == Compile::AliasIdxRaw || (Opcode() == Op_StoreL && st->Opcode() == Op_StoreI) || // expanded ClearArrayNode (Opcode() == Op_StoreI && st->Opcode() == Op_StoreL) || // initialization by arraycopy + (Opcode() == Op_StoreL && st->Opcode() == Op_StoreN) || + (st->adr_type()->isa_aryptr() && st->adr_type()->is_aryptr()->is_flat()) || // TODO 8343835 (is_mismatched_access() || st->as_Store()->is_mismatched_access()), "no mismatched stores, except on raw memory: %s %s", NodeClassNames[Opcode()], NodeClassNames[st->Opcode()]); @@ -3539,14 +3728,14 @@ Node* StoreNode::Identity(PhaseGVN* phase) { // Store of zero anywhere into a freshly-allocated object? // Then the store is useless. // (It must already have been captured by the InitializeNode.) - if (result == this && - ReduceFieldZeroing && phase->type(val)->is_zero_type()) { + if (result == this && ReduceFieldZeroing) { // a newly allocated object is already all-zeroes everywhere - if (mem->is_Proj() && mem->in(0)->is_Allocate()) { + if (mem->is_Proj() && mem->in(0)->is_Allocate() && + (phase->type(val)->is_zero_type() || mem->in(0)->in(AllocateNode::InitValue) == val)) { result = mem; } - if (result == this) { + if (result == this && phase->type(val)->is_zero_type()) { // the store may also apply to zero-bits in an earlier object Node* prev_mem = find_previous_store(phase); // Steps (a), (b): Walk past independent stores to find an exact match. @@ -4050,7 +4239,7 @@ Node *ClearArrayNode::Ideal(PhaseGVN *phase, bool can_reshape) { // Length too long; communicate this to matchers and assemblers. // Assemblers are responsible to produce fast hardware clears for it. if (size > InitArrayShortSize) { - return new ClearArrayNode(in(0), in(1), in(2), in(3), true); + return new ClearArrayNode(in(0), in(1), in(2), in(3), in(4), true); } else if (size > 2 && Matcher::match_rule_supported_vector(Op_ClearArray, 4, T_LONG)) { return nullptr; } @@ -4068,14 +4257,14 @@ Node *ClearArrayNode::Ideal(PhaseGVN *phase, bool can_reshape) { if( adr->Opcode() != Op_AddP ) Unimplemented(); Node *base = adr->in(1); - Node *zero = phase->makecon(TypeLong::ZERO); + Node *val = in(4); Node *off = phase->MakeConX(BytesPerLong); - mem = new StoreLNode(in(0),mem,adr,atp,zero,MemNode::unordered,false); + mem = new StoreLNode(in(0), mem, adr, atp, val, MemNode::unordered, false); count--; while( count-- ) { mem = phase->transform(mem); adr = phase->transform(new AddPNode(base,adr,off)); - mem = new StoreLNode(in(0),mem,adr,atp,zero,MemNode::unordered,false); + mem = new StoreLNode(in(0), mem, adr, atp, val, MemNode::unordered, false); } return mem; } @@ -4109,6 +4298,8 @@ bool ClearArrayNode::step_through(Node** np, uint instance_id, PhaseValues* phas //----------------------------clear_memory------------------------------------- // Generate code to initialize object storage to zero. Node* ClearArrayNode::clear_memory(Node* ctl, Node* mem, Node* dest, + Node* val, + Node* raw_val, intptr_t start_offset, Node* end_offset, PhaseGVN* phase) { @@ -4119,17 +4310,24 @@ Node* ClearArrayNode::clear_memory(Node* ctl, Node* mem, Node* dest, Node* adr = new AddPNode(dest, dest, phase->MakeConX(offset)); adr = phase->transform(adr); const TypePtr* atp = TypeRawPtr::BOTTOM; - mem = StoreNode::make(*phase, ctl, mem, adr, atp, phase->zerocon(T_INT), T_INT, MemNode::unordered); + if (val != nullptr) { + assert(phase->type(val)->isa_narrowoop(), "should be narrow oop"); + mem = new StoreNNode(ctl, mem, adr, atp, val, MemNode::unordered); + } else { + assert(raw_val == nullptr, "val may not be null"); + mem = StoreNode::make(*phase, ctl, mem, adr, atp, phase->zerocon(T_INT), T_INT, MemNode::unordered); + } mem = phase->transform(mem); offset += BytesPerInt; } assert((offset % unit) == 0, ""); // Initialize the remaining stuff, if any, with a ClearArray. - return clear_memory(ctl, mem, dest, phase->MakeConX(offset), end_offset, phase); + return clear_memory(ctl, mem, dest, raw_val, phase->MakeConX(offset), end_offset, phase); } Node* ClearArrayNode::clear_memory(Node* ctl, Node* mem, Node* dest, + Node* raw_val, Node* start_offset, Node* end_offset, PhaseGVN* phase) { @@ -4152,11 +4350,16 @@ Node* ClearArrayNode::clear_memory(Node* ctl, Node* mem, Node* dest, // Bulk clear double-words Node* zsize = phase->transform(new SubXNode(zend, zbase) ); Node* adr = phase->transform(new AddPNode(dest, dest, start_offset) ); - mem = new ClearArrayNode(ctl, mem, zsize, adr, false); + if (raw_val == nullptr) { + raw_val = phase->MakeConX(0); + } + mem = new ClearArrayNode(ctl, mem, zsize, adr, raw_val, false); return phase->transform(mem); } Node* ClearArrayNode::clear_memory(Node* ctl, Node* mem, Node* dest, + Node* val, + Node* raw_val, intptr_t start_offset, intptr_t end_offset, PhaseGVN* phase) { @@ -4171,14 +4374,20 @@ Node* ClearArrayNode::clear_memory(Node* ctl, Node* mem, Node* dest, done_offset -= BytesPerInt; } if (done_offset > start_offset) { - mem = clear_memory(ctl, mem, dest, + mem = clear_memory(ctl, mem, dest, val, raw_val, start_offset, phase->MakeConX(done_offset), phase); } if (done_offset < end_offset) { // emit the final 32-bit store Node* adr = new AddPNode(dest, dest, phase->MakeConX(done_offset)); adr = phase->transform(adr); const TypePtr* atp = TypeRawPtr::BOTTOM; - mem = StoreNode::make(*phase, ctl, mem, adr, atp, phase->zerocon(T_INT), T_INT, MemNode::unordered); + if (val != nullptr) { + assert(phase->type(val)->isa_narrowoop(), "should be narrow oop"); + mem = new StoreNNode(ctl, mem, adr, atp, val, MemNode::unordered); + } else { + assert(raw_val == nullptr, "val may not be null"); + mem = StoreNode::make(*phase, ctl, mem, adr, atp, phase->zerocon(T_INT), T_INT, MemNode::unordered); + } mem = phase->transform(mem); done_offset += BytesPerInt; } @@ -4324,7 +4533,7 @@ const Type* MemBarNode::Value(PhaseGVN* phase) const { //------------------------------match------------------------------------------ // Construct projections for memory. -Node *MemBarNode::match( const ProjNode *proj, const Matcher *m ) { +Node *MemBarNode::match(const ProjNode *proj, const Matcher *m, const RegMask* mask) { switch (proj->_con) { case TypeFunc::Control: case TypeFunc::Memory: @@ -4611,7 +4820,9 @@ void InitializeNode::set_complete(PhaseGVN* phase) { // return false if the init contains any stores already bool AllocateNode::maybe_set_complete(PhaseGVN* phase) { InitializeNode* init = initialization(); - if (init == nullptr || init->is_complete()) return false; + if (init == nullptr || init->is_complete()) { + return false; + } init->remove_extra_zeroes(); // for now, if this allocation has already collected any inits, bail: if (init->is_non_zero()) return false; @@ -4795,6 +5006,12 @@ intptr_t InitializeNode::can_capture_store(StoreNode* st, PhaseGVN* phase, bool // the store control then we cannot capture the store. assert(!n->is_Store(), "2 stores to same slice on same control?"); Node* base = other_adr; + if (base->is_Phi()) { + // In rare case, base may be a PhiNode and it may read + // the same memory slice between InitializeNode and store. + failed = true; + break; + } assert(base->is_AddP(), "should be addp but is %s", base->Name()); base = base->in(AddPNode::Base); if (base != nullptr) { @@ -5381,6 +5598,8 @@ Node* InitializeNode::complete_stores(Node* rawctl, Node* rawmem, Node* rawptr, // Do some incremental zeroing on rawmem, in parallel with inits. zeroes_done = align_down(zeroes_done, BytesPerInt); rawmem = ClearArrayNode::clear_memory(rawctl, rawmem, rawptr, + allocation()->in(AllocateNode::InitValue), + allocation()->in(AllocateNode::RawInitValue), zeroes_done, zeroes_needed, phase); zeroes_done = zeroes_needed; @@ -5440,6 +5659,8 @@ Node* InitializeNode::complete_stores(Node* rawctl, Node* rawmem, Node* rawptr, } if (zeroes_done < size_limit) { rawmem = ClearArrayNode::clear_memory(rawctl, rawmem, rawptr, + allocation()->in(AllocateNode::InitValue), + allocation()->in(AllocateNode::RawInitValue), zeroes_done, size_in_bytes, phase); } } diff --git a/src/hotspot/share/opto/memnode.hpp b/src/hotspot/share/opto/memnode.hpp index 810a9fe9445..f47796a11e2 100644 --- a/src/hotspot/share/opto/memnode.hpp +++ b/src/hotspot/share/opto/memnode.hpp @@ -126,6 +126,10 @@ class MemNode : public Node { return DEBUG_ONLY(_adr_type) NOT_DEBUG(nullptr); } +#ifdef ASSERT + void set_adr_type(const TypePtr* adr_type) { _adr_type = adr_type; } +#endif + // Return the barrier data of n, if available, or 0 otherwise. static uint8_t barrier_data(const Node* n); @@ -517,6 +521,7 @@ class LoadNNode : public LoadNode { public: LoadNNode(Node *c, Node *mem, Node *adr, const TypePtr *at, const Type* t, MemOrd mo, ControlDependency control_dependency = DependsOnlyOnTest) : LoadNode(c, mem, adr, at, t, mo, control_dependency) {} + virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); virtual int Opcode() const; virtual uint ideal_reg() const { return Op_RegN; } virtual int store_Opcode() const { return Op_StoreN; } @@ -566,7 +571,6 @@ class LoadNKlassNode : public LoadNNode { virtual bool depends_only_on_test() const { return true; } }; - //------------------------------StoreNode-------------------------------------- // Store value; requires Store, Address and Value class StoreNode : public MemNode { @@ -720,6 +724,25 @@ class StoreLNode : public StoreNode { #endif }; +// Special StoreL for flat stores that emits GC barriers for field at 'oop_off' in the backend +class StoreLSpecialNode : public StoreNode { + +public: + StoreLSpecialNode(Node* c, Node* mem, Node* adr, const TypePtr* at, Node* val, Node* oop_off, MemOrd mo) + : StoreNode(c, mem, adr, at, val, mo) { + set_mismatched_access(); + if (oop_off != nullptr) { + add_req(oop_off); + } + } + virtual int Opcode() const; + virtual BasicType value_basic_type() const { return T_LONG; } + + virtual uint match_edge(uint idx) const { return idx == MemNode::Address || + idx == MemNode::ValueIn || + idx == MemNode::ValueIn + 1; } +}; + //------------------------------StoreFNode------------------------------------- // Store float to memory class StoreFNode : public StoreNode { @@ -1075,9 +1098,11 @@ class GetAndSetNNode : public LoadStoreNode { class ClearArrayNode: public Node { private: bool _is_large; + bool _word_copy_only; public: - ClearArrayNode( Node *ctrl, Node *arymem, Node *word_cnt, Node *base, bool is_large) - : Node(ctrl,arymem,word_cnt,base), _is_large(is_large) { + ClearArrayNode( Node *ctrl, Node *arymem, Node *word_cnt, Node *base, Node* val, bool is_large) + : Node(ctrl, arymem, word_cnt, base, val), _is_large(is_large), + _word_copy_only(val->bottom_type()->isa_long() && (!val->bottom_type()->is_long()->is_con() || val->bottom_type()->is_long()->get_con() != 0)) { init_class_id(Class_ClearArray); } virtual int Opcode() const; @@ -1089,6 +1114,7 @@ class ClearArrayNode: public Node { virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); virtual uint match_edge(uint idx) const; bool is_large() const { return _is_large; } + bool word_copy_only() const { return _word_copy_only; } virtual uint size_of() const { return sizeof(ClearArrayNode); } virtual uint hash() const { return Node::hash() + _is_large; } virtual bool cmp(const Node& n) const { @@ -1100,14 +1126,19 @@ class ClearArrayNode: public Node { // The end offset must always be aligned mod BytesPerLong. // Return the new memory. static Node* clear_memory(Node* control, Node* mem, Node* dest, + Node* val, + Node* raw_val, intptr_t start_offset, intptr_t end_offset, PhaseGVN* phase); static Node* clear_memory(Node* control, Node* mem, Node* dest, + Node* val, + Node* raw_val, intptr_t start_offset, Node* end_offset, PhaseGVN* phase); static Node* clear_memory(Node* control, Node* mem, Node* dest, + Node* raw_val, Node* start_offset, Node* end_offset, PhaseGVN* phase); @@ -1159,7 +1190,7 @@ class MemBarNode: public MultiNode { virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); virtual uint match_edge(uint idx) const { return 0; } virtual const Type *bottom_type() const { return TypeTuple::MEMBAR; } - virtual Node *match( const ProjNode *proj, const Matcher *m ); + virtual Node *match(const ProjNode *proj, const Matcher *m, const RegMask* mask); // Factory method. Builds a wide or narrow membar. // Optional 'precedent' becomes an extra edge if not null. static MemBarNode* make(Compile* C, int opcode, diff --git a/src/hotspot/share/opto/mulnode.cpp b/src/hotspot/share/opto/mulnode.cpp index 4691c6a45b0..ba696b9c431 100644 --- a/src/hotspot/share/opto/mulnode.cpp +++ b/src/hotspot/share/opto/mulnode.cpp @@ -198,6 +198,18 @@ const Type* MulNode::Value(PhaseGVN* phase) const { if( t2->higher_equal( zero ) ) return zero; } + // Code pattern on return from a call that returns an __Value. Can + // be optimized away if the return value turns out to be an oop. + if (op == Op_AndX && + in(1) != nullptr && + in(1)->Opcode() == Op_CastP2X && + in(1)->in(1) != nullptr && + phase->type(in(1)->in(1))->isa_oopptr() && + t2->isa_intptr_t()->_lo >= 0 && + t2->isa_intptr_t()->_hi <= MinObjAlignmentInBytesMask) { + return add_id(); + } + // Either input is BOTTOM ==> the result is the local BOTTOM if( t1 == Type::BOTTOM || t2 == Type::BOTTOM ) return bottom_type(); @@ -937,6 +949,47 @@ Node *AndLNode::Ideal(PhaseGVN *phase, bool can_reshape) { } } + // Search for GraphKit::mark_word_test patterns and fold the test if the result is statically known + Node* load1 = in(1); + Node* load2 = nullptr; + if (load1->is_Phi() && phase->type(load1)->isa_long()) { + load1 = in(1)->in(1); + load2 = in(1)->in(2); + } + if (load1 != nullptr && load1->is_Load() && phase->type(load1)->isa_long() && + (load2 == nullptr || (load2->is_Load() && phase->type(load2)->isa_long()))) { + const TypePtr* adr_t1 = phase->type(load1->in(MemNode::Address))->isa_ptr(); + const TypePtr* adr_t2 = (load2 != nullptr) ? phase->type(load2->in(MemNode::Address))->isa_ptr() : nullptr; + if (adr_t1 != nullptr && adr_t1->offset() == oopDesc::mark_offset_in_bytes() && + (load2 == nullptr || (adr_t2 != nullptr && adr_t2->offset() == in_bytes(Klass::prototype_header_offset())))) { + if (mask == markWord::inline_type_pattern) { + if (adr_t1->is_inlinetypeptr()) { + set_req_X(1, in(2), phase); + return this; + } else if (!adr_t1->can_be_inline_type()) { + set_req_X(1, phase->longcon(0), phase); + return this; + } + } else if (mask == markWord::null_free_array_bit_in_place) { + if (adr_t1->is_null_free()) { + set_req_X(1, in(2), phase); + return this; + } else if (adr_t1->is_not_null_free()) { + set_req_X(1, phase->longcon(0), phase); + return this; + } + } else if (mask == markWord::flat_array_bit_in_place) { + if (adr_t1->is_flat()) { + set_req_X(1, in(2), phase); + return this; + } else if (adr_t1->is_not_flat()) { + set_req_X(1, phase->longcon(0), phase); + return this; + } + } + } + } + return MulNode::Ideal(phase, can_reshape); } diff --git a/src/hotspot/share/opto/multnode.cpp b/src/hotspot/share/opto/multnode.cpp index f429d5daac0..1e16138a940 100644 --- a/src/hotspot/share/opto/multnode.cpp +++ b/src/hotspot/share/opto/multnode.cpp @@ -39,7 +39,7 @@ const RegMask &MultiNode::out_RegMask() const { return RegMask::Empty; } -Node *MultiNode::match( const ProjNode *proj, const Matcher *m ) { return proj->clone(); } +Node *MultiNode::match(const ProjNode *proj, const Matcher *m, const RegMask* mask) { return proj->clone(); } //------------------------------proj_out--------------------------------------- // Get a named projection or null if not found diff --git a/src/hotspot/share/opto/multnode.hpp b/src/hotspot/share/opto/multnode.hpp index 834dcfdca6d..c4bd22ebb9c 100644 --- a/src/hotspot/share/opto/multnode.hpp +++ b/src/hotspot/share/opto/multnode.hpp @@ -44,7 +44,7 @@ class MultiNode : public Node { virtual uint hash() const { return NO_HASH; } // CFG nodes do not hash virtual bool depends_only_on_test() const { return false; } virtual const RegMask &out_RegMask() const; - virtual Node *match( const ProjNode *proj, const Matcher *m ); + virtual Node *match(const ProjNode *proj, const Matcher *m, const RegMask* mask); virtual uint ideal_reg() const { return NotAMachineReg; } ProjNode* proj_out(uint which_proj) const; // Get a named projection ProjNode* proj_out_or_null(uint which_proj) const; diff --git a/src/hotspot/share/opto/narrowptrnode.cpp b/src/hotspot/share/opto/narrowptrnode.cpp index 7f86b8caecf..f032f3353ad 100644 --- a/src/hotspot/share/opto/narrowptrnode.cpp +++ b/src/hotspot/share/opto/narrowptrnode.cpp @@ -41,7 +41,7 @@ const Type* DecodeNNode::Value(PhaseGVN* phase) const { if (t == Type::TOP) return Type::TOP; if (t == TypeNarrowOop::NULL_PTR) return TypePtr::NULL_PTR; - assert(t->isa_narrowoop(), "only narrowoop here"); + assert(t->isa_narrowoop(), "only narrowoop here"); return t->make_ptr(); } diff --git a/src/hotspot/share/opto/node.cpp b/src/hotspot/share/opto/node.cpp index 5ecc038954d..6c9d9f7ef0a 100644 --- a/src/hotspot/share/opto/node.cpp +++ b/src/hotspot/share/opto/node.cpp @@ -33,6 +33,7 @@ #include "opto/castnode.hpp" #include "opto/cfgnode.hpp" #include "opto/connode.hpp" +#include "opto/inlinetypenode.hpp" #include "opto/loopnode.hpp" #include "opto/machnode.hpp" #include "opto/matcher.hpp" @@ -563,6 +564,9 @@ Node *Node::clone() const { n->as_SafePoint()->clone_jvms(C); n->as_SafePoint()->clone_replaced_nodes(); } + if (n->is_InlineType()) { + C->add_inline_type(n); + } Compile::current()->record_modified_node(n); return n; // Return the clone } @@ -623,6 +627,9 @@ void Node::destruct(PhaseValues* phase) { if (for_post_loop_opts_igvn()) { compile->remove_from_post_loop_opts_igvn(this); } + if (is_InlineType()) { + compile->remove_inline_type(this); + } if (for_merge_stores_igvn()) { compile->remove_from_merge_stores_igvn(this); } diff --git a/src/hotspot/share/opto/node.hpp b/src/hotspot/share/opto/node.hpp index c2b3c4fb0ad..c840288b73d 100644 --- a/src/hotspot/share/opto/node.hpp +++ b/src/hotspot/share/opto/node.hpp @@ -87,6 +87,7 @@ class EncodePNode; class EncodePKlassNode; class FastLockNode; class FastUnlockNode; +class FlatArrayCheckNode; class HaltNode; class IfNode; class IfProjNode; @@ -119,12 +120,14 @@ class MachJumpNode; class MachNode; class MachNullCheckNode; class MachProjNode; +class MachPrologNode; class MachReturnNode; class MachSafePointNode; class MachSpillCopyNode; class MachTempNode; class MachMergeNode; class MachMemBarNode; +class MachVEPNode; class Matcher; class MemBarNode; class MemBarStoreStoreNode; @@ -182,6 +185,7 @@ class SubTypeCheckNode; class Type; class TypeNode; class UnlockNode; +class InlineTypeNode; class VectorNode; class LoadVectorNode; class LoadVectorMaskedNode; @@ -697,6 +701,7 @@ class Node { DEFINE_CLASS_ID(MemBar, Multi, 3) DEFINE_CLASS_ID(Initialize, MemBar, 0) DEFINE_CLASS_ID(MemBarStoreStore, MemBar, 1) + DEFINE_CLASS_ID(Blackhole, MemBar, 2) DEFINE_CLASS_ID(Mach, Node, 1) DEFINE_CLASS_ID(MachReturn, Mach, 0) @@ -718,6 +723,8 @@ class Node { DEFINE_CLASS_ID(MachJump, MachConstant, 0) DEFINE_CLASS_ID(MachMerge, Mach, 6) DEFINE_CLASS_ID(MachMemBar, Mach, 7) + DEFINE_CLASS_ID(MachProlog, Mach, 8) + DEFINE_CLASS_ID(MachVEP, Mach, 9) DEFINE_CLASS_ID(Type, Node, 2) DEFINE_CLASS_ID(Phi, Type, 0) @@ -750,10 +757,11 @@ class Node { DEFINE_CLASS_ID(NegV, Vector, 8) DEFINE_CLASS_ID(SaturatingVector, Vector, 9) DEFINE_CLASS_ID(MulVL, Vector, 10) - DEFINE_CLASS_ID(Con, Type, 8) + DEFINE_CLASS_ID(InlineType, Type, 8) + DEFINE_CLASS_ID(Con, Type, 9) DEFINE_CLASS_ID(ConI, Con, 0) - DEFINE_CLASS_ID(SafePointScalarMerge, Type, 9) - DEFINE_CLASS_ID(Convert, Type, 10) + DEFINE_CLASS_ID(SafePointScalarMerge, Type, 10) + DEFINE_CLASS_ID(Convert, Type, 11) DEFINE_CLASS_ID(Proj, Node, 3) @@ -791,9 +799,10 @@ class Node { DEFINE_CLASS_ID(Sub, Node, 6) DEFINE_CLASS_ID(Cmp, Sub, 0) - DEFINE_CLASS_ID(FastLock, Cmp, 0) - DEFINE_CLASS_ID(FastUnlock, Cmp, 1) - DEFINE_CLASS_ID(SubTypeCheck,Cmp, 2) + DEFINE_CLASS_ID(FastLock, Cmp, 0) + DEFINE_CLASS_ID(FastUnlock, Cmp, 1) + DEFINE_CLASS_ID(SubTypeCheck, Cmp, 2) + DEFINE_CLASS_ID(FlatArrayCheck, Cmp, 3) DEFINE_CLASS_ID(MergeMem, Node, 7) DEFINE_CLASS_ID(Bool, Node, 8) @@ -902,6 +911,7 @@ class Node { DEFINE_CLASS_QUERY(ArrayCopy) DEFINE_CLASS_QUERY(BaseCountedLoop) DEFINE_CLASS_QUERY(BaseCountedLoopEnd) + DEFINE_CLASS_QUERY(Blackhole) DEFINE_CLASS_QUERY(Bool) DEFINE_CLASS_QUERY(BoxLock) DEFINE_CLASS_QUERY(Call) @@ -935,6 +945,7 @@ class Node { DEFINE_CLASS_QUERY(EncodePKlass) DEFINE_CLASS_QUERY(FastLock) DEFINE_CLASS_QUERY(FastUnlock) + DEFINE_CLASS_QUERY(FlatArrayCheck) DEFINE_CLASS_QUERY(Halt) DEFINE_CLASS_QUERY(If) DEFINE_CLASS_QUERY(RangeCheck) @@ -967,12 +978,14 @@ class Node { DEFINE_CLASS_QUERY(MachJump) DEFINE_CLASS_QUERY(MachNullCheck) DEFINE_CLASS_QUERY(MachProj) + DEFINE_CLASS_QUERY(MachProlog) DEFINE_CLASS_QUERY(MachReturn) DEFINE_CLASS_QUERY(MachSafePoint) DEFINE_CLASS_QUERY(MachSpillCopy) DEFINE_CLASS_QUERY(MachTemp) DEFINE_CLASS_QUERY(MachMemBar) DEFINE_CLASS_QUERY(MachMerge) + DEFINE_CLASS_QUERY(MachVEP) DEFINE_CLASS_QUERY(Mem) DEFINE_CLASS_QUERY(MemBar) DEFINE_CLASS_QUERY(MemBarStoreStore) @@ -1010,6 +1023,7 @@ class Node { DEFINE_CLASS_QUERY(Sub) DEFINE_CLASS_QUERY(SubTypeCheck) DEFINE_CLASS_QUERY(Type) + DEFINE_CLASS_QUERY(InlineType) DEFINE_CLASS_QUERY(Vector) DEFINE_CLASS_QUERY(VectorMaskCmp) DEFINE_CLASS_QUERY(VectorUnbox) diff --git a/src/hotspot/share/opto/output.cpp b/src/hotspot/share/opto/output.cpp index 124b00a6549..b79f4fab9bb 100644 --- a/src/hotspot/share/opto/output.cpp +++ b/src/hotspot/share/opto/output.cpp @@ -33,6 +33,7 @@ #include "compiler/oopMap.hpp" #include "gc/shared/barrierSet.hpp" #include "gc/shared/c2/barrierSetC2.hpp" +#include "gc/shared/gc_globals.hpp" #include "memory/allocation.hpp" #include "opto/ad.hpp" #include "opto/block.hpp" @@ -241,7 +242,15 @@ PhaseOutput::PhaseOutput() _index(0) { C->set_output(this); if (C->stub_name() == nullptr) { - _orig_pc_slot = C->fixed_slots() - (sizeof(address) / VMRegImpl::stack_slot_size); + int fixed_slots = C->fixed_slots(); + if (C->needs_stack_repair()) { + fixed_slots -= 2; + } + // TODO 8284443 Only reserve extra slot if needed + if (InlineTypeReturnedAsFields) { + fixed_slots -= 2; + } + _orig_pc_slot = fixed_slots - (sizeof(address) / VMRegImpl::stack_slot_size); } } @@ -282,24 +291,34 @@ void PhaseOutput::Output() { const StartNode *start = entry->head()->as_Start(); // Replace StartNode with prolog - MachPrologNode *prolog = new MachPrologNode(); + Label verified_entry; + MachPrologNode* prolog = new MachPrologNode(&verified_entry); entry->map_node(prolog, 0); C->cfg()->map_node_to_block(prolog, entry); C->cfg()->unmap_node_from_block(start); // start is no longer in any block // Virtual methods need an unverified entry point - - if( C->is_osr_compilation() ) { - if( PoisonOSREntry ) { + if (C->is_osr_compilation()) { + if (PoisonOSREntry) { // TODO: Should use a ShouldNotReachHereNode... C->cfg()->insert( broot, 0, new MachBreakpointNode() ); } } else { - if( C->method() && !C->method()->flags().is_static() ) { - // Insert unvalidated entry point - C->cfg()->insert( broot, 0, new MachUEPNode() ); + if (C->method()) { + if (C->method()->has_scalarized_args()) { + // Add entry point to unpack all inline type arguments + C->cfg()->insert(broot, 0, new MachVEPNode(&verified_entry, /* verified */ true, /* receiver_only */ false)); + if (!C->method()->is_static()) { + // Add verified/unverified entry points to only unpack inline type receiver at interface calls + C->cfg()->insert(broot, 0, new MachVEPNode(&verified_entry, /* verified */ false, /* receiver_only */ false)); + C->cfg()->insert(broot, 0, new MachVEPNode(&verified_entry, /* verified */ true, /* receiver_only */ true)); + C->cfg()->insert(broot, 0, new MachVEPNode(&verified_entry, /* verified */ false, /* receiver_only */ true)); + } + } else if (!C->method()->is_static()) { + // Insert unvalidated entry point + C->cfg()->insert(broot, 0, new MachUEPNode()); + } } - } // Break before main entry point @@ -339,6 +358,31 @@ void PhaseOutput::Output() { blk_starts[0] = 0; shorten_branches(blk_starts); + if (!C->is_osr_compilation() && C->has_scalarized_args()) { + // Compute the offsets of the entry points required by the inline type calling convention + if (!C->method()->is_static()) { + // We have entries at the beginning of the method, implemented by the first 4 nodes. + // Entry (unverified) @ offset 0 + // Verified_Inline_Entry_RO + // Inline_Entry (unverified) + // Verified_Inline_Entry + uint offset = 0; + _code_offsets.set_value(CodeOffsets::Entry, offset); + + offset += ((MachVEPNode*)broot->get_node(0))->size(C->regalloc()); + _code_offsets.set_value(CodeOffsets::Verified_Inline_Entry_RO, offset); + + offset += ((MachVEPNode*)broot->get_node(1))->size(C->regalloc()); + _code_offsets.set_value(CodeOffsets::Inline_Entry, offset); + + offset += ((MachVEPNode*)broot->get_node(2))->size(C->regalloc()); + _code_offsets.set_value(CodeOffsets::Verified_Inline_Entry, offset); + } else { + _code_offsets.set_value(CodeOffsets::Entry, -1); // will be patched later + _code_offsets.set_value(CodeOffsets::Verified_Inline_Entry, 0); + } + } + ScheduleAndBundle(); if (C->failing()) { return; @@ -500,7 +544,9 @@ void PhaseOutput::shorten_branches(uint* blk_starts) { MachCallNode *mcall = mach->as_MachCall(); // This destination address is NOT PC-relative - mcall->method_set((intptr_t)mcall->entry_point()); + if (mcall->entry_point() != nullptr) { + mcall->method_set((intptr_t)mcall->entry_point()); + } if (mcall->is_MachCallJava() && mcall->as_MachCallJava()->_method) { stub_size += CompiledDirectCall::to_interp_stub_size(); @@ -755,11 +801,39 @@ void PhaseOutput::FillLocArray( int idx, MachSafePointNode* sfpt, Node *local, ciKlass* cik = t->is_oopptr()->exact_klass(); assert(cik->is_instance_klass() || cik->is_array_klass(), "Not supported allocation."); + uint first_ind = spobj->first_index(sfpt->jvms()); + // Nullable, scalarized inline types have a null_marker input + // that needs to be checked before using the field values. + ScopeValue* properties = nullptr; + if (cik->is_inlinetype()) { + Node* null_marker_node = sfpt->in(first_ind++); + assert(null_marker_node != nullptr, "null_marker node not found"); + if (!null_marker_node->is_top()) { + const TypeInt* null_marker_type = null_marker_node->bottom_type()->is_int(); + if (null_marker_node->is_Con()) { + properties = new ConstantIntValue(null_marker_type->get_con()); + } else { + OptoReg::Name null_marker_reg = C->regalloc()->get_reg_first(null_marker_node); + properties = new_loc_value(C->regalloc(), null_marker_reg, Location::normal); + } + } + } + if (cik->is_array_klass() && !cik->is_type_array_klass()) { + jint props = ArrayKlass::ArrayProperties::DEFAULT; + if (cik->as_array_klass()->element_klass()->is_inlinetype()) { + if (cik->as_array_klass()->is_elem_null_free()) { + props |= ArrayKlass::ArrayProperties::NULL_RESTRICTED; + } + if (!cik->as_array_klass()->is_elem_atomic()) { + props |= ArrayKlass::ArrayProperties::NON_ATOMIC; + } + } + properties = new ConstantIntValue(props); + } sv = new ObjectValue(spobj->_idx, - new ConstantOopWriteValue(cik->java_mirror()->constant_encoding())); + new ConstantOopWriteValue(cik->java_mirror()->constant_encoding()), true, properties); set_sv_for_object_node(objs, sv); - uint first_ind = spobj->first_index(sfpt->jvms()); for (uint i = 0; i < spobj->n_fields(); i++) { Node* fld_node = sfpt->in(first_ind+i); (void)FillLocArray(sv->field_values()->length(), sfpt, fld_node, sv->field_values(), objs); @@ -1006,6 +1080,7 @@ void PhaseOutput::Process_OopMap_Node(MachNode *mach, int current_offset) { int safepoint_pc_offset = current_offset; bool is_method_handle_invoke = false; bool return_oop = false; + bool return_scalarized = false; bool has_ea_local_in_scope = sfn->_has_ea_local_in_scope; bool arg_escape = false; @@ -1026,9 +1101,12 @@ void PhaseOutput::Process_OopMap_Node(MachNode *mach, int current_offset) { } // Check if a call returns an object. - if (mcall->returns_pointer()) { + if (mcall->returns_pointer() || mcall->returns_scalarized()) { return_oop = true; } + if (mcall->returns_scalarized()) { + return_scalarized = true; + } safepoint_pc_offset += mcall->ret_addr_offset(); C->debug_info()->add_safepoint(safepoint_pc_offset, mcall->_oop_map); } @@ -1097,8 +1175,22 @@ void PhaseOutput::Process_OopMap_Node(MachNode *mach, int current_offset) { ciKlass* cik = t->is_oopptr()->exact_klass(); assert(cik->is_instance_klass() || cik->is_array_klass(), "Not supported allocation."); + assert(!cik->is_inlinetype(), "Synchronization on value object?"); + ScopeValue* properties = nullptr; + if (cik->is_array_klass() && !cik->is_type_array_klass()) { + jint props = ArrayKlass::ArrayProperties::DEFAULT; + if (cik->as_array_klass()->element_klass()->is_inlinetype()) { + if (cik->as_array_klass()->is_elem_null_free()) { + props |= ArrayKlass::ArrayProperties::NULL_RESTRICTED; + } + if (!cik->as_array_klass()->is_elem_atomic()) { + props |= ArrayKlass::ArrayProperties::NON_ATOMIC; + } + } + properties = new ConstantIntValue(props); + } ObjectValue* sv = new ObjectValue(spobj->_idx, - new ConstantOopWriteValue(cik->java_mirror()->constant_encoding())); + new ConstantOopWriteValue(cik->java_mirror()->constant_encoding()), true, properties); PhaseOutput::set_sv_for_object_node(objs, sv); uint first_ind = spobj->first_index(youngest_jvms); @@ -1206,6 +1298,7 @@ void PhaseOutput::Process_OopMap_Node(MachNode *mach, int current_offset) { rethrow_exception, is_method_handle_invoke, return_oop, + return_scalarized, has_ea_local_in_scope, arg_escape, locvals, @@ -1581,8 +1674,10 @@ void PhaseOutput::fill_buffer(C2_MacroAssembler* masm, uint* blk_starts) { if (is_mcall) { MachCallNode *mcall = mach->as_MachCall(); - // This destination address is NOT PC-relative - mcall->method_set((intptr_t)mcall->entry_point()); + if (mcall->entry_point() != nullptr) { + // This destination address is NOT PC-relative + mcall->method_set((intptr_t)mcall->entry_point()); + } // Save the return address call_returns[block->_pre_order] = current_offset + mcall->ret_addr_offset(); @@ -1723,7 +1818,6 @@ void PhaseOutput::fill_buffer(C2_MacroAssembler* masm, uint* blk_starts) { assert(!is_mcall || (call_returns[block->_pre_order] <= (uint)current_offset), "ret_addr_offset() not within emitted code"); - #ifdef ASSERT uint n_size = n->size(C->regalloc()); if (n_size < (current_offset-instr_offset)) { @@ -3161,6 +3255,19 @@ void Scheduling::ComputeRegisterAntidependencies(Block *b) { break; } } + + // Do not allow a CheckCastPP node whose input is a raw pointer to + // float past a safepoint. This can occur when a buffered inline + // type is allocated in a loop and the CheckCastPP from that + // allocation is reused outside the loop. If the use inside the + // loop is scalarized the CheckCastPP will no longer be connected + // to the loop safepoint. See JDK-8264340. + if (m->is_Mach() && m->as_Mach()->ideal_Opcode() == Op_CheckCastPP) { + Node *def = m->in(1); + if (def != nullptr && def->bottom_type()->base() == Type::RawPtr) { + last_safept_node->add_prec(m); + } + } } if( n->jvms() ) { // Precedence edge from derived to safept @@ -3319,6 +3426,25 @@ void PhaseOutput::init_scratch_buffer_blob(int const_size) { ResourceMark rm; _scratch_const_size = const_size; int size = C2Compiler::initial_code_buffer_size(const_size); + if (C->has_scalarized_args()) { + // Inline type entry points (MachVEPNodes) require lots of space for GC barriers and oop verification + // when loading object fields from the buffered argument. Increase scratch buffer size accordingly. + ciMethod* method = C->method(); + int barrier_size = UseZGC ? 200 : (7 DEBUG_ONLY(+ 37)); + int arg_num = 0; + if (!method->is_static()) { + if (method->is_scalarized_arg(arg_num)) { + size += method->holder()->as_inline_klass()->oop_count() * barrier_size; + } + arg_num++; + } + for (ciSignatureStream str(method->signature()); !str.at_return_type(); str.next()) { + if (method->is_scalarized_arg(arg_num)) { + size += str.type()->as_inline_klass()->oop_count() * barrier_size; + } + arg_num++; + } + } blob = BufferBlob::create("Compile::scratch_buffer", size); // Record the buffer blob for next time. set_scratch_buffer_blob(blob); @@ -3389,8 +3515,10 @@ uint PhaseOutput::scratch_emit_size(const Node* n) { // Emitting into the scratch buffer should not fail assert(!C->failing_internal() || C->failure_is_artificial(), "Must not have pending failure. Reason is: %s", C->failure_reason()); - if (is_branch) // Restore label. + // Restore label. + if (is_branch) { n->as_MachBranch()->label_set(saveL, save_bnum); + } // End scratch_emit_size section. set_in_scratch_emit_size(false); @@ -3431,31 +3559,34 @@ void PhaseOutput::install_code(ciMethod* target, _code_offsets.set_value(CodeOffsets::Verified_Entry, 0); _code_offsets.set_value(CodeOffsets::OSR_Entry, _first_block_size); } else { - if (!target->is_static()) { - // The UEP of an nmethod ensures that the VEP is padded. However, the padding of the UEP is placed - // before the inline cache check, so we don't have to execute any nop instructions when dispatching - // through the UEP, yet we can ensure that the VEP is aligned appropriately. - _code_offsets.set_value(CodeOffsets::Entry, _first_block_size - MacroAssembler::ic_check_size()); - } _code_offsets.set_value(CodeOffsets::Verified_Entry, _first_block_size); + if (_code_offsets.value(CodeOffsets::Verified_Inline_Entry) == -1) { + _code_offsets.set_value(CodeOffsets::Verified_Inline_Entry, _first_block_size); + } + if (_code_offsets.value(CodeOffsets::Verified_Inline_Entry_RO) == -1) { + _code_offsets.set_value(CodeOffsets::Verified_Inline_Entry_RO, _first_block_size); + } + if (_code_offsets.value(CodeOffsets::Entry) == -1) { + _code_offsets.set_value(CodeOffsets::Entry, _first_block_size); + } _code_offsets.set_value(CodeOffsets::OSR_Entry, 0); } C->env()->register_method(target, - entry_bci, - &_code_offsets, - _orig_pc_slot_offset_in_bytes, - code_buffer(), - frame_size_in_words(), - oop_map_set(), - &_handler_table, - inc_table(), - compiler, - has_unsafe_access, - SharedRuntime::is_wide_vector(C->max_vector_size()), - C->has_monitors(), - C->has_scoped_access(), - 0); + entry_bci, + &_code_offsets, + _orig_pc_slot_offset_in_bytes, + code_buffer(), + frame_size_in_words(), + _oop_map_set, + &_handler_table, + inc_table(), + compiler, + has_unsafe_access, + SharedRuntime::is_wide_vector(C->max_vector_size()), + C->has_monitors(), + C->has_scoped_access(), + 0); if (C->log() != nullptr) { // Print code cache state into compiler log C->log()->code_cache_state(); diff --git a/src/hotspot/share/opto/parse.hpp b/src/hotspot/share/opto/parse.hpp index 83b211828ce..c0a445dc892 100644 --- a/src/hotspot/share/opto/parse.hpp +++ b/src/hotspot/share/opto/parse.hpp @@ -444,8 +444,8 @@ class Parse : public GraphKit { SafePointNode* create_entry_map(); // OSR helpers - Node *fetch_interpreter_state(int index, BasicType bt, Node *local_addrs, Node *local_addrs_base); - Node* check_interpreter_type(Node* l, const Type* type, SafePointNode* &bad_type_exit); + Node* fetch_interpreter_state(int index, const Type* type, Node* local_addrs, Node* local_addrs_base); + Node* check_interpreter_type(Node* l, const Type* type, SafePointNode* &bad_type_exit, bool is_larval); void load_interpreter_state(Node* osr_buf); // Functions for managing basic blocks: @@ -481,8 +481,8 @@ class Parse : public GraphKit { // Helper: Merge the current mapping into the given basic block void merge_common(Block* target, int pnum); // Helper functions for merging individual cells. - PhiNode *ensure_phi( int idx, bool nocreate = false); - PhiNode *ensure_memory_phi(int idx, bool nocreate = false); + Node* ensure_phi( int idx, bool nocreate = false); + PhiNode* ensure_memory_phi(int idx, bool nocreate = false); // Helper to merge the current memory state into the given basic block void merge_memory_edges(MergeMemNode* n, int pnum, bool nophi); @@ -490,13 +490,23 @@ class Parse : public GraphKit { void do_one_bytecode(); // helper function to generate array store check - void array_store_check(); + Node* array_store_check(Node*& adr, const Type*& elemtype); // Helper function to generate array load void array_load(BasicType etype); + Node* load_from_unknown_flat_array(Node* array, Node* array_index, const TypeOopPtr* element_ptr); // Helper function to generate array store void array_store(BasicType etype); + void store_to_unknown_flat_array(Node* array, Node* idx, Node* non_null_stored_value); // Helper function to compute array addressing Node* array_addressing(BasicType type, int vals, const Type*& elemtype); + bool needs_range_check(const TypeInt* size_type, const Node* index) const; + Node* create_speculative_inline_type_array_checks(Node* array, const TypeAryPtr* array_type, const Type*& element_type); + Node* cast_to_speculative_array_type(Node* array, const TypeAryPtr*& array_type, const Type*& element_type); + Node* cast_to_profiled_array_type(Node* const array); + Node* speculate_non_null_free_array(Node* array, const TypeAryPtr*& array_type); + Node* speculate_non_flat_array(Node* array, const TypeAryPtr* array_type); + void create_range_check(Node* idx, Node* ary, const TypeInt* sizetype); + Node* record_profile_for_speculation_at_array_load(Node* ld); void clinit_deopt(); @@ -542,13 +552,15 @@ class Parse : public GraphKit { void do_field_access(bool is_get, bool is_field); // common code for actually performing the load or store - void do_get_xxx(Node* obj, ciField* field, bool is_field); + void do_get_xxx(Node* obj, ciField* field); void do_put_xxx(Node* obj, ciField* field, bool is_field); + ciType* improve_abstract_inline_type_klass(ciType* field_klass); + // implementation of object creation bytecodes void do_new(); void do_newarray(BasicType elemtype); - void do_anewarray(); + void do_newarray(); void do_multianewarray(); Node* expand_multianewarray(ciArrayKlass* array_klass, Node* *lengths, int ndimensions, int nargs); @@ -562,9 +574,14 @@ class Parse : public GraphKit { bool path_is_suitable_for_uncommon_trap(float prob) const; void do_ifnull(BoolTest::mask btest, Node* c); - void do_if(BoolTest::mask btest, Node* c); + void do_if(BoolTest::mask btest, Node* c, bool can_trap = true, bool new_path = false, Node** ctrl_taken = nullptr); + void do_acmp(BoolTest::mask btest, Node* left, Node* right); + void acmp_always_null_input(Node* input, const TypeOopPtr* tinput, BoolTest::mask btest, Node* eq_region); + void acmp_known_non_inline_type_input(Node* input, const TypeOopPtr* tinput, ProfilePtrKind input_ptr, ciKlass* input_type, BoolTest::mask btest, Node* eq_region); + Node* acmp_null_check(Node* input, const TypeOopPtr* tinput, ProfilePtrKind input_ptr, Node*& null_ctl); + void acmp_unknown_non_inline_type_input(Node* input, const TypeOopPtr* tinput, ProfilePtrKind input_ptr, BoolTest::mask btest, Node* eq_region); int repush_if_args(); - void adjust_map_after_if(BoolTest::mask btest, Node* c, float prob, Block* path); + void adjust_map_after_if(BoolTest::mask btest, Node* c, float prob, Block* path, bool can_trap = true); void sharpen_type_after_if(BoolTest::mask btest, Node* con, const Type* tcon, Node* val, const Type* tval); diff --git a/src/hotspot/share/opto/parse1.cpp b/src/hotspot/share/opto/parse1.cpp index f7e11f0d213..5a99f6d3653 100644 --- a/src/hotspot/share/opto/parse1.cpp +++ b/src/hotspot/share/opto/parse1.cpp @@ -29,7 +29,9 @@ #include "opto/addnode.hpp" #include "opto/c2compiler.hpp" #include "opto/castnode.hpp" +#include "opto/convertnode.hpp" #include "opto/idealGraphPrinter.hpp" +#include "opto/inlinetypenode.hpp" #include "opto/locknode.hpp" #include "opto/memnode.hpp" #include "opto/opaquenode.hpp" @@ -101,10 +103,16 @@ void Parse::print_statistics() { // Construct a node which can be used to get incoming state for // on stack replacement. -Node *Parse::fetch_interpreter_state(int index, - BasicType bt, - Node *local_addrs, - Node *local_addrs_base) { +Node* Parse::fetch_interpreter_state(int index, + const Type* type, + Node* local_addrs, + Node* local_addrs_base) { + BasicType bt = type->basic_type(); + if (type == TypePtr::NULL_PTR) { + // Ptr types are mixed together with T_ADDRESS but nullptr is + // really for T_OBJECT types so correct it. + bt = T_OBJECT; + } Node *mem = memory(Compile::AliasIdxRaw); Node *adr = basic_plus_adr( local_addrs_base, local_addrs, -index*wordSize ); Node *ctl = control(); @@ -145,8 +153,7 @@ Node *Parse::fetch_interpreter_state(int index, // not a general type, but can only come from Type::get_typeflow_type. // The safepoint is a map which will feed an uncommon trap. Node* Parse::check_interpreter_type(Node* l, const Type* type, - SafePointNode* &bad_type_exit) { - + SafePointNode* &bad_type_exit, bool is_early_larval) { const TypeOopPtr* tp = type->isa_oopptr(); // TypeFlow may assert null-ness if a type appears unloaded. @@ -170,7 +177,14 @@ Node* Parse::check_interpreter_type(Node* l, const Type* type, if (tp != nullptr && !tp->is_same_java_type_as(TypeInstPtr::BOTTOM)) { // TypeFlow asserted a specific object type. Value must have that type. Node* bad_type_ctrl = nullptr; - l = gen_checkcast(l, makecon(tp->as_klass_type()->cast_to_exactness(true)), &bad_type_ctrl); + if (tp->is_inlinetypeptr() && !tp->maybe_null()) { + // Check inline types for null here to prevent checkcast from adding an + // exception state before the bytecode entry (use 'bad_type_ctrl' instead). + l = null_check_oop(l, &bad_type_ctrl); + bad_type_exit->control()->add_req(bad_type_ctrl); + } + + l = gen_checkcast(l, makecon(tp->as_klass_type()->cast_to_exactness(true)), &bad_type_ctrl, false, is_early_larval); bad_type_exit->control()->add_req(bad_type_ctrl); } @@ -186,7 +200,6 @@ void Parse::load_interpreter_state(Node* osr_buf) { int max_locals = jvms()->loc_size(); int max_stack = jvms()->stk_size(); - // Mismatch between method and jvms can occur since map briefly held // an OSR entry state (which takes up one RawPtr word). assert(max_locals == method()->max_locals(), "sanity"); @@ -241,10 +254,9 @@ void Parse::load_interpreter_state(Node* osr_buf) { // Displaced headers and locked objects are interleaved in the // temp OSR buffer. We only copy the locked objects out here. // Fetch the locked object from the OSR temp buffer and copy to our fastlock node. - Node *lock_object = fetch_interpreter_state(index*2, T_OBJECT, monitors_addr, osr_buf); + Node* lock_object = fetch_interpreter_state(index*2, Type::get_const_basic_type(T_OBJECT), monitors_addr, osr_buf); // Try and copy the displaced header to the BoxNode - Node *displaced_hdr = fetch_interpreter_state((index*2) + 1, T_ADDRESS, monitors_addr, osr_buf); - + Node* displaced_hdr = fetch_interpreter_state((index*2) + 1, Type::get_const_basic_type(T_ADDRESS), monitors_addr, osr_buf); store_to_memory(control(), box, displaced_hdr, T_ADDRESS, MemNode::unordered); @@ -312,13 +324,7 @@ void Parse::load_interpreter_state(Node* osr_buf) { continue; } // Construct code to access the appropriate local. - BasicType bt = type->basic_type(); - if (type == TypePtr::NULL_PTR) { - // Ptr types are mixed together with T_ADDRESS but null is - // really for T_OBJECT types so correct it. - bt = T_OBJECT; - } - Node *value = fetch_interpreter_state(index, bt, locals_addr, osr_buf); + Node* value = fetch_interpreter_state(index, type, locals_addr, osr_buf); set_local(index, value); } @@ -369,7 +375,8 @@ void Parse::load_interpreter_state(Node* osr_buf) { // value and the expected type is a constant. continue; } - set_local(index, check_interpreter_type(l, type, bad_type_exit)); + bool is_early_larval = osr_block->flow()->local_type_at(index)->is_early_larval(); + set_local(index, check_interpreter_type(l, type, bad_type_exit, is_early_larval)); } for (index = 0; index < sp(); index++) { @@ -377,7 +384,8 @@ void Parse::load_interpreter_state(Node* osr_buf) { Node* l = stack(index); if (l->is_top()) continue; // nothing here const Type *type = osr_block->stack_type_at(index); - set_stack(index, check_interpreter_type(l, type, bad_type_exit)); + bool is_early_larval = osr_block->flow()->stack_type_at(index)->is_early_larval(); + set_stack(index, check_interpreter_type(l, type, bad_type_exit, is_early_larval)); } if (bad_type_exit->control()->req() > 1) { @@ -518,7 +526,9 @@ Parse::Parse(JVMState* caller, ciMethod* parse_method, float expected_uses) } if (_flow->failing()) { - assert(false, "type flow analysis failed during parsing"); + // TODO Adding a trap due to an unloaded return type in ciTypeFlow::StateVector::do_invoke + // can lead to this. Re-enable once 8284443 is fixed. + //assert(false, "type flow analysis failed during parsing"); C->record_method_not_compilable(_flow->failure_reason()); #ifndef PRODUCT if (PrintOpto && (Verbose || WizardMode)) { @@ -609,6 +619,32 @@ Parse::Parse(JVMState* caller, ciMethod* parse_method, float expected_uses) return; } + // Handle inline type arguments + int arg_size = method()->arg_size(); + for (int i = 0; i < arg_size; i++) { + Node* parm = local(i); + const Type* t = _gvn.type(parm); + if (t->is_inlinetypeptr()) { + // If the parameter is a value object, try to scalarize it if we know that it is unrestricted (not early larval) + // Parameters are non-larval except the receiver of a constructor, which must be an early larval object. + if (!(method()->is_object_constructor() && i == 0)) { + // Create InlineTypeNode from the oop and replace the parameter + Node* vt = InlineTypeNode::make_from_oop(this, parm, t->inline_klass()); + replace_in_map(parm, vt); + } + } else if (UseTypeSpeculation && (i == (arg_size - 1)) && !is_osr_parse() && method()->has_vararg() && + t->isa_aryptr() != nullptr && !t->is_aryptr()->is_null_free() && !t->is_aryptr()->is_flat() && + (!t->is_aryptr()->is_not_null_free() || !t->is_aryptr()->is_not_flat())) { + // Speculate on varargs Object array being not null-free and not flat + const TypePtr* spec_type = t->speculative(); + spec_type = (spec_type != nullptr && spec_type->isa_aryptr() != nullptr) ? spec_type : t->is_aryptr(); + spec_type = spec_type->remove_speculative()->is_aryptr()->cast_to_not_null_free()->cast_to_not_flat(); + spec_type = TypeOopPtr::make(TypePtr::BotPTR, Type::Offset::bottom, TypeOopPtr::InstanceBot, spec_type); + Node* cast = _gvn.transform(new CheckCastPPNode(control(), parm, t->join_speculative(spec_type))); + replace_in_map(parm, cast); + } + } + entry_map = map(); // capture any changes performed by method setup code assert(jvms()->endoff() == map()->req(), "map matches JVMS layout"); @@ -794,8 +830,8 @@ void Parse::build_exits() { _exits.set_all_memory(memphi); // Add a return value to the exit state. (Do not push it yet.) - if (tf()->range()->cnt() > TypeFunc::Parms) { - const Type* ret_type = tf()->range()->field_at(TypeFunc::Parms); + if (tf()->range_sig()->cnt() > TypeFunc::Parms) { + const Type* ret_type = tf()->range_sig()->field_at(TypeFunc::Parms); if (ret_type->isa_int()) { BasicType ret_bt = method()->return_type()->basic_type(); if (ret_bt == T_BOOLEAN || @@ -817,22 +853,22 @@ void Parse::build_exits() { Node* ret_phi = new PhiNode(region, ret_type); gvn().set_type_bottom(ret_phi); _exits.ensure_stack(ret_size); - assert((int)(tf()->range()->cnt() - TypeFunc::Parms) == ret_size, "good tf range"); + assert((int)(tf()->range_sig()->cnt() - TypeFunc::Parms) == ret_size, "good tf range"); assert(method()->return_type()->size() == ret_size, "tf agrees w/ method"); _exits.set_argument(0, ret_phi); // here is where the parser finds it // Note: ret_phi is not yet pushed, until do_exits. } } - //----------------------------build_start_state------------------------------- // Construct a state which contains only the incoming arguments from an // unknown caller. The method & bci will be null & InvocationEntryBci. JVMState* Compile::build_start_state(StartNode* start, const TypeFunc* tf) { - int arg_size = tf->domain()->cnt(); - int max_size = MAX2(arg_size, (int)tf->range()->cnt()); + int arg_size = tf->domain_sig()->cnt(); + int max_size = MAX2(arg_size, (int)tf->range_cc()->cnt()); JVMState* jvms = new (this) JVMState(max_size - TypeFunc::Parms); SafePointNode* map = new SafePointNode(max_size, jvms); + jvms->set_map(map); record_for_igvn(map); assert(arg_size == TypeFunc::Parms + (is_osr_compilation() ? 1 : method()->arg_size()), "correct arg_size"); Node_Notes* old_nn = default_node_notes(); @@ -844,19 +880,38 @@ JVMState* Compile::build_start_state(StartNode* start, const TypeFunc* tf) { entry_nn->set_jvms(entry_jvms); set_default_node_notes(entry_nn); } - uint i; - for (i = 0; i < (uint)arg_size; i++) { - Node* parm = initial_gvn()->transform(new ParmNode(start, i)); + PhaseGVN& gvn = *initial_gvn(); + uint i = 0; + int arg_num = 0; + for (uint j = 0; i < (uint)arg_size; i++) { + const Type* t = tf->domain_sig()->field_at(i); + Node* parm = nullptr; + if (t->is_inlinetypeptr() && method()->is_scalarized_arg(arg_num)) { + // Inline type arguments are not passed by reference: we get an argument per + // field of the inline type. Build InlineTypeNodes from the inline type arguments. + GraphKit kit(jvms, &gvn); + kit.set_control(map->control()); + Node* old_mem = map->memory(); + // Use immutable memory for inline type loads and restore it below + kit.set_all_memory(C->immutable_memory()); + parm = InlineTypeNode::make_from_multi(&kit, start, t->inline_klass(), j, /* in= */ true, /* null_free= */ !t->maybe_null()); + map->set_control(kit.control()); + map->set_memory(old_mem); + } else { + parm = gvn.transform(new ParmNode(start, j++)); + } map->init_req(i, parm); // Record all these guys for later GVN. record_for_igvn(parm); + if (i >= TypeFunc::Parms && t != Type::HALF) { + arg_num++; + } } for (; i < map->req(); i++) { map->init_req(i, top()); } assert(jvms->argoff() == TypeFunc::Parms, "parser gets arguments here"); set_default_node_notes(old_nn); - jvms->set_map(map); return jvms; } @@ -883,12 +938,34 @@ void Compile::return_values(JVMState* jvms) { kit.frameptr(), kit.returnadr()); // Add zero or 1 return values - int ret_size = tf()->range()->cnt() - TypeFunc::Parms; + int ret_size = tf()->range_sig()->cnt() - TypeFunc::Parms; if (ret_size > 0) { kit.inc_sp(-ret_size); // pop the return value(s) kit.sync_jvms(); - ret->add_req(kit.argument(0)); - // Note: The second dummy edge is not needed by a ReturnNode. + Node* res = kit.argument(0); + if (tf()->returns_inline_type_as_fields()) { + // Multiple return values (inline type fields): add as many edges + // to the Return node as returned values. + InlineTypeNode* vt = res->as_InlineType(); + ret->add_req_batch(nullptr, tf()->range_cc()->cnt() - TypeFunc::Parms); + if (vt->is_allocated(&kit.gvn()) && !StressCallingConvention) { + ret->init_req(TypeFunc::Parms, vt); + } else { + // Return the tagged klass pointer to signal scalarization to the caller + Node* tagged_klass = vt->tagged_klass(kit.gvn()); + // Return null if the inline type is null (null marker field is not set) + Node* conv = kit.gvn().transform(new ConvI2LNode(vt->get_null_marker())); + Node* shl = kit.gvn().transform(new LShiftLNode(conv, kit.intcon(63))); + Node* shr = kit.gvn().transform(new RShiftLNode(shl, kit.intcon(63))); + tagged_klass = kit.gvn().transform(new AndLNode(tagged_klass, shr)); + ret->init_req(TypeFunc::Parms, tagged_klass); + } + uint idx = TypeFunc::Parms + 1; + vt->pass_fields(&kit, ret, idx, false, false); + } else { + ret->add_req(res); + // Note: The second dummy edge is not needed by a ReturnNode. + } } // bind it to root root()->add_req(ret); @@ -1012,7 +1089,7 @@ void Parse::do_exits() { // such unusual early publications. But no barrier is needed on // exceptional returns, since they cannot publish normally. // - if (method()->is_object_initializer() && + if ((method()->is_object_constructor() || method()->is_class_initializer()) && (wrote_final() || wrote_stable() || (AlwaysSafeConstructors && wrote_fields()) || (support_IRIW_for_not_multiple_copy_atomic_cpu && wrote_volatile()))) { @@ -1040,8 +1117,8 @@ void Parse::do_exits() { // Clean up input MergeMems created by transforming the slices _gvn.transform(_exits.merged_memory()); - if (tf()->range()->cnt() > TypeFunc::Parms) { - const Type* ret_type = tf()->range()->field_at(TypeFunc::Parms); + if (tf()->range_sig()->cnt() > TypeFunc::Parms) { + const Type* ret_type = tf()->range_sig()->field_at(TypeFunc::Parms); Node* ret_phi = _gvn.transform( _exits.argument(0) ); if (!_exits.control()->is_top() && _gvn.type(ret_phi)->empty()) { // If the type we set for the ret_phi in build_exits() is too optimistic and @@ -1135,8 +1212,10 @@ SafePointNode* Parse::create_entry_map() { // If this is an inlined method, we may have to do a receiver null check. if (_caller->has_method() && is_normal_parse() && !method()->is_static()) { GraphKit kit(_caller); - kit.null_check_receiver_before_call(method()); + Node* receiver = kit.argument(0); + Node* null_free = kit.null_check_receiver_before_call(method()); _caller = kit.transfer_exceptions_into_jvms(); + if (kit.stopped()) { _exits.add_exception_states_from(_caller); _exits.set_jvms(_caller); @@ -1173,7 +1252,7 @@ SafePointNode* Parse::create_entry_map() { assert(merged_memory(), ""); // Now add the locals which are initially bound to arguments: - uint arg_size = tf()->domain()->cnt(); + uint arg_size = tf()->domain_sig()->cnt(); ensure_stack(arg_size - TypeFunc::Parms); // OSR methods have funny args for (i = TypeFunc::Parms; i < arg_size; i++) { map()->init_req(i, inmap->argument(_caller, i - TypeFunc::Parms)); @@ -1197,6 +1276,36 @@ void Parse::do_method_entry() { NOT_PRODUCT( count_compiled_calls(true/*at_method_entry*/, false/*is_inline*/); ) + // Check if we need a membar at the beginning of the java.lang.Object + // constructor to satisfy the memory model for strict fields. + if (EnableValhalla && method()->intrinsic_id() == vmIntrinsics::_Object_init) { + Node* receiver_obj = local(0); + const TypeInstPtr* receiver_type = _gvn.type(receiver_obj)->isa_instptr(); + // If there's no exact type, check if the declared type has no implementors and add a dependency + const TypeKlassPtr* klass_ptr = receiver_type->as_klass_type(/* try_for_exact= */ true); + ciType* klass = klass_ptr->klass_is_exact() ? klass_ptr->exact_klass() : nullptr; + if (klass != nullptr && klass->is_instance_klass()) { + // Exact receiver type, check if there is a strict field + ciInstanceKlass* holder = klass->as_instance_klass(); + for (int i = 0; i < holder->nof_nonstatic_fields(); i++) { + ciField* field = holder->nonstatic_field_at(i); + if (field->is_strict()) { + // Found a strict field, a membar is needed + AllocateNode* alloc = AllocateNode::Ideal_allocation(receiver_obj); + insert_mem_bar(UseStoreStoreForCtor ? Op_MemBarStoreStore : Op_MemBarRelease, receiver_obj); + if (DoEscapeAnalysis && (alloc != nullptr)) { + alloc->compute_MemBar_redundancy(method()); + } + break; + } + } + } else if (klass == nullptr) { + // We can't statically determine the type of the receiver and therefore need + // to put a membar here because it could have a strict field. + insert_mem_bar(UseStoreStoreForCtor ? Op_MemBarStoreStore : Op_MemBarRelease); + } + } + if (C->env()->dtrace_method_probes()) { make_dtrace_method_entry(method()); } @@ -1247,6 +1356,7 @@ void Parse::do_method_entry() { lock_obj = makecon(t_lock); } else { // Else pass the "this" pointer, lock_obj = local(0); // which is Parm0 from StartNode + assert(!_gvn.type(lock_obj)->make_oopptr()->can_be_inline_type(), "can't be an inline type"); } // Clear out dead values from the debug info. kill_dead_locals(); @@ -1682,6 +1792,72 @@ void Parse::merge_common(Parse::Block* target, int pnum) { assert(sp() == target->start_sp(), ""); clean_stack(sp()); + // Check for merge conflicts involving inline types + JVMState* old_jvms = map()->jvms(); + int old_bci = bci(); + JVMState* tmp_jvms = old_jvms->clone_shallow(C); + tmp_jvms->set_should_reexecute(true); + tmp_jvms->bind_map(map()); + // Execution needs to restart a the next bytecode (entry of next + // block) + if (target->is_merged() || + pnum > PhiNode::Input || + target->is_handler() || + target->is_loop_head()) { + set_parse_bci(target->start()); + for (uint j = TypeFunc::Parms; j < map()->req(); j++) { + Node* n = map()->in(j); // Incoming change to target state. + const Type* t = nullptr; + if (tmp_jvms->is_loc(j)) { + t = target->local_type_at(j - tmp_jvms->locoff()); + } else if (tmp_jvms->is_stk(j) && j < (uint)sp() + tmp_jvms->stkoff()) { + t = target->stack_type_at(j - tmp_jvms->stkoff()); + } + if (t != nullptr && t != Type::BOTTOM) { + // An object can appear in the JVMS as either an oop or an InlineTypeNode. If the merge is + // an InlineTypeNode, we need all the merge inputs to be InlineTypeNodes. Else, if the + // merge is an oop, each merge input needs to be either an oop or an buffered + // InlineTypeNode. + if (!t->is_inlinetypeptr()) { + // The merge cannot be an InlineTypeNode, ensure the input is buffered if it is an + // InlineTypeNode + if (n->is_InlineType()) { + map()->set_req(j, n->as_InlineType()->buffer(this)); + } + } else { + // Since the merge is a value object, it can either be an oop or an InlineTypeNode + if (!target->is_merged()) { + // This is the first processed input of the merge. If it is an InlineTypeNode, the + // merge will be an InlineTypeNode. Else, try to scalarize so the merge can be + // scalarized as well. However, we cannot blindly scalarize an inline type oop here + // since it may be larval + if (!n->is_InlineType() && gvn().type(n)->is_zero_type()) { + // Null constant implies that this is not a larval object + map()->set_req(j, InlineTypeNode::make_null(gvn(), t->inline_klass())); + } + } else { + Node* phi = target->start_map()->in(j); + if (phi->is_InlineType()) { + // Larval oops cannot be merged with non-larval ones, and since the merge point is + // non-larval, n must be non-larval as well. As a result, we can scalarize n to merge + // into phi + if (!n->is_InlineType()) { + map()->set_req(j, InlineTypeNode::make_from_oop(this, n, t->inline_klass())); + } + } else { + // The merge is an oop phi, ensure the input is buffered if it is an InlineTypeNode + if (n->is_InlineType()) { + map()->set_req(j, n->as_InlineType()->buffer(this)); + } + } + } + } + } + } + } + old_jvms->bind_map(map()); + set_parse_bci(old_bci); + if (!target->is_merged()) { // No prior mapping at this bci if (TraceOptoParse) { tty->print(" with empty state"); } @@ -1736,6 +1912,7 @@ void Parse::merge_common(Parse::Block* target, int pnum) { target->mark_merged_backedge(block()); } #endif + // We must not manufacture more phis if the target is already parsed. bool nophi = target->is_parsed(); @@ -1771,14 +1948,18 @@ void Parse::merge_common(Parse::Block* target, int pnum) { // Update all the non-control inputs to map: assert(TypeFunc::Parms == newin->jvms()->locoff(), "parser map should contain only youngest jvms"); bool check_elide_phi = target->is_SEL_backedge(save_block); + bool last_merge = (pnum == PhiNode::Input); for (uint j = 1; j < newin->req(); j++) { Node* m = map()->in(j); // Current state of target. Node* n = newin->in(j); // Incoming change to target state. - PhiNode* phi; - if (m->is_Phi() && m->as_Phi()->region() == r) - phi = m->as_Phi(); - else + Node* phi; + if (m->is_Phi() && m->as_Phi()->region() == r) { + phi = m; + } else if (m->is_InlineType() && m->as_InlineType()->has_phi_inputs(r)) { + phi = m; + } else { phi = nullptr; + } if (m != n) { // Different; must merge switch (j) { // Frame pointer and Return Address never changes @@ -1825,11 +2006,36 @@ void Parse::merge_common(Parse::Block* target, int pnum) { // - the corresponding control edges is top (a dead incoming path) // It is a bug if we create a phi which sees a garbage value on a live path. - if (phi != nullptr) { + // Merging two inline types? + if (phi != nullptr && phi->is_InlineType()) { + // Reload current state because it may have been updated by ensure_phi + assert(phi == map()->in(j), "unexpected value in map"); + assert(phi->as_InlineType()->has_phi_inputs(r), ""); + InlineTypeNode* vtm = phi->as_InlineType(); // Current inline type + InlineTypeNode* vtn = n->as_InlineType(); // Incoming inline type + assert(vtm == phi, "Inline type should have Phi input"); + +#ifdef ASSERT + if (TraceOptoParse) { + tty->print_cr("\nMerging inline types"); + tty->print_cr("Current:"); + vtm->dump(2); + tty->print_cr("Incoming:"); + vtn->dump(2); + tty->cr(); + } +#endif + // Do the merge + vtm->merge_with(&_gvn, vtn, pnum, last_merge); + if (last_merge) { + map()->set_req(j, _gvn.transform(vtm)); + record_for_igvn(vtm); + } + } else if (phi != nullptr) { assert(n != top() || r->in(pnum) == top(), "live value must not be garbage"); - assert(phi->region() == r, ""); + assert(phi->as_Phi()->region() == r, ""); phi->set_req(pnum, n); // Then add 'n' to the merge - if (pnum == PhiNode::Input) { + if (last_merge) { // Last merge for this Phi. // So far, Phis have had a reasonable type from ciTypeFlow. // Now _gvn will join that with the meet of current inputs. @@ -1845,8 +2051,7 @@ void Parse::merge_common(Parse::Block* target, int pnum) { } } // End of for all values to be merged - if (pnum == PhiNode::Input && - !r->in(0)) { // The occasional useless Region + if (last_merge && !r->in(0)) { // The occasional useless Region assert(control() == r, ""); set_control(r->nonnull_req()); } @@ -1998,6 +2203,8 @@ int Parse::Block::add_new_path() { if (n->is_Phi() && n->as_Phi()->region() == r) { assert(n->req() == pnum, "must be same size as region"); n->add_req(nullptr); + } else if (n->is_InlineType() && n->as_InlineType()->has_phi_inputs(r)) { + n->as_InlineType()->add_new_path(r); } } } @@ -2007,7 +2214,7 @@ int Parse::Block::add_new_path() { //------------------------------ensure_phi------------------------------------- // Turn the idx'th entry of the current map into a Phi -PhiNode *Parse::ensure_phi(int idx, bool nocreate) { +Node* Parse::ensure_phi(int idx, bool nocreate) { SafePointNode* map = this->map(); Node* region = map->control(); assert(region->is_Region(), ""); @@ -2020,6 +2227,10 @@ PhiNode *Parse::ensure_phi(int idx, bool nocreate) { if (o->is_Phi() && o->as_Phi()->region() == region) { return o->as_Phi(); } + InlineTypeNode* vt = o->isa_InlineType(); + if (vt != nullptr && vt->has_phi_inputs(region)) { + return vt; + } // Now use a Phi here for merging assert(!nocreate, "Cannot build a phi for a block already parsed."); @@ -2039,8 +2250,8 @@ PhiNode *Parse::ensure_phi(int idx, bool nocreate) { } // If the type falls to bottom, then this must be a local that - // is mixing ints and oops or some such. Forcing it to top - // makes it go dead. + // is already dead or is mixing ints and oops or some such. + // Forcing it to top makes it go dead. if (t == Type::BOTTOM) { map->set_req(idx, top()); return nullptr; @@ -2053,11 +2264,20 @@ PhiNode *Parse::ensure_phi(int idx, bool nocreate) { return nullptr; } - PhiNode* phi = PhiNode::make(region, o, t); - gvn().set_type(phi, t); - if (C->do_escape_analysis()) record_for_igvn(phi); - map->set_req(idx, phi); - return phi; + if (vt != nullptr && t->is_inlinetypeptr()) { + // Inline types are merged by merging their field values. + // Create a cloned InlineTypeNode with phi inputs that + // represents the merged inline type and update the map. + vt = vt->clone_with_phis(&_gvn, region); + map->set_req(idx, vt); + return vt; + } else { + PhiNode* phi = PhiNode::make(region, o, t); + gvn().set_type(phi, t); + if (C->do_escape_analysis()) record_for_igvn(phi); + map->set_req(idx, phi); + return phi; + } } //--------------------------ensure_memory_phi---------------------------------- @@ -2187,6 +2407,40 @@ void Parse::return_current(Node* value) { call_register_finalizer(); } + // frame pointer is always same, already captured + if (value != nullptr) { + Node* phi = _exits.argument(0); + const Type* return_type = phi->bottom_type(); + const TypeInstPtr* tr = return_type->isa_instptr(); + if ((tf()->returns_inline_type_as_fields() || (_caller->has_method() && !Compile::current()->inlining_incrementally())) && + return_type->is_inlinetypeptr()) { + // Inline type is returned as fields, make sure it is scalarized + if (!value->is_InlineType()) { + value = InlineTypeNode::make_from_oop(this, value, return_type->inline_klass()); + } + if (!_caller->has_method() || Compile::current()->inlining_incrementally()) { + // Returning from root or an incrementally inlined method. Make sure all non-flat + // fields are buffered and re-execute if allocation triggers deoptimization. + PreserveReexecuteState preexecs(this); + assert(tf()->returns_inline_type_as_fields(), "must be returned as fields"); + jvms()->set_should_reexecute(true); + inc_sp(1); + value = value->as_InlineType()->allocate_fields(this); + } + } else if (value->is_InlineType()) { + // Inline type is returned as oop, make sure it is buffered and re-execute + // if allocation triggers deoptimization. + PreserveReexecuteState preexecs(this); + jvms()->set_should_reexecute(true); + inc_sp(1); + value = value->as_InlineType()->buffer(this); + } + // ...else + // If returning oops to an interface-return, there is a silent free + // cast from oop to interface allowed by the Verifier. Make it explicit here. + phi->add_req(value); + } + // Do not set_parse_bci, so that return goo is credited to the return insn. set_bci(InvocationEntryBci); if (method()->is_synchronized()) { @@ -2195,6 +2449,7 @@ void Parse::return_current(Node* value) { if (C->env()->dtrace_method_probes()) { make_dtrace_method_exit(method()); } + SafePointNode* exit_return = _exits.map(); exit_return->in( TypeFunc::Control )->add_req( control() ); exit_return->in( TypeFunc::I_O )->add_req( i_o () ); @@ -2212,15 +2467,6 @@ void Parse::return_current(Node* value) { mms.memory()->add_req(mms.memory2()); } - // frame pointer is always same, already captured - if (value != nullptr) { - // If returning oops to an interface-return, there is a silent free - // cast from oop to interface allowed by the Verifier. Make it explicit - // here. - Node* phi = _exits.argument(0); - phi->add_req(value); - } - if (_first_return) { _exits.map()->transfer_replaced_nodes_from(map(), _new_idx); _first_return = false; diff --git a/src/hotspot/share/opto/parse2.cpp b/src/hotspot/share/opto/parse2.cpp index 04b6e49b620..b164421fb3a 100644 --- a/src/hotspot/share/opto/parse2.cpp +++ b/src/hotspot/share/opto/parse2.cpp @@ -22,7 +22,9 @@ * */ +#include "ci/ciInlineKlass.hpp" #include "ci/ciMethodData.hpp" +#include "ci/ciSymbols.hpp" #include "classfile/vmSymbols.hpp" #include "compiler/compileLog.hpp" #include "interpreter/linkResolver.hpp" @@ -35,6 +37,8 @@ #include "opto/convertnode.hpp" #include "opto/divnode.hpp" #include "opto/idealGraphPrinter.hpp" +#include "opto/idealKit.hpp" +#include "opto/inlinetypenode.hpp" #include "opto/matcher.hpp" #include "opto/memnode.hpp" #include "opto/mulnode.hpp" @@ -49,60 +53,279 @@ extern uint explicit_null_checks_inserted, explicit_null_checks_elided; #endif +Node* Parse::record_profile_for_speculation_at_array_load(Node* ld) { + // Feed unused profile data to type speculation + if (UseTypeSpeculation && UseArrayLoadStoreProfile) { + ciKlass* array_type = nullptr; + ciKlass* element_type = nullptr; + ProfilePtrKind element_ptr = ProfileMaybeNull; + bool flat_array = true; + bool null_free_array = true; + method()->array_access_profiled_type(bci(), array_type, element_type, element_ptr, flat_array, null_free_array); + if (element_type != nullptr || element_ptr != ProfileMaybeNull) { + ld = record_profile_for_speculation(ld, element_type, element_ptr); + } + } + return ld; +} + + //---------------------------------array_load---------------------------------- void Parse::array_load(BasicType bt) { const Type* elemtype = Type::TOP; - bool big_val = bt == T_DOUBLE || bt == T_LONG; Node* adr = array_addressing(bt, 0, elemtype); if (stopped()) return; // guaranteed null or range check - pop(); // index (already used) - Node* array = pop(); // the array itself + Node* array_index = pop(); + Node* array = pop(); + + // Handle inline type arrays + const TypeOopPtr* element_ptr = elemtype->make_oopptr(); + const TypeAryPtr* array_type = _gvn.type(array)->is_aryptr(); + + if (!array_type->is_not_flat()) { + // Cannot statically determine if array is a flat array, emit runtime check + assert(UseArrayFlattening && is_reference_type(bt) && element_ptr->can_be_inline_type() && + (!element_ptr->is_inlinetypeptr() || element_ptr->inline_klass()->maybe_flat_in_array()), "array can't be flat"); + IdealKit ideal(this); + IdealVariable res(ideal); + ideal.declarations_done(); + ideal.if_then(flat_array_test(array, /* flat = */ false)); { + // Non-flat array + sync_kit(ideal); + if (!array_type->is_flat()) { + assert(array_type->is_flat() || control()->in(0)->as_If()->is_flat_array_check(&_gvn), "Should be found"); + const TypeAryPtr* adr_type = TypeAryPtr::get_array_body_type(bt); + DecoratorSet decorator_set = IN_HEAP | IS_ARRAY | C2_CONTROL_DEPENDENT_LOAD; + if (needs_range_check(array_type->size(), array_index)) { + // We've emitted a RangeCheck but now insert an additional check between the range check and the actual load. + // We cannot pin the load to two separate nodes. Instead, we pin it conservatively here such that it cannot + // possibly float above the range check at any point. + decorator_set |= C2_UNKNOWN_CONTROL_LOAD; + } + Node* ld = access_load_at(array, adr, adr_type, element_ptr, bt, decorator_set); + if (element_ptr->is_inlinetypeptr()) { + ld = InlineTypeNode::make_from_oop(this, ld, element_ptr->inline_klass()); + } + ideal.set(res, ld); + } + ideal.sync_kit(this); + } ideal.else_(); { + // Flat array + sync_kit(ideal); + if (!array_type->is_not_flat()) { + if (element_ptr->is_inlinetypeptr()) { + ciInlineKlass* vk = element_ptr->inline_klass(); + Node* flat_array = cast_to_flat_array(array, vk, false, false, false); + Node* vt = InlineTypeNode::make_from_flat_array(this, vk, flat_array, array_index); + ideal.set(res, vt); + } else { + // Element type is unknown, and thus we cannot statically determine the exact flat array layout. Emit a + // runtime call to correctly load the inline type element from the flat array. + Node* inline_type = load_from_unknown_flat_array(array, array_index, element_ptr); + bool is_null_free = array_type->is_null_free() || !UseNullableValueFlattening; + if (is_null_free) { + inline_type = cast_not_null(inline_type); + } + ideal.set(res, inline_type); + } + } + ideal.sync_kit(this); + } ideal.end_if(); + sync_kit(ideal); + Node* ld = _gvn.transform(ideal.value(res)); + ld = record_profile_for_speculation_at_array_load(ld); + push_node(bt, ld); + return; + } if (elemtype == TypeInt::BOOL) { bt = T_BOOLEAN; } const TypeAryPtr* adr_type = TypeAryPtr::get_array_body_type(bt); - Node* ld = access_load_at(array, adr, adr_type, elemtype, bt, IN_HEAP | IS_ARRAY | C2_CONTROL_DEPENDENT_LOAD); - if (big_val) { - push_pair(ld); - } else { - push(ld); + ld = record_profile_for_speculation_at_array_load(ld); + // Loading an inline type from a non-flat array + if (element_ptr != nullptr && element_ptr->is_inlinetypeptr()) { + assert(!array_type->is_null_free() || !element_ptr->maybe_null(), "inline type array elements should never be null"); + ld = InlineTypeNode::make_from_oop(this, ld, element_ptr->inline_klass()); } + push_node(bt, ld); } +Node* Parse::load_from_unknown_flat_array(Node* array, Node* array_index, const TypeOopPtr* element_ptr) { + // Below membars keep this access to an unknown flat array correctly + // ordered with other unknown and known flat array accesses. + insert_mem_bar_volatile(Op_MemBarCPUOrder, C->get_alias_index(TypeAryPtr::INLINES)); + + Node* call = nullptr; + { + // Re-execute flat array load if runtime call triggers deoptimization + PreserveReexecuteState preexecs(this); + jvms()->set_bci(_bci); + jvms()->set_should_reexecute(true); + inc_sp(2); + kill_dead_locals(); + call = make_runtime_call(RC_NO_LEAF | RC_NO_IO, + OptoRuntime::load_unknown_inline_Type(), + OptoRuntime::load_unknown_inline_Java(), + nullptr, TypeRawPtr::BOTTOM, + array, array_index); + } + make_slow_call_ex(call, env()->Throwable_klass(), false); + Node* buffer = _gvn.transform(new ProjNode(call, TypeFunc::Parms)); + + insert_mem_bar_volatile(Op_MemBarCPUOrder, C->get_alias_index(TypeAryPtr::INLINES)); + + // Keep track of the information that the inline type is in flat arrays + const Type* unknown_value = element_ptr->is_instptr()->cast_to_flat_in_array(); + return _gvn.transform(new CheckCastPPNode(control(), buffer, unknown_value)); +} //--------------------------------array_store---------------------------------- void Parse::array_store(BasicType bt) { const Type* elemtype = Type::TOP; - bool big_val = bt == T_DOUBLE || bt == T_LONG; - Node* adr = array_addressing(bt, big_val ? 2 : 1, elemtype); + Node* adr = array_addressing(bt, type2size[bt], elemtype); if (stopped()) return; // guaranteed null or range check + Node* stored_value_casted = nullptr; if (bt == T_OBJECT) { - array_store_check(); + stored_value_casted = array_store_check(adr, elemtype); if (stopped()) { return; } } - Node* val; // Oop to store - if (big_val) { - val = pop_pair(); - } else { - val = pop(); - } - pop(); // index (already used) - Node* array = pop(); // the array itself + Node* const stored_value = pop_node(bt); // Value to store + Node* const array_index = pop(); // Index in the array + Node* array = pop(); // The array itself + + const TypeAryPtr* array_type = _gvn.type(array)->is_aryptr(); + const TypeAryPtr* adr_type = TypeAryPtr::get_array_body_type(bt); if (elemtype == TypeInt::BOOL) { bt = T_BOOLEAN; - } - const TypeAryPtr* adr_type = TypeAryPtr::get_array_body_type(bt); + } else if (bt == T_OBJECT) { + elemtype = elemtype->make_oopptr(); + const Type* stored_value_casted_type = _gvn.type(stored_value_casted); + // Based on the value to be stored, try to determine if the array is not null-free and/or not flat. + // This is only legal for non-null stores because the array_store_check always passes for null, even + // if the array is null-free. Null stores are handled in GraphKit::inline_array_null_guard(). + bool not_inline = !stored_value_casted_type->maybe_null() && !stored_value_casted_type->is_oopptr()->can_be_inline_type(); + bool not_null_free = not_inline; + bool not_flat = not_inline || ( stored_value_casted_type->is_inlinetypeptr() && + !stored_value_casted_type->inline_klass()->maybe_flat_in_array()); + if (!array_type->is_not_null_free() && not_null_free) { + // Storing a non-inline type, mark array as not null-free. + array_type = array_type->cast_to_not_null_free(); + Node* cast = _gvn.transform(new CheckCastPPNode(control(), array, array_type)); + replace_in_map(array, cast); + array = cast; + } + if (!array_type->is_not_flat() && not_flat) { + // Storing to a non-flat array, mark array as not flat. + array_type = array_type->cast_to_not_flat(); + Node* cast = _gvn.transform(new CheckCastPPNode(control(), array, array_type)); + replace_in_map(array, cast); + array = cast; + } + + if (!array_type->is_flat() && array_type->is_null_free()) { + // Store to non-flat null-free inline type array (elements can never be null) + assert(!stored_value_casted_type->maybe_null(), "should be guaranteed by array store check"); + if (elemtype->is_inlinetypeptr() && elemtype->inline_klass()->is_empty()) { + // Ignore empty inline stores, array is already initialized. + return; + } + } else if (!array_type->is_not_flat()) { + // Array might be a flat array, emit runtime checks (for nullptr, a simple inline_array_null_guard is sufficient). + assert(UseArrayFlattening && !not_flat && elemtype->is_oopptr()->can_be_inline_type() && + (!array_type->klass_is_exact() || array_type->is_flat()), "array can't be a flat array"); + // TODO 8350865 Depending on the available layouts, we can avoid this check in below flat/not-flat branches. Also the safe_for_replace arg is now always true. + array = inline_array_null_guard(array, stored_value_casted, 3, true); + IdealKit ideal(this); + ideal.if_then(flat_array_test(array, /* flat = */ false)); { + // Non-flat array + if (!array_type->is_flat()) { + sync_kit(ideal); + assert(array_type->is_flat() || ideal.ctrl()->in(0)->as_If()->is_flat_array_check(&_gvn), "Should be found"); + inc_sp(3); + access_store_at(array, adr, adr_type, stored_value_casted, elemtype, bt, MO_UNORDERED | IN_HEAP | IS_ARRAY, false); + dec_sp(3); + ideal.sync_kit(this); + } + } ideal.else_(); { + // Flat array + sync_kit(ideal); + if (!array_type->is_not_flat()) { + // Try to determine the inline klass type of the stored value + ciInlineKlass* vk = nullptr; + if (stored_value_casted_type->is_inlinetypeptr()) { + vk = stored_value_casted_type->inline_klass(); + } else if (elemtype->is_inlinetypeptr()) { + vk = elemtype->inline_klass(); + } + + if (vk != nullptr) { + // Element type is known, cast and store to flat array layout. + Node* flat_array = cast_to_flat_array(array, vk, false, false, false); + + // Re-execute flat array store if buffering triggers deoptimization + PreserveReexecuteState preexecs(this); + jvms()->set_should_reexecute(true); + inc_sp(3); + + if (!stored_value_casted->is_InlineType()) { + assert(_gvn.type(stored_value_casted) == TypePtr::NULL_PTR, "Unexpected value"); + stored_value_casted = InlineTypeNode::make_null(_gvn, vk); + } - access_store_at(array, adr, adr_type, val, elemtype, bt, MO_UNORDERED | IN_HEAP | IS_ARRAY); + stored_value_casted->as_InlineType()->store_flat_array(this, flat_array, array_index); + } else { + // Element type is unknown, emit a runtime call since the flat array layout is not statically known. + store_to_unknown_flat_array(array, array_index, stored_value_casted); + } + } + ideal.sync_kit(this); + } + ideal.end_if(); + sync_kit(ideal); + return; + } else if (!array_type->is_not_null_free()) { + // Array is not flat but may be null free + assert(elemtype->is_oopptr()->can_be_inline_type(), "array can't be null-free"); + array = inline_array_null_guard(array, stored_value_casted, 3, true); + } + } + inc_sp(3); + access_store_at(array, adr, adr_type, stored_value, elemtype, bt, MO_UNORDERED | IN_HEAP | IS_ARRAY); + dec_sp(3); } +// Emit a runtime call to store to a flat array whose element type is either unknown (i.e. we do not know the flat +// array layout) or not exact (could have different flat array layouts at runtime). +void Parse::store_to_unknown_flat_array(Node* array, Node* const idx, Node* non_null_stored_value) { + // Below membars keep this access to an unknown flat array correctly + // ordered with other unknown and known flat array accesses. + insert_mem_bar_volatile(Op_MemBarCPUOrder, C->get_alias_index(TypeAryPtr::INLINES)); + + Node* call = nullptr; + { + // Re-execute flat array store if runtime call triggers deoptimization + PreserveReexecuteState preexecs(this); + jvms()->set_bci(_bci); + jvms()->set_should_reexecute(true); + inc_sp(3); + kill_dead_locals(); + call = make_runtime_call(RC_NO_LEAF | RC_NO_IO, + OptoRuntime::store_unknown_inline_Type(), + OptoRuntime::store_unknown_inline_Java(), + nullptr, TypeRawPtr::BOTTOM, + non_null_stored_value, array, idx); + } + make_slow_call_ex(call, env()->Throwable_klass(), false); + + insert_mem_bar_volatile(Op_MemBarCPUOrder, C->get_alias_index(TypeAryPtr::INLINES)); +} //------------------------------array_addressing------------------------------- // Pull array and index from the stack. Compute pointer-to-element. @@ -131,17 +354,6 @@ Node* Parse::array_addressing(BasicType type, int vals, const Type*& elemtype) { } } - // Check for big class initializers with all constant offsets - // feeding into a known-size array. - const TypeInt* idxtype = _gvn.type(idx)->is_int(); - // See if the highest idx value is less than the lowest array bound, - // and if the idx value cannot be negative: - bool need_range_check = true; - if (idxtype->_hi < sizetype->_lo && idxtype->_lo >= 0) { - need_range_check = false; - if (C->log() != nullptr) C->log()->elem("observe that='!need_range_check'"); - } - if (!arytype->is_loaded()) { // Only fails for some -Xcomp runs // The class is unloaded. We have to run this bytecode in the interpreter. @@ -153,51 +365,14 @@ Node* Parse::array_addressing(BasicType type, int vals, const Type*& elemtype) { return top(); } - // Do the range check - if (need_range_check) { - Node* tst; - if (sizetype->_hi <= 0) { - // The greatest array bound is negative, so we can conclude that we're - // compiling unreachable code, but the unsigned compare trick used below - // only works with non-negative lengths. Instead, hack "tst" to be zero so - // the uncommon_trap path will always be taken. - tst = _gvn.intcon(0); - } else { - // Range is constant in array-oop, so we can use the original state of mem - Node* len = load_array_length(ary); + ary = create_speculative_inline_type_array_checks(ary, arytype, elemtype); - // Test length vs index (standard trick using unsigned compare) - Node* chk = _gvn.transform( new CmpUNode(idx, len) ); - BoolTest::mask btest = BoolTest::lt; - tst = _gvn.transform( new BoolNode(chk, btest) ); - } - RangeCheckNode* rc = new RangeCheckNode(control(), tst, PROB_MAX, COUNT_UNKNOWN); - _gvn.set_type(rc, rc->Value(&_gvn)); - if (!tst->is_Con()) { - record_for_igvn(rc); - } - set_control(_gvn.transform(new IfTrueNode(rc))); - // Branch to failure if out of bounds - { - PreserveJVMState pjvms(this); - set_control(_gvn.transform(new IfFalseNode(rc))); - if (C->allow_range_check_smearing()) { - // Do not use builtin_throw, since range checks are sometimes - // made more stringent by an optimistic transformation. - // This creates "tentative" range checks at this point, - // which are not guaranteed to throw exceptions. - // See IfNode::Ideal, is_range_check, adjust_check. - uncommon_trap(Deoptimization::Reason_range_check, - Deoptimization::Action_make_not_entrant, - nullptr, "range_check"); - } else { - // If we have already recompiled with the range-check-widening - // heroic optimization turned off, then we must really be throwing - // range check exceptions. - builtin_throw(Deoptimization::Reason_range_check); - } - } + if (needs_range_check(sizetype, idx)) { + create_range_check(idx, ary, sizetype); + } else if (C->log() != nullptr) { + C->log()->elem("observe that='!need_range_check'"); } + // Check for always knowing you are throwing a range-check exception if (stopped()) return top(); @@ -209,6 +384,205 @@ Node* Parse::array_addressing(BasicType type, int vals, const Type*& elemtype) { return ptr; } +// Check if we need a range check for an array access. This is the case if the index is either negative or if it could +// be greater or equal the smallest possible array size (i.e. out-of-bounds). +bool Parse::needs_range_check(const TypeInt* size_type, const Node* index) const { + const TypeInt* index_type = _gvn.type(index)->is_int(); + return index_type->_hi >= size_type->_lo || index_type->_lo < 0; +} + +void Parse::create_range_check(Node* idx, Node* ary, const TypeInt* sizetype) { + Node* tst; + if (sizetype->_hi <= 0) { + // The greatest array bound is negative, so we can conclude that we're + // compiling unreachable code, but the unsigned compare trick used below + // only works with non-negative lengths. Instead, hack "tst" to be zero so + // the uncommon_trap path will always be taken. + tst = _gvn.intcon(0); + } else { + // Range is constant in array-oop, so we can use the original state of mem + Node* len = load_array_length(ary); + + // Test length vs index (standard trick using unsigned compare) + Node* chk = _gvn.transform(new CmpUNode(idx, len) ); + BoolTest::mask btest = BoolTest::lt; + tst = _gvn.transform(new BoolNode(chk, btest) ); + } + RangeCheckNode* rc = new RangeCheckNode(control(), tst, PROB_MAX, COUNT_UNKNOWN); + _gvn.set_type(rc, rc->Value(&_gvn)); + if (!tst->is_Con()) { + record_for_igvn(rc); + } + set_control(_gvn.transform(new IfTrueNode(rc))); + // Branch to failure if out of bounds + { + PreserveJVMState pjvms(this); + set_control(_gvn.transform(new IfFalseNode(rc))); + if (C->allow_range_check_smearing()) { + // Do not use builtin_throw, since range checks are sometimes + // made more stringent by an optimistic transformation. + // This creates "tentative" range checks at this point, + // which are not guaranteed to throw exceptions. + // See IfNode::Ideal, is_range_check, adjust_check. + uncommon_trap(Deoptimization::Reason_range_check, + Deoptimization::Action_make_not_entrant, + nullptr, "range_check"); + } else { + // If we have already recompiled with the range-check-widening + // heroic optimization turned off, then we must really be throwing + // range check exceptions. + builtin_throw(Deoptimization::Reason_range_check); + } + } +} + +// For inline type arrays, we can use the profiling information for array accesses to speculate on the type, flatness, +// and null-freeness. We can either prepare the speculative type for later uses or emit explicit speculative checks with +// traps now. In the latter case, the speculative type guarantees can avoid additional runtime checks later (e.g. +// non-null-free implies non-flat which allows us to remove flatness checks). This makes the graph simpler. +Node* Parse::create_speculative_inline_type_array_checks(Node* array, const TypeAryPtr* array_type, + const Type*& element_type) { + if (!array_type->is_flat() && !array_type->is_not_flat()) { + // For arrays that might be flat, speculate that the array has the exact type reported in the profile data such that + // we can rely on a fixed memory layout (i.e. either a flat layout or not). + array = cast_to_speculative_array_type(array, array_type, element_type); + } else if (UseTypeSpeculation && UseArrayLoadStoreProfile) { + // Array is known to be either flat or not flat. If possible, update the speculative type by using the profile data + // at this bci. + array = cast_to_profiled_array_type(array); + } + + // Even though the type does not tell us whether we have an inline type array or not, we can still check the profile data + // whether we have a non-null-free or non-flat array. Speculating on a non-null-free array doesn't help aaload but could + // be profitable for a subsequent aastore. + if (!array_type->is_null_free() && !array_type->is_not_null_free()) { + array = speculate_non_null_free_array(array, array_type); + } + if (!array_type->is_flat() && !array_type->is_not_flat()) { + array = speculate_non_flat_array(array, array_type); + } + return array; +} + +// Speculate that the array has the exact type reported in the profile data. We emit a trap when this turns out to be +// wrong. On the fast path, we add a CheckCastPP to use the exact type. +Node* Parse::cast_to_speculative_array_type(Node* const array, const TypeAryPtr*& array_type, const Type*& element_type) { + Deoptimization::DeoptReason reason = Deoptimization::Reason_speculate_class_check; + ciKlass* speculative_array_type = array_type->speculative_type(); + if (too_many_traps_or_recompiles(reason) || speculative_array_type == nullptr) { + // No speculative type, check profile data at this bci + speculative_array_type = nullptr; + reason = Deoptimization::Reason_class_check; + if (UseArrayLoadStoreProfile && !too_many_traps_or_recompiles(reason)) { + ciKlass* profiled_element_type = nullptr; + ProfilePtrKind element_ptr = ProfileMaybeNull; + bool flat_array = true; + bool null_free_array = true; + method()->array_access_profiled_type(bci(), speculative_array_type, profiled_element_type, element_ptr, flat_array, + null_free_array); + } + } + if (speculative_array_type != nullptr) { + // Speculate that this array has the exact type reported by profile data + Node* casted_array = nullptr; + DEBUG_ONLY(Node* old_control = control();) + Node* slow_ctl = type_check_receiver(array, speculative_array_type, 1.0, &casted_array); + if (stopped()) { + // The check always fails and therefore profile information is incorrect. Don't use it. + assert(old_control == slow_ctl, "type check should have been removed"); + set_control(slow_ctl); + } else if (!slow_ctl->is_top()) { + { PreserveJVMState pjvms(this); + set_control(slow_ctl); + uncommon_trap_exact(reason, Deoptimization::Action_maybe_recompile); + } + replace_in_map(array, casted_array); + array_type = _gvn.type(casted_array)->is_aryptr(); + element_type = array_type->elem(); + return casted_array; + } + } + return array; +} + +// Create a CheckCastPP when the speculative type can improve the current type. +Node* Parse::cast_to_profiled_array_type(Node* const array) { + ciKlass* array_type = nullptr; + ciKlass* element_type = nullptr; + ProfilePtrKind element_ptr = ProfileMaybeNull; + bool flat_array = true; + bool null_free_array = true; + method()->array_access_profiled_type(bci(), array_type, element_type, element_ptr, flat_array, null_free_array); + if (array_type != nullptr) { + return record_profile_for_speculation(array, array_type, ProfileMaybeNull); + } + return array; +} + +// Speculate that the array is non-null-free. We emit a trap when this turns out to be +// wrong. On the fast path, we add a CheckCastPP to use the non-null-free type. +Node* Parse::speculate_non_null_free_array(Node* const array, const TypeAryPtr*& array_type) { + bool null_free_array = true; + Deoptimization::DeoptReason reason = Deoptimization::Reason_none; + if (array_type->speculative() != nullptr && + array_type->speculative()->is_aryptr()->is_not_null_free() && + !too_many_traps_or_recompiles(Deoptimization::Reason_speculate_class_check)) { + null_free_array = false; + reason = Deoptimization::Reason_speculate_class_check; + } else if (UseArrayLoadStoreProfile && !too_many_traps_or_recompiles(Deoptimization::Reason_class_check)) { + ciKlass* profiled_array_type = nullptr; + ciKlass* profiled_element_type = nullptr; + ProfilePtrKind element_ptr = ProfileMaybeNull; + bool flat_array = true; + method()->array_access_profiled_type(bci(), profiled_array_type, profiled_element_type, element_ptr, flat_array, + null_free_array); + reason = Deoptimization::Reason_class_check; + } + if (!null_free_array) { + { // Deoptimize if null-free array + BuildCutout unless(this, null_free_array_test(array, /* null_free = */ false), PROB_MAX); + uncommon_trap_exact(reason, Deoptimization::Action_maybe_recompile); + } + assert(!stopped(), "null-free array should have been caught earlier"); + Node* casted_array = _gvn.transform(new CheckCastPPNode(control(), array, array_type->cast_to_not_null_free())); + replace_in_map(array, casted_array); + array_type = _gvn.type(casted_array)->is_aryptr(); + return casted_array; + } + return array; +} + +// Speculate that the array is non-flat. We emit a trap when this turns out to be wrong. +// On the fast path, we add a CheckCastPP to use the non-flat type. +Node* Parse::speculate_non_flat_array(Node* const array, const TypeAryPtr* const array_type) { + bool flat_array = true; + Deoptimization::DeoptReason reason = Deoptimization::Reason_none; + if (array_type->speculative() != nullptr && + array_type->speculative()->is_aryptr()->is_not_flat() && + !too_many_traps_or_recompiles(Deoptimization::Reason_speculate_class_check)) { + flat_array = false; + reason = Deoptimization::Reason_speculate_class_check; + } else if (UseArrayLoadStoreProfile && !too_many_traps_or_recompiles(reason)) { + ciKlass* profiled_array_type = nullptr; + ciKlass* profiled_element_type = nullptr; + ProfilePtrKind element_ptr = ProfileMaybeNull; + bool null_free_array = true; + method()->array_access_profiled_type(bci(), profiled_array_type, profiled_element_type, element_ptr, flat_array, + null_free_array); + reason = Deoptimization::Reason_class_check; + } + if (!flat_array) { + { // Deoptimize if flat array + BuildCutout unless(this, flat_array_test(array, /* flat = */ false), PROB_MAX); + uncommon_trap_exact(reason, Deoptimization::Action_maybe_recompile); + } + assert(!stopped(), "flat array should have been caught earlier"); + Node* casted_array = _gvn.transform(new CheckCastPPNode(control(), array, array_type->cast_to_not_flat())); + replace_in_map(array, casted_array); + return casted_array; + } + return array; +} // returns IfNode IfNode* Parse::jump_if_fork_int(Node* a, Node* b, BoolTest::mask mask, float prob, float cnt) { @@ -1445,7 +1819,7 @@ void Parse::do_ifnull(BoolTest::mask btest, Node *c) { } //------------------------------------do_if------------------------------------ -void Parse::do_if(BoolTest::mask btest, Node* c) { +void Parse::do_if(BoolTest::mask btest, Node* c, bool can_trap, bool new_path, Node** ctrl_taken) { int target_bci = iter().get_dest(); Block* branch_block = successor_for_bci(target_bci); @@ -1536,14 +1910,22 @@ void Parse::do_if(BoolTest::mask btest, Node* c) { set_control(taken_branch); if (stopped()) { - if (C->eliminate_boxing()) { - // Mark the successor block as parsed + if (C->eliminate_boxing() && !new_path) { + // Mark the successor block as parsed (if we haven't created a new path) branch_block->next_path_num(); } } else { - adjust_map_after_if(taken_btest, c, prob, branch_block); + adjust_map_after_if(taken_btest, c, prob, branch_block, can_trap); if (!stopped()) { - merge(target_bci); + if (new_path) { + // Merge by using a new path + merge_new_path(target_bci); + } else if (ctrl_taken != nullptr) { + // Don't merge but save taken branch to be wired by caller + *ctrl_taken = control(); + } else { + merge(target_bci); + } } } } @@ -1552,13 +1934,13 @@ void Parse::do_if(BoolTest::mask btest, Node* c) { set_control(untaken_branch); // Branch not taken. - if (stopped()) { + if (stopped() && ctrl_taken == nullptr) { if (C->eliminate_boxing()) { - // Mark the successor block as parsed + // Mark the successor block as parsed (if caller does not re-wire control flow) next_block->next_path_num(); } } else { - adjust_map_after_if(untaken_btest, c, untaken_prob, next_block); + adjust_map_after_if(untaken_btest, c, untaken_prob, next_block, can_trap); } if (do_stress_trap) { @@ -1566,6 +1948,410 @@ void Parse::do_if(BoolTest::mask btest, Node* c) { } } + +static ProfilePtrKind speculative_ptr_kind(const TypeOopPtr* t) { + if (t->speculative() == nullptr) { + return ProfileUnknownNull; + } + if (t->speculative_always_null()) { + return ProfileAlwaysNull; + } + if (t->speculative_maybe_null()) { + return ProfileMaybeNull; + } + return ProfileNeverNull; +} + +void Parse::acmp_always_null_input(Node* input, const TypeOopPtr* tinput, BoolTest::mask btest, Node* eq_region) { + inc_sp(2); + Node* cast = null_check_common(input, T_OBJECT, true, nullptr, + !too_many_traps_or_recompiles(Deoptimization::Reason_speculate_null_check) && + speculative_ptr_kind(tinput) == ProfileAlwaysNull); + dec_sp(2); + if (btest == BoolTest::ne) { + { + PreserveJVMState pjvms(this); + replace_in_map(input, cast); + int target_bci = iter().get_dest(); + merge(target_bci); + } + record_for_igvn(eq_region); + set_control(_gvn.transform(eq_region)); + } else { + replace_in_map(input, cast); + } +} + +Node* Parse::acmp_null_check(Node* input, const TypeOopPtr* tinput, ProfilePtrKind input_ptr, Node*& null_ctl) { + inc_sp(2); + null_ctl = top(); + Node* cast = null_check_oop(input, &null_ctl, + input_ptr == ProfileNeverNull || (input_ptr == ProfileUnknownNull && !too_many_traps_or_recompiles(Deoptimization::Reason_null_check)), + false, + speculative_ptr_kind(tinput) == ProfileNeverNull && + !too_many_traps_or_recompiles(Deoptimization::Reason_speculate_null_check)); + dec_sp(2); + assert(!stopped(), "null input should have been caught earlier"); + return cast; +} + +void Parse::acmp_known_non_inline_type_input(Node* input, const TypeOopPtr* tinput, ProfilePtrKind input_ptr, ciKlass* input_type, BoolTest::mask btest, Node* eq_region) { + Node* ne_region = new RegionNode(1); + Node* null_ctl; + Node* cast = acmp_null_check(input, tinput, input_ptr, null_ctl); + ne_region->add_req(null_ctl); + + Node* slow_ctl = type_check_receiver(cast, input_type, 1.0, &cast); + { + PreserveJVMState pjvms(this); + inc_sp(2); + set_control(slow_ctl); + Deoptimization::DeoptReason reason; + if (tinput->speculative_type() != nullptr && !too_many_traps_or_recompiles(Deoptimization::Reason_speculate_class_check)) { + reason = Deoptimization::Reason_speculate_class_check; + } else { + reason = Deoptimization::Reason_class_check; + } + uncommon_trap_exact(reason, Deoptimization::Action_maybe_recompile); + } + ne_region->add_req(control()); + + record_for_igvn(ne_region); + set_control(_gvn.transform(ne_region)); + if (btest == BoolTest::ne) { + { + PreserveJVMState pjvms(this); + if (null_ctl == top()) { + replace_in_map(input, cast); + } + int target_bci = iter().get_dest(); + merge(target_bci); + } + record_for_igvn(eq_region); + set_control(_gvn.transform(eq_region)); + } else { + if (null_ctl == top()) { + replace_in_map(input, cast); + } + set_control(_gvn.transform(ne_region)); + } +} + +void Parse::acmp_unknown_non_inline_type_input(Node* input, const TypeOopPtr* tinput, ProfilePtrKind input_ptr, BoolTest::mask btest, Node* eq_region) { + Node* ne_region = new RegionNode(1); + Node* null_ctl; + Node* cast = acmp_null_check(input, tinput, input_ptr, null_ctl); + ne_region->add_req(null_ctl); + + { + BuildCutout unless(this, inline_type_test(cast, /* is_inline = */ false), PROB_MAX); + inc_sp(2); + uncommon_trap_exact(Deoptimization::Reason_class_check, Deoptimization::Action_maybe_recompile); + } + + ne_region->add_req(control()); + + record_for_igvn(ne_region); + set_control(_gvn.transform(ne_region)); + if (btest == BoolTest::ne) { + { + PreserveJVMState pjvms(this); + if (null_ctl == top()) { + replace_in_map(input, cast); + } + int target_bci = iter().get_dest(); + merge(target_bci); + } + record_for_igvn(eq_region); + set_control(_gvn.transform(eq_region)); + } else { + if (null_ctl == top()) { + replace_in_map(input, cast); + } + set_control(_gvn.transform(ne_region)); + } +} + +void Parse::do_acmp(BoolTest::mask btest, Node* left, Node* right) { + ciKlass* left_type = nullptr; + ciKlass* right_type = nullptr; + ProfilePtrKind left_ptr = ProfileUnknownNull; + ProfilePtrKind right_ptr = ProfileUnknownNull; + bool left_inline_type = true; + bool right_inline_type = true; + + // Leverage profiling at acmp + if (UseACmpProfile) { + method()->acmp_profiled_type(bci(), left_type, right_type, left_ptr, right_ptr, left_inline_type, right_inline_type); + if (too_many_traps_or_recompiles(Deoptimization::Reason_class_check)) { + left_type = nullptr; + right_type = nullptr; + left_inline_type = true; + right_inline_type = true; + } + if (too_many_traps_or_recompiles(Deoptimization::Reason_null_check)) { + left_ptr = ProfileUnknownNull; + right_ptr = ProfileUnknownNull; + } + } + + if (UseTypeSpeculation) { + record_profile_for_speculation(left, left_type, left_ptr); + record_profile_for_speculation(right, right_type, right_ptr); + } + + if (!EnableValhalla) { + Node* cmp = CmpP(left, right); + cmp = optimize_cmp_with_klass(cmp); + do_if(btest, cmp); + return; + } + + // Check for equality before potentially allocating + if (left == right) { + do_if(btest, makecon(TypeInt::CC_EQ)); + return; + } + + // Allocate inline type operands and re-execute on deoptimization + if (left->is_InlineType()) { + if (_gvn.type(right)->is_zero_type() || + (right->is_InlineType() && _gvn.type(right->as_InlineType()->get_null_marker())->is_zero_type())) { + // Null checking a scalarized but nullable inline type. Check the null marker + // input instead of the oop input to avoid keeping buffer allocations alive. + Node* cmp = CmpI(left->as_InlineType()->get_null_marker(), intcon(0)); + do_if(btest, cmp); + return; + } else { + PreserveReexecuteState preexecs(this); + inc_sp(2); + jvms()->set_should_reexecute(true); + left = left->as_InlineType()->buffer(this)->get_oop(); + } + } + if (right->is_InlineType()) { + PreserveReexecuteState preexecs(this); + inc_sp(2); + jvms()->set_should_reexecute(true); + right = right->as_InlineType()->buffer(this)->get_oop(); + } + + // First, do a normal pointer comparison + const TypeOopPtr* tleft = _gvn.type(left)->isa_oopptr(); + const TypeOopPtr* tright = _gvn.type(right)->isa_oopptr(); + Node* cmp = CmpP(left, right); + cmp = optimize_cmp_with_klass(cmp); + if (tleft == nullptr || !tleft->can_be_inline_type() || + tright == nullptr || !tright->can_be_inline_type()) { + // This is sufficient, if one of the operands can't be an inline type + do_if(btest, cmp); + return; + } + + // Don't add traps to unstable if branches because additional checks are required to + // decide if the operands are equal/substitutable and we therefore shouldn't prune + // branches for one if based on the profiling of the acmp branches. + // Also, OptimizeUnstableIf would set an incorrect re-rexecution state because it + // assumes that there is a 1-1 mapping between the if and the acmp branches and that + // hitting a trap means that we will take the corresponding acmp branch on re-execution. + const bool can_trap = true; + + Node* eq_region = nullptr; + if (btest == BoolTest::eq) { + do_if(btest, cmp, !can_trap, true); + if (stopped()) { + // Pointers are equal, operands must be equal + return; + } + } else { + assert(btest == BoolTest::ne, "only eq or ne"); + Node* is_not_equal = nullptr; + eq_region = new RegionNode(3); + { + PreserveJVMState pjvms(this); + // Pointers are not equal, but more checks are needed to determine if the operands are (not) substitutable + do_if(btest, cmp, !can_trap, false, &is_not_equal); + if (!stopped()) { + eq_region->init_req(1, control()); + } + } + if (is_not_equal == nullptr || is_not_equal->is_top()) { + record_for_igvn(eq_region); + set_control(_gvn.transform(eq_region)); + return; + } + set_control(is_not_equal); + } + + // Prefer speculative types if available + if (!too_many_traps_or_recompiles(Deoptimization::Reason_speculate_class_check)) { + if (tleft->speculative_type() != nullptr) { + left_type = tleft->speculative_type(); + } + if (tright->speculative_type() != nullptr) { + right_type = tright->speculative_type(); + } + } + + if (speculative_ptr_kind(tleft) != ProfileMaybeNull && speculative_ptr_kind(tleft) != ProfileUnknownNull) { + ProfilePtrKind speculative_left_ptr = speculative_ptr_kind(tleft); + if (speculative_left_ptr == ProfileAlwaysNull && !too_many_traps_or_recompiles(Deoptimization::Reason_speculate_null_assert)) { + left_ptr = speculative_left_ptr; + } else if (speculative_left_ptr == ProfileNeverNull && !too_many_traps_or_recompiles(Deoptimization::Reason_speculate_null_check)) { + left_ptr = speculative_left_ptr; + } + } + if (speculative_ptr_kind(tright) != ProfileMaybeNull && speculative_ptr_kind(tright) != ProfileUnknownNull) { + ProfilePtrKind speculative_right_ptr = speculative_ptr_kind(tright); + if (speculative_right_ptr == ProfileAlwaysNull && !too_many_traps_or_recompiles(Deoptimization::Reason_speculate_null_assert)) { + right_ptr = speculative_right_ptr; + } else if (speculative_right_ptr == ProfileNeverNull && !too_many_traps_or_recompiles(Deoptimization::Reason_speculate_null_check)) { + right_ptr = speculative_right_ptr; + } + } + + if (left_ptr == ProfileAlwaysNull) { + // Comparison with null. Assert the input is indeed null and we're done. + acmp_always_null_input(left, tleft, btest, eq_region); + return; + } + if (right_ptr == ProfileAlwaysNull) { + // Comparison with null. Assert the input is indeed null and we're done. + acmp_always_null_input(right, tright, btest, eq_region); + return; + } + if (left_type != nullptr && !left_type->is_inlinetype()) { + // Comparison with an object of known type + acmp_known_non_inline_type_input(left, tleft, left_ptr, left_type, btest, eq_region); + return; + } + if (right_type != nullptr && !right_type->is_inlinetype()) { + // Comparison with an object of known type + acmp_known_non_inline_type_input(right, tright, right_ptr, right_type, btest, eq_region); + return; + } + if (!left_inline_type) { + // Comparison with an object known not to be an inline type + acmp_unknown_non_inline_type_input(left, tleft, left_ptr, btest, eq_region); + return; + } + if (!right_inline_type) { + // Comparison with an object known not to be an inline type + acmp_unknown_non_inline_type_input(right, tright, right_ptr, btest, eq_region); + return; + } + + // Pointers are not equal, check if first operand is non-null + Node* ne_region = new RegionNode(6); + Node* null_ctl; + Node* not_null_right = acmp_null_check(right, tright, right_ptr, null_ctl); + ne_region->init_req(1, null_ctl); + + // First operand is non-null, check if it is an inline type + Node* is_value = inline_type_test(not_null_right); + IfNode* is_value_iff = create_and_map_if(control(), is_value, PROB_FAIR, COUNT_UNKNOWN); + Node* not_value = _gvn.transform(new IfFalseNode(is_value_iff)); + ne_region->init_req(2, not_value); + set_control(_gvn.transform(new IfTrueNode(is_value_iff))); + + // The first operand is an inline type, check if the second operand is non-null + Node* not_null_left = acmp_null_check(left, tleft, left_ptr, null_ctl); + ne_region->init_req(3, null_ctl); + + // Check if both operands are of the same class. + Node* kls_left = load_object_klass(not_null_left); + Node* kls_right = load_object_klass(not_null_right); + Node* kls_cmp = CmpP(kls_left, kls_right); + Node* kls_bol = _gvn.transform(new BoolNode(kls_cmp, BoolTest::ne)); + IfNode* kls_iff = create_and_map_if(control(), kls_bol, PROB_FAIR, COUNT_UNKNOWN); + Node* kls_ne = _gvn.transform(new IfTrueNode(kls_iff)); + set_control(_gvn.transform(new IfFalseNode(kls_iff))); + ne_region->init_req(4, kls_ne); + + if (stopped()) { + record_for_igvn(ne_region); + set_control(_gvn.transform(ne_region)); + if (btest == BoolTest::ne) { + { + PreserveJVMState pjvms(this); + int target_bci = iter().get_dest(); + merge(target_bci); + } + record_for_igvn(eq_region); + set_control(_gvn.transform(eq_region)); + } + return; + } + + // Both operands are values types of the same class, we need to perform a + // substitutability test. Delegate to ValueObjectMethods::isSubstitutable(). + Node* ne_io_phi = PhiNode::make(ne_region, i_o()); + Node* mem = reset_memory(); + Node* ne_mem_phi = PhiNode::make(ne_region, mem); + + Node* eq_io_phi = nullptr; + Node* eq_mem_phi = nullptr; + if (eq_region != nullptr) { + eq_io_phi = PhiNode::make(eq_region, i_o()); + eq_mem_phi = PhiNode::make(eq_region, mem); + } + + set_all_memory(mem); + + kill_dead_locals(); + ciMethod* subst_method = ciEnv::current()->ValueObjectMethods_klass()->find_method(ciSymbols::isSubstitutable_name(), ciSymbols::object_object_boolean_signature()); + CallStaticJavaNode *call = new CallStaticJavaNode(C, TypeFunc::make(subst_method), SharedRuntime::get_resolve_static_call_stub(), subst_method); + call->set_override_symbolic_info(true); + call->init_req(TypeFunc::Parms, not_null_left); + call->init_req(TypeFunc::Parms+1, not_null_right); + inc_sp(2); + set_edges_for_java_call(call, false, false); + Node* ret = set_results_for_java_call(call, false, true); + dec_sp(2); + + // Test the return value of ValueObjectMethods::isSubstitutable() + // This is the last check, do_if can emit traps now. + Node* subst_cmp = _gvn.transform(new CmpINode(ret, intcon(1))); + Node* ctl = C->top(); + if (btest == BoolTest::eq) { + PreserveJVMState pjvms(this); + do_if(btest, subst_cmp, can_trap); + if (!stopped()) { + ctl = control(); + } + } else { + assert(btest == BoolTest::ne, "only eq or ne"); + PreserveJVMState pjvms(this); + do_if(btest, subst_cmp, can_trap, false, &ctl); + if (!stopped()) { + eq_region->init_req(2, control()); + eq_io_phi->init_req(2, i_o()); + eq_mem_phi->init_req(2, reset_memory()); + } + } + ne_region->init_req(5, ctl); + ne_io_phi->init_req(5, i_o()); + ne_mem_phi->init_req(5, reset_memory()); + + record_for_igvn(ne_region); + set_control(_gvn.transform(ne_region)); + set_i_o(_gvn.transform(ne_io_phi)); + set_all_memory(_gvn.transform(ne_mem_phi)); + + if (btest == BoolTest::ne) { + { + PreserveJVMState pjvms(this); + int target_bci = iter().get_dest(); + merge(target_bci); + } + + record_for_igvn(eq_region); + set_control(_gvn.transform(eq_region)); + set_i_o(_gvn.transform(eq_io_phi)); + set_all_memory(_gvn.transform(eq_mem_phi)); + } +} + // Force unstable if traps to be taken randomly to trigger intermittent bugs such as incorrect debug information. // Add another if before the unstable if that checks a "random" condition at runtime (a simple shared counter) and // then either takes the trap or executes the original, unstable if. @@ -1638,7 +2424,7 @@ void Parse::maybe_add_predicate_after_if(Block* path) { // branch, seeing how it constrains a tested value, and then // deciding if it's worth our while to encode this constraint // as graph nodes in the current abstract interpretation map. -void Parse::adjust_map_after_if(BoolTest::mask btest, Node* c, float prob, Block* path) { +void Parse::adjust_map_after_if(BoolTest::mask btest, Node* c, float prob, Block* path, bool can_trap) { if (!c->is_Cmp()) { maybe_add_predicate_after_if(path); return; @@ -1650,7 +2436,7 @@ void Parse::adjust_map_after_if(BoolTest::mask btest, Node* c, float prob, Block bool is_fallthrough = (path == successor_for_bci(iter().next_bci())); - if (path_is_suitable_for_uncommon_trap(prob)) { + if (can_trap && path_is_suitable_for_uncommon_trap(prob)) { repush_if_args(); Node* call = uncommon_trap(Deoptimization::Reason_unstable_if, Deoptimization::Action_reinterpret, @@ -1747,6 +2533,9 @@ void Parse::sharpen_type_after_if(BoolTest::mask btest, // at the control merge. _gvn.set_type_bottom(ccast); record_for_igvn(ccast); + if (tboth->is_inlinetypeptr()) { + ccast = InlineTypeNode::make_from_oop(this, ccast, tboth->exact_klass(true)->as_inline_klass()); + } // Here's the payoff. replace_in_map(obj, ccast); } @@ -1851,6 +2640,10 @@ Node* Parse::optimize_cmp_with_klass(Node* c) { inc_sp(2); obj = maybe_cast_profiled_obj(obj, k); dec_sp(2); + if (obj->is_InlineType()) { + assert(obj->as_InlineType()->is_allocated(&_gvn), "must be allocated"); + obj = obj->as_InlineType()->get_oop(); + } // Make the CmpP use the casted obj addp = basic_plus_adr(obj, addp->in(AddPNode::Offset)); load_klass = load_klass->clone(); @@ -2590,11 +3383,9 @@ void Parse::do_one_bytecode() { case Bytecodes::_ireturn: case Bytecodes::_areturn: case Bytecodes::_freturn: - return_current(pop()); + return_current(cast_to_non_larval(pop())); break; case Bytecodes::_lreturn: - return_current(pop_pair()); - break; case Bytecodes::_dreturn: return_current(pop_pair()); break; @@ -2647,21 +3438,27 @@ void Parse::do_one_bytecode() { // If this is a backwards branch in the bytecodes, add Safepoint maybe_add_safepoint(iter().get_dest()); a = null(); - b = pop(); - if (!_gvn.type(b)->speculative_maybe_null() && - !too_many_traps(Deoptimization::Reason_speculate_null_check)) { - inc_sp(1); - Node* null_ctl = top(); - b = null_check_oop(b, &null_ctl, true, true, true); - assert(null_ctl->is_top(), "no null control here"); - dec_sp(1); - } else if (_gvn.type(b)->speculative_always_null() && - !too_many_traps(Deoptimization::Reason_speculate_null_assert)) { - inc_sp(1); - b = null_assert(b); - dec_sp(1); - } - c = _gvn.transform( new CmpPNode(b, a) ); + b = cast_to_non_larval(pop()); + if (b->is_InlineType()) { + // Null checking a scalarized but nullable inline type. Check the null marker + // input instead of the oop input to avoid keeping buffer allocations alive + c = _gvn.transform(new CmpINode(b->as_InlineType()->get_null_marker(), zerocon(T_INT))); + } else { + if (!_gvn.type(b)->speculative_maybe_null() && + !too_many_traps(Deoptimization::Reason_speculate_null_check)) { + inc_sp(1); + Node* null_ctl = top(); + b = null_check_oop(b, &null_ctl, true, true, true); + assert(null_ctl->is_top(), "no null control here"); + dec_sp(1); + } else if (_gvn.type(b)->speculative_always_null() && + !too_many_traps(Deoptimization::Reason_speculate_null_assert)) { + inc_sp(1); + b = null_assert(b); + dec_sp(1); + } + c = _gvn.transform( new CmpPNode(b, a) ); + } do_ifnull(btest, c); break; @@ -2670,11 +3467,9 @@ void Parse::do_one_bytecode() { handle_if_acmp: // If this is a backwards branch in the bytecodes, add Safepoint maybe_add_safepoint(iter().get_dest()); - a = pop(); - b = pop(); - c = _gvn.transform( new CmpPNode(b, a) ); - c = optimize_cmp_with_klass(c); - do_if(btest, c); + a = cast_to_non_larval(pop()); + b = cast_to_non_larval(pop()); + do_acmp(btest, b, a); break; case Bytecodes::_ifeq: btest = BoolTest::eq; goto handle_ifxx; @@ -2729,7 +3524,7 @@ void Parse::do_one_bytecode() { do_instanceof(); break; case Bytecodes::_anewarray: - do_anewarray(); + do_newarray(); break; case Bytecodes::_newarray: do_newarray((BasicType)iter().get_index()); @@ -2779,7 +3574,7 @@ void Parse::do_one_bytecode() { if (C->should_print_igv(perBytecode)) { IdealGraphPrinter* printer = C->igv_printer(); char buffer[256]; - jio_snprintf(buffer, sizeof(buffer), "Bytecode %d: %s", bci(), Bytecodes::name(bc())); + jio_snprintf(buffer, sizeof(buffer), "Bytecode %d: %s, map: %d", bci(), Bytecodes::name(bc()), map() == nullptr ? -1 : map()->_idx); bool old = printer->traverse_outs(); printer->set_traverse_outs(true); printer->print_graph(buffer); diff --git a/src/hotspot/share/opto/parse3.cpp b/src/hotspot/share/opto/parse3.cpp index d0e2e90ec6b..e0b1cf38ce2 100644 --- a/src/hotspot/share/opto/parse3.cpp +++ b/src/hotspot/share/opto/parse3.cpp @@ -22,12 +22,16 @@ * */ +#include "ci/ciInstanceKlass.hpp" #include "compiler/compileLog.hpp" #include "interpreter/linkResolver.hpp" #include "memory/universe.hpp" +#include "oops/accessDecorators.hpp" +#include "oops/flatArrayKlass.hpp" #include "oops/objArrayKlass.hpp" #include "opto/addnode.hpp" #include "opto/castnode.hpp" +#include "opto/inlinetypenode.hpp" #include "opto/memnode.hpp" #include "opto/parse.hpp" #include "opto/rootnode.hpp" @@ -39,13 +43,12 @@ //============================================================================= // Helper methods for _get* and _put* bytecodes //============================================================================= + void Parse::do_field_access(bool is_get, bool is_field) { bool will_link; ciField* field = iter().get_field(will_link); assert(will_link, "getfield: typeflow responsibility"); - ciInstanceKlass* field_holder = field->holder(); - if (is_field == field->is_static()) { // Interpreter will throw java_lang_IncompatibleClassChangeError // Check this before allowing methods to access static fields @@ -55,8 +58,9 @@ void Parse::do_field_access(bool is_get, bool is_field) { } // Deoptimize on putfield writes to call site target field outside of CallSite ctor. + ciInstanceKlass* field_holder = field->holder(); if (!is_get && field->is_call_site_target() && - !(method()->holder() == field_holder && method()->is_object_initializer())) { + !(method()->holder() == field_holder && method()->is_object_constructor())) { uncommon_trap(Deoptimization::Reason_unhandled, Deoptimization::Action_reinterpret, nullptr, "put to call site target field"); @@ -87,28 +91,30 @@ void Parse::do_field_access(bool is_get, bool is_field) { if (is_get) { (void) pop(); // pop receiver before getting - do_get_xxx(obj, field, is_field); + do_get_xxx(obj, field); } else { do_put_xxx(obj, field, is_field); + if (stopped()) { + return; + } (void) pop(); // pop receiver after putting } } else { const TypeInstPtr* tip = TypeInstPtr::make(field_holder->java_mirror()); obj = _gvn.makecon(tip); if (is_get) { - do_get_xxx(obj, field, is_field); + do_get_xxx(obj, field); } else { do_put_xxx(obj, field, is_field); } } } - -void Parse::do_get_xxx(Node* obj, ciField* field, bool is_field) { +void Parse::do_get_xxx(Node* obj, ciField* field) { + obj = cast_to_non_larval(obj); BasicType bt = field->layout_type(); - // Does this field have a constant value? If so, just push the value. - if (field->is_constant() && + if (field->is_constant() && !field->is_flat() && // Keep consistent with types found by ciTypeFlow: for an // unloaded field type, ciTypeFlow::StateVector::do_getstatic() // speculates the field is null. The code in the rest of this @@ -123,49 +129,69 @@ void Parse::do_get_xxx(Node* obj, ciField* field, bool is_field) { } } - ciType* field_klass = field->type(); - bool is_vol = field->is_volatile(); + if (obj->is_InlineType()) { + InlineTypeNode* vt = obj->as_InlineType(); + Node* value = vt->field_value_by_offset(field->offset_in_bytes(), false); + if (value->is_InlineType()) { + value = value->as_InlineType()->adjust_scalarization_depth(this); + } + push_node(field->layout_type(), value); + return; + } - // Compute address and memory type. + ciType* field_klass = field->type(); + field_klass = improve_abstract_inline_type_klass(field_klass); int offset = field->offset_in_bytes(); - const TypePtr* adr_type = C->alias_type(field)->adr_type(); - Node *adr = basic_plus_adr(obj, obj, offset); - assert(C->get_alias_index(adr_type) == C->get_alias_index(_gvn.type(adr)->isa_ptr()), - "slice of address and input slice don't match"); - - // Build the resultant type of the load - const Type *type; - bool must_assert_null = false; + Node* adr = basic_plus_adr(obj, obj, offset); - DecoratorSet decorators = IN_HEAP; - decorators |= is_vol ? MO_SEQ_CST : MO_UNORDERED; - - bool is_obj = is_reference_type(bt); - - if (is_obj) { - if (!field->type()->is_loaded()) { - type = TypeInstPtr::BOTTOM; - must_assert_null = true; - } else if (field->is_static_constant()) { - // This can happen if the constant oop is non-perm. - ciObject* con = field->constant_value().as_object(); - // Do not "join" in the previous type; it doesn't add value, - // and may yield a vacuous result if the field is of interface type. - if (con->is_null_object()) { - type = TypePtr::NULL_PTR; + Node* ld = nullptr; + if (field->is_null_free() && field_klass->as_inline_klass()->is_empty()) { + // Loading from a field of an empty inline type. Just return the default instance. + ld = InlineTypeNode::make_all_zero(_gvn, field_klass->as_inline_klass()); + } else if (field->is_flat()) { + // Loading from a flat inline type field. + ciInlineKlass* vk = field->type()->as_inline_klass(); + bool is_immutable = field->is_final() && field->is_strict(); + bool atomic = vk->must_be_atomic() || !field->is_null_free(); + ld = InlineTypeNode::make_from_flat(this, field_klass->as_inline_klass(), obj, adr, atomic, is_immutable, field->is_null_free(), IN_HEAP | MO_UNORDERED); + } else { + // Build the resultant type of the load + const Type* type; + if (is_reference_type(bt)) { + if (!field_klass->is_loaded()) { + type = TypeInstPtr::BOTTOM; + must_assert_null = true; + } else if (field->is_static_constant()) { + // This can happen if the constant oop is non-perm. + ciObject* con = field->constant_value().as_object(); + // Do not "join" in the previous type; it doesn't add value, + // and may yield a vacuous result if the field is of interface type. + if (con->is_null_object()) { + type = TypePtr::NULL_PTR; + } else { + type = TypeOopPtr::make_from_constant(con)->isa_oopptr(); + } + assert(type != nullptr, "field singleton type must be consistent"); } else { - type = TypeOopPtr::make_from_constant(con)->isa_oopptr(); + type = TypeOopPtr::make_from_klass(field_klass->as_klass()); + if (field->is_null_free()) { + type = type->join_speculative(TypePtr::NOTNULL); + } } - assert(type != nullptr, "field singleton type must be consistent"); } else { - type = TypeOopPtr::make_from_klass(field_klass->as_klass()); + type = Type::get_const_basic_type(bt); } - } else { - type = Type::get_const_basic_type(bt); - } - Node* ld = access_load_at(obj, adr, adr_type, type, bt, decorators); + const TypePtr* adr_type = C->alias_type(field)->adr_type(); + DecoratorSet decorators = IN_HEAP; + decorators |= field->is_volatile() ? MO_SEQ_CST : MO_UNORDERED; + ld = access_load_at(obj, adr, adr_type, type, bt, decorators); + if (field_klass->is_inlinetype()) { + // Load a non-flattened inline type from memory + ld = InlineTypeNode::make_from_oop(this, ld, field_klass->as_inline_klass()); + } + } // Adjust Java stack if (type2size[bt] == 1) @@ -189,7 +215,7 @@ void Parse::do_get_xxx(Node* obj, ciField* field, bool is_field) { } if (C->log() != nullptr) { C->log()->elem("assert_null reason='field' klass='%d'", - C->log()->identify(field->type())); + C->log()->identify(field_klass)); } // If there is going to be a trap, put it at the next bytecode: set_bci(iter().next_bci()); @@ -198,36 +224,79 @@ void Parse::do_get_xxx(Node* obj, ciField* field, bool is_field) { } } +// If the field klass is an abstract value klass (for which we do not know the layout, yet), it could have a unique +// concrete sub klass for which we have a fixed layout. This allows us to use InlineTypeNodes instead. +ciType* Parse::improve_abstract_inline_type_klass(ciType* field_klass) { + Dependencies* dependencies = C->dependencies(); + if (UseUniqueSubclasses && dependencies != nullptr && field_klass->is_instance_klass()) { + ciInstanceKlass* instance_klass = field_klass->as_instance_klass(); + if (instance_klass->is_loaded() && instance_klass->is_abstract_value_klass()) { + ciInstanceKlass* sub_klass = instance_klass->unique_concrete_subklass(); + if (sub_klass != nullptr && sub_klass != field_klass) { + field_klass = sub_klass; + dependencies->assert_abstract_with_unique_concrete_subtype(instance_klass, sub_klass); + } + } + } + return field_klass; +} + void Parse::do_put_xxx(Node* obj, ciField* field, bool is_field) { bool is_vol = field->is_volatile(); - - // Compute address and memory type. int offset = field->offset_in_bytes(); - const TypePtr* adr_type = C->alias_type(field)->adr_type(); - Node* adr = basic_plus_adr(obj, obj, offset); - assert(C->get_alias_index(adr_type) == C->get_alias_index(_gvn.type(adr)->isa_ptr()), - "slice of address and input slice don't match"); + BasicType bt = field->layout_type(); - // Value to be stored Node* val = type2size[bt] == 1 ? pop() : pop_pair(); + if (field->is_null_free()) { + PreserveReexecuteState preexecs(this); + jvms()->set_should_reexecute(true); + inc_sp(1); + val = null_check(val); + if (stopped()) { + return; + } + } - DecoratorSet decorators = IN_HEAP; - decorators |= is_vol ? MO_SEQ_CST : MO_UNORDERED; - - bool is_obj = is_reference_type(bt); + val = cast_to_non_larval(val); + Node* adr = basic_plus_adr(obj, obj, offset); - // Store the value. - const Type* field_type; - if (!field->type()->is_loaded()) { - field_type = TypeInstPtr::BOTTOM; + // We cannot store into a non-larval object, so obj must not be an InlineTypeNode + assert(!obj->is_InlineType(), "InlineTypeNodes are non-larval value objects"); + if (field->is_null_free() && field->type()->as_inline_klass()->is_empty() && (!method()->is_object_constructor() || field->is_flat())) { + // Storing to a field of an empty, null-free inline type that is already initialized. Ignore. + return; + } else if (field->is_flat()) { + // Storing to a flat inline type field. + ciInlineKlass* vk = field->type()->as_inline_klass(); + if (!val->is_InlineType()) { + assert(gvn().type(val) == TypePtr::NULL_PTR, "Unexpected value"); + val = InlineTypeNode::make_null(gvn(), vk); + } + inc_sp(1); + bool is_immutable = field->is_final() && field->is_strict(); + bool atomic = vk->must_be_atomic() || !field->is_null_free(); + val->as_InlineType()->store_flat(this, obj, adr, atomic, is_immutable, field->is_null_free(), IN_HEAP | MO_UNORDERED); + dec_sp(1); } else { - if (is_obj) { - field_type = TypeOopPtr::make_from_klass(field->type()->as_klass()); + // Store the value. + const Type* field_type; + if (!field->type()->is_loaded()) { + field_type = TypeInstPtr::BOTTOM; } else { - field_type = Type::BOTTOM; + if (is_reference_type(bt)) { + field_type = TypeOopPtr::make_from_klass(field->type()->as_klass()); + } else { + field_type = Type::BOTTOM; + } } + + const TypePtr* adr_type = C->alias_type(field)->adr_type(); + DecoratorSet decorators = IN_HEAP; + decorators |= is_vol ? MO_SEQ_CST : MO_UNORDERED; + inc_sp(1); + access_store_at(obj, adr, adr_type, val, field_type, bt, decorators); + dec_sp(1); } - access_store_at(obj, adr, adr_type, val, field_type, bt, decorators); if (is_field) { // Remember we wrote a volatile field. @@ -262,16 +331,18 @@ void Parse::do_put_xxx(Node* obj, ciField* field, bool is_field) { } //============================================================================= -void Parse::do_anewarray() { + +void Parse::do_newarray() { bool will_link; ciKlass* klass = iter().get_klass(will_link); // Uncommon Trap when class that array contains is not loaded // we need the loaded class for the rest of graph; do not // initialize the container class (see Java spec)!!! - assert(will_link, "anewarray: typeflow responsibility"); + assert(will_link, "newarray: typeflow responsibility"); + + ciArrayKlass* array_klass = ciArrayKlass::make(klass); - ciObjArrayKlass* array_klass = ciObjArrayKlass::make(klass); // Check that array_klass object is loaded if (!array_klass->is_loaded()) { // Generate uncommon_trap for unloaded array_class @@ -279,11 +350,21 @@ void Parse::do_anewarray() { Deoptimization::Action_reinterpret, array_klass); return; + } else if (array_klass->element_klass() != nullptr && + array_klass->element_klass()->is_inlinetype() && + !array_klass->element_klass()->as_inline_klass()->is_initialized()) { + uncommon_trap(Deoptimization::Reason_uninitialized, + Deoptimization::Action_reinterpret, + nullptr); + return; } kill_dead_locals(); const TypeKlassPtr* array_klass_type = TypeKlassPtr::make(array_klass, Type::trust_interfaces); + if (array_klass_type->exact_klass()->is_obj_array_klass()) { + array_klass_type = array_klass_type->isa_aryklassptr()->get_vm_type(); + } Node* count_val = pop(); Node* obj = new_array(makecon(array_klass_type), count_val, 1); push(obj); @@ -305,7 +386,11 @@ void Parse::do_newarray(BasicType elem_type) { Node* Parse::expand_multianewarray(ciArrayKlass* array_klass, Node* *lengths, int ndimensions, int nargs) { Node* length = lengths[0]; assert(length != nullptr, ""); - Node* array = new_array(makecon(TypeKlassPtr::make(array_klass, Type::trust_interfaces)), length, nargs); + const TypeKlassPtr* array_klass_ptr = TypeKlassPtr::make(array_klass, Type::trust_interfaces); + if (array_klass_ptr->exact_klass()->is_obj_array_klass()) { + array_klass_ptr = array_klass_ptr->isa_aryklassptr()->get_vm_type(); + } + Node* array = new_array(makecon(array_klass_ptr), length, nargs); if (ndimensions > 1) { jint length_con = find_int_con(length, -1); guarantee(length_con >= 0, "non-constant multianewarray"); @@ -339,7 +424,18 @@ void Parse::do_multianewarray() { Node** length = NEW_RESOURCE_ARRAY(Node*, ndimensions + 1); length[ndimensions] = nullptr; // terminating null for make_runtime_call int j; - for (j = ndimensions-1; j >= 0 ; j--) length[j] = pop(); + ciKlass* elem_klass = array_klass; + for (j = ndimensions-1; j >= 0; j--) { + length[j] = pop(); + elem_klass = elem_klass->as_array_klass()->element_klass(); + } + if (elem_klass != nullptr && elem_klass->is_inlinetype() && !elem_klass->as_inline_klass()->is_initialized()) { + inc_sp(ndimensions); + uncommon_trap(Deoptimization::Reason_uninitialized, + Deoptimization::Action_reinterpret, + nullptr); + return; + } // The original expression was of this form: new T[length0][length1]... // It is often the case that the lengths are small (except the last). diff --git a/src/hotspot/share/opto/parseHelper.cpp b/src/hotspot/share/opto/parseHelper.cpp index 1a1cea05454..48f57a71bbb 100644 --- a/src/hotspot/share/opto/parseHelper.cpp +++ b/src/hotspot/share/opto/parseHelper.cpp @@ -22,10 +22,14 @@ * */ +#include "ci/ciInlineKlass.hpp" #include "ci/ciSymbols.hpp" #include "compiler/compileLog.hpp" +#include "oops/flatArrayKlass.hpp" #include "oops/objArrayKlass.hpp" #include "opto/addnode.hpp" +#include "opto/castnode.hpp" +#include "opto/inlinetypenode.hpp" #include "opto/memnode.hpp" #include "opto/mulnode.hpp" #include "opto/parse.hpp" @@ -64,7 +68,6 @@ void GraphKit::make_dtrace_method_entry_exit(ciMethod* method, bool is_entry) { void Parse::do_checkcast() { bool will_link; ciKlass* klass = iter().get_klass(will_link); - Node *obj = peek(); // Throw uncommon trap if class is not loaded or the value we are casting @@ -137,8 +140,7 @@ void Parse::do_instanceof() { //------------------------------array_store_check------------------------------ // pull array from stack and check that the store is valid -void Parse::array_store_check() { - +Node* Parse::array_store_check(Node*& adr, const Type*& elemtype) { // Shorthand access to array store elements without popping them. Node *obj = peek(0); Node *idx = peek(1); @@ -149,27 +151,47 @@ void Parse::array_store_check() { // This cutout lets us avoid the uncommon_trap(Reason_array_check) // below, which turns into a performance liability if the // gen_checkcast folds up completely. - return; + if (_gvn.type(ary)->is_aryptr()->is_null_free()) { + null_check(obj); + } + return obj; } // Extract the array klass type - int klass_offset = oopDesc::klass_offset_in_bytes(); - Node* p = basic_plus_adr( ary, ary, klass_offset ); - // p's type is array-of-OOPS plus klass_offset - Node* array_klass = _gvn.transform(LoadKlassNode::make(_gvn, immutable_memory(), p, TypeInstPtr::KLASS)); + Node* array_klass = load_object_klass(ary); // Get the array klass - const TypeKlassPtr *tak = _gvn.type(array_klass)->is_klassptr(); + const TypeKlassPtr* tak = _gvn.type(array_klass)->is_klassptr(); // The type of array_klass is usually INexact array-of-oop. Heroically // cast array_klass to EXACT array and uncommon-trap if the cast fails. // Make constant out of the inexact array klass, but use it only if the cast // succeeds. - if (MonomorphicArrayCheck && - !too_many_traps(Deoptimization::Reason_array_check) && - !tak->klass_is_exact() && - tak->isa_aryklassptr()) { - // Regarding the fourth condition in the if-statement from above: - // + if (MonomorphicArrayCheck && !tak->klass_is_exact()) { + // Make a constant out of the inexact array klass + const TypeAryKlassPtr* extak = nullptr; + const TypeOopPtr* ary_t = _gvn.type(ary)->is_oopptr(); + ciKlass* ary_spec = ary_t->speculative_type(); + Deoptimization::DeoptReason reason = Deoptimization::Reason_none; + // Try to cast the array to an exact type from profile data. First + // check the speculative type. + if (ary_spec != nullptr && !too_many_traps(Deoptimization::Reason_speculate_class_check)) { + extak = TypeKlassPtr::make(ary_spec)->is_aryklassptr(); + reason = Deoptimization::Reason_speculate_class_check; + } else if (UseArrayLoadStoreProfile) { + // No speculative type: check profile data at this bci. + reason = Deoptimization::Reason_class_check; + if (!too_many_traps(reason)) { + ciKlass* array_type = nullptr; + ciKlass* element_type = nullptr; + ProfilePtrKind element_ptr = ProfileMaybeNull; + bool flat_array = true; + bool null_free_array = true; + method()->array_access_profiled_type(bci(), array_type, element_type, element_ptr, flat_array, null_free_array); + if (array_type != nullptr) { + extak = TypeKlassPtr::make(array_type)->is_aryklassptr(); + } + } + } else if (!too_many_traps(Deoptimization::Reason_array_check) && tak->isa_aryklassptr()) { // If the compiler has determined that the type of array 'ary' (represented // by 'array_klass') is java/lang/Object, the compiler must not assume that // the array 'ary' is monomorphic. @@ -188,30 +210,42 @@ void Parse::array_store_check() { // 'array_klass' to be ObjArrayKlass, which can result in invalid memory accesses. // // See issue JDK-8057622 for details. + extak = tak->cast_to_exactness(true)->is_aryklassptr(); + reason = Deoptimization::Reason_array_check; + } + if (extak != nullptr && extak->exact_klass(true) != nullptr) { + // TODO 8366668 TestLWorld and TestLWorldProfiling are sensitive to this. But this hack just assumes we always have the default properties ... + if (extak->exact_klass()->is_obj_array_klass()) { + extak = extak->get_vm_type(); + } - // Make a constant out of the exact array klass - const TypeAryKlassPtr* extak = tak->cast_to_exactness(true)->is_aryklassptr(); - if (extak->exact_klass(true) != nullptr) { Node* con = makecon(extak); Node* cmp = _gvn.transform(new CmpPNode(array_klass, con)); Node* bol = _gvn.transform(new BoolNode(cmp, BoolTest::eq)); - Node* ctrl= control(); - { BuildCutout unless(this, bol, PROB_MAX); - uncommon_trap(Deoptimization::Reason_array_check, - Deoptimization::Action_maybe_recompile, - extak->exact_klass()); - } - if (stopped()) { // MUST uncommon-trap? - set_control(ctrl); // Then Don't Do It, just fall into the normal checking - } else { // Cast array klass to exactness: - // Use the exact constant value we know it is. + // Only do it if the check does not always pass/fail + if (!bol->is_Con()) { + { BuildCutout unless(this, bol, PROB_MAX); + uncommon_trap(reason, + Deoptimization::Action_maybe_recompile, + extak->exact_klass()); + } + // Cast array klass to exactness replace_in_map(array_klass, con); + array_klass = con; + Node* cast = _gvn.transform(new CheckCastPPNode(control(), ary, extak->as_instance_type())); + replace_in_map(ary, cast); + ary = cast; + + // Recompute element type and address + const TypeAryPtr* arytype = _gvn.type(ary)->is_aryptr(); + elemtype = arytype->elem(); + adr = array_element_address(ary, idx, T_OBJECT, arytype->size(), control()); + CompileLog* log = C->log(); if (log != nullptr) { log->elem("cast_up reason='monomorphic_array' from='%d' to='(exact)'", log->identify(extak->exact_klass())); } - array_klass = con; // Use cast value moving forward } } } @@ -222,11 +256,44 @@ void Parse::array_store_check() { int element_klass_offset = in_bytes(ObjArrayKlass::element_klass_offset()); Node* p2 = basic_plus_adr(array_klass, array_klass, element_klass_offset); Node* a_e_klass = _gvn.transform(LoadKlassNode::make(_gvn, immutable_memory(), p2, tak)); - assert(array_klass->is_Con() == a_e_klass->is_Con() || StressReflectiveCode, "a constant array type must come with a constant element type"); + + // If we statically know that this is an inline type array, use precise element klass for checkcast + const TypeAryPtr* arytype = _gvn.type(ary)->is_aryptr(); + const TypePtr* elem_ptr = elemtype->make_ptr(); + bool null_free = arytype->is_null_free(); + if (elem_ptr->is_inlinetypeptr()) { + // We statically know that this is an inline type array, use precise klass ptr + a_e_klass = makecon(TypeKlassPtr::make(elemtype->inline_klass())); + } +#ifdef ASSERT + if (!StressReflectiveCode && array_klass->is_Con() != a_e_klass->is_Con()) { + // When the element type is exact, the array type also needs to be exact. There is one exception, though: + // Nullable arrays are not exact because the null-free array is a subtype while the element type being a + // concrete value class (i.e. final) is always exact. + assert(!array_klass->is_Con() && a_e_klass->is_Con() && elem_ptr->is_inlinetypeptr() && !null_free, + "a constant element type either matches a constant array type or a non-constant nullable value class array"); + } + + // If the element type is exact, the array can be null-free (i.e. the element type is NotNull) if: + // - The elements are inline types + // - The array is from an autobox cache. + // If the element type is inexact, it could represent multiple null-free arrays. Since autobox cache arrays + // are local to very few cache classes and are only used in the valueOf() methods, they are always exact and are not + // merged or hidden behind super types. Therefore, an inexact null-free array always represents some kind of + // inline type array - either of an abstract value class or Object. + if (null_free) { + ciKlass* klass = elem_ptr->is_instptr()->instance_klass(); + if (klass->exact_klass()) { + assert(elem_ptr->is_inlinetypeptr() || arytype->is_autobox_cache(), "elements must be inline type or autobox cache"); + } else { + assert(!arytype->is_autobox_cache() && elem_ptr->can_be_inline_type() && + (klass->is_java_lang_Object() || klass->is_abstract()), "cannot have inexact non-inline type elements"); + } + } +#endif // ASSERT // Check (the hard way) and throw if not a subklass. - // Result is ignored, we just need the CFG effects. - gen_checkcast(obj, a_e_klass); + return gen_checkcast(obj, a_e_klass, nullptr, null_free); } diff --git a/src/hotspot/share/opto/phaseX.cpp b/src/hotspot/share/opto/phaseX.cpp index 1df2cdb179e..5a07d77775b 100644 --- a/src/hotspot/share/opto/phaseX.cpp +++ b/src/hotspot/share/opto/phaseX.cpp @@ -1181,7 +1181,7 @@ bool PhaseIterGVN::verify_Value_for(Node* n) { stringStream ss; // Print as a block without tty lock. ss.cr(); ss.print_cr("Missed Value optimization:"); - n->dump_bfs(1, nullptr, "", &ss); + n->dump_bfs(3, nullptr, "", &ss); ss.print_cr("Current type:"); told->dump_on(&ss); ss.cr(); @@ -2073,18 +2073,18 @@ Node* PhaseIterGVN::register_new_node_with_optimizer(Node* n, Node* orig) { //------------------------------transform-------------------------------------- // Non-recursive: idealize Node 'n' with respect to its inputs and its value Node *PhaseIterGVN::transform( Node *n ) { - if (_delay_transform) { - // Register the node but don't optimize for now - register_new_node_with_optimizer(n); - return n; - } - // If brand new node, make space in type array, and give it a type. ensure_type_or_null(n); if (type_or_null(n) == nullptr) { set_type_bottom(n); } + if (_delay_transform) { + // Add the node to the worklist but don't optimize for now + _worklist.push(n); + return n; + } + return transform_old(n); } @@ -2357,6 +2357,19 @@ void PhaseIterGVN::subsume_node( Node *old, Node *nn ) { temp->destruct(this); // reuse the _idx of this little guy } +void PhaseIterGVN::replace_in_uses(Node* n, Node* m) { + assert(n != nullptr, "sanity"); + for (DUIterator_Fast imax, i = n->fast_outs(imax); i < imax; i++) { + Node* u = n->fast_out(i); + if (u != n) { + rehash_node_delayed(u); + int nb = u->replace_edge(n, m); + --i, imax -= nb; + } + } + assert(n->outcnt() == 0, "all uses must be deleted"); +} + //------------------------------add_users_to_worklist-------------------------- void PhaseIterGVN::add_users_to_worklist0(Node* n, Unique_Node_List& worklist) { for (DUIterator_Fast imax, i = n->fast_outs(imax); i < imax; i++) { @@ -2412,6 +2425,16 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ } } + // AndLNode::Ideal folds GraphKit::mark_word_test patterns. Give it a chance to run. + if (n->is_Load() && use->is_Phi()) { + for (DUIterator_Fast imax, i = use->fast_outs(imax); i < imax; i++) { + Node* u = use->fast_out(i); + if (u->Opcode() == Op_AndL) { + worklist.push(u); + } + } + } + uint use_op = use->Opcode(); if(use->is_Cmp()) { // Enable CMP/BOOL optimization add_users_to_worklist0(use, worklist); // Put Bool on worklist @@ -2509,6 +2532,15 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ } } + // Inline type nodes can have other inline types as users. If an input gets + // updated, make sure that inline type users get a chance for optimization. + if (use->is_InlineType()) { + for (DUIterator_Fast i2max, i2 = use->fast_outs(i2max); i2 < i2max; i2++) { + Node* u = use->fast_out(i2); + if (u->is_InlineType()) + worklist.push(u); + } + } // If changed Cast input, notify down for Phi, Sub, and Xor - all do "uncast" // Patterns: // ConstraintCast+ -> Sub @@ -2619,6 +2651,14 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); bool has_load_barrier_nodes = bs->has_load_barrier_nodes(); + if (use_op == Op_CastP2X) { + for (DUIterator_Fast i2max, i2 = use->fast_outs(i2max); i2 < i2max; i2++) { + Node* u = use->fast_out(i2); + if (u->Opcode() == Op_AndX) { + worklist.push(u); + } + } + } if (use_op == Op_LoadP && use->bottom_type()->isa_rawptr()) { for (DUIterator_Fast i2max, i2 = use->fast_outs(i2max); i2 < i2max; i2++) { Node* u = use->fast_out(i2); @@ -2637,6 +2677,16 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ } } } + // Give CallStaticJavaNode::remove_useless_allocation a chance to run + if (use->is_Region()) { + Node* c = use; + do { + c = c->unique_ctrl_out_or_null(); + } while (c != nullptr && c->is_Region()); + if (c != nullptr && c->is_CallStaticJava() && c->as_CallStaticJava()->uncommon_trap_request() != 0) { + worklist.push(c); + } + } if (use->Opcode() == Op_OpaqueZeroTripGuard) { assert(use->outcnt() <= 1, "OpaqueZeroTripGuard can't be shared"); if (use->outcnt() == 1) { @@ -2752,7 +2802,7 @@ PhaseCCP::~PhaseCCP() { #ifdef ASSERT void PhaseCCP::verify_type(Node* n, const Type* tnew, const Type* told) { if (tnew->meet(told) != tnew->remove_speculative()) { - n->dump(1); + n->dump(3); tty->print("told = "); told->dump(); tty->cr(); tty->print("tnew = "); tnew->dump(); tty->cr(); fatal("Not monotonic"); @@ -2874,6 +2924,7 @@ void PhaseCCP::push_more_uses(Unique_Node_List& worklist, Node* parent, const No push_catch(worklist, use); push_cmpu(worklist, use); push_counted_loop_phi(worklist, parent, use); + push_cast(worklist, use); push_loadp(worklist, use); push_and(worklist, parent, use); push_cast_ii(worklist, parent, use); @@ -2988,6 +3039,18 @@ void PhaseCCP::push_counted_loop_phi(Unique_Node_List& worklist, Node* parent, c } } +void PhaseCCP::push_cast(Unique_Node_List& worklist, const Node* use) { + uint use_op = use->Opcode(); + if (use_op == Op_CastP2X) { + for (DUIterator_Fast i2max, i2 = use->fast_outs(i2max); i2 < i2max; i2++) { + Node* u = use->fast_out(i2); + if (u->Opcode() == Op_AndX) { + worklist.push(u); + } + } + } +} + // Loading the java mirror from a Klass requires two loads and the type of the mirror load depends on the type of 'n'. // See LoadNode::Value(). void PhaseCCP::push_loadp(Unique_Node_List& worklist, const Node* use) const { diff --git a/src/hotspot/share/opto/phaseX.hpp b/src/hotspot/share/opto/phaseX.hpp index 300c8fc2757..05fdb664954 100644 --- a/src/hotspot/share/opto/phaseX.hpp +++ b/src/hotspot/share/opto/phaseX.hpp @@ -479,7 +479,7 @@ class PhaseIterGVN : public PhaseGVN { // Idealize new Node 'n' with respect to its inputs and its value virtual Node *transform( Node *a_node ); - virtual void record_for_igvn(Node *n) { } + virtual void record_for_igvn(Node *n) { _worklist.push(n); } // Iterative worklist. Reference to "C->igvn_worklist()". Unique_Node_List &_worklist; @@ -538,6 +538,8 @@ class PhaseIterGVN : public PhaseGVN { subsume_node(old, nn); } + void replace_in_uses(Node* n, Node* m); + // Delayed node rehash: remove a node from the hash table and rehash it during // next optimizing pass void rehash_node_delayed(Node* n) { @@ -629,6 +631,7 @@ class PhaseCCP : public PhaseIterGVN { static void push_catch(Unique_Node_List& worklist, const Node* use); void push_cmpu(Unique_Node_List& worklist, const Node* use) const; static void push_counted_loop_phi(Unique_Node_List& worklist, Node* parent, const Node* use); + static void push_cast(Unique_Node_List& worklist, const Node* use); void push_loadp(Unique_Node_List& worklist, const Node* use) const; static void push_load_barrier(Unique_Node_List& worklist, const BarrierSetC2* barrier_set, const Node* use); void push_and(Unique_Node_List& worklist, const Node* parent, const Node* use) const; diff --git a/src/hotspot/share/opto/phasetype.hpp b/src/hotspot/share/opto/phasetype.hpp index 5c733c7dc0a..3e488bf4e3c 100644 --- a/src/hotspot/share/opto/phasetype.hpp +++ b/src/hotspot/share/opto/phasetype.hpp @@ -127,6 +127,8 @@ flags(FINAL_CODE, "Final Code") \ flags(END, "End") \ flags(FAILURE, "Failure") \ + flags(SPLIT_INLINES_ARRAY, "Split inlines array") \ + flags(SPLIT_INLINES_ARRAY_IGVN, "IGVN after split inlines array") \ flags(ALL, "All") \ flags(DEBUG, "Debug") diff --git a/src/hotspot/share/opto/replacednodes.cpp b/src/hotspot/share/opto/replacednodes.cpp index 96216f9d777..30216cc8c4e 100644 --- a/src/hotspot/share/opto/replacednodes.cpp +++ b/src/hotspot/share/opto/replacednodes.cpp @@ -137,7 +137,6 @@ void ReplacedNodes::apply(Compile* C, Node* ctl) { stack.push(initial, 1); Node* use = initial->raw_out(0); stack.push(use, 0); - while (!stack.is_empty()) { assert(stack.size() > 1, "at least initial + one use"); Node* n = stack.node(); diff --git a/src/hotspot/share/opto/runtime.cpp b/src/hotspot/share/opto/runtime.cpp index 3cee7653455..bb5c4ae4c50 100644 --- a/src/hotspot/share/opto/runtime.cpp +++ b/src/hotspot/share/opto/runtime.cpp @@ -44,6 +44,8 @@ #include "logging/logStream.hpp" #include "memory/oopFactory.hpp" #include "memory/resourceArea.hpp" +#include "oops/flatArrayKlass.hpp" +#include "oops/flatArrayOop.inline.hpp" #include "oops/klass.inline.hpp" #include "oops/objArrayKlass.hpp" #include "oops/oop.inline.hpp" @@ -188,6 +190,7 @@ bool OptoRuntime::generate(ciEnv* env) { const TypeFunc* OptoRuntime::_new_instance_Type = nullptr; const TypeFunc* OptoRuntime::_new_array_Type = nullptr; +const TypeFunc* OptoRuntime::_new_array_nozero_Type = nullptr; const TypeFunc* OptoRuntime::_multianewarray2_Type = nullptr; const TypeFunc* OptoRuntime::_multianewarray3_Type = nullptr; const TypeFunc* OptoRuntime::_multianewarray4_Type = nullptr; @@ -318,7 +321,7 @@ void OptoRuntime::complete_monitor_locking_C(oopDesc* obj, BasicLock* lock, Java // and try allocation again. // object allocation -JRT_BLOCK_ENTRY(void, OptoRuntime::new_instance_C(Klass* klass, JavaThread* current)) +JRT_BLOCK_ENTRY(void, OptoRuntime::new_instance_C(Klass* klass, bool is_larval, JavaThread* current)) JRT_BLOCK; #ifndef PRODUCT SharedRuntime::_new_instance_ctr++; // new instance requires GC @@ -338,7 +341,11 @@ JRT_BLOCK_ENTRY(void, OptoRuntime::new_instance_C(Klass* klass, JavaThread* curr if (!HAS_PENDING_EXCEPTION) { // Scavenge and allocate an instance. Handle holder(current, klass->klass_holder()); // keep the klass alive - oop result = InstanceKlass::cast(klass)->allocate_instance(THREAD); + instanceOop result = InstanceKlass::cast(klass)->allocate_instance(THREAD); + if (is_larval) { + // Check if this is a larval buffer allocation + result->set_mark(result->mark().enter_larval_state()); + } current->set_vm_result_oop(result); // Pass oops back through thread local storage. Our apparent type to Java @@ -356,7 +363,7 @@ JRT_END // array allocation -JRT_BLOCK_ENTRY(void, OptoRuntime::new_array_C(Klass* array_type, int len, JavaThread* current)) +JRT_BLOCK_ENTRY(void, OptoRuntime::new_array_C(Klass* array_type, int len, oopDesc* init_val, JavaThread* current)) JRT_BLOCK; #ifndef PRODUCT SharedRuntime::_new_array_ctr++; // new array requires GC @@ -365,19 +372,47 @@ JRT_BLOCK_ENTRY(void, OptoRuntime::new_array_C(Klass* array_type, int len, JavaT // Scavenge and allocate an instance. oop result; + Handle h_init_val(current, init_val); // keep the init_val object alive - if (array_type->is_typeArray_klass()) { + if (array_type->is_flatArray_klass()) { + Handle holder(current, array_type->klass_holder()); // keep the array klass alive + FlatArrayKlass* fak = FlatArrayKlass::cast(array_type); + InlineKlass* vk = fak->element_klass(); + ArrayKlass::ArrayProperties props = ArrayKlass::ArrayProperties::DEFAULT; + switch(fak->layout_kind()) { + case LayoutKind::ATOMIC_FLAT: + props = ArrayKlass::ArrayProperties::NULL_RESTRICTED; + break; + case LayoutKind::NON_ATOMIC_FLAT: + props = (ArrayKlass::ArrayProperties)(ArrayKlass::ArrayProperties::NULL_RESTRICTED | ArrayKlass::ArrayProperties::NON_ATOMIC); + break; + case LayoutKind::NULLABLE_ATOMIC_FLAT: + props = ArrayKlass::ArrayProperties::NON_ATOMIC; + break; + default: + ShouldNotReachHere(); + } + result = oopFactory::new_flatArray(vk, len, props, fak->layout_kind(), THREAD); + if (array_type->is_null_free_array_klass() && !h_init_val.is_null()) { + // Null-free arrays need to be initialized + for (int i = 0; i < len; i++) { + vk->write_value_to_addr(h_init_val(), ((flatArrayOop)result)->value_at_addr(i, fak->layout_helper()), fak->layout_kind(), true, CHECK); + } + } + } else if (array_type->is_typeArray_klass()) { // The oopFactory likes to work with the element type. // (We could bypass the oopFactory, since it doesn't add much value.) BasicType elem_type = TypeArrayKlass::cast(array_type)->element_type(); result = oopFactory::new_typeArray(elem_type, len, THREAD); } else { - // Although the oopFactory likes to work with the elem_type, - // the compiler prefers the array_type, since it must already have - // that latter value in hand for the fast path. Handle holder(current, array_type->klass_holder()); // keep the array klass alive - Klass* elem_type = ObjArrayKlass::cast(array_type)->element_klass(); - result = oopFactory::new_objArray(elem_type, len, THREAD); + result = oopFactory::new_refArray(array_type, len, THREAD); + if (array_type->is_null_free_array_klass() && !h_init_val.is_null()) { + // Null-free arrays need to be initialized + for (int i = 0; i < len; i++) { + ((objArrayOop)result)->obj_at_put(i, h_init_val()); + } + } } // Pass oops back through thread local storage. Our apparent type to Java @@ -574,9 +609,10 @@ JRT_END static const TypeFunc* make_new_instance_Type() { // create input type (domain) - const Type **fields = TypeTuple::fields(1); + const Type **fields = TypeTuple::fields(2); fields[TypeFunc::Parms+0] = TypeInstPtr::NOTNULL; // Klass to be allocated - const TypeTuple *domain = TypeTuple::make(TypeFunc::Parms+1, fields); + fields[TypeFunc::Parms+1] = TypeInt::BOOL; // is_larval + const TypeTuple *domain = TypeTuple::make(TypeFunc::Parms+2, fields); // create result type (range) fields = TypeTuple::fields(1); @@ -619,6 +655,23 @@ static const TypeFunc* make_athrow_Type() { } static const TypeFunc* make_new_array_Type() { + // create input type (domain) + const Type **fields = TypeTuple::fields(3); + fields[TypeFunc::Parms+0] = TypeInstPtr::NOTNULL; // element klass + fields[TypeFunc::Parms+1] = TypeInt::INT; // array size + fields[TypeFunc::Parms+2] = TypeInstPtr::NOTNULL; // init value + const TypeTuple *domain = TypeTuple::make(TypeFunc::Parms+3, fields); + + // create result type (range) + fields = TypeTuple::fields(1); + fields[TypeFunc::Parms+0] = TypeRawPtr::NOTNULL; // Returned oop + + const TypeTuple *range = TypeTuple::make(TypeFunc::Parms+1, fields); + + return TypeFunc::make(domain, range); +} + +static const TypeFunc* make_new_array_nozero_Type() { // create input type (domain) const Type **fields = TypeTuple::fields(2); fields[TypeFunc::Parms+0] = TypeInstPtr::NOTNULL; // element klass @@ -694,7 +747,7 @@ static const TypeFunc* make_complete_monitor_enter_Type() { const TypeTuple *range = TypeTuple::make(TypeFunc::Parms+0,fields); - return TypeFunc::make(domain,range); + return TypeFunc::make(domain, range); } //----------------------------------------------------------------------------- @@ -2074,7 +2127,7 @@ static const TypeFunc* make_register_finalizer_Type() { const TypeTuple *range = TypeTuple::make(TypeFunc::Parms+0,fields); - return TypeFunc::make(domain,range); + return TypeFunc::make(domain, range); } #if INCLUDE_JFR @@ -2106,7 +2159,7 @@ static const TypeFunc* make_dtrace_method_entry_exit_Type() { const TypeTuple *range = TypeTuple::make(TypeFunc::Parms+0,fields); - return TypeFunc::make(domain,range); + return TypeFunc::make(domain, range); } static const TypeFunc* make_dtrace_object_alloc_Type() { @@ -2122,7 +2175,7 @@ static const TypeFunc* make_dtrace_object_alloc_Type() { const TypeTuple *range = TypeTuple::make(TypeFunc::Parms+0,fields); - return TypeFunc::make(domain,range); + return TypeFunc::make(domain, range); } JRT_ENTRY_NO_ASYNC(void, OptoRuntime::register_finalizer_C(oopDesc* obj, JavaThread* current)) @@ -2213,6 +2266,7 @@ NamedCounter* OptoRuntime::new_named_counter(JVMState* youngest_jvms, NamedCount void OptoRuntime::initialize_types() { _new_instance_Type = make_new_instance_Type(); _new_array_Type = make_new_array_Type(); + _new_array_nozero_Type = make_new_array_nozero_Type(); _multianewarray2_Type = multianewarray_Type(2); _multianewarray3_Type = multianewarray_Type(3); _multianewarray4_Type = multianewarray_Type(4); @@ -2313,3 +2367,108 @@ static void trace_exception(outputStream* st, oop exception_oop, address excepti st->print_raw_cr(tempst.freeze()); } + +const TypeFunc *OptoRuntime::store_inline_type_fields_Type() { + // create input type (domain) + uint total = SharedRuntime::java_return_convention_max_int + SharedRuntime::java_return_convention_max_float*2; + const Type **fields = TypeTuple::fields(total); + // We don't know the number of returned values and their + // types. Assume all registers available to the return convention + // are used. + fields[TypeFunc::Parms] = TypePtr::BOTTOM; + uint i = 1; + for (; i < SharedRuntime::java_return_convention_max_int; i++) { + fields[TypeFunc::Parms+i] = TypeInt::INT; + } + for (; i < total; i+=2) { + fields[TypeFunc::Parms+i] = Type::DOUBLE; + fields[TypeFunc::Parms+i+1] = Type::HALF; + } + const TypeTuple* domain = TypeTuple::make(TypeFunc::Parms + total, fields); + + // create result type (range) + fields = TypeTuple::fields(1); + fields[TypeFunc::Parms+0] = TypeInstPtr::BOTTOM; + + const TypeTuple *range = TypeTuple::make(TypeFunc::Parms+1,fields); + + return TypeFunc::make(domain, range); +} + +const TypeFunc *OptoRuntime::pack_inline_type_Type() { + // create input type (domain) + uint total = 1 + SharedRuntime::java_return_convention_max_int + SharedRuntime::java_return_convention_max_float*2; + const Type **fields = TypeTuple::fields(total); + // We don't know the number of returned values and their + // types. Assume all registers available to the return convention + // are used. + fields[TypeFunc::Parms] = TypeRawPtr::BOTTOM; + fields[TypeFunc::Parms+1] = TypeRawPtr::BOTTOM; + uint i = 2; + for (; i < SharedRuntime::java_return_convention_max_int+1; i++) { + fields[TypeFunc::Parms+i] = TypeInt::INT; + } + for (; i < total; i+=2) { + fields[TypeFunc::Parms+i] = Type::DOUBLE; + fields[TypeFunc::Parms+i+1] = Type::HALF; + } + const TypeTuple* domain = TypeTuple::make(TypeFunc::Parms + total, fields); + + // create result type (range) + fields = TypeTuple::fields(1); + fields[TypeFunc::Parms+0] = TypeInstPtr::NOTNULL; + + const TypeTuple *range = TypeTuple::make(TypeFunc::Parms+1,fields); + + return TypeFunc::make(domain, range); +} + +JRT_BLOCK_ENTRY(void, OptoRuntime::load_unknown_inline_C(flatArrayOopDesc* array, int index, JavaThread* current)) + JRT_BLOCK; + oop buffer = array->obj_at(index, THREAD); + deoptimize_caller_frame(current, HAS_PENDING_EXCEPTION); + current->set_vm_result_oop(buffer); + JRT_BLOCK_END; +JRT_END + +const TypeFunc* OptoRuntime::load_unknown_inline_Type() { + // create input type (domain) + const Type** fields = TypeTuple::fields(2); + fields[TypeFunc::Parms] = TypeOopPtr::NOTNULL; + fields[TypeFunc::Parms+1] = TypeInt::POS; + + const TypeTuple* domain = TypeTuple::make(TypeFunc::Parms+2, fields); + + // create result type (range) + fields = TypeTuple::fields(1); + fields[TypeFunc::Parms] = TypeInstPtr::BOTTOM; + + const TypeTuple* range = TypeTuple::make(TypeFunc::Parms+1, fields); + + return TypeFunc::make(domain, range); +} + +JRT_BLOCK_ENTRY(void, OptoRuntime::store_unknown_inline_C(instanceOopDesc* buffer, flatArrayOopDesc* array, int index, JavaThread* current)) + JRT_BLOCK; + array->obj_at_put(index, buffer, THREAD); + if (HAS_PENDING_EXCEPTION) { + fatal("This entry must be changed to be a non-leaf entry because writing to a flat array can now throw an exception"); + } + JRT_BLOCK_END; +JRT_END + +const TypeFunc* OptoRuntime::store_unknown_inline_Type() { + // create input type (domain) + const Type** fields = TypeTuple::fields(3); + fields[TypeFunc::Parms] = TypeInstPtr::NOTNULL; + fields[TypeFunc::Parms+1] = TypeOopPtr::NOTNULL; + fields[TypeFunc::Parms+2] = TypeInt::POS; + + const TypeTuple* domain = TypeTuple::make(TypeFunc::Parms+3, fields); + + // create result type (range) + fields = TypeTuple::fields(0); + const TypeTuple* range = TypeTuple::make(TypeFunc::Parms, fields); + + return TypeFunc::make(domain, range); +} diff --git a/src/hotspot/share/opto/runtime.hpp b/src/hotspot/share/opto/runtime.hpp index 40e436c0d5c..f4c5a6ad4a9 100644 --- a/src/hotspot/share/opto/runtime.hpp +++ b/src/hotspot/share/opto/runtime.hpp @@ -122,6 +122,7 @@ class OptoRuntime : public AllStatic { // static TypeFunc* data members static const TypeFunc* _new_instance_Type; static const TypeFunc* _new_array_Type; + static const TypeFunc* _new_array_nozero_Type; static const TypeFunc* _multianewarray2_Type; static const TypeFunc* _multianewarray3_Type; static const TypeFunc* _multianewarray4_Type; @@ -208,10 +209,10 @@ class OptoRuntime : public AllStatic { // ================================= // Allocate storage for a Java instance. - static void new_instance_C(Klass* instance_klass, JavaThread* current); + static void new_instance_C(Klass* instance_klass, bool is_larval, JavaThread* current); // Allocate storage for a objArray or typeArray - static void new_array_C(Klass* array_klass, int len, JavaThread* current); + static void new_array_C(Klass* array_klass, int len, oopDesc* init_val, JavaThread* current); static void new_array_nozero_C(Klass* array_klass, int len, JavaThread* current); // Allocate storage for a multi-dimensional arrays @@ -255,6 +256,8 @@ class OptoRuntime : public AllStatic { static void register_finalizer_C(oopDesc* obj, JavaThread* current); public: + static void load_unknown_inline_C(flatArrayOopDesc* array, int index, JavaThread* current); + static void store_unknown_inline_C(instanceOopDesc* buffer, flatArrayOopDesc* array, int index, JavaThread* current); static bool is_callee_saved_register(MachRegisterNumbers reg); @@ -287,6 +290,8 @@ class OptoRuntime : public AllStatic { static address slow_arraycopy_Java() { return _slow_arraycopy_Java; } static address register_finalizer_Java() { return _register_finalizer_Java; } + static address load_unknown_inline_Java() { return _load_unknown_inline_Java; } + static address store_unknown_inline_Java() { return _store_unknown_inline_Java; } #if INCLUDE_JVMTI static address notify_jvmti_vthread_start() { return _notify_jvmti_vthread_start; } static address notify_jvmti_vthread_end() { return _notify_jvmti_vthread_end; } @@ -319,7 +324,8 @@ class OptoRuntime : public AllStatic { } static inline const TypeFunc* new_array_nozero_Type() { - return new_array_Type(); + assert(_new_array_nozero_Type != nullptr, "should be initialized"); + return _new_array_nozero_Type; } static const TypeFunc* multianewarray_Type(int ndim); // multianewarray @@ -719,6 +725,12 @@ class OptoRuntime : public AllStatic { } #endif // INCLUDE_JFR + static const TypeFunc* load_unknown_inline_Type(); + static const TypeFunc* store_unknown_inline_Type(); + + static const TypeFunc* store_inline_type_fields_Type(); + static const TypeFunc* pack_inline_type_Type(); + #if INCLUDE_JVMTI static inline const TypeFunc* notify_jvmti_vthread_Type() { assert(_notify_jvmti_vthread_Type != nullptr, "should be initialized"); diff --git a/src/hotspot/share/opto/split_if.cpp b/src/hotspot/share/opto/split_if.cpp index bede04c6b2c..45deb93f4b8 100644 --- a/src/hotspot/share/opto/split_if.cpp +++ b/src/hotspot/share/opto/split_if.cpp @@ -25,6 +25,7 @@ #include "memory/allocation.inline.hpp" #include "opto/addnode.hpp" #include "opto/callnode.hpp" +#include "opto/inlinetypenode.hpp" #include "opto/loopnode.hpp" #include "opto/movenode.hpp" #include "opto/node.hpp" @@ -612,8 +613,7 @@ void PhaseIdealLoop::do_split_if(Node* iff, RegionNode** new_false_region, Regio if (_loop_or_ctrl[m->_idx] == nullptr) { _igvn.remove_dead_node(m); // fall through - } - else if (m != iff && split_up(m, region, iff)) { + } else if (m != iff && split_up(m, region, iff)) { // fall through } else { continue; diff --git a/src/hotspot/share/opto/stringopts.cpp b/src/hotspot/share/opto/stringopts.cpp index 28936a04219..027a6077442 100644 --- a/src/hotspot/share/opto/stringopts.cpp +++ b/src/hotspot/share/opto/stringopts.cpp @@ -321,37 +321,37 @@ StringConcat* StringConcat::merge(StringConcat* other, Node* arg) { void StringConcat::eliminate_call(CallNode* call) { Compile* C = _stringopts->C; - CallProjections projs; - call->extract_projections(&projs, false); - if (projs.fallthrough_catchproj != nullptr) { - C->gvn_replace_by(projs.fallthrough_catchproj, call->in(TypeFunc::Control)); + CallProjections* projs = call->extract_projections(false); + if (projs->fallthrough_catchproj != nullptr) { + C->gvn_replace_by(projs->fallthrough_catchproj, call->in(TypeFunc::Control)); } - if (projs.fallthrough_memproj != nullptr) { - C->gvn_replace_by(projs.fallthrough_memproj, call->in(TypeFunc::Memory)); + if (projs->fallthrough_memproj != nullptr) { + C->gvn_replace_by(projs->fallthrough_memproj, call->in(TypeFunc::Memory)); } - if (projs.catchall_memproj != nullptr) { - C->gvn_replace_by(projs.catchall_memproj, C->top()); + if (projs->catchall_memproj != nullptr) { + C->gvn_replace_by(projs->catchall_memproj, C->top()); } - if (projs.fallthrough_ioproj != nullptr) { - C->gvn_replace_by(projs.fallthrough_ioproj, call->in(TypeFunc::I_O)); + if (projs->fallthrough_ioproj != nullptr) { + C->gvn_replace_by(projs->fallthrough_ioproj, call->in(TypeFunc::I_O)); } - if (projs.catchall_ioproj != nullptr) { - C->gvn_replace_by(projs.catchall_ioproj, C->top()); + if (projs->catchall_ioproj != nullptr) { + C->gvn_replace_by(projs->catchall_ioproj, C->top()); } - if (projs.catchall_catchproj != nullptr) { + if (projs->catchall_catchproj != nullptr) { // EA can't cope with the partially collapsed graph this // creates so put it on the worklist to be collapsed later. - for (SimpleDUIterator i(projs.catchall_catchproj); i.has_next(); i.next()) { + for (SimpleDUIterator i(projs->catchall_catchproj); i.has_next(); i.next()) { Node *use = i.get(); int opc = use->Opcode(); if (opc == Op_CreateEx || opc == Op_Region) { _stringopts->record_dead_node(use); } } - C->gvn_replace_by(projs.catchall_catchproj, C->top()); + C->gvn_replace_by(projs->catchall_catchproj, C->top()); } - if (projs.resproj != nullptr) { - C->gvn_replace_by(projs.resproj, C->top()); + if (projs->resproj[0] != nullptr) { + assert(projs->nb_resproj == 1, "unexpected number of results"); + C->gvn_replace_by(projs->resproj[0], C->top()); } C->gvn_replace_by(call, C->top()); } diff --git a/src/hotspot/share/opto/subnode.cpp b/src/hotspot/share/opto/subnode.cpp index 9c6c7498dd0..a89ad7efc51 100644 --- a/src/hotspot/share/opto/subnode.cpp +++ b/src/hotspot/share/opto/subnode.cpp @@ -29,6 +29,7 @@ #include "opto/addnode.hpp" #include "opto/callnode.hpp" #include "opto/cfgnode.hpp" +#include "opto/inlinetypenode.hpp" #include "opto/loopnode.hpp" #include "opto/matcher.hpp" #include "opto/movenode.hpp" @@ -920,7 +921,14 @@ Node *CmpINode::Ideal( PhaseGVN *phase, bool can_reshape ) { return nullptr; // No change } -Node *CmpLNode::Ideal( PhaseGVN *phase, bool can_reshape ) { +//------------------------------Ideal------------------------------------------ +Node* CmpLNode::Ideal(PhaseGVN* phase, bool can_reshape) { + Node* a = nullptr; + Node* b = nullptr; + if (is_double_null_check(phase, a, b) && (phase->type(a)->is_zero_type() || phase->type(b)->is_zero_type())) { + // Degraded to a simple null check, use old acmp + return new CmpPNode(a, b); + } const TypeLong *t2 = phase->type(in(2))->isa_long(); if (Opcode() == Op_CmpL && in(1)->Opcode() == Op_ConvI2L && t2 && t2->is_con()) { const jlong con = t2->get_con(); @@ -931,6 +939,31 @@ Node *CmpLNode::Ideal( PhaseGVN *phase, bool can_reshape ) { return nullptr; } +// Match double null check emitted by Compile::optimize_acmp() +bool CmpLNode::is_double_null_check(PhaseGVN* phase, Node*& a, Node*& b) const { + if (in(1)->Opcode() == Op_OrL && + in(1)->in(1)->Opcode() == Op_CastP2X && + in(1)->in(2)->Opcode() == Op_CastP2X && + in(2)->bottom_type()->is_zero_type()) { + assert(EnableValhalla, "unexpected double null check"); + a = in(1)->in(1)->in(1); + b = in(1)->in(2)->in(1); + return true; + } + return false; +} + +//------------------------------Value------------------------------------------ +const Type* CmpLNode::Value(PhaseGVN* phase) const { + Node* a = nullptr; + Node* b = nullptr; + if (is_double_null_check(phase, a, b) && (!phase->type(a)->maybe_null() || !phase->type(b)->maybe_null())) { + // One operand is never nullptr, emit constant false + return TypeInt::CC_GT; + } + return SubNode::Value(phase); +} + //============================================================================= // Simplify a CmpL (compare 2 longs ) node, based on local information. // If both inputs are constants, compare them. @@ -1067,7 +1100,22 @@ const Type *CmpPNode::sub( const Type *t1, const Type *t2 ) const { (k0 && !k0->maybe_java_subtype_of(k1))) { unrelated_classes = xklass0; } - + if (!unrelated_classes) { + // Handle inline type arrays + if ((r0->flat_in_array() && r1->not_flat_in_array()) || + (r1->flat_in_array() && r0->not_flat_in_array())) { + // One type is in flat arrays but the other type is not. Must be unrelated. + unrelated_classes = true; + } else if ((r0->is_not_flat() && r1->is_flat()) || + (r1->is_not_flat() && r0->is_flat())) { + // One type is a non-flat array and the other type is a flat array. Must be unrelated. + unrelated_classes = true; + } else if ((r0->is_not_null_free() && r1->is_null_free()) || + (r1->is_not_null_free() && r0->is_null_free())) { + // One type is a nullable array and the other type is a null-free array. Must be unrelated. + unrelated_classes = true; + } + } if (unrelated_classes) { // The oops classes are known to be unrelated. If the joined PTRs of // two oops is not Null and not Bottom, then we are sure that one @@ -1152,7 +1200,14 @@ static inline Node* isa_const_java_mirror(PhaseGVN* phase, Node* n) { // super-type array vs a known klass with no subtypes. This amounts to // checking to see an unknown klass subtypes a known klass with no subtypes; // this only happens on an exact match. We can shorten this test by 1 load. -Node *CmpPNode::Ideal( PhaseGVN *phase, bool can_reshape ) { +Node* CmpPNode::Ideal(PhaseGVN *phase, bool can_reshape) { + // TODO 8284443 in(1) could be cast? + if (in(1)->is_InlineType() && phase->type(in(2))->is_zero_type()) { + // Null checking a scalarized but nullable inline type. Check the null marker + // input instead of the oop input to avoid keeping buffer allocations alive. + return new CmpINode(in(1)->as_InlineType()->get_null_marker(), phase->intcon(0)); + } + // Normalize comparisons between Java mirrors into comparisons of the low- // level klass, where a dependent load could be shortened. // @@ -1170,7 +1225,9 @@ Node *CmpPNode::Ideal( PhaseGVN *phase, bool can_reshape ) { Node* k2 = isa_java_mirror_load(phase, in(2)); Node* conk2 = isa_const_java_mirror(phase, in(2)); - if (k1 && (k2 || conk2)) { + // TODO 8366668 add a test for this. Improve this condition + bool doIt = (conk2 && !phase->type(conk2)->isa_aryklassptr()); + if (k1 && (k2 || conk2) && doIt) { Node* lhs = k1; Node* rhs = (k2 != nullptr) ? k2 : conk2; set_req_X(1, lhs, phase); @@ -1224,6 +1281,13 @@ Node *CmpPNode::Ideal( PhaseGVN *phase, bool can_reshape ) { if (con2 != (intptr_t) superklass->super_check_offset()) return nullptr; // Might be element-klass loading from array klass + // Do not fold the subtype check to an array klass pointer comparison for null-able inline type arrays + // because null-free [LMyValue <: null-able [LMyValue but the klasses are different. Perform a full test. + if (superklass->is_obj_array_klass() && !superklass->as_array_klass()->is_elem_null_free() && + superklass->as_array_klass()->element_klass()->is_inlinetype()) { + return nullptr; + } + // If 'superklass' has no subklasses and is not an interface, then we are // assured that the only input which will pass the type check is // 'superklass' itself. @@ -1367,6 +1431,43 @@ Node *CmpDNode::Ideal(PhaseGVN *phase, bool can_reshape){ return nullptr; // No change } +//============================================================================= +//------------------------------Value------------------------------------------ +const Type* FlatArrayCheckNode::Value(PhaseGVN* phase) const { + bool all_not_flat = true; + for (uint i = ArrayOrKlass; i < req(); ++i) { + const Type* t = phase->type(in(i)); + if (t == Type::TOP) { + return Type::TOP; + } + if (t->is_ptr()->is_flat()) { + // One of the input arrays is flat, check always passes + return TypeInt::CC_EQ; + } else if (!t->is_ptr()->is_not_flat()) { + // One of the input arrays might be flat + all_not_flat = false; + } + } + if (all_not_flat) { + // None of the input arrays can be flat, check always fails + return TypeInt::CC_GT; + } + return TypeInt::CC; +} + +//------------------------------Ideal------------------------------------------ +Node* FlatArrayCheckNode::Ideal(PhaseGVN* phase, bool can_reshape) { + bool changed = false; + // Remove inputs that are known to be non-flat + for (uint i = ArrayOrKlass; i < req(); ++i) { + const Type* t = phase->type(in(i)); + if (t->isa_ptr() && t->is_ptr()->is_not_flat()) { + del_req(i--); + changed = true; + } + } + return changed ? this : nullptr; +} //============================================================================= //------------------------------cc2logical------------------------------------- diff --git a/src/hotspot/share/opto/subnode.hpp b/src/hotspot/share/opto/subnode.hpp index 57a501ecbc3..2f39d846cf0 100644 --- a/src/hotspot/share/opto/subnode.hpp +++ b/src/hotspot/share/opto/subnode.hpp @@ -217,8 +217,10 @@ class CmpLNode : public CmpNode { public: CmpLNode( Node *in1, Node *in2 ) : CmpNode(in1,in2) {} virtual int Opcode() const; - virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); + virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); + virtual const Type* Value(PhaseGVN* phase) const; virtual const Type *sub( const Type *, const Type * ) const; + bool is_double_null_check(PhaseGVN* phase, Node*& a, Node*& b) const; }; //------------------------------CmpULNode--------------------------------------- @@ -309,6 +311,26 @@ class CmpD3Node : public CmpDNode { virtual uint ideal_reg() const { return Op_RegI; } }; +//--------------------------FlatArrayCheckNode--------------------------------- +// Returns true if one of the input array objects or array klass ptrs (there +// can be multiple) is flat. +class FlatArrayCheckNode : public CmpNode { +public: + enum { + Control, + Memory, + ArrayOrKlass + }; + FlatArrayCheckNode(Compile* C, Node* mem, Node* array_or_klass) : CmpNode(mem, array_or_klass) { + init_class_id(Class_FlatArrayCheck); + init_flags(Flag_is_macro); + C->add_macro_node(this); + } + virtual int Opcode() const; + virtual const Type* sub(const Type*, const Type*) const { ShouldNotReachHere(); return nullptr; } + const Type* Value(PhaseGVN* phase) const; + virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); +}; //------------------------------BoolTest--------------------------------------- // Convert condition codes to a boolean test value (0 or -1). diff --git a/src/hotspot/share/opto/subtypenode.cpp b/src/hotspot/share/opto/subtypenode.cpp index b58194b87d2..bd46806ef9b 100644 --- a/src/hotspot/share/opto/subtypenode.cpp +++ b/src/hotspot/share/opto/subtypenode.cpp @@ -46,20 +46,39 @@ const Type* SubTypeCheckNode::sub(const Type* sub_t, const Type* super_t) const } } - if (subk != nullptr) { - switch (Compile::current()->static_subtype_check(superk, subk, false)) { - case Compile::SSC_always_false: - return TypeInt::CC_GT; - case Compile::SSC_always_true: - return TypeInt::CC_EQ; - case Compile::SSC_easy_test: - case Compile::SSC_full_test: - break; - default: - ShouldNotReachHere(); + // FIXME: shouldn't this be encoded in helper methods of the type system (maybe_java_subtype_of() etc.?) + // Similar to logic in CmpPNode::sub() + bool unrelated_classes = false; + // Handle inline type arrays + if (subk->flat_in_array() && superk->not_flat_in_array_inexact()) { + // The subtype is in flat arrays and the supertype is not in flat arrays and no subklass can be. Must be unrelated. + unrelated_classes = true; + } else if (subk->is_not_flat() && superk->is_flat()) { + // The subtype is a non-flat array and the supertype is a flat array. Must be unrelated. + unrelated_classes = true; + } else if (subk->is_not_null_free() && superk->is_null_free()) { + // The subtype is a nullable array and the supertype is null-free array. Must be unrelated. + unrelated_classes = true; + } + if (unrelated_classes) { + TypePtr::PTR jp = sub_t->is_ptr()->join_ptr(super_t->is_ptr()->_ptr); + if (jp != TypePtr::Null && jp != TypePtr::BotPTR) { + return TypeInt::CC_GT; } } + switch (Compile::current()->static_subtype_check(superk, subk, false)) { + case Compile::SSC_always_false: + return TypeInt::CC_GT; + case Compile::SSC_always_true: + return TypeInt::CC_EQ; + case Compile::SSC_easy_test: + case Compile::SSC_full_test: + break; + default: + ShouldNotReachHere(); + } + return bottom_type(); } @@ -187,7 +206,8 @@ bool SubTypeCheckNode::verify(PhaseGVN* phase) { record_for_cleanup(chk_off, phase); int cacheoff_con = in_bytes(Klass::secondary_super_cache_offset()); - bool might_be_cache = (phase->find_int_con(chk_off, cacheoff_con) == cacheoff_con); + // TODO 8366668 Re-enable. This breaks TestArrays.java with -XX:+StressReflectiveCode + bool might_be_cache = true; // (phase->find_int_con(chk_off, cacheoff_con) == cacheoff_con); if (!might_be_cache) { Node* subklass = load_klass(phase); Node* chk_off_X = chk_off; @@ -243,4 +263,4 @@ void SubTypeCheckNode::dump_spec(outputStream* st) const { st->print(":%d", _bci); } } -#endif \ No newline at end of file +#endif diff --git a/src/hotspot/share/opto/type.cpp b/src/hotspot/share/opto/type.cpp index f62eea893cd..89c4285b4e8 100644 --- a/src/hotspot/share/opto/type.cpp +++ b/src/hotspot/share/opto/type.cpp @@ -22,6 +22,9 @@ * */ +#include "ci/ciField.hpp" +#include "ci/ciFlatArrayKlass.hpp" +#include "ci/ciInlineKlass.hpp" #include "ci/ciMethodData.hpp" #include "ci/ciTypeFlow.hpp" #include "classfile/javaClasses.hpp" @@ -45,6 +48,7 @@ #include "opto/type.hpp" #include "runtime/stubRoutines.hpp" #include "utilities/checkedCast.hpp" +#include "utilities/globalDefinitions.hpp" #include "utilities/powerOfTwo.hpp" #include "utilities/stringUtils.hpp" @@ -54,6 +58,51 @@ // Dictionary of types shared among compilations. Dict* Type::_shared_type_dict = nullptr; +const Type::Offset Type::Offset::top(Type::OffsetTop); +const Type::Offset Type::Offset::bottom(Type::OffsetBot); + +const Type::Offset Type::Offset::meet(const Type::Offset other) const { + // Either is 'TOP' offset? Return the other offset! + if (_offset == OffsetTop) return other; + if (other._offset == OffsetTop) return *this; + // If either is different, return 'BOTTOM' offset + if (_offset != other._offset) return bottom; + return Offset(_offset); +} + +const Type::Offset Type::Offset::dual() const { + if (_offset == OffsetTop) return bottom;// Map 'TOP' into 'BOTTOM' + if (_offset == OffsetBot) return top;// Map 'BOTTOM' into 'TOP' + return Offset(_offset); // Map everything else into self +} + +const Type::Offset Type::Offset::add(intptr_t offset) const { + // Adding to 'TOP' offset? Return 'TOP'! + if (_offset == OffsetTop || offset == OffsetTop) return top; + // Adding to 'BOTTOM' offset? Return 'BOTTOM'! + if (_offset == OffsetBot || offset == OffsetBot) return bottom; + // Addition overflows or "accidentally" equals to OffsetTop? Return 'BOTTOM'! + offset += (intptr_t)_offset; + if (offset != (int)offset || offset == OffsetTop) return bottom; + + // assert( _offset >= 0 && _offset+offset >= 0, "" ); + // It is possible to construct a negative offset during PhaseCCP + + return Offset((int)offset); // Sum valid offsets +} + +void Type::Offset::dump2(outputStream *st) const { + if (_offset == 0) { + return; + } else if (_offset == OffsetTop) { + st->print("+top"); + } + else if (_offset == OffsetBot) { + st->print("+bot"); + } else if (_offset) { + st->print("+%d", _offset); + } +} // Array which maps compiler types to Basic Types const Type::TypeInfo Type::_type_info[Type::lastype] = { @@ -232,6 +281,9 @@ const Type* Type::get_typeflow_type(ciType* type) { assert(type->is_return_address(), ""); return TypeRawPtr::make((address)(intptr_t)type->as_return_address()->bci()); + case T_OBJECT: + return Type::get_const_type(type->unwrap())->join_speculative(type->is_null_free() ? TypePtr::NOTNULL : TypePtr::BOTTOM); + default: // make sure we did not mix up the cases: assert(type != ciTypeFlow::StateVector::bottom_type(), ""); @@ -554,9 +606,9 @@ void Type::Initialize_shared(Compile* current) { floop[1] = TypeInt::INT; TypeTuple::LOOPBODY = TypeTuple::make( 2, floop ); - TypePtr::NULL_PTR= TypePtr::make(AnyPtr, TypePtr::Null, 0); - TypePtr::NOTNULL = TypePtr::make(AnyPtr, TypePtr::NotNull, OffsetBot); - TypePtr::BOTTOM = TypePtr::make(AnyPtr, TypePtr::BotPTR, OffsetBot); + TypePtr::NULL_PTR= TypePtr::make(AnyPtr, TypePtr::Null, Offset(0)); + TypePtr::NOTNULL = TypePtr::make(AnyPtr, TypePtr::NotNull, Offset::bottom); + TypePtr::BOTTOM = TypePtr::make(AnyPtr, TypePtr::BotPTR, Offset::bottom); TypeRawPtr::BOTTOM = TypeRawPtr::make( TypePtr::BotPTR ); TypeRawPtr::NOTNULL= TypeRawPtr::make( TypePtr::NotNull ); @@ -573,12 +625,12 @@ void Type::Initialize_shared(Compile* current) { TypeInstPtr::BOTTOM = TypeInstPtr::make(TypePtr::BotPTR, current->env()->Object_klass()); TypeInstPtr::MIRROR = TypeInstPtr::make(TypePtr::NotNull, current->env()->Class_klass()); TypeInstPtr::MARK = TypeInstPtr::make(TypePtr::BotPTR, current->env()->Object_klass(), - false, nullptr, oopDesc::mark_offset_in_bytes()); + false, nullptr, Offset(oopDesc::mark_offset_in_bytes())); TypeInstPtr::KLASS = TypeInstPtr::make(TypePtr::BotPTR, current->env()->Object_klass(), - false, nullptr, oopDesc::klass_offset_in_bytes()); - TypeOopPtr::BOTTOM = TypeOopPtr::make(TypePtr::BotPTR, OffsetBot, TypeOopPtr::InstanceBot); + false, nullptr, Offset(oopDesc::klass_offset_in_bytes())); + TypeOopPtr::BOTTOM = TypeOopPtr::make(TypePtr::BotPTR, Offset::bottom, TypeOopPtr::InstanceBot); - TypeMetadataPtr::BOTTOM = TypeMetadataPtr::make(TypePtr::BotPTR, nullptr, OffsetBot); + TypeMetadataPtr::BOTTOM = TypeMetadataPtr::make(TypePtr::BotPTR, nullptr, Offset::bottom); TypeNarrowOop::NULL_PTR = TypeNarrowOop::make( TypePtr::NULL_PTR ); TypeNarrowOop::BOTTOM = TypeNarrowOop::make( TypeInstPtr::BOTTOM ); @@ -601,10 +653,10 @@ void Type::Initialize_shared(Compile* current) { TypeAryPtr::_array_interfaces = TypeInterfaces::make(&array_interfaces); TypeAryKlassPtr::_array_interfaces = TypeAryPtr::_array_interfaces; - TypeAryPtr::BOTTOM = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(Type::BOTTOM, TypeInt::POS), nullptr, false, Type::OffsetBot); - TypeAryPtr::RANGE = TypeAryPtr::make( TypePtr::BotPTR, TypeAry::make(Type::BOTTOM,TypeInt::POS), nullptr /* current->env()->Object_klass() */, false, arrayOopDesc::length_offset_in_bytes()); + TypeAryPtr::BOTTOM = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(Type::BOTTOM, TypeInt::POS), nullptr, false, Offset::bottom); + TypeAryPtr::RANGE = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(Type::BOTTOM,TypeInt::POS), nullptr /* current->env()->Object_klass() */, false, Offset(arrayOopDesc::length_offset_in_bytes())); - TypeAryPtr::NARROWOOPS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeNarrowOop::BOTTOM, TypeInt::POS), nullptr /*ciArrayKlass::make(o)*/, false, Type::OffsetBot); + TypeAryPtr::NARROWOOPS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeNarrowOop::BOTTOM, TypeInt::POS), nullptr /*ciArrayKlass::make(o)*/, false, Offset::bottom); #ifdef _LP64 if (UseCompressedOops) { @@ -614,19 +666,21 @@ void Type::Initialize_shared(Compile* current) { #endif { // There is no shared klass for Object[]. See note in TypeAryPtr::klass(). - TypeAryPtr::OOPS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeInstPtr::BOTTOM,TypeInt::POS), nullptr /*ciArrayKlass::make(o)*/, false, Type::OffsetBot); + TypeAryPtr::OOPS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeInstPtr::BOTTOM,TypeInt::POS), nullptr /*ciArrayKlass::make(o)*/, false, Offset::bottom); } - TypeAryPtr::BYTES = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeInt::BYTE ,TypeInt::POS), ciTypeArrayKlass::make(T_BYTE), true, Type::OffsetBot); - TypeAryPtr::SHORTS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeInt::SHORT ,TypeInt::POS), ciTypeArrayKlass::make(T_SHORT), true, Type::OffsetBot); - TypeAryPtr::CHARS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeInt::CHAR ,TypeInt::POS), ciTypeArrayKlass::make(T_CHAR), true, Type::OffsetBot); - TypeAryPtr::INTS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeInt::INT ,TypeInt::POS), ciTypeArrayKlass::make(T_INT), true, Type::OffsetBot); - TypeAryPtr::LONGS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeLong::LONG ,TypeInt::POS), ciTypeArrayKlass::make(T_LONG), true, Type::OffsetBot); - TypeAryPtr::FLOATS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(Type::FLOAT ,TypeInt::POS), ciTypeArrayKlass::make(T_FLOAT), true, Type::OffsetBot); - TypeAryPtr::DOUBLES = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(Type::DOUBLE ,TypeInt::POS), ciTypeArrayKlass::make(T_DOUBLE), true, Type::OffsetBot); + TypeAryPtr::BYTES = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeInt::BYTE ,TypeInt::POS), ciTypeArrayKlass::make(T_BYTE), true, Offset::bottom); + TypeAryPtr::SHORTS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeInt::SHORT ,TypeInt::POS), ciTypeArrayKlass::make(T_SHORT), true, Offset::bottom); + TypeAryPtr::CHARS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeInt::CHAR ,TypeInt::POS), ciTypeArrayKlass::make(T_CHAR), true, Offset::bottom); + TypeAryPtr::INTS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeInt::INT ,TypeInt::POS), ciTypeArrayKlass::make(T_INT), true, Offset::bottom); + TypeAryPtr::LONGS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeLong::LONG ,TypeInt::POS), ciTypeArrayKlass::make(T_LONG), true, Offset::bottom); + TypeAryPtr::FLOATS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(Type::FLOAT ,TypeInt::POS), ciTypeArrayKlass::make(T_FLOAT), true, Offset::bottom); + TypeAryPtr::DOUBLES = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(Type::DOUBLE ,TypeInt::POS), ciTypeArrayKlass::make(T_DOUBLE), true, Offset::bottom); + TypeAryPtr::INLINES = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeInstPtr::BOTTOM,TypeInt::POS, /* stable= */ false, /* flat= */ true), nullptr, false, Offset::bottom); // Nobody should ask _array_body_type[T_NARROWOOP]. Use null as assert. TypeAryPtr::_array_body_type[T_NARROWOOP] = nullptr; TypeAryPtr::_array_body_type[T_OBJECT] = TypeAryPtr::OOPS; + TypeAryPtr::_array_body_type[T_FLAT_ELEMENT] = TypeAryPtr::OOPS; TypeAryPtr::_array_body_type[T_ARRAY] = TypeAryPtr::OOPS; // arrays are stored in oop arrays TypeAryPtr::_array_body_type[T_BYTE] = TypeAryPtr::BYTES; TypeAryPtr::_array_body_type[T_BOOLEAN] = TypeAryPtr::BYTES; // boolean[] is a byte array @@ -637,8 +691,8 @@ void Type::Initialize_shared(Compile* current) { TypeAryPtr::_array_body_type[T_FLOAT] = TypeAryPtr::FLOATS; TypeAryPtr::_array_body_type[T_DOUBLE] = TypeAryPtr::DOUBLES; - TypeInstKlassPtr::OBJECT = TypeInstKlassPtr::make(TypePtr::NotNull, current->env()->Object_klass(), 0); - TypeInstKlassPtr::OBJECT_OR_NULL = TypeInstKlassPtr::make(TypePtr::BotPTR, current->env()->Object_klass(), 0); + TypeInstKlassPtr::OBJECT = TypeInstKlassPtr::make(TypePtr::NotNull, current->env()->Object_klass(), Offset(0)); + TypeInstKlassPtr::OBJECT_OR_NULL = TypeInstKlassPtr::make(TypePtr::BotPTR, current->env()->Object_klass(), Offset(0)); const Type **fi2c = TypeTuple::fields(2); fi2c[TypeFunc::Parms+0] = TypeInstPtr::BOTTOM; // Method* @@ -677,6 +731,7 @@ void Type::Initialize_shared(Compile* current) { _const_basic_type[T_DOUBLE] = Type::DOUBLE; _const_basic_type[T_OBJECT] = TypeInstPtr::BOTTOM; _const_basic_type[T_ARRAY] = TypeInstPtr::BOTTOM; // there is no separate bottom for arrays + _const_basic_type[T_FLAT_ELEMENT] = TypeInstPtr::BOTTOM; _const_basic_type[T_VOID] = TypePtr::NULL_PTR; // reflection represents void this way _const_basic_type[T_ADDRESS] = TypeRawPtr::BOTTOM; // both interpreter return addresses & random raw ptrs _const_basic_type[T_CONFLICT] = Type::BOTTOM; // why not? @@ -693,6 +748,7 @@ void Type::Initialize_shared(Compile* current) { _zero_type[T_DOUBLE] = TypeD::ZERO; _zero_type[T_OBJECT] = TypePtr::NULL_PTR; _zero_type[T_ARRAY] = TypePtr::NULL_PTR; // null array is null oop + _zero_type[T_FLAT_ELEMENT] = TypePtr::NULL_PTR; _zero_type[T_ADDRESS] = TypePtr::NULL_PTR; // raw pointers use the same null _zero_type[T_VOID] = Type::TOP; // the only void value is no value at all @@ -967,6 +1023,9 @@ class VerifyMeet { void Type::check_symmetrical(const Type* t, const Type* mt, const VerifyMeet& verify) const { Compile* C = Compile::current(); const Type* mt2 = verify.meet(t, this); + + // Verify that: + // this meet t == t meet this if (mt != mt2) { tty->print_cr("=== Meet Not Commutative ==="); tty->print("t = "); t->dump(); tty->cr(); @@ -983,6 +1042,15 @@ void Type::check_symmetrical(const Type* t, const Type* mt, const VerifyMeet& ve // Interface:AnyNull meet Oop:AnyNull == Interface:AnyNull // Interface:NotNull meet Oop:NotNull == java/lang/Object:NotNull + // Verify that: + // 1) mt_dual meet t_dual == t_dual + // which corresponds to + // !(t meet this) meet !t == + // (!t join !this) meet !t == !t + // 2) mt_dual meet this_dual == this_dual + // which corresponds to + // !(t meet this) meet !this == + // (!t join !this) meet !this == !this if (t2t != t->_dual || t2this != this->_dual) { tty->print_cr("=== Meet Not Symmetric ==="); tty->print("t = "); t->dump(); tty->cr(); @@ -993,7 +1061,9 @@ void Type::check_symmetrical(const Type* t, const Type* mt, const VerifyMeet& ve tty->print("this_dual= "); _dual->dump(); tty->cr(); tty->print("mt_dual= "); mt->_dual->dump(); tty->cr(); + // 1) tty->print("mt_dual meet t_dual= "); t2t ->dump(); tty->cr(); + // 2) tty->print("mt_dual meet this_dual= "); t2this ->dump(); tty->cr(); fatal("meet not symmetric"); @@ -1031,6 +1101,9 @@ const Type *Type::meet_helper(const Type *t, bool include_speculative) const { if (isa_narrowklass() || t->isa_narrowklass()) { return mt; } + // TODO 8350865 This currently triggers a verification failure, the code around "// Even though MyValue is final" needs adjustments + if ((this_t->isa_ptr() && this_t->is_ptr()->is_not_flat()) || + (this_t->_dual->isa_ptr() && this_t->_dual->is_ptr()->is_not_flat())) return mt; this_t->check_symmetrical(t, mt, verify); const Type *mt_dual = verify.meet(this_t->_dual, t->_dual); this_t->_dual->check_symmetrical(t->_dual, mt_dual, verify); @@ -2047,11 +2120,37 @@ const TypeTuple *TypeTuple::LONG_PAIR; const TypeTuple *TypeTuple::INT_CC_PAIR; const TypeTuple *TypeTuple::LONG_CC_PAIR; +static void collect_inline_fields(ciInlineKlass* vk, const Type** field_array, uint& pos) { + for (int i = 0; i < vk->nof_declared_nonstatic_fields(); i++) { + ciField* field = vk->declared_nonstatic_field_at(i); + if (field->is_flat()) { + collect_inline_fields(field->type()->as_inline_klass(), field_array, pos); + if (!field->is_null_free()) { + // Use T_INT instead of T_BOOLEAN here because the upper bits can contain garbage if the holder + // is null and C2 will only zero them for T_INT assuming that T_BOOLEAN is already canonicalized. + field_array[pos++] = Type::get_const_basic_type(T_INT); + } + } else { + BasicType bt = field->type()->basic_type(); + const Type* ft = Type::get_const_type(field->type()); + field_array[pos++] = ft; + if (type2size[bt] == 2) { + field_array[pos++] = Type::HALF; + } + } + } +} + //------------------------------make------------------------------------------- // Make a TypeTuple from the range of a method signature -const TypeTuple *TypeTuple::make_range(ciSignature* sig, InterfaceHandling interface_handling) { +const TypeTuple *TypeTuple::make_range(ciSignature* sig, InterfaceHandling interface_handling, bool ret_vt_fields) { ciType* return_type = sig->return_type(); uint arg_cnt = return_type->size(); + if (ret_vt_fields) { + arg_cnt = return_type->as_inline_klass()->inline_arg_slots() + 1; + // InlineTypeNode::NullMarker field used for null checking + arg_cnt++; + } const Type **field_array = fields(arg_cnt); switch (return_type->basic_type()) { case T_LONG: @@ -2063,6 +2162,18 @@ const TypeTuple *TypeTuple::make_range(ciSignature* sig, InterfaceHandling inter field_array[TypeFunc::Parms+1] = Type::HALF; break; case T_OBJECT: + if (return_type->is_inlinetype() && ret_vt_fields) { + uint pos = TypeFunc::Parms; + field_array[pos++] = get_const_type(return_type); // Oop might be null when returning as fields + collect_inline_fields(return_type->as_inline_klass(), field_array, pos); + // InlineTypeNode::NullMarker field used for null checking + field_array[pos++] = get_const_basic_type(T_BOOLEAN); + assert(pos == (TypeFunc::Parms + arg_cnt), "out of bounds"); + break; + } else { + field_array[TypeFunc::Parms] = get_const_type(return_type, interface_handling)->join_speculative(TypePtr::BOTTOM); + } + break; case T_ARRAY: case T_BOOLEAN: case T_CHAR: @@ -2081,25 +2192,34 @@ const TypeTuple *TypeTuple::make_range(ciSignature* sig, InterfaceHandling inter } // Make a TypeTuple from the domain of a method signature -const TypeTuple *TypeTuple::make_domain(ciInstanceKlass* recv, ciSignature* sig, InterfaceHandling interface_handling) { - uint arg_cnt = sig->size(); +const TypeTuple *TypeTuple::make_domain(ciMethod* method, InterfaceHandling interface_handling, bool vt_fields_as_args) { + ciSignature* sig = method->signature(); + uint arg_cnt = sig->size() + (method->is_static() ? 0 : 1); + if (vt_fields_as_args) { + arg_cnt = 0; + assert(method->get_sig_cc() != nullptr, "Should have scalarized signature"); + for (ExtendedSignature sig_cc = ExtendedSignature(method->get_sig_cc(), SigEntryFilter()); !sig_cc.at_end(); ++sig_cc) { + arg_cnt += type2size[(*sig_cc)._bt]; + } + } uint pos = TypeFunc::Parms; - const Type **field_array; - if (recv != nullptr) { - arg_cnt++; - field_array = fields(arg_cnt); - // Use get_const_type here because it respects UseUniqueSubclasses: - field_array[pos++] = get_const_type(recv, interface_handling)->join_speculative(TypePtr::NOTNULL); - } else { - field_array = fields(arg_cnt); + const Type** field_array = fields(arg_cnt); + if (!method->is_static()) { + ciInstanceKlass* recv = method->holder(); + if (vt_fields_as_args && recv->is_inlinetype() && recv->as_inline_klass()->can_be_passed_as_fields() && method->is_scalarized_arg(0)) { + collect_inline_fields(recv->as_inline_klass(), field_array, pos); + } else { + field_array[pos++] = get_const_type(recv, interface_handling)->join_speculative(TypePtr::NOTNULL); + } } int i = 0; while (pos < TypeFunc::Parms + arg_cnt) { ciType* type = sig->type_at(i); + BasicType bt = type->basic_type(); - switch (type->basic_type()) { + switch (bt) { case T_LONG: field_array[pos++] = TypeLong::LONG; field_array[pos++] = Type::HALF; @@ -2109,6 +2229,14 @@ const TypeTuple *TypeTuple::make_domain(ciInstanceKlass* recv, ciSignature* sig, field_array[pos++] = Type::HALF; break; case T_OBJECT: + if (type->is_inlinetype() && vt_fields_as_args && method->is_scalarized_arg(i + (method->is_static() ? 0 : 1))) { + // InlineTypeNode::NullMarker field used for null checking + field_array[pos++] = get_const_basic_type(T_BOOLEAN); + collect_inline_fields(type->as_inline_klass(), field_array, pos); + } else { + field_array[pos++] = get_const_type(type, interface_handling); + } + break; case T_ARRAY: case T_FLOAT: case T_INT: @@ -2125,6 +2253,7 @@ const TypeTuple *TypeTuple::make_domain(ciInstanceKlass* recv, ciSignature* sig, } i++; } + assert(pos == TypeFunc::Parms + arg_cnt, "wrong number of arguments"); return (TypeTuple*)(new TypeTuple(TypeFunc::Parms + arg_cnt, field_array))->hashcons(); } @@ -2259,12 +2388,13 @@ inline const TypeInt* normalize_array_size(const TypeInt* size) { } //------------------------------make------------------------------------------- -const TypeAry* TypeAry::make(const Type* elem, const TypeInt* size, bool stable) { +const TypeAry* TypeAry::make(const Type* elem, const TypeInt* size, bool stable, + bool flat, bool not_flat, bool not_null_free, bool atomic) { if (UseCompressedOops && elem->isa_oopptr()) { elem = elem->make_narrowoop(); } size = normalize_array_size(size); - return (TypeAry*)(new TypeAry(elem,size,stable))->hashcons(); + return (TypeAry*)(new TypeAry(elem, size, stable, flat, not_flat, not_null_free, atomic))->hashcons(); } //------------------------------meet------------------------------------------- @@ -2291,7 +2421,11 @@ const Type *TypeAry::xmeet( const Type *t ) const { return size; } return TypeAry::make(_elem->meet_speculative(a->_elem), - isize, _stable && a->_stable); + isize, _stable && a->_stable, + _flat && a->_flat, + _not_flat && a->_not_flat, + _not_null_free && a->_not_null_free, + _atomic && a->_atomic); } case Top: break; @@ -2304,7 +2438,7 @@ const Type *TypeAry::xmeet( const Type *t ) const { const Type *TypeAry::xdual() const { const TypeInt* size_dual = _size->dual()->is_int(); size_dual = normalize_array_size(size_dual); - return new TypeAry(_elem->dual(), size_dual, !_stable); + return new TypeAry(_elem->dual(), size_dual, !_stable, !_flat, !_not_flat, !_not_null_free, !_atomic); } //------------------------------eq--------------------------------------------- @@ -2313,27 +2447,33 @@ bool TypeAry::eq( const Type *t ) const { const TypeAry *a = (const TypeAry*)t; return _elem == a->_elem && _stable == a->_stable && - _size == a->_size; + _size == a->_size && + _flat == a->_flat && + _not_flat == a->_not_flat && + _not_null_free == a->_not_null_free && + _atomic == a->_atomic; + } //------------------------------hash------------------------------------------- // Type-specific hashing function. uint TypeAry::hash(void) const { - return (uint)(uintptr_t)_elem + (uint)(uintptr_t)_size + (uint)(_stable ? 43 : 0); + return (uint)(uintptr_t)_elem + (uint)(uintptr_t)_size + (uint)(_stable ? 43 : 0) + + (uint)(_flat ? 44 : 0) + (uint)(_not_flat ? 45 : 0) + (uint)(_not_null_free ? 46 : 0) + (uint)(_atomic ? 47 : 0); } /** * Return same type without a speculative part in the element */ const TypeAry* TypeAry::remove_speculative() const { - return make(_elem->remove_speculative(), _size, _stable); + return make(_elem->remove_speculative(), _size, _stable, _flat, _not_flat, _not_null_free, _atomic); } /** * Return same type with cleaned up speculative part of element */ const Type* TypeAry::cleanup_speculative() const { - return make(_elem->cleanup_speculative(), _size, _stable); + return make(_elem->cleanup_speculative(), _size, _stable, _flat, _not_flat, _not_null_free, _atomic); } /** @@ -2352,6 +2492,12 @@ const TypePtr* TypePtr::with_inline_depth(int depth) const { #ifndef PRODUCT void TypeAry::dump2( Dict &d, uint depth, outputStream *st ) const { if (_stable) st->print("stable:"); + if (_flat) st->print("flat:"); + if (Verbose) { + if (_not_flat) st->print("not flat:"); + if (_not_null_free) st->print("not null free:"); + } + if (_atomic) st->print("atomic:"); _elem->dump2(d, depth, st); st->print("["); _size->dump2(d, depth, st); @@ -2391,8 +2537,19 @@ bool TypeAry::ary_must_be_exact() const { tinst = _elem->make_ptr()->isa_instptr(); else tinst = _elem->isa_instptr(); - if (tinst) - return tinst->instance_klass()->is_final(); + if (tinst) { + if (tinst->instance_klass()->is_final()) { + // Even though MyValue is final, [LMyValue is only exact if the array + // is (not) null-free due to null-free [LMyValue <: null-able [LMyValue. + // TODO 8350865 If we know that the array can't be null-free, it's allowed to be exact, right? + // If so, we should add '&& !_not_null_free' + if (tinst->is_inlinetypeptr() && (tinst->ptr() != TypePtr::NotNull)) { + return false; + } + return true; + } + return false; + } const TypeAryPtr* tap; if (_elem->isa_narrowoop()) tap = _elem->make_ptr()->isa_aryptr(); @@ -2567,7 +2724,7 @@ const TypePtr::PTR TypePtr::ptr_meet[TypePtr::lastPTR][TypePtr::lastPTR] = { }; //------------------------------make------------------------------------------- -const TypePtr *TypePtr::make(TYPES t, enum PTR ptr, int offset, const TypePtr* speculative, int inline_depth) { +const TypePtr* TypePtr::make(TYPES t, enum PTR ptr, Offset offset, const TypePtr* speculative, int inline_depth) { return (TypePtr*)(new TypePtr(t,ptr,offset, speculative, inline_depth))->hashcons(); } @@ -2581,7 +2738,7 @@ const TypePtr* TypePtr::cast_to_ptr_type(PTR ptr) const { //------------------------------get_con---------------------------------------- intptr_t TypePtr::get_con() const { assert( _ptr == Null, "" ); - return _offset; + return offset(); } //------------------------------meet------------------------------------------- @@ -2655,20 +2812,13 @@ const Type *TypePtr::xmeet_helper(const Type *t) const { } //------------------------------meet_offset------------------------------------ -int TypePtr::meet_offset( int offset ) const { - // Either is 'TOP' offset? Return the other offset! - if( _offset == OffsetTop ) return offset; - if( offset == OffsetTop ) return _offset; - // If either is different, return 'BOTTOM' offset - if( _offset != offset ) return OffsetBot; - return _offset; +Type::Offset TypePtr::meet_offset(int offset) const { + return _offset.meet(Offset(offset)); } //------------------------------dual_offset------------------------------------ -int TypePtr::dual_offset( ) const { - if( _offset == OffsetTop ) return OffsetBot;// Map 'TOP' into 'BOTTOM' - if( _offset == OffsetBot ) return OffsetTop;// Map 'BOTTOM' into 'TOP' - return _offset; // Map everything else into self +Type::Offset TypePtr::dual_offset() const { + return _offset.dual(); } //------------------------------xdual------------------------------------------ @@ -2681,19 +2831,8 @@ const Type *TypePtr::xdual() const { } //------------------------------xadd_offset------------------------------------ -int TypePtr::xadd_offset( intptr_t offset ) const { - // Adding to 'TOP' offset? Return 'TOP'! - if( _offset == OffsetTop || offset == OffsetTop ) return OffsetTop; - // Adding to 'BOTTOM' offset? Return 'BOTTOM'! - if( _offset == OffsetBot || offset == OffsetBot ) return OffsetBot; - // Addition overflows or "accidentally" equals to OffsetTop? Return 'BOTTOM'! - offset += (intptr_t)_offset; - if (offset != (int)offset || offset == OffsetTop) return OffsetBot; - - // assert( _offset >= 0 && _offset+offset >= 0, "" ); - // It is possible to construct a negative offset during PhaseCCP - - return (int)offset; // Sum valid offsets +Type::Offset TypePtr::xadd_offset(intptr_t offset) const { + return _offset.add(offset); } //------------------------------add_offset------------------------------------- @@ -2702,20 +2841,20 @@ const TypePtr *TypePtr::add_offset( intptr_t offset ) const { } const TypePtr *TypePtr::with_offset(intptr_t offset) const { - return make(AnyPtr, _ptr, offset, _speculative, _inline_depth); + return make(AnyPtr, _ptr, Offset(offset), _speculative, _inline_depth); } //------------------------------eq--------------------------------------------- // Structural equality check for Type representations bool TypePtr::eq( const Type *t ) const { const TypePtr *a = (const TypePtr*)t; - return _ptr == a->ptr() && _offset == a->offset() && eq_speculative(a) && _inline_depth == a->_inline_depth; + return _ptr == a->ptr() && _offset == a->_offset && eq_speculative(a) && _inline_depth == a->_inline_depth; } //------------------------------hash------------------------------------------- // Type-specific hashing function. uint TypePtr::hash(void) const { - return (uint)_ptr + (uint)_offset + (uint)hash_speculative() + (uint)_inline_depth; + return (uint)_ptr + (uint)offset() + (uint)hash_speculative() + (uint)_inline_depth; } /** @@ -2981,9 +3120,7 @@ const char *const TypePtr::ptr_msg[TypePtr::lastPTR] = { void TypePtr::dump2( Dict &d, uint depth, outputStream *st ) const { if( _ptr == Null ) st->print("null"); else st->print("%s *", ptr_msg[_ptr]); - if( _offset == OffsetTop ) st->print("+top"); - else if( _offset == OffsetBot ) st->print("+bot"); - else if( _offset ) st->print("+%d", _offset); + _offset.dump2(st); dump_inline_depth(st); dump_speculative(st); } @@ -3018,11 +3155,11 @@ void TypePtr::dump_inline_depth(outputStream *st) const { // constants bool TypePtr::singleton(void) const { // TopPTR, Null, AnyNull, Constant are all singletons - return (_offset != OffsetBot) && !below_centerline(_ptr); + return (_offset != Offset::bottom) && !below_centerline(_ptr); } bool TypePtr::empty(void) const { - return (_offset == OffsetTop) || above_centerline(_ptr); + return (_offset == Offset::top) || above_centerline(_ptr); } //============================================================================= @@ -3429,7 +3566,7 @@ bool TypeInterfaces::has_non_array_interface() const { } //------------------------------TypeOopPtr------------------------------------- -TypeOopPtr::TypeOopPtr(TYPES t, PTR ptr, ciKlass* k, const TypeInterfaces* interfaces, bool xk, ciObject* o, int offset, +TypeOopPtr::TypeOopPtr(TYPES t, PTR ptr, ciKlass* k, const TypeInterfaces* interfaces, bool xk, ciObject* o, Offset offset, Offset field_offset, int instance_id, const TypePtr* speculative, int inline_depth) : TypePtr(t, ptr, offset, speculative, inline_depth), _const_oop(o), _klass(k), @@ -3445,43 +3582,55 @@ TypeOopPtr::TypeOopPtr(TYPES t, PTR ptr, ciKlass* k, const TypeInterfaces* inter } #endif if (Compile::current()->eliminate_boxing() && (t == InstPtr) && - (offset > 0) && xk && (k != nullptr) && k->is_instance_klass()) { - _is_ptr_to_boxed_value = k->as_instance_klass()->is_boxed_value_offset(offset); + (offset.get() > 0) && xk && (k != nullptr) && k->is_instance_klass()) { + _is_ptr_to_boxed_value = k->as_instance_klass()->is_boxed_value_offset(offset.get()); } #ifdef _LP64 - if (_offset > 0 || _offset == Type::OffsetTop || _offset == Type::OffsetBot) { - if (_offset == oopDesc::klass_offset_in_bytes()) { + if (this->offset() > 0 || this->offset() == Type::OffsetTop || this->offset() == Type::OffsetBot) { + if (this->offset() == oopDesc::klass_offset_in_bytes()) { _is_ptr_to_narrowklass = UseCompressedClassPointers; } else if (klass() == nullptr) { // Array with unknown body type assert(this->isa_aryptr(), "only arrays without klass"); _is_ptr_to_narrowoop = UseCompressedOops; - } else if (this->isa_aryptr()) { - _is_ptr_to_narrowoop = (UseCompressedOops && klass()->is_obj_array_klass() && - _offset != arrayOopDesc::length_offset_in_bytes()); + } else if (UseCompressedOops && this->isa_aryptr() && this->offset() != arrayOopDesc::length_offset_in_bytes()) { + if (klass()->is_obj_array_klass()) { + _is_ptr_to_narrowoop = true; + } else if (klass()->is_flat_array_klass() && field_offset != Offset::top && field_offset != Offset::bottom) { + // Check if the field of the inline type array element contains oops + ciInlineKlass* vk = klass()->as_flat_array_klass()->element_klass()->as_inline_klass(); + int foffset = field_offset.get() + vk->payload_offset(); + BasicType field_bt; + ciField* field = vk->get_field_by_offset(foffset, false); + if (field != nullptr) { + field_bt = field->layout_type(); + } else { + assert(field_offset.get() == vk->null_marker_offset_in_payload(), "no field or null marker of %s at offset %d", vk->name()->as_utf8(), foffset); + field_bt = T_BOOLEAN; + } + _is_ptr_to_narrowoop = UseCompressedOops && ::is_reference_type(field_bt); + } } else if (klass()->is_instance_klass()) { - ciInstanceKlass* ik = klass()->as_instance_klass(); if (this->isa_klassptr()) { // Perm objects don't use compressed references - } else if (_offset == OffsetBot || _offset == OffsetTop) { + } else if (_offset == Offset::bottom || _offset == Offset::top) { // unsafe access _is_ptr_to_narrowoop = UseCompressedOops; } else { assert(this->isa_instptr(), "must be an instance ptr."); - if (klass() == ciEnv::current()->Class_klass() && - (_offset == java_lang_Class::klass_offset() || - _offset == java_lang_Class::array_klass_offset())) { + (this->offset() == java_lang_Class::klass_offset() || + this->offset() == java_lang_Class::array_klass_offset())) { // Special hidden fields from the Class. assert(this->isa_instptr(), "must be an instance ptr."); _is_ptr_to_narrowoop = false; } else if (klass() == ciEnv::current()->Class_klass() && - _offset >= InstanceMirrorKlass::offset_of_static_fields()) { + this->offset() >= InstanceMirrorKlass::offset_of_static_fields()) { // Static fields ciField* field = nullptr; if (const_oop() != nullptr) { ciInstanceKlass* k = const_oop()->as_instance()->java_lang_Class_klass()->as_instance_klass(); - field = k->get_field_by_offset(_offset, true); + field = k->get_field_by_offset(this->offset(), true); } if (field != nullptr) { BasicType basic_elem_type = field->layout_type(); @@ -3492,7 +3641,8 @@ TypeOopPtr::TypeOopPtr(TYPES t, PTR ptr, ciKlass* k, const TypeInterfaces* inter } } else { // Instance fields which contains a compressed oop references. - ciField* field = ik->get_field_by_offset(_offset, false); + ciInstanceKlass* ik = klass()->as_instance_klass(); + ciField* field = ik->get_field_by_offset(this->offset(), false); if (field != nullptr) { BasicType basic_elem_type = field->layout_type(); _is_ptr_to_narrowoop = UseCompressedOops && ::is_reference_type(basic_elem_type); @@ -3512,14 +3662,14 @@ TypeOopPtr::TypeOopPtr(TYPES t, PTR ptr, ciKlass* k, const TypeInterfaces* inter } //------------------------------make------------------------------------------- -const TypeOopPtr *TypeOopPtr::make(PTR ptr, int offset, int instance_id, - const TypePtr* speculative, int inline_depth) { +const TypeOopPtr *TypeOopPtr::make(PTR ptr, Offset offset, int instance_id, + const TypePtr* speculative, int inline_depth) { assert(ptr != Constant, "no constant generic pointers"); ciKlass* k = Compile::current()->env()->Object_klass(); bool xk = false; ciObject* o = nullptr; const TypeInterfaces* interfaces = TypeInterfaces::make(); - return (TypeOopPtr*)(new TypeOopPtr(OopPtr, ptr, k, interfaces, xk, o, offset, instance_id, speculative, inline_depth))->hashcons(); + return (TypeOopPtr*)(new TypeOopPtr(OopPtr, ptr, k, interfaces, xk, o, offset, Offset::bottom, instance_id, speculative, inline_depth))->hashcons(); } @@ -3544,7 +3694,6 @@ const TypeOopPtr* TypeOopPtr::cast_to_exactness(bool klass_is_exact) const { return this; } - //------------------------------as_klass_type---------------------------------- // Return the klass type corresponding to this instance or array type. // It is the type that is loaded from an object of this type. @@ -3593,7 +3742,7 @@ const Type *TypeOopPtr::xmeet_helper(const Type *t) const { case AnyPtr: { // Found an AnyPtr type vs self-OopPtr type const TypePtr *tp = t->is_ptr(); - int offset = meet_offset(tp->offset()); + Offset offset = meet_offset(tp->offset()); PTR ptr = meet_ptr(tp->ptr()); const TypePtr* speculative = xmeet_speculative(tp); int depth = meet_inline_depth(tp->inline_depth()); @@ -3635,13 +3784,13 @@ const Type *TypeOopPtr::xmeet_helper(const Type *t) const { const Type *TypeOopPtr::xdual() const { assert(klass() == Compile::current()->env()->Object_klass(), "no klasses here"); assert(const_oop() == nullptr, "no constants here"); - return new TypeOopPtr(_base, dual_ptr(), klass(), _interfaces, klass_is_exact(), const_oop(), dual_offset(), dual_instance_id(), dual_speculative(), dual_inline_depth()); + return new TypeOopPtr(_base, dual_ptr(), klass(), _interfaces, klass_is_exact(), const_oop(), dual_offset(), Offset::bottom, dual_instance_id(), dual_speculative(), dual_inline_depth()); } //--------------------------make_from_klass_common----------------------------- // Computes the element-type given a klass. -const TypeOopPtr* TypeOopPtr::make_from_klass_common(ciKlass* klass, bool klass_change, bool try_for_exact, InterfaceHandling interface_handling) { - if (klass->is_instance_klass()) { +const TypeOopPtr* TypeOopPtr::make_from_klass_common(ciKlass *klass, bool klass_change, bool try_for_exact, InterfaceHandling interface_handling) { + if (klass->is_instance_klass() || klass->is_inlinetype()) { Compile* C = Compile::current(); Dependencies* deps = C->dependencies(); assert((deps != nullptr) == (C->method() != nullptr && C->method()->code_size() > 0), "sanity"); @@ -3668,25 +3817,51 @@ const TypeOopPtr* TypeOopPtr::make_from_klass_common(ciKlass* klass, bool klass_ } } const TypeInterfaces* interfaces = TypePtr::interfaces(klass, true, true, false, interface_handling); - return TypeInstPtr::make(TypePtr::BotPTR, klass, interfaces, klass_is_exact, nullptr, 0); + return TypeInstPtr::make(TypePtr::BotPTR, klass, interfaces, klass_is_exact, nullptr, Offset(0)); } else if (klass->is_obj_array_klass()) { - // Element is an object array. Recursively call ourself. - ciKlass* eklass = klass->as_obj_array_klass()->element_klass(); - const TypeOopPtr *etype = TypeOopPtr::make_from_klass_common(eklass, false, try_for_exact, interface_handling); - bool xk = etype->klass_is_exact(); - const TypeAry* arr0 = TypeAry::make(etype, TypeInt::POS); + // Element is an object or inline type array. Recursively call ourself. + const TypeOopPtr* etype = TypeOopPtr::make_from_klass_common(klass->as_array_klass()->element_klass(), /* klass_change= */ false, try_for_exact, interface_handling); + // Determine null-free/flat properties + const bool is_null_free = klass->as_array_klass()->is_elem_null_free(); + if (is_null_free) { + etype = etype->join_speculative(NOTNULL)->is_oopptr(); + } + const TypeOopPtr* exact_etype = etype; + if (etype->can_be_inline_type()) { + // Use exact type if element can be an inline type + exact_etype = TypeOopPtr::make_from_klass_common(klass->as_array_klass()->element_klass(), /* klass_change= */ true, /* try_for_exact= */ true, interface_handling); + } + bool not_inline = !exact_etype->can_be_inline_type(); + bool not_null_free = not_inline; + bool not_flat = !UseArrayFlattening || not_inline || (exact_etype->is_inlinetypeptr() && !exact_etype->inline_klass()->maybe_flat_in_array()); + bool atomic = klass->as_array_klass()->is_elem_atomic(); + // Even though MyValue is final, [LMyValue is not exact because null-free [LMyValue is a subtype. + bool xk = etype->klass_is_exact() && !etype->is_inlinetypeptr(); + const TypeAry* arr0 = TypeAry::make(etype, TypeInt::POS, /* stable= */ false, /* flat= */ false, not_flat, not_null_free, atomic); // We used to pass NotNull in here, asserting that the sub-arrays // are all not-null. This is not true in generally, as code can - // slam nulls down in the subarrays. - const TypeAryPtr* arr = TypeAryPtr::make(TypePtr::BotPTR, arr0, nullptr, xk, 0); + // slam nullptrs down in the subarrays. + const TypeAryPtr* arr = TypeAryPtr::make(TypePtr::BotPTR, arr0, nullptr, xk, Offset(0)); return arr; } else if (klass->is_type_array_klass()) { // Element is an typeArray const Type* etype = get_const_basic_type(klass->as_type_array_klass()->element_type()); - const TypeAry* arr0 = TypeAry::make(etype, TypeInt::POS); + const TypeAry* arr0 = TypeAry::make(etype, TypeInt::POS, + /* stable= */ false, /* flat= */ false, /* not_flat= */ true, /* not_null_free= */ true); // We used to pass NotNull in here, asserting that the array pointer // is not-null. That was not true in general. - const TypeAryPtr* arr = TypeAryPtr::make(TypePtr::BotPTR, arr0, klass, true, 0); + const TypeAryPtr* arr = TypeAryPtr::make(TypePtr::BotPTR, arr0, klass, true, Offset(0)); + return arr; + } else if (klass->is_flat_array_klass()) { + const TypeOopPtr* etype = TypeOopPtr::make_from_klass_raw(klass->as_array_klass()->element_klass(), trust_interfaces); + const bool is_null_free = klass->as_array_klass()->is_elem_null_free(); + if (is_null_free) { + etype = etype->join_speculative(NOTNULL)->is_oopptr(); + } + bool atomic = klass->as_array_klass()->is_elem_atomic(); + const TypeAry* arr0 = TypeAry::make(etype, TypeInt::POS, /* stable= */ false, /* flat= */ true, /* not_flat= */ false, /* not_null_free= */ false, atomic); + const bool exact = is_null_free; // Only exact if null-free because "null-free [LMyValue <: null-able [LMyValue". + const TypeAryPtr* arr = TypeAryPtr::make(TypePtr::BotPTR, arr0, klass, exact, Offset(0)); return arr; } else { ShouldNotReachHere(); @@ -3702,37 +3877,60 @@ const TypeOopPtr* TypeOopPtr::make_from_constant(ciObject* o, bool require_const const bool make_constant = require_constant || o->should_be_constant(); ciKlass* klass = o->klass(); - if (klass->is_instance_klass()) { - // Element is an instance + if (klass->is_instance_klass() || klass->is_inlinetype()) { + // Element is an instance or inline type if (make_constant) { return TypeInstPtr::make(o); } else { - return TypeInstPtr::make(TypePtr::NotNull, klass, true, nullptr, 0); + return TypeInstPtr::make(TypePtr::NotNull, klass, true, nullptr, Offset(0)); } } else if (klass->is_obj_array_klass()) { // Element is an object array. Recursively call ourself. - const TypeOopPtr *etype = - TypeOopPtr::make_from_klass_raw(klass->as_obj_array_klass()->element_klass(), trust_interfaces); - const TypeAry* arr0 = TypeAry::make(etype, TypeInt::make(o->as_array()->length())); + const TypeOopPtr* etype = TypeOopPtr::make_from_klass_raw(klass->as_array_klass()->element_klass(), trust_interfaces); + bool is_flat = o->as_array()->is_flat(); + bool is_null_free = o->as_array()->is_null_free(); + if (is_null_free) { + etype = etype->join_speculative(TypePtr::NOTNULL)->is_oopptr(); + } + bool is_atomic = o->as_array()->is_atomic(); + const TypeAry* arr0 = TypeAry::make(etype, TypeInt::make(o->as_array()->length()), /* stable= */ false, /* flat= */ false, + /* not_flat= */ !is_flat, /* not_null_free= */ !is_null_free, /* atomic= */ is_atomic); // We used to pass NotNull in here, asserting that the sub-arrays // are all not-null. This is not true in generally, as code can // slam nulls down in the subarrays. if (make_constant) { - return TypeAryPtr::make(TypePtr::Constant, o, arr0, klass, true, 0); + return TypeAryPtr::make(TypePtr::Constant, o, arr0, klass, true, Offset(0)); } else { - return TypeAryPtr::make(TypePtr::NotNull, arr0, klass, true, 0); + return TypeAryPtr::make(TypePtr::NotNull, arr0, klass, true, Offset(0)); } } else if (klass->is_type_array_klass()) { // Element is an typeArray - const Type* etype = - (Type*)get_const_basic_type(klass->as_type_array_klass()->element_type()); - const TypeAry* arr0 = TypeAry::make(etype, TypeInt::make(o->as_array()->length())); + const Type* etype = (Type*)get_const_basic_type(klass->as_type_array_klass()->element_type()); + const TypeAry* arr0 = TypeAry::make(etype, TypeInt::make(o->as_array()->length()), /* stable= */ false, /* flat= */ false, + /* not_flat= */ true, /* not_null_free= */ true); // We used to pass NotNull in here, asserting that the array pointer // is not-null. That was not true in general. if (make_constant) { - return TypeAryPtr::make(TypePtr::Constant, o, arr0, klass, true, 0); + return TypeAryPtr::make(TypePtr::Constant, o, arr0, klass, true, Offset(0)); + } else { + return TypeAryPtr::make(TypePtr::NotNull, arr0, klass, true, Offset(0)); + } + } else if (klass->is_flat_array_klass()) { + const TypeOopPtr* etype = TypeOopPtr::make_from_klass_raw(klass->as_array_klass()->element_klass(), trust_interfaces); + bool is_null_free = o->as_array()->is_null_free(); + if (is_null_free) { + etype = etype->join_speculative(TypePtr::NOTNULL)->is_oopptr(); + } + bool is_atomic = o->as_array()->is_atomic(); + const TypeAry* arr0 = TypeAry::make(etype, TypeInt::make(o->as_array()->length()), /* stable= */ false, /* flat= */ true, + /* not_flat= */ false, /* not_null_free= */ !is_null_free, /* atomic= */ is_atomic); + // We used to pass NotNull in here, asserting that the sub-arrays + // are all not-null. This is not true in generally, as code can + // slam nullptrs down in the subarrays. + if (make_constant) { + return TypeAryPtr::make(TypePtr::Constant, o, arr0, klass, true, Offset(0)); } else { - return TypeAryPtr::make(TypePtr::NotNull, arr0, klass, true, 0); + return TypeAryPtr::make(TypePtr::NotNull, arr0, klass, true, Offset(0)); } } @@ -3743,9 +3941,9 @@ const TypeOopPtr* TypeOopPtr::make_from_constant(ciObject* o, bool require_const //------------------------------get_con---------------------------------------- intptr_t TypeOopPtr::get_con() const { assert( _ptr == Null || _ptr == Constant, "" ); - assert( _offset >= 0, "" ); + assert(offset() >= 0, ""); - if (_offset != 0) { + if (offset() != 0) { // After being ported to the compiler interface, the compiler no longer // directly manipulates the addresses of oops. Rather, it only has a pointer // to a handle at compile time. This handle is embedded in the generated @@ -3804,12 +4002,7 @@ void TypeOopPtr::dump2( Dict &d, uint depth, outputStream *st ) const { st->print("oopptr:%s", ptr_msg[_ptr]); if( _klass_is_exact ) st->print(":exact"); if( const_oop() ) st->print(INTPTR_FORMAT, p2i(const_oop())); - switch( _offset ) { - case OffsetTop: st->print("+top"); break; - case OffsetBot: st->print("+any"); break; - case 0: break; - default: st->print("+%d",_offset); break; - } + _offset.dump2(st); if (_instance_id == InstanceTop) st->print(",iid=top"); else if (_instance_id != InstanceBot) @@ -3826,7 +4019,7 @@ void TypeOopPtr::dump2( Dict &d, uint depth, outputStream *st ) const { bool TypeOopPtr::singleton(void) const { // detune optimizer to not generate constant oop + constant offset as a constant! // TopPTR, Null, AnyNull, Constant are all singletons - return (_offset == 0) && !below_centerline(_ptr); + return (offset() == 0) && !below_centerline(_ptr); } //------------------------------add_offset------------------------------------- @@ -3835,7 +4028,7 @@ const TypePtr* TypeOopPtr::add_offset(intptr_t offset) const { } const TypeOopPtr* TypeOopPtr::with_offset(intptr_t offset) const { - return make(_ptr, offset, _instance_id, with_offset_speculative(offset), _inline_depth); + return make(_ptr, Offset(offset), _instance_id, with_offset_speculative(offset), _inline_depth); } /** @@ -3948,13 +4141,16 @@ ciKlass* TypeInstPtr::exact_klass_helper() const { } //------------------------------TypeInstPtr------------------------------------- -TypeInstPtr::TypeInstPtr(PTR ptr, ciKlass* k, const TypeInterfaces* interfaces, bool xk, ciObject* o, int off, - int instance_id, const TypePtr* speculative, int inline_depth) - : TypeOopPtr(InstPtr, ptr, k, interfaces, xk, o, off, instance_id, speculative, inline_depth) { +TypeInstPtr::TypeInstPtr(PTR ptr, ciKlass* k, const TypeInterfaces* interfaces, bool xk, ciObject* o, Offset off, + bool flat_in_array, int instance_id, const TypePtr* speculative, int inline_depth) + : TypeOopPtr(InstPtr, ptr, k, interfaces, xk, o, off, Offset::bottom, instance_id, speculative, inline_depth), + _flat_in_array(flat_in_array) { assert(k == nullptr || !k->is_loaded() || !k->is_interface(), "no interface here"); assert(k != nullptr && (k->is_loaded() || o == nullptr), "cannot have constants with non-loaded klass"); + assert(!klass()->maybe_flat_in_array() || flat_in_array, "Should be flat in array"); + assert(!flat_in_array || can_be_inline_type(), "Only inline types can be flat in array"); }; //------------------------------make------------------------------------------- @@ -3963,7 +4159,8 @@ const TypeInstPtr *TypeInstPtr::make(PTR ptr, const TypeInterfaces* interfaces, bool xk, ciObject* o, - int offset, + Offset offset, + bool flat_in_array, int instance_id, const TypePtr* speculative, int inline_depth) { @@ -3985,9 +4182,12 @@ const TypeInstPtr *TypeInstPtr::make(PTR ptr, if (xk && ik->is_interface()) xk = false; // no exact interface } + // Check if this type is known to be flat in arrays + flat_in_array = flat_in_array || k->maybe_flat_in_array(); + // Now hash this baby TypeInstPtr *result = - (TypeInstPtr*)(new TypeInstPtr(ptr, k, interfaces, xk, o ,offset, instance_id, speculative, inline_depth))->hashcons(); + (TypeInstPtr*)(new TypeInstPtr(ptr, k, interfaces, xk, o, offset, flat_in_array, instance_id, speculative, inline_depth))->hashcons(); return result; } @@ -4053,7 +4253,7 @@ const TypeInstPtr* TypeInstPtr::cast_to_ptr_type(PTR ptr) const { if( ptr == _ptr ) return this; // Reconstruct _sig info here since not a problem with later lazy // construction, _sig will show up on demand. - return make(ptr, klass(), _interfaces, klass_is_exact(), ptr == Constant ? const_oop() : nullptr, _offset, _instance_id, _speculative, _inline_depth); + return make(ptr, klass(), _interfaces, klass_is_exact(), ptr == Constant ? const_oop() : nullptr, _offset, _flat_in_array, _instance_id, _speculative, _inline_depth); } @@ -4064,20 +4264,20 @@ const TypeInstPtr* TypeInstPtr::cast_to_exactness(bool klass_is_exact) const { ciInstanceKlass* ik = _klass->as_instance_klass(); if( (ik->is_final() || _const_oop) ) return this; // cannot clear xk assert(!ik->is_interface(), "no interface here"); - return make(ptr(), klass(), _interfaces, klass_is_exact, const_oop(), _offset, _instance_id, _speculative, _inline_depth); + return make(ptr(), klass(), _interfaces, klass_is_exact, const_oop(), _offset, _flat_in_array, _instance_id, _speculative, _inline_depth); } //-----------------------------cast_to_instance_id---------------------------- const TypeInstPtr* TypeInstPtr::cast_to_instance_id(int instance_id) const { if( instance_id == _instance_id ) return this; - return make(_ptr, klass(), _interfaces, _klass_is_exact, const_oop(), _offset, instance_id, _speculative, _inline_depth); + return make(_ptr, klass(), _interfaces, _klass_is_exact, const_oop(), _offset, _flat_in_array, instance_id, _speculative, _inline_depth); } //------------------------------xmeet_unloaded--------------------------------- // Compute the MEET of two InstPtrs when at least one is unloaded. // Assume classes are different since called after check for same name/class-loader const TypeInstPtr *TypeInstPtr::xmeet_unloaded(const TypeInstPtr *tinst, const TypeInterfaces* interfaces) const { - int off = meet_offset(tinst->offset()); + Offset off = meet_offset(tinst->offset()); PTR ptr = meet_ptr(tinst->ptr()); int instance_id = meet_instance_id(tinst->instance_id()); const TypePtr* speculative = xmeet_speculative(tinst); @@ -4102,7 +4302,7 @@ const TypeInstPtr *TypeInstPtr::xmeet_unloaded(const TypeInstPtr *tinst, const T assert(loaded->ptr() != TypePtr::Null, "insanity check"); // if (loaded->ptr() == TypePtr::TopPTR) { return unloaded->with_speculative(speculative); } - else if (loaded->ptr() == TypePtr::AnyNull) { return make(ptr, unloaded->klass(), interfaces, false, nullptr, off, instance_id, speculative, depth); } + else if (loaded->ptr() == TypePtr::AnyNull) { return make(ptr, unloaded->klass(), interfaces, false, nullptr, off, false, instance_id, speculative, depth); } else if (loaded->ptr() == TypePtr::BotPTR) { return TypeInstPtr::BOTTOM->with_speculative(speculative); } else if (loaded->ptr() == TypePtr::Constant || loaded->ptr() == TypePtr::NotNull) { if (unloaded->ptr() == TypePtr::BotPTR) { return TypeInstPtr::BOTTOM->with_speculative(speculative); } @@ -4166,7 +4366,7 @@ const Type *TypeInstPtr::xmeet_helper(const Type *t) const { case OopPtr: { // Meeting to OopPtrs // Found a OopPtr type vs self-InstPtr type const TypeOopPtr *tp = t->is_oopptr(); - int offset = meet_offset(tp->offset()); + Offset offset = meet_offset(tp->offset()); PTR ptr = meet_ptr(tp->ptr()); switch (tp->ptr()) { case TopPTR: @@ -4175,7 +4375,7 @@ const Type *TypeInstPtr::xmeet_helper(const Type *t) const { const TypePtr* speculative = xmeet_speculative(tp); int depth = meet_inline_depth(tp->inline_depth()); return make(ptr, klass(), _interfaces, klass_is_exact(), - (ptr == Constant ? const_oop() : nullptr), offset, instance_id, speculative, depth); + (ptr == Constant ? const_oop() : nullptr), offset, flat_in_array(), instance_id, speculative, depth); } case NotNull: case BotPTR: { @@ -4191,7 +4391,7 @@ const Type *TypeInstPtr::xmeet_helper(const Type *t) const { case AnyPtr: { // Meeting to AnyPtrs // Found an AnyPtr type vs self-InstPtr type const TypePtr *tp = t->is_ptr(); - int offset = meet_offset(tp->offset()); + Offset offset = meet_offset(tp->offset()); PTR ptr = meet_ptr(tp->ptr()); int instance_id = meet_instance_id(InstanceTop); const TypePtr* speculative = xmeet_speculative(tp); @@ -4203,7 +4403,7 @@ const Type *TypeInstPtr::xmeet_helper(const Type *t) const { case TopPTR: case AnyNull: { return make(ptr, klass(), _interfaces, klass_is_exact(), - (ptr == Constant ? const_oop() : nullptr), offset, instance_id, speculative, depth); + (ptr == Constant ? const_oop() : nullptr), offset, flat_in_array(), instance_id, speculative, depth); } case NotNull: case BotPTR: @@ -4231,7 +4431,7 @@ const Type *TypeInstPtr::xmeet_helper(const Type *t) const { case InstPtr: { // Meeting 2 Oops? // Found an InstPtr sub-type vs self-InstPtr type const TypeInstPtr *tinst = t->is_instptr(); - int off = meet_offset(tinst->offset()); + Offset off = meet_offset(tinst->offset()); PTR ptr = meet_ptr(tinst->ptr()); int instance_id = meet_instance_id(tinst->instance_id()); const TypePtr* speculative = xmeet_speculative(tinst); @@ -4243,8 +4443,9 @@ const Type *TypeInstPtr::xmeet_helper(const Type *t) const { ciKlass* res_klass = nullptr; bool res_xk = false; + bool res_flat_in_array = false; const Type* res; - MeetResult kind = meet_instptr(ptr, interfaces, this, tinst, res_klass, res_xk); + MeetResult kind = meet_instptr(ptr, interfaces, this, tinst, res_klass, res_xk, res_flat_in_array); if (kind == UNLOADED) { // One of these classes has not been loaded @@ -4285,7 +4486,7 @@ const Type *TypeInstPtr::xmeet_helper(const Type *t) const { } else ptr = NotNull; } - res = make(ptr, res_klass, interfaces, res_xk, o, off, instance_id, speculative, depth); + res = make(ptr, res_klass, interfaces, res_xk, o, off, res_flat_in_array, instance_id, speculative, depth); } return res; @@ -4297,9 +4498,14 @@ const Type *TypeInstPtr::xmeet_helper(const Type *t) const { } template TypePtr::MeetResult TypePtr::meet_instptr(PTR& ptr, const TypeInterfaces*& interfaces, const T* this_type, const T* other_type, - ciKlass*& res_klass, bool& res_xk) { + ciKlass*& res_klass, bool& res_xk, bool& res_flat_in_array) { ciKlass* this_klass = this_type->klass(); ciKlass* other_klass = other_type->klass(); + const bool this_flat_in_array = this_type->flat_in_array(); + const bool other_flat_in_array = other_type->flat_in_array(); + const bool this_not_flat_in_array = this_type->not_flat_in_array(); + const bool other_not_flat_in_array = other_type->not_flat_in_array(); + bool this_xk = this_type->klass_is_exact(); bool other_xk = other_type->klass_is_exact(); PTR this_ptr = this_type->ptr(); @@ -4310,9 +4516,10 @@ template TypePtr::MeetResult TypePtr::meet_instptr(PTR& ptr, const Type // If we have constants, then we created oops so classes are loaded // and we can handle the constants further down. This case handles // both-not-loaded or both-loaded classes - if (ptr != Constant && this_klass->equals(other_klass) && this_xk == other_xk) { + if (ptr != Constant && this_klass->equals(other_klass) && this_xk == other_xk && this_flat_in_array == other_flat_in_array) { res_klass = this_klass; res_xk = this_xk; + res_flat_in_array = this_flat_in_array; return QUICK; } @@ -4345,42 +4552,137 @@ template TypePtr::MeetResult TypePtr::meet_instptr(PTR& ptr, const Type // If both types are equal to the subtype, exactness is and-ed below the // centerline and or-ed above it. (N.B. Constants are always exact.) - // Check for subtyping: + // Flat in Array property _flat_in_array. + // For simplicity, _flat_in_array is a boolean but we actually have a tri state: + // - Flat in array -> flat_in_array() + // - Not flat in array -> not_flat_in_array() + // - Maybe flat in array -> !not_flat_in_array() + // + // Maybe we should convert _flat_in_array to a proper lattice with four elements at some point: + // + // Top + // Flat in Array Not Flat in Array + // Maybe Flat in Array + // + // where + // Top = dual(maybe Flat In Array) = "Flat in Array AND Not Flat in Array" + // + // But for now we stick with the current model with _flat_in_array as a boolean. + // + // When meeting two InstPtr types, we want to have the following behavior: + // + // (FiA-M) Meet(this, other): + // 'this' and 'other' are either the same klass OR sub klasses: + // + // yes maybe no + // yes y m m y = Flat in Array + // maybe m m m n = Not Flat in Array + // no m m n m = Maybe Flat in Array + // + // Join(this, other): + // (FiA-J-Same) 'this' and 'other' are the SAME klass: + // + // yes maybe no E = Empty set + // yes y y E y = Flat in Array + // maybe y m m n = Not Flat in Array + // no E m n m = Maybe Flat in Array + // + // (FiA-J-Sub) 'this' and 'other' are SUB klasses: + // + // yes maybe no -> Super Klass E = Empty set + // yes y y y y = Flat in Array + // maybe y m m n = Not Flat in Array + // no E m n m = Maybe Flat in Array + // | + // v + // Sub Klass + // + // Note the difference when joining a super klass that is not flat in array with a sub klass that is compared to + // the same klass case. We will take over the flat in array property of the sub klass. This can be done because + // the super klass could be Object (i.e. not an inline type and thus not flat in array) while the sub klass is a + // value class which can be flat in array. + // + // The empty set is only a possible result when matching 'ptr' above the center line (i.e. joining). In this case, + // we can "fall hard" by setting 'ptr' to NotNull such that when we take the dual of that meet above the center + // line, we get an empty set again. + // + // Note: When changing to a separate lattice with _flat_in_array we may want to add TypeInst(Klass)Ptr::empty() + // that returns true when the meet result is FlatInArray::Top (i.e. dual(maybe flat in array)). + const T* subtype = nullptr; bool subtype_exact = false; + bool flat_in_array = false; + bool is_empty = false; if (this_type->is_same_java_type_as(other_type)) { + // Same klass subtype = this_type; subtype_exact = below_centerline(ptr) ? (this_xk && other_xk) : (this_xk || other_xk); - } else if (!other_xk && this_type->is_meet_subtype_of(other_type)) { + if (above_centerline(ptr)) { + // Case (FiA-J-Same) + // One is flat in array and the other not? Result is empty/"fall hard". + is_empty = (this_flat_in_array && other_not_flat_in_array) || (this_not_flat_in_array && other_flat_in_array); + } + } else if (!other_xk && is_meet_subtype_of(this_type, other_type)) { subtype = this_type; // Pick subtyping class subtype_exact = this_xk; - } else if(!this_xk && other_type->is_meet_subtype_of(this_type)) { + if (above_centerline(ptr)) { + // Case (FiA-J-Sub) + is_empty = this_not_flat_in_array && other_flat_in_array; + if (!is_empty) { + bool other_flat_this_maybe_flat = other_flat_in_array && (!this_flat_in_array && !this_not_flat_in_array); + flat_in_array = this_flat_in_array || other_flat_this_maybe_flat; + } + } + } else if (!this_xk && is_meet_subtype_of(other_type, this_type)) { subtype = other_type; // Pick subtyping class subtype_exact = other_xk; + if (above_centerline(ptr)) { + // Case (FiA-J-Sub) + is_empty = this_flat_in_array && other_not_flat_in_array; + if (!is_empty) { + bool this_flat_other_maybe_flat = this_flat_in_array && (!other_flat_in_array && !other_not_flat_in_array); + flat_in_array = other_flat_in_array || this_flat_other_maybe_flat; + } + } } - if (subtype) { - if (above_centerline(ptr)) { // both are up? + + if (subtype && !is_empty) { + if (above_centerline(ptr)) { + // Both types are empty. this_type = other_type = subtype; this_xk = other_xk = subtype_exact; + // Case (FiA-J-Sub) + bool other_flat_this_maybe_flat = other_flat_in_array && (!this_flat_in_array && !this_not_flat_in_array); + flat_in_array = this_flat_in_array || other_flat_this_maybe_flat; + // One is flat in array and the other not? Result is empty/"fall hard". + is_empty = (this_flat_in_array && other_not_flat_in_array) || (this_not_flat_in_array && other_flat_in_array); } else if (above_centerline(this_ptr) && !above_centerline(other_ptr)) { - this_type = other_type; // tinst is down; keep down man + // this_type is empty while other_type is not. Take other_type. + this_type = other_type; this_xk = other_xk; + flat_in_array = other_flat_in_array; } else if (above_centerline(other_ptr) && !above_centerline(this_ptr)) { + // other_type is empty while this_type is not. Take this_type. other_type = this_type; // this is down; keep down man - other_xk = this_xk; + flat_in_array = this_flat_in_array; } else { + // this_type and other_type are both non-empty. this_xk = subtype_exact; // either they are equal, or we'll do an LCA + // Case (FiA-M) + // Meeting two types below the center line: Only flat in array if both are. + flat_in_array = this_flat_in_array && other_flat_in_array; } } // Check for classes now being equal - if (this_type->is_same_java_type_as(other_type)) { + if (this_type->is_same_java_type_as(other_type) && !is_empty) { // If the klasses are equal, the constants may still differ. Fall to // NotNull if they do (neither constant is null; that is a special case // handled elsewhere). res_klass = this_type->klass(); res_xk = this_xk; + res_flat_in_array = flat_in_array; return SUBTYPE; } // Else classes are not equal @@ -4397,10 +4699,15 @@ template TypePtr::MeetResult TypePtr::meet_instptr(PTR& ptr, const Type res_klass = k; res_xk = false; + res_flat_in_array = this_flat_in_array && other_flat_in_array; return LCA; } +template bool TypePtr::is_meet_subtype_of(const T* sub_type, const T* super_type) { + return sub_type->is_meet_subtype_of(super_type) && !(super_type->flat_in_array() && sub_type->not_flat_in_array()); +} + //------------------------java_mirror_type-------------------------------------- ciType* TypeInstPtr::java_mirror_type() const { // must be a singleton type @@ -4408,7 +4715,6 @@ ciType* TypeInstPtr::java_mirror_type() const { // must be of type java.lang.Class if( klass() != ciEnv::current()->Class_klass() ) return nullptr; - return const_oop()->as_instance()->java_mirror_type(); } @@ -4417,7 +4723,7 @@ ciType* TypeInstPtr::java_mirror_type() const { // Dual: do NOT dual on klasses. This means I do NOT understand the Java // inheritance mechanism. const Type *TypeInstPtr::xdual() const { - return new TypeInstPtr(dual_ptr(), klass(), _interfaces, klass_is_exact(), const_oop(), dual_offset(), dual_instance_id(), dual_speculative(), dual_inline_depth()); + return new TypeInstPtr(dual_ptr(), klass(), _interfaces, klass_is_exact(), const_oop(), dual_offset(), flat_in_array(), dual_instance_id(), dual_speculative(), dual_inline_depth()); } //------------------------------eq--------------------------------------------- @@ -4426,6 +4732,7 @@ bool TypeInstPtr::eq( const Type *t ) const { const TypeInstPtr *p = t->is_instptr(); return klass()->equals(p->klass()) && + flat_in_array() == p->flat_in_array() && _interfaces->eq(p->_interfaces) && TypeOopPtr::eq(p); // Check sub-type stuff } @@ -4433,7 +4740,7 @@ bool TypeInstPtr::eq( const Type *t ) const { //------------------------------hash------------------------------------------- // Type-specific hashing function. uint TypeInstPtr::hash(void) const { - return klass()->hash() + TypeOopPtr::hash() + _interfaces->hash(); + return klass()->hash() + TypeOopPtr::hash() + _interfaces->hash() + (uint)flat_in_array(); } bool TypeInstPtr::is_java_subtype_of_helper(const TypeOopPtr* other, bool this_exact, bool other_exact) const { @@ -4487,13 +4794,14 @@ void TypeInstPtr::dump2(Dict &d, uint depth, outputStream* st) const { break; } - if( _offset ) { // Dump offset, if any - if( _offset == OffsetBot ) st->print("+any"); - else if( _offset == OffsetTop ) st->print("+unknown"); - else st->print("+%d", _offset); - } + _offset.dump2(st); st->print(" *"); + + if (flat_in_array() && !klass()->is_inlinetype()) { + st->print(" (flat in array)"); + } + if (_instance_id == InstanceTop) st->print(",iid=top"); else if (_instance_id != InstanceBot) @@ -4506,12 +4814,12 @@ void TypeInstPtr::dump2(Dict &d, uint depth, outputStream* st) const { //------------------------------add_offset------------------------------------- const TypePtr* TypeInstPtr::add_offset(intptr_t offset) const { - return make(_ptr, klass(), _interfaces, klass_is_exact(), const_oop(), xadd_offset(offset), + return make(_ptr, klass(), _interfaces, klass_is_exact(), const_oop(), xadd_offset(offset), flat_in_array(), _instance_id, add_offset_speculative(offset), _inline_depth); } const TypeInstPtr* TypeInstPtr::with_offset(intptr_t offset) const { - return make(_ptr, klass(), _interfaces, klass_is_exact(), const_oop(), offset, + return make(_ptr, klass(), _interfaces, klass_is_exact(), const_oop(), Offset(offset), flat_in_array(), _instance_id, with_offset_speculative(offset), _inline_depth); } @@ -4520,24 +4828,28 @@ const TypeInstPtr* TypeInstPtr::remove_speculative() const { return this; } assert(_inline_depth == InlineDepthTop || _inline_depth == InlineDepthBottom, "non speculative type shouldn't have inline depth"); - return make(_ptr, klass(), _interfaces, klass_is_exact(), const_oop(), _offset, + return make(_ptr, klass(), _interfaces, klass_is_exact(), const_oop(), _offset, flat_in_array(), _instance_id, nullptr, _inline_depth); } const TypeInstPtr* TypeInstPtr::with_speculative(const TypePtr* speculative) const { - return make(_ptr, klass(), _interfaces, klass_is_exact(), const_oop(), _offset, _instance_id, speculative, _inline_depth); + return make(_ptr, klass(), _interfaces, klass_is_exact(), const_oop(), _offset, flat_in_array(), _instance_id, speculative, _inline_depth); } const TypePtr* TypeInstPtr::with_inline_depth(int depth) const { if (!UseInlineDepthForSpeculativeTypes) { return this; } - return make(_ptr, klass(), _interfaces, klass_is_exact(), const_oop(), _offset, _instance_id, _speculative, depth); + return make(_ptr, klass(), _interfaces, klass_is_exact(), const_oop(), _offset, flat_in_array(), _instance_id, _speculative, depth); } const TypePtr* TypeInstPtr::with_instance_id(int instance_id) const { assert(is_known_instance(), "should be known"); - return make(_ptr, klass(), _interfaces, klass_is_exact(), const_oop(), _offset, instance_id, _speculative, _inline_depth); + return make(_ptr, klass(), _interfaces, klass_is_exact(), const_oop(), _offset, flat_in_array(), instance_id, _speculative, _inline_depth); +} + +const TypeInstPtr *TypeInstPtr::cast_to_flat_in_array() const { + return make(_ptr, klass(), _interfaces, klass_is_exact(), const_oop(), _offset, true, _instance_id, _speculative, _inline_depth); } const TypeKlassPtr* TypeInstPtr::as_klass_type(bool try_for_exact) const { @@ -4551,7 +4863,7 @@ const TypeKlassPtr* TypeInstPtr::as_klass_type(bool try_for_exact) const { xk = true; } } - return TypeInstKlassPtr::make(xk ? TypePtr::Constant : TypePtr::NotNull, klass(), _interfaces, 0); + return TypeInstKlassPtr::make(xk ? TypePtr::Constant : TypePtr::NotNull, klass(), _interfaces, Offset(0), flat_in_array()); } template bool TypePtr::is_meet_subtype_of_helper_for_instance(const T1* this_one, const T2* other, bool this_xk, bool other_xk) { @@ -4596,7 +4908,6 @@ template bool TypePtr::is_meet_subtype_of_helper_for_array if (other_elem != nullptr && this_elem != nullptr) { return this_one->is_reference_type(this_elem)->is_meet_subtype_of_helper(this_one->is_reference_type(other_elem), this_xk, other_xk); } - if (other_elem == nullptr && this_elem == nullptr) { return this_one->klass()->is_subtype_of(other->klass()); } @@ -4619,19 +4930,20 @@ bool TypeAryKlassPtr::is_meet_subtype_of_helper(const TypeKlassPtr *other, bool //============================================================================= // Convenience common pre-built types. const TypeAryPtr* TypeAryPtr::BOTTOM; -const TypeAryPtr* TypeAryPtr::RANGE; -const TypeAryPtr* TypeAryPtr::OOPS; -const TypeAryPtr* TypeAryPtr::NARROWOOPS; -const TypeAryPtr* TypeAryPtr::BYTES; -const TypeAryPtr* TypeAryPtr::SHORTS; -const TypeAryPtr* TypeAryPtr::CHARS; -const TypeAryPtr* TypeAryPtr::INTS; -const TypeAryPtr* TypeAryPtr::LONGS; -const TypeAryPtr* TypeAryPtr::FLOATS; -const TypeAryPtr* TypeAryPtr::DOUBLES; +const TypeAryPtr *TypeAryPtr::RANGE; +const TypeAryPtr *TypeAryPtr::OOPS; +const TypeAryPtr *TypeAryPtr::NARROWOOPS; +const TypeAryPtr *TypeAryPtr::BYTES; +const TypeAryPtr *TypeAryPtr::SHORTS; +const TypeAryPtr *TypeAryPtr::CHARS; +const TypeAryPtr *TypeAryPtr::INTS; +const TypeAryPtr *TypeAryPtr::LONGS; +const TypeAryPtr *TypeAryPtr::FLOATS; +const TypeAryPtr *TypeAryPtr::DOUBLES; +const TypeAryPtr *TypeAryPtr::INLINES; //------------------------------make------------------------------------------- -const TypeAryPtr *TypeAryPtr::make(PTR ptr, const TypeAry *ary, ciKlass* k, bool xk, int offset, +const TypeAryPtr* TypeAryPtr::make(PTR ptr, const TypeAry *ary, ciKlass* k, bool xk, Offset offset, Offset field_offset, int instance_id, const TypePtr* speculative, int inline_depth) { assert(!(k == nullptr && ary->_elem->isa_int()), "integral arrays must be pre-equipped with a class"); @@ -4641,11 +4953,11 @@ const TypeAryPtr *TypeAryPtr::make(PTR ptr, const TypeAry *ary, ciKlass* k, bool k->as_obj_array_klass()->base_element_klass()->is_interface()) { k = nullptr; } - return (TypeAryPtr*)(new TypeAryPtr(ptr, nullptr, ary, k, xk, offset, instance_id, false, speculative, inline_depth))->hashcons(); + return (TypeAryPtr*)(new TypeAryPtr(ptr, nullptr, ary, k, xk, offset, field_offset, instance_id, false, speculative, inline_depth))->hashcons(); } //------------------------------make------------------------------------------- -const TypeAryPtr *TypeAryPtr::make(PTR ptr, ciObject* o, const TypeAry *ary, ciKlass* k, bool xk, int offset, +const TypeAryPtr* TypeAryPtr::make(PTR ptr, ciObject* o, const TypeAry *ary, ciKlass* k, bool xk, Offset offset, Offset field_offset, int instance_id, const TypePtr* speculative, int inline_depth, bool is_autobox_cache) { assert(!(k == nullptr && ary->_elem->isa_int()), @@ -4657,13 +4969,13 @@ const TypeAryPtr *TypeAryPtr::make(PTR ptr, ciObject* o, const TypeAry *ary, ciK k->as_obj_array_klass()->base_element_klass()->is_interface()) { k = nullptr; } - return (TypeAryPtr*)(new TypeAryPtr(ptr, o, ary, k, xk, offset, instance_id, is_autobox_cache, speculative, inline_depth))->hashcons(); + return (TypeAryPtr*)(new TypeAryPtr(ptr, o, ary, k, xk, offset, field_offset, instance_id, is_autobox_cache, speculative, inline_depth))->hashcons(); } //------------------------------cast_to_ptr_type------------------------------- const TypeAryPtr* TypeAryPtr::cast_to_ptr_type(PTR ptr) const { if( ptr == _ptr ) return this; - return make(ptr, ptr == Constant ? const_oop() : nullptr, _ary, klass(), klass_is_exact(), _offset, _instance_id, _speculative, _inline_depth); + return make(ptr, ptr == Constant ? const_oop() : nullptr, _ary, klass(), klass_is_exact(), _offset, _field_offset, _instance_id, _speculative, _inline_depth, _is_autobox_cache); } @@ -4671,13 +4983,13 @@ const TypeAryPtr* TypeAryPtr::cast_to_ptr_type(PTR ptr) const { const TypeAryPtr* TypeAryPtr::cast_to_exactness(bool klass_is_exact) const { if( klass_is_exact == _klass_is_exact ) return this; if (_ary->ary_must_be_exact()) return this; // cannot clear xk - return make(ptr(), const_oop(), _ary, klass(), klass_is_exact, _offset, _instance_id, _speculative, _inline_depth); + return make(ptr(), const_oop(), _ary, klass(), klass_is_exact, _offset, _field_offset, _instance_id, _speculative, _inline_depth, _is_autobox_cache); } //-----------------------------cast_to_instance_id---------------------------- const TypeAryPtr* TypeAryPtr::cast_to_instance_id(int instance_id) const { if( instance_id == _instance_id ) return this; - return make(_ptr, const_oop(), _ary, klass(), _klass_is_exact, _offset, instance_id, _speculative, _inline_depth); + return make(_ptr, const_oop(), _ary, klass(), _klass_is_exact, _offset, _field_offset, instance_id, _speculative, _inline_depth, _is_autobox_cache); } @@ -4735,8 +5047,71 @@ const TypeAryPtr* TypeAryPtr::cast_to_size(const TypeInt* new_size) const { assert(new_size != nullptr, ""); new_size = narrow_size_type(new_size); if (new_size == size()) return this; - const TypeAry* new_ary = TypeAry::make(elem(), new_size, is_stable()); - return make(ptr(), const_oop(), new_ary, klass(), klass_is_exact(), _offset, _instance_id, _speculative, _inline_depth); + const TypeAry* new_ary = TypeAry::make(elem(), new_size, is_stable(), is_flat(), is_not_flat(), is_not_null_free(), is_atomic()); + return make(ptr(), const_oop(), new_ary, klass(), klass_is_exact(), _offset, _field_offset, _instance_id, _speculative, _inline_depth, _is_autobox_cache); +} + +//-------------------------------cast_to_not_flat------------------------------ +const TypeAryPtr* TypeAryPtr::cast_to_not_flat(bool not_flat) const { + if (not_flat == is_not_flat()) { + return this; + } + assert(!not_flat || !is_flat(), "inconsistency"); + const TypeAry* new_ary = TypeAry::make(elem(), size(), is_stable(), is_flat(), not_flat, is_not_null_free(), is_atomic()); + const TypeAryPtr* res = make(ptr(), const_oop(), new_ary, klass(), klass_is_exact(), _offset, _field_offset, _instance_id, _speculative, _inline_depth, _is_autobox_cache); + // We keep the speculative part if it contains information about flat-/nullability. + // Make sure it's removed if it's not better than the non-speculative type anymore. + if (res->speculative() == res->remove_speculative()) { + return res->remove_speculative(); + } + return res; +} + +//-------------------------------cast_to_not_null_free------------------------- +const TypeAryPtr* TypeAryPtr::cast_to_not_null_free(bool not_null_free) const { + if (not_null_free == is_not_null_free()) { + return this; + } + assert(!not_null_free || !is_null_free(), "inconsistency"); + const TypeAry* new_ary = TypeAry::make(elem(), size(), is_stable(), is_flat(), is_not_flat(), not_null_free, is_atomic()); + const TypeAryPtr* res = make(ptr(), const_oop(), new_ary, klass(), klass_is_exact(), _offset, _field_offset, + _instance_id, _speculative, _inline_depth, _is_autobox_cache); + // We keep the speculative part if it contains information about flat-/nullability. + // Make sure it's removed if it's not better than the non-speculative type anymore. + if (res->speculative() == res->remove_speculative()) { + return res->remove_speculative(); + } + return res; +} + +//---------------------------------update_properties--------------------------- +const TypeAryPtr* TypeAryPtr::update_properties(const TypeAryPtr* from) const { + if ((from->is_flat() && is_not_flat()) || + (from->is_not_flat() && is_flat()) || + (from->is_null_free() && is_not_null_free()) || + (from->is_not_null_free() && is_null_free())) { + return nullptr; // Inconsistent properties + } + const TypeAryPtr* res = this; + if (from->is_not_null_free()) { + res = res->cast_to_not_null_free(); + } + if (from->is_not_flat()) { + res = res->cast_to_not_flat(); + } + return res; +} + +jint TypeAryPtr::flat_layout_helper() const { + return klass()->as_flat_array_klass()->layout_helper(); +} + +int TypeAryPtr::flat_elem_size() const { + return klass()->as_flat_array_klass()->element_byte_size(); +} + +int TypeAryPtr::flat_log_elem_size() const { + return klass()->as_flat_array_klass()->log2_element_size(); } //------------------------------cast_to_stable--------------------------------- @@ -4752,9 +5127,9 @@ const TypeAryPtr* TypeAryPtr::cast_to_stable(bool stable, int stable_dimension) elem = elem_ptr = elem_ptr->is_aryptr()->cast_to_stable(stable, stable_dimension - 1); } - const TypeAry* new_ary = TypeAry::make(elem, size(), stable); + const TypeAry* new_ary = TypeAry::make(elem, size(), stable, is_flat(), is_not_flat(), is_not_null_free(), is_atomic()); - return make(ptr(), const_oop(), new_ary, klass(), klass_is_exact(), _offset, _instance_id, _speculative, _inline_depth); + return make(ptr(), const_oop(), new_ary, klass(), klass_is_exact(), _offset, _field_offset, _instance_id, _speculative, _inline_depth, _is_autobox_cache); } //-----------------------------stable_dimension-------------------------------- @@ -4774,8 +5149,8 @@ const TypeAryPtr* TypeAryPtr::cast_to_autobox_cache() const { if (etype == nullptr) return this; // The pointers in the autobox arrays are always non-null. etype = etype->cast_to_ptr_type(TypePtr::NotNull)->is_oopptr(); - const TypeAry* new_ary = TypeAry::make(etype, size(), is_stable()); - return make(ptr(), const_oop(), new_ary, klass(), klass_is_exact(), _offset, _instance_id, _speculative, _inline_depth, /*is_autobox_cache=*/true); + const TypeAry* new_ary = TypeAry::make(etype, size(), is_stable(), is_flat(), is_not_flat(), is_not_null_free(), is_atomic()); + return make(ptr(), const_oop(), new_ary, klass(), klass_is_exact(), _offset, _field_offset, _instance_id, _speculative, _inline_depth, /*is_autobox_cache=*/true); } //------------------------------eq--------------------------------------------- @@ -4784,13 +5159,14 @@ bool TypeAryPtr::eq( const Type *t ) const { const TypeAryPtr *p = t->is_aryptr(); return _ary == p->_ary && // Check array - TypeOopPtr::eq(p); // Check sub-parts + TypeOopPtr::eq(p) &&// Check sub-parts + _field_offset == p->_field_offset; } //------------------------------hash------------------------------------------- // Type-specific hashing function. uint TypeAryPtr::hash(void) const { - return (uint)(uintptr_t)_ary + TypeOopPtr::hash(); + return (uint)(uintptr_t)_ary + TypeOopPtr::hash() + _field_offset.get(); } bool TypeAryPtr::is_java_subtype_of_helper(const TypeOopPtr* other, bool this_exact, bool other_exact) const { @@ -4837,7 +5213,7 @@ const Type *TypeAryPtr::xmeet_helper(const Type *t) const { case OopPtr: { // Meeting to OopPtrs // Found a OopPtr type vs self-AryPtr type const TypeOopPtr *tp = t->is_oopptr(); - int offset = meet_offset(tp->offset()); + Offset offset = meet_offset(tp->offset()); PTR ptr = meet_ptr(tp->ptr()); int depth = meet_inline_depth(tp->inline_depth()); const TypePtr* speculative = xmeet_speculative(tp); @@ -4846,7 +5222,7 @@ const Type *TypeAryPtr::xmeet_helper(const Type *t) const { case AnyNull: { int instance_id = meet_instance_id(InstanceTop); return make(ptr, (ptr == Constant ? const_oop() : nullptr), - _ary, _klass, _klass_is_exact, offset, instance_id, speculative, depth); + _ary, _klass, _klass_is_exact, offset, _field_offset, instance_id, speculative, depth); } case BotPTR: case NotNull: { @@ -4860,7 +5236,7 @@ const Type *TypeAryPtr::xmeet_helper(const Type *t) const { case AnyPtr: { // Meeting two AnyPtrs // Found an AnyPtr type vs self-AryPtr type const TypePtr *tp = t->is_ptr(); - int offset = meet_offset(tp->offset()); + Offset offset = meet_offset(tp->offset()); PTR ptr = meet_ptr(tp->ptr()); const TypePtr* speculative = xmeet_speculative(tp); int depth = meet_inline_depth(tp->inline_depth()); @@ -4876,7 +5252,7 @@ const Type *TypeAryPtr::xmeet_helper(const Type *t) const { case AnyNull: { int instance_id = meet_instance_id(InstanceTop); return make(ptr, (ptr == Constant ? const_oop() : nullptr), - _ary, _klass, _klass_is_exact, offset, instance_id, speculative, depth); + _ary, _klass, _klass_is_exact, offset, _field_offset, instance_id, speculative, depth); } default: ShouldNotReachHere(); } @@ -4890,7 +5266,8 @@ const Type *TypeAryPtr::xmeet_helper(const Type *t) const { case AryPtr: { // Meeting 2 references? const TypeAryPtr *tap = t->is_aryptr(); - int off = meet_offset(tap->offset()); + Offset off = meet_offset(tap->offset()); + Offset field_off = meet_field_offset(tap->field_offset()); const Type* tm = _ary->meet_speculative(tap->_ary); const TypeAry* tary = tm->isa_ary(); if (tary == nullptr) { @@ -4904,9 +5281,27 @@ const Type *TypeAryPtr::xmeet_helper(const Type *t) const { ciKlass* res_klass = nullptr; bool res_xk = false; + bool res_flat = false; + bool res_not_flat = false; + bool res_not_null_free = false; + bool res_atomic = false; const Type* elem = tary->_elem; - if (meet_aryptr(ptr, elem, this, tap, res_klass, res_xk) == NOT_SUBTYPE) { + if (meet_aryptr(ptr, elem, this, tap, res_klass, res_xk, res_flat, res_not_flat, res_not_null_free, res_atomic) == NOT_SUBTYPE) { instance_id = InstanceBot; + } else if (this->is_flat() != tap->is_flat()) { + // Meeting flat inline type array with non-flat array. Adjust (field) offset accordingly. + if (tary->_flat) { + // Result is in a flat representation + off = Offset(is_flat() ? offset() : tap->offset()); + field_off = is_flat() ? field_offset() : tap->field_offset(); + } else if (below_centerline(ptr)) { + // Result is in a non-flat representation + off = Offset(flat_offset()).meet(Offset(tap->flat_offset())); + field_off = (field_off == Offset::top) ? Offset::top : Offset::bottom; + } else if (flat_offset() == tap->flat_offset()) { + off = Offset(!is_flat() ? offset() : tap->offset()); + field_off = !is_flat() ? field_offset() : tap->field_offset(); + } } ciObject* o = nullptr; // Assume not constant when done @@ -4924,13 +5319,13 @@ const Type *TypeAryPtr::xmeet_helper(const Type *t) const { ptr = NotNull; } } - return make(ptr, o, TypeAry::make(elem, tary->_size, tary->_stable), res_klass, res_xk, off, instance_id, speculative, depth); + return make(ptr, o, TypeAry::make(elem, tary->_size, tary->_stable, res_flat, res_not_flat, res_not_null_free, res_atomic), res_klass, res_xk, off, field_off, instance_id, speculative, depth); } // All arrays inherit from Object class case InstPtr: { const TypeInstPtr *tp = t->is_instptr(); - int offset = meet_offset(tp->offset()); + Offset offset = meet_offset(tp->offset()); PTR ptr = meet_ptr(tp->ptr()); int instance_id = meet_instance_id(tp->instance_id()); const TypePtr* speculative = xmeet_speculative(tp); @@ -4945,14 +5340,14 @@ const Type *TypeAryPtr::xmeet_helper(const Type *t) const { // For instances when a subclass meets a superclass we fall // below the centerline when the superclass is exact. We need to // do the same here. - if (tp->klass()->equals(ciEnv::current()->Object_klass()) && this_interfaces->contains(tp_interfaces) && !tp->klass_is_exact()) { - return TypeAryPtr::make(ptr, _ary, _klass, _klass_is_exact, offset, instance_id, speculative, depth); + if (tp->klass()->equals(ciEnv::current()->Object_klass()) && this_interfaces->contains(tp_interfaces) && !tp->klass_is_exact() && !tp->flat_in_array()) { + return TypeAryPtr::make(ptr, _ary, _klass, _klass_is_exact, offset, _field_offset, instance_id, speculative, depth); } else { // cannot subclass, so the meet has to fall badly below the centerline ptr = NotNull; instance_id = InstanceBot; interfaces = this_interfaces->intersection_with(tp_interfaces); - return TypeInstPtr::make(ptr, ciEnv::current()->Object_klass(), interfaces, false, nullptr,offset, instance_id, speculative, depth); + return TypeInstPtr::make(ptr, ciEnv::current()->Object_klass(), interfaces, false, nullptr, offset, false, instance_id, speculative, depth); } case Constant: case NotNull: @@ -4964,10 +5359,10 @@ const Type *TypeAryPtr::xmeet_helper(const Type *t) const { // For instances when a subclass meets a superclass we fall // below the centerline when the superclass is exact. We need // to do the same here. - if (tp->klass()->equals(ciEnv::current()->Object_klass()) && this_interfaces->contains(tp_interfaces) && !tp->klass_is_exact()) { + if (tp->klass()->equals(ciEnv::current()->Object_klass()) && this_interfaces->contains(tp_interfaces) && !tp->klass_is_exact() && !tp->flat_in_array()) { // that is, my array type is a subtype of 'tp' klass return make(ptr, (ptr == Constant ? const_oop() : nullptr), - _ary, _klass, _klass_is_exact, offset, instance_id, speculative, depth); + _ary, _klass, _klass_is_exact, offset, _field_offset, instance_id, speculative, depth); } } // The other case cannot happen, since t cannot be a subtype of an array. @@ -4979,7 +5374,7 @@ const Type *TypeAryPtr::xmeet_helper(const Type *t) const { instance_id = InstanceBot; } interfaces = this_interfaces->intersection_with(tp_interfaces); - return TypeInstPtr::make(ptr, ciEnv::current()->Object_klass(), interfaces, false, nullptr, offset, instance_id, speculative, depth); + return TypeInstPtr::make(ptr, ciEnv::current()->Object_klass(), interfaces, false, nullptr, offset, false, instance_id, speculative, depth); default: typerr(t); } } @@ -4988,8 +5383,8 @@ const Type *TypeAryPtr::xmeet_helper(const Type *t) const { } -template TypePtr::MeetResult TypePtr::meet_aryptr(PTR& ptr, const Type*& elem, const T* this_ary, - const T* other_ary, ciKlass*& res_klass, bool& res_xk) { +template TypePtr::MeetResult TypePtr::meet_aryptr(PTR& ptr, const Type*& elem, const T* this_ary, const T* other_ary, + ciKlass*& res_klass, bool& res_xk, bool &res_flat, bool& res_not_flat, bool& res_not_null_free, bool &res_atomic) { int dummy; bool this_top_or_bottom = (this_ary->base_element_type(dummy) == Type::TOP || this_ary->base_element_type(dummy) == Type::BOTTOM); bool other_top_or_bottom = (other_ary->base_element_type(dummy) == Type::TOP || other_ary->base_element_type(dummy) == Type::BOTTOM); @@ -4999,14 +5394,29 @@ template TypePtr::MeetResult TypePtr::meet_aryptr(PTR& ptr, const Type* bool other_xk = other_ary->klass_is_exact(); PTR this_ptr = this_ary->ptr(); PTR other_ptr = other_ary->ptr(); + bool this_flat = this_ary->is_flat(); + bool this_not_flat = this_ary->is_not_flat(); + bool other_flat = other_ary->is_flat(); + bool other_not_flat = other_ary->is_not_flat(); + bool this_not_null_free = this_ary->is_not_null_free(); + bool other_not_null_free = other_ary->is_not_null_free(); + bool this_atomic = this_ary->is_atomic(); + bool other_atomic = other_ary->is_atomic(); + const bool same_nullness = this_ary->is_null_free() == other_ary->is_null_free(); res_klass = nullptr; MeetResult result = SUBTYPE; + res_flat = this_flat && other_flat; + bool res_null_free = this_ary->is_null_free() && other_ary->is_null_free(); + res_not_flat = this_not_flat && other_not_flat; + res_not_null_free = this_not_null_free && other_not_null_free; + res_atomic = this_atomic && other_atomic; + if (elem->isa_int()) { // Integral array element types have irrelevant lattice relations. // It is the klass that determines array layout, not the element type. - if (this_top_or_bottom) - res_klass = other_klass; - else if (other_top_or_bottom || other_klass == this_klass) { + if (this_top_or_bottom) { + res_klass = other_klass; + } else if (other_top_or_bottom || other_klass == this_klass) { res_klass = this_klass; } else { // Something like byte[int+] meets char[int+]. @@ -5048,31 +5458,49 @@ template TypePtr::MeetResult TypePtr::meet_aryptr(PTR& ptr, const Type* // Compute new klass on demand, do not use tap->_klass if (below_centerline(this_ptr)) { res_xk = this_xk; + if (this_ary->is_flat()) { + elem = this_ary->elem(); + } } else { res_xk = (other_xk || this_xk); } - return result; + break; case Constant: { - if (this_ptr == Constant) { + if (this_ptr == Constant && same_nullness) { + // Only exact if same nullness since: + // null-free [LMyValue <: nullable [LMyValue. res_xk = true; - } else if(above_centerline(this_ptr)) { + } else if (above_centerline(this_ptr)) { res_xk = true; } else { // Only precise for identical arrays res_xk = this_xk && (this_ary->is_same_java_type_as(other_ary) || (this_top_or_bottom && other_top_or_bottom)); + // Even though MyValue is final, [LMyValue is only exact if the array + // is (not) null-free due to null-free [LMyValue <: null-able [LMyValue. + if (res_xk && !res_null_free && !res_not_null_free) { + res_xk = false; + } } - return result; + break; } case NotNull: case BotPTR: // Compute new klass on demand, do not use tap->_klass if (above_centerline(this_ptr)) { res_xk = other_xk; + if (other_ary->is_flat()) { + elem = other_ary->elem(); + } } else { res_xk = (other_xk && this_xk) && (this_ary->is_same_java_type_as(other_ary) || (this_top_or_bottom && other_top_or_bottom)); // Only precise for identical arrays + // Even though MyValue is final, [LMyValue is only exact if the array + // is (not) null-free due to null-free [LMyValue <: null-able [LMyValue. + if (res_xk && !res_null_free && !res_not_null_free) { + res_xk = false; + } } - return result; + break; default: { ShouldNotReachHere(); return result; @@ -5085,7 +5513,17 @@ template TypePtr::MeetResult TypePtr::meet_aryptr(PTR& ptr, const Type* //------------------------------xdual------------------------------------------ // Dual: compute field-by-field dual const Type *TypeAryPtr::xdual() const { - return new TypeAryPtr(dual_ptr(), _const_oop, _ary->dual()->is_ary(),_klass, _klass_is_exact, dual_offset(), dual_instance_id(), is_autobox_cache(), dual_speculative(), dual_inline_depth()); + bool xk = _klass_is_exact; + return new TypeAryPtr(dual_ptr(), _const_oop, _ary->dual()->is_ary(), _klass, xk, dual_offset(), dual_field_offset(), dual_instance_id(), is_autobox_cache(), dual_speculative(), dual_inline_depth()); +} + +Type::Offset TypeAryPtr::meet_field_offset(const Type::Offset offset) const { + return _field_offset.meet(offset); +} + +//------------------------------dual_offset------------------------------------ +Type::Offset TypeAryPtr::dual_field_offset() const { + return _field_offset.dual(); } //------------------------------dump2------------------------------------------ @@ -5113,18 +5551,40 @@ void TypeAryPtr::dump2( Dict &d, uint depth, outputStream *st ) const { break; } - if( _offset != 0 ) { + if (is_flat()) { + st->print(":flat"); + st->print("("); + _field_offset.dump2(st); + st->print(")"); + } else if (is_not_flat()) { + st->print(":not_flat"); + } + if (is_null_free()) { + st->print(":null free"); + } + if (is_atomic()) { + st->print(":atomic"); + } + if (Verbose) { + if (is_not_flat()) { + st->print(":not flat"); + } + if (is_not_null_free()) { + st->print(":nullable"); + } + } + if (offset() != 0) { BasicType basic_elem_type = elem()->basic_type(); int header_size = arrayOopDesc::base_offset_in_bytes(basic_elem_type); - if( _offset == OffsetTop ) st->print("+undefined"); - else if( _offset == OffsetBot ) st->print("+any"); - else if( _offset < header_size ) st->print("+%d", _offset); + if( _offset == Offset::top ) st->print("+undefined"); + else if( _offset == Offset::bottom ) st->print("+any"); + else if( offset() < header_size ) st->print("+%d", offset()); else { if (basic_elem_type == T_ILLEGAL) { st->print("+any"); } else { int elem_size = type2aelembytes(basic_elem_type); - st->print("[%d]", (_offset - header_size)/elem_size); + st->print("[%d]", (offset() - header_size)/elem_size); } } } @@ -5141,20 +5601,24 @@ void TypeAryPtr::dump2( Dict &d, uint depth, outputStream *st ) const { bool TypeAryPtr::empty(void) const { if (_ary->empty()) return true; + // FIXME: Does this belong here? Or in the meet code itself? + if (is_flat() && is_not_flat()) { + return true; + } return TypeOopPtr::empty(); } //------------------------------add_offset------------------------------------- const TypePtr* TypeAryPtr::add_offset(intptr_t offset) const { - return make(_ptr, _const_oop, _ary, _klass, _klass_is_exact, xadd_offset(offset), _instance_id, add_offset_speculative(offset), _inline_depth); + return make(_ptr, _const_oop, _ary, _klass, _klass_is_exact, xadd_offset(offset), _field_offset, _instance_id, add_offset_speculative(offset), _inline_depth, _is_autobox_cache); } const TypeAryPtr* TypeAryPtr::with_offset(intptr_t offset) const { - return make(_ptr, _const_oop, _ary, _klass, _klass_is_exact, offset, _instance_id, with_offset_speculative(offset), _inline_depth); + return make(_ptr, _const_oop, _ary, _klass, _klass_is_exact, Offset(offset), _field_offset, _instance_id, with_offset_speculative(offset), _inline_depth, _is_autobox_cache); } const TypeAryPtr* TypeAryPtr::with_ary(const TypeAry* ary) const { - return make(_ptr, _const_oop, ary, _klass, _klass_is_exact, _offset, _instance_id, _speculative, _inline_depth); + return make(_ptr, _const_oop, ary, _klass, _klass_is_exact, _offset, _field_offset, _instance_id, _speculative, _inline_depth, _is_autobox_cache); } const TypeAryPtr* TypeAryPtr::remove_speculative() const { @@ -5162,23 +5626,80 @@ const TypeAryPtr* TypeAryPtr::remove_speculative() const { return this; } assert(_inline_depth == InlineDepthTop || _inline_depth == InlineDepthBottom, "non speculative type shouldn't have inline depth"); - return make(_ptr, _const_oop, _ary->remove_speculative()->is_ary(), _klass, _klass_is_exact, _offset, _instance_id, nullptr, _inline_depth); + return make(_ptr, _const_oop, _ary->remove_speculative()->is_ary(), _klass, _klass_is_exact, _offset, _field_offset, _instance_id, nullptr, _inline_depth, _is_autobox_cache); +} + +const Type* TypeAryPtr::cleanup_speculative() const { + if (speculative() == nullptr) { + return this; + } + // Keep speculative part if it contains information about flat-/nullability + const TypeAryPtr* spec_aryptr = speculative()->isa_aryptr(); + if (spec_aryptr != nullptr && !above_centerline(spec_aryptr->ptr()) && + (spec_aryptr->is_not_flat() || spec_aryptr->is_not_null_free())) { + return this; + } + return TypeOopPtr::cleanup_speculative(); } const TypePtr* TypeAryPtr::with_inline_depth(int depth) const { if (!UseInlineDepthForSpeculativeTypes) { return this; } - return make(_ptr, _const_oop, _ary->remove_speculative()->is_ary(), _klass, _klass_is_exact, _offset, _instance_id, _speculative, depth); + return make(_ptr, _const_oop, _ary->remove_speculative()->is_ary(), _klass, _klass_is_exact, _offset, _field_offset, _instance_id, _speculative, depth, _is_autobox_cache); +} + +const TypeAryPtr* TypeAryPtr::with_field_offset(int offset) const { + return make(_ptr, _const_oop, _ary->remove_speculative()->is_ary(), _klass, _klass_is_exact, _offset, Offset(offset), _instance_id, _speculative, _inline_depth, _is_autobox_cache); +} + +const TypePtr* TypeAryPtr::add_field_offset_and_offset(intptr_t offset) const { + int adj = 0; + if (is_flat() && offset != Type::OffsetBot && offset != Type::OffsetTop) { + if (_offset.get() != OffsetBot && _offset.get() != OffsetTop) { + adj = _offset.get(); + offset += _offset.get(); + } + uint header = arrayOopDesc::base_offset_in_bytes(T_FLAT_ELEMENT); + if (_field_offset.get() != OffsetBot && _field_offset.get() != OffsetTop) { + offset += _field_offset.get(); + if (_offset.get() == OffsetBot || _offset.get() == OffsetTop) { + offset += header; + } + } + if (elem()->make_oopptr()->is_inlinetypeptr() && (offset >= (intptr_t)header || offset < 0)) { + // Try to get the field of the inline type array element we are pointing to + ciInlineKlass* vk = elem()->inline_klass(); + int shift = flat_log_elem_size(); + int mask = (1 << shift) - 1; + intptr_t field_offset = ((offset - header) & mask); + ciField* field = vk->get_field_by_offset(field_offset + vk->payload_offset(), false); + if (field != nullptr || field_offset == vk->null_marker_offset_in_payload()) { + return with_field_offset(field_offset)->add_offset(offset - field_offset - adj); + } + } + } + return add_offset(offset - adj); +} + +// Return offset incremented by field_offset for flat inline type arrays +int TypeAryPtr::flat_offset() const { + int offset = _offset.get(); + if (offset != Type::OffsetBot && offset != Type::OffsetTop && + _field_offset != Offset::bottom && _field_offset != Offset::top) { + offset += _field_offset.get(); + } + return offset; } const TypePtr* TypeAryPtr::with_instance_id(int instance_id) const { assert(is_known_instance(), "should be known"); - return make(_ptr, _const_oop, _ary->remove_speculative()->is_ary(), _klass, _klass_is_exact, _offset, instance_id, _speculative, _inline_depth); + return make(_ptr, _const_oop, _ary->remove_speculative()->is_ary(), _klass, _klass_is_exact, _offset, _field_offset, instance_id, _speculative, _inline_depth); } //============================================================================= + //------------------------------hash------------------------------------------- // Type-specific hashing function. uint TypeNarrowPtr::hash(void) const { @@ -5272,7 +5793,6 @@ const Type *TypeNarrowPtr::xmeet( const Type *t ) const { case AryKlassPtr: case NarrowOop: case NarrowKlass: - case Bottom: // Ye Olde Default return Type::BOTTOM; case Top: @@ -5356,7 +5876,7 @@ uint TypeMetadataPtr::hash(void) const { bool TypeMetadataPtr::singleton(void) const { // detune optimizer to not generate constant metadata + constant offset as a constant! // TopPTR, Null, AnyNull, Constant are all singletons - return (_offset == 0) && !below_centerline(_ptr); + return (offset() == 0) && !below_centerline(_ptr); } //------------------------------add_offset------------------------------------- @@ -5376,9 +5896,9 @@ const Type *TypeMetadataPtr::filter_helper(const Type *kills, bool include_specu //------------------------------get_con---------------------------------------- intptr_t TypeMetadataPtr::get_con() const { assert( _ptr == Null || _ptr == Constant, "" ); - assert( _offset >= 0, "" ); + assert(offset() >= 0, ""); - if (_offset != 0) { + if (offset() != 0) { // After being ported to the compiler interface, the compiler no longer // directly manipulates the addresses of oops. Rather, it only has a pointer // to a handle at compile time. This handle is embedded in the generated @@ -5432,7 +5952,7 @@ const Type *TypeMetadataPtr::xmeet( const Type *t ) const { case AnyPtr: { // Found an AnyPtr type vs self-OopPtr type const TypePtr *tp = t->is_ptr(); - int offset = meet_offset(tp->offset()); + Offset offset = meet_offset(tp->offset()); PTR ptr = meet_ptr(tp->ptr()); switch (tp->ptr()) { case Null: @@ -5460,7 +5980,7 @@ const Type *TypeMetadataPtr::xmeet( const Type *t ) const { case MetadataPtr: { const TypeMetadataPtr *tp = t->is_metadataptr(); - int offset = meet_offset(tp->offset()); + Offset offset = meet_offset(tp->offset()); PTR tptr = tp->ptr(); PTR ptr = meet_ptr(tptr); ciMetadata* md = (tptr == TopPTR) ? metadata() : tp->metadata(); @@ -5493,11 +6013,11 @@ const Type *TypeMetadataPtr::xdual() const { void TypeMetadataPtr::dump2( Dict &d, uint depth, outputStream *st ) const { st->print("metadataptr:%s", ptr_msg[_ptr]); if( metadata() ) st->print(INTPTR_FORMAT, p2i(metadata())); - switch( _offset ) { + switch (offset()) { case OffsetTop: st->print("+top"); break; case OffsetBot: st->print("+any"); break; case 0: break; - default: st->print("+%d",_offset); break; + default: st->print("+%d",offset()); break; } } #endif @@ -5507,20 +6027,20 @@ void TypeMetadataPtr::dump2( Dict &d, uint depth, outputStream *st ) const { // Convenience common pre-built type. const TypeMetadataPtr *TypeMetadataPtr::BOTTOM; -TypeMetadataPtr::TypeMetadataPtr(PTR ptr, ciMetadata* metadata, int offset): +TypeMetadataPtr::TypeMetadataPtr(PTR ptr, ciMetadata* metadata, Offset offset): TypePtr(MetadataPtr, ptr, offset), _metadata(metadata) { } const TypeMetadataPtr* TypeMetadataPtr::make(ciMethod* m) { - return make(Constant, m, 0); + return make(Constant, m, Offset(0)); } const TypeMetadataPtr* TypeMetadataPtr::make(ciMethodData* m) { - return make(Constant, m, 0); + return make(Constant, m, Offset(0)); } //------------------------------make------------------------------------------- // Create a meta data constant -const TypeMetadataPtr *TypeMetadataPtr::make(PTR ptr, ciMetadata* m, int offset) { +const TypeMetadataPtr* TypeMetadataPtr::make(PTR ptr, ciMetadata* m, Offset offset) { assert(m == nullptr || !m->is_klass(), "wrong type"); return (TypeMetadataPtr*)(new TypeMetadataPtr(ptr, m, offset))->hashcons(); } @@ -5531,21 +6051,26 @@ const TypeKlassPtr* TypeAryPtr::as_klass_type(bool try_for_exact) const { bool xk = klass_is_exact(); if (elem->make_oopptr() != nullptr) { elem = elem->make_oopptr()->as_klass_type(try_for_exact); - if (elem->is_klassptr()->klass_is_exact()) { + if (elem->is_klassptr()->klass_is_exact() && + // Even though MyValue is final, [LMyValue is only exact if the array + // is (not) null-free due to null-free [LMyValue <: null-able [LMyValue. + // TODO 8350865 If we know that the array can't be null-free, it's allowed to be exact, right? + // If so, we should add '|| is_not_null_free()' + (is_null_free() || !_ary->_elem->make_oopptr()->is_inlinetypeptr())) { xk = true; } } - return TypeAryKlassPtr::make(xk ? TypePtr::Constant : TypePtr::NotNull, elem, klass(), 0); + return TypeAryKlassPtr::make(xk ? TypePtr::Constant : TypePtr::NotNull, elem, klass(), Offset(0), is_not_flat(), is_not_null_free(), is_flat(), is_null_free(), is_atomic(), is_flat() || is_null_free()); } -const TypeKlassPtr* TypeKlassPtr::make(ciKlass *klass, InterfaceHandling interface_handling) { +const TypeKlassPtr* TypeKlassPtr::make(ciKlass* klass, InterfaceHandling interface_handling) { if (klass->is_instance_klass()) { return TypeInstKlassPtr::make(klass, interface_handling); } return TypeAryKlassPtr::make(klass, interface_handling); } -const TypeKlassPtr* TypeKlassPtr::make(PTR ptr, ciKlass* klass, int offset, InterfaceHandling interface_handling) { +const TypeKlassPtr* TypeKlassPtr::make(PTR ptr, ciKlass* klass, Offset offset, InterfaceHandling interface_handling) { if (klass->is_instance_klass()) { const TypeInterfaces* interfaces = TypePtr::interfaces(klass, true, true, false, interface_handling); return TypeInstKlassPtr::make(ptr, klass, interfaces, offset); @@ -5553,12 +6078,10 @@ const TypeKlassPtr* TypeKlassPtr::make(PTR ptr, ciKlass* klass, int offset, Inte return TypeAryKlassPtr::make(ptr, klass, offset, interface_handling); } - -//------------------------------TypeKlassPtr----------------------------------- -TypeKlassPtr::TypeKlassPtr(TYPES t, PTR ptr, ciKlass* klass, const TypeInterfaces* interfaces, int offset) +TypeKlassPtr::TypeKlassPtr(TYPES t, PTR ptr, ciKlass* klass, const TypeInterfaces* interfaces, Offset offset) : TypePtr(t, ptr, offset), _klass(klass), _interfaces(interfaces) { assert(klass == nullptr || !klass->is_loaded() || (klass->is_instance_klass() && !klass->is_interface()) || - klass->is_type_array_klass() || !klass->as_obj_array_klass()->base_element_klass()->is_interface(), "no interface here"); + klass->is_type_array_klass() || klass->is_flat_array_klass() || !klass->as_obj_array_klass()->base_element_klass()->is_interface(), "no interface here"); } // Is there a single ciKlass* that can represent that type? @@ -5597,7 +6120,7 @@ uint TypeKlassPtr::hash(void) const { bool TypeKlassPtr::singleton(void) const { // detune optimizer to not generate constant klass + constant offset as a constant! // TopPTR, Null, AnyNull, Constant are all singletons - return (_offset == 0) && !below_centerline(_ptr); + return (offset() == 0) && !below_centerline(_ptr); } // Do not allow interface-vs.-noninterface joins to collapse to top. @@ -5627,9 +6150,9 @@ const TypeInterfaces* TypeKlassPtr::meet_interfaces(const TypeKlassPtr* other) c //------------------------------get_con---------------------------------------- intptr_t TypeKlassPtr::get_con() const { assert( _ptr == Null || _ptr == Constant, "" ); - assert( _offset >= 0, "" ); + assert( offset() >= 0, "" ); - if (_offset != 0) { + if (offset() != 0) { // After being ported to the compiler interface, the compiler no longer // directly manipulates the addresses of oops. Rather, it only has a pointer // to a handle at compile time. This handle is embedded in the generated @@ -5673,14 +6196,15 @@ void TypeKlassPtr::dump2(Dict & d, uint depth, outputStream *st) const { default: break; } - - if (_offset) { // Dump offset, if any - if (_offset == OffsetBot) { st->print("+any"); } - else if (_offset == OffsetTop) { st->print("+unknown"); } - else { st->print("+%d", _offset); } + if (Verbose) { + if (isa_instklassptr() && is_instklassptr()->flat_in_array()) st->print(":flat in array"); } - + _offset.dump2(st); st->print(" *"); + + if (flat_in_array() && !klass()->is_inlinetype()) { + st->print(" (flat in array)"); + } } #endif @@ -5695,35 +6219,38 @@ bool TypeInstKlassPtr::eq(const Type *t) const { const TypeKlassPtr *p = t->is_klassptr(); return klass()->equals(p->klass()) && + flat_in_array() == p->flat_in_array() && TypeKlassPtr::eq(p); } uint TypeInstKlassPtr::hash(void) const { - return klass()->hash() + TypeKlassPtr::hash(); + return klass()->hash() + TypeKlassPtr::hash() + (uint)flat_in_array(); } -const TypeInstKlassPtr *TypeInstKlassPtr::make(PTR ptr, ciKlass* k, const TypeInterfaces* interfaces, int offset) { +const TypeInstKlassPtr *TypeInstKlassPtr::make(PTR ptr, ciKlass* k, const TypeInterfaces* interfaces, Offset offset, bool flat_in_array) { + flat_in_array = flat_in_array || k->maybe_flat_in_array(); + TypeInstKlassPtr *r = - (TypeInstKlassPtr*)(new TypeInstKlassPtr(ptr, k, interfaces, offset))->hashcons(); + (TypeInstKlassPtr*)(new TypeInstKlassPtr(ptr, k, interfaces, offset, flat_in_array))->hashcons(); return r; } //------------------------------add_offset------------------------------------- // Access internals of klass object -const TypePtr* TypeInstKlassPtr::add_offset( intptr_t offset ) const { - return make( _ptr, klass(), _interfaces, xadd_offset(offset) ); +const TypePtr *TypeInstKlassPtr::add_offset( intptr_t offset ) const { + return make(_ptr, klass(), _interfaces, xadd_offset(offset), flat_in_array()); } const TypeInstKlassPtr* TypeInstKlassPtr::with_offset(intptr_t offset) const { - return make(_ptr, klass(), _interfaces, offset); + return make(_ptr, klass(), _interfaces, Offset(offset), flat_in_array()); } //------------------------------cast_to_ptr_type------------------------------- const TypeInstKlassPtr* TypeInstKlassPtr::cast_to_ptr_type(PTR ptr) const { assert(_base == InstKlassPtr, "subclass must override cast_to_ptr_type"); if( ptr == _ptr ) return this; - return make(ptr, _klass, _interfaces, _offset); + return make(ptr, _klass, _interfaces, _offset, flat_in_array()); } @@ -5739,7 +6266,7 @@ const TypeKlassPtr* TypeInstKlassPtr::cast_to_exactness(bool klass_is_exact) con if (klass_is_exact == (_ptr == Constant)) return this; if (must_be_exact()) return this; ciKlass* k = klass(); - return make(klass_is_exact ? Constant : NotNull, k, _interfaces, _offset); + return make(klass_is_exact ? Constant : NotNull, k, _interfaces, _offset, flat_in_array()); } @@ -5771,7 +6298,7 @@ const TypeOopPtr* TypeInstKlassPtr::as_instance_type(bool klass_change) const { } } } - return TypeInstPtr::make(TypePtr::BotPTR, k, interfaces, xk, nullptr, 0); + return TypeInstPtr::make(TypePtr::BotPTR, k, interfaces, xk, nullptr, Offset(0), flat_in_array() && !klass()->is_inlinetype()); } //------------------------------xmeet------------------------------------------ @@ -5807,7 +6334,7 @@ const Type *TypeInstKlassPtr::xmeet( const Type *t ) const { case AnyPtr: { // Meeting to AnyPtrs // Found an AnyPtr type vs self-KlassPtr type const TypePtr *tp = t->is_ptr(); - int offset = meet_offset(tp->offset()); + Offset offset = meet_offset(tp->offset()); PTR ptr = meet_ptr(tp->ptr()); switch (tp->ptr()) { case TopPTR: @@ -5815,7 +6342,7 @@ const Type *TypeInstKlassPtr::xmeet( const Type *t ) const { case Null: if( ptr == Null ) return TypePtr::make(AnyPtr, ptr, offset, tp->speculative(), tp->inline_depth()); case AnyNull: - return make( ptr, klass(), _interfaces, offset ); + return make(ptr, klass(), _interfaces, offset, flat_in_array()); case BotPTR: case NotNull: return TypePtr::make(AnyPtr, ptr, offset, tp->speculative(), tp->inline_depth()); @@ -5828,7 +6355,7 @@ const Type *TypeInstKlassPtr::xmeet( const Type *t ) const { case OopPtr: case AryPtr: // Meet with AryPtr case InstPtr: // Meet with InstPtr - return TypePtr::BOTTOM; + return TypePtr::BOTTOM; // // A-top } @@ -5848,13 +6375,14 @@ const Type *TypeInstKlassPtr::xmeet( const Type *t ) const { case InstKlassPtr: { // Meet two KlassPtr types const TypeInstKlassPtr *tkls = t->is_instklassptr(); - int off = meet_offset(tkls->offset()); + Offset off = meet_offset(tkls->offset()); PTR ptr = meet_ptr(tkls->ptr()); const TypeInterfaces* interfaces = meet_interfaces(tkls); ciKlass* res_klass = nullptr; bool res_xk = false; - switch(meet_instptr(ptr, interfaces, this, tkls, res_klass, res_xk)) { + bool res_flat_in_array = false; + switch(meet_instptr(ptr, interfaces, this, tkls, res_klass, res_xk, res_flat_in_array)) { case UNLOADED: ShouldNotReachHere(); case SUBTYPE: @@ -5862,7 +6390,7 @@ const Type *TypeInstKlassPtr::xmeet( const Type *t ) const { case LCA: case QUICK: { assert(res_xk == (ptr == Constant), ""); - const Type* res = make(ptr, res_klass, interfaces, off); + const Type* res = make(ptr, res_klass, interfaces, off, res_flat_in_array); return res; } default: @@ -5871,7 +6399,7 @@ const Type *TypeInstKlassPtr::xmeet( const Type *t ) const { } // End of case KlassPtr case AryKlassPtr: { // All arrays inherit from Object class const TypeAryKlassPtr *tp = t->is_aryklassptr(); - int offset = meet_offset(tp->offset()); + Offset offset = meet_offset(tp->offset()); PTR ptr = meet_ptr(tp->ptr()); const TypeInterfaces* interfaces = meet_interfaces(tp); const TypeInterfaces* tp_interfaces = tp->_interfaces; @@ -5884,12 +6412,12 @@ const Type *TypeInstKlassPtr::xmeet( const Type *t ) const { // below the centerline when the superclass is exact. We need to // do the same here. if (klass()->equals(ciEnv::current()->Object_klass()) && tp_interfaces->contains(this_interfaces) && !klass_is_exact()) { - return TypeAryKlassPtr::make(ptr, tp->elem(), tp->klass(), offset); + return TypeAryKlassPtr::make(ptr, tp->elem(), tp->klass(), offset, tp->is_not_flat(), tp->is_not_null_free(), tp->is_flat(), tp->is_null_free(), tp->is_atomic(), tp->is_vm_type()); } else { // cannot subclass, so the meet has to fall badly below the centerline ptr = NotNull; interfaces = _interfaces->intersection_with(tp->_interfaces); - return make(ptr, ciEnv::current()->Object_klass(), interfaces, offset); + return make(ptr, ciEnv::current()->Object_klass(), interfaces, offset, false); } case Constant: case NotNull: @@ -5903,8 +6431,7 @@ const Type *TypeInstKlassPtr::xmeet( const Type *t ) const { // to do the same here. if (klass()->equals(ciEnv::current()->Object_klass()) && tp_interfaces->contains(this_interfaces) && !klass_is_exact()) { // that is, tp's array type is a subtype of my klass - return TypeAryKlassPtr::make(ptr, - tp->elem(), tp->klass(), offset); + return TypeAryKlassPtr::make(ptr, tp->elem(), tp->klass(), offset, tp->is_not_flat(), tp->is_not_null_free(), tp->is_flat(), tp->is_null_free(), tp->is_atomic(), tp->is_vm_type()); } } // The other case cannot happen, since I cannot be a subtype of an array. @@ -5912,7 +6439,7 @@ const Type *TypeInstKlassPtr::xmeet( const Type *t ) const { if( ptr == Constant ) ptr = NotNull; interfaces = this_interfaces->intersection_with(tp_interfaces); - return make(ptr, ciEnv::current()->Object_klass(), interfaces, offset); + return make(ptr, ciEnv::current()->Object_klass(), interfaces, offset, false); default: typerr(t); } } @@ -5924,7 +6451,7 @@ const Type *TypeInstKlassPtr::xmeet( const Type *t ) const { //------------------------------xdual------------------------------------------ // Dual: compute field-by-field dual const Type *TypeInstKlassPtr::xdual() const { - return new TypeInstKlassPtr(dual_ptr(), klass(), _interfaces, dual_offset()); + return new TypeInstKlassPtr(dual_ptr(), klass(), _interfaces, dual_offset(), flat_in_array()); } template bool TypePtr::is_java_subtype_of_helper_for_instance(const T1* this_one, const T2* other, bool this_exact, bool other_exact) { @@ -6038,29 +6565,72 @@ const TypeKlassPtr* TypeInstKlassPtr::try_improve() const { return this; } +bool TypeInstKlassPtr::can_be_inline_array() const { + return _klass->equals(ciEnv::current()->Object_klass()) && TypeAryKlassPtr::_array_interfaces->contains(_interfaces); +} -const TypeAryKlassPtr *TypeAryKlassPtr::make(PTR ptr, const Type* elem, ciKlass* k, int offset) { - return (TypeAryKlassPtr*)(new TypeAryKlassPtr(ptr, elem, k, offset))->hashcons(); +bool TypeAryKlassPtr::can_be_inline_array() const { + return _elem->isa_instklassptr() && _elem->is_instklassptr()->_klass->can_be_inline_klass(); } -const TypeAryKlassPtr *TypeAryKlassPtr::make(PTR ptr, ciKlass* k, int offset, InterfaceHandling interface_handling) { +bool TypeInstPtr::can_be_inline_array() const { + return _klass->equals(ciEnv::current()->Object_klass()) && TypeAryPtr::_array_interfaces->contains(_interfaces); +} + +bool TypeAryPtr::can_be_inline_array() const { + return elem()->make_ptr() && elem()->make_ptr()->isa_instptr() && elem()->make_ptr()->is_instptr()->_klass->can_be_inline_klass(); +} + +const TypeAryKlassPtr *TypeAryKlassPtr::make(PTR ptr, const Type* elem, ciKlass* k, Offset offset, bool not_flat, bool not_null_free, bool flat, bool null_free, bool atomic, bool vm_type) { + return (TypeAryKlassPtr*)(new TypeAryKlassPtr(ptr, elem, k, offset, not_flat, not_null_free, flat, null_free, atomic, vm_type))->hashcons(); +} + +const TypeAryKlassPtr* TypeAryKlassPtr::make(PTR ptr, ciKlass* k, Offset offset, InterfaceHandling interface_handling, bool not_flat, bool not_null_free, bool flat, bool null_free, bool atomic, bool vm_type) { if (k->is_obj_array_klass()) { // Element is an object array. Recursively call ourself. ciKlass* eklass = k->as_obj_array_klass()->element_klass(); - const TypeKlassPtr *etype = TypeKlassPtr::make(eklass, interface_handling)->cast_to_exactness(false); - return TypeAryKlassPtr::make(ptr, etype, nullptr, offset); + const TypeKlassPtr* etype = TypeKlassPtr::make(eklass, interface_handling)->cast_to_exactness(false); + return TypeAryKlassPtr::make(ptr, etype, nullptr, offset, not_flat, not_null_free, flat, null_free, atomic, vm_type); } else if (k->is_type_array_klass()) { // Element is an typeArray const Type* etype = get_const_basic_type(k->as_type_array_klass()->element_type()); - return TypeAryKlassPtr::make(ptr, etype, k, offset); + return TypeAryKlassPtr::make(ptr, etype, k, offset, not_flat, not_null_free, flat, null_free, atomic); + } else if (k->is_flat_array_klass()) { + ciKlass* eklass = k->as_flat_array_klass()->element_klass(); + const TypeKlassPtr* etype = TypeKlassPtr::make(eklass, interface_handling)->cast_to_exactness(false); + return TypeAryKlassPtr::make(ptr, etype, k, offset, not_flat, not_null_free, flat, null_free, atomic, vm_type); } else { ShouldNotReachHere(); return nullptr; } } -const TypeAryKlassPtr* TypeAryKlassPtr::make(ciKlass* klass, InterfaceHandling interface_handling) { - return TypeAryKlassPtr::make(Constant, klass, 0, interface_handling); +const TypeAryKlassPtr* TypeAryKlassPtr::make(PTR ptr, ciKlass* k, Offset offset, InterfaceHandling interface_handling, bool vm_type) { + bool flat = k->is_flat_array_klass(); + bool null_free = k->as_array_klass()->is_elem_null_free(); + bool atomic = k->as_array_klass()->is_elem_atomic(); + + bool not_inline = k->is_type_array_klass() || !k->as_array_klass()->element_klass()->can_be_inline_klass(false); + bool not_null_free = (ptr == Constant) ? !null_free : not_inline; + bool not_flat = (ptr == Constant) ? !flat : (!UseArrayFlattening || not_inline || + (k->as_array_klass()->element_klass() != nullptr && + k->as_array_klass()->element_klass()->is_inlinetype() && + !k->as_array_klass()->element_klass()->maybe_flat_in_array())); + + return TypeAryKlassPtr::make(ptr, k, offset, interface_handling, not_flat, not_null_free, flat, null_free, atomic, vm_type); +} + +const TypeAryKlassPtr* TypeAryKlassPtr::make(ciKlass* klass, InterfaceHandling interface_handling, bool vm_type) { + return TypeAryKlassPtr::make(Constant, klass, Offset(0), interface_handling, vm_type); +} + +const TypeAryKlassPtr* TypeAryKlassPtr::get_vm_type(bool vm_type) const { + ciKlass* eklass = elem()->is_klassptr()->exact_klass_helper(); + if (elem()->isa_aryklassptr()) { + eklass = exact_klass()->as_obj_array_klass()->element_klass(); + } + ciKlass* array_klass = ciArrayKlass::make(eklass, is_null_free(), is_atomic(), true); + return make(_ptr, array_klass, Offset(0), trust_interfaces, vm_type); } //------------------------------eq--------------------------------------------- @@ -6069,13 +6639,20 @@ bool TypeAryKlassPtr::eq(const Type *t) const { const TypeAryKlassPtr *p = t->is_aryklassptr(); return _elem == p->_elem && // Check array + _flat == p->_flat && + _not_flat == p->_not_flat && + _null_free == p->_null_free && + _not_null_free == p->_not_null_free && + _atomic == p->_atomic && + _vm_type == p->_vm_type && TypeKlassPtr::eq(p); // Check sub-parts } //------------------------------hash------------------------------------------- // Type-specific hashing function. uint TypeAryKlassPtr::hash(void) const { - return (uint)(uintptr_t)_elem + TypeKlassPtr::hash(); + return (uint)(uintptr_t)_elem + TypeKlassPtr::hash() + (uint)(_not_flat ? 43 : 0) + + (uint)(_not_null_free ? 44 : 0) + (uint)(_flat ? 45 : 0) + (uint)(_null_free ? 46 : 0) + (uint)(_atomic ? 47 : 0) + (uint)(_vm_type ? 48 : 0); } //----------------------compute_klass------------------------------------------ @@ -6091,10 +6668,17 @@ ciKlass* TypeAryPtr::compute_klass() const { } // Get element klass - if ((tinst = el->isa_instptr()) != nullptr) { - // Leave k_ary at null. + if (is_flat() && el->is_inlinetypeptr()) { + // Klass is required by TypeAryPtr::flat_layout_helper() and others + if (el->inline_klass() != nullptr) { + // TODO 8350865 We assume atomic if the atomic layout is available, use is_atomic() here + bool atomic = is_null_free() ? el->inline_klass()->has_atomic_layout() : el->inline_klass()->has_nullable_atomic_layout(); + k_ary = ciArrayKlass::make(el->inline_klass(), is_null_free(), atomic, true); + } + } else if ((tinst = el->isa_instptr()) != nullptr) { + // Leave k_ary at nullptr. } else if ((tary = el->isa_aryptr()) != nullptr) { - // Leave k_ary at null. + // Leave k_ary at nullptr. } else if ((el->base() == Type::Top) || (el->base() == Type::Bottom)) { // element type of Bottom occurs from meet of basic type @@ -6142,7 +6726,7 @@ ciKlass* TypeAryPtr::exact_klass_helper() const { if (k == nullptr) { return nullptr; } - k = ciObjArrayKlass::make(k); + k = ciArrayKlass::make(k, is_null_free(), is_atomic(), is_flat() || is_null_free()); return k; } @@ -6162,18 +6746,18 @@ const Type* TypeAryPtr::base_element_type(int& dims) const { //------------------------------add_offset------------------------------------- // Access internals of klass object const TypePtr* TypeAryKlassPtr::add_offset(intptr_t offset) const { - return make(_ptr, elem(), klass(), xadd_offset(offset)); + return make(_ptr, elem(), klass(), xadd_offset(offset), is_not_flat(), is_not_null_free(), _flat, _null_free, _atomic, _vm_type); } const TypeAryKlassPtr* TypeAryKlassPtr::with_offset(intptr_t offset) const { - return make(_ptr, elem(), klass(), offset); + return make(_ptr, elem(), klass(), Offset(offset), is_not_flat(), is_not_null_free(), _flat, _null_free, _atomic, _vm_type); } //------------------------------cast_to_ptr_type------------------------------- const TypeAryKlassPtr* TypeAryKlassPtr::cast_to_ptr_type(PTR ptr) const { assert(_base == AryKlassPtr, "subclass must override cast_to_ptr_type"); if (ptr == _ptr) return this; - return make(ptr, elem(), _klass, _offset); + return make(ptr, elem(), _klass, _offset, is_not_flat(), is_not_null_free(), _flat, _null_free, _atomic, _vm_type); } bool TypeAryKlassPtr::must_be_exact() const { @@ -6181,22 +6765,49 @@ bool TypeAryKlassPtr::must_be_exact() const { if (_elem == Type::TOP ) return false; const TypeKlassPtr* tk = _elem->isa_klassptr(); if (!tk) return true; // a primitive type, like int + // Even though MyValue is final, [LMyValue is only exact if the array + // is (not) null-free due to null-free [LMyValue <: null-able [LMyValue. + // TODO 8350865 If we know that the array can't be null-free, it's allowed to be exact, right? + // If so, we should add '&& !is_not_null_free()' + if (tk->isa_instklassptr() && tk->klass()->is_inlinetype() && !is_null_free()) { + return false; + } return tk->must_be_exact(); } //-----------------------------cast_to_exactness------------------------------- const TypeKlassPtr *TypeAryKlassPtr::cast_to_exactness(bool klass_is_exact) const { - if (must_be_exact()) return this; // cannot clear xk + if (must_be_exact() && !klass_is_exact) return this; // cannot clear xk + if (klass_is_exact == this->klass_is_exact()) { + return this; + } ciKlass* k = _klass; const Type* elem = this->elem(); if (elem->isa_klassptr() && !klass_is_exact) { elem = elem->is_klassptr()->cast_to_exactness(klass_is_exact); } - return make(klass_is_exact ? Constant : NotNull, elem, k, _offset); + bool not_flat = is_not_flat(); + bool not_null_free = is_not_null_free(); + if (_elem->isa_klassptr()) { + if (klass_is_exact || _elem->isa_aryklassptr()) { + assert((!is_null_free() && !is_flat()) || + _elem->is_klassptr()->klass()->is_abstract() || _elem->is_klassptr()->klass()->is_java_lang_Object(), + "null-free (or flat) concrete inline type arrays should always be exact"); + // An array can't be null-free (or flat) if the klass is exact + not_null_free = true; + not_flat = true; + } else { + // Klass is not exact (anymore), re-compute null-free/flat properties + const TypeOopPtr* exact_etype = TypeOopPtr::make_from_klass_unique(_elem->is_instklassptr()->instance_klass()); + bool not_inline = !exact_etype->can_be_inline_type(); + not_null_free = not_inline; + not_flat = !UseArrayFlattening || not_inline || (exact_etype->is_inlinetypeptr() && !exact_etype->inline_klass()->maybe_flat_in_array()); + } + } + return make(klass_is_exact ? Constant : NotNull, elem, k, _offset, not_flat, not_null_free, _flat, _null_free, _atomic, _vm_type); } - //-----------------------------as_instance_type-------------------------------- // Corresponding type for an instance of the given class. // It will be NotNull, and exact if and only if the klass type is exact. @@ -6210,7 +6821,11 @@ const TypeOopPtr* TypeAryKlassPtr::as_instance_type(bool klass_change) const { } else { el = elem(); } - return TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(el, TypeInt::POS), k, xk, 0); + bool null_free = _null_free; + if (null_free && el->isa_ptr()) { + el = el->is_ptr()->join_speculative(TypePtr::NOTNULL); + } + return TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(el, TypeInt::POS, false, is_flat(), is_not_flat(), is_not_null_free(), is_atomic()), k, xk, Offset(0)); } @@ -6247,7 +6862,7 @@ const Type *TypeAryKlassPtr::xmeet( const Type *t ) const { case AnyPtr: { // Meeting to AnyPtrs // Found an AnyPtr type vs self-KlassPtr type const TypePtr *tp = t->is_ptr(); - int offset = meet_offset(tp->offset()); + Offset offset = meet_offset(tp->offset()); PTR ptr = meet_ptr(tp->ptr()); switch (tp->ptr()) { case TopPTR: @@ -6255,7 +6870,7 @@ const Type *TypeAryKlassPtr::xmeet( const Type *t ) const { case Null: if( ptr == Null ) return TypePtr::make(AnyPtr, ptr, offset, tp->speculative(), tp->inline_depth()); case AnyNull: - return make( ptr, _elem, klass(), offset ); + return make(ptr, _elem, klass(), offset, is_not_flat(), is_not_null_free(), is_flat(), is_null_free(), is_atomic(), is_vm_type()); case BotPTR: case NotNull: return TypePtr::make(AnyPtr, ptr, offset, tp->speculative(), tp->inline_depth()); @@ -6288,19 +6903,50 @@ const Type *TypeAryKlassPtr::xmeet( const Type *t ) const { case AryKlassPtr: { // Meet two KlassPtr types const TypeAryKlassPtr *tap = t->is_aryklassptr(); - int off = meet_offset(tap->offset()); + Offset off = meet_offset(tap->offset()); const Type* elem = _elem->meet(tap->_elem); - PTR ptr = meet_ptr(tap->ptr()); ciKlass* res_klass = nullptr; bool res_xk = false; - meet_aryptr(ptr, elem, this, tap, res_klass, res_xk); + bool res_flat = false; + bool res_not_flat = false; + bool res_not_null_free = false; + bool res_atomic = false; + MeetResult res = meet_aryptr(ptr, elem, this, tap, + res_klass, res_xk, res_flat, res_not_flat, res_not_null_free, res_atomic); assert(res_xk == (ptr == Constant), ""); - return make(ptr, elem, res_klass, off); + bool flat = meet_flat(tap->_flat); + bool null_free = meet_null_free(tap->_null_free); + bool atomic = meet_atomic(tap->_atomic); + bool vm_type = _vm_type && tap->_vm_type; + if (res == NOT_SUBTYPE) { + flat = false; + null_free = false; + atomic = false; + vm_type = false; + } else if (res == SUBTYPE) { + if (above_centerline(tap->ptr()) && !above_centerline(this->ptr())) { + flat = _flat; + null_free = _null_free; + atomic = _atomic; + vm_type = _vm_type; + } else if (above_centerline(this->ptr()) && !above_centerline(tap->ptr())) { + flat = tap->_flat; + null_free = tap->_null_free; + atomic = tap->_atomic; + vm_type = tap->_vm_type; + } else if (above_centerline(this->ptr()) && above_centerline(tap->ptr())) { + flat = _flat || tap->_flat; + null_free = _null_free || tap->_null_free; + atomic = _atomic || tap->_atomic; + vm_type = _vm_type || tap->_vm_type; + } + } + return make(ptr, elem, res_klass, off, res_not_flat, res_not_null_free, flat, null_free, atomic, vm_type); } // End of case KlassPtr case InstKlassPtr: { const TypeInstKlassPtr *tp = t->is_instklassptr(); - int offset = meet_offset(tp->offset()); + Offset offset = meet_offset(tp->offset()); PTR ptr = meet_ptr(tp->ptr()); const TypeInterfaces* interfaces = meet_interfaces(tp); const TypeInterfaces* tp_interfaces = tp->_interfaces; @@ -6314,12 +6960,12 @@ const Type *TypeAryKlassPtr::xmeet( const Type *t ) const { // do the same here. if (tp->klass()->equals(ciEnv::current()->Object_klass()) && this_interfaces->contains(tp_interfaces) && !tp->klass_is_exact()) { - return TypeAryKlassPtr::make(ptr, _elem, _klass, offset); + return TypeAryKlassPtr::make(ptr, _elem, _klass, offset, is_not_flat(), is_not_null_free(), is_flat(), is_null_free(), is_atomic(), is_vm_type()); } else { // cannot subclass, so the meet has to fall badly below the centerline ptr = NotNull; interfaces = this_interfaces->intersection_with(tp->_interfaces); - return TypeInstKlassPtr::make(ptr, ciEnv::current()->Object_klass(), interfaces, offset); + return TypeInstKlassPtr::make(ptr, ciEnv::current()->Object_klass(), interfaces, offset, false); } case Constant: case NotNull: @@ -6334,7 +6980,7 @@ const Type *TypeAryKlassPtr::xmeet( const Type *t ) const { if (tp->klass()->equals(ciEnv::current()->Object_klass()) && this_interfaces->contains(tp_interfaces) && !tp->klass_is_exact()) { // that is, my array type is a subtype of 'tp' klass - return make(ptr, _elem, _klass, offset); + return make(ptr, _elem, _klass, offset, is_not_flat(), is_not_null_free(), is_flat(), is_null_free(), is_atomic(), is_vm_type()); } } // The other case cannot happen, since t cannot be a subtype of an array. @@ -6342,7 +6988,7 @@ const Type *TypeAryKlassPtr::xmeet( const Type *t ) const { if (ptr == Constant) ptr = NotNull; interfaces = this_interfaces->intersection_with(tp_interfaces); - return TypeInstKlassPtr::make(ptr, ciEnv::current()->Object_klass(), interfaces, offset); + return TypeInstKlassPtr::make(ptr, ciEnv::current()->Object_klass(), interfaces, offset, false); default: typerr(t); } } @@ -6380,6 +7026,9 @@ template bool TypePtr::is_java_subtype_of_helper_for_array( const TypePtr* other_elem = other_ary->elem()->make_ptr(); const TypePtr* this_elem = this_one->elem()->make_ptr(); if (this_elem != nullptr && other_elem != nullptr) { + if (other->is_null_free() && !this_one->is_null_free()) { + return false; // A nullable array can't be a subtype of a null-free array + } return this_one->is_reference_type(this_elem)->is_java_subtype_of_helper(this_one->is_reference_type(other_elem), this_exact, other_exact); } if (this_elem == nullptr && other_elem == nullptr) { @@ -6472,7 +7121,7 @@ bool TypeAryKlassPtr::maybe_java_subtype_of_helper(const TypeKlassPtr* other, bo //------------------------------xdual------------------------------------------ // Dual: compute field-by-field dual const Type *TypeAryKlassPtr::xdual() const { - return new TypeAryKlassPtr(dual_ptr(), elem()->dual(), klass(), dual_offset()); + return new TypeAryKlassPtr(dual_ptr(), elem()->dual(), klass(), dual_offset(), !is_not_flat(), !is_not_null_free(), dual_flat(), dual_null_free(), dual_atomic(), _vm_type); } // Is there a single ciKlass* that can represent that type? @@ -6482,7 +7131,7 @@ ciKlass* TypeAryKlassPtr::exact_klass_helper() const { if (k == nullptr) { return nullptr; } - k = ciObjArrayKlass::make(k); + k = ciArrayKlass::make(k, is_null_free(), is_atomic(), _vm_type); return k; } @@ -6529,13 +7178,17 @@ void TypeAryKlassPtr::dump2( Dict & d, uint depth, outputStream *st ) const { default: break; } - - if( _offset ) { // Dump offset, if any - if( _offset == OffsetBot ) { st->print("+any"); } - else if( _offset == OffsetTop ) { st->print("+unknown"); } - else { st->print("+%d", _offset); } + if (_flat) st->print(":flat"); + if (_null_free) st->print(":null free"); + if (_atomic) st->print(":atomic"); + if (_vm_type) st->print(":vm_type"); + if (Verbose) { + if (_not_flat) st->print(":not flat"); + if (_not_null_free) st->print(":nullable"); } + _offset.dump2(st); + st->print(" *"); } #endif @@ -6554,24 +7207,50 @@ const Type* TypeAryKlassPtr::base_element_type(int& dims) const { // Convenience common pre-built types. //------------------------------make------------------------------------------- -const TypeFunc *TypeFunc::make( const TypeTuple *domain, const TypeTuple *range ) { - return (TypeFunc*)(new TypeFunc(domain,range))->hashcons(); +const TypeFunc *TypeFunc::make(const TypeTuple *domain_sig, const TypeTuple* domain_cc, + const TypeTuple *range_sig, const TypeTuple *range_cc) { + return (TypeFunc*)(new TypeFunc(domain_sig, domain_cc, range_sig, range_cc))->hashcons(); +} + +const TypeFunc *TypeFunc::make(const TypeTuple *domain, const TypeTuple *range) { + return make(domain, domain, range, range); +} + +//------------------------------osr_domain----------------------------- +const TypeTuple* osr_domain() { + const Type **fields = TypeTuple::fields(2); + fields[TypeFunc::Parms+0] = TypeRawPtr::BOTTOM; // address of osr buffer + return TypeTuple::make(TypeFunc::Parms+1, fields); } //------------------------------make------------------------------------------- -const TypeFunc *TypeFunc::make(ciMethod* method) { +const TypeFunc* TypeFunc::make(ciMethod* method, bool is_osr_compilation) { Compile* C = Compile::current(); - const TypeFunc* tf = C->last_tf(method); // check cache - if (tf != nullptr) return tf; // The hit rate here is almost 50%. - const TypeTuple *domain; - if (method->is_static()) { - domain = TypeTuple::make_domain(nullptr, method->signature(), ignore_interfaces); - } else { - domain = TypeTuple::make_domain(method->holder(), method->signature(), ignore_interfaces); + const TypeFunc* tf = nullptr; + if (!is_osr_compilation) { + tf = C->last_tf(method); // check cache + if (tf != nullptr) return tf; // The hit rate here is almost 50%. + } + // Inline types are not passed/returned by reference, instead each field of + // the inline type is passed/returned as an argument. We maintain two views of + // the argument/return list here: one based on the signature (with an inline + // type argument/return as a single slot), one based on the actual calling + // convention (with an inline type argument/return as a list of its fields). + bool has_scalar_args = method->has_scalarized_args() && !is_osr_compilation; + // Fall back to the non-scalarized calling convention when compiling a call via a mismatching method + if (method != C->method() && method->get_Method()->mismatch()) { + has_scalar_args = false; + } + const TypeTuple* domain_sig = is_osr_compilation ? osr_domain() : TypeTuple::make_domain(method, ignore_interfaces, false); + const TypeTuple* domain_cc = has_scalar_args ? TypeTuple::make_domain(method, ignore_interfaces, true) : domain_sig; + ciSignature* sig = method->signature(); + bool has_scalar_ret = !method->is_native() && sig->return_type()->is_inlinetype() && sig->return_type()->as_inline_klass()->can_be_returned_as_fields(); + const TypeTuple* range_sig = TypeTuple::make_range(sig, ignore_interfaces, false); + const TypeTuple* range_cc = has_scalar_ret ? TypeTuple::make_range(sig, ignore_interfaces, true) : range_sig; + tf = TypeFunc::make(domain_sig, domain_cc, range_sig, range_cc); + if (!is_osr_compilation) { + C->set_last_tf(method, tf); // fill cache } - const TypeTuple *range = TypeTuple::make_range(method->signature(), ignore_interfaces); - tf = TypeFunc::make(domain, range); - C->set_last_tf(method, tf); // fill cache return tf; } @@ -6606,29 +7285,31 @@ const Type *TypeFunc::xdual() const { // Structural equality check for Type representations bool TypeFunc::eq( const Type *t ) const { const TypeFunc *a = (const TypeFunc*)t; - return _domain == a->_domain && - _range == a->_range; + return _domain_sig == a->_domain_sig && + _domain_cc == a->_domain_cc && + _range_sig == a->_range_sig && + _range_cc == a->_range_cc; } //------------------------------hash------------------------------------------- // Type-specific hashing function. uint TypeFunc::hash(void) const { - return (uint)(uintptr_t)_domain + (uint)(uintptr_t)_range; + return (uint)(intptr_t)_domain_sig + (uint)(intptr_t)_domain_cc + (uint)(intptr_t)_range_sig + (uint)(intptr_t)_range_cc; } //------------------------------dump2------------------------------------------ // Dump Function Type #ifndef PRODUCT void TypeFunc::dump2( Dict &d, uint depth, outputStream *st ) const { - if( _range->cnt() <= Parms ) + if( _range_sig->cnt() <= Parms ) st->print("void"); else { uint i; - for (i = Parms; i < _range->cnt()-1; i++) { - _range->field_at(i)->dump2(d,depth,st); + for (i = Parms; i < _range_sig->cnt()-1; i++) { + _range_sig->field_at(i)->dump2(d,depth,st); st->print("/"); } - _range->field_at(i)->dump2(d,depth,st); + _range_sig->field_at(i)->dump2(d,depth,st); } st->print(" "); st->print("( "); @@ -6637,11 +7318,11 @@ void TypeFunc::dump2( Dict &d, uint depth, outputStream *st ) const { return; } d.Insert((void*)this,(void*)this); // Stop recursion - if (Parms < _domain->cnt()) - _domain->field_at(Parms)->dump2(d,depth-1,st); - for (uint i = Parms+1; i < _domain->cnt(); i++) { + if (Parms < _domain_sig->cnt()) + _domain_sig->field_at(Parms)->dump2(d,depth-1,st); + for (uint i = Parms+1; i < _domain_sig->cnt(); i++) { st->print(", "); - _domain->field_at(i)->dump2(d,depth-1,st); + _domain_sig->field_at(i)->dump2(d,depth-1,st); } st->print(" )"); } @@ -6661,8 +7342,8 @@ bool TypeFunc::empty(void) const { BasicType TypeFunc::return_type() const{ - if (range()->cnt() == TypeFunc::Parms) { + if (range_sig()->cnt() == TypeFunc::Parms) { return T_VOID; } - return range()->field_at(TypeFunc::Parms)->basic_type(); + return range_sig()->field_at(TypeFunc::Parms)->basic_type(); } diff --git a/src/hotspot/share/opto/type.hpp b/src/hotspot/share/opto/type.hpp index c61c2a64278..8c5c1b6adf2 100644 --- a/src/hotspot/share/opto/type.hpp +++ b/src/hotspot/share/opto/type.hpp @@ -25,10 +25,12 @@ #ifndef SHARE_OPTO_TYPE_HPP #define SHARE_OPTO_TYPE_HPP +#include "ci/ciInlineKlass.hpp" #include "opto/adlcVMDeps.hpp" #include "opto/compile.hpp" #include "opto/rangeinference.hpp" #include "runtime/handles.hpp" +#include "runtime/sharedRuntime.hpp" // Portions of code courtesy of Clifford Click @@ -144,6 +146,30 @@ class Type { OffsetBot = -2000000001 // any possible offset }; + class Offset { + private: + int _offset; + + public: + explicit Offset(int offset) : _offset(offset) {} + + const Offset meet(const Offset other) const; + const Offset dual() const; + const Offset add(intptr_t offset) const; + bool operator==(const Offset& other) const { + return _offset == other._offset; + } + bool operator!=(const Offset& other) const { + return _offset != other._offset; + } + int get() const { return _offset; } + + void dump2(outputStream *st) const; + + static const Offset top; + static const Offset bottom; + }; + // Min and max WIDEN values. enum WIDEN { WidenMin = 0, @@ -345,6 +371,9 @@ class Type { virtual bool is_finite() const; // Has a finite value virtual bool is_nan() const; // Is not a number (NaN) + bool is_inlinetypeptr() const; + virtual ciInlineKlass* inline_klass() const; + // Returns this ptr type or the equivalent ptr type for this compressed pointer. const TypePtr* make_ptr() const; @@ -952,8 +981,8 @@ class TypeTuple : public Type { } static const TypeTuple *make( uint cnt, const Type **fields ); - static const TypeTuple *make_range(ciSignature *sig, InterfaceHandling interface_handling = ignore_interfaces); - static const TypeTuple *make_domain(ciInstanceKlass* recv, ciSignature *sig, InterfaceHandling interface_handling); + static const TypeTuple *make_range(ciSignature* sig, InterfaceHandling interface_handling = ignore_interfaces, bool ret_vt_fields = false); + static const TypeTuple *make_domain(ciMethod* method, InterfaceHandling interface_handling, bool vt_fields_as_args = false); // Subroutine call type with space allocated for argument types // Memory for Control, I_O, Memory, FramePtr, and ReturnAdr is allocated implicitly @@ -982,8 +1011,8 @@ class TypeTuple : public Type { //------------------------------TypeAry---------------------------------------- // Class of Array Types class TypeAry : public Type { - TypeAry(const Type* elem, const TypeInt* size, bool stable) : Type(Array), - _elem(elem), _size(size), _stable(stable) {} + TypeAry(const Type* elem, const TypeInt* size, bool stable, bool flat, bool not_flat, bool not_null_free, bool atomic) : Type(Array), + _elem(elem), _size(size), _stable(stable), _flat(flat), _not_flat(not_flat), _not_null_free(not_null_free), _atomic(atomic) {} public: virtual bool eq( const Type *t ) const; virtual uint hash() const; // Type specific hashing @@ -994,10 +1023,18 @@ class TypeAry : public Type { const Type *_elem; // Element type of array const TypeInt *_size; // Elements in array const bool _stable; // Are elements @Stable? + + // Inline type array properties + const bool _flat; // Array is flat + const bool _not_flat; // Array is never flat + const bool _not_null_free; // Array is never null-free + const bool _atomic; // Array is atomic + friend class TypeAryPtr; public: - static const TypeAry* make(const Type* elem, const TypeInt* size, bool stable = false); + static const TypeAry* make(const Type* elem, const TypeInt* size, bool stable = false, + bool flat = false, bool not_flat = false, bool not_null_free = false, bool atomic = false); virtual const Type *xmeet( const Type *t ) const; virtual const Type *xdual() const; // Compute dual right now. @@ -1144,7 +1181,7 @@ class TypePtr : public Type { public: enum PTR { TopPTR, AnyNull, Constant, Null, NotNull, BotPTR, lastPTR }; protected: - TypePtr(TYPES t, PTR ptr, int offset, + TypePtr(TYPES t, PTR ptr, Offset offset, const TypePtr* speculative = nullptr, int inline_depth = InlineDepthBottom) : Type(t), _speculative(speculative), _inline_depth(inline_depth), _offset(offset), @@ -1200,10 +1237,13 @@ class TypePtr : public Type { LCA }; template static TypePtr::MeetResult meet_instptr(PTR& ptr, const TypeInterfaces*& interfaces, const T* this_type, - const T* other_type, ciKlass*& res_klass, bool& res_xk); + const T* other_type, ciKlass*& res_klass, bool& res_xk, bool& res_flat_array); + private: + template static bool is_meet_subtype_of(const T* sub_type, const T* super_type); + protected: template static MeetResult meet_aryptr(PTR& ptr, const Type*& elem, const T* this_ary, const T* other_ary, - ciKlass*& res_klass, bool& res_xk); + ciKlass*& res_klass, bool& res_xk, bool &res_flat, bool &res_not_flat, bool &res_not_null_free, bool &res_atomic); template static bool is_java_subtype_of_helper_for_instance(const T1* this_one, const T2* other, bool this_exact, bool other_exact); template static bool is_same_java_type_as_helper_for_instance(const T1* this_one, const T2* other); @@ -1214,13 +1254,13 @@ class TypePtr : public Type { template static bool is_meet_subtype_of_helper_for_instance(const T1* this_one, const T2* other, bool this_xk, bool other_xk); template static bool is_meet_subtype_of_helper_for_array(const T1* this_one, const T2* other, bool this_xk, bool other_xk); public: - const int _offset; // Offset into oop, with TOP & BOT + const Offset _offset; // Offset into oop, with TOP & BOT const PTR _ptr; // Pointer equivalence class - int offset() const { return _offset; } + int offset() const { return _offset.get(); } PTR ptr() const { return _ptr; } - static const TypePtr *make(TYPES t, PTR ptr, int offset, + static const TypePtr* make(TYPES t, PTR ptr, Offset offset, const TypePtr* speculative = nullptr, int inline_depth = InlineDepthBottom); @@ -1229,9 +1269,10 @@ class TypePtr : public Type { virtual intptr_t get_con() const; - int xadd_offset( intptr_t offset ) const; + Type::Offset xadd_offset(intptr_t offset) const; virtual const TypePtr* add_offset(intptr_t offset) const; virtual const TypePtr* with_offset(intptr_t offset) const; + virtual int flat_offset() const { return offset(); } virtual bool eq(const Type *t) const; virtual uint hash() const; // Type specific hashing @@ -1239,8 +1280,8 @@ class TypePtr : public Type { virtual bool empty(void) const; // TRUE if type is vacuous virtual const Type *xmeet( const Type *t ) const; virtual const Type *xmeet_helper( const Type *t ) const; - int meet_offset( int offset ) const; - int dual_offset( ) const; + Offset meet_offset(int offset) const; + Offset dual_offset() const; virtual const Type *xdual() const; // Compute dual right now. // meet, dual and join over pointer equivalence sets @@ -1268,6 +1309,15 @@ class TypePtr : public Type { virtual bool maybe_null() const { return meet_ptr(Null) == ptr(); } + virtual bool can_be_inline_type() const { return false; } + virtual bool flat_in_array() const { return false; } + virtual bool not_flat_in_array() const { return true; } + virtual bool is_flat() const { return false; } + virtual bool is_not_flat() const { return false; } + virtual bool is_null_free() const { return false; } + virtual bool is_not_null_free() const { return false; } + virtual bool is_atomic() const { return false; } + // Tests for relation to centerline of type lattice: static bool above_centerline(PTR ptr) { return (ptr <= AnyNull); } static bool below_centerline(PTR ptr) { return (ptr >= NotNull); } @@ -1285,7 +1335,7 @@ class TypePtr : public Type { // include the stack pointer, top of heap, card-marking area, handles, etc. class TypeRawPtr : public TypePtr { protected: - TypeRawPtr( PTR ptr, address bits ) : TypePtr(RawPtr,ptr,0), _bits(bits){} + TypeRawPtr(PTR ptr, address bits) : TypePtr(RawPtr,ptr,Offset(0)), _bits(bits){} public: virtual bool eq( const Type *t ) const; virtual uint hash() const; // Type specific hashing @@ -1321,7 +1371,7 @@ class TypeOopPtr : public TypePtr { friend class TypeInstPtr; friend class TypeAryPtr; protected: - TypeOopPtr(TYPES t, PTR ptr, ciKlass* k, const TypeInterfaces* interfaces, bool xk, ciObject* o, int offset, int instance_id, + TypeOopPtr(TYPES t, PTR ptr, ciKlass* k, const TypeInterfaces* interfaces, bool xk, ciObject* o, Offset offset, Offset field_offset, int instance_id, const TypePtr* speculative, int inline_depth); public: virtual bool eq( const Type *t ) const; @@ -1362,7 +1412,7 @@ class TypeOopPtr : public TypePtr { virtual const Type *filter_helper(const Type *kills, bool include_speculative) const; virtual ciKlass* exact_klass_helper() const { return nullptr; } - virtual ciKlass* klass() const { return _klass; } + virtual ciKlass* klass() const { return _klass; } public: @@ -1409,7 +1459,7 @@ class TypeOopPtr : public TypePtr { bool require_constant = false); // Make a generic (unclassed) pointer to an oop. - static const TypeOopPtr* make(PTR ptr, int offset, int instance_id, + static const TypeOopPtr* make(PTR ptr, Offset offset, int instance_id, const TypePtr* speculative = nullptr, int inline_depth = InlineDepthBottom); @@ -1428,7 +1478,10 @@ class TypeOopPtr : public TypePtr { bool is_ptr_to_boxed_value() const { return _is_ptr_to_boxed_value; } bool is_known_instance() const { return _instance_id > 0; } int instance_id() const { return _instance_id; } - bool is_known_instance_field() const { return is_known_instance() && _offset >= 0; } + bool is_known_instance_field() const { return is_known_instance() && _offset.get() >= 0; } + + virtual bool can_be_inline_type() const { return (_klass == nullptr || _klass->can_be_inline_klass(_klass_is_exact)); } + virtual bool can_be_inline_array() const { ShouldNotReachHere(); return false; } virtual intptr_t get_con() const; @@ -1491,11 +1544,12 @@ class TypeOopPtr : public TypePtr { // Class of Java object pointers, pointing either to non-array Java instances // or to a Klass* (including array klasses). class TypeInstPtr : public TypeOopPtr { - TypeInstPtr(PTR ptr, ciKlass* k, const TypeInterfaces* interfaces, bool xk, ciObject* o, int off, int instance_id, - const TypePtr* speculative, int inline_depth); + TypeInstPtr(PTR ptr, ciKlass* k, const TypeInterfaces* interfaces, bool xk, ciObject* o, Offset offset, + bool flat_in_array, int instance_id, const TypePtr* speculative, + int inline_depth); virtual bool eq( const Type *t ) const; virtual uint hash() const; // Type specific hashing - + bool _flat_in_array; // Type is flat in arrays ciKlass* exact_klass_helper() const; public: @@ -1514,41 +1568,43 @@ class TypeInstPtr : public TypeOopPtr { static const TypeInstPtr *make(ciObject* o) { ciKlass* k = o->klass(); const TypeInterfaces* interfaces = TypePtr::interfaces(k, true, false, false, ignore_interfaces); - return make(TypePtr::Constant, k, interfaces, true, o, 0, InstanceBot); + return make(TypePtr::Constant, k, interfaces, true, o, Offset(0)); } // Make a pointer to a constant oop with offset. - static const TypeInstPtr *make(ciObject* o, int offset) { + static const TypeInstPtr *make(ciObject* o, Offset offset) { ciKlass* k = o->klass(); const TypeInterfaces* interfaces = TypePtr::interfaces(k, true, false, false, ignore_interfaces); - return make(TypePtr::Constant, k, interfaces, true, o, offset, InstanceBot); + return make(TypePtr::Constant, k, interfaces, true, o, offset); } // Make a pointer to some value of type klass. static const TypeInstPtr *make(PTR ptr, ciKlass* klass, InterfaceHandling interface_handling = ignore_interfaces) { const TypeInterfaces* interfaces = TypePtr::interfaces(klass, true, true, false, interface_handling); - return make(ptr, klass, interfaces, false, nullptr, 0, InstanceBot); + return make(ptr, klass, interfaces, false, nullptr, Offset(0)); } // Make a pointer to some non-polymorphic value of exactly type klass. static const TypeInstPtr *make_exact(PTR ptr, ciKlass* klass) { const TypeInterfaces* interfaces = TypePtr::interfaces(klass, true, false, false, ignore_interfaces); - return make(ptr, klass, interfaces, true, nullptr, 0, InstanceBot); + return make(ptr, klass, interfaces, true, nullptr, Offset(0)); } // Make a pointer to some value of type klass with offset. - static const TypeInstPtr *make(PTR ptr, ciKlass* klass, int offset) { + static const TypeInstPtr *make(PTR ptr, ciKlass* klass, Offset offset) { const TypeInterfaces* interfaces = TypePtr::interfaces(klass, true, false, false, ignore_interfaces); - return make(ptr, klass, interfaces, false, nullptr, offset, InstanceBot); + return make(ptr, klass, interfaces, false, nullptr, offset); } - static const TypeInstPtr *make(PTR ptr, ciKlass* k, const TypeInterfaces* interfaces, bool xk, ciObject* o, int offset, + // Make a pointer to an oop. + static const TypeInstPtr* make(PTR ptr, ciKlass* k, const TypeInterfaces* interfaces, bool xk, ciObject* o, Offset offset, + bool flat_in_array = false, int instance_id = InstanceBot, const TypePtr* speculative = nullptr, int inline_depth = InlineDepthBottom); - static const TypeInstPtr *make(PTR ptr, ciKlass* k, bool xk, ciObject* o, int offset, int instance_id = InstanceBot) { + static const TypeInstPtr *make(PTR ptr, ciKlass* k, bool xk, ciObject* o, Offset offset, int instance_id = InstanceBot) { const TypeInterfaces* interfaces = TypePtr::interfaces(k, true, false, false, ignore_interfaces); - return make(ptr, k, interfaces, xk, o, offset, instance_id); + return make(ptr, k, interfaces, xk, o, offset, false, instance_id); } /** Create constant type for a constant boxed value */ @@ -1574,6 +1630,10 @@ class TypeInstPtr : public TypeOopPtr { virtual const TypePtr* with_inline_depth(int depth) const; virtual const TypePtr* with_instance_id(int instance_id) const; + virtual const TypeInstPtr* cast_to_flat_in_array() const; + virtual bool flat_in_array() const { return _flat_in_array; } + virtual bool not_flat_in_array() const { return !can_be_inline_type() || (_klass->is_inlinetype() && !flat_in_array()); } + // the core of the computation of the meet of 2 types virtual const Type *xmeet_helper(const Type *t) const; virtual const TypeInstPtr *xmeet_unloaded(const TypeInstPtr *tinst, const TypeInterfaces* interfaces) const; @@ -1581,6 +1641,8 @@ class TypeInstPtr : public TypeOopPtr { const TypeKlassPtr* as_klass_type(bool try_for_exact = false) const; + virtual bool can_be_inline_array() const; + // Convenience common pre-built types. static const TypeInstPtr *NOTNULL; static const TypeInstPtr *BOTTOM; @@ -1605,21 +1667,23 @@ class TypeInstPtr : public TypeOopPtr { class TypeAryPtr : public TypeOopPtr { friend class Type; friend class TypePtr; + friend class TypeInstPtr; friend class TypeInterfaces; - TypeAryPtr( PTR ptr, ciObject* o, const TypeAry *ary, ciKlass* k, bool xk, - int offset, int instance_id, bool is_autobox_cache, - const TypePtr* speculative, int inline_depth) - : TypeOopPtr(AryPtr,ptr,k,_array_interfaces,xk,o,offset, instance_id, speculative, inline_depth), + TypeAryPtr(PTR ptr, ciObject* o, const TypeAry *ary, ciKlass* k, bool xk, + Offset offset, Offset field_offset, int instance_id, bool is_autobox_cache, + const TypePtr* speculative, int inline_depth) + : TypeOopPtr(AryPtr, ptr, k, _array_interfaces, xk, o, offset, field_offset, instance_id, speculative, inline_depth), _ary(ary), - _is_autobox_cache(is_autobox_cache) + _is_autobox_cache(is_autobox_cache), + _field_offset(field_offset) { int dummy; bool top_or_bottom = (base_element_type(dummy) == Type::TOP || base_element_type(dummy) == Type::BOTTOM); if (UseCompressedOops && (elem()->make_oopptr() != nullptr && !top_or_bottom) && - _offset != 0 && _offset != arrayOopDesc::length_offset_in_bytes() && - _offset != arrayOopDesc::klass_offset_in_bytes()) { + _offset.get() != 0 && _offset.get() != arrayOopDesc::length_offset_in_bytes() && + _offset.get() != arrayOopDesc::klass_offset_in_bytes()) { _is_ptr_to_narrowoop = true; } @@ -1628,6 +1692,12 @@ class TypeAryPtr : public TypeOopPtr { virtual uint hash() const; // Type specific hashing const TypeAry *_ary; // Array we point into const bool _is_autobox_cache; + // For flat inline type arrays, each field of the inline type in + // the array has its own memory slice so we need to keep track of + // which field is accessed + const Offset _field_offset; + Offset meet_field_offset(const Type::Offset offset) const; + Offset dual_field_offset() const; ciKlass* compute_klass() const; @@ -1655,17 +1725,27 @@ class TypeAryPtr : public TypeOopPtr { const TypeInt* size() const { return _ary->_size; } bool is_stable() const { return _ary->_stable; } + // Inline type array properties + bool is_flat() const { return _ary->_flat; } + bool is_not_flat() const { return _ary->_not_flat; } + bool is_null_free() const { return _ary->_elem->make_ptr() != nullptr && (_ary->_elem->make_ptr()->ptr() == NotNull || _ary->_elem->make_ptr()->ptr() == AnyNull); } + bool is_not_null_free() const { return _ary->_not_null_free; } + bool is_atomic() const { return _ary->_atomic; } + bool is_autobox_cache() const { return _is_autobox_cache; } - static const TypeAryPtr *make(PTR ptr, const TypeAry *ary, ciKlass* k, bool xk, int offset, + static const TypeAryPtr* make(PTR ptr, const TypeAry *ary, ciKlass* k, bool xk, Offset offset, + Offset field_offset = Offset::bottom, int instance_id = InstanceBot, const TypePtr* speculative = nullptr, int inline_depth = InlineDepthBottom); // Constant pointer to array - static const TypeAryPtr *make(PTR ptr, ciObject* o, const TypeAry *ary, ciKlass* k, bool xk, int offset, + static const TypeAryPtr* make(PTR ptr, ciObject* o, const TypeAry *ary, ciKlass* k, bool xk, Offset offset, + Offset field_offset = Offset::bottom, int instance_id = InstanceBot, const TypePtr* speculative = nullptr, - int inline_depth = InlineDepthBottom, bool is_autobox_cache = false); + int inline_depth = InlineDepthBottom, + bool is_autobox_cache = false); // Return a 'ptr' version of this type virtual const TypeAryPtr* cast_to_ptr_type(PTR ptr) const; @@ -1684,6 +1764,7 @@ class TypeAryPtr : public TypeOopPtr { // Speculative type helper methods. virtual const TypeAryPtr* remove_speculative() const; + virtual const Type* cleanup_speculative() const; virtual const TypePtr* with_inline_depth(int depth) const; virtual const TypePtr* with_instance_id(int instance_id) const; @@ -1691,26 +1772,44 @@ class TypeAryPtr : public TypeOopPtr { virtual const Type *xmeet_helper(const Type *t) const; virtual const Type *xdual() const; // Compute dual right now. + // Inline type array properties + const TypeAryPtr* cast_to_not_flat(bool not_flat = true) const; + const TypeAryPtr* cast_to_not_null_free(bool not_null_free = true) const; + const TypeAryPtr* update_properties(const TypeAryPtr* new_type) const; + jint flat_layout_helper() const; + int flat_elem_size() const; + int flat_log_elem_size() const; + const TypeAryPtr* cast_to_stable(bool stable, int stable_dimension = 1) const; int stable_dimension() const; const TypeAryPtr* cast_to_autobox_cache() const; - static jint max_array_length(BasicType etype) ; + static jint max_array_length(BasicType etype); + + int flat_offset() const; + const Offset field_offset() const { return _field_offset; } + const TypeAryPtr* with_field_offset(int offset) const; + const TypePtr* add_field_offset_and_offset(intptr_t offset) const; + + virtual bool can_be_inline_type() const { return false; } virtual const TypeKlassPtr* as_klass_type(bool try_for_exact = false) const; + virtual bool can_be_inline_array() const; + // Convenience common pre-built types. static const TypeAryPtr* BOTTOM; - static const TypeAryPtr* RANGE; - static const TypeAryPtr* OOPS; - static const TypeAryPtr* NARROWOOPS; - static const TypeAryPtr* BYTES; - static const TypeAryPtr* SHORTS; - static const TypeAryPtr* CHARS; - static const TypeAryPtr* INTS; - static const TypeAryPtr* LONGS; - static const TypeAryPtr* FLOATS; - static const TypeAryPtr* DOUBLES; + static const TypeAryPtr *RANGE; + static const TypeAryPtr *OOPS; + static const TypeAryPtr *NARROWOOPS; + static const TypeAryPtr *BYTES; + static const TypeAryPtr *SHORTS; + static const TypeAryPtr *CHARS; + static const TypeAryPtr *INTS; + static const TypeAryPtr *LONGS; + static const TypeAryPtr *FLOATS; + static const TypeAryPtr *DOUBLES; + static const TypeAryPtr *INLINES; // selects one of the above: static const TypeAryPtr *get_array_body_type(BasicType elem) { assert((uint)elem <= T_CONFLICT && _array_body_type[elem] != nullptr, "bad elem type"); @@ -1729,7 +1828,7 @@ class TypeAryPtr : public TypeOopPtr { // Some kind of metadata, either Method*, MethodData* or CPCacheOop class TypeMetadataPtr : public TypePtr { protected: - TypeMetadataPtr(PTR ptr, ciMetadata* metadata, int offset); + TypeMetadataPtr(PTR ptr, ciMetadata* metadata, Offset offset); // Do not allow interface-vs.-noninterface joins to collapse to top. virtual const Type *filter_helper(const Type *kills, bool include_speculative) const; public: @@ -1741,7 +1840,7 @@ class TypeMetadataPtr : public TypePtr { ciMetadata* _metadata; public: - static const TypeMetadataPtr* make(PTR ptr, ciMetadata* m, int offset); + static const TypeMetadataPtr* make(PTR ptr, ciMetadata* m, Offset offset); static const TypeMetadataPtr* make(ciMethod* m); static const TypeMetadataPtr* make(ciMethodData* m); @@ -1772,7 +1871,7 @@ class TypeKlassPtr : public TypePtr { friend class TypeAryKlassPtr; friend class TypePtr; protected: - TypeKlassPtr(TYPES t, PTR ptr, ciKlass* klass, const TypeInterfaces* interfaces, int offset); + TypeKlassPtr(TYPES t, PTR ptr, ciKlass* klass, const TypeInterfaces* interfaces, Offset offset); virtual const Type *filter_helper(const Type *kills, bool include_speculative) const; @@ -1811,7 +1910,7 @@ class TypeKlassPtr : public TypePtr { virtual bool klass_is_exact() const { return _ptr == Constant; } static const TypeKlassPtr* make(ciKlass* klass, InterfaceHandling interface_handling = ignore_interfaces); - static const TypeKlassPtr *make(PTR ptr, ciKlass* klass, int offset, InterfaceHandling interface_handling = ignore_interfaces); + static const TypeKlassPtr *make(PTR ptr, ciKlass* klass, Offset offset, InterfaceHandling interface_handling = ignore_interfaces); virtual bool is_loaded() const { return _klass->is_loaded(); } @@ -1830,6 +1929,12 @@ class TypeKlassPtr : public TypePtr { virtual const TypeKlassPtr* with_offset(intptr_t offset) const { ShouldNotReachHere(); return nullptr; } + virtual bool can_be_inline_array() const { ShouldNotReachHere(); return false; } + + virtual bool not_flat_in_array_inexact() const { + return true; + } + virtual const TypeKlassPtr* try_improve() const { return this; } #ifndef PRODUCT @@ -1864,13 +1969,15 @@ class TypeKlassPtr : public TypePtr { // Instance klass pointer, mirrors TypeInstPtr class TypeInstKlassPtr : public TypeKlassPtr { - TypeInstKlassPtr(PTR ptr, ciKlass* klass, const TypeInterfaces* interfaces, int offset) - : TypeKlassPtr(InstKlassPtr, ptr, klass, interfaces, offset) { + TypeInstKlassPtr(PTR ptr, ciKlass* klass, const TypeInterfaces* interfaces, Offset offset, bool flat_in_array) + : TypeKlassPtr(InstKlassPtr, ptr, klass, interfaces, offset), _flat_in_array(flat_in_array) { assert(klass->is_instance_klass() && (!klass->is_loaded() || !klass->is_interface()), ""); } virtual bool must_be_exact() const; + const bool _flat_in_array; // Type is flat in arrays + public: // Instance klass ignoring any interface ciInstanceKlass* instance_klass() const { @@ -1884,13 +1991,15 @@ class TypeInstKlassPtr : public TypeKlassPtr { bool is_java_subtype_of_helper(const TypeKlassPtr* other, bool this_exact, bool other_exact) const; bool maybe_java_subtype_of_helper(const TypeKlassPtr* other, bool this_exact, bool other_exact) const; + virtual bool can_be_inline_type() const { return (_klass == nullptr || _klass->can_be_inline_klass(klass_is_exact())); } + static const TypeInstKlassPtr *make(ciKlass* k, InterfaceHandling interface_handling) { const TypeInterfaces* interfaces = TypePtr::interfaces(k, true, true, false, interface_handling); - return make(TypePtr::Constant, k, interfaces, 0); + return make(TypePtr::Constant, k, interfaces, Offset(0)); } - static const TypeInstKlassPtr* make(PTR ptr, ciKlass* k, const TypeInterfaces* interfaces, int offset); + static const TypeInstKlassPtr* make(PTR ptr, ciKlass* k, const TypeInterfaces* interfaces, Offset offset, bool flat_in_array = false); - static const TypeInstKlassPtr* make(PTR ptr, ciKlass* k, int offset) { + static const TypeInstKlassPtr* make(PTR ptr, ciKlass* k, Offset offset) { const TypeInterfaces* interfaces = TypePtr::interfaces(k, true, false, false, ignore_interfaces); return make(ptr, k, interfaces, offset); } @@ -1911,6 +2020,27 @@ class TypeInstKlassPtr : public TypeKlassPtr { virtual const TypeKlassPtr* try_improve() const; + virtual bool flat_in_array() const { return _flat_in_array; } + + // Checks if this klass pointer is not flat in array by also considering exactness information. + virtual bool not_flat_in_array() const { + return !_klass->can_be_inline_klass(klass_is_exact()) || (_klass->is_inlinetype() && !flat_in_array()); + } + + // not_flat_in_array() version that assumes that the klass is inexact. This is used for sub type checks where the + // super klass is always an exact klass constant (and thus possibly known to be not flat in array), while a sub + // klass could very well be flat in array: + // + // MyValue <: Object + // flat in array not flat in array + // + // Thus, this version checks if we know that the klass is not flat in array even if it's not exact. + virtual bool not_flat_in_array_inexact() const { + return !_klass->can_be_inline_klass() || (_klass->is_inlinetype() && !flat_in_array()); + } + + virtual bool can_be_inline_array() const; + // Convenience common pre-built types. static const TypeInstKlassPtr* OBJECT; // Not-null object klass or below static const TypeInstKlassPtr* OBJECT_OR_NULL; // Maybe-null version of same @@ -1925,11 +2055,17 @@ class TypeAryKlassPtr : public TypeKlassPtr { friend class TypePtr; const Type *_elem; + const bool _not_flat; // Array is never flat + const bool _not_null_free; // Array is never null-free + const bool _flat; + const bool _null_free; + const bool _atomic; + const bool _vm_type; static const TypeInterfaces* _array_interfaces; - TypeAryKlassPtr(PTR ptr, const Type *elem, ciKlass* klass, int offset) - : TypeKlassPtr(AryKlassPtr, ptr, klass, _array_interfaces, offset), _elem(elem) { - assert(klass == nullptr || klass->is_type_array_klass() || !klass->as_obj_array_klass()->base_element_klass()->is_interface(), ""); + TypeAryKlassPtr(PTR ptr, const Type *elem, ciKlass* klass, Offset offset, bool not_flat, int not_null_free, bool flat, bool null_free, bool atomic, bool vm_type) + : TypeKlassPtr(AryKlassPtr, ptr, klass, _array_interfaces, offset), _elem(elem), _not_flat(not_flat), _not_null_free(not_null_free), _flat(flat), _null_free(null_free), _atomic(atomic), _vm_type(vm_type) { + assert(klass == nullptr || klass->is_type_array_klass() || klass->is_flat_array_klass() || !klass->as_obj_array_klass()->base_element_klass()->is_interface(), ""); } virtual ciKlass* exact_klass_helper() const; @@ -1938,12 +2074,36 @@ class TypeAryKlassPtr : public TypeKlassPtr { virtual bool must_be_exact() const; + bool dual_flat() const { + return _flat; + } + + bool meet_flat(bool other) const { + return _flat && other; + } + + bool dual_null_free() const { + return _null_free; + } + + bool meet_null_free(bool other) const { + return _null_free && other; + } + + bool dual_atomic() const { + return _atomic; + } + + bool meet_atomic(bool other) const { + return _atomic && other; + } + public: // returns base element type, an instance klass (and not interface) for object arrays const Type* base_element_type(int& dims) const; - static const TypeAryKlassPtr *make(PTR ptr, ciKlass* k, int offset, InterfaceHandling interface_handling); + static const TypeAryKlassPtr* make(PTR ptr, ciKlass* k, Offset offset, InterfaceHandling interface_handling, bool not_flat, bool not_null_free, bool flat, bool null_free, bool atomic, bool vm_type = false); bool is_same_java_type_as_helper(const TypeKlassPtr* other) const; bool is_java_subtype_of_helper(const TypeKlassPtr* other, bool this_exact, bool other_exact) const; @@ -1951,8 +2111,11 @@ class TypeAryKlassPtr : public TypeKlassPtr { bool is_loaded() const { return (_elem->isa_klassptr() ? _elem->is_klassptr()->is_loaded() : true); } - static const TypeAryKlassPtr *make(PTR ptr, const Type *elem, ciKlass* k, int offset); - static const TypeAryKlassPtr* make(ciKlass* klass, InterfaceHandling interface_handling); + static const TypeAryKlassPtr* make(PTR ptr, const Type* elem, ciKlass* k, Offset offset, bool not_flat, bool not_null_free, bool flat, bool null_free, bool atomic, bool vm_type = false); + static const TypeAryKlassPtr* make(PTR ptr, ciKlass* k, Offset offset, InterfaceHandling interface_handling, bool vm_type = false); + static const TypeAryKlassPtr* make(ciKlass* klass, InterfaceHandling interface_handling, bool vm_type = false); + + const TypeAryKlassPtr* get_vm_type(bool vm_type = true) const; const Type *elem() const { return _elem; } @@ -1976,6 +2139,14 @@ class TypeAryKlassPtr : public TypeKlassPtr { return TypeKlassPtr::empty() || _elem->empty(); } + bool is_flat() const { return _flat; } + bool is_not_flat() const { return _not_flat; } + bool is_null_free() const { return _null_free; } + bool is_not_null_free() const { return _not_null_free; } + bool is_atomic() const { return _atomic; } + bool is_vm_type() const { return _vm_type; } + virtual bool can_be_inline_array() const; + #ifndef PRODUCT virtual void dump2( Dict &d, uint depth, outputStream *st ) const; // Specialized per-Type dumping #endif @@ -2111,14 +2282,26 @@ class TypeNarrowKlass : public TypeNarrowPtr { //------------------------------TypeFunc--------------------------------------- // Class of Array Types class TypeFunc : public Type { - TypeFunc( const TypeTuple *domain, const TypeTuple *range ) : Type(Function), _domain(domain), _range(range) {} + TypeFunc(const TypeTuple *domain_sig, const TypeTuple *domain_cc, const TypeTuple *range_sig, const TypeTuple *range_cc) + : Type(Function), _domain_sig(domain_sig), _domain_cc(domain_cc), _range_sig(range_sig), _range_cc(range_cc) {} virtual bool eq( const Type *t ) const; virtual uint hash() const; // Type specific hashing virtual bool singleton(void) const; // TRUE if type is a singleton virtual bool empty(void) const; // TRUE if type is vacuous - const TypeTuple* const _domain; // Domain of inputs - const TypeTuple* const _range; // Range of results + // Domains of inputs: inline type arguments are not passed by + // reference, instead each field of the inline type is passed as an + // argument. We maintain 2 views of the argument list here: one + // based on the signature (with an inline type argument as a single + // slot), one based on the actual calling convention (with a value + // type argument as a list of its fields). + const TypeTuple* const _domain_sig; + const TypeTuple* const _domain_cc; + // Range of results. Similar to domains: an inline type result can be + // returned in registers in which case range_cc lists all fields and + // is the actual calling convention. + const TypeTuple* const _range_sig; + const TypeTuple* const _range_cc; public: // Constants are shared among ADLC and VM @@ -2132,11 +2315,14 @@ class TypeFunc : public Type { // Accessors: - const TypeTuple* domain() const { return _domain; } - const TypeTuple* range() const { return _range; } - - static const TypeFunc *make(ciMethod* method); - static const TypeFunc *make(ciSignature signature, const Type* extra); + const TypeTuple* domain_sig() const { return _domain_sig; } + const TypeTuple* domain_cc() const { return _domain_cc; } + const TypeTuple* range_sig() const { return _range_sig; } + const TypeTuple* range_cc() const { return _range_cc; } + + static const TypeFunc* make(ciMethod* method, bool is_osr_compilation = false); + static const TypeFunc *make(const TypeTuple* domain_sig, const TypeTuple* domain_cc, + const TypeTuple* range_sig, const TypeTuple* range_cc); static const TypeFunc *make(const TypeTuple* domain, const TypeTuple* range); virtual const Type *xmeet( const Type *t ) const; @@ -2144,6 +2330,8 @@ class TypeFunc : public Type { BasicType return_type() const; + bool returns_inline_type_as_fields() const { return range_sig() != range_cc(); } + #ifndef PRODUCT virtual void dump2( Dict &d, uint depth, outputStream *st ) const; // Specialized per-Type dumping #endif @@ -2420,6 +2608,14 @@ inline bool Type::is_floatingpoint() const { return false; } +inline bool Type::is_inlinetypeptr() const { + return isa_instptr() != nullptr && is_instptr()->instance_klass()->is_inlinetype(); +} + +inline ciInlineKlass* Type::inline_klass() const { + return make_ptr()->is_instptr()->instance_klass()->as_inline_klass(); +} + template <> inline const TypeInt* Type::cast() const { return is_int(); @@ -2465,6 +2661,7 @@ inline const TypeLong* Type::try_cast() const { #define AndXNode AndLNode #define OrXNode OrLNode #define CmpXNode CmpLNode +#define CmpUXNode CmpULNode #define SubXNode SubLNode #define LShiftXNode LShiftLNode // For object size computation: @@ -2483,6 +2680,7 @@ inline const TypeLong* Type::try_cast() const { #define Op_XorX Op_XorL #define Op_URShiftX Op_URShiftL #define Op_LoadX Op_LoadL +#define Op_StoreX Op_StoreL // conversions #define ConvI2X(x) ConvI2L(x) #define ConvL2X(x) (x) @@ -2511,6 +2709,7 @@ inline const TypeLong* Type::try_cast() const { #define AndXNode AndINode #define OrXNode OrINode #define CmpXNode CmpINode +#define CmpUXNode CmpUNode #define SubXNode SubINode #define LShiftXNode LShiftINode // For object size computation: @@ -2529,6 +2728,7 @@ inline const TypeLong* Type::try_cast() const { #define Op_XorX Op_XorI #define Op_URShiftX Op_URShiftI #define Op_LoadX Op_LoadI +#define Op_StoreX Op_StoreI // conversions #define ConvI2X(x) (x) #define ConvL2X(x) ConvL2I(x) diff --git a/src/hotspot/share/opto/vector.cpp b/src/hotspot/share/opto/vector.cpp index cf01b2442e6..7b0fcd8cac0 100644 --- a/src/hotspot/share/opto/vector.cpp +++ b/src/hotspot/share/opto/vector.cpp @@ -204,7 +204,7 @@ void PhaseVector::scalarize_vbox_node(VectorBoxNode* vec_box) { // Adjust JVMS from post-call to pre-call state: put args on stack uint nargs = call->method()->arg_size(); kit.ensure_stack(kit.sp() + nargs); - for (uint i = TypeFunc::Parms; i < call->tf()->domain()->cnt(); i++) { + for (uint i = TypeFunc::Parms; i < call->tf()->domain_sig()->cnt(); i++) { kit.push(call->in(i)); } jvms = kit.sync_jvms(); diff --git a/src/hotspot/share/prims/jni.cpp b/src/hotspot/share/prims/jni.cpp index cd356863a8e..6d403de2408 100644 --- a/src/hotspot/share/prims/jni.cpp +++ b/src/hotspot/share/prims/jni.cpp @@ -51,6 +51,8 @@ #include "nmt/memTracker.hpp" #include "oops/access.inline.hpp" #include "oops/arrayOop.hpp" +#include "oops/flatArrayOop.inline.hpp" +#include "oops/inlineKlass.inline.hpp" #include "oops/instanceKlass.inline.hpp" #include "oops/instanceOop.hpp" #include "oops/klass.inline.hpp" @@ -418,8 +420,9 @@ JNI_ENTRY(jfieldID, jni_FromReflectedField(JNIEnv *env, jobject field)) // The jfieldID is the offset of the field within the object // It may also have hash bits for k, if VerifyJNIFields is turned on. int offset = InstanceKlass::cast(k1)->field_offset( slot ); + bool is_flat = InstanceKlass::cast(k1)->field_is_flat(slot); assert(InstanceKlass::cast(k1)->contains_field_offset(offset), "stay within object"); - ret = jfieldIDWorkaround::to_instance_jfieldID(k1, offset); + ret = jfieldIDWorkaround::to_instance_jfieldID(k1, offset, is_flat); return ret; JNI_END @@ -436,7 +439,7 @@ JNI_ENTRY(jobject, jni_ToReflectedMethod(JNIEnv *env, jclass cls, jmethodID meth methodHandle m (THREAD, Method::resolve_jmethod_id(method_id)); assert(m->is_static() == (isStatic != 0), "jni_ToReflectedMethod access flags doesn't match"); oop reflection_method; - if (m->is_object_initializer()) { + if (m->is_object_constructor()) { reflection_method = Reflection::new_constructor(m, CHECK_NULL); } else { // Note: Static initializers can theoretically be here, if JNI users manage @@ -795,7 +798,7 @@ class JNI_ArgumentPusherVaArg : public JNI_ArgumentPusher { case T_DOUBLE: push_double(va_arg(_ap, jdouble)); break; case T_ARRAY: - case T_OBJECT: push_object(va_arg(_ap, jobject)); break; + case T_OBJECT: push_object(va_arg(_ap, jobject)); break; default: ShouldNotReachHere(); } } @@ -962,7 +965,13 @@ JNI_ENTRY(jobject, jni_AllocObject(JNIEnv *env, jclass clazz)) jobject ret = nullptr; DT_RETURN_MARK(AllocObject, jobject, (const jobject&)ret); - instanceOop i = InstanceKlass::allocate_instance(JNIHandles::resolve_non_null(clazz), CHECK_NULL); + oop clazzoop = JNIHandles::resolve_non_null(clazz); + Klass* k = java_lang_Class::as_Klass(clazzoop); + if (k == nullptr || k->is_inline_klass()) { + ResourceMark rm(THREAD); + THROW_(vmSymbols::java_lang_InstantiationException(), nullptr); + } + instanceOop i = InstanceKlass::allocate_instance(clazzoop, CHECK_NULL); ret = JNIHandles::make_local(THREAD, i); return ret; JNI_END @@ -976,13 +985,21 @@ JNI_ENTRY(jobject, jni_NewObjectA(JNIEnv *env, jclass clazz, jmethodID methodID, jobject obj = nullptr; DT_RETURN_MARK(NewObjectA, jobject, (const jobject&)obj); - instanceOop i = InstanceKlass::allocate_instance(JNIHandles::resolve_non_null(clazz), CHECK_NULL); + oop clazzoop = JNIHandles::resolve_non_null(clazz); + Klass* k = java_lang_Class::as_Klass(clazzoop); + if (k == nullptr) { + ResourceMark rm(THREAD); + THROW_(vmSymbols::java_lang_InstantiationException(), nullptr); + } + + instanceOop i = InstanceKlass::allocate_instance(clazzoop, CHECK_NULL); obj = JNIHandles::make_local(THREAD, i); JavaValue jvalue(T_VOID); JNI_ArgumentPusherArray ap(methodID, args); jni_invoke_nonstatic(env, &jvalue, obj, JNI_NONVIRTUAL, methodID, &ap, CHECK_NULL); + return obj; -JNI_END + JNI_END DT_RETURN_MARK_DECL(NewObjectV, jobject @@ -994,11 +1011,19 @@ JNI_ENTRY(jobject, jni_NewObjectV(JNIEnv *env, jclass clazz, jmethodID methodID, jobject obj = nullptr; DT_RETURN_MARK(NewObjectV, jobject, (const jobject&)obj); - instanceOop i = InstanceKlass::allocate_instance(JNIHandles::resolve_non_null(clazz), CHECK_NULL); + oop clazzoop = JNIHandles::resolve_non_null(clazz); + Klass* k = java_lang_Class::as_Klass(clazzoop); + if (k == nullptr) { + ResourceMark rm(THREAD); + THROW_(vmSymbols::java_lang_InstantiationException(), nullptr); + } + + instanceOop i = InstanceKlass::allocate_instance(clazzoop, CHECK_NULL); obj = JNIHandles::make_local(THREAD, i); JavaValue jvalue(T_VOID); JNI_ArgumentPusherVaArg ap(methodID, args); jni_invoke_nonstatic(env, &jvalue, obj, JNI_NONVIRTUAL, methodID, &ap, CHECK_NULL); + return obj; JNI_END @@ -1012,7 +1037,14 @@ JNI_ENTRY(jobject, jni_NewObject(JNIEnv *env, jclass clazz, jmethodID methodID, jobject obj = nullptr; DT_RETURN_MARK(NewObject, jobject, (const jobject&)obj); - instanceOop i = InstanceKlass::allocate_instance(JNIHandles::resolve_non_null(clazz), CHECK_NULL); + oop clazzoop = JNIHandles::resolve_non_null(clazz); + Klass* k = java_lang_Class::as_Klass(clazzoop); + if (k == nullptr) { + ResourceMark rm(THREAD); + THROW_(vmSymbols::java_lang_InstantiationException(), nullptr); + } + + instanceOop i = InstanceKlass::allocate_instance(clazzoop, CHECK_NULL); obj = JNIHandles::make_local(THREAD, i); va_list args; va_start(args, methodID); @@ -1020,6 +1052,7 @@ JNI_ENTRY(jobject, jni_NewObject(JNIEnv *env, jclass clazz, jmethodID methodID, JNI_ArgumentPusherVaArg ap(methodID, args); jni_invoke_nonstatic(env, &jvalue, obj, JNI_NONVIRTUAL, methodID, &ap, CHECK_NULL); va_end(args); + return obj; JNI_END @@ -1770,7 +1803,7 @@ JNI_ENTRY(jfieldID, jni_GetFieldID(JNIEnv *env, jclass clazz, // A jfieldID for a non-static field is simply the offset of the field within the instanceOop // It may also have hash bits for k, if VerifyJNIFields is turned on. - ret = jfieldIDWorkaround::to_instance_jfieldID(k, fd.offset()); + ret = jfieldIDWorkaround::to_instance_jfieldID(k, fd.offset(), fd.is_flat()); return ret; JNI_END @@ -1780,19 +1813,31 @@ JNI_ENTRY(jobject, jni_GetObjectField(JNIEnv *env, jobject obj, jfieldID fieldID oop o = JNIHandles::resolve_non_null(obj); Klass* k = o->klass(); int offset = jfieldIDWorkaround::from_instance_jfieldID(k, fieldID); + oop res = nullptr; // Keep JVMTI addition small and only check enabled flag here. // jni_GetField_probe() assumes that is okay to create handles. if (JvmtiExport::should_post_field_access()) { o = JvmtiExport::jni_GetField_probe(thread, obj, o, k, fieldID, false); } - oop loaded_obj = HeapAccess::oop_load_at(o, offset); - jobject ret = JNIHandles::make_local(THREAD, loaded_obj); + if (!jfieldIDWorkaround::is_flat_jfieldID(fieldID)) { + res = HeapAccess::oop_load_at(o, offset); + } else { + assert(k->is_instance_klass(), "Only instance can have flat fields"); + InstanceKlass* ik = InstanceKlass::cast(k); + fieldDescriptor fd; + bool found = ik->find_field_from_offset(offset, false, &fd); // performance bottleneck + assert(found, "Field not found"); + InstanceKlass* holder = fd.field_holder(); + assert(holder->field_is_flat(fd.index()), "Must be"); + InlineLayoutInfo* li = holder->inline_layout_info_adr(fd.index()); + InlineKlass* field_vklass = li->klass(); + res = field_vklass->read_payload_from_addr(o, ik->field_offset(fd.index()), li->kind(), CHECK_NULL); + } + jobject ret = JNIHandles::make_local(THREAD, res); HOTSPOT_JNI_GETOBJECTFIELD_RETURN(ret); return ret; JNI_END - - #define DEFINE_GETFIELD(Return,Fieldname,Result \ , EntryProbe, ReturnProbe) \ \ @@ -1878,7 +1923,28 @@ JNI_ENTRY_NO_PRESERVE(void, jni_SetObjectField(JNIEnv *env, jobject obj, jfieldI field_value.l = value; o = JvmtiExport::jni_SetField_probe(thread, obj, o, k, fieldID, false, JVM_SIGNATURE_CLASS, (jvalue *)&field_value); } - HeapAccess::oop_store_at(o, offset, JNIHandles::resolve(value)); + if (!jfieldIDWorkaround::is_flat_jfieldID(fieldID)) { + oop v = JNIHandles::resolve(value); + if (v == nullptr) { + InstanceKlass *ik = InstanceKlass::cast(k); + fieldDescriptor fd; + ik->find_field_from_offset(offset, false, &fd); + if (fd.is_null_free_inline_type()) { + THROW_MSG(vmSymbols::java_lang_NullPointerException(), "Cannot store null in a null-restricted field"); + } + } + HeapAccess::oop_store_at(o, offset, v); + } else { + assert(k->is_instance_klass(), "Only instances can have flat fields"); + InstanceKlass* ik = InstanceKlass::cast(k); + fieldDescriptor fd; + ik->find_field_from_offset(offset, false, &fd); + InstanceKlass* holder = fd.field_holder(); + InlineLayoutInfo* li = holder->inline_layout_info_adr(fd.index()); + InlineKlass* vklass = li->klass(); + oop v = JNIHandles::resolve(value); + vklass->write_value_to_addr(v, ((char*)(oopDesc*)o) + offset, li->kind(), true, CHECK); + } HOTSPOT_JNI_SETOBJECTFIELD_RETURN(); JNI_END @@ -2307,7 +2373,9 @@ JNI_ENTRY(jobject, jni_GetObjectArrayElement(JNIEnv *env, jobjectArray array, js DT_RETURN_MARK(GetObjectArrayElement, jobject, (const jobject&)ret); objArrayOop a = objArrayOop(JNIHandles::resolve_non_null(array)); if (a->is_within_bounds(index)) { - ret = JNIHandles::make_local(THREAD, a->obj_at(index)); + oop res = a->obj_at(index, CHECK_NULL); + assert(res != nullptr || !a->is_null_free_array(), "Invalid value"); + ret = JNIHandles::make_local(THREAD, res); return ret; } else { ResourceMark rm(THREAD); @@ -2324,11 +2392,12 @@ JNI_ENTRY(void, jni_SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize HOTSPOT_JNI_SETOBJECTARRAYELEMENT_ENTRY(env, array, index, value); DT_VOID_RETURN_MARK(SetObjectArrayElement); - objArrayOop a = objArrayOop(JNIHandles::resolve_non_null(array)); - oop v = JNIHandles::resolve(value); - if (a->is_within_bounds(index)) { - if (v == nullptr || v->is_a(ObjArrayKlass::cast(a->klass())->element_klass())) { - a->obj_at_put(index, v); + objArrayOop a = objArrayOop(JNIHandles::resolve_non_null(array)); + oop v = JNIHandles::resolve(value); + if (a->is_within_bounds(index)) { + Klass* ek = a->is_flatArray() ? FlatArrayKlass::cast(a->klass())->element_klass() : RefArrayKlass::cast(a->klass())->element_klass(); + if (v == nullptr || v->is_a(ek)) { + a->obj_at_put(index, v, CHECK); } else { ResourceMark rm(THREAD); stringStream ss; @@ -2342,12 +2411,12 @@ JNI_ENTRY(void, jni_SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize } THROW_MSG(vmSymbols::java_lang_ArrayStoreException(), ss.as_string()); } - } else { - ResourceMark rm(THREAD); - stringStream ss; - ss.print("Index %d out of bounds for length %d", index, a->length()); - THROW_MSG(vmSymbols::java_lang_ArrayIndexOutOfBoundsException(), ss.as_string()); - } + } else { + ResourceMark rm(THREAD); + stringStream ss; + ss.print("Index %d out of bounds for length %d", index, a->length()); + THROW_MSG(vmSymbols::java_lang_ArrayIndexOutOfBoundsException(), ss.as_string()); + } JNI_END @@ -2723,7 +2792,7 @@ JNI_ENTRY(jint, jni_MonitorEnter(JNIEnv *env, jobject jobj)) } Handle obj(thread, JNIHandles::resolve_non_null(jobj)); - ObjectSynchronizer::jni_enter(obj, thread); + ObjectSynchronizer::jni_enter(obj, CHECK_(JNI_ERR)); return JNI_OK; JNI_END diff --git a/src/hotspot/share/prims/jniCheck.cpp b/src/hotspot/share/prims/jniCheck.cpp index 43cc61d7363..774c628f464 100644 --- a/src/hotspot/share/prims/jniCheck.cpp +++ b/src/hotspot/share/prims/jniCheck.cpp @@ -255,7 +255,7 @@ checkStaticFieldID(JavaThread* thr, jfieldID fid, jclass cls, int ftype) if (!id->find_local_field(&fd)) ReportJNIFatalError(thr, fatal_static_field_not_found); if ((fd.field_type() != ftype) && - !(fd.field_type() == T_ARRAY && ftype == T_OBJECT)) { + !(fd.field_type() == T_ARRAY && ftype == T_OBJECT)) { ReportJNIFatalError(thr, fatal_static_field_mismatch); } } @@ -343,9 +343,9 @@ check_primitive_array_type(JavaThread* thr, jarray jArray, BasicType elementType } static inline void -check_is_obj_array(JavaThread* thr, jarray jArray) { +check_is_obj_or_inline_array(JavaThread* thr, jarray jArray) { arrayOop aOop = check_is_array(thr, jArray); - if (!aOop->is_objArray()) { + if (!aOop->is_objArray() && !aOop->is_flatArray()) { ReportJNIFatalError(thr, fatal_object_array_expected); } } @@ -1652,7 +1652,7 @@ JNI_ENTRY_CHECKED(jobject, jsize index)) functionEnter(thr); IN_VM( - check_is_obj_array(thr, array); + check_is_obj_or_inline_array(thr, array); ) jobject result = UNCHECKED()->GetObjectArrayElement(env,array,index); functionExit(thr); @@ -1666,7 +1666,7 @@ JNI_ENTRY_CHECKED(void, jobject val)) functionEnter(thr); IN_VM( - check_is_obj_array(thr, array); + check_is_obj_or_inline_array(thr, array); ) UNCHECKED()->SetObjectArrayElement(env,array,index,val); functionExit(thr); diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index 511f9efdfb9..f45a6fdf5fc 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -57,6 +57,7 @@ #include "oops/access.inline.hpp" #include "oops/constantPool.hpp" #include "oops/fieldStreams.inline.hpp" +#include "oops/flatArrayKlass.hpp" #include "oops/instanceKlass.hpp" #include "oops/klass.inline.hpp" #include "oops/method.hpp" @@ -64,6 +65,7 @@ #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" #include "oops/recordComponent.hpp" +#include "oops/refArrayOop.inline.hpp" #include "prims/foreignGlobals.hpp" #include "prims/jvm_misc.hpp" #include "prims/jvmtiExport.hpp" @@ -412,6 +414,154 @@ JVM_ENTRY(jstring, JVM_GetTemporaryDirectory(JNIEnv *env)) return (jstring) JNIHandles::make_local(THREAD, h()); JVM_END +static void validate_array_arguments(Klass* elmClass, jint len, TRAPS) { + if (len < 0) { + THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "Array length is negative"); + } + elmClass->initialize(CHECK); + if (elmClass->is_array_klass() || elmClass->is_identity_class()) { + THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "Element class is not a value class"); + } + if (elmClass->is_abstract()) { + THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "Element class is abstract"); + } +} + +JVM_ENTRY(jarray, JVM_CopyOfSpecialArray(JNIEnv *env, jarray orig, jint from, jint to)) + oop o = JNIHandles::resolve_non_null(orig); + assert(o->is_array(), "Must be"); + oop array = nullptr; + arrayOop org = (arrayOop)o; + arrayHandle oh(THREAD, org); + ObjArrayKlass* ak = ObjArrayKlass::cast(org->klass()); + InlineKlass* vk = InlineKlass::cast(ak->element_klass()); + int len = to - from; // length of the new array + if (ak->is_null_free_array_klass()) { + if ((len != 0) && (from >= org->length() || to > org->length())) { + THROW_MSG_NULL(vmSymbols::java_lang_IllegalArgumentException(), "Copying of null-free array with uninitialized elements"); + } + } + if (org->is_flatArray()) { + FlatArrayKlass* fak = FlatArrayKlass::cast(org->klass()); + LayoutKind lk = fak->layout_kind(); + ArrayKlass::ArrayProperties props = ArrayKlass::ArrayProperties::DEFAULT; + switch(lk) { + case LayoutKind::ATOMIC_FLAT: + props = ArrayKlass::ArrayProperties::NULL_RESTRICTED; + break; + case LayoutKind::NON_ATOMIC_FLAT: + props = (ArrayKlass::ArrayProperties)(ArrayKlass::ArrayProperties::NULL_RESTRICTED | ArrayKlass::ArrayProperties::NON_ATOMIC); + break; + case LayoutKind::NULLABLE_ATOMIC_FLAT: + props = ArrayKlass::ArrayProperties::NON_ATOMIC; + break; + default: + ShouldNotReachHere(); + } + array = oopFactory::new_flatArray(vk, len, props, lk, CHECK_NULL); + arrayHandle ah(THREAD, (arrayOop)array); + int end = to < oh()->length() ? to : oh()->length(); + for (int i = from; i < end; i++) { + void* src = ((flatArrayOop)oh())->value_at_addr(i, fak->layout_helper()); + void* dst = ((flatArrayOop)ah())->value_at_addr(i - from, fak->layout_helper()); + vk->copy_payload_to_addr(src, dst, lk, false); + } + array = ah(); + } else { + ArrayKlass::ArrayProperties props = org->is_null_free_array() ? ArrayKlass::ArrayProperties::NULL_RESTRICTED : ArrayKlass::ArrayProperties::DEFAULT; + array = oopFactory::new_objArray(vk, len, props, CHECK_NULL); + int end = to < oh()->length() ? to : oh()->length(); + for (int i = from; i < end; i++) { + if (i < ((objArrayOop)oh())->length()) { + ((objArrayOop)array)->obj_at_put(i - from, ((objArrayOop)oh())->obj_at(i)); + } else { + assert(!ak->is_null_free_array_klass(), "Must be a nullable array"); + ((objArrayOop)array)->obj_at_put(i - from, nullptr); + } + } + } + return (jarray) JNIHandles::make_local(THREAD, array); +JVM_END + +JVM_ENTRY(jarray, JVM_NewNullRestrictedNonAtomicArray(JNIEnv *env, jclass elmClass, jint len, jobject initVal)) + oop mirror = JNIHandles::resolve_non_null(elmClass); + oop init = JNIHandles::resolve(initVal); + if (init == nullptr) { + THROW_MSG_NULL(vmSymbols::java_lang_IllegalArgumentException(), "Initial value cannot be null"); + } + Handle init_h(THREAD, init); + Klass* klass = java_lang_Class::as_Klass(mirror); + if (klass != init_h()->klass()) { + THROW_MSG_NULL(vmSymbols::java_lang_IllegalArgumentException(), "Type mismatch between array and initial value"); + } + validate_array_arguments(klass, len, CHECK_NULL); + InlineKlass* vk = InlineKlass::cast(klass); + ArrayKlass::ArrayProperties props = (ArrayKlass::ArrayProperties)(ArrayKlass::ArrayProperties::NON_ATOMIC | ArrayKlass::ArrayProperties::NULL_RESTRICTED); + objArrayOop array = oopFactory::new_objArray(klass, len, props, CHECK_NULL); + for (int i = 0; i < len; i++) { + array->obj_at_put(i, init_h() /*, CHECK_NULL*/ ); + } + return (jarray) JNIHandles::make_local(THREAD, array); +JVM_END + +JVM_ENTRY(jarray, JVM_NewNullRestrictedAtomicArray(JNIEnv *env, jclass elmClass, jint len, jobject initVal)) + oop mirror = JNIHandles::resolve_non_null(elmClass); + oop init = JNIHandles::resolve(initVal); + if (init == nullptr) { + THROW_MSG_NULL(vmSymbols::java_lang_IllegalArgumentException(), "Initial value cannot be null"); + } + Handle init_h(THREAD, init); + Klass* klass = java_lang_Class::as_Klass(mirror); + if (klass != init_h()->klass()) { + THROW_MSG_NULL(vmSymbols::java_lang_IllegalArgumentException(), "Type mismatch between array and initial value"); + } + validate_array_arguments(klass, len, CHECK_NULL); + InlineKlass* vk = InlineKlass::cast(klass); + ArrayKlass::ArrayProperties props = (ArrayKlass::ArrayProperties)(ArrayKlass::ArrayProperties::NULL_RESTRICTED); + objArrayOop array = oopFactory::new_objArray(klass, len, props, CHECK_NULL); + for (int i = 0; i < len; i++) { + array->obj_at_put(i, init_h() /*, CHECK_NULL*/ ); + } + return (jarray) JNIHandles::make_local(THREAD, array); +JVM_END + +JVM_ENTRY(jarray, JVM_NewNullableAtomicArray(JNIEnv *env, jclass elmClass, jint len)) + oop mirror = JNIHandles::resolve_non_null(elmClass); + Klass* klass = java_lang_Class::as_Klass(mirror); + klass->initialize(CHECK_NULL); + validate_array_arguments(klass, len, CHECK_NULL); + InlineKlass* vk = InlineKlass::cast(klass); + ArrayKlass::ArrayProperties props = (ArrayKlass::ArrayProperties)(ArrayKlass::ArrayProperties::DEFAULT); + objArrayOop array = oopFactory::new_objArray(klass, len, props, CHECK_NULL); + return (jarray) JNIHandles::make_local(THREAD, array); +JVM_END + +JVM_ENTRY(jboolean, JVM_IsFlatArray(JNIEnv *env, jobject obj)) + arrayOop oop = arrayOop(JNIHandles::resolve_non_null(obj)); + return oop->is_flatArray(); +JVM_END + +JVM_ENTRY(jboolean, JVM_IsNullRestrictedArray(JNIEnv *env, jobject obj)) + arrayOop oop = arrayOop(JNIHandles::resolve_non_null(obj)); + return oop->is_null_free_array(); +JVM_END + +JVM_ENTRY(jboolean, JVM_IsAtomicArray(JNIEnv *env, jobject obj)) + // There are multiple cases where an array can/must support atomic access: + // - the array is a reference array + // - the array uses an atomic flat layout: NULLABLE_ATOMIC_FLAT or ATOMIC_FLAT + // - the array is flat and its component type is naturally atomic + arrayOop oop = arrayOop(JNIHandles::resolve_non_null(obj)); + if (oop->is_refArray()) return true; + if (oop->is_flatArray()) { + FlatArrayKlass* fak = FlatArrayKlass::cast(oop->klass()); + if (fak->layout_kind() == LayoutKind::ATOMIC_FLAT || fak->layout_kind() == LayoutKind::NULLABLE_ATOMIC_FLAT) { + return true; + } + if (fak->element_klass()->is_naturally_atomic()) return true; + } + return false; +JVM_END // java.lang.Runtime ///////////////////////////////////////////////////////////////////////// @@ -620,8 +770,28 @@ JVM_END JVM_ENTRY(jint, JVM_IHashCode(JNIEnv* env, jobject handle)) // as implemented in the classic virtual machine; return 0 if object is null - return handle == nullptr ? 0 : - checked_cast(ObjectSynchronizer::FastHashCode (THREAD, JNIHandles::resolve_non_null(handle))); + if (handle == nullptr) { + return 0; + } + oop obj = JNIHandles::resolve_non_null(handle); + if (EnableValhalla && obj->klass()->is_inline_klass()) { + JavaValue result(T_INT); + JavaCallArguments args; + Handle ho(THREAD, obj); + args.push_oop(ho); + methodHandle method(THREAD, Universe::value_object_hash_code_method()); + JavaCalls::call(&result, method, &args, THREAD); + if (HAS_PENDING_EXCEPTION) { + if (!PENDING_EXCEPTION->is_a(vmClasses::Error_klass())) { + Handle e(THREAD, PENDING_EXCEPTION); + CLEAR_PENDING_EXCEPTION; + THROW_MSG_CAUSE_(vmSymbols::java_lang_InternalError(), "Internal error in hashCode", e, false); + } + } + return result.get_jint(); + } else { + return checked_cast(ObjectSynchronizer::FastHashCode(THREAD, obj)); + } JVM_END @@ -669,6 +839,12 @@ JVM_ENTRY(jobject, JVM_Clone(JNIEnv* env, jobject handle)) THROW_MSG_NULL(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name()); } + if (klass->is_inline_klass()) { + // Value instances have no identity, so return the current instance instead of allocating a new one + // Value classes cannot have finalizers, so the method can return immediately + return JNIHandles::make_local(THREAD, obj()); + } + // Make shallow object copy const size_t size = obj->size(); oop new_obj_oop = nullptr; @@ -1163,7 +1339,8 @@ JVM_ENTRY(jobjectArray, JVM_GetClassInterfaces(JNIEnv *env, jclass cls)) // Figure size of result array int size; if (klass->is_instance_klass()) { - size = InstanceKlass::cast(klass)->local_interfaces()->length(); + InstanceKlass* ik = InstanceKlass::cast(klass); + size = ik->local_interfaces()->length(); } else { assert(klass->is_objArray_klass() || klass->is_typeArray_klass(), "Illegal mirror klass"); size = 2; @@ -1197,7 +1374,6 @@ JVM_ENTRY(jboolean, JVM_IsHiddenClass(JNIEnv *env, jclass cls)) return k->is_hidden(); JVM_END - class ScopedValueBindingsResolver { public: InstanceKlass* Carrier_klass; @@ -1681,11 +1857,11 @@ static jobjectArray get_class_declared_methods_helper( // Select methods matching the criteria. for (int i = 0; i < methods_length; i++) { Method* method = methods->at(i); - if (want_constructor && !method->is_object_initializer()) { + if (want_constructor && !method->is_object_constructor()) { continue; } if (!want_constructor && - (method->is_object_initializer() || method->is_static_initializer() || + (method->is_object_constructor() || method->is_class_initializer() || method->is_overpass())) { continue; } @@ -1713,6 +1889,7 @@ static jobjectArray get_class_declared_methods_helper( } else { oop m; if (want_constructor) { + assert(method->is_object_constructor(), "must be"); m = Reflection::new_constructor(method, CHECK_NULL); } else { m = Reflection::new_method(method, false, CHECK_NULL); @@ -1982,10 +2159,9 @@ static jobject get_method_at_helper(const constantPoolHandle& cp, jint index, bo THROW_MSG_NULL(vmSymbols::java_lang_RuntimeException(), "Unable to look up method in target class"); } oop method; - if (m->is_object_initializer()) { + if (m->is_object_constructor()) { method = Reflection::new_constructor(m, CHECK_NULL); } else { - // new_method accepts as Method here method = Reflection::new_method(m, true, CHECK_NULL); } return JNIHandles::make_local(THREAD, method); @@ -2435,7 +2611,7 @@ JVM_ENTRY(jboolean, JVM_IsConstructorIx(JNIEnv *env, jclass cls, int method_inde Klass* k = java_lang_Class::as_Klass(JNIHandles::resolve_non_null(cls)); k = JvmtiThreadState::class_to_verify_considering_redefinition(k, thread); Method* method = InstanceKlass::cast(k)->methods()->at(method_index); - return method->name() == vmSymbols::object_initializer_name(); + return method->is_object_constructor(); JVM_END @@ -3244,6 +3420,10 @@ JVM_LEAF(jboolean, JVM_IsPreviewEnabled(void)) return Arguments::enable_preview() ? JNI_TRUE : JNI_FALSE; JVM_END +JVM_LEAF(jboolean, JVM_IsValhallaEnabled(void)) + return EnableValhalla ? JNI_TRUE : JNI_FALSE; +JVM_END + JVM_LEAF(jboolean, JVM_IsContinuationsSupported(void)) return VMContinuations ? JNI_TRUE : JNI_FALSE; JVM_END @@ -3323,7 +3503,9 @@ JVM_ENTRY(jobject, JVM_InvokeMethod(JNIEnv *env, jobject method, jobject obj, jo if (thread->stack_overflow_state()->stack_available((address) &method_handle) >= JVMInvokeMethodSlack) { method_handle = Handle(THREAD, JNIHandles::resolve(method)); Handle receiver(THREAD, JNIHandles::resolve(obj)); - objArrayHandle args(THREAD, objArrayOop(JNIHandles::resolve(args0))); + objArrayHandle args(THREAD, (objArrayOop)JNIHandles::resolve(args0)); + assert(args() == nullptr || !args->is_flatArray(), "args are never flat or are they???"); + oop result = Reflection::invoke_method(method_handle(), receiver, args, CHECK_NULL); jobject res = JNIHandles::make_local(THREAD, result); if (JvmtiExport::should_post_vm_object_alloc()) { @@ -3343,8 +3525,9 @@ JVM_END JVM_ENTRY(jobject, JVM_NewInstanceFromConstructor(JNIEnv *env, jobject c, jobjectArray args0)) + objArrayHandle args(THREAD, (objArrayOop)JNIHandles::resolve(args0)); + assert(args() == nullptr || !args->is_flatArray(), "args are never flat or are they???"); oop constructor_mirror = JNIHandles::resolve(c); - objArrayHandle args(THREAD, objArrayOop(JNIHandles::resolve(args0))); oop result = Reflection::invoke_constructor(constructor_mirror, args, CHECK_NULL); jobject res = JNIHandles::make_local(THREAD, result); if (JvmtiExport::should_post_vm_object_alloc()) { @@ -3584,12 +3767,13 @@ JVM_ENTRY(jobjectArray, JVM_DumpThreads(JNIEnv *env, jclass threadClass, jobject if (k != vmClasses::Thread_klass()) { THROW_NULL(vmSymbols::java_lang_IllegalArgumentException()); } + refArrayHandle rah(THREAD, (refArrayOop)ah()); // j.l.Thread is an identity class, arrays are always reference arrays ResourceMark rm(THREAD); GrowableArray* thread_handle_array = new GrowableArray(num_threads); for (int i = 0; i < num_threads; i++) { - oop thread_obj = ah->obj_at(i); + oop thread_obj = rah->obj_at(i); instanceHandle h(THREAD, (instanceOop) thread_obj); thread_handle_array->append(h); } diff --git a/src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp b/src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp index 831f407e7ec..e751ad4da7b 100644 --- a/src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp +++ b/src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp @@ -23,6 +23,7 @@ */ #include "classfile/symbolTable.hpp" +#include "classfile/vmClasses.hpp" #include "interpreter/bytecodeStream.hpp" #include "memory/universe.hpp" #include "oops/constantPool.inline.hpp" @@ -471,6 +472,26 @@ void JvmtiClassFileReconstituter::write_permitted_subclasses_attribute() { } } +// LoadableDescriptors { +// u2 attribute_name_index; +// u4 attribute_length; +// u2 number_of_descriptors; +// u2 descriptors[number_of_descriptors]; +// } +void JvmtiClassFileReconstituter::write_loadable_descriptors_attribute() { + Array* loadable_descriptors = ik()->loadable_descriptors(); + int number_of_descriptors = loadable_descriptors->length(); + int length = sizeof(u2) * (1 + number_of_descriptors); // '1 +' is for number_of_descriptors field + + write_attribute_name_index("LoadableDescriptors"); + write_u4(length); + write_u2(checked_cast(number_of_descriptors)); + for (int i = 0; i < number_of_descriptors; i++) { + u2 utf8_index = loadable_descriptors->at(i); + write_u2(utf8_index); + } +} + // Record { // u2 attribute_name_index; // u4 attribute_length; @@ -551,7 +572,12 @@ void JvmtiClassFileReconstituter::write_inner_classes_attribute(int length) { write_u2(iter.inner_class_info_index()); write_u2(iter.outer_class_info_index()); write_u2(iter.inner_name_index()); - write_u2(iter.inner_access_flags()); + u2 flags = iter.inner_access_flags(); + // ClassFileParser may add identity to inner class attributes, so remove it. + if (!ik()->supports_inline_types()) { + flags &= ~JVM_ACC_IDENTITY;; + } + write_u2(flags); } } @@ -810,6 +836,9 @@ void JvmtiClassFileReconstituter::write_class_attributes() { if (ik()->permitted_subclasses() != Universe::the_empty_short_array()) { ++attr_count; } + if (ik()->loadable_descriptors() != Universe::the_empty_short_array()) { + ++attr_count; + } if (ik()->record_components() != nullptr) { ++attr_count; } @@ -840,6 +869,9 @@ void JvmtiClassFileReconstituter::write_class_attributes() { if (ik()->permitted_subclasses() != Universe::the_empty_short_array()) { write_permitted_subclasses_attribute(); } + if (ik()->loadable_descriptors() != Universe::the_empty_short_array()) { + write_loadable_descriptors_attribute(); + } if (ik()->record_components() != nullptr) { write_record_attribute(); } @@ -1029,7 +1061,7 @@ void JvmtiClassFileReconstituter::copy_bytecodes(const methodHandle& mh, case Bytecodes::_getstatic : // fall through case Bytecodes::_putstatic : // fall through case Bytecodes::_getfield : // fall through - case Bytecodes::_putfield : { + case Bytecodes::_putfield : { int field_index = Bytes::get_native_u2(bcp+1); u2 pool_index = mh->constants()->resolved_field_entry_at(field_index)->constant_pool_index(); assert(pool_index < mh->constants()->length(), "sanity check"); diff --git a/src/hotspot/share/prims/jvmtiClassFileReconstituter.hpp b/src/hotspot/share/prims/jvmtiClassFileReconstituter.hpp index 015042a8543..6b170844038 100644 --- a/src/hotspot/share/prims/jvmtiClassFileReconstituter.hpp +++ b/src/hotspot/share/prims/jvmtiClassFileReconstituter.hpp @@ -122,6 +122,7 @@ class JvmtiClassFileReconstituter : public JvmtiConstantPoolReconstituter { void write_nest_members_attribute(); void write_permitted_subclasses_attribute(); void write_record_attribute(); + void write_loadable_descriptors_attribute(); address writeable_address(size_t size); void write_u1(u1 x); diff --git a/src/hotspot/share/prims/jvmtiEnv.cpp b/src/hotspot/share/prims/jvmtiEnv.cpp index bc4013a7cab..d195c5c9602 100644 --- a/src/hotspot/share/prims/jvmtiEnv.cpp +++ b/src/hotspot/share/prims/jvmtiEnv.cpp @@ -2726,10 +2726,6 @@ JvmtiEnv::GetSourceFileName(oop k_mirror, char** source_name_ptr) { jvmtiError JvmtiEnv::GetClassModifiers(oop k_mirror, jint* modifiers_ptr) { jint result = java_lang_Class::modifiers(k_mirror); - if (!java_lang_Class::is_primitive(k_mirror)) { - // Reset the deleted ACC_SUPER bit (deleted in compute_modifier_flags()). - result |= JVM_ACC_SUPER; - } *modifiers_ptr = result; return JVMTI_ERROR_NONE; @@ -2847,7 +2843,8 @@ JvmtiEnv::GetClassFields(oop k_mirror, jint* field_count_ptr, jfieldID** fields_ jfieldID* result_list = (jfieldID*)jvmtiMalloc(result_count * sizeof(jfieldID)); for (int i = 0; i < result_count; i++, flds.next()) { result_list[i] = jfieldIDWorkaround::to_jfieldID(ik, flds.offset(), - flds.access_flags().is_static()); + flds.access_flags().is_static(), + flds.field_descriptor().is_flat()); } assert(flds.done(), "just checking"); @@ -2885,8 +2882,9 @@ JvmtiEnv::GetImplementedInterfaces(oop k_mirror, jint* interface_count_ptr, jcla return JVMTI_ERROR_NONE; } - Array* interface_list = InstanceKlass::cast(k)->local_interfaces(); - const int result_length = (interface_list == nullptr ? 0 : interface_list->length()); + InstanceKlass* ik = InstanceKlass::cast(k); + Array* interface_list = ik->local_interfaces(); + int result_length = (interface_list == nullptr ? 0 : interface_list->length()); jclass* result_list = (jclass*) jvmtiMalloc(result_length * sizeof(jclass)); for (int i_index = 0; i_index < result_length; i_index += 1) { InstanceKlass* klass_at = interface_list->at(i_index); @@ -3082,9 +3080,12 @@ JvmtiEnv::GetObjectHashCode(jobject object, jint* hash_code_ptr) { NULL_CHECK(mirror, JVMTI_ERROR_INVALID_OBJECT); NULL_CHECK(hash_code_ptr, JVMTI_ERROR_NULL_POINTER); - { - jint result = (jint) mirror->identity_hash(); - *hash_code_ptr = result; + if (mirror->is_inline_type()) { + // For inline types, use the klass as a hash code. + // TBD to improve this (see also JvmtiTagMapKey::get_hash for similar case). + *hash_code_ptr = (jint)((int64_t)mirror->klass() >> 3); + } else { + *hash_code_ptr = (jint)mirror->identity_hash(); } return JVMTI_ERROR_NONE; } /* end GetObjectHashCode */ diff --git a/src/hotspot/share/prims/jvmtiGetLoadedClasses.cpp b/src/hotspot/share/prims/jvmtiGetLoadedClasses.cpp index 094a963500f..1b2237b15b2 100644 --- a/src/hotspot/share/prims/jvmtiGetLoadedClasses.cpp +++ b/src/hotspot/share/prims/jvmtiGetLoadedClasses.cpp @@ -27,6 +27,7 @@ #include "classfile/javaClasses.hpp" #include "gc/shared/collectedHeap.hpp" #include "memory/universe.hpp" +#include "oops/inlineKlass.hpp" #include "oops/klass.inline.hpp" #include "prims/jvmtiGetLoadedClasses.hpp" #include "runtime/handles.inline.hpp" @@ -57,10 +58,20 @@ class LoadedClassesClosure : public KlassClosure { } // Return current size of the Stack - int get_count() { + int get_count() const { return (int)_classStack.size(); } + // Some klasses should not be reported by GetLoadedClasses/GetClassLoaderClasses + bool exclude_klass(Klass* k) const { + // Direct instances of ObjArrayKlass represent the Java types that Java code can see. + // RefArrayKlass/FlatArrayKlass describe different implementations of the arrays, filter them out. + if (k->is_objArray_klass() && k->kind() != Klass::KlassKind::ObjArrayKlassKind) { + return true; + } + return false; + } + public: LoadedClassesClosure(JvmtiEnv* env, bool dictionary_walk) : _env(env), @@ -70,12 +81,16 @@ class LoadedClassesClosure : public KlassClosure { void do_klass(Klass* k) { // Collect all jclasses - _classStack.push((jclass) _env->jni_reference(Handle(_cur_thread, k->java_mirror()))); + if (!exclude_klass(k)) { + _classStack.push((jclass)_env->jni_reference(Handle(_cur_thread, k->java_mirror()))); + } if (_dictionary_walk) { // Collect array classes this way when walking the dictionary (because array classes are // not in the dictionary). for (Klass* l = k->array_klass_or_null(); l != nullptr; l = l->array_klass_or_null()) { - _classStack.push((jclass) _env->jni_reference(Handle(_cur_thread, l->java_mirror()))); + if (!exclude_klass(l)) { + _classStack.push((jclass)_env->jni_reference(Handle(_cur_thread, l->java_mirror()))); + } } } } diff --git a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp index dc2d621f694..b9220c60010 100644 --- a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp +++ b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp @@ -615,8 +615,7 @@ void VM_RedefineClasses::append_entry(const constantPoolHandle& scratch_cp, // At this stage, String could be here, but not StringIndex case JVM_CONSTANT_StringIndex: // fall through - // At this stage JVM_CONSTANT_UnresolvedClassInError should not be - // here + // At this stage JVM_CONSTANT_UnresolvedClassInError should not be here case JVM_CONSTANT_UnresolvedClassInError: // fall through default: @@ -1935,6 +1934,12 @@ bool VM_RedefineClasses::rewrite_cp_refs(InstanceKlass* scratch_class) { return false; } + // rewrite constant pool references in the LoadableDescriptors attribute: + if (!rewrite_cp_refs_in_loadable_descriptors_attribute(scratch_class)) { + // propagate failure back to caller + return false; + } + // rewrite constant pool references in the methods: if (!rewrite_cp_refs_in_methods(scratch_class)) { // propagate failure back to caller @@ -2083,6 +2088,19 @@ bool VM_RedefineClasses::rewrite_cp_refs_in_permitted_subclasses_attribute( return true; } +// Rewrite constant pool references in the LoadableDescriptors attribute. +bool VM_RedefineClasses::rewrite_cp_refs_in_loadable_descriptors_attribute( + InstanceKlass* scratch_class) { + + Array* loadable_descriptors = scratch_class->loadable_descriptors(); + assert(loadable_descriptors != nullptr, "unexpected null loadable_descriptors"); + for (int i = 0; i < loadable_descriptors->length(); i++) { + u2 cp_index = loadable_descriptors->at(i); + loadable_descriptors->at_put(i, find_new_index(cp_index)); + } + return true; +} + // Rewrite constant pool references in the methods. bool VM_RedefineClasses::rewrite_cp_refs_in_methods(InstanceKlass* scratch_class) { @@ -3270,6 +3288,14 @@ void VM_RedefineClasses::rewrite_cp_refs_in_stack_map_table( u1 frame_type = *stackmap_p; stackmap_p++; + if (frame_type == 246) { // EARLY_LARVAL + // rewrite_cp_refs in unset fields and fall through. + rewrite_cp_refs_in_early_larval_stackmaps(stackmap_p, stackmap_end, calc_number_of_entries, frame_type); + // The larval frames point to the next frame, so advance to the next frame and fall through. + frame_type = *stackmap_p; + stackmap_p++; + } + // same_frame { // u1 frame_type = SAME; /* 0-63 */ // } @@ -3479,6 +3505,29 @@ void VM_RedefineClasses::rewrite_cp_refs_in_verification_type_info( } // end rewrite_cp_refs_in_verification_type_info() +void VM_RedefineClasses::rewrite_cp_refs_in_early_larval_stackmaps( + address& stackmap_p_ref, address stackmap_end, u2 frame_i, + u1 frame_type) { + + u2 num_early_larval_stackmaps = Bytes::get_Java_u2(stackmap_p_ref); + stackmap_p_ref += 2; + + for (u2 i = 0; i < num_early_larval_stackmaps; i++) { + + u2 name_and_ref_index = Bytes::get_Java_u2(stackmap_p_ref); + u2 new_cp_index = find_new_index(name_and_ref_index); + if (new_cp_index != 0) { + log_debug(redefine, class, stackmap)("mapped old name_and_ref_index=%d", name_and_ref_index); + Bytes::put_Java_u2(stackmap_p_ref, new_cp_index); + name_and_ref_index = new_cp_index; + } + log_debug(redefine, class, stackmap) + ("frame_i=%u, frame_type=%u, name_and_ref_index=%d", frame_i, frame_type, name_and_ref_index); + + stackmap_p_ref += 2; + } +} // rewrite_cp_refs_in_early_larval_stackmaps + // Change the constant pool associated with klass scratch_class to scratch_cp. // scratch_cp_length elements are copied from scratch_cp to a smaller constant pool // and the smaller constant pool is associated with scratch_class. diff --git a/src/hotspot/share/prims/jvmtiRedefineClasses.hpp b/src/hotspot/share/prims/jvmtiRedefineClasses.hpp index d2eda1f3eed..c077db9e233 100644 --- a/src/hotspot/share/prims/jvmtiRedefineClasses.hpp +++ b/src/hotspot/share/prims/jvmtiRedefineClasses.hpp @@ -471,6 +471,7 @@ class VM_RedefineClasses: public VM_Operation { bool rewrite_cp_refs_in_nest_attributes(InstanceKlass* scratch_class); bool rewrite_cp_refs_in_record_attribute(InstanceKlass* scratch_class); bool rewrite_cp_refs_in_permitted_subclasses_attribute(InstanceKlass* scratch_class); + bool rewrite_cp_refs_in_loadable_descriptors_attribute(InstanceKlass* scratch_class); void rewrite_cp_refs_in_method(methodHandle method, methodHandle * new_method_p, TRAPS); @@ -487,6 +488,10 @@ class VM_RedefineClasses: public VM_Operation { void rewrite_cp_refs_in_verification_type_info( address& stackmap_addr_ref, address stackmap_end, u2 frame_i, u1 frame_size); + void rewrite_cp_refs_in_early_larval_stackmaps( + address& stackmap_p_ref, address stackmap_end, u2 frame_i, + u1 frame_type); + void set_new_constant_pool(ClassLoaderData* loader_data, InstanceKlass* scratch_class, constantPoolHandle scratch_cp, int scratch_cp_length, TRAPS); diff --git a/src/hotspot/share/prims/jvmtiTagMap.cpp b/src/hotspot/share/prims/jvmtiTagMap.cpp index 4febb4f3125..0d1c275de99 100644 --- a/src/hotspot/share/prims/jvmtiTagMap.cpp +++ b/src/hotspot/share/prims/jvmtiTagMap.cpp @@ -70,6 +70,67 @@ typedef ObjectBitSet JVMTIBitSet; + +// Helper class to store objects to visit. +class JvmtiHeapwalkVisitStack { +private: + enum { + initial_visit_stack_size = 4000 + }; + + GrowableArray* _visit_stack; + JVMTIBitSet _bitset; + + static GrowableArray* create_visit_stack() { + return new (mtServiceability) GrowableArray(initial_visit_stack_size, mtServiceability); + } + +public: + JvmtiHeapwalkVisitStack(): _visit_stack(create_visit_stack()) { + } + ~JvmtiHeapwalkVisitStack() { + if (_visit_stack != nullptr) { + delete _visit_stack; + } + } + + bool is_empty() const { + return _visit_stack->is_empty(); + } + + void push(const JvmtiHeapwalkObject& obj) { + _visit_stack->push(obj); + } + + // If the object hasn't been visited then push it onto the visit stack + // so that it will be visited later. + void check_for_visit(const JvmtiHeapwalkObject& obj) { + if (!is_visited(obj)) { + _visit_stack->push(obj); + } + } + + JvmtiHeapwalkObject pop() { + return _visit_stack->pop(); + } + + bool is_visited(const JvmtiHeapwalkObject& obj) /*const*/ { // TODO: _bitset.is_marked() should be const + // The method is called only for objects from visit_stack to ensure an object is not visited twice. + // Flat objects can be added to visit_stack only when we visit their holder object, so we cannot get duplicate reference to it. + if (obj.is_flat()) { + return false; + } + return _bitset.is_marked(obj.obj()); + } + + void mark_visited(const JvmtiHeapwalkObject& obj) { + if (!obj.is_flat()) { + _bitset.mark_obj(obj.obj()); + } + } +}; + + bool JvmtiTagMap::_has_object_free_events = false; // create a JvmtiTagMap @@ -77,12 +138,14 @@ JvmtiTagMap::JvmtiTagMap(JvmtiEnv* env) : _env(env), _lock(Mutex::nosafepoint, "JvmtiTagMap_lock"), _needs_cleaning(false), - _posting_events(false) { + _posting_events(false), + _converting_flat_object(false) { assert(JvmtiThreadState_lock->is_locked(), "sanity check"); assert(((JvmtiEnvBase *)env)->tag_map() == nullptr, "tag map already exists for environment"); _hashmap = new JvmtiTagMapTable(); + _flat_hashmap = new JvmtiFlatTagMapTable(); // finally add us to the environment ((JvmtiEnvBase *)env)->release_set_tag_map(this); @@ -98,6 +161,7 @@ JvmtiTagMap::~JvmtiTagMap() { // finally destroy the hashmap delete _hashmap; _hashmap = nullptr; + delete _flat_hashmap; } // Called by env_dispose() to reclaim memory before deallocation. @@ -106,6 +170,7 @@ JvmtiTagMap::~JvmtiTagMap() { void JvmtiTagMap::clear() { MutexLocker ml(lock(), Mutex::_no_safepoint_check_flag); _hashmap->clear(); + _flat_hashmap->clear(); } // returns the tag map for the given environments. If the tag map @@ -124,15 +189,10 @@ JvmtiTagMap* JvmtiTagMap::tag_map_for(JvmtiEnv* env) { return tag_map; } -// iterate over all entries in the tag map. -void JvmtiTagMap::entry_iterate(JvmtiTagMapKeyClosure* closure) { - hashmap()->entry_iterate(closure); -} - // returns true if the hashmaps are empty -bool JvmtiTagMap::is_empty() { +bool JvmtiTagMap::is_empty() const { assert(SafepointSynchronize::is_at_safepoint() || is_locked(), "checking"); - return hashmap()->is_empty(); + return _hashmap->is_empty() && _flat_hashmap->is_empty(); } // This checks for posting before operations that use @@ -166,13 +226,187 @@ void JvmtiTagMap::check_hashmaps_for_heapwalk(GrowableArray* objects) { } } -// Return the tag value for an object, or 0 if the object is -// not tagged -// -static inline jlong tag_for(JvmtiTagMap* tag_map, oop o) { - return tag_map->hashmap()->find(o); +// Converts entries from JvmtiFlatTagMapTable to JvmtiTagMapTable in batches. +// 1. (JvmtiTagMap is locked) +// reads entries from JvmtiFlatTagMapTable (describe flat value objects); +// 2. (JvmtiTagMap is unlocked) +// creates heap-allocated copies of the flat object; +// 3. (JvmtiTagMap is locked) +// ensures source entry still exists, removes it from JvmtiFlatTagMapTable, adds new entry to JvmtiTagMapTable. +// If some error occurs in step 2 (OOM?), the process stops. +class JvmtiTagMapFlatEntryConverter: public StackObj { +private: + struct Entry { + // source flat value object + Handle holder; + int offset; + InlineKlass* inline_klass; + LayoutKind layout_kind; + // converted heap-allocated object + Handle dst; + + Entry(): holder(), offset(0), inline_klass(nullptr), dst() {} + Entry(Handle holder, int offset, InlineKlass* inline_klass, LayoutKind lk) + : holder(holder), offset(offset), inline_klass(inline_klass), layout_kind(lk), dst() {} + }; + + int _batch_size; + GrowableArray _entries; + bool _has_error; + +public: + JvmtiTagMapFlatEntryConverter(int batch_size): _batch_size(batch_size), _entries(batch_size, mtServiceability), _has_error(false) { } + ~JvmtiTagMapFlatEntryConverter() {} + + // returns false if there is nothing to convert + bool import_entries(JvmtiFlatTagMapTable* table) { + if (_has_error) { + // stop the process to avoid infinite loop + return false; + } + + class Importer: public JvmtiFlatTagMapKeyClosure { + private: + GrowableArray& _entries; + int _batch_size; + public: + Importer(GrowableArray& entries, int batch_size): _entries(entries), _batch_size(batch_size) {} + + bool do_entry(JvmtiFlatTagMapKey& key, jlong& tag) { + Entry entry(Handle(Thread::current(), key.holder()), key.offset(), key.inline_klass(), key.layout_kind()); + _entries.append(entry); + + return _entries.length() < _batch_size; + } + } importer(_entries, _batch_size); + table->entry_iterate(&importer); + + return !_entries.is_empty(); + } + + void convert() { + for (int i = 0; i < _entries.length(); i++) { + EXCEPTION_MARK; + Entry& entry = _entries.at(i); + oop obj = entry.inline_klass->read_payload_from_addr(entry.holder(), entry.offset, entry.layout_kind, JavaThread::current()); + + if (HAS_PENDING_EXCEPTION) { + tty->print_cr("Exception in JvmtiTagMapFlatEntryConverter: "); + java_lang_Throwable::print(PENDING_EXCEPTION, tty); + tty->cr(); + CLEAR_PENDING_EXCEPTION; + // stop the conversion + _has_error = true; + } else { + entry.dst = Handle(Thread::current(), obj); + } + } + } + + // returns number of converted entries + int move(JvmtiFlatTagMapTable* src_table, JvmtiTagMapTable* dst_table) { + int count = 0; + for (int i = 0; i < _entries.length(); i++) { + Entry& entry = _entries.at(i); + if (entry.dst() == nullptr) { + // some error during conversion, skip the entry + continue; + } + JvmtiHeapwalkObject obj(entry.holder(), entry.offset, entry.inline_klass, entry.layout_kind); + jlong tag = src_table->remove(obj); + + if (tag != 0) { // ensure the entry is still in the src_table + dst_table->add(entry.dst(), tag); + count++; + } else { + + } + } + // and clean the array + _entries.clear(); + return count; + } +}; + + +void JvmtiTagMap::convert_flat_object_entries() { + Thread* current = Thread::current(); + assert(current->is_Java_thread(), "must be executed on JavaThread"); + + log_debug(jvmti, table)("convert_flat_object_entries, main table size = %d, flat table size = %d", + _hashmap->number_of_entries(), _flat_hashmap->number_of_entries()); + + { + MonitorLocker ml(lock(), Mutex::_no_safepoint_check_flag); + // If another thread is converting, let it finish. + while (_converting_flat_object) { + ml.wait(); + } + if (_flat_hashmap->is_empty()) { + // nothing to convert + return; + } + _converting_flat_object = true; + } + + const int BATCH_SIZE = 1024; + JvmtiTagMapFlatEntryConverter converter(BATCH_SIZE); + + int count = 0; + while (true) { + HandleMark hm(current); + { + MonitorLocker ml(lock(), Mutex::_no_safepoint_check_flag); + if (!converter.import_entries(_flat_hashmap)) { + break; + } + } + // Convert flat objects to heap-allocated without table lock (so agent callbacks can get/set tags). + converter.convert(); + { + MonitorLocker ml(lock(), Mutex::_no_safepoint_check_flag); + count += converter.move(_flat_hashmap, _hashmap); + } + } + + log_info(jvmti, table)("%d flat value objects are converted, flat table size = %d", + count, _flat_hashmap->number_of_entries()); + { + MonitorLocker ml(lock(), Mutex::_no_safepoint_check_flag); + _converting_flat_object = false; + ml.notify_all(); + } } +jlong JvmtiTagMap::find(const JvmtiHeapwalkObject& obj) const { + jlong tag = _hashmap->find(obj); + if (tag == 0 && obj.is_value()) { + tag = _flat_hashmap->find(obj); + } + return tag; +} + +void JvmtiTagMap::add(const JvmtiHeapwalkObject& obj, jlong tag) { + if (obj.is_flat()) { + // we may have tag for equal (non-flat) object in _hashmap, try to update it 1st + if (!_hashmap->update(obj, tag)) { + // no entry in _hashmap, add to _flat_hashmap + _flat_hashmap->add(obj, tag); + } + } else { + _hashmap->add(obj, tag); + } +} + +void JvmtiTagMap::remove(const JvmtiHeapwalkObject& obj) { + if (!_hashmap->remove(obj)) { + if (obj.is_value()) { + _flat_hashmap->remove(obj); + } + } +} + + // A CallbackWrapper is a support class for querying and tagging an object // around a callback to a profiler. The constructor does pre-callback // work to get the tag value, klass tag value, ... and the destructor @@ -191,8 +425,7 @@ static inline jlong tag_for(JvmtiTagMap* tag_map, oop o) { class CallbackWrapper : public StackObj { private: JvmtiTagMap* _tag_map; - JvmtiTagMapTable* _hashmap; - oop _o; + const JvmtiHeapwalkObject& _o; jlong _obj_size; jlong _obj_tag; jlong _klass_tag; @@ -201,34 +434,36 @@ class CallbackWrapper : public StackObj { JvmtiTagMap* tag_map() const { return _tag_map; } // invoked post-callback to tag, untag, or update the tag of an object - void inline post_callback_tag_update(oop o, JvmtiTagMapTable* hashmap, - jlong obj_tag); + void inline post_callback_tag_update(const JvmtiHeapwalkObject& o, JvmtiTagMap* tag_map, jlong obj_tag); + public: - CallbackWrapper(JvmtiTagMap* tag_map, oop o) { + CallbackWrapper(JvmtiTagMap* tag_map, const JvmtiHeapwalkObject& o) + : _tag_map(tag_map), _o(o) + { assert(Thread::current()->is_VM_thread() || tag_map->is_locked(), "MT unsafe or must be VM thread"); - // object to tag - _o = o; - // object size - _obj_size = (jlong)_o->size() * wordSize; - - // record the context - _tag_map = tag_map; - _hashmap = tag_map->hashmap(); + if (!o.is_flat()) { + // common case: we have oop + _obj_size = (jlong)o.obj()->size() * wordSize; + } else { + // flat value object, we know its InstanceKlass + assert(_o.inline_klass() != nullptr, "must be"); + _obj_size = _o.inline_klass()->size() * wordSize;; + } // get object tag - _obj_tag = _hashmap->find(_o); + _obj_tag = _tag_map->find(_o); // get the class and the class's tag value assert(vmClasses::Class_klass()->is_mirror_instance_klass(), "Is not?"); - _klass_tag = tag_for(tag_map, _o->klass()->java_mirror()); + _klass_tag = _tag_map->find(_o.klass()->java_mirror()); } ~CallbackWrapper() { - post_callback_tag_update(_o, _hashmap, _obj_tag); + post_callback_tag_update(_o, _tag_map, _obj_tag); } inline jlong* obj_tag_p() { return &_obj_tag; } @@ -238,17 +473,17 @@ class CallbackWrapper : public StackObj { }; // callback post-callback to tag, untag, or update the tag of an object -void inline CallbackWrapper::post_callback_tag_update(oop o, - JvmtiTagMapTable* hashmap, +void inline CallbackWrapper::post_callback_tag_update(const JvmtiHeapwalkObject& o, + JvmtiTagMap* tag_map, jlong obj_tag) { if (obj_tag == 0) { // callback has untagged the object, remove the entry if present - hashmap->remove(o); + tag_map->remove(o); } else { // object was previously tagged or not present - the callback may have // changed the tag value assert(Thread::current()->is_VM_thread(), "must be VMThread"); - hashmap->add(o, obj_tag); + tag_map->add(o, obj_tag); } } @@ -270,9 +505,8 @@ void inline CallbackWrapper::post_callback_tag_update(oop o, // class TwoOopCallbackWrapper : public CallbackWrapper { private: + const JvmtiHeapwalkObject& _referrer; bool _is_reference_to_self; - JvmtiTagMapTable* _referrer_hashmap; - oop _referrer; jlong _referrer_obj_tag; jlong _referrer_klass_tag; jlong* _referrer_tag_p; @@ -280,8 +514,8 @@ class TwoOopCallbackWrapper : public CallbackWrapper { bool is_reference_to_self() const { return _is_reference_to_self; } public: - TwoOopCallbackWrapper(JvmtiTagMap* tag_map, oop referrer, oop o) : - CallbackWrapper(tag_map, o) + TwoOopCallbackWrapper(JvmtiTagMap* tag_map, const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& o) : + CallbackWrapper(tag_map, o), _referrer(referrer) { // self reference needs to be handled in a special way _is_reference_to_self = (referrer == o); @@ -290,24 +524,20 @@ class TwoOopCallbackWrapper : public CallbackWrapper { _referrer_klass_tag = klass_tag(); _referrer_tag_p = obj_tag_p(); } else { - _referrer = referrer; - // record the context - _referrer_hashmap = tag_map->hashmap(); - // get object tag - _referrer_obj_tag = _referrer_hashmap->find(_referrer); + _referrer_obj_tag = tag_map->find(_referrer); _referrer_tag_p = &_referrer_obj_tag; // get referrer class tag. - _referrer_klass_tag = tag_for(tag_map, _referrer->klass()->java_mirror()); + _referrer_klass_tag = tag_map->find(_referrer.klass()->java_mirror()); } } ~TwoOopCallbackWrapper() { if (!is_reference_to_self()) { post_callback_tag_update(_referrer, - _referrer_hashmap, + tag_map(), _referrer_obj_tag); } } @@ -335,17 +565,15 @@ void JvmtiTagMap::set_tag(jobject object, jlong tag) { // resolve the object oop o = JNIHandles::resolve_non_null(object); - // see if the object is already tagged - JvmtiTagMapTable* hashmap = _hashmap; - + JvmtiHeapwalkObject obj(o); if (tag == 0) { // remove the entry if present - hashmap->remove(o); + _hashmap->remove(obj); } else { // if the object is already tagged or not present then we add/update // the tag - hashmap->add(o, tag); + add(obj, tag); } } @@ -361,7 +589,7 @@ jlong JvmtiTagMap::get_tag(jobject object) { // resolve the object oop o = JNIHandles::resolve_non_null(object); - return tag_for(this, o); + return find(o); } @@ -374,13 +602,28 @@ class ClassFieldDescriptor: public CHeapObj { int _field_index; int _field_offset; char _field_type; + InlineKlass* _inline_klass; // nullptr for heap object + LayoutKind _layout_kind; public: - ClassFieldDescriptor(int index, char type, int offset) : - _field_index(index), _field_offset(offset), _field_type(type) { + ClassFieldDescriptor(int index, const FieldStreamBase& fld) : + _field_index(index), _field_offset(fld.offset()), _field_type(fld.signature()->char_at(0)) { + if (fld.is_flat()) { + const fieldDescriptor& fd = fld.field_descriptor(); + InstanceKlass* holder_klass = fd.field_holder(); + InlineLayoutInfo* layout_info = holder_klass->inline_layout_info_adr(fd.index()); + _inline_klass = layout_info->klass(); + _layout_kind = layout_info->kind(); + } else { + _inline_klass = nullptr; + _layout_kind = LayoutKind::REFERENCE; + } } int field_index() const { return _field_index; } char field_type() const { return _field_type; } int field_offset() const { return _field_offset; } + bool is_flat() const { return _inline_klass != nullptr; } + InlineKlass* inline_klass() const { return _inline_klass; } + LayoutKind layout_kind() const { return _layout_kind; } }; class ClassFieldMap: public CHeapObj { @@ -399,7 +642,7 @@ class ClassFieldMap: public CHeapObj { static int interfaces_field_count(InstanceKlass* ik); // add a field - void add(int index, char type, int offset); + void add(int index, const FieldStreamBase& fld); public: ~ClassFieldMap(); @@ -410,7 +653,7 @@ class ClassFieldMap: public CHeapObj { // functions to create maps of static or instance fields static ClassFieldMap* create_map_of_static_fields(Klass* k); - static ClassFieldMap* create_map_of_instance_fields(oop obj); + static ClassFieldMap* create_map_of_instance_fields(Klass* k); }; ClassFieldMap::ClassFieldMap() { @@ -435,8 +678,8 @@ int ClassFieldMap::interfaces_field_count(InstanceKlass* ik) { return count; } -void ClassFieldMap::add(int index, char type, int offset) { - ClassFieldDescriptor* field = new ClassFieldDescriptor(index, type, offset); +void ClassFieldMap::add(int index, const FieldStreamBase& fld) { + ClassFieldDescriptor* field = new ClassFieldDescriptor(index, fld); _fields->append(field); } @@ -461,7 +704,7 @@ ClassFieldMap* ClassFieldMap::create_map_of_static_fields(Klass* k) { if (!fld.access_flags().is_static()) { continue; } - field_map->add(index, fld.signature()->char_at(0), fld.offset()); + field_map->add(index, fld); } return field_map; @@ -470,8 +713,8 @@ ClassFieldMap* ClassFieldMap::create_map_of_static_fields(Klass* k) { // Returns a heap allocated ClassFieldMap to describe the instance fields // of the given class. All instance fields are included (this means public // and private fields declared in superclasses too). -ClassFieldMap* ClassFieldMap::create_map_of_instance_fields(oop obj) { - InstanceKlass* ik = InstanceKlass::cast(obj->klass()); +ClassFieldMap* ClassFieldMap::create_map_of_instance_fields(Klass* k) { + InstanceKlass* ik = InstanceKlass::cast(k); // create the field map ClassFieldMap* field_map = new ClassFieldMap(); @@ -491,7 +734,7 @@ ClassFieldMap* ClassFieldMap::create_map_of_instance_fields(oop obj) { if (fld.access_flags().is_static()) { continue; } - field_map->add(start_index + index, fld.signature()->char_at(0), fld.offset()); + field_map->add(start_index + index, fld); } // update total_field_number for superclass (decrease by the field count in the current class) total_field_number = start_index; @@ -521,9 +764,9 @@ class JvmtiCachedClassFieldMap : public CHeapObj { static void add_to_class_list(InstanceKlass* ik); public: - // returns the field map for a given object (returning map cached + // returns the field map for a given klass (returning map cached // by InstanceKlass if possible - static ClassFieldMap* get_map_of_instance_fields(oop obj); + static ClassFieldMap* get_map_of_instance_fields(Klass* k); // removes the field map from all instanceKlasses - should be // called before VM operation completes @@ -575,13 +818,12 @@ void JvmtiCachedClassFieldMap::add_to_class_list(InstanceKlass* ik) { _class_list->push(ik); } -// returns the instance field map for the given object +// returns the instance field map for the given klass // (returns field map cached by the InstanceKlass if possible) -ClassFieldMap* JvmtiCachedClassFieldMap::get_map_of_instance_fields(oop obj) { +ClassFieldMap* JvmtiCachedClassFieldMap::get_map_of_instance_fields(Klass *k) { assert(Thread::current()->is_VM_thread(), "must be VMThread"); assert(ClassFieldMapCacheMark::is_active(), "ClassFieldMapCacheMark not active"); - Klass* k = obj->klass(); InstanceKlass* ik = InstanceKlass::cast(k); // return cached map if possible @@ -590,7 +832,7 @@ ClassFieldMap* JvmtiCachedClassFieldMap::get_map_of_instance_fields(oop obj) { assert(cached_map->field_map() != nullptr, "missing field list"); return cached_map->field_map(); } else { - ClassFieldMap* field_map = ClassFieldMap::create_map_of_instance_fields(obj); + ClassFieldMap* field_map = ClassFieldMap::create_map_of_instance_fields(k); cached_map = new JvmtiCachedClassFieldMap(field_map); ik->set_jvmti_cached_class_field_map(cached_map); add_to_class_list(ik); @@ -642,9 +884,9 @@ static inline bool is_filtered_by_heap_filter(jlong obj_tag, } // helper function to indicate if an object is filtered by a klass filter -static inline bool is_filtered_by_klass_filter(oop obj, Klass* klass_filter) { +static inline bool is_filtered_by_klass_filter(const JvmtiHeapwalkObject& obj, Klass* klass_filter) { if (klass_filter != nullptr) { - if (obj->klass() != klass_filter) { + if (obj.klass() != klass_filter) { return true; } } @@ -675,9 +917,11 @@ static inline void copy_to_jvalue(jvalue *v, address addr, jvmtiPrimitiveType va // returns visit control flags static jint invoke_string_value_callback(jvmtiStringPrimitiveValueCallback cb, CallbackWrapper* wrapper, - oop str, + const JvmtiHeapwalkObject& obj, void* user_data) { + assert(!obj.is_flat(), "cannot be flat"); + oop str = obj.obj(); assert(str->klass() == vmClasses::String_klass(), "not a string"); typeArrayOop s_value = java_lang_String::value(str); @@ -726,13 +970,14 @@ static jint invoke_string_value_callback(jvmtiStringPrimitiveValueCallback cb, // returns visit control flags static jint invoke_array_primitive_value_callback(jvmtiArrayPrimitiveValueCallback cb, CallbackWrapper* wrapper, - oop obj, + const JvmtiHeapwalkObject& obj, void* user_data) { - assert(obj->is_typeArray(), "not a primitive array"); + assert(!obj.is_flat(), "cannot be flat"); + assert(obj.obj()->is_typeArray(), "not a primitive array"); // get base address of first element - typeArrayOop array = typeArrayOop(obj); + typeArrayOop array = typeArrayOop(obj.obj()); BasicType type = TypeArrayKlass::cast(array->klass())->element_type(); void* elements = array->base(type); @@ -822,7 +1067,7 @@ static jint invoke_primitive_field_callback_for_static_fields // of a given object static jint invoke_primitive_field_callback_for_instance_fields( CallbackWrapper* wrapper, - oop obj, + const JvmtiHeapwalkObject& obj, jvmtiPrimitiveFieldCallback cb, void* user_data) { @@ -830,7 +1075,7 @@ static jint invoke_primitive_field_callback_for_instance_fields( static jvmtiHeapReferenceInfo reference_info = { 0 }; // get the map of the instance fields - ClassFieldMap* fields = JvmtiCachedClassFieldMap::get_map_of_instance_fields(obj); + ClassFieldMap* fields = JvmtiCachedClassFieldMap::get_map_of_instance_fields(obj.klass()); // invoke the callback for each instance primitive field for (int i=0; ifield_count(); i++) { @@ -844,9 +1089,8 @@ static jint invoke_primitive_field_callback_for_instance_fields( // one-to-one mapping jvmtiPrimitiveType value_type = (jvmtiPrimitiveType)type; - // get offset and field value - int offset = field->field_offset(); - address addr = cast_from_oop
(obj) + offset; + // get field value + address addr = cast_from_oop
(obj.obj()) + obj.offset() + field->field_offset(); jvalue value; copy_to_jvalue(&value, addr, value_type); @@ -960,7 +1204,8 @@ void IterateOverHeapObjectClosure::do_object(oop o) { } // prepare for the calllback - CallbackWrapper wrapper(tag_map(), o); + JvmtiHeapwalkObject wrapper_obj(o); + CallbackWrapper wrapper(tag_map(), wrapper_obj); // if the object is tagged and we're only interested in untagged objects // then don't invoke the callback. Similarly, if the object is untagged @@ -1012,6 +1257,10 @@ class IterateThroughHeapObjectClosure: public ObjectClosure { return is_abort; } + void visit_object(const JvmtiHeapwalkObject& obj); + void visit_flat_fields(const JvmtiHeapwalkObject& obj); + void visit_flat_array_elements(const JvmtiHeapwalkObject& obj); + public: IterateThroughHeapObjectClosure(JvmtiTagMap* tag_map, Klass* klass, @@ -1027,7 +1276,7 @@ class IterateThroughHeapObjectClosure: public ObjectClosure { { } - void do_object(oop o); + void do_object(oop obj); }; // invoked for each object in the heap @@ -1036,16 +1285,20 @@ void IterateThroughHeapObjectClosure::do_object(oop obj) { // check if iteration has been halted if (is_iteration_aborted()) return; - // apply class filter - if (is_filtered_by_klass_filter(obj, klass())) return; - // skip if object is a dormant shared object whose mirror hasn't been loaded - if (obj->klass()->java_mirror() == nullptr) { + if (obj != nullptr && obj->klass()->java_mirror() == nullptr) { log_debug(aot, heap)("skipped dormant archived object " INTPTR_FORMAT " (%s)", p2i(obj), obj->klass()->external_name()); return; } + visit_object(obj); +} + +void IterateThroughHeapObjectClosure::visit_object(const JvmtiHeapwalkObject& obj) { + // apply class filter + if (is_filtered_by_klass_filter(obj, klass())) return; + // prepare for callback CallbackWrapper wrapper(tag_map(), obj); @@ -1055,8 +1308,8 @@ void IterateThroughHeapObjectClosure::do_object(oop obj) { } // for arrays we need the length, otherwise -1 - bool is_array = obj->is_array(); - int len = is_array ? arrayOop(obj)->length() : -1; + bool is_array = obj.klass()->is_array_klass(); + int len = is_array ? arrayOop(obj.obj())->length() : -1; // invoke the object callback (if callback is provided) if (callbacks()->heap_iteration_callback != nullptr) { @@ -1070,19 +1323,20 @@ void IterateThroughHeapObjectClosure::do_object(oop obj) { } // for objects and classes we report primitive fields if callback provided - if (callbacks()->primitive_field_callback != nullptr && obj->is_instance()) { + if (callbacks()->primitive_field_callback != nullptr && obj.klass()->is_instance_klass()) { jint res; jvmtiPrimitiveFieldCallback cb = callbacks()->primitive_field_callback; - if (obj->klass() == vmClasses::Class_klass()) { + if (obj.klass() == vmClasses::Class_klass()) { + assert(!obj.is_flat(), "Class object cannot be flattened"); res = invoke_primitive_field_callback_for_static_fields(&wrapper, - obj, - cb, - (void*)user_data()); + obj.obj(), + cb, + (void*)user_data()); } else { res = invoke_primitive_field_callback_for_instance_fields(&wrapper, - obj, - cb, - (void*)user_data()); + obj, + cb, + (void*)user_data()); } if (check_flags_for_abort(res)) return; } @@ -1090,28 +1344,108 @@ void IterateThroughHeapObjectClosure::do_object(oop obj) { // string callback if (!is_array && callbacks()->string_primitive_value_callback != nullptr && - obj->klass() == vmClasses::String_klass()) { + obj.klass() == vmClasses::String_klass()) { jint res = invoke_string_value_callback( callbacks()->string_primitive_value_callback, &wrapper, obj, - (void*)user_data() ); + (void*)user_data()); if (check_flags_for_abort(res)) return; } // array callback if (is_array && callbacks()->array_primitive_value_callback != nullptr && - obj->is_typeArray()) { + obj.klass()->is_typeArray_klass()) { jint res = invoke_array_primitive_value_callback( callbacks()->array_primitive_value_callback, &wrapper, obj, - (void*)user_data() ); + (void*)user_data()); if (check_flags_for_abort(res)) return; } -}; + // All info for the object is reported. + + // If the object has flat fields, report them as heap objects. + if (obj.klass()->is_instance_klass()) { + if (InstanceKlass::cast(obj.klass())->has_inline_type_fields()) { + visit_flat_fields(obj); + // check if iteration has been halted + if (is_iteration_aborted()) { + return; + } + } + } + // If the object is flat array, report all elements as heap objects. + if (is_array && obj.obj()->is_flatArray()) { + assert(!obj.is_flat(), "Array object cannot be flattened"); + visit_flat_array_elements(obj); + } +} + +void IterateThroughHeapObjectClosure::visit_flat_fields(const JvmtiHeapwalkObject& obj) { + // iterate over instance fields + ClassFieldMap* fields = JvmtiCachedClassFieldMap::get_map_of_instance_fields(obj.klass()); + for (int i = 0; i < fields->field_count(); i++) { + ClassFieldDescriptor* field = fields->field_at(i); + // skip non-flat and (for safety) primitive fields + if (!field->is_flat() || is_primitive_field_type(field->field_type())) { + continue; + } + + int field_offset = field->field_offset(); + if (obj.is_flat()) { + // the object is inlined, its fields are stored without the header + field_offset += obj.offset() - obj.inline_klass()->payload_offset(); + } + // check for possible nulls + bool can_be_null = field->layout_kind() == LayoutKind::NULLABLE_ATOMIC_FLAT; + if (can_be_null) { + address payload = cast_from_oop
(obj.obj()) + field_offset; + if (field->inline_klass()->is_payload_marked_as_null(payload)) { + continue; + } + } + JvmtiHeapwalkObject field_obj(obj.obj(), field_offset, field->inline_klass(), field->layout_kind()); + + visit_object(field_obj); + + // check if iteration has been halted + if (is_iteration_aborted()) { + return; + } + } +} + +void IterateThroughHeapObjectClosure::visit_flat_array_elements(const JvmtiHeapwalkObject& obj) { + assert(!obj.is_flat() && obj.obj()->is_flatArray() , "sanity check"); + flatArrayOop array = flatArrayOop(obj.obj()); + FlatArrayKlass* faklass = FlatArrayKlass::cast(array->klass()); + InlineKlass* vk = InlineKlass::cast(faklass->element_klass()); + bool need_null_check = faklass->layout_kind() == LayoutKind::NULLABLE_ATOMIC_FLAT; + + for (int index = 0; index < array->length(); index++) { + address addr = (address)array->value_at_addr(index, faklass->layout_helper()); + // check for null + if (need_null_check) { + if (vk->is_payload_marked_as_null(addr)) { + continue; + } + } + + // offset in the array oop + int offset = (int)(addr - cast_from_oop
(array)); + JvmtiHeapwalkObject elem(obj.obj(), offset, vk, faklass->layout_kind()); + + visit_object(elem); + + // check if iteration has been halted + if (is_iteration_aborted()) { + return; + } + } +} // Deprecated function to iterate over all objects in the heap void JvmtiTagMap::iterate_over_heap(jvmtiHeapObjectFilter object_filter, @@ -1136,6 +1470,8 @@ void JvmtiTagMap::iterate_over_heap(jvmtiHeapObjectFilter object_filter, VM_HeapIterateOperation op(&blk, &dead_objects); VMThread::execute(&op); } + convert_flat_object_entries(); + // Post events outside of Heap_lock post_dead_objects(&dead_objects); } @@ -1163,6 +1499,8 @@ void JvmtiTagMap::iterate_through_heap(jint heap_filter, VM_HeapIterateOperation op(&blk, &dead_objects); VMThread::execute(&op); } + convert_flat_object_entries(); + // Post events outside of Heap_lock post_dead_objects(&dead_objects); } @@ -1176,7 +1514,7 @@ void JvmtiTagMap::remove_dead_entries_locked(GrowableArray* objects) { } log_info(jvmti, table)("TagMap table needs cleaning%s", ((objects != nullptr) ? " and posting" : "")); - hashmap()->remove_dead_entries(objects); + _hashmap->remove_dead_entries(objects); _needs_cleaning = false; } } @@ -1329,6 +1667,9 @@ class TagObjectCollector : public JvmtiTagMapKeyClosure { jvmtiError JvmtiTagMap::get_objects_with_tags(const jlong* tags, jint count, jint* count_ptr, jobject** object_result_ptr, jlong** tag_result_ptr) { + // ensure flat object conversion is completed + convert_flat_object_entries(); + TagObjectCollector collector(env(), tags, count); { // iterate over all tagged objects @@ -1337,7 +1678,7 @@ jvmtiError JvmtiTagMap::get_objects_with_tags(const jlong* tags, // will race with the gc_notification thread in the tiny // window where the object is not marked but hasn't been notified that // it is collected yet. - entry_iterate(&collector); + _hashmap->entry_iterate(&collector); } return collector.result(count_ptr, object_result_ptr, tag_result_ptr); } @@ -1377,7 +1718,7 @@ class BasicHeapWalkContext: public HeapWalkContext { jvmtiObjectReferenceCallback _object_ref_callback; // used for caching - oop _last_referrer; + JvmtiHeapwalkObject _last_referrer; jlong _last_referrer_tag; public: @@ -1390,7 +1731,7 @@ class BasicHeapWalkContext: public HeapWalkContext { _heap_root_callback(heap_root_callback), _stack_ref_callback(stack_ref_callback), _object_ref_callback(object_ref_callback), - _last_referrer(nullptr), + _last_referrer(), _last_referrer_tag(0) { } @@ -1399,8 +1740,8 @@ class BasicHeapWalkContext: public HeapWalkContext { jvmtiStackReferenceCallback stack_ref_callback() const { return _stack_ref_callback; } jvmtiObjectReferenceCallback object_ref_callback() const { return _object_ref_callback; } - oop last_referrer() const { return _last_referrer; } - void set_last_referrer(oop referrer) { _last_referrer = referrer; } + JvmtiHeapwalkObject last_referrer() const { return _last_referrer; } + void set_last_referrer(const JvmtiHeapwalkObject& referrer) { _last_referrer = referrer; } jlong last_referrer_tag() const { return _last_referrer_tag; } void set_last_referrer_tag(jlong value) { _last_referrer_tag = value; } }; @@ -1473,80 +1814,87 @@ class CallbackInvoker : AllStatic { // context needed for all heap walks static JvmtiTagMap* _tag_map; static const void* _user_data; - static GrowableArray* _visit_stack; - static JVMTIBitSet* _bitset; + static JvmtiHeapwalkVisitStack* _visit_stack; // accessors static JvmtiTagMap* tag_map() { return _tag_map; } static const void* user_data() { return _user_data; } - static GrowableArray* visit_stack() { return _visit_stack; } + static JvmtiHeapwalkVisitStack* visit_stack() { return _visit_stack; } // if the object hasn't been visited then push it onto the visit stack // so that it will be visited later - static inline bool check_for_visit(oop obj) { - if (!_bitset->is_marked(obj)) visit_stack()->push(obj); + static inline bool check_for_visit(const JvmtiHeapwalkObject&obj) { + visit_stack()->check_for_visit(obj); return true; } + // return element count if the obj is array, -1 otherwise + static jint get_array_length(const JvmtiHeapwalkObject& obj) { + if (!obj.klass()->is_array_klass()) { + return -1; + } + assert(!obj.is_flat(), "array cannot be flat"); + return (jint)arrayOop(obj.obj())->length(); + } + + // invoke basic style callbacks static inline bool invoke_basic_heap_root_callback - (jvmtiHeapRootKind root_kind, oop obj); + (jvmtiHeapRootKind root_kind, const JvmtiHeapwalkObject& obj); static inline bool invoke_basic_stack_ref_callback (jvmtiHeapRootKind root_kind, jlong thread_tag, jint depth, jmethodID method, - int slot, oop obj); + int slot, const JvmtiHeapwalkObject& obj); static inline bool invoke_basic_object_reference_callback - (jvmtiObjectReferenceKind ref_kind, oop referrer, oop referree, jint index); + (jvmtiObjectReferenceKind ref_kind, const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint index); // invoke advanced style callbacks static inline bool invoke_advanced_heap_root_callback - (jvmtiHeapReferenceKind ref_kind, oop obj); + (jvmtiHeapReferenceKind ref_kind, const JvmtiHeapwalkObject& obj); static inline bool invoke_advanced_stack_ref_callback (jvmtiHeapReferenceKind ref_kind, jlong thread_tag, jlong tid, int depth, - jmethodID method, jlocation bci, jint slot, oop obj); + jmethodID method, jlocation bci, jint slot, const JvmtiHeapwalkObject& obj); static inline bool invoke_advanced_object_reference_callback - (jvmtiHeapReferenceKind ref_kind, oop referrer, oop referree, jint index); + (jvmtiHeapReferenceKind ref_kind, const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint index); // used to report the value of primitive fields static inline bool report_primitive_field - (jvmtiHeapReferenceKind ref_kind, oop obj, jint index, address addr, char type); + (jvmtiHeapReferenceKind ref_kind, const JvmtiHeapwalkObject& obj, jint index, address addr, char type); public: // initialize for basic mode static void initialize_for_basic_heap_walk(JvmtiTagMap* tag_map, - GrowableArray* visit_stack, const void* user_data, BasicHeapWalkContext context, - JVMTIBitSet* bitset); + JvmtiHeapwalkVisitStack* visit_stack); // initialize for advanced mode static void initialize_for_advanced_heap_walk(JvmtiTagMap* tag_map, - GrowableArray* visit_stack, const void* user_data, AdvancedHeapWalkContext context, - JVMTIBitSet* bitset); + JvmtiHeapwalkVisitStack* visit_stack); // functions to report roots - static inline bool report_simple_root(jvmtiHeapReferenceKind kind, oop o); + static inline bool report_simple_root(jvmtiHeapReferenceKind kind, const JvmtiHeapwalkObject& o); static inline bool report_jni_local_root(jlong thread_tag, jlong tid, jint depth, - jmethodID m, oop o); + jmethodID m, const JvmtiHeapwalkObject& o); static inline bool report_stack_ref_root(jlong thread_tag, jlong tid, jint depth, - jmethodID method, jlocation bci, jint slot, oop o); + jmethodID method, jlocation bci, jint slot, const JvmtiHeapwalkObject& o); // functions to report references - static inline bool report_array_element_reference(oop referrer, oop referree, jint index); - static inline bool report_class_reference(oop referrer, oop referree); - static inline bool report_class_loader_reference(oop referrer, oop referree); - static inline bool report_signers_reference(oop referrer, oop referree); - static inline bool report_protection_domain_reference(oop referrer, oop referree); - static inline bool report_superclass_reference(oop referrer, oop referree); - static inline bool report_interface_reference(oop referrer, oop referree); - static inline bool report_static_field_reference(oop referrer, oop referree, jint slot); - static inline bool report_field_reference(oop referrer, oop referree, jint slot); - static inline bool report_constant_pool_reference(oop referrer, oop referree, jint index); - static inline bool report_primitive_array_values(oop array); - static inline bool report_string_value(oop str); - static inline bool report_primitive_instance_field(oop o, jint index, address value, char type); - static inline bool report_primitive_static_field(oop o, jint index, address value, char type); + static inline bool report_array_element_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint index); + static inline bool report_class_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree); + static inline bool report_class_loader_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree); + static inline bool report_signers_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree); + static inline bool report_protection_domain_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree); + static inline bool report_superclass_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree); + static inline bool report_interface_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree); + static inline bool report_static_field_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint slot); + static inline bool report_field_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint slot); + static inline bool report_constant_pool_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint index); + static inline bool report_primitive_array_values(const JvmtiHeapwalkObject& array); + static inline bool report_string_value(const JvmtiHeapwalkObject& str); + static inline bool report_primitive_instance_field(const JvmtiHeapwalkObject& o, jint index, address value, char type); + static inline bool report_primitive_static_field(const JvmtiHeapwalkObject& o, jint index, address value, char type); }; // statics @@ -1555,42 +1903,37 @@ BasicHeapWalkContext CallbackInvoker::_basic_context; AdvancedHeapWalkContext CallbackInvoker::_advanced_context; JvmtiTagMap* CallbackInvoker::_tag_map; const void* CallbackInvoker::_user_data; -GrowableArray* CallbackInvoker::_visit_stack; -JVMTIBitSet* CallbackInvoker::_bitset; +JvmtiHeapwalkVisitStack* CallbackInvoker::_visit_stack; // initialize for basic heap walk (IterateOverReachableObjects et al) void CallbackInvoker::initialize_for_basic_heap_walk(JvmtiTagMap* tag_map, - GrowableArray* visit_stack, const void* user_data, BasicHeapWalkContext context, - JVMTIBitSet* bitset) { + JvmtiHeapwalkVisitStack* visit_stack) { _tag_map = tag_map; - _visit_stack = visit_stack; _user_data = user_data; _basic_context = context; _advanced_context.invalidate(); // will trigger assertion if used _heap_walk_type = basic; - _bitset = bitset; + _visit_stack = visit_stack; } // initialize for advanced heap walk (FollowReferences) void CallbackInvoker::initialize_for_advanced_heap_walk(JvmtiTagMap* tag_map, - GrowableArray* visit_stack, const void* user_data, AdvancedHeapWalkContext context, - JVMTIBitSet* bitset) { + JvmtiHeapwalkVisitStack* visit_stack) { _tag_map = tag_map; - _visit_stack = visit_stack; _user_data = user_data; _advanced_context = context; _basic_context.invalidate(); // will trigger assertion if used _heap_walk_type = advanced; - _bitset = bitset; + _visit_stack = visit_stack; } // invoke basic style heap root callback -inline bool CallbackInvoker::invoke_basic_heap_root_callback(jvmtiHeapRootKind root_kind, oop obj) { +inline bool CallbackInvoker::invoke_basic_heap_root_callback(jvmtiHeapRootKind root_kind, const JvmtiHeapwalkObject& obj) { // if we heap roots should be reported jvmtiHeapRootCallback cb = basic_context()->heap_root_callback(); if (cb == nullptr) { @@ -1617,7 +1960,7 @@ inline bool CallbackInvoker::invoke_basic_stack_ref_callback(jvmtiHeapRootKind r jint depth, jmethodID method, int slot, - oop obj) { + const JvmtiHeapwalkObject& obj) { // if we stack refs should be reported jvmtiStackReferenceCallback cb = basic_context()->stack_ref_callback(); if (cb == nullptr) { @@ -1644,8 +1987,8 @@ inline bool CallbackInvoker::invoke_basic_stack_ref_callback(jvmtiHeapRootKind r // invoke basic style object reference callback inline bool CallbackInvoker::invoke_basic_object_reference_callback(jvmtiObjectReferenceKind ref_kind, - oop referrer, - oop referree, + const JvmtiHeapwalkObject& referrer, + const JvmtiHeapwalkObject& referree, jint index) { BasicHeapWalkContext* context = basic_context(); @@ -1656,7 +1999,7 @@ inline bool CallbackInvoker::invoke_basic_object_reference_callback(jvmtiObjectR if (referrer == context->last_referrer()) { referrer_tag = context->last_referrer_tag(); } else { - referrer_tag = tag_for(tag_map(), referrer); + referrer_tag = tag_map()->find(referrer); } // do the callback @@ -1688,7 +2031,7 @@ inline bool CallbackInvoker::invoke_basic_object_reference_callback(jvmtiObjectR // invoke advanced style heap root callback inline bool CallbackInvoker::invoke_advanced_heap_root_callback(jvmtiHeapReferenceKind ref_kind, - oop obj) { + const JvmtiHeapwalkObject& obj) { AdvancedHeapWalkContext* context = advanced_context(); // check that callback is provided @@ -1713,7 +2056,7 @@ inline bool CallbackInvoker::invoke_advanced_heap_root_callback(jvmtiHeapReferen } // for arrays we need the length, otherwise -1 - jint len = (jint)(obj->is_array() ? arrayOop(obj)->length() : -1); + jint len = get_array_length(obj); // invoke the callback jint res = (*cb)(ref_kind, @@ -1742,7 +2085,7 @@ inline bool CallbackInvoker::invoke_advanced_stack_ref_callback(jvmtiHeapReferen jmethodID method, jlocation bci, jint slot, - oop obj) { + const JvmtiHeapwalkObject& obj) { AdvancedHeapWalkContext* context = advanced_context(); // check that callback is provider @@ -1776,7 +2119,7 @@ inline bool CallbackInvoker::invoke_advanced_stack_ref_callback(jvmtiHeapReferen reference_info.stack_local.slot = slot; // for arrays we need the length, otherwise -1 - jint len = (jint)(obj->is_array() ? arrayOop(obj)->length() : -1); + jint len = get_array_length(obj); // call into the agent int res = (*cb)(ref_kind, @@ -1809,8 +2152,8 @@ inline bool CallbackInvoker::invoke_advanced_stack_ref_callback(jvmtiHeapReferen // invoke the object reference callback to report a reference inline bool CallbackInvoker::invoke_advanced_object_reference_callback(jvmtiHeapReferenceKind ref_kind, - oop referrer, - oop obj, + const JvmtiHeapwalkObject& referrer, + const JvmtiHeapwalkObject& obj, jint index) { // field index is only valid field in reference_info @@ -1843,7 +2186,7 @@ inline bool CallbackInvoker::invoke_advanced_object_reference_callback(jvmtiHeap reference_info.field.index = index; // for arrays we need the length, otherwise -1 - jint len = (jint)(obj->is_array() ? arrayOop(obj)->length() : -1); + jint len = get_array_length(obj); // invoke the callback int res = (*cb)(ref_kind, @@ -1866,7 +2209,7 @@ inline bool CallbackInvoker::invoke_advanced_object_reference_callback(jvmtiHeap } // report a "simple root" -inline bool CallbackInvoker::report_simple_root(jvmtiHeapReferenceKind kind, oop obj) { +inline bool CallbackInvoker::report_simple_root(jvmtiHeapReferenceKind kind, const JvmtiHeapwalkObject& obj) { assert(kind != JVMTI_HEAP_REFERENCE_STACK_LOCAL && kind != JVMTI_HEAP_REFERENCE_JNI_LOCAL, "not a simple root"); @@ -1882,8 +2225,8 @@ inline bool CallbackInvoker::report_simple_root(jvmtiHeapReferenceKind kind, oop // invoke the primitive array values -inline bool CallbackInvoker::report_primitive_array_values(oop obj) { - assert(obj->is_typeArray(), "not a primitive array"); +inline bool CallbackInvoker::report_primitive_array_values(const JvmtiHeapwalkObject& obj) { + assert(obj.klass()->is_typeArray_klass(), "not a primitive array"); AdvancedHeapWalkContext* context = advanced_context(); assert(context->array_primitive_value_callback() != nullptr, "no callback"); @@ -1911,8 +2254,8 @@ inline bool CallbackInvoker::report_primitive_array_values(oop obj) { } // invoke the string value callback -inline bool CallbackInvoker::report_string_value(oop str) { - assert(str->klass() == vmClasses::String_klass(), "not a string"); +inline bool CallbackInvoker::report_string_value(const JvmtiHeapwalkObject& str) { + assert(str.klass() == vmClasses::String_klass(), "not a string"); AdvancedHeapWalkContext* context = advanced_context(); assert(context->string_primitive_value_callback() != nullptr, "no callback"); @@ -1941,7 +2284,7 @@ inline bool CallbackInvoker::report_string_value(oop str) { // invoke the primitive field callback inline bool CallbackInvoker::report_primitive_field(jvmtiHeapReferenceKind ref_kind, - oop obj, + const JvmtiHeapwalkObject& obj, jint index, address addr, char type) @@ -1989,7 +2332,7 @@ inline bool CallbackInvoker::report_primitive_field(jvmtiHeapReferenceKind ref_k // instance field -inline bool CallbackInvoker::report_primitive_instance_field(oop obj, +inline bool CallbackInvoker::report_primitive_instance_field(const JvmtiHeapwalkObject& obj, jint index, address value, char type) { @@ -2001,7 +2344,7 @@ inline bool CallbackInvoker::report_primitive_instance_field(oop obj, } // static field -inline bool CallbackInvoker::report_primitive_static_field(oop obj, +inline bool CallbackInvoker::report_primitive_static_field(const JvmtiHeapwalkObject& obj, jint index, address value, char type) { @@ -2013,7 +2356,7 @@ inline bool CallbackInvoker::report_primitive_static_field(oop obj, } // report a JNI local (root object) to the profiler -inline bool CallbackInvoker::report_jni_local_root(jlong thread_tag, jlong tid, jint depth, jmethodID m, oop obj) { +inline bool CallbackInvoker::report_jni_local_root(jlong thread_tag, jlong tid, jint depth, jmethodID m, const JvmtiHeapwalkObject& obj) { if (is_basic_heap_walk()) { return invoke_basic_stack_ref_callback(JVMTI_HEAP_ROOT_JNI_LOCAL, thread_tag, @@ -2040,7 +2383,7 @@ inline bool CallbackInvoker::report_stack_ref_root(jlong thread_tag, jmethodID method, jlocation bci, jint slot, - oop obj) { + const JvmtiHeapwalkObject& obj) { if (is_basic_heap_walk()) { return invoke_basic_stack_ref_callback(JVMTI_HEAP_ROOT_STACK_LOCAL, thread_tag, @@ -2061,7 +2404,7 @@ inline bool CallbackInvoker::report_stack_ref_root(jlong thread_tag, } // report an object referencing a class. -inline bool CallbackInvoker::report_class_reference(oop referrer, oop referree) { +inline bool CallbackInvoker::report_class_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree) { if (is_basic_heap_walk()) { return invoke_basic_object_reference_callback(JVMTI_REFERENCE_CLASS, referrer, referree, -1); } else { @@ -2070,7 +2413,7 @@ inline bool CallbackInvoker::report_class_reference(oop referrer, oop referree) } // report a class referencing its class loader. -inline bool CallbackInvoker::report_class_loader_reference(oop referrer, oop referree) { +inline bool CallbackInvoker::report_class_loader_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree) { if (is_basic_heap_walk()) { return invoke_basic_object_reference_callback(JVMTI_REFERENCE_CLASS_LOADER, referrer, referree, -1); } else { @@ -2079,7 +2422,7 @@ inline bool CallbackInvoker::report_class_loader_reference(oop referrer, oop ref } // report a class referencing its signers. -inline bool CallbackInvoker::report_signers_reference(oop referrer, oop referree) { +inline bool CallbackInvoker::report_signers_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree) { if (is_basic_heap_walk()) { return invoke_basic_object_reference_callback(JVMTI_REFERENCE_SIGNERS, referrer, referree, -1); } else { @@ -2088,7 +2431,7 @@ inline bool CallbackInvoker::report_signers_reference(oop referrer, oop referree } // report a class referencing its protection domain.. -inline bool CallbackInvoker::report_protection_domain_reference(oop referrer, oop referree) { +inline bool CallbackInvoker::report_protection_domain_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree) { if (is_basic_heap_walk()) { return invoke_basic_object_reference_callback(JVMTI_REFERENCE_PROTECTION_DOMAIN, referrer, referree, -1); } else { @@ -2097,7 +2440,7 @@ inline bool CallbackInvoker::report_protection_domain_reference(oop referrer, oo } // report a class referencing its superclass. -inline bool CallbackInvoker::report_superclass_reference(oop referrer, oop referree) { +inline bool CallbackInvoker::report_superclass_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree) { if (is_basic_heap_walk()) { // Send this to be consistent with past implementation return invoke_basic_object_reference_callback(JVMTI_REFERENCE_CLASS, referrer, referree, -1); @@ -2107,7 +2450,7 @@ inline bool CallbackInvoker::report_superclass_reference(oop referrer, oop refer } // report a class referencing one of its interfaces. -inline bool CallbackInvoker::report_interface_reference(oop referrer, oop referree) { +inline bool CallbackInvoker::report_interface_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree) { if (is_basic_heap_walk()) { return invoke_basic_object_reference_callback(JVMTI_REFERENCE_INTERFACE, referrer, referree, -1); } else { @@ -2116,7 +2459,7 @@ inline bool CallbackInvoker::report_interface_reference(oop referrer, oop referr } // report a class referencing one of its static fields. -inline bool CallbackInvoker::report_static_field_reference(oop referrer, oop referree, jint slot) { +inline bool CallbackInvoker::report_static_field_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint slot) { if (is_basic_heap_walk()) { return invoke_basic_object_reference_callback(JVMTI_REFERENCE_STATIC_FIELD, referrer, referree, slot); } else { @@ -2125,7 +2468,7 @@ inline bool CallbackInvoker::report_static_field_reference(oop referrer, oop ref } // report an array referencing an element object -inline bool CallbackInvoker::report_array_element_reference(oop referrer, oop referree, jint index) { +inline bool CallbackInvoker::report_array_element_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint index) { if (is_basic_heap_walk()) { return invoke_basic_object_reference_callback(JVMTI_REFERENCE_ARRAY_ELEMENT, referrer, referree, index); } else { @@ -2134,7 +2477,7 @@ inline bool CallbackInvoker::report_array_element_reference(oop referrer, oop re } // report an object referencing an instance field object -inline bool CallbackInvoker::report_field_reference(oop referrer, oop referree, jint slot) { +inline bool CallbackInvoker::report_field_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint slot) { if (is_basic_heap_walk()) { return invoke_basic_object_reference_callback(JVMTI_REFERENCE_FIELD, referrer, referree, slot); } else { @@ -2143,7 +2486,7 @@ inline bool CallbackInvoker::report_field_reference(oop referrer, oop referree, } // report an array referencing an element object -inline bool CallbackInvoker::report_constant_pool_reference(oop referrer, oop referree, jint index) { +inline bool CallbackInvoker::report_constant_pool_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint index) { if (is_basic_heap_walk()) { return invoke_basic_object_reference_callback(JVMTI_REFERENCE_CONSTANT_POOL, referrer, referree, index); } else { @@ -2270,7 +2613,7 @@ class StackRefCollector { bool StackRefCollector::set_thread(oop o) { _threadObj = o; - _thread_tag = tag_for(_tag_map, _threadObj); + _thread_tag = _tag_map->find(_threadObj); _tid = java_lang_Thread::thread_id(_threadObj); _is_top_frame = true; @@ -2403,16 +2746,10 @@ bool StackRefCollector::process_frames(vframe* vf) { // class VM_HeapWalkOperation: public VM_Operation { private: - enum { - initial_visit_stack_size = 4000 - }; - bool _is_advanced_heap_walk; // indicates FollowReferences JvmtiTagMap* _tag_map; Handle _initial_object; - GrowableArray* _visit_stack; // the visit stack - - JVMTIBitSet _bitset; + JvmtiHeapwalkVisitStack _visit_stack; // Dead object tags in JvmtiTagMap GrowableArray* _dead_objects; @@ -2423,10 +2760,6 @@ class VM_HeapWalkOperation: public VM_Operation { bool _reporting_primitive_array_values; bool _reporting_string_values; - GrowableArray* create_visit_stack() { - return new (mtServiceability) GrowableArray(initial_visit_stack_size, mtServiceability); - } - // accessors bool is_advanced_heap_walk() const { return _is_advanced_heap_walk; } JvmtiTagMap* tag_map() const { return _tag_map; } @@ -2438,13 +2771,14 @@ class VM_HeapWalkOperation: public VM_Operation { bool is_reporting_primitive_array_values() const { return _reporting_primitive_array_values; } bool is_reporting_string_values() const { return _reporting_string_values; } - GrowableArray* visit_stack() const { return _visit_stack; } + JvmtiHeapwalkVisitStack* visit_stack() { return &_visit_stack; } // iterate over the various object types - inline bool iterate_over_array(oop o); - inline bool iterate_over_type_array(oop o); - inline bool iterate_over_class(oop o); - inline bool iterate_over_object(oop o); + inline bool iterate_over_array(const JvmtiHeapwalkObject& o); + inline bool iterate_over_flat_array(const JvmtiHeapwalkObject& o); + inline bool iterate_over_type_array(const JvmtiHeapwalkObject& o); + inline bool iterate_over_class(const JvmtiHeapwalkObject& o); + inline bool iterate_over_object(const JvmtiHeapwalkObject& o); // root collection inline bool collect_simple_roots(); @@ -2453,7 +2787,7 @@ class VM_HeapWalkOperation: public VM_Operation { inline bool collect_vthread_stack_refs(oop vt); // visit an object - inline bool visit(oop o); + inline bool visit(const JvmtiHeapwalkObject& o); public: VM_HeapWalkOperation(JvmtiTagMap* tag_map, @@ -2487,10 +2821,8 @@ VM_HeapWalkOperation::VM_HeapWalkOperation(JvmtiTagMap* tag_map, _reporting_primitive_fields = false; _reporting_primitive_array_values = false; _reporting_string_values = false; - _visit_stack = create_visit_stack(); _dead_objects = objects; - - CallbackInvoker::initialize_for_basic_heap_walk(tag_map, _visit_stack, user_data, callbacks, &_bitset); + CallbackInvoker::initialize_for_basic_heap_walk(tag_map, user_data, callbacks, &_visit_stack); } VM_HeapWalkOperation::VM_HeapWalkOperation(JvmtiTagMap* tag_map, @@ -2505,23 +2837,18 @@ VM_HeapWalkOperation::VM_HeapWalkOperation(JvmtiTagMap* tag_map, _reporting_primitive_fields = (callbacks.primitive_field_callback() != nullptr);; _reporting_primitive_array_values = (callbacks.array_primitive_value_callback() != nullptr);; _reporting_string_values = (callbacks.string_primitive_value_callback() != nullptr);; - _visit_stack = create_visit_stack(); _dead_objects = objects; - CallbackInvoker::initialize_for_advanced_heap_walk(tag_map, _visit_stack, user_data, callbacks, &_bitset); + CallbackInvoker::initialize_for_advanced_heap_walk(tag_map, user_data, callbacks, &_visit_stack); } VM_HeapWalkOperation::~VM_HeapWalkOperation() { - if (_following_object_refs) { - assert(_visit_stack != nullptr, "checking"); - delete _visit_stack; - _visit_stack = nullptr; - } } // an array references its class and has a reference to // each element in the array -inline bool VM_HeapWalkOperation::iterate_over_array(oop o) { - objArrayOop array = objArrayOop(o); +inline bool VM_HeapWalkOperation::iterate_over_array(const JvmtiHeapwalkObject& o) { + assert(!o.is_flat(), "Array object cannot be flattened"); + objArrayOop array = objArrayOop(o.obj()); // array reference to its class oop mirror = ObjArrayKlass::cast(array->klass())->java_mirror(); @@ -2545,9 +2872,48 @@ inline bool VM_HeapWalkOperation::iterate_over_array(oop o) { return true; } +// similar to iterate_over_array(), but itrates over flat array +inline bool VM_HeapWalkOperation::iterate_over_flat_array(const JvmtiHeapwalkObject& o) { + assert(!o.is_flat(), "Array object cannot be flattened"); + flatArrayOop array = flatArrayOop(o.obj()); + FlatArrayKlass* faklass = FlatArrayKlass::cast(array->klass()); + InlineKlass* vk = InlineKlass::cast(faklass->element_klass()); + bool need_null_check = faklass->layout_kind() == LayoutKind::NULLABLE_ATOMIC_FLAT; + + // array reference to its class + oop mirror = faklass->java_mirror(); + if (!CallbackInvoker::report_class_reference(o, mirror)) { + return false; + } + + // iterate over the array and report each reference to a + // non-null element + for (int index = 0; index < array->length(); index++) { + address addr = (address)array->value_at_addr(index, faklass->layout_helper()); + + // check for null + if (need_null_check) { + if (vk->is_payload_marked_as_null(addr)) { + continue; + } + } + + // offset in the array oop + int offset = (int)(addr - cast_from_oop
(array)); + JvmtiHeapwalkObject elem(o.obj(), offset, vk, faklass->layout_kind()); + + // report the array reference + if (!CallbackInvoker::report_array_element_reference(o, elem, index)) { + return false; + } + } + return true; +} + // a type array references its class -inline bool VM_HeapWalkOperation::iterate_over_type_array(oop o) { - Klass* k = o->klass(); +inline bool VM_HeapWalkOperation::iterate_over_type_array(const JvmtiHeapwalkObject& o) { + assert(!o.is_flat(), "Array object cannot be flattened"); + Klass* k = o.klass(); oop mirror = k->java_mirror(); if (!CallbackInvoker::report_class_reference(o, mirror)) { return false; @@ -2581,9 +2947,10 @@ static inline bool verify_static_oop(InstanceKlass* ik, // a class references its super class, interfaces, class loader, ... // and finally its static fields -inline bool VM_HeapWalkOperation::iterate_over_class(oop java_class) { +inline bool VM_HeapWalkOperation::iterate_over_class(const JvmtiHeapwalkObject& o) { + assert(!o.is_flat(), "Klass object cannot be flattened"); + Klass* klass = java_lang_Class::as_Klass(o.obj()); int i; - Klass* klass = java_lang_Class::as_Klass(java_class); if (klass->is_instance_klass()) { InstanceKlass* ik = InstanceKlass::cast(klass); @@ -2594,7 +2961,8 @@ inline bool VM_HeapWalkOperation::iterate_over_class(oop java_class) { } // get the java mirror - oop mirror = klass->java_mirror(); + oop mirror_oop = klass->java_mirror(); + JvmtiHeapwalkObject mirror(mirror_oop); // super (only if something more interesting than java.lang.Object) InstanceKlass* super_klass = ik->super(); @@ -2683,8 +3051,8 @@ inline bool VM_HeapWalkOperation::iterate_over_class(oop java_class) { ClassFieldDescriptor* field = field_map->field_at(i); char type = field->field_type(); if (!is_primitive_field_type(type)) { - oop fld_o = mirror->obj_field(field->field_offset()); - assert(verify_static_oop(ik, mirror, field->field_offset()), "sanity check"); + oop fld_o = mirror_oop->obj_field(field->field_offset()); + assert(verify_static_oop(ik, mirror_oop, field->field_offset()), "sanity check"); if (fld_o != nullptr) { int slot = field->field_index(); if (!CallbackInvoker::report_static_field_reference(mirror, fld_o, slot)) { @@ -2694,7 +3062,7 @@ inline bool VM_HeapWalkOperation::iterate_over_class(oop java_class) { } } else { if (is_reporting_primitive_fields()) { - address addr = cast_from_oop
(mirror) + field->field_offset(); + address addr = cast_from_oop
(mirror_oop) + field->field_offset(); int slot = field->field_index(); if (!CallbackInvoker::report_primitive_static_field(mirror, slot, addr, type)) { delete field_map; @@ -2714,33 +3082,51 @@ inline bool VM_HeapWalkOperation::iterate_over_class(oop java_class) { // an object references a class and its instance fields // (static fields are ignored here as we report these as // references from the class). -inline bool VM_HeapWalkOperation::iterate_over_object(oop o) { +inline bool VM_HeapWalkOperation::iterate_over_object(const JvmtiHeapwalkObject& o) { // reference to the class - if (!CallbackInvoker::report_class_reference(o, o->klass()->java_mirror())) { + if (!CallbackInvoker::report_class_reference(o, o.klass()->java_mirror())) { return false; } // iterate over instance fields - ClassFieldMap* field_map = JvmtiCachedClassFieldMap::get_map_of_instance_fields(o); + ClassFieldMap* field_map = JvmtiCachedClassFieldMap::get_map_of_instance_fields(o.klass()); for (int i=0; ifield_count(); i++) { ClassFieldDescriptor* field = field_map->field_at(i); char type = field->field_type(); + int slot = field->field_index(); + int field_offset = field->field_offset(); + if (o.is_flat()) { + // the object is inlined, its fields are stored without the header + field_offset += o.offset() - o.inline_klass()->payload_offset(); + } if (!is_primitive_field_type(type)) { - oop fld_o = o->obj_field_access(field->field_offset()); - // ignore any objects that aren't visible to profiler - if (fld_o != nullptr) { - assert(Universe::heap()->is_in(fld_o), "unsafe code should not " - "have references to Klass* anymore"); - int slot = field->field_index(); - if (!CallbackInvoker::report_field_reference(o, fld_o, slot)) { + if (field->is_flat()) { + // check for possible nulls + bool can_be_null = field->layout_kind() == LayoutKind::NULLABLE_ATOMIC_FLAT; + if (can_be_null) { + address payload = cast_from_oop
(o.obj()) + field_offset; + if (field->inline_klass()->is_payload_marked_as_null(payload)) { + continue; + } + } + JvmtiHeapwalkObject field_obj(o.obj(), field_offset, field->inline_klass(), field->layout_kind()); + if (!CallbackInvoker::report_field_reference(o, field_obj, slot)) { return false; } + } else { + oop fld_o = o.obj()->obj_field_access(field_offset); + // ignore any objects that aren't visible to profiler + if (fld_o != nullptr) { + assert(Universe::heap()->is_in(fld_o), "unsafe code should not have references to Klass* anymore"); + if (!CallbackInvoker::report_field_reference(o, fld_o, slot)) { + return false; + } + } } } else { if (is_reporting_primitive_fields()) { // primitive instance field - address addr = cast_from_oop
(o) + field->field_offset(); - int slot = field->field_index(); + address addr = cast_from_oop
(o.obj()) + field_offset; if (!CallbackInvoker::report_primitive_instance_field(o, slot, addr, type)) { return false; } @@ -2750,7 +3136,7 @@ inline bool VM_HeapWalkOperation::iterate_over_object(oop o) { // if the object is a java.lang.String if (is_reporting_string_values() && - o->klass() == vmClasses::String_klass()) { + o.klass() == vmClasses::String_klass()) { if (!CallbackInvoker::report_string_value(o)) { return false; } @@ -2819,7 +3205,7 @@ inline bool VM_HeapWalkOperation::collect_stack_refs(JavaThread* java_thread, return false; } // no last java frame but there may be JNI locals - blk->set_context(tag_for(_tag_map, threadObj), java_lang_Thread::thread_id(threadObj), 0, (jmethodID)nullptr); + blk->set_context(_tag_map->find(threadObj), java_lang_Thread::thread_id(threadObj), 0, (jmethodID)nullptr); java_thread->active_handles()->oops_do(blk); return !blk->stopped(); } @@ -2919,23 +3305,26 @@ inline bool VM_HeapWalkOperation::collect_vthread_stack_refs(oop vt) { // second get all the outbound references from this object (in other words, all // the objects referenced by this object). // -bool VM_HeapWalkOperation::visit(oop o) { +bool VM_HeapWalkOperation::visit(const JvmtiHeapwalkObject& o) { // mark object as visited - assert(!_bitset.is_marked(o), "can't visit same object more than once"); - _bitset.mark_obj(o); + assert(!visit_stack()->is_visited(o), "can't visit same object more than once"); + visit_stack()->mark_visited(o); + Klass* klass = o.klass(); // instance - if (o->is_instance()) { - if (o->klass() == vmClasses::Class_klass()) { - if (!java_lang_Class::is_primitive(o)) { + if (klass->is_instance_klass()) { + if (klass == vmClasses::Class_klass()) { + assert(!o.is_flat(), "Class object cannot be flattened"); + if (!java_lang_Class::is_primitive(o.obj())) { // a java.lang.Class return iterate_over_class(o); } } else { // we report stack references only when initial object is not specified // (in the case we start from heap roots which include platform thread stack references) - if (initial_object().is_null() && java_lang_VirtualThread::is_subclass(o->klass())) { - if (!collect_vthread_stack_refs(o)) { + if (initial_object().is_null() && java_lang_VirtualThread::is_subclass(klass)) { + assert(!o.is_flat(), "VirtualThread object cannot be flattened"); + if (!collect_vthread_stack_refs(o.obj())) { return false; } } @@ -2943,13 +3332,18 @@ bool VM_HeapWalkOperation::visit(oop o) { } } + // flat object array + if (klass->is_flatArray_klass()) { + return iterate_over_flat_array(o); + } + // object array - if (o->is_objArray()) { + if (klass->is_objArray_klass()) { return iterate_over_array(o); } // type array - if (o->is_typeArray()) { + if (klass->is_typeArray_klass()) { return iterate_over_type_array(o); } @@ -2981,8 +3375,8 @@ void VM_HeapWalkOperation::doit() { // visit each object until all reachable objects have been // visited or the callback asked to terminate the iteration. while (!visit_stack()->is_empty()) { - oop o = visit_stack()->pop(); - if (!_bitset.is_marked(o)) { + const JvmtiHeapwalkObject o = visit_stack()->pop(); + if (!visit_stack()->is_visited(o)) { if (!visit(o)) { break; } @@ -3011,6 +3405,8 @@ void JvmtiTagMap::iterate_over_reachable_objects(jvmtiHeapRootCallback heap_root VM_HeapWalkOperation op(this, Handle(), context, user_data, &dead_objects); VMThread::execute(&op); } + convert_flat_object_entries(); + // Post events outside of Heap_lock post_dead_objects(&dead_objects); } @@ -3033,6 +3429,8 @@ void JvmtiTagMap::iterate_over_objects_reachable_from_object(jobject object, VM_HeapWalkOperation op(this, initial_object, context, user_data, &dead_objects); VMThread::execute(&op); } + convert_flat_object_entries(); + // Post events outside of Heap_lock post_dead_objects(&dead_objects); } @@ -3065,6 +3463,8 @@ void JvmtiTagMap::follow_references(jint heap_filter, VM_HeapWalkOperation op(this, initial_object, context, user_data, &dead_objects); VMThread::execute(&op); } + convert_flat_object_entries(); + // Post events outside of Heap_lock post_dead_objects(&dead_objects); } diff --git a/src/hotspot/share/prims/jvmtiTagMap.hpp b/src/hotspot/share/prims/jvmtiTagMap.hpp index 08ab52ed686..0d260c9047c 100644 --- a/src/hotspot/share/prims/jvmtiTagMap.hpp +++ b/src/hotspot/share/prims/jvmtiTagMap.hpp @@ -32,7 +32,9 @@ class JvmtiEnv; class JvmtiTagMapTable; +class JvmtiFlatTagMapTable; class JvmtiTagMapKeyClosure; +class JvmtiHeapwalkObject; class JvmtiTagMap : public CHeapObj { private: @@ -40,11 +42,15 @@ class JvmtiTagMap : public CHeapObj { JvmtiEnv* _env; // the jvmti environment Monitor _lock; // lock for this tag map JvmtiTagMapTable* _hashmap; // the hashmap for tags + JvmtiFlatTagMapTable* _flat_hashmap; + bool _needs_cleaning; bool _posting_events; static bool _has_object_free_events; + bool _converting_flat_object; + // create a tag map JvmtiTagMap(JvmtiEnv* env); @@ -53,19 +59,25 @@ class JvmtiTagMap : public CHeapObj { void check_hashmap(GrowableArray* objects); - void entry_iterate(JvmtiTagMapKeyClosure* closure); + // moves entries from _flat_hashmap to _hashmap + void convert_flat_object_entries(); + + public: + // for inernal use + jlong find(const JvmtiHeapwalkObject& obj) const; + void add(const JvmtiHeapwalkObject& obj, jlong tag); + void remove(const JvmtiHeapwalkObject& obj); public: // indicates if this tag map is locked - bool is_locked() { return lock()->is_locked(); } + bool is_locked() const { return lock()->is_locked(); } + inline const Monitor* lock() const { return &_lock; } inline Monitor* lock() { return &_lock; } - JvmtiTagMapTable* hashmap() { return _hashmap; } - // returns true if the hashmaps are empty - bool is_empty(); + bool is_empty() const; - // return tag for the given environment + // return tag map for the given environment static JvmtiTagMap* tag_map_for(JvmtiEnv* env); // destroy tag map @@ -118,7 +130,7 @@ class JvmtiTagMap : public CHeapObj { static void gc_notification(size_t num_dead_entries) NOT_JVMTI_RETURN; void flush_object_free_events(); - void clear(); // Clear tagmap table after the env is disposed. + void clear(); // Clear hash tables after the env is disposed. // For ServiceThread static void flush_all_object_free_events() NOT_JVMTI_RETURN; diff --git a/src/hotspot/share/prims/jvmtiTagMapTable.cpp b/src/hotspot/share/prims/jvmtiTagMapTable.cpp index 968a95eecd2..8db8ecd0fe3 100644 --- a/src/hotspot/share/prims/jvmtiTagMapTable.cpp +++ b/src/hotspot/share/prims/jvmtiTagMapTable.cpp @@ -24,43 +24,189 @@ #include "memory/allocation.hpp" #include "memory/universe.hpp" +#include "oops/fieldStreams.inline.hpp" +#include "oops/inlineKlass.hpp" #include "oops/oop.inline.hpp" #include "oops/weakHandle.inline.hpp" #include "prims/jvmtiExport.hpp" #include "prims/jvmtiTagMapTable.hpp" -JvmtiTagMapKey::JvmtiTagMapKey(oop obj) : _obj(obj) {} +static unsigned get_value_object_hash(oop holder, int offset, Klass* klass) { + assert(klass->is_inline_klass(), "Must be InlineKlass"); + // For inline types, use the klass as a hash code and let the equals match the obj. + // It might have a long bucket but TBD to improve this if a customer situation arises. + return (unsigned)((int64_t)klass >> 3); +} + +static unsigned get_value_object_hash(const JvmtiHeapwalkObject & obj) { + assert(obj.is_value(), "Must be value class"); + return get_value_object_hash(obj.obj(), obj.offset(), obj.inline_klass()); +} + +static bool equal_oops(oop obj1, oop obj2); // forward declaration -JvmtiTagMapKey::JvmtiTagMapKey(const JvmtiTagMapKey& src) { - // move object into WeakHandle when copying into the table +static bool equal_fields(char type, oop obj1, int offset1, oop obj2, int offset2) { + switch (type) { + case JVM_SIGNATURE_BOOLEAN: + return obj1->bool_field(offset1) == obj2->bool_field(offset2); + case JVM_SIGNATURE_CHAR: + return obj1->char_field(offset1) == obj2->char_field(offset2); + case JVM_SIGNATURE_FLOAT: + return obj1->float_field(offset1) == obj2->float_field(offset2); + case JVM_SIGNATURE_DOUBLE: + return obj1->double_field(offset1) == obj2->double_field(offset2); + case JVM_SIGNATURE_BYTE: + return obj1->byte_field(offset1) == obj2->byte_field(offset2); + case JVM_SIGNATURE_SHORT: + return obj1->short_field(offset1) == obj2->short_field(offset2); + case JVM_SIGNATURE_INT: + return obj1->int_field(offset1) == obj2->int_field(offset2); + case JVM_SIGNATURE_LONG: + return obj1->long_field(offset1) == obj2->long_field(offset2); + case JVM_SIGNATURE_CLASS: + case JVM_SIGNATURE_ARRAY: + return equal_oops(obj1->obj_field(offset1), obj2->obj_field(offset2)); + } + ShouldNotReachHere(); +} + +static bool is_null_flat_field(oop obj, int offset, InlineKlass* klass) { + return klass->is_payload_marked_as_null(cast_from_oop
(obj) + offset); +} + +// For heap-allocated objects offset is 0 and 'klass' is obj1->klass() (== obj2->klass()). +// For flattened objects offset is the offset in the holder object, 'klass' is inlined object class. +// The object must be prechecked for non-null values. +static bool equal_value_objects(oop obj1, int offset1, oop obj2, int offset2, InlineKlass* klass) { + for (JavaFieldStream fld(klass); !fld.done(); fld.next()) { + // ignore static fields + if (fld.access_flags().is_static()) { + continue; + } + int field_offset1 = offset1 + fld.offset() - (offset1 > 0 ? klass->payload_offset() : 0); + int field_offset2 = offset2 + fld.offset() - (offset2 > 0 ? klass->payload_offset() : 0); + if (fld.is_flat()) { // flat value field + InstanceKlass* holder_klass = fld.field_holder(); + InlineKlass* field_klass = holder_klass->get_inline_type_field_klass(fld.index()); + if (!fld.is_null_free_inline_type()) { + bool field1_is_null = is_null_flat_field(obj1, field_offset1, field_klass); + bool field2_is_null = is_null_flat_field(obj2, field_offset2, field_klass); + if (field1_is_null != field2_is_null) { + return false; + } + if (field1_is_null) { // if both fields are null, go to next field + continue; + } + } + + if (!equal_value_objects(obj1, field_offset1, obj2, field_offset2, field_klass)) { + return false; + } + } else { + if (!equal_fields(fld.signature()->char_at(0), obj1, field_offset1, obj2, field_offset2)) { + return false; + } + } + } + return true; +} + +// handles null oops +static bool equal_oops(oop obj1, oop obj2) { + if (obj1 == obj2) { + return true; + } + + if (obj1 != nullptr && obj2 != nullptr && obj1->klass() == obj2->klass() && obj1->is_inline_type()) { + InlineKlass* vk = InlineKlass::cast(obj1->klass()); + return equal_value_objects(obj1, 0, obj2, 0, vk); + } + return false; +} + + +bool JvmtiHeapwalkObject::equals(const JvmtiHeapwalkObject& obj1, const JvmtiHeapwalkObject& obj2) { + if (obj1 == obj2) { // the same oop/offset/inline_klass + return true; + } + + if (obj1.is_value() && obj1.inline_klass() == obj2.inline_klass()) { + // instances of the same value class + return equal_value_objects(obj1.obj(), obj1.offset(), obj2.obj(), obj2.offset(), obj1.inline_klass()); + } + return false; +} + + +JvmtiTagMapKey::JvmtiTagMapKey(const JvmtiHeapwalkObject* obj) : _obj(obj) { +} + +JvmtiTagMapKey::JvmtiTagMapKey(const JvmtiTagMapKey& src): _h() { if (src._obj != nullptr) { + // move object into Handle when copying into the table + assert(!src._obj->is_flat(), "cannot put flat object to JvmtiTagMapKey"); + _is_weak = !src._obj->is_value(); // obj was read with AS_NO_KEEPALIVE, or equivalent, like during // a heap walk. The object needs to be kept alive when it is published. - Universe::heap()->keep_alive(src._obj); + Universe::heap()->keep_alive(src._obj->obj()); - _wh = WeakHandle(JvmtiExport::weak_tag_storage(), src._obj); + if (_is_weak) { + _wh = WeakHandle(JvmtiExport::weak_tag_storage(), src._obj->obj()); + } else { + _h = OopHandle(JvmtiExport::jvmti_oop_storage(), src._obj->obj()); + } } else { // resizing needs to create a copy. - _wh = src._wh; + _is_weak = src._is_weak; + if (_is_weak) { + _wh = src._wh; + } else { + _h = src._h; + } } // obj is always null after a copy. _obj = nullptr; } -void JvmtiTagMapKey::release_weak_handle() { - _wh.release(JvmtiExport::weak_tag_storage()); +void JvmtiTagMapKey::release_handle() { + if (_is_weak) { + _wh.release(JvmtiExport::weak_tag_storage()); + } else { + _h.release(JvmtiExport::jvmti_oop_storage()); + } +} + +JvmtiHeapwalkObject JvmtiTagMapKey::heapwalk_object() const { + return _obj != nullptr ? JvmtiHeapwalkObject(_obj->obj(), _obj->offset(), _obj->inline_klass(), _obj->layout_kind()) + : JvmtiHeapwalkObject(object_no_keepalive()); } oop JvmtiTagMapKey::object() const { assert(_obj == nullptr, "Must have a handle and not object"); - return _wh.resolve(); + return _is_weak ? _wh.resolve() : _h.resolve(); } oop JvmtiTagMapKey::object_no_keepalive() const { assert(_obj == nullptr, "Must have a handle and not object"); - return _wh.peek(); + return _is_weak ? _wh.peek() : _h.peek(); +} + +unsigned JvmtiTagMapKey::get_hash(const JvmtiTagMapKey& entry) { + const JvmtiHeapwalkObject* obj = entry._obj; + assert(obj != nullptr, "must lookup obj to hash"); + if (obj->is_value()) { + return get_value_object_hash(*obj); + } else { + return (unsigned)obj->obj()->identity_hash(); + } +} + +bool JvmtiTagMapKey::equals(const JvmtiTagMapKey& lhs, const JvmtiTagMapKey& rhs) { + JvmtiHeapwalkObject lhs_obj = lhs.heapwalk_object(); + JvmtiHeapwalkObject rhs_obj = rhs.heapwalk_object(); + return JvmtiHeapwalkObject::equals(lhs_obj, rhs_obj); } static const int INITIAL_TABLE_SIZE = 1007; @@ -71,7 +217,7 @@ JvmtiTagMapTable::JvmtiTagMapTable() : _table(INITIAL_TABLE_SIZE, MAX_TABLE_SIZE void JvmtiTagMapTable::clear() { struct RemoveAll { bool do_entry(JvmtiTagMapKey& entry, const jlong& tag) { - entry.release_weak_handle(); + entry.release_handle(); return true; } } remove_all; @@ -88,25 +234,33 @@ JvmtiTagMapTable::~JvmtiTagMapTable() { clear(); } -jlong JvmtiTagMapTable::find(oop obj) { +jlong* JvmtiTagMapTable::lookup(const JvmtiHeapwalkObject& obj) const { if (is_empty()) { return 0; } - if (obj->fast_no_hash_check()) { - // Objects in the table all have a hashcode. - return 0; + if (!obj.is_value()) { + if (obj.obj()->fast_no_hash_check()) { + // Objects in the table all have a hashcode, unless inlined types. + return nullptr; + } } + JvmtiTagMapKey entry(&obj); + jlong* found = _table.get(entry); + return found; +} + - JvmtiTagMapKey jtme(obj); - jlong* found = _table.get(jtme); +jlong JvmtiTagMapTable::find(const JvmtiHeapwalkObject& obj) const { + jlong* found = lookup(obj); return found == nullptr ? 0 : *found; } -void JvmtiTagMapTable::add(oop obj, jlong tag) { - JvmtiTagMapKey new_entry(obj); +void JvmtiTagMapTable::add(const JvmtiHeapwalkObject& obj, jlong tag) { + assert(!obj.is_flat(), "Cannot add flat object to JvmtiTagMapTable"); + JvmtiTagMapKey new_entry(&obj); bool is_added; - if (obj->fast_no_hash_check()) { + if (!obj.is_value() && obj.obj()->fast_no_hash_check()) { // Can't be in the table so add it fast. is_added = _table.put_when_absent(new_entry, tag); } else { @@ -122,12 +276,21 @@ void JvmtiTagMapTable::add(oop obj, jlong tag) { } } -void JvmtiTagMapTable::remove(oop obj) { - JvmtiTagMapKey jtme(obj); - auto clean = [] (JvmtiTagMapKey& entry, jlong tag) { - entry.release_weak_handle(); +bool JvmtiTagMapTable::update(const JvmtiHeapwalkObject& obj, jlong tag) { + jlong* found = lookup(obj); + if (found == nullptr) { + return false; + } + *found = tag; + return true; +} + +bool JvmtiTagMapTable::remove(const JvmtiHeapwalkObject& obj) { + JvmtiTagMapKey entry(&obj); + auto clean = [](JvmtiTagMapKey & entry, jlong tag) { + entry.release_handle(); }; - _table.remove(jtme, clean); + return _table.remove(entry, clean); } void JvmtiTagMapTable::entry_iterate(JvmtiTagMapKeyClosure* closure) { @@ -143,7 +306,7 @@ void JvmtiTagMapTable::remove_dead_entries(GrowableArray* objects) { if (_objects != nullptr) { _objects->append(tag); } - entry.release_weak_handle(); + entry.release_handle(); return true; } return false;; @@ -151,3 +314,120 @@ void JvmtiTagMapTable::remove_dead_entries(GrowableArray* objects) { } is_dead(objects); _table.unlink(&is_dead); } + + +JvmtiFlatTagMapKey::JvmtiFlatTagMapKey(const JvmtiHeapwalkObject& obj) + : _holder(obj.obj()), _offset(obj.offset()), _inline_klass(obj.inline_klass()), _layout_kind(obj.layout_kind()) { +} + +JvmtiFlatTagMapKey::JvmtiFlatTagMapKey(const JvmtiFlatTagMapKey& src) : _h() { + // move object into Handle when copying into the table + if (src._holder != nullptr) { + // Holder object was read with AS_NO_KEEPALIVE. Needs to be kept alive when it is published. + Universe::heap()->keep_alive(src._holder); + _h = OopHandle(JvmtiExport::jvmti_oop_storage(), src._holder); + } else { + // resizing needs to create a copy. + _h = src._h; + } + // holder object is always null after a copy. + _holder = nullptr; + _offset = src._offset; + _inline_klass = src._inline_klass; + _layout_kind = src._layout_kind; +} + +JvmtiHeapwalkObject JvmtiFlatTagMapKey::heapwalk_object() const { + return JvmtiHeapwalkObject(_holder != nullptr ? _holder : holder_no_keepalive(), _offset, _inline_klass, _layout_kind); +} + +oop JvmtiFlatTagMapKey::holder() const { + assert(_holder == nullptr, "Must have a handle and not object"); + return _h.resolve(); +} + +oop JvmtiFlatTagMapKey::holder_no_keepalive() const { + assert(_holder == nullptr, "Must have a handle and not object"); + return _h.peek(); + +} + +void JvmtiFlatTagMapKey::release_handle() { + _h.release(JvmtiExport::jvmti_oop_storage()); +} + +unsigned JvmtiFlatTagMapKey::get_hash(const JvmtiFlatTagMapKey& entry) { + return get_value_object_hash(entry._holder, entry._offset, entry._inline_klass); +} + +bool JvmtiFlatTagMapKey::equals(const JvmtiFlatTagMapKey& lhs, const JvmtiFlatTagMapKey& rhs) { + if (lhs._inline_klass == rhs._inline_klass) { + oop lhs_obj = lhs._holder != nullptr ? lhs._holder : lhs._h.peek(); + oop rhs_obj = rhs._holder != nullptr ? rhs._holder : rhs._h.peek(); + return equal_value_objects(lhs_obj, lhs._offset, rhs_obj, rhs._offset, lhs._inline_klass); + } + return false; +} + + +JvmtiFlatTagMapTable::JvmtiFlatTagMapTable(): _table(INITIAL_TABLE_SIZE, MAX_TABLE_SIZE) {} + +JvmtiFlatTagMapTable::~JvmtiFlatTagMapTable() { + clear(); +} + +jlong JvmtiFlatTagMapTable::find(const JvmtiHeapwalkObject& obj) const { + if (is_empty()) { + return 0; + } + + JvmtiFlatTagMapKey entry(obj); + jlong* found = _table.get(entry); + return found == nullptr ? 0 : *found; +} + +void JvmtiFlatTagMapTable::add(const JvmtiHeapwalkObject& obj, jlong tag) { + assert(obj.is_value() && obj.is_flat(), "Must be flattened value object"); + JvmtiFlatTagMapKey entry(obj); + bool is_added; + jlong* value = _table.put_if_absent(entry, tag, &is_added); + *value = tag; // assign the new tag + if (is_added) { + if (_table.maybe_grow(5, true /* use_large_table_sizes */)) { + int max_bucket_size = DEBUG_ONLY(_table.verify()) NOT_DEBUG(0); + log_info(jvmti, table) ("JvmtiFlatTagMapTable table resized to %d for %d entries max bucket %d", + _table.table_size(), _table.number_of_entries(), max_bucket_size); + } + } +} + +jlong JvmtiFlatTagMapTable::remove(const JvmtiHeapwalkObject& obj) { + JvmtiFlatTagMapKey entry(obj); + jlong ret = 0; + auto clean = [&](JvmtiFlatTagMapKey& entry, jlong tag) { + ret = tag; + entry.release_handle(); + }; + _table.remove(entry, clean); + return ret; +} + +void JvmtiFlatTagMapTable::entry_iterate(JvmtiFlatTagMapKeyClosure* closure) { + _table.iterate(closure); +} + +void JvmtiFlatTagMapTable::clear() { + struct RemoveAll { + bool do_entry(JvmtiFlatTagMapKey& entry, const jlong& tag) { + entry.release_handle(); + return true; + } + } remove_all; + // The unlink method of ResourceHashTable gets a pointer to a type whose 'do_entry(K,V)' method is callled + // while iterating over all the elements of the table. If the do_entry() method returns true the element + // will be removed. + // In this case, we always return true from do_entry to clear all the elements. + _table.unlink(&remove_all); + + assert(_table.number_of_entries() == 0, "should have removed all entries"); +} diff --git a/src/hotspot/share/prims/jvmtiTagMapTable.hpp b/src/hotspot/share/prims/jvmtiTagMapTable.hpp index f717552da2b..f46eef4b363 100644 --- a/src/hotspot/share/prims/jvmtiTagMapTable.hpp +++ b/src/hotspot/share/prims/jvmtiTagMapTable.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 @@ -27,41 +27,93 @@ #include "gc/shared/collectedHeap.hpp" #include "memory/allocation.hpp" +#include "oops/layoutKind.hpp" #include "oops/weakHandle.hpp" #include "utilities/resizableHashTable.hpp" class JvmtiEnv; -class JvmtiTagMapKeyClosure; + +// Describes an object which can be tagged during heap walk operation. +// - generic heap object: _obj: oop, offset == 0, _inline_klass == nullptr; +// - value heap object: _obj: oop, offset == 0, _inline_klass == _obj.klass(); +// - flat value object: _obj: holder object, offset == offset in the holder, _inline_klass == klass of the flattened object; +class JvmtiHeapwalkObject { + oop _obj; // for flattened value object this is holder object + int _offset; // == 0 for heap objects + InlineKlass* _inline_klass; // for value object, nullptr otherwise + LayoutKind _layout_kind; // layout kind in holder object, used only for flat->heap conversion + + static InlineKlass* inline_klass_or_null(oop obj) { + Klass* k = obj->klass(); + return k->is_inline_klass() ? InlineKlass::cast(k) : nullptr; + } +public: + JvmtiHeapwalkObject(): _obj(nullptr), _offset(0), _inline_klass(nullptr), _layout_kind(LayoutKind::UNKNOWN) {} + JvmtiHeapwalkObject(oop obj): _obj(obj), _offset(0), _inline_klass(inline_klass_or_null(obj)), _layout_kind(LayoutKind::REFERENCE) {} + JvmtiHeapwalkObject(oop obj, int offset, InlineKlass* ik, LayoutKind lk): _obj(obj), _offset(offset), _inline_klass(ik), _layout_kind(lk) {} + + inline bool is_empty() const { return _obj == nullptr; } + inline bool is_value() const { return _inline_klass != nullptr; } + inline bool is_flat() const { return _offset != 0; } + + inline oop obj() const { return _obj; } + inline int offset() const { return _offset; } + inline InlineKlass* inline_klass() const { return _inline_klass; } + inline LayoutKind layout_kind() const { return _layout_kind; } + + inline Klass* klass() const { return is_value() ? _inline_klass : obj()->klass(); } + + static bool equals(const JvmtiHeapwalkObject& obj1, const JvmtiHeapwalkObject& obj2); + + bool operator==(const JvmtiHeapwalkObject& other) const { + // need to compare inline_klass too to handle the case when flat object has flat field at offset 0 + return _obj == other._obj && _offset == other._offset && _inline_klass == other._inline_klass; + } + bool operator!=(const JvmtiHeapwalkObject& other) const { + return !(*this == other); + } +}; + // The oop is needed for lookup rather than creating a WeakHandle during // lookup because the HeapWalker may walk soon to be dead objects and // creating a WeakHandle for an otherwise dead object makes G1 unhappy. // -// This class is the Key type for inserting in ResizeableResourceHashTable +// This class is the Key type for inserting in ResizeableHashTable // Its get_hash() and equals() methods are also used for getting the hash // value of a Key and comparing two Keys, respectively. +// +// Value objects: keep just one tag for all equal value objects including heap allocated value objects. +// We have to keep a strong reference to each unique value object with a non-zero tag. +// During heap walking flattened value object tags stored in separate JvmtiFlatTagMapTable, +// converted to standard strong entries in JvmtiTagMapTable outside of sefepoint. +// All equal value objects should have the same tag. +// Keep value objects alive (1 copy for each "value") until their tags are removed. + class JvmtiTagMapKey : public CHeapObj { - WeakHandle _wh; - oop _obj; // temporarily hold obj while searching + union { + WeakHandle _wh; + OopHandle _h; // for value objects (_is_weak == false) + }; + bool _is_weak; + // temporarily hold obj while searching + const JvmtiHeapwalkObject* _obj; + public: - JvmtiTagMapKey(oop obj); + JvmtiTagMapKey(const JvmtiHeapwalkObject* obj); + // Copy ctor is called when we put entry to the hash table. JvmtiTagMapKey(const JvmtiTagMapKey& src); + JvmtiTagMapKey& operator=(const JvmtiTagMapKey&) = delete; + JvmtiHeapwalkObject heapwalk_object() const; + oop object() const; oop object_no_keepalive() const; - void release_weak_handle(); - - static unsigned get_hash(const JvmtiTagMapKey& entry) { - assert(entry._obj != nullptr, "must lookup obj to hash"); - return (unsigned)entry._obj->identity_hash(); - } + void release_handle(); - static bool equals(const JvmtiTagMapKey& lhs, const JvmtiTagMapKey& rhs) { - oop lhs_obj = lhs._obj != nullptr ? lhs._obj : lhs.object_no_keepalive(); - oop rhs_obj = rhs._obj != nullptr ? rhs._obj : rhs.object_no_keepalive(); - return lhs_obj == rhs_obj; - } + static unsigned get_hash(const JvmtiTagMapKey& entry); + static bool equals(const JvmtiTagMapKey& lhs, const JvmtiTagMapKey& rhs); }; typedef @@ -70,18 +122,31 @@ ResizeableHashTable ResizableHT; +// A supporting class for iterating over all entries in Hashmap +class JvmtiTagMapKeyClosure { +public: + virtual bool do_entry(JvmtiTagMapKey& key, jlong& value) = 0; +}; + class JvmtiTagMapTable : public CHeapObj { private: ResizableHT _table; + jlong* lookup(const JvmtiHeapwalkObject& obj) const; + public: JvmtiTagMapTable(); ~JvmtiTagMapTable(); - jlong find(oop obj); - void add(oop obj, jlong tag); + int number_of_entries() const { return _table.number_of_entries(); } + + jlong find(const JvmtiHeapwalkObject& obj) const; + // obj cannot be flat + void add(const JvmtiHeapwalkObject& obj, jlong tag); + // update the tag if the entry exists, returns false otherwise + bool update(const JvmtiHeapwalkObject& obj, jlong tag); - void remove(oop obj); + bool remove(const JvmtiHeapwalkObject& obj); // iterate over all entries in the hashmap void entry_iterate(JvmtiTagMapKeyClosure* closure); @@ -93,10 +158,74 @@ class JvmtiTagMapTable : public CHeapObj { void clear(); }; -// A supporting class for iterating over all entries in Hashmap -class JvmtiTagMapKeyClosure { - public: - virtual bool do_entry(JvmtiTagMapKey& key, jlong& value) = 0; + +// This class is the Key type for hash table to keep flattened value objects during heap walk operations. +// The objects needs to be moved to JvmtiTagMapTable outside of safepoint. +class JvmtiFlatTagMapKey: public CHeapObj { +private: + // holder object + OopHandle _h; + // temporarily holds holder object while searching + oop _holder; + int _offset; + InlineKlass* _inline_klass; + LayoutKind _layout_kind; +public: + JvmtiFlatTagMapKey(const JvmtiHeapwalkObject& obj); + // Copy ctor is called when we put entry to the hash table. + JvmtiFlatTagMapKey(const JvmtiFlatTagMapKey& src); + + JvmtiFlatTagMapKey& operator=(const JvmtiFlatTagMapKey&) = delete; + + JvmtiHeapwalkObject heapwalk_object() const; + + oop holder() const; + oop holder_no_keepalive() const; + int offset() const { return _offset; } + InlineKlass* inline_klass() const { return _inline_klass; } + LayoutKind layout_kind() const { return _layout_kind; } + + void release_handle(); + + static unsigned get_hash(const JvmtiFlatTagMapKey& entry); + static bool equals(const JvmtiFlatTagMapKey& lhs, const JvmtiFlatTagMapKey& rhs); +}; + +typedef +ResizeableHashTable FlatObjectHashtable; + +// A supporting class for iterating over all entries in JvmtiFlatTagMapTable. +class JvmtiFlatTagMapKeyClosure { +public: + virtual bool do_entry(JvmtiFlatTagMapKey& key, jlong& value) = 0; +}; + +class JvmtiFlatTagMapTable: public CHeapObj { +private: + FlatObjectHashtable _table; + +public: + JvmtiFlatTagMapTable(); + ~JvmtiFlatTagMapTable(); + + int number_of_entries() const { return _table.number_of_entries(); } + + jlong find(const JvmtiHeapwalkObject& obj) const; + // obj must be flat + void add(const JvmtiHeapwalkObject& obj, jlong tag); + + // returns tag for the entry, 0 is not found + jlong remove(const JvmtiHeapwalkObject& obj); + + // iterate over entries in the hashmap + void entry_iterate(JvmtiFlatTagMapKeyClosure* closure); + + bool is_empty() const { return _table.number_of_entries() == 0; } + + void clear(); }; #endif // SHARE_VM_PRIMS_TAGMAPTABLE_HPP diff --git a/src/hotspot/share/prims/methodHandles.cpp b/src/hotspot/share/prims/methodHandles.cpp index c46b46b1af1..2329a514ab2 100644 --- a/src/hotspot/share/prims/methodHandles.cpp +++ b/src/hotspot/share/prims/methodHandles.cpp @@ -129,19 +129,22 @@ void MethodHandles::set_enabled(bool z) { // import java_lang_invoke_MemberName.* enum { - IS_METHOD = java_lang_invoke_MemberName::MN_IS_METHOD, - IS_CONSTRUCTOR = java_lang_invoke_MemberName::MN_IS_CONSTRUCTOR, - IS_FIELD = java_lang_invoke_MemberName::MN_IS_FIELD, - IS_TYPE = java_lang_invoke_MemberName::MN_IS_TYPE, - CALLER_SENSITIVE = java_lang_invoke_MemberName::MN_CALLER_SENSITIVE, - TRUSTED_FINAL = java_lang_invoke_MemberName::MN_TRUSTED_FINAL, - HIDDEN_MEMBER = java_lang_invoke_MemberName::MN_HIDDEN_MEMBER, - REFERENCE_KIND_SHIFT = java_lang_invoke_MemberName::MN_REFERENCE_KIND_SHIFT, - REFERENCE_KIND_MASK = java_lang_invoke_MemberName::MN_REFERENCE_KIND_MASK, - LM_UNCONDITIONAL = java_lang_invoke_MemberName::MN_UNCONDITIONAL_MODE, - LM_MODULE = java_lang_invoke_MemberName::MN_MODULE_MODE, - LM_TRUSTED = java_lang_invoke_MemberName::MN_TRUSTED_MODE, - ALL_KINDS = IS_METHOD | IS_CONSTRUCTOR | IS_FIELD | IS_TYPE + IS_METHOD = java_lang_invoke_MemberName::MN_IS_METHOD, + IS_OBJECT_CONSTRUCTOR = java_lang_invoke_MemberName::MN_IS_OBJECT_CONSTRUCTOR, + IS_FIELD = java_lang_invoke_MemberName::MN_IS_FIELD, + IS_TYPE = java_lang_invoke_MemberName::MN_IS_TYPE, + CALLER_SENSITIVE = java_lang_invoke_MemberName::MN_CALLER_SENSITIVE, + TRUSTED_FINAL = java_lang_invoke_MemberName::MN_TRUSTED_FINAL, + HIDDEN_MEMBER = java_lang_invoke_MemberName::MN_HIDDEN_MEMBER, + NULL_RESTRICTED = java_lang_invoke_MemberName::MN_NULL_RESTRICTED_FIELD, + REFERENCE_KIND_SHIFT = java_lang_invoke_MemberName::MN_REFERENCE_KIND_SHIFT, + REFERENCE_KIND_MASK = java_lang_invoke_MemberName::MN_REFERENCE_KIND_MASK, + LAYOUT_SHIFT = java_lang_invoke_MemberName::MN_LAYOUT_SHIFT, + LAYOUT_MASK = java_lang_invoke_MemberName::MN_LAYOUT_MASK, + LM_UNCONDITIONAL = java_lang_invoke_MemberName::MN_UNCONDITIONAL_MODE, + LM_MODULE = java_lang_invoke_MemberName::MN_MODULE_MODE, + LM_TRUSTED = java_lang_invoke_MemberName::MN_TRUSTED_MODE, + ALL_KINDS = IS_METHOD | IS_OBJECT_CONSTRUCTOR | IS_FIELD | IS_TYPE }; int MethodHandles::ref_kind_to_flags(int ref_kind) { @@ -152,7 +155,7 @@ int MethodHandles::ref_kind_to_flags(int ref_kind) { } else if (ref_kind_is_method(ref_kind)) { flags |= IS_METHOD; } else if (ref_kind == JVM_REF_newInvokeSpecial) { - flags |= IS_CONSTRUCTOR; + flags |= IS_OBJECT_CONSTRUCTOR; } return flags; } @@ -171,7 +174,7 @@ Handle MethodHandles::resolve_MemberName_type(Handle mname, Klass* caller, TRAPS int flags = java_lang_invoke_MemberName::flags(mname()); switch (flags & ALL_KINDS) { case IS_METHOD: - case IS_CONSTRUCTOR: + case IS_OBJECT_CONSTRUCTOR: resolved = SystemDictionary::find_method_handle_type(signature, caller, CHECK_(empty)); break; case IS_FIELD: @@ -312,10 +315,10 @@ oop MethodHandles::init_method_MemberName(Handle mname, CallInfo& info) { case CallInfo::direct_call: vmindex = Method::nonvirtual_vtable_index; if (m->is_static()) { - assert(!m->is_static_initializer(), "Cannot be static initializer"); + assert(!m->is_class_initializer(), "Cannot be static initializer"); flags |= IS_METHOD | (JVM_REF_invokeStatic << REFERENCE_KIND_SHIFT); - } else if (m->is_object_initializer()) { - flags |= IS_CONSTRUCTOR | (JVM_REF_invokeSpecial << REFERENCE_KIND_SHIFT); + } else if (m->is_object_constructor()) { + flags |= IS_OBJECT_CONSTRUCTOR | (JVM_REF_invokeSpecial << REFERENCE_KIND_SHIFT); } else { // "special" reflects that this is a direct call, not that it // necessarily originates from an invokespecial. We can also do @@ -354,6 +357,12 @@ oop MethodHandles::init_field_MemberName(Handle mname, fieldDescriptor& fd, bool int flags = fd.access_flags().as_field_flags(); flags |= IS_FIELD | ((fd.is_static() ? JVM_REF_getStatic : JVM_REF_getField) << REFERENCE_KIND_SHIFT); if (fd.is_trusted_final()) flags |= TRUSTED_FINAL; + if (fd.is_flat()) { + int layout_kind = (int)fd.layout_kind(); + assert((layout_kind & LAYOUT_MASK) == layout_kind, "Layout information loss"); + flags |= layout_kind << LAYOUT_SHIFT; + } + if (fd.is_null_free_inline_type()) flags |= NULL_RESTRICTED; if (is_setter) flags += ((JVM_REF_putField - JVM_REF_getField) << REFERENCE_KIND_SHIFT); int vmindex = fd.offset(); // determines the field uniquely when combined with static bit @@ -804,13 +813,13 @@ Handle MethodHandles::resolve_MemberName(Handle mname, Klass* caller, int lookup oop mname2 = init_method_MemberName(mname, result); return Handle(THREAD, mname2); } - case IS_CONSTRUCTOR: + case IS_OBJECT_CONSTRUCTOR: { CallInfo result; LinkInfo link_info(defc, name, type, caller, access_check, loader_constraint_check); { assert(!HAS_PENDING_EXCEPTION, ""); - if (name == vmSymbols::object_initializer_name()) { + if (name == vmSymbols::object_initializer_name() && type->is_void_method_signature()) { LinkResolver::resolve_special_call(result, Handle(), link_info, THREAD); } else { break; // will throw after end of switch @@ -873,7 +882,7 @@ void MethodHandles::expand_MemberName(Handle mname, int suppress, TRAPS) { switch (flags & ALL_KINDS) { case IS_METHOD: - case IS_CONSTRUCTOR: + case IS_OBJECT_CONSTRUCTOR: { Method* vmtarget = java_lang_invoke_MemberName::vmtarget(mname()); if (vmtarget == nullptr) { @@ -986,7 +995,7 @@ void MethodHandles::trace_method_handle_interpreter_entry(MacroAssembler* _masm, #ifndef PRODUCT #define EACH_NAMED_CON(template, requirement) \ template(java_lang_invoke_MemberName,MN_IS_METHOD) \ - template(java_lang_invoke_MemberName,MN_IS_CONSTRUCTOR) \ + template(java_lang_invoke_MemberName,MN_IS_OBJECT_CONSTRUCTOR) \ template(java_lang_invoke_MemberName,MN_IS_FIELD) \ template(java_lang_invoke_MemberName,MN_IS_TYPE) \ template(java_lang_invoke_MemberName,MN_CALLER_SENSITIVE) \ @@ -994,6 +1003,8 @@ void MethodHandles::trace_method_handle_interpreter_entry(MacroAssembler* _masm, template(java_lang_invoke_MemberName,MN_HIDDEN_MEMBER) \ template(java_lang_invoke_MemberName,MN_REFERENCE_KIND_SHIFT) \ template(java_lang_invoke_MemberName,MN_REFERENCE_KIND_MASK) \ + template(java_lang_invoke_MemberName,MN_LAYOUT_SHIFT) \ + template(java_lang_invoke_MemberName,MN_LAYOUT_MASK) \ template(java_lang_invoke_MemberName,MN_NESTMATE_CLASS) \ template(java_lang_invoke_MemberName,MN_HIDDEN_CLASS) \ template(java_lang_invoke_MemberName,MN_STRONG_LOADER_LINK) \ @@ -1129,7 +1140,7 @@ JVM_ENTRY(jobject, MHN_resolve_Mem(JNIEnv *env, jobject igcls, jobject mname_jh, if ((flags & ALL_KINDS) == IS_FIELD) { THROW_MSG_NULL(vmSymbols::java_lang_NoSuchFieldError(), "field resolution failed"); } else if ((flags & ALL_KINDS) == IS_METHOD || - (flags & ALL_KINDS) == IS_CONSTRUCTOR) { + (flags & ALL_KINDS) == IS_OBJECT_CONSTRUCTOR) { THROW_MSG_NULL(vmSymbols::java_lang_NoSuchMethodError(), "method resolution failed"); } else { THROW_MSG_NULL(vmSymbols::java_lang_LinkageError(), "resolution failed"); diff --git a/src/hotspot/share/prims/unsafe.cpp b/src/hotspot/share/prims/unsafe.cpp index b4718a9a18a..3c67b0e9054 100644 --- a/src/hotspot/share/prims/unsafe.cpp +++ b/src/hotspot/share/prims/unsafe.cpp @@ -31,10 +31,16 @@ #include "jfr/jfrEvents.hpp" #include "jni.h" #include "jvm.h" +#include "logging/log.hpp" +#include "logging/logStream.hpp" #include "memory/allocation.inline.hpp" +#include "memory/oopFactory.hpp" #include "memory/resourceArea.hpp" #include "oops/access.inline.hpp" #include "oops/fieldStreams.inline.hpp" +#include "oops/flatArrayKlass.hpp" +#include "oops/flatArrayOop.inline.hpp" +#include "oops/inlineKlass.inline.hpp" #include "oops/instanceKlass.inline.hpp" #include "oops/klass.inline.hpp" #include "oops/objArrayOop.inline.hpp" @@ -42,6 +48,7 @@ #include "oops/typeArrayOop.inline.hpp" #include "prims/jvmtiExport.hpp" #include "prims/unsafe.hpp" +#include "runtime/fieldDescriptor.inline.hpp" #include "runtime/globals.hpp" #include "runtime/handles.inline.hpp" #include "runtime/interfaceSupport.inline.hpp" @@ -168,7 +175,6 @@ jlong Unsafe_field_offset_from_byte_offset(jlong byte_offset) { return byte_offset; } - ///// Data read/writes on the Java heap and in native (off-heap) memory /** @@ -247,10 +253,10 @@ class MemoryAccess : StackObj { ATTRIBUTE_NO_UBSAN void put(T x) { GuardUnsafeAccess guard(_thread); + assert(_obj == nullptr || !_obj->is_inline_type() || _obj->mark().is_larval_state(), "must be an object instance or a larval inline type"); *addr() = normalize_for_write(x); } - T get_volatile() { GuardUnsafeAccess guard(_thread); volatile T ret = RawAccess::load(addr()); @@ -263,6 +269,68 @@ class MemoryAccess : StackObj { } }; +#ifdef ASSERT +/* + * Get the field descriptor of the field of the given object at the given offset. + */ +static bool get_field_descriptor(oop p, jlong offset, fieldDescriptor* fd) { + bool found = false; + Klass* k = p->klass(); + if (k->is_instance_klass()) { + InstanceKlass* ik = InstanceKlass::cast(k); + found = ik->find_field_from_offset((int)offset, false, fd); + if (!found && ik->is_mirror_instance_klass()) { + Klass* k2 = java_lang_Class::as_Klass(p); + if (k2->is_instance_klass()) { + ik = InstanceKlass::cast(k2); + found = ik->find_field_from_offset((int)offset, true, fd); + } + } + } + return found; +} +#endif // ASSERT + +static void assert_and_log_unsafe_value_access(oop p, jlong offset, InlineKlass* vk) { + Klass* k = p->klass(); +#ifdef ASSERT + if (k->is_instance_klass()) { + assert_field_offset_sane(p, offset); + fieldDescriptor fd; + bool found = get_field_descriptor(p, offset, &fd); + if (found) { + assert(found, "value field not found"); + assert(fd.is_flat(), "field not flat"); + } else { + if (log_is_enabled(Trace, valuetypes)) { + log_trace(valuetypes)("not a field in %s at offset " UINT64_FORMAT_X, + p->klass()->external_name(), (uint64_t)offset); + } + } + } else if (k->is_flatArray_klass()) { + FlatArrayKlass* vak = FlatArrayKlass::cast(k); + int index = (offset - vak->array_header_in_bytes()) / vak->element_byte_size(); + address dest = (address)((flatArrayOop)p)->value_at_addr(index, vak->layout_helper()); + assert(dest == (cast_from_oop
(p) + offset), "invalid offset"); + } else { + ShouldNotReachHere(); + } +#endif // ASSERT + if (log_is_enabled(Trace, valuetypes)) { + if (k->is_flatArray_klass()) { + FlatArrayKlass* vak = FlatArrayKlass::cast(k); + int index = (offset - vak->array_header_in_bytes()) / vak->element_byte_size(); + address dest = (address)((flatArrayOop)p)->value_at_addr(index, vak->layout_helper()); + log_trace(valuetypes)("%s array type %s index %d element size %d offset " UINT64_FORMAT_X " at " INTPTR_FORMAT, + p->klass()->external_name(), vak->external_name(), + index, vak->element_byte_size(), (uint64_t)offset, p2i(dest)); + } else { + log_trace(valuetypes)("%s field type %s at offset " UINT64_FORMAT_X, + p->klass()->external_name(), vk->external_name(), (uint64_t)offset); + } + } +} + // These functions allow a null base pointer with an arbitrary address. // But if the base pointer is non-null, the offset should make some sense. // That is, it should be in the range [0, MAX_OBJECT_SIZE]. @@ -277,9 +345,206 @@ UNSAFE_ENTRY(void, Unsafe_PutReference(JNIEnv *env, jobject unsafe, jobject obj, oop x = JNIHandles::resolve(x_h); oop p = JNIHandles::resolve(obj); assert_field_offset_sane(p, offset); + assert(!p->is_inline_type() || p->mark().is_larval_state(), "must be an object instance or a larval inline type"); HeapAccess::oop_store_at(p, offset, x); } UNSAFE_END +UNSAFE_ENTRY(jlong, Unsafe_ValueHeaderSize(JNIEnv *env, jobject unsafe, jclass c)) { + Klass* k = java_lang_Class::as_Klass(JNIHandles::resolve_non_null(c)); + InlineKlass* vk = InlineKlass::cast(k); + return vk->payload_offset(); +} UNSAFE_END + +UNSAFE_ENTRY(jboolean, Unsafe_IsFlatField(JNIEnv *env, jobject unsafe, jobject o)) { + oop f = JNIHandles::resolve_non_null(o); + Klass* k = java_lang_Class::as_Klass(java_lang_reflect_Field::clazz(f)); + int slot = java_lang_reflect_Field::slot(f); + return InstanceKlass::cast(k)->field_is_flat(slot); +} UNSAFE_END + +UNSAFE_ENTRY(jboolean, Unsafe_HasNullMarker(JNIEnv *env, jobject unsafe, jobject o)) { + oop f = JNIHandles::resolve_non_null(o); + Klass* k = java_lang_Class::as_Klass(java_lang_reflect_Field::clazz(f)); + int slot = java_lang_reflect_Field::slot(f); + return InstanceKlass::cast(k)->field_has_null_marker(slot); +} UNSAFE_END + +UNSAFE_ENTRY(jint, Unsafe_NullMarkerOffset(JNIEnv *env, jobject unsafe, jobject o)) { + oop f = JNIHandles::resolve_non_null(o); + Klass* k = java_lang_Class::as_Klass(java_lang_reflect_Field::clazz(f)); + int slot = java_lang_reflect_Field::slot(f); + return InstanceKlass::cast(k)->null_marker_offset(slot); +} UNSAFE_END + +UNSAFE_ENTRY(jint, Unsafe_ArrayLayout(JNIEnv *env, jobject unsafe, jarray array)) { + oop ar = JNIHandles::resolve_non_null(array); + ArrayKlass* ak = ArrayKlass::cast(ar->klass()); + if (ak->is_refArray_klass()) { + return (jint)LayoutKind::REFERENCE; + } else if (ak->is_flatArray_klass()) { + return (jint)FlatArrayKlass::cast(ak)->layout_kind(); + } else { + ShouldNotReachHere(); + return -1; + } +} UNSAFE_END + +UNSAFE_ENTRY(jint, Unsafe_FieldLayout(JNIEnv *env, jobject unsafe, jobject field)) { + assert(field != nullptr, "field must not be null"); + + oop reflected = JNIHandles::resolve_non_null(field); + oop mirror = java_lang_reflect_Field::clazz(reflected); + Klass* k = java_lang_Class::as_Klass(mirror); + int slot = java_lang_reflect_Field::slot(reflected); + int modifiers = java_lang_reflect_Field::modifiers(reflected); + + if ((modifiers & JVM_ACC_STATIC) != 0) { + return (jint)LayoutKind::REFERENCE; // static fields are never flat + } else { + InstanceKlass* ik = InstanceKlass::cast(k); + if (ik->field_is_flat(slot)) { + return (jint)ik->inline_layout_info(slot).kind(); + } else { + return (jint)LayoutKind::REFERENCE; + } + } +} UNSAFE_END + +UNSAFE_ENTRY(jarray, Unsafe_NewSpecialArray(JNIEnv *env, jobject unsafe, jclass elmClass, jint len, jint layoutKind)) { + oop mirror = JNIHandles::resolve_non_null(elmClass); + Klass* klass = java_lang_Class::as_Klass(mirror); + klass->initialize(CHECK_NULL); + if (len < 0) { + THROW_MSG_NULL(vmSymbols::java_lang_IllegalArgumentException(), "Array length is negative"); + } + if (klass->is_array_klass() || klass->is_identity_class()) { + THROW_MSG_NULL(vmSymbols::java_lang_IllegalArgumentException(), "Element class is not a value class"); + } + if (klass->is_abstract()) { + THROW_MSG_NULL(vmSymbols::java_lang_IllegalArgumentException(), "Element class is abstract"); + } + LayoutKind lk = static_cast(layoutKind); + if (lk <= LayoutKind::REFERENCE || lk >= LayoutKind::UNKNOWN) { + THROW_MSG_NULL(vmSymbols::java_lang_IllegalArgumentException(), "Invalid layout kind"); + } + InlineKlass* vk = InlineKlass::cast(klass); + // WARNING: test below will need modifications when flat layouts supported for fields + // but not for arrays are introduce (NULLABLE_NON_ATOMIC_FLAT for instance) + if (!UseArrayFlattening || !vk->is_layout_supported(lk)) { + THROW_MSG_NULL(vmSymbols::java_lang_UnsupportedOperationException(), "Layout not supported"); + } + ArrayKlass::ArrayProperties props = ArrayKlass::ArrayProperties::DEFAULT; + switch(lk) { + case LayoutKind::ATOMIC_FLAT: + props = ArrayKlass::ArrayProperties::NULL_RESTRICTED; + break; + case LayoutKind::NON_ATOMIC_FLAT: + props = (ArrayKlass::ArrayProperties)(ArrayKlass::ArrayProperties::NULL_RESTRICTED | ArrayKlass::ArrayProperties::NON_ATOMIC); + break; + case LayoutKind::NULLABLE_ATOMIC_FLAT: + props = ArrayKlass::ArrayProperties::NON_ATOMIC; + break; + default: + ShouldNotReachHere(); + } + oop array = oopFactory::new_flatArray(vk, len, props, lk, CHECK_NULL); + return (jarray) JNIHandles::make_local(THREAD, array); +} UNSAFE_END + +UNSAFE_ENTRY(jobject, Unsafe_GetValue(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jclass vc)) { + oop base = JNIHandles::resolve(obj); + if (base == nullptr) { + THROW_NULL(vmSymbols::java_lang_NullPointerException()); + } + Klass* k = java_lang_Class::as_Klass(JNIHandles::resolve_non_null(vc)); + InlineKlass* vk = InlineKlass::cast(k); + assert_and_log_unsafe_value_access(base, offset, vk); + LayoutKind lk = LayoutKind::UNKNOWN; + if (base->is_array()) { + FlatArrayKlass* fak = FlatArrayKlass::cast(base->klass()); + lk = fak->layout_kind(); + } else { + fieldDescriptor fd; + InstanceKlass::cast(base->klass())->find_field_from_offset(offset, false, &fd); + lk = fd.field_holder()->inline_layout_info(fd.index()).kind(); + } + Handle base_h(THREAD, base); + oop v = vk->read_payload_from_addr(base_h(), offset, lk, CHECK_NULL); + return JNIHandles::make_local(THREAD, v); +} UNSAFE_END + +UNSAFE_ENTRY(jobject, Unsafe_GetFlatValue(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint layoutKind, jclass vc)) { + assert(layoutKind != (int)LayoutKind::REFERENCE, "This method handles only flat layouts"); + oop base = JNIHandles::resolve(obj); + if (base == nullptr) { + THROW_NULL(vmSymbols::java_lang_NullPointerException()); + } + Klass* k = java_lang_Class::as_Klass(JNIHandles::resolve_non_null(vc)); + InlineKlass* vk = InlineKlass::cast(k); + assert_and_log_unsafe_value_access(base, offset, vk); + LayoutKind lk = (LayoutKind)layoutKind; + Handle base_h(THREAD, base); + oop v = vk->read_payload_from_addr(base_h(), offset, lk, CHECK_NULL); + return JNIHandles::make_local(THREAD, v); +} UNSAFE_END + +UNSAFE_ENTRY(void, Unsafe_PutValue(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jclass vc, jobject value)) { + oop base = JNIHandles::resolve(obj); + if (base == nullptr) { + THROW(vmSymbols::java_lang_NullPointerException()); + } + Klass* k = java_lang_Class::as_Klass(JNIHandles::resolve_non_null(vc)); + InlineKlass* vk = InlineKlass::cast(k); + assert(!base->is_inline_type() || base->mark().is_larval_state(), "must be an object instance or a larval inline type"); + assert_and_log_unsafe_value_access(base, offset, vk); + LayoutKind lk = LayoutKind::UNKNOWN; + if (base->is_array()) { + FlatArrayKlass* fak = FlatArrayKlass::cast(base->klass()); + lk = fak->layout_kind(); + } else { + fieldDescriptor fd; + InstanceKlass::cast(base->klass())->find_field_from_offset(offset, false, &fd); + lk = fd.field_holder()->inline_layout_info(fd.index()).kind(); + } + oop v = JNIHandles::resolve(value); + vk->write_value_to_addr(v, ((char*)(oopDesc*)base) + offset, lk, true, CHECK); +} UNSAFE_END + +UNSAFE_ENTRY(void, Unsafe_PutFlatValue(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint layoutKind, jclass vc, jobject value)) { + assert(layoutKind != (int)LayoutKind::REFERENCE, "This method handles only flat layouts"); + oop base = JNIHandles::resolve(obj); + if (base == nullptr) { + THROW(vmSymbols::java_lang_NullPointerException()); + } + Klass* k = java_lang_Class::as_Klass(JNIHandles::resolve_non_null(vc)); + InlineKlass* vk = InlineKlass::cast(k); + assert(!base->is_inline_type() || base->mark().is_larval_state(), "must be an object instance or a larval inline type"); + assert_and_log_unsafe_value_access(base, offset, vk); + LayoutKind lk = (LayoutKind)layoutKind; + oop v = JNIHandles::resolve(value); + vk->write_value_to_addr(v, ((char*)(oopDesc*)base) + offset, lk, true, CHECK); +} UNSAFE_END + +UNSAFE_ENTRY(jobject, Unsafe_MakePrivateBuffer(JNIEnv *env, jobject unsafe, jobject value)) { + oop v = JNIHandles::resolve_non_null(value); + assert(v->is_inline_type(), "must be an inline type instance"); + Handle vh(THREAD, v); + InlineKlass* vk = InlineKlass::cast(v->klass()); + instanceOop new_value = vk->allocate_instance_buffer(CHECK_NULL); + vk->copy_payload_to_addr(vk->payload_addr(vh()), vk->payload_addr(new_value), LayoutKind::BUFFERED, false); + markWord mark = new_value->mark(); + new_value->set_mark(mark.enter_larval_state()); + return JNIHandles::make_local(THREAD, new_value); +} UNSAFE_END + +UNSAFE_ENTRY(jobject, Unsafe_FinishPrivateBuffer(JNIEnv *env, jobject unsafe, jobject value)) { + oop v = JNIHandles::resolve(value); + assert(v->mark().is_larval_state(), "must be a larval value"); + markWord mark = v->mark(); + v->set_mark(mark.exit_larval_state()); + return JNIHandles::make_local(THREAD, v); +} UNSAFE_END + UNSAFE_ENTRY(jobject, Unsafe_GetReferenceVolatile(JNIEnv *env, jobject unsafe, jobject obj, jlong offset)) { oop p = JNIHandles::resolve(obj); assert_field_offset_sane(p, offset); @@ -590,6 +855,27 @@ UNSAFE_ENTRY(jboolean, Unsafe_ShouldBeInitialized0(JNIEnv *env, jobject unsafe, } UNSAFE_END +UNSAFE_ENTRY(void, Unsafe_NotifyStrictStaticAccess0(JNIEnv *env, jobject unsafe, jobject clazz, + jlong sfoffset, jboolean writing)) { + assert(clazz != nullptr, "clazz must not be null"); + + oop mirror = JNIHandles::resolve_non_null(clazz); + Klass* klass = java_lang_Class::as_Klass(mirror); + + if (klass != nullptr && klass->is_instance_klass()) { + InstanceKlass* ik = InstanceKlass::cast(klass); + fieldDescriptor fd; + if (ik->find_local_field_from_offset((int)sfoffset, true, &fd)) { + // Note: The Unsafe API takes an OFFSET, but the InstanceKlass wants the INDEX. + // We could surface field indexes into Unsafe, but that's too much churn. + ik->notify_strict_static_access(fd.index(), writing, CHECK); + return; + } + } + THROW(vmSymbols::java_lang_InternalError()); +} +UNSAFE_END + static void getBaseAndScale(int& base, int& scale, jclass clazz, TRAPS) { assert(clazz != nullptr, "clazz must not be null"); @@ -598,19 +884,40 @@ static void getBaseAndScale(int& base, int& scale, jclass clazz, TRAPS) { if (k == nullptr || !k->is_array_klass()) { THROW(vmSymbols::java_lang_InvalidClassException()); - } else if (k->is_objArray_klass()) { - base = arrayOopDesc::base_offset_in_bytes(T_OBJECT); - scale = heapOopSize; } else if (k->is_typeArray_klass()) { TypeArrayKlass* tak = TypeArrayKlass::cast(k); base = tak->array_header_in_bytes(); assert(base == arrayOopDesc::base_offset_in_bytes(tak->element_type()), "array_header_size semantics ok"); scale = (1 << tak->log2_element_size()); + } else if (k->is_objArray_klass()) { + Klass* ek = ObjArrayKlass::cast(k)->element_klass(); + if (!ek->is_identity_class() && !ek->is_abstract()) { + // Arrays of a concrete value class type can have multiple layouts + // There's no good value to return, so throwing an exception is the way out + THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "Arrays of a concrete value class don't have a single base and offset"); + } + base = arrayOopDesc::base_offset_in_bytes(T_OBJECT); + scale = heapOopSize; } else { ShouldNotReachHere(); } } +UNSAFE_ENTRY(jint, Unsafe_ArrayBaseOffset1(JNIEnv *env, jobject unsafe, jarray array)) { + assert(array != nullptr, "array must not be null"); + oop ar = JNIHandles::resolve_non_null(array); + assert(ar->is_array(), "Must be an array"); + ArrayKlass* ak = ArrayKlass::cast(ar->klass()); + if (ak->is_refArray_klass()) { + return arrayOopDesc::base_offset_in_bytes(T_OBJECT); + } else if (ak->is_flatArray_klass()) { + FlatArrayKlass* fak = FlatArrayKlass::cast(ak); + return fak->array_header_in_bytes(); + } else { + ShouldNotReachHere(); + } +} UNSAFE_END + UNSAFE_ENTRY(jint, Unsafe_ArrayBaseOffset0(JNIEnv *env, jobject unsafe, jclass clazz)) { int base = 0, scale = 0; getBaseAndScale(base, scale, clazz, CHECK_0); @@ -640,6 +947,26 @@ UNSAFE_ENTRY(jint, Unsafe_ArrayIndexScale0(JNIEnv *env, jobject unsafe, jclass c return field_offset_from_byte_offset(scale) - field_offset_from_byte_offset(0); } UNSAFE_END +UNSAFE_ENTRY(jint, Unsafe_ArrayIndexScale1(JNIEnv *env, jobject unsafe, jarray array)) { + assert(array != nullptr, "array must not be null"); + oop ar = JNIHandles::resolve_non_null(array); + assert(ar->is_array(), "Must be an array"); + ArrayKlass* ak = ArrayKlass::cast(ar->klass()); + if (ak->is_refArray_klass()) { + return heapOopSize; + } else if (ak->is_flatArray_klass()) { + FlatArrayKlass* fak = FlatArrayKlass::cast(ak); + return fak->element_byte_size(); + } else { + ShouldNotReachHere(); + } +} UNSAFE_END + +UNSAFE_ENTRY(jlong, Unsafe_GetObjectSize0(JNIEnv* env, jobject o, jobject obj)) + oop p = JNIHandles::resolve(obj); + return p->size() * HeapWordSize; +UNSAFE_END + static inline void throw_new(JNIEnv *env, const char *ename) { jclass cls = env->FindClass(ename); @@ -853,6 +1180,8 @@ UNSAFE_ENTRY(jint, Unsafe_GetLoadAverage0(JNIEnv *env, jobject unsafe, jdoubleAr #define FLD LANG "reflect/Field;" #define THR LANG "Throwable;" +#define OBJ_ARR "[" OBJ + #define DC_Args LANG "String;[BII" LANG "ClassLoader;" "Ljava/security/ProtectionDomain;" #define DAC_Args CLS "[B[" OBJ @@ -860,10 +1189,10 @@ UNSAFE_ENTRY(jint, Unsafe_GetLoadAverage0(JNIEnv *env, jobject unsafe, jdoubleAr #define FN_PTR(f) CAST_FROM_FN_PTR(void*, &f) #define DECLARE_GETPUTOOP(Type, Desc) \ - {CC "get" #Type, CC "(" OBJ "J)" #Desc, FN_PTR(Unsafe_Get##Type)}, \ - {CC "put" #Type, CC "(" OBJ "J" #Desc ")V", FN_PTR(Unsafe_Put##Type)}, \ - {CC "get" #Type "Volatile", CC "(" OBJ "J)" #Desc, FN_PTR(Unsafe_Get##Type##Volatile)}, \ - {CC "put" #Type "Volatile", CC "(" OBJ "J" #Desc ")V", FN_PTR(Unsafe_Put##Type##Volatile)} + {CC "get" #Type, CC "(" OBJ "J)" #Desc, FN_PTR(Unsafe_Get##Type)}, \ + {CC "put" #Type, CC "(" OBJ "J" #Desc ")V", FN_PTR(Unsafe_Put##Type)}, \ + {CC "get" #Type "Volatile", CC "(" OBJ "J)" #Desc, FN_PTR(Unsafe_Get##Type##Volatile)}, \ + {CC "put" #Type "Volatile", CC "(" OBJ "J" #Desc ")V", FN_PTR(Unsafe_Put##Type##Volatile)} static JNINativeMethod jdk_internal_misc_Unsafe_methods[] = { @@ -872,6 +1201,20 @@ static JNINativeMethod jdk_internal_misc_Unsafe_methods[] = { {CC "getReferenceVolatile", CC "(" OBJ "J)" OBJ, FN_PTR(Unsafe_GetReferenceVolatile)}, {CC "putReferenceVolatile", CC "(" OBJ "J" OBJ ")V", FN_PTR(Unsafe_PutReferenceVolatile)}, + {CC "isFlatField0", CC "(" OBJ ")Z", FN_PTR(Unsafe_IsFlatField)}, + {CC "hasNullMarker0", CC "(" OBJ ")Z", FN_PTR(Unsafe_HasNullMarker)}, + {CC "nullMarkerOffset0", CC "(" OBJ ")I", FN_PTR(Unsafe_NullMarkerOffset)}, + {CC "arrayLayout0", CC "(" OBJ_ARR ")I", FN_PTR(Unsafe_ArrayLayout)}, + {CC "fieldLayout0", CC "(" OBJ ")I", FN_PTR(Unsafe_FieldLayout)}, + {CC "newSpecialArray", CC "(" CLS "II)[" OBJ, FN_PTR(Unsafe_NewSpecialArray)}, + {CC "getValue", CC "(" OBJ "J" CLS ")" OBJ, FN_PTR(Unsafe_GetValue)}, + {CC "getFlatValue", CC "(" OBJ "JI" CLS ")" OBJ, FN_PTR(Unsafe_GetFlatValue)}, + {CC "putValue", CC "(" OBJ "J" CLS OBJ ")V", FN_PTR(Unsafe_PutValue)}, + {CC "putFlatValue", CC "(" OBJ "JI" CLS OBJ ")V", FN_PTR(Unsafe_PutFlatValue)}, + {CC "makePrivateBuffer", CC "(" OBJ ")" OBJ, FN_PTR(Unsafe_MakePrivateBuffer)}, + {CC "finishPrivateBuffer", CC "(" OBJ ")" OBJ, FN_PTR(Unsafe_FinishPrivateBuffer)}, + {CC "valueHeaderSize", CC "(" CLS ")J", FN_PTR(Unsafe_ValueHeaderSize)}, + {CC "getUncompressedObject", CC "(" ADR ")" OBJ, FN_PTR(Unsafe_GetUncompressedObject)}, DECLARE_GETPUTOOP(Boolean, Z), @@ -893,7 +1236,10 @@ static JNINativeMethod jdk_internal_misc_Unsafe_methods[] = { {CC "staticFieldBase0", CC "(" FLD ")" OBJ, FN_PTR(Unsafe_StaticFieldBase0)}, {CC "ensureClassInitialized0", CC "(" CLS ")V", FN_PTR(Unsafe_EnsureClassInitialized0)}, {CC "arrayBaseOffset0", CC "(" CLS ")I", FN_PTR(Unsafe_ArrayBaseOffset0)}, + {CC "arrayBaseOffset1", CC "(" OBJ_ARR ")I", FN_PTR(Unsafe_ArrayBaseOffset1)}, {CC "arrayIndexScale0", CC "(" CLS ")I", FN_PTR(Unsafe_ArrayIndexScale0)}, + {CC "arrayIndexScale1", CC "(" OBJ_ARR ")I", FN_PTR(Unsafe_ArrayIndexScale1)}, + {CC "getObjectSize0", CC "(Ljava/lang/Object;)J", FN_PTR(Unsafe_GetObjectSize0)}, {CC "defineClass0", CC "(" DC_Args ")" CLS, FN_PTR(Unsafe_DefineClass0)}, {CC "allocateInstance", CC "(" CLS ")" OBJ, FN_PTR(Unsafe_AllocateInstance)}, @@ -918,6 +1264,7 @@ static JNINativeMethod jdk_internal_misc_Unsafe_methods[] = { {CC "setMemory0", CC "(" OBJ "JJB)V", FN_PTR(Unsafe_SetMemory0)}, {CC "shouldBeInitialized0", CC "(" CLS ")Z", FN_PTR(Unsafe_ShouldBeInitialized0)}, + {CC "notifyStrictStaticAccess0", CC "(" CLS "JZ)V", FN_PTR(Unsafe_NotifyStrictStaticAccess0)}, {CC "fullFence", CC "()V", FN_PTR(Unsafe_FullFence)}, }; diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index 63920583e18..e8c14cba881 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -50,6 +50,7 @@ #include "jvmtifiles/jvmtiEnv.hpp" #include "logging/log.hpp" #include "memory/iterator.hpp" +#include "memory/iterator.inline.hpp" #include "memory/memoryReserver.hpp" #include "memory/metadataFactory.hpp" #include "memory/metaspace/testHelpers.hpp" @@ -58,8 +59,10 @@ #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "nmt/mallocSiteTable.hpp" +#include "oops/access.hpp" #include "oops/array.hpp" #include "oops/compressedOops.hpp" +#include "oops/compressedOops.inline.hpp" #include "oops/constantPool.inline.hpp" #include "oops/klass.inline.hpp" #include "oops/method.inline.hpp" @@ -85,6 +88,7 @@ #include "runtime/javaCalls.hpp" #include "runtime/javaThread.inline.hpp" #include "runtime/jniHandles.inline.hpp" +#include "runtime/keepStackGCProcessed.hpp" #include "runtime/lightweightSynchronizer.hpp" #include "runtime/lockStack.hpp" #include "runtime/os.hpp" @@ -1945,6 +1949,109 @@ WB_ENTRY(jobjectArray, WB_GetResolvedReferences(JNIEnv* env, jobject wb, jclass return (jobjectArray)JNIHandles::make_local(THREAD, resolved_refs); WB_END +WB_ENTRY(jobjectArray, WB_getObjectsViaKlassOopMaps(JNIEnv* env, jobject wb, jobject thing)) + oop aoop = JNIHandles::resolve(thing); + if (!aoop->is_instance()) { + return nullptr; + } + instanceHandle ih(THREAD, (instanceOop) aoop); + InstanceKlass* klass = InstanceKlass::cast(ih->klass()); + if (klass->nonstatic_oop_map_count() == 0) { + return nullptr; + } + const OopMapBlock* map = klass->start_of_nonstatic_oop_maps(); + const OopMapBlock* const end = map + klass->nonstatic_oop_map_count(); + int oop_count = 0; + while (map < end) { + oop_count += map->count(); + map++; + } + + objArrayHandle result_array = + oopFactory::new_objArray_handle(vmClasses::Object_klass(), oop_count, CHECK_NULL); + map = klass->start_of_nonstatic_oop_maps(); + int index = 0; + while (map < end) { + int offset = map->offset(); + for (unsigned int j = 0; j < map->count(); j++) { + result_array->obj_at_put(index++, ih->obj_field(offset)); + offset += heapOopSize; + } + map++; + } + return (jobjectArray)JNIHandles::make_local(THREAD, result_array()); +WB_END + +// Collect Object oops but not value objects...loaded from heap +class CollectObjectOops : public BasicOopIterateClosure { + public: + GrowableArray* _array; + + CollectObjectOops() { + _array = new GrowableArray(128); + } + + void add_oop(oop o) { + Handle oh = Handle(Thread::current(), o); + if (oh != nullptr && oh->is_inline_type()) { + oh->oop_iterate(this); + } else { + _array->append(oh); + } + } + + template inline void add_oop(T* p) { add_oop(HeapAccess<>::oop_load(p)); } + void do_oop(oop* o) { add_oop(o); } + void do_oop(narrowOop* v) { add_oop(v); } + + jobjectArray create_jni_result(JNIEnv* env, TRAPS) { + objArrayHandle result_array = + oopFactory::new_objArray_handle(vmClasses::Object_klass(), _array->length(), CHECK_NULL); + for (int i = 0 ; i < _array->length(); i++) { + result_array->obj_at_put(i, _array->at(i)()); + } + return (jobjectArray)JNIHandles::make_local(THREAD, result_array()); + } +}; + +// Collect Object oops but not value objects...loaded from frames +class CollectFrameObjectOops : public BasicOopIterateClosure { + public: + CollectObjectOops _collect; + + template inline void add_oop(T* p) { _collect.add_oop(RawAccess<>::oop_load(p)); } + void do_oop(oop* o) { add_oop(o); } + void do_oop(narrowOop* v) { add_oop(v); } + + jobjectArray create_jni_result(JNIEnv* env, TRAPS) { + return _collect.create_jni_result(env, THREAD); + } +}; + +// Collect Object oops for the given oop, iterate through value objects +WB_ENTRY(jobjectArray, WB_getObjectsViaOopIterator(JNIEnv* env, jobject wb, jobject thing)) + ResourceMark rm(thread); + Handle objh(thread, JNIHandles::resolve(thing)); + CollectObjectOops collectOops; + objh->oop_iterate(&collectOops); + return collectOops.create_jni_result(env, THREAD); +WB_END + +// Collect Object oops for the given frame deep, iterate through value objects +WB_ENTRY(jobjectArray, WB_getObjectsViaFrameOopIterator(JNIEnv* env, jobject wb, jint depth)) + KeepStackGCProcessedMark ksgcpm(THREAD); + ResourceMark rm(THREAD); + CollectFrameObjectOops collectOops; + StackFrameStream sfs(thread, true /* update */, true /* process_frames */); + while (depth > 0) { // Skip the native WB API frame + sfs.next(); + frame* f = sfs.current(); + f->oops_do(&collectOops, nullptr, sfs.register_map()); + depth--; + } + return collectOops.create_jni_result(env, THREAD); +WB_END + WB_ENTRY(jint, WB_getFieldEntriesLength(JNIEnv* env, jobject wb, jclass klass)) InstanceKlass* ik = InstanceKlass::cast(java_lang_Class::as_Klass(JNIHandles::resolve(klass))); ConstantPool* cp = ik->constants(); @@ -2922,6 +3029,12 @@ static JNINativeMethod methods[] = { {CC"forceClassLoaderStatsSafepoint", CC"()V", (void*)&WB_ForceClassLoaderStatsSafepoint }, {CC"getConstantPool0", CC"(Ljava/lang/Class;)J", (void*)&WB_GetConstantPool }, {CC"getResolvedReferences0", CC"(Ljava/lang/Class;)[Ljava/lang/Object;", (void*)&WB_GetResolvedReferences}, + {CC"getObjectsViaKlassOopMaps0", + CC"(Ljava/lang/Object;)[Ljava/lang/Object;", (void*)&WB_getObjectsViaKlassOopMaps}, + {CC"getObjectsViaOopIterator0", + CC"(Ljava/lang/Object;)[Ljava/lang/Object;",(void*)&WB_getObjectsViaOopIterator}, + {CC"getObjectsViaFrameOopIterator", + CC"(I)[Ljava/lang/Object;", (void*)&WB_getObjectsViaFrameOopIterator}, {CC"getFieldEntriesLength0", CC"(Ljava/lang/Class;)I", (void*)&WB_getFieldEntriesLength}, {CC"getFieldCPIndex0", CC"(Ljava/lang/Class;I)I", (void*)&WB_getFieldCPIndex}, {CC"getMethodEntriesLength0", CC"(Ljava/lang/Class;)I", (void*)&WB_getMethodEntriesLength}, diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 0d4adc7f587..fb0a7c77eac 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -75,6 +75,7 @@ #endif #include +#include static const char _default_java_launcher[] = "generic"; @@ -362,6 +363,18 @@ bool Arguments::internal_module_property_helper(const char* property, bool check return false; } +bool Arguments::patching_migrated_classes(const char* property, const char* value) { + if (strncmp(property, MODULE_PROPERTY_PREFIX, MODULE_PROPERTY_PREFIX_LEN) == 0) { + const char* property_suffix = property + MODULE_PROPERTY_PREFIX_LEN; + if (matches_property_suffix(property_suffix, PATCH, PATCH_LEN)) { + if (strcmp(value, "java.base-valueclasses.jar")) { + return true; + } + } + } + return false; +} + // Process java launcher properties. void Arguments::process_sun_java_launcher_properties(JavaVMInitArgs* args) { // See if sun.java.launcher is defined. @@ -1801,14 +1814,13 @@ bool Arguments::executing_unit_tests() { static unsigned int addreads_count = 0; static unsigned int addexports_count = 0; static unsigned int addopens_count = 0; -static unsigned int patch_mod_count = 0; static unsigned int enable_native_access_count = 0; static bool patch_mod_javabase = false; // Check the consistency of vm_init_args bool Arguments::check_vm_args_consistency() { // This may modify compiler flags. Must be called before CompilerConfig::check_args_consistency() - if (!CDSConfig::check_vm_args_consistency(patch_mod_javabase, mode_flag_cmd_line)) { + if (!CDSConfig::check_vm_args_consistency(mode_flag_cmd_line)) { return false; } @@ -2075,11 +2087,8 @@ int Arguments::process_patch_mod_option(const char* patch_mod_tail) { memcpy(module_name, patch_mod_tail, module_len); *(module_name + module_len) = '\0'; // The path piece begins one past the module_equal sign - add_patch_mod_prefix(module_name, module_equal + 1); + add_patch_mod_prefix(module_name, module_equal + 1, false /* no append */, false /* no cds */); FREE_C_HEAP_ARRAY(char, module_name); - if (!create_numbered_module_property("jdk.module.patch", patch_mod_tail, patch_mod_count++)) { - return JNI_ENOMEM; - } } else { return JNI_ENOMEM; } @@ -2087,6 +2096,70 @@ int Arguments::process_patch_mod_option(const char* patch_mod_tail) { return JNI_OK; } +// VALUECLASS_STR must match string used in the build +#define VALUECLASS_STR "valueclasses" +#define VALUECLASS_JAR "-" VALUECLASS_STR ".jar" + +// Finalize --patch-module args and --enable-preview related to value class module patches. +// Create all numbered properties passing module patches. +int Arguments::finalize_patch_module() { + // If --enable-preview and EnableValhalla is true, each module may have value classes that + // are to be patched into the module. + // For each -valueclasses.jar in /lib/valueclasses/ + // appends the equivalent of --patch-module =/lib/valueclasses/-valueclasses.jar + if (enable_preview() && EnableValhalla) { + char * valueclasses_dir = AllocateHeap(JVM_MAXPATHLEN, mtArguments); + const char * fileSep = os::file_separator(); + + jio_snprintf(valueclasses_dir, JVM_MAXPATHLEN, "%s%slib%s" VALUECLASS_STR "%s", + Arguments::get_java_home(), fileSep, fileSep, fileSep); + DIR* dir = os::opendir(valueclasses_dir); + if (dir != nullptr) { + char * module_name = AllocateHeap(JVM_MAXPATHLEN, mtArguments); + char * path = AllocateHeap(JVM_MAXPATHLEN, mtArguments); + + for (dirent * entry = os::readdir(dir); entry != nullptr; entry = os::readdir(dir)) { + // Test if file ends-with "-valueclasses.jar" + int len = (int)strlen(entry->d_name) - (sizeof(VALUECLASS_JAR) - 1); + if (len <= 0 || strcmp(&entry->d_name[len], VALUECLASS_JAR) != 0) { + continue; // too short or not the expected suffix + } + + strcpy(module_name, entry->d_name); + module_name[len] = '\0'; // truncate to just module-name + + jio_snprintf(path, JVM_MAXPATHLEN, "%s%s", valueclasses_dir, &entry->d_name); + add_patch_mod_prefix(module_name, path, true /* append */, true /* cds OK*/); + log_info(class)("--enable-preview appending value classes for module %s: %s", module_name, entry->d_name); + } + FreeHeap(module_name); + FreeHeap(path); + os::closedir(dir); + } + FreeHeap(valueclasses_dir); + } + + // Create numbered properties for each module that has been patched either + // by --patch-module or --enable-preview + // Format is "jdk.module.patch.==" + if (_patch_mod_prefix != nullptr) { + char * prop_value = AllocateHeap(JVM_MAXPATHLEN + JVM_MAXPATHLEN + 1, mtArguments); + unsigned int patch_mod_count = 0; + + for (GrowableArrayIterator it = _patch_mod_prefix->begin(); + it != _patch_mod_prefix->end(); ++it) { + jio_snprintf(prop_value, JVM_MAXPATHLEN + JVM_MAXPATHLEN + 1, "%s=%s", + (*it)->module_name(), (*it)->path_string()); + if (!create_numbered_module_property("jdk.module.patch", prop_value, patch_mod_count++)) { + FreeHeap(prop_value); + return JNI_ENOMEM; + } + } + FreeHeap(prop_value); + } + return JNI_OK; +} + // Parse -Xss memory string parameter and convert to ThreadStackSize in K. jint Arguments::parse_xss(const JavaVMOption* option, const char* tail, intx* out_ThreadStackSize) { // The min and max sizes match the values in globals.hpp, but scaled @@ -2355,6 +2428,10 @@ jint Arguments::parse_each_vm_init_arg(const JavaVMInitArgs* args, JVMFlagOrigin // --enable_preview } else if (match_option(option, "--enable-preview")) { set_enable_preview(); + // --enable-preview enables Valhalla, EnableValhalla VM option will eventually be removed before integration + if (FLAG_SET_CMDLINE(EnableValhalla, true) != JVMFlag::SUCCESS) { + return JNI_EINVAL; + } // -Xnoclassgc } else if (match_option(option, "-Xnoclassgc")) { if (FLAG_SET_CMDLINE(ClassUnloading, false) != JVMFlag::SUCCESS) { @@ -2834,16 +2911,11 @@ jint Arguments::parse_each_vm_init_arg(const JavaVMInitArgs* args, JVMFlagOrigin return JNI_OK; } -void Arguments::add_patch_mod_prefix(const char* module_name, const char* path) { - // For java.base check for duplicate --patch-module options being specified on the command line. - // This check is only required for java.base, all other duplicate module specifications - // will be checked during module system initialization. The module system initialization - // will throw an ExceptionInInitializerError if this situation occurs. - if (strcmp(module_name, JAVA_BASE_NAME) == 0) { - if (patch_mod_javabase) { - vm_exit_during_initialization("Cannot specify " JAVA_BASE_NAME " more than once to --patch-module"); - } else { - patch_mod_javabase = true; +void Arguments::add_patch_mod_prefix(const char* module_name, const char* path, bool allow_append, bool allow_cds) { + if (!allow_cds) { + CDSConfig::set_module_patching_disables_cds(); + if (strcmp(module_name, JAVA_BASE_NAME) == 0) { + CDSConfig::set_java_base_module_patching_disables_cds(); } } @@ -2852,7 +2924,24 @@ void Arguments::add_patch_mod_prefix(const char* module_name, const char* path) _patch_mod_prefix = new (mtArguments) GrowableArray(10, mtArguments); } - _patch_mod_prefix->push(new ModulePatchPath(module_name, path)); + // Scan patches for matching module + int i = _patch_mod_prefix->find_if([&](ModulePatchPath* patch) { + return (strcmp(module_name, patch->module_name()) == 0); + }); + if (i == -1) { + _patch_mod_prefix->push(new ModulePatchPath(module_name, path)); + } else { + if (allow_append) { + // append path to existing module entry + _patch_mod_prefix->at(i)->append_path(path); + } else { + if (strcmp(module_name, JAVA_BASE_NAME) == 0) { + vm_exit_during_initialization("Cannot specify " JAVA_BASE_NAME " more than once to --patch-module"); + } else { + vm_exit_during_initialization("Cannot specify a module more than once to --patch-module", module_name); + } + } + } } // Remove all empty paths from the app classpath (if IgnoreEmptyClassPaths is enabled) @@ -2965,10 +3054,14 @@ jint Arguments::finalize_vm_init_args() { return JNI_ERR; } - if (!check_vm_args_consistency()) { + // finalize --module-patch and related --enable-preview + if (finalize_patch_module() != JNI_OK) { return JNI_ERR; } + if (!check_vm_args_consistency()) { + return JNI_ERR; + } #ifndef CAN_SHOW_REGISTERS_ON_ASSERT UNSUPPORTED_OPTION(ShowRegistersOnAssert); @@ -3854,6 +3947,18 @@ jint Arguments::apply_ergo() { log_info(verification)("Turning on remote verification because local verification is on"); FLAG_SET_DEFAULT(BytecodeVerificationRemote, true); } + if (!EnableValhalla || (is_interpreter_only() && !CDSConfig::is_dumping_archive() && !UseSharedSpaces)) { + // Disable calling convention optimizations if inline types are not supported. + // Also these aren't useful in -Xint. However, don't disable them when dumping or using + // the CDS archive, as the values must match between dumptime and runtime. + FLAG_SET_DEFAULT(InlineTypePassFieldsAsArgs, false); + FLAG_SET_DEFAULT(InlineTypeReturnedAsFields, false); + } + if (!UseNonAtomicValueFlattening && !UseNullableValueFlattening && !UseAtomicValueFlattening) { + // Flattening is disabled + FLAG_SET_DEFAULT(UseArrayFlattening, false); + FLAG_SET_DEFAULT(UseFieldFlattening, false); + } #ifndef PRODUCT if (!LogVMOutput && FLAG_IS_DEFAULT(LogVMOutput)) { diff --git a/src/hotspot/share/runtime/arguments.hpp b/src/hotspot/share/runtime/arguments.hpp index 3d83959d76a..4314347856e 100644 --- a/src/hotspot/share/runtime/arguments.hpp +++ b/src/hotspot/share/runtime/arguments.hpp @@ -91,6 +91,7 @@ class ModulePatchPath : public CHeapObj { inline const char* module_name() const { return _module_name; } inline char* path_string() const { return _path->value(); } + inline void append_path(const char* path) { _path->append_value(path); } }; // Element describing System and User (-Dkey=value flags) defined property. @@ -474,6 +475,7 @@ class Arguments : AllStatic { static bool is_internal_module_property(const char* option); static bool is_incompatible_cds_internal_module_property(const char* property); + static bool patching_migrated_classes(const char* property, const char* value); // Miscellaneous System property value getter and setters. static void set_dll_dir(const char *value) { _sun_boot_library_path->set_value(value); } @@ -482,7 +484,8 @@ class Arguments : AllStatic { static void set_ext_dirs(char *value) { _ext_dirs = os::strdup_check_oom(value); } // Set up the underlying pieces of the boot class path - static void add_patch_mod_prefix(const char *module_name, const char *path); + static void add_patch_mod_prefix(const char *module_name, const char *path, bool allow_append, bool allow_cds); + static int finalize_patch_module(); static void set_boot_class_path(const char *value, bool has_jimage) { // During start up, set by os::set_boot_path() assert(get_boot_class_path() == nullptr, "Boot class path previously set"); diff --git a/src/hotspot/share/runtime/continuation.cpp b/src/hotspot/share/runtime/continuation.cpp index 727dcf8fc26..7d0fda21d64 100644 --- a/src/hotspot/share/runtime/continuation.cpp +++ b/src/hotspot/share/runtime/continuation.cpp @@ -170,12 +170,6 @@ freeze_result Continuation::try_preempt(JavaThread* current, oop continuation) { return res; } -#ifndef PRODUCT -static jlong java_tid(JavaThread* thread) { - return java_lang_Thread::thread_id(thread->threadObj()); -} -#endif - ContinuationEntry* Continuation::get_continuation_entry_for_continuation(JavaThread* thread, oop continuation) { if (thread == nullptr || continuation == nullptr) { return nullptr; @@ -405,9 +399,8 @@ frame Continuation::continuation_bottom_sender(JavaThread* thread, const frame& ContinuationEntry* ce = get_continuation_entry_for_sp(thread, callee.sp()); assert(ce != nullptr, "callee.sp(): " INTPTR_FORMAT, p2i(callee.sp())); - log_develop_debug(continuations)("continuation_bottom_sender: [" JLONG_FORMAT "] [%d] callee: " INTPTR_FORMAT - " sender_sp: " INTPTR_FORMAT, - java_tid(thread), thread->osthread()->thread_id(), p2i(callee.sp()), p2i(sender_sp)); + log_develop_debug(continuations)("continuation_bottom_sender: [%d] callee: " INTPTR_FORMAT " sender_sp: " INTPTR_FORMAT, + thread->osthread()->thread_id(), p2i(callee.sp()), p2i(sender_sp)); frame entry = ce->to_frame(); if (callee.is_interpreted_frame()) { diff --git a/src/hotspot/share/runtime/continuationFreezeThaw.cpp b/src/hotspot/share/runtime/continuationFreezeThaw.cpp index 40a8987e139..983c6e1a922 100644 --- a/src/hotspot/share/runtime/continuationFreezeThaw.cpp +++ b/src/hotspot/share/runtime/continuationFreezeThaw.cpp @@ -457,9 +457,9 @@ class FreezeBase : public StackObj { static frame sender(const frame& f) { return f.is_interpreted_frame() ? sender(f) : sender(f); } template static inline frame sender(const frame& f); - template frame new_heap_frame(frame& f, frame& caller); + template frame new_heap_frame(frame& f, frame& caller, int size_adjust = 0); inline void set_top_frame_metadata_pd(const frame& hf); - inline void patch_pd(frame& callee, const frame& caller); + inline void patch_pd(frame& callee, const frame& caller, bool is_bottom_frame); inline void patch_pd_unused(intptr_t* sp); void adjust_interpreted_frame_unextended_sp(frame& f); static inline void prepare_freeze_interpreted_top_frame(frame& f); @@ -1180,7 +1180,7 @@ void FreezeBase::patch(const frame& f, frame& hf, const frame& caller, bool is_b assert(!caller.is_empty(), ""); } - patch_pd(hf, caller); + patch_pd(hf, caller, is_bottom_frame); if (f.is_interpreted_frame()) { assert(hf.is_heap_frame(), "should be"); @@ -1277,13 +1277,35 @@ freeze_result FreezeBase::recurse_freeze_compiled_frame(frame& f, frame& caller, intptr_t* const stack_frame_top = ContinuationHelper::CompiledFrame::frame_top(f, callee_argsize, callee_interpreted); intptr_t* const stack_frame_bottom = ContinuationHelper::CompiledFrame::frame_bottom(f); // including metadata between f and its stackargs - const int argsize = ContinuationHelper::CompiledFrame::stack_argsize(f) + frame::metadata_words_at_top; - const int fsize = pointer_delta_as_int(stack_frame_bottom + argsize, stack_frame_top); + int argsize = ContinuationHelper::CompiledFrame::stack_argsize(f) + frame::metadata_words_at_top; + int fsize = pointer_delta_as_int(stack_frame_bottom + argsize, stack_frame_top); + + int real_frame_size = 0; + bool augmented = f.was_augmented_on_entry(real_frame_size); + if (augmented) { + // The args reside inside the frame so clear argsize. If the caller is compiled, + // this will cause the stack arguments passed by the caller to be freezed when + // freezing the caller frame itself. If the caller is interpreted this will have + // the effect of discarding the arg area created in the i2c stub. + argsize = 0; + fsize = real_frame_size - (callee_interpreted ? 0 : callee_argsize); +#ifdef ASSERT + nmethod* nm = f.cb()->as_nmethod(); + Method* method = nm->method(); + address return_pc = ContinuationHelper::CompiledFrame::return_pc(f); + CodeBlob* caller_cb = CodeCache::find_blob_fast(return_pc); + assert(nm->is_compiled_by_c2() || (caller_cb->is_nmethod() && caller_cb->as_nmethod()->is_compiled_by_c2()), "caller or callee should be c2 compiled"); + assert((!caller_cb->is_nmethod() && nm->is_compiled_by_c2()) || + (nm->compiler_type() != caller_cb->as_nmethod()->compiler_type()) || + (nm->is_compiled_by_c2() && !method->is_static() && method->method_holder()->is_inline_klass()), + "frame should not be extended"); +#endif + } - log_develop_trace(continuations)("recurse_freeze_compiled_frame %s _size: %d fsize: %d argsize: %d", + log_develop_trace(continuations)("recurse_freeze_compiled_frame %s _size: %d fsize: %d argsize: %d augmented: %d", ContinuationHelper::Frame::frame_method(f) != nullptr ? ContinuationHelper::Frame::frame_method(f)->name_and_sig_as_C_string() : "", - _freeze_size, fsize, argsize); + _freeze_size, fsize, argsize, augmented); // we'd rather not yield inside methods annotated with @JvmtiMountTransition assert(!ContinuationHelper::Frame::frame_method(f)->jvmti_mount_transition(), ""); @@ -1294,10 +1316,11 @@ freeze_result FreezeBase::recurse_freeze_compiled_frame(frame& f, frame& caller, bool is_bottom_frame = result == freeze_ok_bottom; assert(!caller.is_empty() || is_bottom_frame, ""); + assert(!is_bottom_frame || !augmented, "thaw extended frame without caller?"); DEBUG_ONLY(before_freeze_java_frame(f, caller, fsize, argsize, is_bottom_frame);) - frame hf = new_heap_frame(f, caller); + frame hf = new_heap_frame(f, caller, augmented ? real_frame_size - f.cb()->as_nmethod()->frame_size() : 0); intptr_t* heap_frame_top = ContinuationHelper::CompiledFrame::frame_top(hf, callee_argsize, callee_interpreted); @@ -1959,6 +1982,7 @@ class ThawBase : public StackObj { void clear_chunk(stackChunkOop chunk); template int remove_top_compiled_frame_from_chunk(stackChunkOop chunk, int &argsize); + int remove_scalarized_frames(StackChunkFrameStream& scfs, int &argsize); void copy_from_chunk(intptr_t* from, intptr_t* to, int size); void thaw_lockstack(stackChunkOop chunk); @@ -1982,7 +2006,7 @@ class ThawBase : public StackObj { inline void before_thaw_java_frame(const frame& hf, const frame& caller, bool bottom, int num_frame); inline void after_thaw_java_frame(const frame& f, bool bottom); - inline void patch(frame& f, const frame& caller, bool bottom); + inline void patch(frame& f, const frame& caller, bool bottom, bool augmented = false); void clear_bitmap_bits(address start, address end); NOINLINE void recurse_thaw_interpreted_frame(const frame& hf, frame& caller, int num_frames); @@ -1992,7 +2016,7 @@ class ThawBase : public StackObj { void push_return_frame(frame& f); inline frame new_entry_frame(); - template frame new_stack_frame(const frame& hf, frame& caller, bool bottom); + template frame new_stack_frame(const frame& hf, frame& caller, bool bottom, int size_adjust = 0); inline void patch_pd(frame& f, const frame& sender); inline void patch_pd(frame& f, intptr_t* caller_sp); inline intptr_t* align(const frame& hf, intptr_t* frame_sp, frame& caller, bool bottom); @@ -2069,6 +2093,20 @@ inline void ThawBase::clear_chunk(stackChunkOop chunk) { chunk->set_max_thawing_size(0); } +int ThawBase::remove_scalarized_frames(StackChunkFrameStream& f, int &argsize) { + intptr_t* top = f.sp(); + + while (f.cb()->as_nmethod()->needs_stack_repair()) { + f.next(SmallRegisterMap::instance(), false /* stop */); + } + assert(!f.is_done(), ""); + assert(f.is_compiled(), ""); + + intptr_t* bottom = f.sp() + f.cb()->frame_size(); + argsize = f.stack_argsize(); + return bottom - top; +} + template int ThawBase::remove_top_compiled_frame_from_chunk(stackChunkOop chunk, int &argsize) { bool empty = false; @@ -2090,15 +2128,21 @@ int ThawBase::remove_top_compiled_frame_from_chunk(stackChunkOop chunk, int &arg f.get_cb(); assert(f.is_compiled(), ""); - frame_size += f.cb()->frame_size(); - argsize = f.stack_argsize(); - if (f.cb()->as_nmethod()->is_marked_for_deoptimization()) { // The caller of the runtime stub when the continuation is preempted is not at a // Java call instruction, and so cannot rely on nmethod patching for deopt. log_develop_trace(continuations)("Deoptimizing runtime stub caller"); f.to_frame().deoptimize(nullptr); // the null thread simply avoids the assertion in deoptimize which we're not set up for } + + if (f.cb()->as_nmethod()->needs_stack_repair()) { + frame_size += remove_scalarized_frames(f, argsize); + } else { + frame_size += f.cb()->frame_size(); + argsize = f.stack_argsize(); + } + } else if (f.cb()->as_nmethod()->needs_stack_repair()) { + frame_size = remove_scalarized_frames(f, argsize); } f.next(SmallRegisterMap::instance(), true /* stop */); @@ -2378,13 +2422,19 @@ bool ThawBase::recurse_thaw_java_frame(frame& caller, int num_frames) { DEBUG_ONLY(_frames++;) int argsize = _stream.stack_argsize(); + CodeBlob* cb = _stream.cb(); _stream.next(SmallRegisterMap::instance()); assert(_stream.to_frame().is_empty() == _stream.is_done(), ""); - // we never leave a compiled caller of an interpreted frame as the top frame in the chunk - // as it makes detecting that situation and adjusting unextended_sp tricky - if (num_frames == 1 && !_stream.is_done() && FKind::interpreted && _stream.is_compiled()) { + // We never leave a compiled caller of an interpreted frame as the top frame in the chunk + // as it makes detecting that situation and adjusting unextended_sp tricky. We also always + // thaw the caller of a frame that needs_stack_repair, as it would otherwise complicate things: + // - Regardless of whether the frame was extended or not, we would need to copy the right arg + // size if its greater than the one given by the normal method signature (non-scalarized). + // - If the frame was indeed extended, leaving its caller as the top frame would complicate walking + // the chunk (we need unextended_sp, but we only have sp). + if (num_frames == 1 && !_stream.is_done() && ((FKind::interpreted && _stream.is_compiled()) || (FKind::compiled && cb->as_nmethod_or_null()->needs_stack_repair()))) { log_develop_trace(continuations)("thawing extra compiled frame to not leave a compiled interpreted-caller at top"); num_frames++; } @@ -2444,15 +2494,15 @@ inline void ThawBase::after_thaw_java_frame(const frame& f, bool bottom) { #endif } -inline void ThawBase::patch(frame& f, const frame& caller, bool bottom) { +inline void ThawBase::patch(frame& f, const frame& caller, bool bottom, bool augmented) { assert(!bottom || caller.fp() == _cont.entryFP(), ""); if (bottom) { ContinuationHelper::Frame::patch_pc(caller, _cont.is_empty() ? caller.pc() : StubRoutines::cont_returnBarrier()); - } else { + } else if (caller.is_compiled_frame()){ // caller might have been deoptimized during thaw but we've overwritten the return address when copying f from the heap. // If the caller is not deoptimized, pc is unchanged. - ContinuationHelper::Frame::patch_pc(caller, caller.raw_pc()); + ContinuationHelper::Frame::patch_pc(caller, caller.raw_pc(), augmented /*callee_augmented*/); } patch_pd(f, caller); @@ -2615,17 +2665,24 @@ void ThawBase::recurse_thaw_compiled_frame(const frame& hf, frame& caller, int n _align_size += frame::align_wiggle; // we add one whether or not we've aligned because we add it in recurse_freeze_compiled_frame } + int fsize = 0; + int added_argsize = 0; + bool augmented = hf.was_augmented_on_entry(fsize); + if (!augmented) { + added_argsize = (is_bottom_frame || caller.is_interpreted_frame()) ? hf.compiled_frame_stack_argsize() : 0; + fsize += added_argsize; + } + assert(!is_bottom_frame || !augmented, ""); + + // new_stack_frame must construct the resulting frame using hf.pc() rather than hf.raw_pc() because the frame is not // yet laid out in the stack, and so the original_pc is not stored in it. // As a result, f.is_deoptimized_frame() is always false and we must test hf to know if the frame is deoptimized. - frame f = new_stack_frame(hf, caller, is_bottom_frame); + frame f = new_stack_frame(hf, caller, is_bottom_frame, augmented ? fsize - hf.cb()->frame_size() : 0); + assert(f.cb()->frame_size() == (int)(caller.sp() - f.sp()), ""); + intptr_t* const stack_frame_top = f.sp(); intptr_t* const heap_frame_top = hf.unextended_sp(); - - const int added_argsize = (is_bottom_frame || caller.is_interpreted_frame()) ? hf.compiled_frame_stack_argsize() : 0; - int fsize = ContinuationHelper::CompiledFrame::size(hf) + added_argsize; - assert(fsize <= (int)(caller.unextended_sp() - f.unextended_sp()), ""); - intptr_t* from = heap_frame_top - frame::metadata_words_at_bottom; intptr_t* to = stack_frame_top - frame::metadata_words_at_bottom; // copy metadata, except the metadata at the top of the (unextended) entry frame @@ -2638,7 +2695,7 @@ void ThawBase::recurse_thaw_compiled_frame(const frame& hf, frame& caller, int n copy_from_chunk(from, to, sz); // copying good oops because we invoked barriers above - patch(f, caller, is_bottom_frame); + patch(f, caller, is_bottom_frame, augmented); // f.is_deoptimized_frame() is always false and we must test hf.is_deoptimized_frame() (see comment above) assert(!f.is_deoptimized_frame(), ""); diff --git a/src/hotspot/share/runtime/continuationHelper.hpp b/src/hotspot/share/runtime/continuationHelper.hpp index dffbf333bcc..d93ac476155 100644 --- a/src/hotspot/share/runtime/continuationHelper.hpp +++ b/src/hotspot/share/runtime/continuationHelper.hpp @@ -72,7 +72,7 @@ class ContinuationHelper::Frame : public AllStatic { static inline intptr_t** callee_link_address(const frame& f); static Method* frame_method(const frame& f); static inline address real_pc(const frame& f); - static inline void patch_pc(const frame& f, address pc); + static inline void patch_pc(const frame& f, address pc, bool callee_augmented = false); static address* return_pc_address(const frame& f); static address return_pc(const frame& f); static bool is_stub(CodeBlob* cb); diff --git a/src/hotspot/share/runtime/deoptimization.cpp b/src/hotspot/share/runtime/deoptimization.cpp index da9a4e9cd9a..856402c8ce8 100644 --- a/src/hotspot/share/runtime/deoptimization.cpp +++ b/src/hotspot/share/runtime/deoptimization.cpp @@ -50,6 +50,9 @@ #include "memory/universe.hpp" #include "oops/constantPool.hpp" #include "oops/fieldStreams.inline.hpp" +#include "oops/flatArrayKlass.hpp" +#include "oops/flatArrayOop.hpp" +#include "oops/inlineKlass.inline.hpp" #include "oops/method.hpp" #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" @@ -298,9 +301,27 @@ JRT_BLOCK_ENTRY(Deoptimization::UnrollBlock*, Deoptimization::fetch_unroll_info( JRT_END #if COMPILER2_OR_JVMCI + +static Klass* get_refined_array_klass(Klass* k, frame* fr, RegisterMap* map, ObjectValue* sv, TRAPS) { + // If it's an array, get the properties + if (k->is_array_klass() && !k->is_typeArray_klass()) { + assert(!k->is_refArray_klass() && !k->is_flatArray_klass(), "Unexpected refined klass"); + nmethod* nm = fr->cb()->as_nmethod_or_null(); + if (nm->is_compiled_by_c2()) { + assert(sv->has_properties(), "Property information is missing"); + ArrayKlass::ArrayProperties props = static_cast(StackValue::create_stack_value(fr, map, sv->properties())->get_jint()); + k = ObjArrayKlass::cast(k)->klass_with_properties(props, THREAD); + } else { + // TODO Graal needs to be fixed. Just go with the default properties for now + k = ObjArrayKlass::cast(k)->klass_with_properties(ArrayKlass::ArrayProperties::DEFAULT, THREAD); + } + } + return k; +} + // print information about reallocated objects -static void print_objects(JavaThread* deoptee_thread, - GrowableArray* objects, bool realloc_failures) { +static void print_objects(JavaThread* deoptee_thread, frame* deoptee, RegisterMap* map, + GrowableArray* objects, bool realloc_failures, TRAPS) { ResourceMark rm; stringStream st; // change to logStream with logging st.print_cr("REALLOC OBJECTS in thread " INTPTR_FORMAT, p2i(deoptee_thread)); @@ -316,6 +337,7 @@ static void print_objects(JavaThread* deoptee_thread, } Klass* k = java_lang_Class::as_Klass(sv->klass()->as_ConstantOopReadValue()->value()()); + k = get_refined_array_klass(k, deoptee, map, sv, THREAD); st.print(" object <" INTPTR_FORMAT "> of type ", p2i(sv->value()())); k->print_value_on(&st); @@ -349,43 +371,68 @@ static bool rematerialize_objects(JavaThread* thread, int exec_mode, nmethod* co // is set during method compilation (see Compile::Process_OopMap_Node()). // If the previous frame was popped or if we are dispatching an exception, // we don't have an oop result. - bool save_oop_result = chunk->at(0)->scope()->return_oop() && !thread->popframe_forcing_deopt_reexecution() && (exec_mode == Deoptimization::Unpack_deopt); - Handle return_value; + ScopeDesc* scope = chunk->at(0)->scope(); + bool save_oop_result = scope->return_oop() && !thread->popframe_forcing_deopt_reexecution() && (exec_mode == Deoptimization::Unpack_deopt); + // In case of the return of multiple values, we must take care + // of all oop return values. + GrowableArray return_oops; + InlineKlass* vk = nullptr; + if (save_oop_result && scope->return_scalarized()) { + vk = InlineKlass::returned_inline_klass(map); + if (vk != nullptr) { + vk->save_oop_fields(map, return_oops); + save_oop_result = false; + } + } if (save_oop_result) { // Reallocation may trigger GC. If deoptimization happened on return from // call which returns oop we need to save it since it is not in oopmap. oop result = deoptee.saved_oop_result(&map); assert(oopDesc::is_oop_or_null(result), "must be oop"); - return_value = Handle(thread, result); + return_oops.push(Handle(thread, result)); assert(Universe::heap()->is_in_or_null(result), "must be heap pointer"); if (TraceDeoptimization) { tty->print_cr("SAVED OOP RESULT " INTPTR_FORMAT " in thread " INTPTR_FORMAT, p2i(result), p2i(thread)); tty->cr(); } } - if (objects != nullptr) { + if (objects != nullptr || vk != nullptr) { if (exec_mode == Deoptimization::Unpack_none) { assert(thread->thread_state() == _thread_in_vm, "assumption"); JavaThread* THREAD = thread; // For exception macros. // Clear pending OOM if reallocation fails and return true indicating allocation failure - realloc_failures = Deoptimization::realloc_objects(thread, &deoptee, &map, objects, CHECK_AND_CLEAR_(true)); + if (vk != nullptr) { + realloc_failures = Deoptimization::realloc_inline_type_result(vk, map, return_oops, CHECK_AND_CLEAR_(true)); + } + if (objects != nullptr) { + realloc_failures = realloc_failures || Deoptimization::realloc_objects(thread, &deoptee, &map, objects, CHECK_AND_CLEAR_(true)); + guarantee(compiled_method != nullptr, "deopt must be associated with an nmethod"); + bool is_jvmci = compiled_method->is_compiled_by_jvmci(); + Deoptimization::reassign_fields(&deoptee, &map, objects, realloc_failures, is_jvmci, CHECK_AND_CLEAR_(true)); + } deoptimized_objects = true; } else { JavaThread* current = thread; // For JRT_BLOCK JRT_BLOCK - realloc_failures = Deoptimization::realloc_objects(thread, &deoptee, &map, objects, THREAD); + if (vk != nullptr) { + realloc_failures = Deoptimization::realloc_inline_type_result(vk, map, return_oops, THREAD); + } + if (objects != nullptr) { + realloc_failures = realloc_failures || Deoptimization::realloc_objects(thread, &deoptee, &map, objects, THREAD); + guarantee(compiled_method != nullptr, "deopt must be associated with an nmethod"); + bool is_jvmci = compiled_method->is_compiled_by_jvmci(); + Deoptimization::reassign_fields(&deoptee, &map, objects, realloc_failures, is_jvmci, THREAD); + } JRT_END } - guarantee(compiled_method != nullptr, "deopt must be associated with an nmethod"); - bool is_jvmci = compiled_method->is_compiled_by_jvmci(); - Deoptimization::reassign_fields(&deoptee, &map, objects, realloc_failures, is_jvmci); - if (TraceDeoptimization) { - print_objects(deoptee_thread, objects, realloc_failures); + if (TraceDeoptimization && objects != nullptr) { + print_objects(deoptee_thread, &deoptee, &map, objects, realloc_failures, thread); } } - if (save_oop_result) { + if (save_oop_result || vk != nullptr) { // Restore result. - deoptee.set_saved_oop_result(&map, return_value()); + assert(return_oops.length() == 1, "no inline type"); + deoptee.set_saved_oop_result(&map, return_oops.pop()()); } return realloc_failures; } @@ -721,7 +768,7 @@ Deoptimization::UnrollBlock* Deoptimization::fetch_unroll_info_helper(JavaThread caller_adjustment = last_frame_adjust(callee_parameters, callee_locals); } - // If the sender is deoptimized the we must retrieve the address of the handler + // If the sender is deoptimized we must retrieve the address of the handler // since the frame will "magically" show the original pc before the deopt // and we'd undo the deopt. @@ -1238,10 +1285,19 @@ bool Deoptimization::realloc_objects(JavaThread* thread, frame* fr, RegisterMap* for (int i = 0; i < objects->length(); i++) { assert(objects->at(i)->is_object(), "invalid debug information"); ObjectValue* sv = (ObjectValue*) objects->at(i); - Klass* k = java_lang_Class::as_Klass(sv->klass()->as_ConstantOopReadValue()->value()()); - oop obj = nullptr; + k = get_refined_array_klass(k, fr, reg_map, sv, THREAD); + + // Check if the object may be null and has an additional null_marker input that needs + // to be checked before using the field values. Skip re-allocation if it is null. + if (k->is_inline_klass() && sv->has_properties()) { + jint null_marker = StackValue::create_stack_value(fr, reg_map, sv->properties())->get_jint(); + if (null_marker == 0) { + continue; + } + } + oop obj = nullptr; bool cache_init_error = false; if (k->is_instance_klass()) { #if INCLUDE_JVMCI @@ -1269,16 +1325,20 @@ bool Deoptimization::realloc_objects(JavaThread* thread, frame* fr, RegisterMap* obj = ik->allocate_instance(THREAD); } } + } else if (k->is_flatArray_klass()) { + FlatArrayKlass* ak = FlatArrayKlass::cast(k); + // Inline type array must be zeroed because not all memory is reassigned + obj = ak->allocate_instance(sv->field_size(), ak->properties(), THREAD); } else if (k->is_typeArray_klass()) { TypeArrayKlass* ak = TypeArrayKlass::cast(k); assert(sv->field_size() % type2size[ak->element_type()] == 0, "non-integral array length"); int len = sv->field_size() / type2size[ak->element_type()]; InternalOOMEMark iom(THREAD); obj = ak->allocate_instance(len, THREAD); - } else if (k->is_objArray_klass()) { - ObjArrayKlass* ak = ObjArrayKlass::cast(k); + } else if (k->is_refArray_klass()) { + RefArrayKlass* ak = RefArrayKlass::cast(k); InternalOOMEMark iom(THREAD); - obj = ak->allocate_instance(sv->field_size(), THREAD); + obj = ak->allocate_instance(sv->field_size(), ak->properties(), THREAD); } if (obj == nullptr) { @@ -1300,6 +1360,21 @@ bool Deoptimization::realloc_objects(JavaThread* thread, frame* fr, RegisterMap* return failures; } +// We're deoptimizing at the return of a call, inline type fields are +// in registers. When we go back to the interpreter, it will expect a +// reference to an inline type instance. Allocate and initialize it from +// the register values here. +bool Deoptimization::realloc_inline_type_result(InlineKlass* vk, const RegisterMap& map, GrowableArray& return_oops, TRAPS) { + oop new_vt = vk->realloc_result(map, return_oops, THREAD); + if (new_vt == nullptr) { + CLEAR_PENDING_EXCEPTION; + THROW_OOP_(Universe::out_of_memory_error_realloc_objects(), true); + } + return_oops.clear(); + return_oops.push(Handle(THREAD, new_vt)); + return false; +} + #if INCLUDE_JVMCI /** * For primitive types whose kind gets "erased" at runtime (shorts become stack ints), @@ -1465,11 +1540,11 @@ class ReassignedField { public: int _offset; BasicType _type; + InstanceKlass* _klass; + bool _is_flat; + bool _is_null_free; public: - ReassignedField() { - _offset = 0; - _type = T_ILLEGAL; - } + ReassignedField() : _offset(0), _type(T_ILLEGAL), _klass(nullptr), _is_flat(false), _is_null_free(false) { } }; // Gets the fields of `klass` that are eliminated by escape analysis and need to be reassigned @@ -1483,22 +1558,47 @@ static GrowableArray* get_reassigned_fields(InstanceKlass* klas ReassignedField field; field._offset = fs.offset(); field._type = Signature::basic_type(fs.signature()); + if (fs.is_flat()) { + field._is_flat = true; + field._is_null_free = fs.is_null_free_inline_type(); + // Resolve klass of flat inline type field + field._klass = InlineKlass::cast(klass->get_inline_type_field_klass(fs.index())); + } fields->append(field); } } return fields; } -// Restore fields of an eliminated instance object employing the same field order used by the compiler. -static int reassign_fields_by_klass(InstanceKlass* klass, frame* fr, RegisterMap* reg_map, ObjectValue* sv, int svIndex, oop obj, bool is_jvmci) { +// Restore fields of an eliminated instance object employing the same field order used by the +// compiler when it scalarizes an object at safepoints. +static int reassign_fields_by_klass(InstanceKlass* klass, frame* fr, RegisterMap* reg_map, ObjectValue* sv, int svIndex, oop obj, bool is_jvmci, int base_offset, TRAPS) { GrowableArray* fields = get_reassigned_fields(klass, new GrowableArray(), is_jvmci); for (int i = 0; i < fields->length(); i++) { + BasicType type = fields->at(i)._type; + int offset = base_offset + fields->at(i)._offset; + // Check for flat inline type field before accessing the ScopeValue because it might not have any fields + if (fields->at(i)._is_flat) { + // Recursively re-assign flat inline type fields + InstanceKlass* vk = fields->at(i)._klass; + assert(vk != nullptr, "must be resolved"); + offset -= InlineKlass::cast(vk)->payload_offset(); // Adjust offset to omit oop header + svIndex = reassign_fields_by_klass(vk, fr, reg_map, sv, svIndex, obj, is_jvmci, offset, CHECK_0); + if (!fields->at(i)._is_null_free) { + ScopeValue* scope_field = sv->field_at(svIndex); + StackValue* value = StackValue::create_stack_value(fr, reg_map, scope_field); + int nm_offset = offset + InlineKlass::cast(vk)->null_marker_offset(); + obj->bool_field_put(nm_offset, value->get_jint() & 1); + svIndex++; + } + continue; // Continue because we don't need to increment svIndex + } + ScopeValue* scope_field = sv->field_at(svIndex); StackValue* value = StackValue::create_stack_value(fr, reg_map, scope_field); - int offset = fields->at(i)._offset; - BasicType type = fields->at(i)._type; switch (type) { - case T_OBJECT: case T_ARRAY: + case T_OBJECT: + case T_ARRAY: assert(value->type() == T_OBJECT, "Agreement."); obj->obj_field_put(offset, value->get_obj()()); break; @@ -1569,17 +1669,34 @@ static int reassign_fields_by_klass(InstanceKlass* klass, frame* fr, RegisterMap } svIndex++; } + return svIndex; } +// restore fields of an eliminated inline type array +void Deoptimization::reassign_flat_array_elements(frame* fr, RegisterMap* reg_map, ObjectValue* sv, flatArrayOop obj, FlatArrayKlass* vak, bool is_jvmci, TRAPS) { + InlineKlass* vk = vak->element_klass(); + assert(vk->maybe_flat_in_array(), "should only be used for flat inline type arrays"); + // Adjust offset to omit oop header + int base_offset = arrayOopDesc::base_offset_in_bytes(T_FLAT_ELEMENT) - InlineKlass::cast(vk)->payload_offset(); + // Initialize all elements of the flat inline type array + for (int i = 0; i < sv->field_size(); i++) { + ScopeValue* val = sv->field_at(i); + int offset = base_offset + (i << Klass::layout_helper_log2_element_size(vak->layout_helper())); + reassign_fields_by_klass(vk, fr, reg_map, val->as_ObjectValue(), 0, (oop)obj, is_jvmci, offset, CHECK); + } +} + // restore fields of all eliminated objects and arrays -void Deoptimization::reassign_fields(frame* fr, RegisterMap* reg_map, GrowableArray* objects, bool realloc_failures, bool is_jvmci) { +void Deoptimization::reassign_fields(frame* fr, RegisterMap* reg_map, GrowableArray* objects, bool realloc_failures, bool is_jvmci, TRAPS) { for (int i = 0; i < objects->length(); i++) { assert(objects->at(i)->is_object(), "invalid debug information"); ObjectValue* sv = (ObjectValue*) objects->at(i); Klass* k = java_lang_Class::as_Klass(sv->klass()->as_ConstantOopReadValue()->value()()); + k = get_refined_array_klass(k, fr, reg_map, sv, THREAD); + Handle obj = sv->value(); - assert(obj.not_null() || realloc_failures, "reallocation was missed"); + assert(obj.not_null() || realloc_failures || sv->has_properties(), "reallocation was missed"); #ifndef PRODUCT if (PrintDeoptimizationDetails) { tty->print_cr("reassign fields for object of type %s!", k->name()->as_C_string()); @@ -1617,11 +1734,14 @@ void Deoptimization::reassign_fields(frame* fr, RegisterMap* reg_map, GrowableAr } if (k->is_instance_klass()) { InstanceKlass* ik = InstanceKlass::cast(k); - reassign_fields_by_klass(ik, fr, reg_map, sv, 0, obj(), is_jvmci); + reassign_fields_by_klass(ik, fr, reg_map, sv, 0, obj(), is_jvmci, 0, CHECK); + } else if (k->is_flatArray_klass()) { + FlatArrayKlass* vak = FlatArrayKlass::cast(k); + reassign_flat_array_elements(fr, reg_map, sv, (flatArrayOop) obj(), vak, is_jvmci, CHECK); } else if (k->is_typeArray_klass()) { TypeArrayKlass* ak = TypeArrayKlass::cast(k); reassign_type_array_elements(fr, reg_map, sv, (typeArrayOop) obj(), ak->element_type()); - } else if (k->is_objArray_klass()) { + } else if (k->is_refArray_klass()) { reassign_object_array_elements(fr, reg_map, sv, (objArrayOop) obj()); } } @@ -1813,7 +1933,7 @@ void Deoptimization::deoptimize_single_frame(JavaThread* thread, frame fr, Deopt } void Deoptimization::deoptimize(JavaThread* thread, frame fr, DeoptReason reason) { - // Deoptimize only if the frame comes from compile code. + // Deoptimize only if the frame comes from compiled code. // Do not deoptimize the frame which is already patched // during the execution of the loops below. if (!fr.is_compiled_frame() || fr.is_deoptimized_frame()) { diff --git a/src/hotspot/share/runtime/deoptimization.hpp b/src/hotspot/share/runtime/deoptimization.hpp index 5d97e2056ad..556e82973eb 100644 --- a/src/hotspot/share/runtime/deoptimization.hpp +++ b/src/hotspot/share/runtime/deoptimization.hpp @@ -203,9 +203,11 @@ class Deoptimization : AllStatic { // Support for restoring non-escaping objects static bool realloc_objects(JavaThread* thread, frame* fr, RegisterMap* reg_map, GrowableArray* objects, TRAPS); + static bool realloc_inline_type_result(InlineKlass* vk, const RegisterMap& map, GrowableArray& return_oops, TRAPS); static void reassign_type_array_elements(frame* fr, RegisterMap* reg_map, ObjectValue* sv, typeArrayOop obj, BasicType type); static void reassign_object_array_elements(frame* fr, RegisterMap* reg_map, ObjectValue* sv, objArrayOop obj); - static void reassign_fields(frame* fr, RegisterMap* reg_map, GrowableArray* objects, bool realloc_failures, bool skip_internal); + static void reassign_flat_array_elements(frame* fr, RegisterMap* reg_map, ObjectValue* sv, flatArrayOop obj, FlatArrayKlass* vak, bool skip_internal, TRAPS); + static void reassign_fields(frame* fr, RegisterMap* reg_map, GrowableArray* objects, bool realloc_failures, bool skip_internal, TRAPS); static bool relock_objects(JavaThread* thread, GrowableArray* monitors, JavaThread* deoptee_thread, frame& fr, int exec_mode, bool realloc_failures); static void pop_frames_failed_reallocs(JavaThread* thread, vframeArray* array); diff --git a/src/hotspot/share/runtime/fieldDescriptor.cpp b/src/hotspot/share/runtime/fieldDescriptor.cpp index c5c3bdbd4bc..2d6f309bda9 100644 --- a/src/hotspot/share/runtime/fieldDescriptor.cpp +++ b/src/hotspot/share/runtime/fieldDescriptor.cpp @@ -27,6 +27,7 @@ #include "oops/annotations.hpp" #include "oops/constantPool.hpp" #include "oops/fieldStreams.inline.hpp" +#include "oops/inlineKlass.inline.hpp" #include "oops/instanceKlass.hpp" #include "oops/klass.inline.hpp" #include "oops/oop.inline.hpp" @@ -43,7 +44,8 @@ Symbol* fieldDescriptor::generic_signature() const { bool fieldDescriptor::is_trusted_final() const { InstanceKlass* ik = field_holder(); - return is_final() && (is_static() || ik->is_hidden() || ik->is_record()); + return is_final() && (is_static() || ik->is_hidden() || ik->is_record() || ik->is_inline_klass() + || (ik->is_abstract() && !ik->is_identity_class() && !ik->is_interface())); } AnnotationArray* fieldDescriptor::annotations() const { @@ -98,13 +100,13 @@ void fieldDescriptor::reinitialize(InstanceKlass* ik, const FieldInfo& fieldinfo guarantee(_fieldinfo.name_index() != 0 && _fieldinfo.signature_index() != 0, "bad constant pool index for fieldDescriptor"); } -void fieldDescriptor::print_on(outputStream* st) const { +void fieldDescriptor::print_on(outputStream* st, int base_offset) const { access_flags().print_on(st); if (field_flags().is_injected()) st->print("injected "); name()->print_value_on(st); st->print(" "); signature()->print_value_on(st); - st->print(" @%d ", offset()); + st->print(" @%d ", offset() + base_offset); if (WizardMode && has_initial_value()) { st->print("(initval "); constantTag t = initial_value_tag(); @@ -122,11 +124,11 @@ void fieldDescriptor::print_on(outputStream* st) const { void fieldDescriptor::print() const { print_on(tty); } -void fieldDescriptor::print_on_for(outputStream* st, oop obj) { - print_on(st); - st->print(" "); - +void fieldDescriptor::print_on_for(outputStream* st, oop obj, int indent, int base_offset) { BasicType ft = field_type(); + print_on(st, base_offset); + st->print(" "); + jint as_int = 0; switch (ft) { case T_BYTE: st->print("%d", obj->byte_field(offset())); @@ -156,13 +158,34 @@ void fieldDescriptor::print_on_for(outputStream* st, oop obj) { st->print("%s", obj->bool_field(offset()) ? "true" : "false"); break; case T_ARRAY: - if (obj->obj_field(offset()) != nullptr) { - obj->obj_field(offset())->print_value_on(st); - } else { - st->print("null"); - } - break; case T_OBJECT: + if (is_flat()) { // only some inline types can be flat + InlineKlass* vk = InlineKlass::cast(field_holder()->get_inline_type_field_klass(index())); + st->print("Flat inline type field '%s':", vk->name()->as_C_string()); + if (!is_null_free_inline_type()) { + assert(has_null_marker(), "should have null marker"); + InlineLayoutInfo* li = field_holder()->inline_layout_info_adr(index()); + int nm_offset = li->null_marker_offset(); + if (obj->byte_field_acquire(nm_offset) == 0) { + st->print(" null"); + return; + } + } + st->cr(); + // Print fields of flat field (recursively) + int field_offset = offset() - vk->payload_offset(); + obj = cast_to_oop(cast_from_oop
(obj) + field_offset); + FieldPrinter print_field(st, obj, indent + 1, base_offset + field_offset ); + vk->do_nonstatic_fields(&print_field); + if (this->field_flags().has_null_marker()) { + for (int i = 0; i < indent + 1; i++) st->print(" "); + st->print_cr(" - [null_marker] @%d %s", + vk->null_marker_offset() - base_offset + field_offset, + obj->bool_field(vk->null_marker_offset()) ? "Field marked as non-null" : "Field marked as null"); + } + return; // Do not print underlying representation + } + // Not flat inline type field, fall through if (obj->obj_field(offset()) != nullptr) { obj->obj_field(offset())->print_value_on(st); } else { diff --git a/src/hotspot/share/runtime/fieldDescriptor.hpp b/src/hotspot/share/runtime/fieldDescriptor.hpp index aae789b1fb7..024fda2cc9b 100644 --- a/src/hotspot/share/runtime/fieldDescriptor.hpp +++ b/src/hotspot/share/runtime/fieldDescriptor.hpp @@ -56,6 +56,7 @@ class fieldDescriptor { AccessFlags access_flags() const { return _fieldinfo.access_flags(); } FieldInfo::FieldFlags field_flags() const { return _fieldinfo.field_flags(); } FieldStatus field_status() const { return field_holder()->fields_status()->at(_fieldinfo.index()); } + LayoutKind layout_kind() const { return _fieldinfo.layout_kind(); } oop loader() const; // Offset (in bytes) of field from start of instanceOop / Klass* inline int offset() const; @@ -74,6 +75,9 @@ class fieldDescriptor { jdouble double_initial_value() const; oop string_initial_value(TRAPS) const; + // Unset strict static + inline bool is_strict_static_unset() const; + // Field signature type inline BasicType field_type() const; @@ -87,6 +91,10 @@ class fieldDescriptor { bool is_injected() const { return field_flags().is_injected(); } bool is_volatile() const { return access_flags().is_volatile(); } bool is_transient() const { return access_flags().is_transient(); } + bool is_strict() const { return access_flags().is_strict(); } + inline bool is_flat() const; + inline bool is_null_free_inline_type() const; + inline bool has_null_marker() const; bool is_synthetic() const { return access_flags().is_synthetic(); } @@ -107,8 +115,8 @@ class fieldDescriptor { // Print void print() const; - void print_on(outputStream* st) const; - void print_on_for(outputStream* st, oop obj); + void print_on(outputStream* st, int base_offset = 0) const; + void print_on_for(outputStream* st, oop obj, int indent = 0, int base_offset = 0); }; #endif // SHARE_RUNTIME_FIELDDESCRIPTOR_HPP diff --git a/src/hotspot/share/runtime/fieldDescriptor.inline.hpp b/src/hotspot/share/runtime/fieldDescriptor.inline.hpp index 6d6290f0fa1..bb146cd6395 100644 --- a/src/hotspot/share/runtime/fieldDescriptor.inline.hpp +++ b/src/hotspot/share/runtime/fieldDescriptor.inline.hpp @@ -50,6 +50,11 @@ inline int fieldDescriptor::offset() const { return field( inline bool fieldDescriptor::has_initial_value() const { return field().field_flags().is_initialized(); } inline int fieldDescriptor::initial_value_index() const { return field().initializer_index(); } +inline bool fieldDescriptor::is_strict_static_unset() const { + return (is_strict() && is_static() && + field_holder()->field_status(index()).is_strict_static_unset()); +} + inline void fieldDescriptor::set_is_field_access_watched(const bool value) { field_holder()->fields_status()->adr_at(index())->update_access_watched(value); } @@ -66,4 +71,8 @@ inline BasicType fieldDescriptor::field_type() const { return Signature::basic_type(signature()); } +inline bool fieldDescriptor::is_flat() const { return field().field_flags().is_flat(); } +inline bool fieldDescriptor::is_null_free_inline_type() const { return field().field_flags().is_null_free_inline_type(); } +inline bool fieldDescriptor::has_null_marker() const { return field().field_flags().has_null_marker(); } + #endif // SHARE_RUNTIME_FIELDDESCRIPTOR_INLINE_HPP diff --git a/src/hotspot/share/runtime/frame.cpp b/src/hotspot/share/runtime/frame.cpp index 75c6e388b0d..f3b375b47ad 100644 --- a/src/hotspot/share/runtime/frame.cpp +++ b/src/hotspot/share/runtime/frame.cpp @@ -35,6 +35,7 @@ #include "logging/log.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" +#include "oops/inlineKlass.hpp" #include "oops/markWord.hpp" #include "oops/method.inline.hpp" #include "oops/methodData.hpp" @@ -59,6 +60,9 @@ #include "utilities/debug.hpp" #include "utilities/decoder.hpp" #include "utilities/formatBuffer.hpp" +#ifdef COMPILER1 +#include "c1/c1_Runtime1.hpp" +#endif RegisterMap::RegisterMap(JavaThread *thread, UpdateMap update_map, ProcessFrames process_frames, WalkContinuation walk_cont) { _thread = thread; @@ -366,6 +370,25 @@ void frame::deoptimize(JavaThread* thread) { // Save the original pc before we patch in the new one nm->set_original_pc(this, pc()); + +#ifdef COMPILER1 + if (nm->is_compiled_by_c1() && nm->method()->has_scalarized_args() && + pc() < nm->verified_inline_entry_point()) { + // The VEP and VIEP(RO) of C1-compiled methods call into the runtime to buffer scalarized value + // type args. We can't deoptimize at that point because the buffers have not yet been initialized. + // Also, if the method is synchronized, we first need to acquire the lock. + // Don't patch the return pc to delay deoptimization until we enter the method body (the check + // added in LIRGenerator::do_Base will detect the pending deoptimization by checking the original_pc). +#if defined ASSERT && !defined AARCH64 // Stub call site does not look like NativeCall on AArch64 + NativeCall* call = nativeCall_before(this->pc()); + address dest = call->destination(); + assert(dest == Runtime1::entry_for(StubId::c1_buffer_inline_args_no_receiver_id) || + dest == Runtime1::entry_for(StubId::c1_buffer_inline_args_id), "unexpected safepoint in entry point"); +#endif + return; + } +#endif + patch_pc(thread, deopt); assert(is_deoptimized_frame(), "must be"); @@ -764,7 +787,7 @@ class InterpreterFrameClosure : public OffsetClosure { public: InterpreterFrameClosure(const frame* fr, int max_locals, int max_stack, - OopClosure* f) { + OopClosure* f, BufferedValueClosure* bvt_f) { _fr = fr; _max_locals = max_locals; _max_stack = max_stack; @@ -776,7 +799,9 @@ class InterpreterFrameClosure : public OffsetClosure { if (offset < _max_locals) { addr = (oop*) _fr->interpreter_frame_local_at(offset); assert((intptr_t*)addr >= _fr->sp(), "must be inside the frame"); - _f->do_oop(addr); + if (_f != nullptr) { + _f->do_oop(addr); + } } else { addr = (oop*) _fr->interpreter_frame_expression_stack_at((offset - _max_locals)); // In case of exceptions, the expression stack is invalid and the esp will be reset to express @@ -788,7 +813,9 @@ class InterpreterFrameClosure : public OffsetClosure { in_stack = (intptr_t*)addr >= _fr->interpreter_frame_tos_address(); } if (in_stack) { - _f->do_oop(addr); + if (_f != nullptr) { + _f->do_oop(addr); + } } } } @@ -963,7 +990,7 @@ void frame::oops_interpreted_do(OopClosure* f, const RegisterMap* map, bool quer } } - InterpreterFrameClosure blk(this, max_locals, m->max_stack(), f); + InterpreterFrameClosure blk(this, max_locals, m->max_stack(), f, nullptr); // process locals & expression stack InterpreterOopMap mask; @@ -975,6 +1002,23 @@ void frame::oops_interpreted_do(OopClosure* f, const RegisterMap* map, bool quer mask.iterate_oop(&blk); } +void frame::buffered_values_interpreted_do(BufferedValueClosure* f) { + assert(is_interpreted_frame(), "Not an interpreted frame"); + Thread *thread = Thread::current(); + methodHandle m (thread, interpreter_frame_method()); + jint bci = interpreter_frame_bci(); + + assert(m->is_method(), "checking frame value"); + assert(!m->is_native() && bci >= 0 && bci < m->code_size(), + "invalid bci value"); + + InterpreterFrameClosure blk(this, m->max_locals(), m->max_stack(), nullptr, f); + + // process locals & expression stack + InterpreterOopMap mask; + m->mask_for(bci, &mask); + mask.iterate_oop(&blk); +} void frame::oops_interpreted_arguments_do(Symbol* signature, bool has_receiver, OopClosure* f) const { InterpretedArgumentOopFinder finder(signature, has_receiver, this, f); @@ -1027,6 +1071,7 @@ class CompiledArgumentOopFinder: public SignatureIterator { virtual void handle_oop_offset() { // Extract low order register number from register array. // In LP64-land, the high-order bits are valid but unhelpful. + assert(_offset < _arg_size, "out of bounds"); VMReg reg = _regs[_offset].first(); oop *loc = _fr.oopmapreg_to_oop_location(reg, _reg_map); #ifdef ASSERT @@ -1053,11 +1098,7 @@ class CompiledArgumentOopFinder: public SignatureIterator { _has_appendix = has_appendix; _fr = fr; _reg_map = (RegisterMap*)reg_map; - _arg_size = ArgumentSizeComputer(signature).size() + (has_receiver ? 1 : 0) + (has_appendix ? 1 : 0); - - int arg_size; - _regs = SharedRuntime::find_callee_arguments(signature, has_receiver, has_appendix, &arg_size); - assert(arg_size == _arg_size, "wrong arg size"); + _regs = SharedRuntime::find_callee_arguments(signature, has_receiver, has_appendix, &_arg_size); } void oops_do() { @@ -1437,8 +1478,8 @@ void frame::describe(FrameValues& values, int frame_no, const RegisterMap* reg_m // For now just label the frame nmethod* nm = cb()->as_nmethod(); values.describe(-1, info_address, - FormatBuffer<1024>("#%d nmethod " INTPTR_FORMAT " for method J %s%s", frame_no, - p2i(nm), + FormatBuffer<1024>("#%d nmethod (%s %d) " INTPTR_FORMAT " for method J %s%s", frame_no, + nm->is_compiled_by_c1() ? "c1" : "c2", nm->frame_size(), p2i(nm), nm->method()->name_and_sig_as_C_string(), (_deopt_state == is_deoptimized) ? " (deoptimized)" : @@ -1448,36 +1489,18 @@ void frame::describe(FrameValues& values, int frame_no, const RegisterMap* reg_m { // mark arguments (see nmethod::print_nmethod_labels) Method* m = nm->method(); + CompiledEntrySignature ces(m); + ces.compute_calling_conventions(false); + const GrowableArray* sig_cc = nm->is_compiled_by_c2() ? ces.sig_cc() : ces.sig(); + const VMRegPair* regs = nm->is_compiled_by_c2() ? ces.regs_cc() : ces.regs(); + int stack_slot_offset = nm->frame_size() * wordSize; // offset, in bytes, to caller sp - int sizeargs = m->size_of_parameters(); - - BasicType* sig_bt = NEW_RESOURCE_ARRAY(BasicType, sizeargs); - VMRegPair* regs = NEW_RESOURCE_ARRAY(VMRegPair, sizeargs); - { - int sig_index = 0; - if (!m->is_static()) { - sig_bt[sig_index++] = T_OBJECT; // 'this' - } - for (SignatureStream ss(m->signature()); !ss.at_return_type(); ss.next()) { - BasicType t = ss.type(); - assert(type2size[t] == 1 || type2size[t] == 2, "size is 1 or 2"); - sig_bt[sig_index++] = t; - if (type2size[t] == 2) { - sig_bt[sig_index++] = T_VOID; - } - } - assert(sig_index == sizeargs, ""); - } - int stack_arg_slots = SharedRuntime::java_calling_convention(sig_bt, regs, sizeargs); - assert(stack_arg_slots == nm->as_nmethod()->num_stack_arg_slots(false /* rounded */) || nm->is_osr_method(), ""); int out_preserve = SharedRuntime::out_preserve_stack_slots(); int sig_index = 0; int arg_index = (m->is_static() ? 0 : -1); - for (SignatureStream ss(m->signature()); !ss.at_return_type(); ) { + for (ExtendedSignature sig = ExtendedSignature(sig_cc, SigEntryFilter()); !sig.at_end(); ++sig) { bool at_this = (arg_index == -1); - bool at_old_sp = false; - BasicType t = (at_this ? T_OBJECT : ss.type()); - assert(t == sig_bt[sig_index], "sigs in sync"); + BasicType t = (*sig)._bt; VMReg fst = regs[sig_index].first(); if (fst->is_stack()) { assert(((int)fst->reg2stack()) >= 0, "reg2stack: %d", fst->reg2stack()); @@ -1491,9 +1514,6 @@ void frame::describe(FrameValues& values, int frame_no, const RegisterMap* reg_m } sig_index += type2size[t]; arg_index += 1; - if (!at_this) { - ss.next(); - } } } diff --git a/src/hotspot/share/runtime/frame.hpp b/src/hotspot/share/runtime/frame.hpp index fbe7310f3ae..773d19357e4 100644 --- a/src/hotspot/share/runtime/frame.hpp +++ b/src/hotspot/share/runtime/frame.hpp @@ -457,6 +457,7 @@ class frame { // Oops-do's void oops_compiled_arguments_do(Symbol* signature, bool has_receiver, bool has_appendix, const RegisterMap* reg_map, OopClosure* f) const; void oops_interpreted_do(OopClosure* f, const RegisterMap* map, bool query_oop_map_cache = true) const; + void buffered_values_interpreted_do(BufferedValueClosure* f); private: void oops_interpreted_arguments_do(Symbol* signature, bool has_receiver, OopClosure* f) const; diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index 9149e389e21..fe0b833804d 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -813,9 +813,36 @@ const int ObjectAlignmentInBytes = 8; product(bool, UseXMMForArrayCopy, false, \ "Use SSE2 MOVQ instruction for Arraycopy") \ \ - develop(bool, PrintFieldLayout, false, \ + product(bool, PrintFieldLayout, false, DIAGNOSTIC, \ "Print field layout for each class") \ \ + product(bool, PrintInlineLayout, false, DIAGNOSTIC, \ + "Print field layout for each inline type or class with inline fields") \ + \ + product(bool, PrintFlatArrayLayout, false, DIAGNOSTIC, \ + "Print array layout for each inline type array") \ + \ + product(bool, UseArrayFlattening, true, \ + "Allow the VM to flatten arrays") \ + \ + product(bool, UseFieldFlattening, true, \ + "Allow the VM to flatten value fields") \ + \ + product(bool, UseNonAtomicValueFlattening, true, \ + "Allow the JVM to flatten some non-atomic null-free values") \ + \ + product(bool, UseNullableValueFlattening, true, \ + "Allow the JVM to flatten some nullable values") \ + \ + product(bool, UseAtomicValueFlattening, true, \ + "Allow the JVM to flatten some atomic values") \ + \ + product(intx, FlatArrayElementMaxOops, 4, \ + "Max nof embedded object references in an inline type to flatten, <0 no limit") \ + \ + develop(ccstrlist, PrintInlineKlassFields, "", \ + "Print fields collected by InlineKlass::collect_fields") \ + \ /* Need to limit the extent of the padding to reasonable size. */\ /* 8K is well beyond the reasonable HW cache line size, even with */\ /* aggressive prefetching, while still leaving the room for segregating */\ @@ -1775,6 +1802,9 @@ const int ObjectAlignmentInBytes = 8; product(bool, VerifyMethodHandles, trueInDebug, DIAGNOSTIC, \ "perform extra checks when constructing method handles") \ \ + product(bool, IgnoreAssertUnsetFields, false, DIAGNOSTIC, \ + "Ignore assert_unset_fields") \ + \ product(bool, ShowHiddenFrames, false, DIAGNOSTIC, \ "show method handle implementation frames (usually hidden)") \ \ @@ -1946,6 +1976,26 @@ const int ObjectAlignmentInBytes = 8; product(bool, UseFastUnorderedTimeStamps, false, EXPERIMENTAL, \ "Use platform unstable time where supported for timestamps only") \ \ + product(bool, EnableValhalla, true, \ + "Enable experimental Valhalla features") \ + \ + product_pd(bool, InlineTypePassFieldsAsArgs, \ + "Pass each inline type field as an argument at calls") \ + \ + product_pd(bool, InlineTypeReturnedAsFields, \ + "Return fields instead of an inline type reference") \ + \ + develop(bool, StressCallingConvention, false, \ + "Stress the scalarized calling convention.") \ + \ + develop(bool, PreloadClasses, true, \ + "Preloading all classes from the LoadableDescriptors attribute") \ + \ + product(ccstrlist, ForceNonTearable, "", DIAGNOSTIC, \ + "List of inline classes which are forced to be atomic " \ + "(whitespace and commas separate names, " \ + "and leading and trailing stars '*' are wildcards)") \ + \ product(bool, DeoptimizeNMethodBarriersALot, false, DIAGNOSTIC, \ "Make nmethod barriers deoptimise a lot.") \ \ diff --git a/src/hotspot/share/runtime/handles.cpp b/src/hotspot/share/runtime/handles.cpp index cd3bd3eb3e0..fd2b83e4071 100644 --- a/src/hotspot/share/runtime/handles.cpp +++ b/src/hotspot/share/runtime/handles.cpp @@ -24,8 +24,10 @@ #include "memory/allocation.inline.hpp" #include "oops/constantPool.hpp" +#include "oops/inlineKlass.hpp" #include "oops/method.hpp" #include "oops/oop.inline.hpp" +#include "runtime/atomic.hpp" #include "runtime/handles.inline.hpp" #include "runtime/javaThread.hpp" diff --git a/src/hotspot/share/runtime/handles.hpp b/src/hotspot/share/runtime/handles.hpp index d2020e34121..9d3ca98925f 100644 --- a/src/hotspot/share/runtime/handles.hpp +++ b/src/hotspot/share/runtime/handles.hpp @@ -29,6 +29,7 @@ #include "oops/oop.hpp" #include "oops/oopsHierarchy.hpp" +class InlineKlass; class InstanceKlass; class Klass; class Thread; @@ -128,6 +129,8 @@ DEF_HANDLE(stackChunk , is_stackChunk_noinline ) DEF_HANDLE(array , is_array_noinline ) DEF_HANDLE(objArray , is_objArray_noinline ) DEF_HANDLE(typeArray , is_typeArray_noinline ) +DEF_HANDLE(flatArray , is_flatArray_noinline ) +DEF_HANDLE(refArray , is_refArray_noinline ) //------------------------------------------------------------------------------------------------------------------------ diff --git a/src/hotspot/share/runtime/handles.inline.hpp b/src/hotspot/share/runtime/handles.inline.hpp index 15b06315823..efa8b997659 100644 --- a/src/hotspot/share/runtime/handles.inline.hpp +++ b/src/hotspot/share/runtime/handles.inline.hpp @@ -61,6 +61,8 @@ DEF_HANDLE_CONSTR(instance , is_instance_noinline ) DEF_HANDLE_CONSTR(array , is_array_noinline ) DEF_HANDLE_CONSTR(objArray , is_objArray_noinline ) DEF_HANDLE_CONSTR(typeArray, is_typeArray_noinline) +DEF_HANDLE_CONSTR(flatArray, is_flatArray_noinline) +DEF_HANDLE_CONSTR(refArray , is_refArray_noinline ) // Constructor for metadata handles #define DEF_METADATA_HANDLE_FN(name, type) \ diff --git a/src/hotspot/share/runtime/init.cpp b/src/hotspot/share/runtime/init.cpp index 56e6ea30c0a..6081516db23 100644 --- a/src/hotspot/share/runtime/init.cpp +++ b/src/hotspot/share/runtime/init.cpp @@ -130,6 +130,7 @@ jint init_globals() { compilationPolicy_init(); codeCache_init(); VM_Version_init(); // depends on codeCache_init for emitting code + VMRegImpl::set_regName(); // need this before generate_stubs (for printing oop maps). icache_init2(); // depends on VM_Version for choosing the mechanism // ensure we know about all blobs, stubs and entries initialize_stub_info(); @@ -162,7 +163,6 @@ jint init_globals() { interpreter_init_stub(); // before methods get loaded accessFlags_init(); InterfaceSupport_init(); - VMRegImpl::set_regName(); // need this before generate_stubs (for printing oop maps). SharedRuntime::generate_stubs(); AOTCodeCache::init_shared_blobs_table(); // need this after generate_stubs SharedRuntime::init_adapter_library(); // do this after AOTCodeCache::init_shared_blobs_table diff --git a/src/hotspot/share/runtime/javaCalls.cpp b/src/hotspot/share/runtime/javaCalls.cpp index f237886c128..6a43abb8c47 100644 --- a/src/hotspot/share/runtime/javaCalls.cpp +++ b/src/hotspot/share/runtime/javaCalls.cpp @@ -30,6 +30,7 @@ #include "interpreter/interpreter.hpp" #include "interpreter/linkResolver.hpp" #include "memory/universe.hpp" +#include "oops/inlineKlass.hpp" #include "oops/method.inline.hpp" #include "oops/oop.inline.hpp" #include "prims/jniCheck.hpp" @@ -140,22 +141,23 @@ void JavaCallWrapper::oops_do(OopClosure* f) { // Helper methods static BasicType runtime_type_from(JavaValue* result) { switch (result->get_type()) { - case T_BOOLEAN: // fall through - case T_CHAR : // fall through - case T_SHORT : // fall through - case T_INT : // fall through + case T_BOOLEAN : // fall through + case T_CHAR : // fall through + case T_SHORT : // fall through + case T_INT : // fall through #ifndef _LP64 - case T_OBJECT : // fall through - case T_ARRAY : // fall through + case T_OBJECT : // fall through + case T_ARRAY : // fall through + case T_FLAT_ELEMENT: // fall through #endif - case T_BYTE : // fall through - case T_VOID : return T_INT; - case T_LONG : return T_LONG; - case T_FLOAT : return T_FLOAT; - case T_DOUBLE : return T_DOUBLE; + case T_BYTE : // fall through + case T_VOID : return T_INT; + case T_LONG : return T_LONG; + case T_FLOAT : return T_FLOAT; + case T_DOUBLE : return T_DOUBLE; #ifdef _LP64 - case T_ARRAY : // fall through - case T_OBJECT: return T_OBJECT; + case T_ARRAY : // fall through + case T_OBJECT : return T_OBJECT; #endif default: ShouldNotReachHere(); @@ -376,6 +378,18 @@ void JavaCalls::call_helper(JavaValue* result, const methodHandle& method, JavaC os::map_stack_shadow_pages(sp); } + jobject value_buffer = nullptr; + if (InlineTypeReturnedAsFields && (result->get_type() == T_OBJECT)) { + // Pre allocate a buffered inline type in case the result is returned + // flattened by compiled code + InlineKlass* vk = method->returns_inline_type(); + if (vk != nullptr && vk->can_be_returned_as_fields()) { + oop instance = vk->allocate_instance(CHECK); + value_buffer = JNIHandles::make_local(thread, instance); + result->set_jobject(value_buffer); + } + } + // do call { JavaCallWrapper link(method, receiver, result, CHECK); { HandleMark hm(thread); // HandleMark used by HandleMarkCleaner @@ -440,6 +454,7 @@ void JavaCalls::call_helper(JavaValue* result, const methodHandle& method, JavaC if (oop_result_flag) { result->set_oop(thread->vm_result_oop()); thread->set_vm_result_oop(nullptr); + JNIHandles::destroy_local(value_buffer); } } @@ -588,7 +603,7 @@ void JavaCallArguments::verify(const methodHandle& method, BasicType return_type guarantee(method->size_of_parameters() == size_of_parameters(), "wrong no. of arguments pushed"); // Treat T_OBJECT and T_ARRAY as the same - if (is_reference_type(return_type)) return_type = T_OBJECT; + if (return_type == T_ARRAY) return_type = T_OBJECT; // Check that oop information is correct Symbol* signature = method->signature(); diff --git a/src/hotspot/share/runtime/javaThread.cpp b/src/hotspot/share/runtime/javaThread.cpp index 89d742e0ea8..d33758451ea 100644 --- a/src/hotspot/share/runtime/javaThread.cpp +++ b/src/hotspot/share/runtime/javaThread.cpp @@ -47,6 +47,7 @@ #include "memory/iterator.hpp" #include "memory/universe.hpp" #include "oops/access.inline.hpp" +#include "oops/inlineKlass.hpp" #include "oops/instanceKlass.hpp" #include "oops/klass.inline.hpp" #include "oops/oop.inline.hpp" diff --git a/src/hotspot/share/runtime/javaThread.hpp b/src/hotspot/share/runtime/javaThread.hpp index bf72fac5737..4bf877589df 100644 --- a/src/hotspot/share/runtime/javaThread.hpp +++ b/src/hotspot/share/runtime/javaThread.hpp @@ -148,6 +148,7 @@ class JavaThread: public Thread { // Used to pass back results to the interpreter or generated code running Java code. oop _vm_result_oop; // oop result is GC-preserved Metadata* _vm_result_metadata; // non-oop result + oop _return_buffered_value; // buffered value being returned // See ReduceInitialCardMarks: this holds the precise space interval of // the most recent slow path allocation for which compiled code has @@ -790,6 +791,9 @@ class JavaThread: public Thread { void set_vm_result_metadata(Metadata* x) { _vm_result_metadata = x; } + oop return_buffered_value() const { return _return_buffered_value; } + void set_return_buffered_value(oop val) { _return_buffered_value = val; } + MemRegion deferred_card_mark() const { return _deferred_card_mark; } void set_deferred_card_mark(MemRegion mr) { _deferred_card_mark = mr; } @@ -856,6 +860,7 @@ class JavaThread: public Thread { static ByteSize callee_target_offset() { return byte_offset_of(JavaThread, _callee_target); } static ByteSize vm_result_oop_offset() { return byte_offset_of(JavaThread, _vm_result_oop); } static ByteSize vm_result_metadata_offset() { return byte_offset_of(JavaThread, _vm_result_metadata); } + static ByteSize return_buffered_value_offset() { return byte_offset_of(JavaThread, _return_buffered_value); } static ByteSize thread_state_offset() { return byte_offset_of(JavaThread, _thread_state); } static ByteSize saved_exception_pc_offset() { return byte_offset_of(JavaThread, _saved_exception_pc); } static ByteSize osthread_offset() { return byte_offset_of(JavaThread, _osthread); } diff --git a/src/hotspot/share/runtime/jfieldIDWorkaround.hpp b/src/hotspot/share/runtime/jfieldIDWorkaround.hpp index 68db2e36d45..76cd18ca580 100644 --- a/src/hotspot/share/runtime/jfieldIDWorkaround.hpp +++ b/src/hotspot/share/runtime/jfieldIDWorkaround.hpp @@ -48,11 +48,15 @@ class jfieldIDWorkaround: AllStatic { // not checked, then the checked bit is zero and the rest of // the word (30 bits) contains only the offset. // + + friend class JNI_FastGetField; + private: enum { checked_bits = 1, instance_bits = 1, - address_bits = BitsPerWord - checked_bits - instance_bits, + flat_bits = 1, + address_bits = BitsPerWord - checked_bits - instance_bits - flat_bits, large_offset_bits = address_bits, // unioned with address small_offset_bits = 7, @@ -60,13 +64,15 @@ class jfieldIDWorkaround: AllStatic { checked_shift = 0, instance_shift = checked_shift + checked_bits, - address_shift = instance_shift + instance_bits, + flat_shift = instance_shift + instance_bits, + address_shift = flat_shift + flat_bits, offset_shift = address_shift, // unioned with address klass_shift = offset_shift + small_offset_bits, checked_mask_in_place = right_n_bits(checked_bits) << checked_shift, instance_mask_in_place = right_n_bits(instance_bits) << instance_shift, + flat_mask_in_place = right_n_bits(flat_bits) << flat_shift, #ifndef _WIN64 large_offset_mask = right_n_bits(large_offset_bits), small_offset_mask = right_n_bits(small_offset_bits), @@ -111,8 +117,17 @@ class jfieldIDWorkaround: AllStatic { return ((as_uint & instance_mask_in_place) == 0); } - static jfieldID to_instance_jfieldID(Klass* k, int offset) { - intptr_t as_uint = ((offset & large_offset_mask) << offset_shift) | instance_mask_in_place; + static bool is_flat_jfieldID(jfieldID id) { + uintptr_t as_uint = (uintptr_t) id; + return ((as_uint & flat_mask_in_place) != 0); + } + + static jfieldID to_instance_jfieldID(Klass* k, int offset, bool is_flat) { + intptr_t as_uint = ((offset & large_offset_mask) << offset_shift) | + instance_mask_in_place; + if (is_flat) { + as_uint |= flat_mask_in_place; + } if (VerifyJNIFields) { as_uint |= encode_klass_hash(k, offset); } @@ -154,13 +169,13 @@ class jfieldIDWorkaround: AllStatic { return result; } - static jfieldID to_jfieldID(InstanceKlass* k, int offset, bool is_static) { + static jfieldID to_jfieldID(InstanceKlass* k, int offset, bool is_static, bool is_flat) { if (is_static) { JNIid *id = k->jni_id_for(offset); DEBUG_ONLY(id->set_is_static_field_id()); return jfieldIDWorkaround::to_static_jfieldID(id); } else { - return jfieldIDWorkaround::to_instance_jfieldID(k, offset); + return jfieldIDWorkaround::to_instance_jfieldID(k, offset, is_flat); } } }; diff --git a/src/hotspot/share/runtime/jniHandles.cpp b/src/hotspot/share/runtime/jniHandles.cpp index e7564467a81..b2471725eb9 100644 --- a/src/hotspot/share/runtime/jniHandles.cpp +++ b/src/hotspot/share/runtime/jniHandles.cpp @@ -22,6 +22,7 @@ * */ +#include "classfile/vmSymbols.hpp" #include "gc/shared/collectedHeap.hpp" #include "gc/shared/oopStorage.inline.hpp" #include "gc/shared/oopStorageSet.hpp" @@ -31,6 +32,7 @@ #include "oops/access.inline.hpp" #include "oops/oop.inline.hpp" #include "runtime/handles.inline.hpp" +#include "runtime/javaCalls.hpp" #include "runtime/javaThread.inline.hpp" #include "runtime/jniHandles.inline.hpp" #include "runtime/mutexLocker.hpp" @@ -282,6 +284,44 @@ bool JNIHandles::current_thread_in_native() { JavaThread::cast(thread)->thread_state() == _thread_in_native); } +bool JNIHandles::is_same_object(jobject handle1, jobject handle2) { + oop obj1 = resolve_no_keepalive(handle1); + oop obj2 = resolve_no_keepalive(handle2); + + bool ret = obj1 == obj2; + + if (EnableValhalla) { + if (!ret && obj1 != nullptr && obj2 != nullptr && obj1->klass() == obj2->klass() && obj1->klass()->is_inline_klass()) { + // The two references are different, they are not null and they are both inline types, + // a full substitutability test is required, calling ValueObjectMethods.isSubstitutable() + // (similarly to InterpreterRuntime::is_substitutable) + JavaThread* THREAD = JavaThread::current(); + Handle ha(THREAD, obj1); + Handle hb(THREAD, obj2); + JavaValue result(T_BOOLEAN); + JavaCallArguments args; + args.push_oop(ha); + args.push_oop(hb); + methodHandle method(THREAD, Universe::is_substitutable_method()); + JavaCalls::call(&result, method, &args, THREAD); + if (HAS_PENDING_EXCEPTION) { + // Something really bad happened because isSubstitutable() should not throw exceptions + // If it is an error, just let it propagate + // If it is an exception, wrap it into an InternalError + if (!PENDING_EXCEPTION->is_a(vmClasses::Error_klass())) { + Handle e(THREAD, PENDING_EXCEPTION); + CLEAR_PENDING_EXCEPTION; + THROW_MSG_CAUSE_(vmSymbols::java_lang_InternalError(), "Internal error in substitutability test", e, false); + } + } + ret = result.get_jboolean(); + } + } + + return ret; +} + + int JNIHandleBlock::_blocks_allocated = 0; static inline bool is_tagged_free_list(uintptr_t value) { diff --git a/src/hotspot/share/runtime/jniHandles.inline.hpp b/src/hotspot/share/runtime/jniHandles.inline.hpp index 3a80d7f66f2..77c966d84d6 100644 --- a/src/hotspot/share/runtime/jniHandles.inline.hpp +++ b/src/hotspot/share/runtime/jniHandles.inline.hpp @@ -104,12 +104,6 @@ inline oop JNIHandles::resolve_no_keepalive(jobject handle) { return result; } -inline bool JNIHandles::is_same_object(jobject handle1, jobject handle2) { - oop obj1 = resolve_no_keepalive(handle1); - oop obj2 = resolve_no_keepalive(handle2); - return obj1 == obj2; -} - inline oop JNIHandles::resolve_non_null(jobject handle) { assert(handle != nullptr, "JNI handle should not be null"); oop result = resolve_impl(handle); diff --git a/src/hotspot/share/runtime/perfMemory.hpp b/src/hotspot/share/runtime/perfMemory.hpp index 0353123cb90..56f4709ce2d 100644 --- a/src/hotspot/share/runtime/perfMemory.hpp +++ b/src/hotspot/share/runtime/perfMemory.hpp @@ -83,7 +83,7 @@ typedef struct { jint name_offset; // offset of the data item name jint vector_length; // length of the vector. If 0, then scalar jbyte data_type; // type of the data item - - // 'B','Z','J','I','S','C','D','F','V','L','[' + // 'B','Z','J','I','S','C','D','F','V','L','Q','[' jbyte flags; // flags indicating misc attributes jbyte data_units; // unit of measure for the data type jbyte data_variability; // variability classification of data type diff --git a/src/hotspot/share/runtime/reflection.cpp b/src/hotspot/share/runtime/reflection.cpp index a7b468c57a3..37629c0d226 100644 --- a/src/hotspot/share/runtime/reflection.cpp +++ b/src/hotspot/share/runtime/reflection.cpp @@ -36,6 +36,7 @@ #include "memory/oopFactory.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" +#include "oops/inlineKlass.inline.hpp" #include "oops/instanceKlass.inline.hpp" #include "oops/klass.inline.hpp" #include "oops/objArrayKlass.hpp" @@ -51,6 +52,7 @@ #include "runtime/signature.hpp" #include "runtime/vframe.inline.hpp" #include "utilities/formatBuffer.hpp" +#include "utilities/globalDefinitions.hpp" static void trace_class_resolution(oop mirror) { if (mirror == nullptr || java_lang_Class::is_primitive(mirror)) { @@ -228,7 +230,8 @@ BasicType Reflection::array_get(jvalue* value, arrayOop a, int index, TRAPS) { THROW_(vmSymbols::java_lang_ArrayIndexOutOfBoundsException(), T_ILLEGAL); } if (a->is_objArray()) { - value->l = cast_from_oop(objArrayOop(a)->obj_at(index)); + oop o = objArrayOop(a)->obj_at(index, CHECK_(T_ILLEGAL)); // reading from a flat array can throw an OOM + value->l = cast_from_oop(o); return T_OBJECT; } else { assert(a->is_typeArray(), "just checking"); @@ -270,9 +273,14 @@ void Reflection::array_set(jvalue* value, arrayOop a, int index, BasicType value if (!a->is_within_bounds(index)) { THROW(vmSymbols::java_lang_ArrayIndexOutOfBoundsException()); } + if (a->is_objArray()) { if (value_type == T_OBJECT) { oop obj = cast_to_oop(value->l); + if (a->is_null_free_array() && obj == nullptr) { + THROW_MSG(vmSymbols::java_lang_NullPointerException(), "null-restricted array"); + } + if (obj != nullptr) { Klass* element_klass = ObjArrayKlass::cast(a->klass())->element_klass(); if (!obj->is_a(element_klass)) { @@ -757,17 +765,13 @@ static objArrayHandle get_exception_types(const methodHandle& method, TRAPS) { static Handle new_type(Symbol* signature, Klass* k, TRAPS) { ResolvingSignatureStream ss(signature, k, false); oop nt = ss.as_java_mirror(SignatureStream::NCDFError, CHECK_NH); - if (log_is_enabled(Debug, class, resolve)) { - trace_class_resolution(nt); - } return Handle(THREAD, nt); } oop Reflection::new_method(const methodHandle& method, bool for_constant_pool_access, TRAPS) { // Allow jdk.internal.reflect.ConstantPool to refer to methods as java.lang.reflect.Methods. - assert(!method()->is_object_initializer() && - (for_constant_pool_access || !method()->is_static_initializer()), - "Should not be the initializer"); + assert(!method()->name()->starts_with('<') || for_constant_pool_access, + "should call new_constructor instead"); InstanceKlass* holder = method->method_holder(); int slot = method->method_idnum(); @@ -815,7 +819,8 @@ oop Reflection::new_method(const methodHandle& method, bool for_constant_pool_ac oop Reflection::new_constructor(const methodHandle& method, TRAPS) { - assert(method()->is_object_initializer(), "Should be the initializer"); + assert(method()->is_object_constructor(), + "should call new_method instead"); InstanceKlass* holder = method->method_holder(); int slot = method->method_idnum(); @@ -864,9 +869,16 @@ oop Reflection::new_field(fieldDescriptor* fd, TRAPS) { java_lang_reflect_Field::set_slot(rh(), fd->index()); java_lang_reflect_Field::set_name(rh(), name()); java_lang_reflect_Field::set_type(rh(), type()); + + int flags = 0; if (fd->is_trusted_final()) { - java_lang_reflect_Field::set_trusted_final(rh()); + flags |= TRUSTED_FINAL; + } + if (fd->is_null_free_inline_type()) { + flags |= NULL_RESTRICTED; } + java_lang_reflect_Field::set_flags(rh(), flags); + // Note the ACC_ANNOTATION bit, which is a per-class access flag, is never set here. java_lang_reflect_Field::set_modifiers(rh(), fd->access_flags().as_field_flags()); java_lang_reflect_Field::set_override(rh(), false); @@ -977,7 +989,8 @@ static oop invoke(InstanceKlass* klass, // target klass is receiver's klass target_klass = receiver->klass(); // no need to resolve if method is private or - if (reflected_method->is_private() || reflected_method->name() == vmSymbols::object_initializer_name()) { + if (reflected_method->is_private() || + reflected_method->name() == vmSymbols::object_initializer_name()) { method = reflected_method; } else { // resolve based on the receiver @@ -1057,8 +1070,8 @@ static oop invoke(InstanceKlass* klass, } for (int i = 0; i < args_len; i++) { - oop type_mirror = ptypes->obj_at(i); - oop arg = args->obj_at(i); + oop type_mirror = ptypes->obj_at(i, CHECK_NULL); + oop arg = args->obj_at(i, CHECK_NULL); if (java_lang_Class::is_primitive(type_mirror)) { jvalue value; BasicType ptype = basic_type_mirror_to_basic_type(type_mirror); @@ -1159,7 +1172,6 @@ oop Reflection::invoke_constructor(oop constructor_mirror, objArrayHandle args, THROW_MSG_NULL(vmSymbols::java_lang_InternalError(), "invoke"); } methodHandle method(THREAD, m); - assert(method->name() == vmSymbols::object_initializer_name(), "invalid constructor"); // Make sure klass gets initialize klass->initialize(CHECK_NULL); diff --git a/src/hotspot/share/runtime/reflection.hpp b/src/hotspot/share/runtime/reflection.hpp index ebf44d54a6d..4b05c7d939f 100644 --- a/src/hotspot/share/runtime/reflection.hpp +++ b/src/hotspot/share/runtime/reflection.hpp @@ -48,6 +48,8 @@ class Reflection: public AllStatic { DECLARED = 1, MEMBER_PUBLIC = 0, MEMBER_DECLARED = 1, + TRUSTED_FINAL = 0x10, + NULL_RESTRICTED = 0x20, MAX_DIM = 255 }; diff --git a/src/hotspot/share/runtime/safepoint.cpp b/src/hotspot/share/runtime/safepoint.cpp index ef9e0981913..bac5b74b01a 100644 --- a/src/hotspot/share/runtime/safepoint.cpp +++ b/src/hotspot/share/runtime/safepoint.cpp @@ -39,6 +39,7 @@ #include "logging/logStream.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" +#include "oops/inlineKlass.hpp" #include "oops/oop.inline.hpp" #include "oops/symbol.hpp" #include "runtime/atomic.hpp" @@ -784,17 +785,31 @@ void ThreadSafepointState::handle_polling_page_exception() { // return point does not mark the return value as an oop (if it is), so // it needs a handle here to be updated. if( nm->is_at_poll_return(real_return_addr) ) { + ResourceMark rm; // See if return type is an oop. - bool return_oop = nm->method()->is_returning_oop(); + Method* method = nm->method(); + bool return_oop = method->is_returning_oop(); HandleMark hm(self); - Handle return_value; + GrowableArray return_values; + InlineKlass* vk = nullptr; + if (InlineTypeReturnedAsFields && return_oop) { + // Check if an inline type is returned as fields + vk = InlineKlass::returned_inline_klass(map, &return_oop, method); + if (vk != nullptr) { + // We're at a safepoint at the return of a method that returns + // multiple values. We must make sure we preserve the oop values + // across the safepoint. + vk->save_oop_fields(map, return_values); + } + } + if (return_oop) { // The oop result has been saved on the stack together with all // the other registers. In order to preserve it over GCs we need // to keep it in a handle. oop result = caller_fr.saved_oop_result(&map); assert(oopDesc::is_oop_or_null(result), "must be oop"); - return_value = Handle(self, result); + return_values.push(Handle(self, result)); assert(Universe::heap()->is_in_or_null(result), "must be heap pointer"); } @@ -808,7 +823,12 @@ void ThreadSafepointState::handle_polling_page_exception() { // restore oop result, if any if (return_oop) { - caller_fr.set_saved_oop_result(&map, return_value()); + assert(vk != nullptr || return_values.length() == 1, "only one return value"); + caller_fr.set_saved_oop_result(&map, return_values.pop()()); + } + // restore oops in scalarized fields + if (vk != nullptr) { + vk->restore_oop_results(map, return_values); } } diff --git a/src/hotspot/share/runtime/sharedRuntime.cpp b/src/hotspot/share/runtime/sharedRuntime.cpp index 4d3f1327d43..2cfe86214e6 100644 --- a/src/hotspot/share/runtime/sharedRuntime.cpp +++ b/src/hotspot/share/runtime/sharedRuntime.cpp @@ -45,12 +45,17 @@ #include "jfr/jfrEvents.hpp" #include "jvm.h" #include "logging/log.hpp" +#include "memory/oopFactory.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "metaprogramming/primitiveConversions.hpp" +#include "oops/access.hpp" +#include "oops/fieldStreams.inline.hpp" +#include "oops/inlineKlass.inline.hpp" #include "oops/klass.hpp" #include "oops/method.inline.hpp" #include "oops/objArrayKlass.hpp" +#include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" #include "prims/forte.hpp" #include "prims/jvmtiExport.hpp" @@ -1194,6 +1199,21 @@ Handle SharedRuntime::find_callee_info_helper(vframeStream& vfst, Bytecodes::Cod return receiver; } + // Substitutability test implementation piggy backs on static call resolution + Bytecodes::Code code = caller->java_code_at(bci); + if (code == Bytecodes::_if_acmpeq || code == Bytecodes::_if_acmpne) { + bc = Bytecodes::_invokestatic; + methodHandle attached_method(THREAD, extract_attached_method(vfst)); + assert(attached_method.not_null(), "must have attached method"); + vmClasses::ValueObjectMethods_klass()->initialize(CHECK_NH); + LinkResolver::resolve_invoke(callinfo, receiver, attached_method, bc, false, CHECK_NH); +#ifdef ASSERT + Method* is_subst = vmClasses::ValueObjectMethods_klass()->find_method(vmSymbols::isSubstitutable_name(), vmSymbols::object_object_boolean_signature()); + assert(callinfo.selected_method() == is_subst, "must be isSubstitutable method"); +#endif + return receiver; + } + Bytecode_invoke bytecode(caller, bci); int bytecode_index = bytecode.index(); bc = bytecode.invoke_code(); @@ -1229,6 +1249,12 @@ Handle SharedRuntime::find_callee_info_helper(vframeStream& vfst, Bytecodes::Cod default: break; } + } else { + assert(attached_method->has_scalarized_args(), "invalid use of attached method"); + if (!attached_method->method_holder()->is_inline_klass()) { + // Ignore the attached method in this case to not confuse below code + attached_method = methodHandle(current, nullptr); + } } } @@ -1237,6 +1263,7 @@ Handle SharedRuntime::find_callee_info_helper(vframeStream& vfst, Bytecodes::Cod bool has_receiver = bc != Bytecodes::_invokestatic && bc != Bytecodes::_invokedynamic && bc != Bytecodes::_invokehandle; + bool check_null_and_abstract = true; // Find receiver for non-static call if (has_receiver) { @@ -1250,26 +1277,37 @@ Handle SharedRuntime::find_callee_info_helper(vframeStream& vfst, Bytecodes::Cod // Caller-frame is a compiled frame frame callerFrame = stubFrame.sender(®_map2); - if (attached_method.is_null()) { - Method* callee = bytecode.static_target(CHECK_NH); + Method* callee = attached_method(); + if (callee == nullptr) { + callee = bytecode.static_target(CHECK_NH); if (callee == nullptr) { THROW_(vmSymbols::java_lang_NoSuchMethodException(), nullHandle); } } - - // Retrieve from a compiled argument list - receiver = Handle(current, callerFrame.retrieve_receiver(®_map2)); - assert(oopDesc::is_oop_or_null(receiver()), ""); - - if (receiver.is_null()) { - THROW_(vmSymbols::java_lang_NullPointerException(), nullHandle); + bool caller_is_c1 = callerFrame.is_compiled_frame() && callerFrame.cb()->as_nmethod()->is_compiled_by_c1(); + if (!caller_is_c1 && callee->is_scalarized_arg(0)) { + // If the receiver is an inline type that is passed as fields, no oop is available + // Resolve the call without receiver null checking. + assert(!callee->mismatch(), "calls with inline type receivers should never mismatch"); + assert(attached_method.not_null() && !attached_method->is_abstract(), "must have non-abstract attached method"); + if (bc == Bytecodes::_invokeinterface) { + bc = Bytecodes::_invokevirtual; // C2 optimistically replaces interface calls by virtual calls + } + check_null_and_abstract = false; + } else { + // Retrieve from a compiled argument list + receiver = Handle(current, callerFrame.retrieve_receiver(®_map2)); + assert(oopDesc::is_oop_or_null(receiver()), ""); + if (receiver.is_null()) { + THROW_(vmSymbols::java_lang_NullPointerException(), nullHandle); + } } } // Resolve method if (attached_method.not_null()) { // Parameterized by attached method. - LinkResolver::resolve_invoke(callinfo, receiver, attached_method, bc, CHECK_NH); + LinkResolver::resolve_invoke(callinfo, receiver, attached_method, bc, check_null_and_abstract, CHECK_NH); } else { // Parameterized by bytecode. constantPoolHandle constants(current, caller->constants()); @@ -1278,7 +1316,7 @@ Handle SharedRuntime::find_callee_info_helper(vframeStream& vfst, Bytecodes::Cod #ifdef ASSERT // Check that the receiver klass is of the right subtype and that it is initialized for virtual calls - if (has_receiver) { + if (has_receiver && check_null_and_abstract) { assert(receiver.not_null(), "should have thrown exception"); Klass* receiver_klass = receiver->klass(); Klass* rk = nullptr; @@ -1306,7 +1344,7 @@ Handle SharedRuntime::find_callee_info_helper(vframeStream& vfst, Bytecodes::Cod return receiver; } -methodHandle SharedRuntime::find_callee_method(TRAPS) { +methodHandle SharedRuntime::find_callee_method(bool is_optimized, bool& caller_is_c1, TRAPS) { JavaThread* current = THREAD; ResourceMark rm(current); // We need first to check if any Java activations (compiled, interpreted) @@ -1332,6 +1370,10 @@ methodHandle SharedRuntime::find_callee_method(TRAPS) { Bytecodes::Code bc; CallInfo callinfo; find_callee_info_helper(vfst, bc, callinfo, CHECK_(methodHandle())); + // Calls via mismatching methods are always non-scalarized + if (callinfo.resolved_method()->mismatch() && !is_optimized) { + caller_is_c1 = true; + } callee_method = methodHandle(current, callinfo.selected_method()); } assert(callee_method()->is_method(), "must be"); @@ -1339,7 +1381,7 @@ methodHandle SharedRuntime::find_callee_method(TRAPS) { } // Resolves a call. -methodHandle SharedRuntime::resolve_helper(bool is_virtual, bool is_optimized, TRAPS) { +methodHandle SharedRuntime::resolve_helper(bool is_virtual, bool is_optimized, bool& caller_is_c1, TRAPS) { JavaThread* current = THREAD; ResourceMark rm(current); RegisterMap cbl_map(current, @@ -1362,6 +1404,10 @@ methodHandle SharedRuntime::resolve_helper(bool is_virtual, bool is_optimized, T NoSafepointVerifier nsv; methodHandle callee_method(current, call_info.selected_method()); + // Calls via mismatching methods are always non-scalarized + if (caller_nm->is_compiled_by_c1() || (call_info.resolved_method()->mismatch() && !is_optimized)) { + caller_is_c1 = true; + } assert((!is_virtual && invoke_code == Bytecodes::_invokestatic ) || (!is_virtual && invoke_code == Bytecodes::_invokespecial) || @@ -1380,9 +1426,9 @@ methodHandle SharedRuntime::resolve_helper(bool is_virtual, bool is_optimized, T if (TraceCallFixup) { ResourceMark rm(current); - tty->print("resolving %s%s (%s) call to", + tty->print("resolving %s%s (%s) call%s to", (is_optimized) ? "optimized " : "", (is_virtual) ? "virtual" : "static", - Bytecodes::name(invoke_code)); + Bytecodes::name(invoke_code), (caller_is_c1) ? " from C1" : ""); callee_method->print_short_name(tty); tty->print_cr(" at pc: " INTPTR_FORMAT " to code: " INTPTR_FORMAT, p2i(caller_frame.pc()), p2i(callee_method->code())); @@ -1421,11 +1467,11 @@ methodHandle SharedRuntime::resolve_helper(bool is_virtual, bool is_optimized, T CompiledICLocker ml(caller_nm); if (is_virtual && !is_optimized) { CompiledIC* inline_cache = CompiledIC_before(caller_nm, caller_frame.pc()); - inline_cache->update(&call_info, receiver->klass()); + inline_cache->update(&call_info, receiver->klass(), caller_is_c1); } else { // Callsite is a direct call - set it to the destination method CompiledDirectCall* callsite = CompiledDirectCall::before(caller_frame.pc()); - callsite->set(callee_method); + callsite->set(callee_method, caller_is_c1); } return callee_method; @@ -1445,13 +1491,15 @@ JRT_BLOCK_ENTRY(address, SharedRuntime::handle_wrong_method_ic_miss(JavaThread* #endif /* ASSERT */ methodHandle callee_method; + bool is_optimized = false; + bool caller_is_c1 = false; JRT_BLOCK - callee_method = SharedRuntime::handle_ic_miss_helper(CHECK_NULL); + callee_method = SharedRuntime::handle_ic_miss_helper(is_optimized, caller_is_c1, CHECK_NULL); // Return Method* through TLS current->set_vm_result_metadata(callee_method()); JRT_BLOCK_END // return compiled code entry point after potential safepoints - return get_resolved_entry(current, callee_method); + return get_resolved_entry(current, callee_method, false, is_optimized, caller_is_c1); JRT_END @@ -1492,19 +1540,26 @@ JRT_BLOCK_ENTRY(address, SharedRuntime::handle_wrong_method(JavaThread* current) // so bypassing it in c2i adapter is benign. return callee->get_c2i_no_clinit_check_entry(); } else { - return callee->get_c2i_entry(); + if (caller_frame.is_interpreted_frame()) { + return callee->get_c2i_inline_entry(); + } else { + return callee->get_c2i_entry(); + } } } // Must be compiled to compiled path which is safe to stackwalk methodHandle callee_method; + bool is_static_call = false; + bool is_optimized = false; + bool caller_is_c1 = false; JRT_BLOCK // Force resolving of caller (if we called from compiled frame) - callee_method = SharedRuntime::reresolve_call_site(CHECK_NULL); + callee_method = SharedRuntime::reresolve_call_site(is_static_call, is_optimized, caller_is_c1, CHECK_NULL); current->set_vm_result_metadata(callee_method()); JRT_BLOCK_END // return compiled code entry point after potential safepoints - return get_resolved_entry(current, callee_method); + return get_resolved_entry(current, callee_method, is_static_call, is_optimized, caller_is_c1); JRT_END // Handle abstract method call @@ -1543,37 +1598,49 @@ JRT_END // return verified_code_entry if interp_only_mode is not set for the current thread; // otherwise return c2i entry. -address SharedRuntime::get_resolved_entry(JavaThread* current, methodHandle callee_method) { +address SharedRuntime::get_resolved_entry(JavaThread* current, methodHandle callee_method, + bool is_static_call, bool is_optimized, bool caller_is_c1) { if (current->is_interp_only_mode() && !callee_method->is_special_native_intrinsic()) { // In interp_only_mode we need to go to the interpreted entry // The c2i won't patch in this mode -- see fixup_callers_callsite return callee_method->get_c2i_entry(); } - assert(callee_method->verified_code_entry() != nullptr, " Jump to zero!"); - return callee_method->verified_code_entry(); + + if (caller_is_c1) { + assert(callee_method->verified_inline_code_entry() != nullptr, "Jump to zero!"); + return callee_method->verified_inline_code_entry(); + } else if (is_static_call || is_optimized) { + assert(callee_method->verified_code_entry() != nullptr, "Jump to zero!"); + return callee_method->verified_code_entry(); + } else { + assert(callee_method->verified_inline_ro_code_entry() != nullptr, "Jump to zero!"); + return callee_method->verified_inline_ro_code_entry(); + } } // resolve a static call and patch code JRT_BLOCK_ENTRY(address, SharedRuntime::resolve_static_call_C(JavaThread* current )) methodHandle callee_method; + bool caller_is_c1 = false; bool enter_special = false; JRT_BLOCK - callee_method = SharedRuntime::resolve_helper(false, false, CHECK_NULL); + callee_method = SharedRuntime::resolve_helper(false, false, caller_is_c1, CHECK_NULL); current->set_vm_result_metadata(callee_method()); JRT_BLOCK_END // return compiled code entry point after potential safepoints - return get_resolved_entry(current, callee_method); + return get_resolved_entry(current, callee_method, true, false, caller_is_c1); JRT_END // resolve virtual call and update inline cache to monomorphic JRT_BLOCK_ENTRY(address, SharedRuntime::resolve_virtual_call_C(JavaThread* current)) methodHandle callee_method; + bool caller_is_c1 = false; JRT_BLOCK - callee_method = SharedRuntime::resolve_helper(true, false, CHECK_NULL); + callee_method = SharedRuntime::resolve_helper(true, false, caller_is_c1, CHECK_NULL); current->set_vm_result_metadata(callee_method()); JRT_BLOCK_END // return compiled code entry point after potential safepoints - return get_resolved_entry(current, callee_method); + return get_resolved_entry(current, callee_method, false, false, caller_is_c1); JRT_END @@ -1581,15 +1648,18 @@ JRT_END // monomorphic, so it has no inline cache). Patch code to resolved target. JRT_BLOCK_ENTRY(address, SharedRuntime::resolve_opt_virtual_call_C(JavaThread* current)) methodHandle callee_method; + bool caller_is_c1 = false; JRT_BLOCK - callee_method = SharedRuntime::resolve_helper(true, true, CHECK_NULL); + callee_method = SharedRuntime::resolve_helper(true, true, caller_is_c1, CHECK_NULL); current->set_vm_result_metadata(callee_method()); JRT_BLOCK_END // return compiled code entry point after potential safepoints - return get_resolved_entry(current, callee_method); + return get_resolved_entry(current, callee_method, false, true, caller_is_c1); JRT_END -methodHandle SharedRuntime::handle_ic_miss_helper(TRAPS) { + + +methodHandle SharedRuntime::handle_ic_miss_helper(bool& is_optimized, bool& caller_is_c1, TRAPS) { JavaThread* current = THREAD; ResourceMark rm(current); CallInfo call_info; @@ -1607,7 +1677,7 @@ methodHandle SharedRuntime::handle_ic_miss_helper(TRAPS) { // Statistics & Tracing if (TraceCallFixup) { ResourceMark rm(current); - tty->print("IC miss (%s) call to", Bytecodes::name(bc)); + tty->print("IC miss (%s) call%s to", Bytecodes::name(bc), (caller_is_c1) ? " from C1" : ""); callee_method->print_short_name(tty); tty->print_cr(" code: " INTPTR_FORMAT, p2i(callee_method->code())); } @@ -1639,10 +1709,14 @@ methodHandle SharedRuntime::handle_ic_miss_helper(TRAPS) { frame caller_frame = current->last_frame().sender(®_map); CodeBlob* cb = caller_frame.cb(); nmethod* caller_nm = cb->as_nmethod(); + // Calls via mismatching methods are always non-scalarized + if (caller_nm->is_compiled_by_c1() || call_info.resolved_method()->mismatch()) { + caller_is_c1 = true; + } CompiledICLocker ml(caller_nm); CompiledIC* inline_cache = CompiledIC_before(caller_nm, caller_frame.pc()); - inline_cache->update(&call_info, receiver()->klass()); + inline_cache->update(&call_info, receiver()->klass(), caller_is_c1); return callee_method; } @@ -1653,7 +1727,7 @@ methodHandle SharedRuntime::handle_ic_miss_helper(TRAPS) { // sites, and static call sites. Typically used to change a call sites // destination from compiled to interpreted. // -methodHandle SharedRuntime::reresolve_call_site(TRAPS) { +methodHandle SharedRuntime::reresolve_call_site(bool& is_static_call, bool& is_optimized, bool& caller_is_c1, TRAPS) { JavaThread* current = THREAD; ResourceMark rm(current); RegisterMap reg_map(current, @@ -1663,6 +1737,9 @@ methodHandle SharedRuntime::reresolve_call_site(TRAPS) { frame stub_frame = current->last_frame(); assert(stub_frame.is_runtime_frame(), "must be a runtimeStub"); frame caller = stub_frame.sender(®_map); + if (caller.is_compiled_frame()) { + caller_is_c1 = caller.cb()->as_nmethod()->is_compiled_by_c1(); + } // Do nothing if the frame isn't a live compiled frame. // nmethod could be deoptimized by the time we get here @@ -1703,14 +1780,17 @@ methodHandle SharedRuntime::reresolve_call_site(TRAPS) { RelocIterator iter(caller_nm, call_addr, call_addr+1); bool ret = iter.next(); // Get item if (ret) { + is_static_call = false; + is_optimized = false; switch (iter.type()) { case relocInfo::static_call_type: + is_static_call = true; case relocInfo::opt_virtual_call_type: { + is_optimized = (iter.type() == relocInfo::opt_virtual_call_type); CompiledDirectCall* cdc = CompiledDirectCall::at(call_addr); cdc->set_to_clean(); break; } - case relocInfo::virtual_call_type: { // compiled, dispatched call (which used to call an interpreted method) CompiledIC* inline_cache = CompiledIC_at(caller_nm, call_addr); @@ -1724,15 +1804,14 @@ methodHandle SharedRuntime::reresolve_call_site(TRAPS) { } } - methodHandle callee_method = find_callee_method(CHECK_(methodHandle())); - + methodHandle callee_method = find_callee_method(is_optimized, caller_is_c1, CHECK_(methodHandle())); #ifndef PRODUCT Atomic::inc(&_wrong_method_ctr); if (TraceCallFixup) { ResourceMark rm(current); - tty->print("handle_wrong_method reresolving call to"); + tty->print("handle_wrong_method reresolving call%s to", (caller_is_c1) ? " from C1" : ""); callee_method->print_short_name(tty); tty->print_cr(" code: " INTPTR_FORMAT, p2i(callee_method->code())); } @@ -1938,6 +2017,21 @@ char* SharedRuntime::generate_class_cast_message( return message; } +char* SharedRuntime::generate_identity_exception_message(JavaThread* current, Klass* klass) { + assert(klass->is_inline_klass(), "Must be a concrete value class"); + const char* desc = "Cannot synchronize on an instance of value class "; + const char* className = klass->external_name(); + size_t msglen = strlen(desc) + strlen(className) + 1; + char* message = NEW_RESOURCE_ARRAY(char, msglen); + if (nullptr == message) { + // Out of memory: can't create detailed error message + message = const_cast(klass->external_name()); + } else { + jio_snprintf(message, msglen, "%s%s", desc, className); + } + return message; +} + JRT_LEAF(void, SharedRuntime::reguard_yellow_pages()) (void) JavaThread::current()->stack_overflow_state()->reguard_stack(); JRT_END @@ -2188,7 +2282,7 @@ static int _runtime_hits; // number of successful lookups in runtime table class AdapterFingerPrint : public MetaspaceObj { private: enum { - _basic_type_bits = 4, + _basic_type_bits = 5, _basic_type_mask = right_n_bits(_basic_type_bits), _basic_types_per_int = BitsPerInt / _basic_type_bits, }; @@ -2203,21 +2297,47 @@ class AdapterFingerPrint : public MetaspaceObj { } // Private construtor. Use allocate() to get an instance. - AdapterFingerPrint(int total_args_passed, BasicType* sig_bt, int len) { + AdapterFingerPrint(const GrowableArray* sig, bool has_ro_adapter = false) { int* data = data_pointer(); // Pack the BasicTypes with 8 per int - assert(len == length(total_args_passed), "sanity"); - _length = len; + int total_args_passed = total_args_passed_in_sig(sig); + _length = length(total_args_passed); int sig_index = 0; + BasicType prev_bt = T_ILLEGAL; + int vt_count = 0; for (int index = 0; index < _length; index++) { int value = 0; - for (int byte = 0; sig_index < total_args_passed && byte < _basic_types_per_int; byte++) { - int bt = adapter_encoding(sig_bt[sig_index++]); - assert((bt & _basic_type_mask) == bt, "must fit in 4 bits"); - value = (value << _basic_type_bits) | bt; + for (int byte = 0; byte < _basic_types_per_int; byte++) { + BasicType bt = T_ILLEGAL; + if (sig_index < total_args_passed) { + bt = sig->at(sig_index++)._bt; + if (bt == T_METADATA) { + // Found start of inline type in signature + assert(InlineTypePassFieldsAsArgs, "unexpected start of inline type"); + if (sig_index == 1 && has_ro_adapter) { + // With a ro_adapter, replace receiver inline type delimiter by T_VOID to prevent matching + // with other adapters that have the same inline type as first argument and no receiver. + bt = T_VOID; + } + vt_count++; + } else if (bt == T_VOID && prev_bt != T_LONG && prev_bt != T_DOUBLE) { + // Found end of inline type in signature + assert(InlineTypePassFieldsAsArgs, "unexpected end of inline type"); + vt_count--; + assert(vt_count >= 0, "invalid vt_count"); + } else if (vt_count == 0) { + // Widen fields that are not part of a scalarized inline type argument + bt = adapter_encoding(bt); + } + prev_bt = bt; + } + int bt_val = (bt == T_ILLEGAL) ? 0 : bt; + assert((bt_val & _basic_type_mask) == bt_val, "must fit in 4 bits"); + value = (value << _basic_type_bits) | bt_val; } data[index] = value; } + assert(vt_count == 0, "invalid vt_count"); } // Call deallocate instead @@ -2225,6 +2345,10 @@ class AdapterFingerPrint : public MetaspaceObj { ShouldNotCallThis(); } + static int total_args_passed_in_sig(const GrowableArray* sig) { + return (sig != nullptr) ? sig->length() : 0; + } + static int length(int total_args) { return (total_args + (_basic_types_per_int-1)) / _basic_types_per_int; } @@ -2236,13 +2360,13 @@ class AdapterFingerPrint : public MetaspaceObj { // Remap BasicTypes that are handled equivalently by the adapters. // These are correct for the current system but someday it might be // necessary to make this mapping platform dependent. - static int adapter_encoding(BasicType in) { + static BasicType adapter_encoding(BasicType in) { switch (in) { case T_BOOLEAN: case T_BYTE: case T_SHORT: case T_CHAR: - // There are all promoted to T_INT in the calling convention + // They are all promoted to T_INT in the calling convention return T_INT; case T_OBJECT: @@ -2275,13 +2399,15 @@ class AdapterFingerPrint : public MetaspaceObj { return p; } +public: template void iterate_args(Function function) { for (int i = 0; i < length(); i++) { unsigned val = (unsigned)value(i); // args are packed so that first/lower arguments are in the highest // bits of each int value, so iterate from highest to the lowest - for (int j = 32 - _basic_type_bits; j >= 0; j -= _basic_type_bits) { + int first_entry = _basic_types_per_int * _basic_type_bits; + for (int j = first_entry; j >= 0; j -= _basic_type_bits) { unsigned v = (val >> j) & _basic_type_mask; if (v == 0) { continue; @@ -2291,11 +2417,11 @@ class AdapterFingerPrint : public MetaspaceObj { } } - public: - static AdapterFingerPrint* allocate(int total_args_passed, BasicType* sig_bt) { + static AdapterFingerPrint* allocate(const GrowableArray* sig, bool has_ro_adapter = false) { + int total_args_passed = total_args_passed_in_sig(sig); int len = length(total_args_passed); int size_in_bytes = BytesPerWord * compute_size_in_words(len); - AdapterFingerPrint* afp = new (size_in_bytes) AdapterFingerPrint(total_args_passed, sig_bt, len); + AdapterFingerPrint* afp = new (size_in_bytes) AdapterFingerPrint(sig, has_ro_adapter); assert((afp->size() * BytesPerWord) == size_in_bytes, "should match"); return afp; } @@ -2344,13 +2470,10 @@ class AdapterFingerPrint : public MetaspaceObj { st.print("L"); } } - switch (arg) { - case T_INT: st.print("I"); break; - case T_LONG: long_prev = true; break; - case T_FLOAT: st.print("F"); break; - case T_DOUBLE: st.print("D"); break; - case T_VOID: break; - default: ShouldNotReachHere(); + if (arg == T_LONG) { + long_prev = true; + } else if (arg != T_VOID) { + st.print("%c", type2char((BasicType)arg)); } }); if (long_prev) { @@ -2359,57 +2482,6 @@ class AdapterFingerPrint : public MetaspaceObj { return st.as_string(); } - BasicType* as_basic_type(int& nargs) { - nargs = 0; - GrowableArray btarray; - bool long_prev = false; - - iterate_args([&] (int arg) { - if (long_prev) { - long_prev = false; - if (arg == T_VOID) { - btarray.append(T_LONG); - } else { - btarray.append(T_OBJECT); // it could be T_ARRAY; it shouldn't matter - } - } - switch (arg) { - case T_INT: // fallthrough - case T_FLOAT: // fallthrough - case T_DOUBLE: - case T_VOID: - btarray.append((BasicType)arg); - break; - case T_LONG: - long_prev = true; - break; - default: ShouldNotReachHere(); - } - }); - - if (long_prev) { - btarray.append(T_OBJECT); - } - - nargs = btarray.length(); - BasicType* sig_bt = NEW_RESOURCE_ARRAY(BasicType, nargs); - int index = 0; - GrowableArrayIterator iter = btarray.begin(); - while (iter != btarray.end()) { - sig_bt[index++] = *iter; - ++iter; - } - assert(index == btarray.length(), "sanity check"); -#ifdef ASSERT - { - AdapterFingerPrint* compare_fp = AdapterFingerPrint::allocate(nargs, sig_bt); - assert(this->equals(compare_fp), "sanity check"); - AdapterFingerPrint::deallocate(compare_fp); - } -#endif - return sig_bt; - } - bool equals(AdapterFingerPrint* other) { if (other->_length != _length) { return false; @@ -2458,10 +2530,10 @@ static AdapterHandlerTable* _adapter_handler_table; static GrowableArray* _adapter_handler_list = nullptr; // Find a entry with the same fingerprint if it exists -AdapterHandlerEntry* AdapterHandlerLibrary::lookup(int total_args_passed, BasicType* sig_bt) { +AdapterHandlerEntry* AdapterHandlerLibrary::lookup(const GrowableArray* sig, bool has_ro_adapter) { NOT_PRODUCT(_lookups++); assert_lock_strong(AdapterHandlerLibrary_lock); - AdapterFingerPrint* fp = AdapterFingerPrint::allocate(total_args_passed, sig_bt); + AdapterFingerPrint* fp = AdapterFingerPrint::allocate(sig, has_ro_adapter); AdapterHandlerEntry* entry = nullptr; #if INCLUDE_CDS // if we are building the archive then the archived adapter table is @@ -2518,7 +2590,7 @@ AdapterHandlerEntry* AdapterHandlerLibrary::_obj_obj_arg_handler = nullptr; #if INCLUDE_CDS ArchivedAdapterTable AdapterHandlerLibrary::_aot_adapter_handler_table; #endif // INCLUDE_CDS -static const int AdapterHandlerLibrary_size = 16*K; +static const int AdapterHandlerLibrary_size = 48*K; BufferBlob* AdapterHandlerLibrary::_buffer = nullptr; BufferBlob* AdapterHandlerLibrary::buffer_blob() { @@ -2570,19 +2642,31 @@ void AdapterHandlerLibrary::initialize() { { MutexLocker mu(AdapterHandlerLibrary_lock); - _no_arg_handler = create_adapter(no_arg_blob, 0, nullptr); + CompiledEntrySignature no_args; + no_args.compute_calling_conventions(); + _no_arg_handler = create_adapter(no_arg_blob, no_args, true); - BasicType obj_args[] = { T_OBJECT }; - _obj_arg_handler = create_adapter(obj_arg_blob, 1, obj_args); + CompiledEntrySignature obj_args; + SigEntry::add_entry(obj_args.sig(), T_OBJECT); + obj_args.compute_calling_conventions(); + _obj_arg_handler = create_adapter(obj_arg_blob, obj_args, true); - BasicType int_args[] = { T_INT }; - _int_arg_handler = create_adapter(int_arg_blob, 1, int_args); + CompiledEntrySignature int_args; + SigEntry::add_entry(int_args.sig(), T_INT); + int_args.compute_calling_conventions(); + _int_arg_handler = create_adapter(int_arg_blob, int_args, true); - BasicType obj_int_args[] = { T_OBJECT, T_INT }; - _obj_int_arg_handler = create_adapter(obj_int_arg_blob, 2, obj_int_args); + CompiledEntrySignature obj_int_args; + SigEntry::add_entry(obj_int_args.sig(), T_OBJECT); + SigEntry::add_entry(obj_int_args.sig(), T_INT); + obj_int_args.compute_calling_conventions(); + _obj_int_arg_handler = create_adapter(obj_int_arg_blob, obj_int_args, true); - BasicType obj_obj_args[] = { T_OBJECT, T_OBJECT }; - _obj_obj_arg_handler = create_adapter(obj_obj_arg_blob, 2, obj_obj_args); + CompiledEntrySignature obj_obj_args; + SigEntry::add_entry(obj_obj_args.sig(), T_OBJECT); + SigEntry::add_entry(obj_obj_args.sig(), T_OBJECT); + obj_obj_args.compute_calling_conventions(); + _obj_obj_arg_handler = create_adapter(obj_obj_arg_blob, obj_obj_args, true); // we should always get an entry back but we don't have any // associated blob on Zero @@ -2614,10 +2698,22 @@ AdapterHandlerEntry* AdapterHandlerLibrary::get_simple_adapter(const methodHandl return _no_arg_handler; } else if (total_args_passed == 1) { if (!method->is_static()) { + if (InlineTypePassFieldsAsArgs && method->method_holder()->is_inline_klass()) { + return nullptr; + } return _obj_arg_handler; } switch (method->signature()->char_at(1)) { - case JVM_SIGNATURE_CLASS: + case JVM_SIGNATURE_CLASS: { + if (InlineTypePassFieldsAsArgs) { + SignatureStream ss(method->signature()); + InlineKlass* vk = ss.as_inline_klass(method->method_holder()); + if (vk != nullptr) { + return nullptr; + } + } + return _obj_arg_handler; + } case JVM_SIGNATURE_ARRAY: return _obj_arg_handler; case JVM_SIGNATURE_INT: @@ -2628,9 +2724,18 @@ AdapterHandlerEntry* AdapterHandlerLibrary::get_simple_adapter(const methodHandl return _int_arg_handler; } } else if (total_args_passed == 2 && - !method->is_static()) { + !method->is_static() && (!InlineTypePassFieldsAsArgs || !method->method_holder()->is_inline_klass())) { switch (method->signature()->char_at(1)) { - case JVM_SIGNATURE_CLASS: + case JVM_SIGNATURE_CLASS: { + if (InlineTypePassFieldsAsArgs) { + SignatureStream ss(method->signature()); + InlineKlass* vk = ss.as_inline_klass(method->method_holder()); + if (vk != nullptr) { + return nullptr; + } + } + return _obj_obj_arg_handler; + } case JVM_SIGNATURE_ARRAY: return _obj_obj_arg_handler; case JVM_SIGNATURE_INT: @@ -2644,59 +2749,363 @@ AdapterHandlerEntry* AdapterHandlerLibrary::get_simple_adapter(const methodHandl return nullptr; } -class AdapterSignatureIterator : public SignatureIterator { - private: - BasicType stack_sig_bt[16]; - BasicType* sig_bt; - int index; +CompiledEntrySignature::CompiledEntrySignature(Method* method) : + _method(method), _num_inline_args(0), _has_inline_recv(false), + _regs(nullptr), _regs_cc(nullptr), _regs_cc_ro(nullptr), + _args_on_stack(0), _args_on_stack_cc(0), _args_on_stack_cc_ro(0), + _c1_needs_stack_repair(false), _c2_needs_stack_repair(false), _supers(nullptr) { + _sig = new GrowableArray((method != nullptr) ? method->size_of_parameters() : 1); + _sig_cc = new GrowableArray((method != nullptr) ? method->size_of_parameters() : 1); + _sig_cc_ro = new GrowableArray((method != nullptr) ? method->size_of_parameters() : 1); +} - public: - AdapterSignatureIterator(Symbol* signature, - fingerprint_t fingerprint, - bool is_static, - int total_args_passed) : - SignatureIterator(signature, fingerprint), - index(0) - { - sig_bt = (total_args_passed <= 16) ? stack_sig_bt : NEW_RESOURCE_ARRAY(BasicType, total_args_passed); - if (!is_static) { // Pass in receiver first - sig_bt[index++] = T_OBJECT; +// See if we can save space by sharing the same entry for VIEP and VIEP(RO), +// or the same entry for VEP and VIEP(RO). +CodeOffsets::Entries CompiledEntrySignature::c1_inline_ro_entry_type() const { + if (!has_scalarized_args()) { + // VEP/VIEP/VIEP(RO) all share the same entry. There's no packing. + return CodeOffsets::Verified_Entry; + } + if (_method->is_static()) { + // Static methods don't need VIEP(RO) + return CodeOffsets::Verified_Entry; + } + + if (has_inline_recv()) { + if (num_inline_args() == 1) { + // Share same entry for VIEP and VIEP(RO). + // This is quite common: we have an instance method in an InlineKlass that has + // no inline type args other than . + return CodeOffsets::Verified_Inline_Entry; + } else { + assert(num_inline_args() > 1, "must be"); + // No sharing: + // VIEP(RO) -- is passed as object + // VEP -- is passed as fields + return CodeOffsets::Verified_Inline_Entry_RO; } - do_parameters_on(this); } - BasicType* basic_types() { - return sig_bt; + // Either a static method, or is not an inline type + if (args_on_stack_cc() != args_on_stack_cc_ro()) { + // No sharing: + // Some arguments are passed on the stack, and we have inserted reserved entries + // into the VEP, but we never insert reserved entries into the VIEP(RO). + return CodeOffsets::Verified_Inline_Entry_RO; + } else { + // Share same entry for VEP and VIEP(RO). + return CodeOffsets::Verified_Entry; } +} -#ifdef ASSERT - int slots() { - return index; +// Returns all super methods (transitive) in classes and interfaces that are overridden by the current method. +GrowableArray* CompiledEntrySignature::get_supers() { + if (_supers != nullptr) { + return _supers; + } + _supers = new GrowableArray(); + // Skip private, static, and methods + if (_method->is_private() || _method->is_static() || _method->is_object_constructor()) { + return _supers; + } + Symbol* name = _method->name(); + Symbol* signature = _method->signature(); + const Klass* holder = _method->method_holder()->super(); + Symbol* holder_name = holder->name(); + ThreadInVMfromUnknown tiv; + JavaThread* current = JavaThread::current(); + HandleMark hm(current); + Handle loader(current, _method->method_holder()->class_loader()); + + // Walk up the class hierarchy and search for super methods + while (holder != nullptr) { + Method* super_method = holder->lookup_method(name, signature); + if (super_method == nullptr) { + break; + } + if (!super_method->is_static() && !super_method->is_private() && + (!super_method->is_package_private() || + super_method->method_holder()->is_same_class_package(loader(), holder_name))) { + _supers->push(super_method); + } + holder = super_method->method_holder()->super(); + } + // Search interfaces for super methods + Array* interfaces = _method->method_holder()->transitive_interfaces(); + for (int i = 0; i < interfaces->length(); ++i) { + Method* m = interfaces->at(i)->lookup_method(name, signature); + if (m != nullptr && !m->is_static() && m->is_public()) { + _supers->push(m); + } } + return _supers; +} + +// Iterate over arguments and compute scalarized and non-scalarized signatures +void CompiledEntrySignature::compute_calling_conventions(bool init) { + bool has_scalarized = false; + if (_method != nullptr) { + InstanceKlass* holder = _method->method_holder(); + int arg_num = 0; + if (!_method->is_static()) { + // We shouldn't scalarize 'this' in a value class constructor + if (holder->is_inline_klass() && InlineKlass::cast(holder)->can_be_passed_as_fields() && !_method->is_object_constructor() && + (init || _method->is_scalarized_arg(arg_num))) { + _sig_cc->appendAll(InlineKlass::cast(holder)->extended_sig()); + has_scalarized = true; + _has_inline_recv = true; + _num_inline_args++; + } else { + SigEntry::add_entry(_sig_cc, T_OBJECT, holder->name()); + } + SigEntry::add_entry(_sig, T_OBJECT, holder->name()); + SigEntry::add_entry(_sig_cc_ro, T_OBJECT, holder->name()); + arg_num++; + } + for (SignatureStream ss(_method->signature()); !ss.at_return_type(); ss.next()) { + BasicType bt = ss.type(); + if (bt == T_OBJECT) { + InlineKlass* vk = ss.as_inline_klass(holder); + if (vk != nullptr && vk->can_be_passed_as_fields() && (init || _method->is_scalarized_arg(arg_num))) { + // Check for a calling convention mismatch with super method(s) + bool scalar_super = false; + bool non_scalar_super = false; + GrowableArray* supers = get_supers(); + for (int i = 0; i < supers->length(); ++i) { + Method* super_method = supers->at(i); + if (super_method->is_scalarized_arg(arg_num)) { + scalar_super = true; + } else { + non_scalar_super = true; + } + } +#ifdef ASSERT + // Randomly enable below code paths for stress testing + bool stress = init && StressCallingConvention; + if (stress && (os::random() & 1) == 1) { + non_scalar_super = true; + if ((os::random() & 1) == 1) { + scalar_super = true; + } + } #endif + if (non_scalar_super) { + // Found a super method with a non-scalarized argument. Fall back to the non-scalarized calling convention. + if (scalar_super) { + // Found non-scalar *and* scalar super methods. We can't handle both. + // Mark the scalar method as mismatch and re-compile call sites to use non-scalarized calling convention. + for (int i = 0; i < supers->length(); ++i) { + Method* super_method = supers->at(i); + if (super_method->is_scalarized_arg(arg_num) DEBUG_ONLY(|| (stress && (os::random() & 1) == 1))) { + super_method->set_mismatch(); + MutexLocker ml(Compile_lock, Mutex::_safepoint_check_flag); + JavaThread* thread = JavaThread::current(); + HandleMark hm(thread); + methodHandle mh(thread, super_method); + DeoptimizationScope deopt_scope; + CodeCache::mark_for_deoptimization(&deopt_scope, mh()); + deopt_scope.deoptimize_marked(); + } + } + } + // Fall back to non-scalarized calling convention + SigEntry::add_entry(_sig_cc, T_OBJECT, ss.as_symbol()); + SigEntry::add_entry(_sig_cc_ro, T_OBJECT, ss.as_symbol()); + } else { + _num_inline_args++; + has_scalarized = true; + int last = _sig_cc->length(); + int last_ro = _sig_cc_ro->length(); + _sig_cc->appendAll(vk->extended_sig()); + _sig_cc_ro->appendAll(vk->extended_sig()); + if (bt == T_OBJECT) { + // Nullable inline type argument, insert InlineTypeNode::NullMarker field right after T_METADATA delimiter + _sig_cc->insert_before(last+1, SigEntry(T_BOOLEAN, -1, nullptr, true)); + _sig_cc_ro->insert_before(last_ro+1, SigEntry(T_BOOLEAN, -1, nullptr, true)); + } + } + } else { + SigEntry::add_entry(_sig_cc, T_OBJECT, ss.as_symbol()); + SigEntry::add_entry(_sig_cc_ro, T_OBJECT, ss.as_symbol()); + } + bt = T_OBJECT; + } else { + SigEntry::add_entry(_sig_cc, ss.type(), ss.as_symbol()); + SigEntry::add_entry(_sig_cc_ro, ss.type(), ss.as_symbol()); + } + SigEntry::add_entry(_sig, bt, ss.as_symbol()); + if (bt != T_VOID) { + arg_num++; + } + } + } - private: + // Compute the non-scalarized calling convention + _regs = NEW_RESOURCE_ARRAY(VMRegPair, _sig->length()); + _args_on_stack = SharedRuntime::java_calling_convention(_sig, _regs); - friend class SignatureIterator; // so do_parameters_on can call do_type - void do_type(BasicType type) { - sig_bt[index++] = type; - if (type == T_LONG || type == T_DOUBLE) { - sig_bt[index++] = T_VOID; // Longs & doubles take 2 Java slots + // Compute the scalarized calling conventions if there are scalarized inline types in the signature + if (has_scalarized && !_method->is_native()) { + _regs_cc = NEW_RESOURCE_ARRAY(VMRegPair, _sig_cc->length()); + _args_on_stack_cc = SharedRuntime::java_calling_convention(_sig_cc, _regs_cc); + + _regs_cc_ro = NEW_RESOURCE_ARRAY(VMRegPair, _sig_cc_ro->length()); + _args_on_stack_cc_ro = SharedRuntime::java_calling_convention(_sig_cc_ro, _regs_cc_ro); + + _c1_needs_stack_repair = (_args_on_stack_cc < _args_on_stack) || (_args_on_stack_cc_ro < _args_on_stack); + _c2_needs_stack_repair = (_args_on_stack_cc > _args_on_stack) || (_args_on_stack_cc > _args_on_stack_cc_ro); + + // Upper bound on stack arguments to avoid hitting the argument limit and + // bailing out of compilation ("unsupported incoming calling sequence"). + // TODO we need a reasonable limit (flag?) here + if (MAX2(_args_on_stack_cc, _args_on_stack_cc_ro) <= 60) { + return; // Success } } -}; + // No scalarized args + _sig_cc = _sig; + _regs_cc = _regs; + _args_on_stack_cc = _args_on_stack; + + _sig_cc_ro = _sig; + _regs_cc_ro = _regs; + _args_on_stack_cc_ro = _args_on_stack; +} + +void CompiledEntrySignature::initialize_from_fingerprint(AdapterFingerPrint* fingerprint) { + int value_object_count = 0; + bool is_receiver = true; + BasicType prev_bt = T_ILLEGAL; + bool long_prev = false; + bool has_scalarized_arguments = false; + + fingerprint->iterate_args([&] (int arg) { + BasicType bt = (BasicType)arg; + if (long_prev) { + long_prev = false; + BasicType bt_to_add; + if (bt == T_VOID) { + bt_to_add = T_LONG; + } else { + bt_to_add = T_OBJECT; // it could be T_ARRAY; it shouldn't matter + } + SigEntry::add_entry(_sig_cc, bt_to_add); + SigEntry::add_entry(_sig_cc_ro, bt_to_add); + if (value_object_count == 0) { + SigEntry::add_entry(_sig, bt_to_add); + } + } + switch (bt) { + case T_VOID: + if (is_receiver) { + // 'this' when ro adapter is available + assert(InlineTypePassFieldsAsArgs, "unexpected start of inline type"); + value_object_count++; + has_scalarized_arguments = true; + _has_inline_recv = true; + SigEntry::add_entry(_sig, T_OBJECT); + SigEntry::add_entry(_sig_cc, T_METADATA); + SigEntry::add_entry(_sig_cc_ro, T_METADATA); + } else if (prev_bt != T_LONG && prev_bt != T_DOUBLE) { + assert(InlineTypePassFieldsAsArgs, "unexpected end of inline type"); + value_object_count--; + SigEntry::add_entry(_sig_cc, T_VOID); + SigEntry::add_entry(_sig_cc_ro, T_VOID); + assert(value_object_count >= 0, "invalid value object count"); + } else { + // Nothing to add for _sig: We already added an addition T_VOID in add_entry() when adding T_LONG or T_DOUBLE. + } + break; + case T_INT: + case T_FLOAT: + case T_DOUBLE: + if (value_object_count == 0) { + SigEntry::add_entry(_sig, bt); + } + SigEntry::add_entry(_sig_cc, bt); + SigEntry::add_entry(_sig_cc_ro, bt); + break; + case T_LONG: + long_prev = true; + break; + case T_BOOLEAN: + case T_CHAR: + case T_BYTE: + case T_SHORT: + case T_OBJECT: + case T_ARRAY: + assert(value_object_count > 0 && !is_receiver, "must be value object field"); + SigEntry::add_entry(_sig_cc, bt); + SigEntry::add_entry(_sig_cc_ro, bt); + break; + case T_METADATA: + assert(InlineTypePassFieldsAsArgs, "unexpected start of inline type"); + value_object_count++; + has_scalarized_arguments = true; + SigEntry::add_entry(_sig, T_OBJECT); + SigEntry::add_entry(_sig_cc, T_METADATA); + SigEntry::add_entry(_sig_cc_ro, T_METADATA); + break; + default: { + fatal("Unexpected BasicType: %s", basictype_to_str(bt)); + } + } + prev_bt = bt; + is_receiver = false; + }); + + if (long_prev) { + // If previous bt was T_LONG and we reached the end of the signature, we know that it must be a T_OBJECT. + SigEntry::add_entry(_sig, T_OBJECT); + SigEntry::add_entry(_sig_cc, T_OBJECT); + SigEntry::add_entry(_sig_cc_ro, T_OBJECT); + } + assert(value_object_count == 0, "invalid value object count"); + + _regs = NEW_RESOURCE_ARRAY(VMRegPair, _sig->length()); + _args_on_stack = SharedRuntime::java_calling_convention(_sig, _regs); + + // Compute the scalarized calling conventions if there are scalarized inline types in the signature + if (has_scalarized_arguments) { + _regs_cc = NEW_RESOURCE_ARRAY(VMRegPair, _sig_cc->length()); + _args_on_stack_cc = SharedRuntime::java_calling_convention(_sig_cc, _regs_cc); + + _regs_cc_ro = NEW_RESOURCE_ARRAY(VMRegPair, _sig_cc_ro->length()); + _args_on_stack_cc_ro = SharedRuntime::java_calling_convention(_sig_cc_ro, _regs_cc_ro); + + _c1_needs_stack_repair = (_args_on_stack_cc < _args_on_stack) || (_args_on_stack_cc_ro < _args_on_stack); + _c2_needs_stack_repair = (_args_on_stack_cc > _args_on_stack) || (_args_on_stack_cc > _args_on_stack_cc_ro); + } else { + // No scalarized args + _sig_cc = _sig; + _regs_cc = _regs; + _args_on_stack_cc = _args_on_stack; + + _sig_cc_ro = _sig; + _regs_cc_ro = _regs; + _args_on_stack_cc_ro = _args_on_stack; + } + +#ifdef ASSERT + { + AdapterFingerPrint* compare_fp = AdapterFingerPrint::allocate(_sig_cc, _has_inline_recv); + assert(fingerprint->equals(compare_fp), "sanity check"); + AdapterFingerPrint::deallocate(compare_fp); + } +#endif +} const char* AdapterHandlerEntry::_entry_names[] = { "i2c", "c2i", "c2i_unverified", "c2i_no_clinit_check" }; #ifdef ASSERT -void AdapterHandlerLibrary::verify_adapter_sharing(int total_args_passed, BasicType* sig_bt, AdapterHandlerEntry* cached_entry) { +void AdapterHandlerLibrary::verify_adapter_sharing(CompiledEntrySignature& ces, AdapterHandlerEntry* cached_entry) { // we can only check for the same code if there is any #ifndef ZERO AdapterBlob* comparison_blob = nullptr; - AdapterHandlerEntry* comparison_entry = create_adapter(comparison_blob, total_args_passed, sig_bt, true); + AdapterHandlerEntry* comparison_entry = create_adapter(comparison_blob, ces, false, true); assert(comparison_blob == nullptr, "no blob should be created when creating an adapter for comparison"); assert(comparison_entry->compare_code(cached_entry), "code must match"); // Release the one just created @@ -2721,28 +3130,48 @@ AdapterHandlerEntry* AdapterHandlerLibrary::get_adapter(const methodHandle& meth ResourceMark rm; AdapterBlob* adapter_blob = nullptr; - // Fill in the signature array, for the calling-convention call. - int total_args_passed = method->size_of_parameters(); // All args on stack + CompiledEntrySignature ces(method()); + ces.compute_calling_conventions(); + if (ces.has_scalarized_args()) { + if (!method->has_scalarized_args()) { + method->set_has_scalarized_args(); + } + if (ces.c1_needs_stack_repair()) { + method->set_c1_needs_stack_repair(); + } + if (ces.c2_needs_stack_repair() && !method->c2_needs_stack_repair()) { + method->set_c2_needs_stack_repair(); + } + } - AdapterSignatureIterator si(method->signature(), method->constMethod()->fingerprint(), - method->is_static(), total_args_passed); - assert(si.slots() == total_args_passed, ""); - BasicType* sig_bt = si.basic_types(); { MutexLocker mu(AdapterHandlerLibrary_lock); + if (ces.has_scalarized_args() && method->is_abstract()) { + // Save a C heap allocated version of the signature for abstract methods with scalarized inline type arguments + address wrong_method_abstract = SharedRuntime::get_handle_wrong_method_abstract_stub(); + entry = AdapterHandlerLibrary::new_entry(AdapterFingerPrint::allocate(nullptr)); + entry->set_entry_points(SharedRuntime::throw_AbstractMethodError_entry(), + wrong_method_abstract, wrong_method_abstract, wrong_method_abstract, + wrong_method_abstract, wrong_method_abstract); + GrowableArray* heap_sig = new (mtInternal) GrowableArray(ces.sig_cc_ro()->length(), mtInternal); + heap_sig->appendAll(ces.sig_cc_ro()); + entry->set_sig_cc(heap_sig); + return entry; + } + // Lookup method signature's fingerprint - entry = lookup(total_args_passed, sig_bt); + entry = lookup(ces.sig_cc(), ces.has_inline_recv()); if (entry != nullptr) { assert(entry->is_linked(), "AdapterHandlerEntry must have been linked"); #ifdef ASSERT if (!entry->in_aot_cache() && VerifyAdapterSharing) { - verify_adapter_sharing(total_args_passed, sig_bt, entry); + verify_adapter_sharing(ces, entry); } #endif } else { - entry = create_adapter(adapter_blob, total_args_passed, sig_bt); + entry = create_adapter(adapter_blob, ces, /* allocate_code_blob */ true); } } @@ -2770,7 +3199,10 @@ AdapterBlob* AdapterHandlerLibrary::lookup_aot_cache(AdapterHandlerEntry* handle i2c_entry, (offsets[1] != -1) ? (i2c_entry + offsets[1]) : nullptr, (offsets[2] != -1) ? (i2c_entry + offsets[2]) : nullptr, - (offsets[3] != -1) ? (i2c_entry + offsets[3]) : nullptr + (offsets[3] != -1) ? (i2c_entry + offsets[3]) : nullptr, + (offsets[4] != -1) ? (i2c_entry + offsets[4]) : nullptr, + (offsets[5] != -1) ? (i2c_entry + offsets[5]) : nullptr, + (offsets[6] != -1) ? (i2c_entry + offsets[6]) : nullptr ); } return adapter_blob; @@ -2803,8 +3235,8 @@ void AdapterHandlerLibrary::print_adapter_handler_info(outputStream* st, Adapter bool AdapterHandlerLibrary::generate_adapter_code(AdapterBlob*& adapter_blob, AdapterHandlerEntry* handler, - int total_args_passed, - BasicType* sig_bt, + CompiledEntrySignature& ces, + bool allocate_code_blob, bool is_transient) { if (log_is_enabled(Info, perf, class, link)) { ClassLoader::perf_method_adapters_count()->inc(); @@ -2816,17 +3248,26 @@ bool AdapterHandlerLibrary::generate_adapter_code(AdapterBlob*& adapter_blob, buffer.insts()->initialize_shared_locs((relocInfo*)buffer_locs, sizeof(buffer_locs)/sizeof(relocInfo)); MacroAssembler masm(&buffer); - VMRegPair stack_regs[16]; - VMRegPair* regs = (total_args_passed <= 16) ? stack_regs : NEW_RESOURCE_ARRAY(VMRegPair, total_args_passed); // Get a description of the compiled java calling convention and the largest used (VMReg) stack slot usage - int comp_args_on_stack = SharedRuntime::java_calling_convention(sig_bt, regs, total_args_passed); SharedRuntime::generate_i2c2i_adapters(&masm, - total_args_passed, - comp_args_on_stack, - sig_bt, - regs, - handler); + ces.args_on_stack(), + ces.sig(), + ces.regs(), + ces.sig_cc(), + ces.regs_cc(), + ces.sig_cc_ro(), + ces.regs_cc_ro(), + handler, + adapter_blob, + allocate_code_blob); + + if (ces.has_scalarized_args()) { + // Save a C heap allocated version of the scalarized signature and store it in the adapter + GrowableArray* heap_sig = new (mtInternal) GrowableArray(ces.sig_cc()->length(), mtInternal); + heap_sig->appendAll(ces.sig_cc()); + handler->set_sig_cc(heap_sig); + } #ifdef ZERO // On zero there is no code to save and no need to create a blob and // or relocate the handler. @@ -2841,18 +3282,6 @@ bool AdapterHandlerLibrary::generate_adapter_code(AdapterBlob*& adapter_blob, } #endif - int entry_offset[AdapterBlob::ENTRY_COUNT]; - assert(AdapterBlob::ENTRY_COUNT == 4, "sanity"); - address i2c_entry = handler->get_i2c_entry(); - entry_offset[0] = 0; // i2c_entry offset - entry_offset[1] = (handler->get_c2i_entry() != nullptr) ? - (handler->get_c2i_entry() - i2c_entry) : -1; - entry_offset[2] = (handler->get_c2i_unverified_entry() != nullptr) ? - (handler->get_c2i_unverified_entry() - i2c_entry) : -1; - entry_offset[3] = (handler->get_c2i_no_clinit_check_entry() != nullptr) ? - (handler->get_c2i_no_clinit_check_entry() - i2c_entry) : -1; - - adapter_blob = AdapterBlob::create(&buffer, entry_offset); if (adapter_blob == nullptr) { // CodeCache is full, disable compilation // Ought to log this but compile log is only per compile thread @@ -2880,12 +3309,17 @@ bool AdapterHandlerLibrary::generate_adapter_code(AdapterBlob*& adapter_blob, } AdapterHandlerEntry* AdapterHandlerLibrary::create_adapter(AdapterBlob*& adapter_blob, - int total_args_passed, - BasicType* sig_bt, + CompiledEntrySignature& ces, + bool allocate_code_blob, bool is_transient) { - AdapterFingerPrint* fp = AdapterFingerPrint::allocate(total_args_passed, sig_bt); + AdapterFingerPrint* fp = AdapterFingerPrint::allocate(ces.sig_cc(), ces.has_inline_recv()); +#ifdef ASSERT + // Verify that we can successfully restore the compiled entry signature object. + CompiledEntrySignature ces_verify; + ces_verify.initialize_from_fingerprint(fp); +#endif AdapterHandlerEntry* handler = AdapterHandlerLibrary::new_entry(fp); - if (!generate_adapter_code(adapter_blob, handler, total_args_passed, sig_bt, is_transient)) { + if (!generate_adapter_code(adapter_blob, handler, ces, allocate_code_blob, is_transient)) { AdapterHandlerEntry::deallocate(handler); return nullptr; } @@ -2902,7 +3336,7 @@ void AdapterHandlerEntry::remove_unshareable_info() { _saved_code = nullptr; _saved_code_length = 0; #endif // ASSERT - set_entry_points(nullptr, nullptr, nullptr, nullptr, false); + set_entry_points(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, false); } class CopyAdapterTableToArchive : StackObj { @@ -2988,9 +3422,9 @@ void AdapterHandlerEntry::link() { generate_code = true; } if (generate_code) { - int nargs; - BasicType* bt = _fingerprint->as_basic_type(nargs); - if (!AdapterHandlerLibrary::generate_adapter_code(adapter_blob, this, nargs, bt, /* is_transient */ false)) { + CompiledEntrySignature ces; + ces.initialize_from_fingerprint(_fingerprint); + if (!AdapterHandlerLibrary::generate_adapter_code(adapter_blob, this, ces, true, false)) { // Don't throw exceptions during VM initialization because java.lang.* classes // might not have been initialized, causing problems when constructing the // Java exception object. @@ -3018,19 +3452,32 @@ void AdapterHandlerLibrary::lookup_simple_adapters() { assert(!_aot_adapter_handler_table.empty(), "archived adapter handler table is empty"); MutexLocker mu(AdapterHandlerLibrary_lock); - _no_arg_handler = lookup(0, nullptr); - - BasicType obj_args[] = { T_OBJECT }; - _obj_arg_handler = lookup(1, obj_args); - - BasicType int_args[] = { T_INT }; - _int_arg_handler = lookup(1, int_args); - - BasicType obj_int_args[] = { T_OBJECT, T_INT }; - _obj_int_arg_handler = lookup(2, obj_int_args); - - BasicType obj_obj_args[] = { T_OBJECT, T_OBJECT }; - _obj_obj_arg_handler = lookup(2, obj_obj_args); + ResourceMark rm; + CompiledEntrySignature no_args; + no_args.compute_calling_conventions(); + _no_arg_handler = lookup(no_args.sig_cc(), no_args.has_inline_recv()); + + CompiledEntrySignature obj_args; + SigEntry::add_entry(obj_args.sig(), T_OBJECT); + obj_args.compute_calling_conventions(); + _obj_arg_handler = lookup(obj_args.sig_cc(), obj_args.has_inline_recv()); + + CompiledEntrySignature int_args; + SigEntry::add_entry(int_args.sig(), T_INT); + int_args.compute_calling_conventions(); + _int_arg_handler = lookup(int_args.sig_cc(), int_args.has_inline_recv()); + + CompiledEntrySignature obj_int_args; + SigEntry::add_entry(obj_int_args.sig(), T_OBJECT); + SigEntry::add_entry(obj_int_args.sig(), T_INT); + obj_int_args.compute_calling_conventions(); + _obj_int_arg_handler = lookup(obj_int_args.sig_cc(), obj_int_args.has_inline_recv()); + + CompiledEntrySignature obj_obj_args; + SigEntry::add_entry(obj_obj_args.sig(), T_OBJECT); + SigEntry::add_entry(obj_obj_args.sig(), T_OBJECT); + obj_obj_args.compute_calling_conventions(); + _obj_obj_arg_handler = lookup(obj_obj_args.sig_cc(), obj_obj_args.has_inline_recv()); assert(_no_arg_handler != nullptr && _obj_arg_handler != nullptr && @@ -3049,7 +3496,10 @@ address AdapterHandlerEntry::base_address() { address base = _i2c_entry; if (base == nullptr) base = _c2i_entry; assert(base <= _c2i_entry || _c2i_entry == nullptr, ""); + assert(base <= _c2i_inline_entry || _c2i_inline_entry == nullptr, ""); + assert(base <= _c2i_inline_ro_entry || _c2i_inline_ro_entry == nullptr, ""); assert(base <= _c2i_unverified_entry || _c2i_unverified_entry == nullptr, ""); + assert(base <= _c2i_unverified_inline_entry || _c2i_unverified_inline_entry == nullptr, ""); assert(base <= _c2i_no_clinit_check_entry || _c2i_no_clinit_check_entry == nullptr, ""); return base; } @@ -3062,8 +3512,14 @@ void AdapterHandlerEntry::relocate(address new_base) { _i2c_entry += delta; if (_c2i_entry != nullptr) _c2i_entry += delta; + if (_c2i_inline_entry != nullptr) + _c2i_inline_entry += delta; + if (_c2i_inline_ro_entry != nullptr) + _c2i_inline_ro_entry += delta; if (_c2i_unverified_entry != nullptr) _c2i_unverified_entry += delta; + if (_c2i_unverified_inline_entry != nullptr) + _c2i_unverified_inline_entry += delta; if (_c2i_no_clinit_check_entry != nullptr) _c2i_no_clinit_check_entry += delta; assert(base_address() == new_base, ""); @@ -3083,6 +3539,9 @@ AdapterHandlerEntry::~AdapterHandlerEntry() { AdapterFingerPrint::deallocate(_fingerprint); _fingerprint = nullptr; } + if (_sig_cc != nullptr) { + delete _sig_cc; + } #ifdef ASSERT FREE_C_HEAP_ARRAY(unsigned char, _saved_code); #endif @@ -3170,14 +3629,24 @@ void AdapterHandlerLibrary::create_native_wrapper(const methodHandle& method) { // Fill in the signature array, for the calling-convention call. const int total_args_passed = method->size_of_parameters(); + BasicType stack_sig_bt[16]; VMRegPair stack_regs[16]; + BasicType* sig_bt = (total_args_passed <= 16) ? stack_sig_bt : NEW_RESOURCE_ARRAY(BasicType, total_args_passed); VMRegPair* regs = (total_args_passed <= 16) ? stack_regs : NEW_RESOURCE_ARRAY(VMRegPair, total_args_passed); - AdapterSignatureIterator si(method->signature(), method->constMethod()->fingerprint(), - method->is_static(), total_args_passed); - BasicType* sig_bt = si.basic_types(); - assert(si.slots() == total_args_passed, ""); - BasicType ret_type = si.return_type(); + int i = 0; + if (!method->is_static()) { // Pass in receiver first + sig_bt[i++] = T_OBJECT; + } + SignatureStream ss(method->signature()); + for (; !ss.at_return_type(); ss.next()) { + sig_bt[i++] = ss.type(); // Collect remaining bits of signature + if (ss.type() == T_LONG || ss.type() == T_DOUBLE) { + sig_bt[i++] = T_VOID; // Longs & doubles take 2 Java slots + } + } + assert(i == total_args_passed, ""); + BasicType ret_type = ss.type(); // Now get the compiled-Java arguments layout. SharedRuntime::java_calling_convention(sig_bt, regs, total_args_passed); @@ -3460,8 +3929,17 @@ void AdapterHandlerEntry::print_adapter_on(outputStream* st) const { if (get_c2i_entry() != nullptr) { st->print(" c2i: " INTPTR_FORMAT, p2i(get_c2i_entry())); } + if (get_c2i_entry() != nullptr) { + st->print(" c2iVE: " INTPTR_FORMAT, p2i(get_c2i_inline_entry())); + } + if (get_c2i_entry() != nullptr) { + st->print(" c2iVROE: " INTPTR_FORMAT, p2i(get_c2i_inline_ro_entry())); + } if (get_c2i_unverified_entry() != nullptr) { - st->print(" c2iUV: " INTPTR_FORMAT, p2i(get_c2i_unverified_entry())); + st->print(" c2iUE: " INTPTR_FORMAT, p2i(get_c2i_unverified_entry())); + } + if (get_c2i_unverified_entry() != nullptr) { + st->print(" c2iUVE: " INTPTR_FORMAT, p2i(get_c2i_unverified_inline_entry())); } if (get_c2i_no_clinit_check_entry() != nullptr) { st->print(" c2iNCI: " INTPTR_FORMAT, p2i(get_c2i_no_clinit_check_entry())); @@ -3550,3 +4028,198 @@ void SharedRuntime::on_slowpath_allocation_exit(JavaThread* current) { BarrierSet *bs = BarrierSet::barrier_set(); bs->on_slowpath_allocation_exit(current, new_obj); } + +// We are at a compiled code to interpreter call. We need backing +// buffers for all inline type arguments. Allocate an object array to +// hold them (convenient because once we're done with it we don't have +// to worry about freeing it). +oop SharedRuntime::allocate_inline_types_impl(JavaThread* current, methodHandle callee, bool allocate_receiver, TRAPS) { + assert(InlineTypePassFieldsAsArgs, "no reason to call this"); + ResourceMark rm; + + int nb_slots = 0; + InstanceKlass* holder = callee->method_holder(); + allocate_receiver &= !callee->is_static() && holder->is_inline_klass() && callee->is_scalarized_arg(0); + if (allocate_receiver) { + nb_slots++; + } + int arg_num = callee->is_static() ? 0 : 1; + for (SignatureStream ss(callee->signature()); !ss.at_return_type(); ss.next()) { + BasicType bt = ss.type(); + if (bt == T_OBJECT && callee->is_scalarized_arg(arg_num)) { + nb_slots++; + } + if (bt != T_VOID) { + arg_num++; + } + } + objArrayOop array_oop = oopFactory::new_objectArray(nb_slots, CHECK_NULL); + objArrayHandle array(THREAD, array_oop); + arg_num = callee->is_static() ? 0 : 1; + int i = 0; + if (allocate_receiver) { + InlineKlass* vk = InlineKlass::cast(holder); + oop res = vk->allocate_instance(CHECK_NULL); + array->obj_at_put(i++, res); + } + for (SignatureStream ss(callee->signature()); !ss.at_return_type(); ss.next()) { + BasicType bt = ss.type(); + if (bt == T_OBJECT && callee->is_scalarized_arg(arg_num)) { + InlineKlass* vk = ss.as_inline_klass(holder); + assert(vk != nullptr, "Unexpected klass"); + oop res = vk->allocate_instance(CHECK_NULL); + array->obj_at_put(i++, res); + } + if (bt != T_VOID) { + arg_num++; + } + } + return array(); +} + +JRT_ENTRY(void, SharedRuntime::allocate_inline_types(JavaThread* current, Method* callee_method, bool allocate_receiver)) + methodHandle callee(current, callee_method); + oop array = SharedRuntime::allocate_inline_types_impl(current, callee, allocate_receiver, CHECK); + current->set_vm_result_oop(array); + current->set_vm_result_metadata(callee()); // TODO: required to keep callee live? +JRT_END + +// We're returning from an interpreted method: load each field into a +// register following the calling convention +JRT_LEAF(void, SharedRuntime::load_inline_type_fields_in_regs(JavaThread* current, oopDesc* res)) +{ + assert(res->klass()->is_inline_klass(), "only inline types here"); + ResourceMark rm; + RegisterMap reg_map(current, + RegisterMap::UpdateMap::include, + RegisterMap::ProcessFrames::include, + RegisterMap::WalkContinuation::skip); + frame stubFrame = current->last_frame(); + frame callerFrame = stubFrame.sender(®_map); + assert(callerFrame.is_interpreted_frame(), "should be coming from interpreter"); + + InlineKlass* vk = InlineKlass::cast(res->klass()); + + const Array* sig_vk = vk->extended_sig(); + const Array* regs = vk->return_regs(); + + if (regs == nullptr) { + // The fields of the inline klass don't fit in registers, bail out + return; + } + + int j = 1; + for (int i = 0; i < sig_vk->length(); i++) { + BasicType bt = sig_vk->at(i)._bt; + if (bt == T_METADATA) { + continue; + } + if (bt == T_VOID) { + if (sig_vk->at(i-1)._bt == T_LONG || + sig_vk->at(i-1)._bt == T_DOUBLE) { + j++; + } + continue; + } + int off = sig_vk->at(i)._offset; + assert(off > 0, "offset in object should be positive"); + VMRegPair pair = regs->at(j); + address loc = reg_map.location(pair.first(), nullptr); + switch(bt) { + case T_BOOLEAN: + *(jboolean*)loc = res->bool_field(off); + break; + case T_CHAR: + *(jchar*)loc = res->char_field(off); + break; + case T_BYTE: + *(jbyte*)loc = res->byte_field(off); + break; + case T_SHORT: + *(jshort*)loc = res->short_field(off); + break; + case T_INT: { + *(jint*)loc = res->int_field(off); + break; + } + case T_LONG: +#ifdef _LP64 + *(intptr_t*)loc = res->long_field(off); +#else + Unimplemented(); +#endif + break; + case T_OBJECT: + case T_ARRAY: { + *(oop*)loc = res->obj_field(off); + break; + } + case T_FLOAT: + *(jfloat*)loc = res->float_field(off); + break; + case T_DOUBLE: + *(jdouble*)loc = res->double_field(off); + break; + default: + ShouldNotReachHere(); + } + j++; + } + assert(j == regs->length(), "missed a field?"); + +#ifdef ASSERT + VMRegPair pair = regs->at(0); + address loc = reg_map.location(pair.first(), nullptr); + assert(*(oopDesc**)loc == res, "overwritten object"); +#endif + + current->set_vm_result_oop(res); +} +JRT_END + +// We've returned to an interpreted method, the interpreter needs a +// reference to an inline type instance. Allocate it and initialize it +// from field's values in registers. +JRT_BLOCK_ENTRY(void, SharedRuntime::store_inline_type_fields_to_buf(JavaThread* current, intptr_t res)) +{ + ResourceMark rm; + RegisterMap reg_map(current, + RegisterMap::UpdateMap::include, + RegisterMap::ProcessFrames::include, + RegisterMap::WalkContinuation::skip); + frame stubFrame = current->last_frame(); + frame callerFrame = stubFrame.sender(®_map); + +#ifdef ASSERT + InlineKlass* verif_vk = InlineKlass::returned_inline_klass(reg_map); +#endif + + if (!is_set_nth_bit(res, 0)) { + // We're not returning with inline type fields in registers (the + // calling convention didn't allow it for this inline klass) + assert(!Metaspace::contains((void*)res), "should be oop or pointer in buffer area"); + current->set_vm_result_oop((oopDesc*)res); + assert(verif_vk == nullptr, "broken calling convention"); + return; + } + + clear_nth_bit(res, 0); + InlineKlass* vk = (InlineKlass*)res; + assert(verif_vk == vk, "broken calling convention"); + assert(Metaspace::contains((void*)res), "should be klass"); + + // Allocate handles for every oop field so they are safe in case of + // a safepoint when allocating + GrowableArray handles; + vk->save_oop_fields(reg_map, handles); + + // It's unsafe to safepoint until we are here + JRT_BLOCK; + { + JavaThread* THREAD = current; + oop vt = vk->realloc_result(reg_map, handles, CHECK); + current->set_vm_result_oop(vt); + } + JRT_BLOCK_END; +} +JRT_END diff --git a/src/hotspot/share/runtime/sharedRuntime.hpp b/src/hotspot/share/runtime/sharedRuntime.hpp index 288bc0adc52..8c197ad7609 100644 --- a/src/hotspot/share/runtime/sharedRuntime.hpp +++ b/src/hotspot/share/runtime/sharedRuntime.hpp @@ -25,6 +25,7 @@ #ifndef SHARE_RUNTIME_SHAREDRUNTIME_HPP #define SHARE_RUNTIME_SHAREDRUNTIME_HPP +#include "asm/codeBuffer.hpp" #include "classfile/compactHashtable.hpp" #include "code/codeBlob.hpp" #include "code/vmreg.hpp" @@ -32,12 +33,14 @@ #include "memory/allStatic.hpp" #include "memory/metaspaceClosure.hpp" #include "memory/resourceArea.hpp" +#include "runtime/signature.hpp" #include "runtime/stubInfo.hpp" #include "utilities/macros.hpp" class AdapterHandlerEntry; class AdapterFingerPrint; class vframeStream; +class SigEntry; // Runtime is the base class for various runtime interfaces // (InterpreterRuntime, CompilerRuntime, etc.). It provides @@ -375,9 +378,11 @@ class SharedRuntime: AllStatic { // static char* generate_class_cast_message(Klass* caster_klass, Klass* target_klass, Symbol* target_klass_name = nullptr); + static char* generate_identity_exception_message(JavaThread* thr, Klass* klass); + // Resolves a call site- may patch in the destination of the call into the // compiled code. - static methodHandle resolve_helper(bool is_virtual, bool is_optimized, TRAPS); + static methodHandle resolve_helper(bool is_virtual, bool is_optimized, bool& caller_is_c1, TRAPS); private: // deopt blob @@ -385,20 +390,20 @@ class SharedRuntime: AllStatic { static bool handle_ic_miss_helper_internal(Handle receiver, nmethod* caller_nm, const frame& caller_frame, methodHandle callee_method, Bytecodes::Code bc, CallInfo& call_info, - bool& needs_ic_stub_refill, TRAPS); + bool& needs_ic_stub_refill, bool& is_optimized, bool caller_is_c1, TRAPS); public: static DeoptimizationBlob* deopt_blob(void) { return _deopt_blob; } // Resets a call-site in compiled code so it will get resolved again. - static methodHandle reresolve_call_site(TRAPS); + static methodHandle reresolve_call_site(bool& is_static_call, bool& is_optimized, bool& caller_is_c1, TRAPS); // In the code prolog, if the klass comparison fails, the inline cache // misses and the call site is patched to megamorphic - static methodHandle handle_ic_miss_helper(TRAPS); + static methodHandle handle_ic_miss_helper(bool& is_optimized, bool& caller_is_c1, TRAPS); // Find the method that called us. - static methodHandle find_callee_method(TRAPS); + static methodHandle find_callee_method(bool is_optimized, bool& caller_is_c1, TRAPS); static void monitor_enter_helper(oopDesc* obj, BasicLock* lock, JavaThread* thread); @@ -429,6 +434,14 @@ class SharedRuntime: AllStatic { // 4-bytes higher. // return value is the maximum number of VMReg stack slots the convention will use. static int java_calling_convention(const BasicType* sig_bt, VMRegPair* regs, int total_args_passed); + static int java_calling_convention(const GrowableArray* sig, VMRegPair* regs) { + BasicType* sig_bt = NEW_RESOURCE_ARRAY(BasicType, sig->length()); + int total_args_passed = SigEntry::fill_sig_bt(sig, sig_bt); + return java_calling_convention(sig_bt, regs, total_args_passed); + } + static int java_return_convention(const BasicType* sig_bt, VMRegPair* regs, int total_args_passed); + static const uint java_return_convention_max_int; + static const uint java_return_convention_max_float; static void check_member_name_argument_is_last_argument(const methodHandle& method, const BasicType* sig_bt, @@ -475,17 +488,21 @@ class SharedRuntime: AllStatic { // pointer as needed. This means the i2c adapter code doesn't need any special // handshaking path with compiled code to keep the stack walking correct. - static void generate_i2c2i_adapters(MacroAssembler *_masm, - int total_args_passed, - int max_arg, - const BasicType *sig_bt, - const VMRegPair *regs, - AdapterHandlerEntry* handler); + static void generate_i2c2i_adapters(MacroAssembler* _masm, + int total_args_passed, + const GrowableArray* sig, + const VMRegPair* regs, + const GrowableArray* sig_cc, + const VMRegPair* regs_cc, + const GrowableArray* sig_cc_ro, + const VMRegPair* regs_cc_ro, + AdapterHandlerEntry* handler, + AdapterBlob*& new_adapter, + bool allocate_code_blob); static void gen_i2c_adapter(MacroAssembler *_masm, - int total_args_passed, int comp_args_on_stack, - const BasicType *sig_bt, + const GrowableArray* sig, const VMRegPair *regs); // OSR support @@ -558,11 +575,15 @@ class SharedRuntime: AllStatic { static void complete_monitor_unlocking_C(oopDesc* obj, BasicLock* lock, JavaThread* current); // Resolving of calls - static address get_resolved_entry (JavaThread* current, methodHandle callee_method); + static address get_resolved_entry (JavaThread* current, methodHandle callee_method, + bool is_static_call, bool is_optimized, bool caller_is_c1); static address resolve_static_call_C (JavaThread* current); static address resolve_virtual_call_C (JavaThread* current); static address resolve_opt_virtual_call_C(JavaThread* current); + static void load_inline_type_fields_in_regs(JavaThread* current, oopDesc* res); + static void store_inline_type_fields_to_buf(JavaThread* current, intptr_t res); + // arraycopy, the non-leaf version. (See StubRoutines for all the leaf calls.) static void slow_arraycopy_C(oopDesc* src, jint src_pos, oopDesc* dest, jint dest_pos, @@ -573,9 +594,12 @@ class SharedRuntime: AllStatic { static address handle_wrong_method(JavaThread* current); static address handle_wrong_method_abstract(JavaThread* current); static address handle_wrong_method_ic_miss(JavaThread* current); + static void allocate_inline_types(JavaThread* current, Method* callee, bool allocate_receiver); + static oop allocate_inline_types_impl(JavaThread* current, methodHandle callee, bool allocate_receiver, TRAPS); static address handle_unsafe_access(JavaThread* thread, address next_pc); + static BufferedInlineTypeBlob* generate_buffered_inline_type_adapter(const InlineKlass* vk); #ifndef PRODUCT // Collect and print inline cache miss statistics @@ -681,18 +705,24 @@ class AdapterHandlerEntry : public MetaspaceObj { friend class AdapterHandlerLibrary; public: - static const int ENTRIES_COUNT = 4; + static const int ENTRIES_COUNT = 7; private: AdapterFingerPrint* _fingerprint; address _i2c_entry; address _c2i_entry; + address _c2i_inline_entry; + address _c2i_inline_ro_entry; address _c2i_unverified_entry; + address _c2i_unverified_inline_entry; address _c2i_no_clinit_check_entry; bool _linked; static const char *_entry_names[]; + // Support for scalarized inline type calling convention + const GrowableArray* _sig_cc; + #ifdef ASSERT // Captures code and signature used to generate this adapter when // verifying adapter equivalence. @@ -704,9 +734,13 @@ class AdapterHandlerEntry : public MetaspaceObj { _fingerprint(fingerprint), _i2c_entry(nullptr), _c2i_entry(nullptr), + _c2i_inline_entry(nullptr), + _c2i_inline_ro_entry(nullptr), _c2i_unverified_entry(nullptr), + _c2i_unverified_inline_entry(nullptr), _c2i_no_clinit_check_entry(nullptr), - _linked(false) + _linked(false), + _sig_cc(nullptr) #ifdef ASSERT , _saved_code(nullptr), _saved_code_length(0) @@ -734,18 +768,27 @@ class AdapterHandlerEntry : public MetaspaceObj { handler->~AdapterHandlerEntry(); } - void set_entry_points(address i2c_entry, address c2i_entry, address c2i_unverified_entry, address c2i_no_clinit_check_entry, bool linked = true) { + void set_entry_points(address i2c_entry, address c2i_entry, address c2i_inline_entry, address c2i_inline_ro_entry, + address c2i_unverified_entry, address c2i_unverified_inline_entry, + address c2i_no_clinit_check_entry = nullptr, + bool linked = true) { _i2c_entry = i2c_entry; _c2i_entry = c2i_entry; + _c2i_inline_entry = c2i_inline_entry; + _c2i_inline_ro_entry = c2i_inline_ro_entry; _c2i_unverified_entry = c2i_unverified_entry; + _c2i_unverified_inline_entry = c2i_unverified_inline_entry; _c2i_no_clinit_check_entry = c2i_no_clinit_check_entry; _linked = linked; } - address get_i2c_entry() const { return _i2c_entry; } - address get_c2i_entry() const { return _c2i_entry; } - address get_c2i_unverified_entry() const { return _c2i_unverified_entry; } - address get_c2i_no_clinit_check_entry() const { return _c2i_no_clinit_check_entry; } + address get_i2c_entry() const { return _i2c_entry; } + address get_c2i_entry() const { return _c2i_entry; } + address get_c2i_inline_entry() const { return _c2i_inline_entry; } + address get_c2i_inline_ro_entry() const { return _c2i_inline_ro_entry; } + address get_c2i_unverified_entry() const { return _c2i_unverified_entry; } + address get_c2i_unverified_inline_entry() const { return _c2i_unverified_inline_entry; } + address get_c2i_no_clinit_check_entry() const { return _c2i_no_clinit_check_entry; } static const char* entry_name(int i) { assert(i >=0 && i < ENTRIES_COUNT, "entry id out of range"); @@ -756,6 +799,10 @@ class AdapterHandlerEntry : public MetaspaceObj { address base_address(); void relocate(address new_base); + // Support for scalarized inline type calling convention + void set_sig_cc(const GrowableArray* sig) { _sig_cc = sig; } + const GrowableArray* get_sig_cc() const { return _sig_cc; } + AdapterFingerPrint* fingerprint() const { return _fingerprint; } #ifdef ASSERT @@ -779,6 +826,8 @@ class AdapterHandlerEntry : public MetaspaceObj { class ArchivedAdapterTable; #endif // INCLUDE_CDS +class CompiledEntrySignature; + class AdapterHandlerLibrary: public AllStatic { friend class SharedRuntime; private: @@ -797,8 +846,8 @@ class AdapterHandlerLibrary: public AllStatic { static AdapterHandlerEntry* get_simple_adapter(const methodHandle& method); static AdapterBlob* lookup_aot_cache(AdapterHandlerEntry* handler); static AdapterHandlerEntry* create_adapter(AdapterBlob*& new_adapter, - int total_args_passed, - BasicType* sig_bt, + CompiledEntrySignature& ces, + bool allocate_code_blob, bool is_transient = false); static void lookup_simple_adapters() NOT_CDS_RETURN; #ifndef PRODUCT @@ -809,15 +858,15 @@ class AdapterHandlerLibrary: public AllStatic { static AdapterHandlerEntry* new_entry(AdapterFingerPrint* fingerprint); static void create_native_wrapper(const methodHandle& method); static AdapterHandlerEntry* get_adapter(const methodHandle& method); - static AdapterHandlerEntry* lookup(int total_args_passed, BasicType* sig_bt); + static AdapterHandlerEntry* lookup(const GrowableArray* sig, bool has_ro_adapter = false); static bool generate_adapter_code(AdapterBlob*& adapter_blob, AdapterHandlerEntry* handler, - int total_args_passed, - BasicType* sig_bt, + CompiledEntrySignature& ces, + bool allocate_code_blob, bool is_transient); #ifdef ASSERT - static void verify_adapter_sharing(int total_args_passed, BasicType* sig_bt, AdapterHandlerEntry* cached); + static void verify_adapter_sharing(CompiledEntrySignature& ces, AdapterHandlerEntry* cached_entry); #endif // ASSERT static void print_handler(const CodeBlob* b) { print_handler_on(tty, b); } @@ -835,4 +884,64 @@ class AdapterHandlerLibrary: public AllStatic { static void link_aot_adapters() NOT_CDS_RETURN; }; +// Utility class for computing the calling convention of the 3 types +// of compiled method entries: +// Method::_from_compiled_entry - sig_cc +// Method::_from_compiled_inline_ro_entry - sig_cc_ro +// Method::_from_compiled_inline_entry - sig +class CompiledEntrySignature : public StackObj { + Method* _method; + int _num_inline_args; + bool _has_inline_recv; + GrowableArray* _sig; + GrowableArray* _sig_cc; + GrowableArray* _sig_cc_ro; + VMRegPair* _regs; + VMRegPair* _regs_cc; + VMRegPair* _regs_cc_ro; + + int _args_on_stack; + int _args_on_stack_cc; + int _args_on_stack_cc_ro; + + bool _c1_needs_stack_repair; + bool _c2_needs_stack_repair; + + GrowableArray* _supers; + +public: + Method* method() const { return _method; } + + // Used by Method::_from_compiled_inline_entry + GrowableArray* sig() const { return _sig; } + + // Used by Method::_from_compiled_entry + GrowableArray* sig_cc() const { return _sig_cc; } + + // Used by Method::_from_compiled_inline_ro_entry + GrowableArray* sig_cc_ro() const { return _sig_cc_ro; } + + VMRegPair* regs() const { return _regs; } + VMRegPair* regs_cc() const { return _regs_cc; } + VMRegPair* regs_cc_ro() const { return _regs_cc_ro; } + + int args_on_stack() const { return _args_on_stack; } + int args_on_stack_cc() const { return _args_on_stack_cc; } + int args_on_stack_cc_ro() const { return _args_on_stack_cc_ro; } + + int num_inline_args() const { return _num_inline_args; } + bool has_inline_recv() const { return _has_inline_recv; } + + bool has_scalarized_args() const { return _sig != _sig_cc; } + bool c1_needs_stack_repair() const { return _c1_needs_stack_repair; } + bool c2_needs_stack_repair() const { return _c2_needs_stack_repair; } + CodeOffsets::Entries c1_inline_ro_entry_type() const; + + GrowableArray* get_supers(); + + CompiledEntrySignature(Method* method = nullptr); + void compute_calling_conventions(bool init = true); + void initialize_from_fingerprint(AdapterFingerPrint* fingerprint); +}; + #endif // SHARE_RUNTIME_SHAREDRUNTIME_HPP diff --git a/src/hotspot/share/runtime/signature.cpp b/src/hotspot/share/runtime/signature.cpp index 9ae5b3df415..2faea424a51 100644 --- a/src/hotspot/share/runtime/signature.cpp +++ b/src/hotspot/share/runtime/signature.cpp @@ -29,6 +29,7 @@ #include "memory/oopFactory.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" +#include "oops/inlineKlass.inline.hpp" #include "oops/instanceKlass.hpp" #include "oops/klass.inline.hpp" #include "oops/oop.inline.hpp" @@ -36,6 +37,7 @@ #include "oops/typeArrayKlass.hpp" #include "runtime/fieldDescriptor.inline.hpp" #include "runtime/handles.inline.hpp" +#include "runtime/interfaceSupport.inline.hpp" #include "runtime/safepointVerifiers.hpp" #include "runtime/sharedRuntime.hpp" #include "runtime/signature.hpp" @@ -47,7 +49,7 @@ // Signature = "(" {Parameter} ")" ReturnType. // Parameter = FieldType. // ReturnType = FieldType | "V". -// FieldType = "B" | "C" | "D" | "F" | "I" | "J" | "S" | "Z" | "L" ClassName ";" | "[" FieldType. +// FieldType = "B" | "C" | "D" | "F" | "I" | "J" | "S" | "Z" | "L" ClassName ";" | "Q" ValueClassName ";" | "[" FieldType. // ClassName = string. // The ClassName string can be any JVM-style UTF8 string except: @@ -499,6 +501,20 @@ Symbol* SignatureStream::find_symbol() { return name; } +InlineKlass* SignatureStream::as_inline_klass(InstanceKlass* holder) { + ThreadInVMfromUnknown tiv; + JavaThread* THREAD = JavaThread::current(); + HandleMark hm(THREAD); + Handle class_loader(THREAD, holder->class_loader()); + Klass* k = as_klass(class_loader, SignatureStream::CachedOrNull, THREAD); + assert(!HAS_PENDING_EXCEPTION, "Should never throw"); + if (k != nullptr && k->is_inline_klass()) { + return InlineKlass::cast(k); + } else { + return nullptr; + } +} + Klass* SignatureStream::as_klass(Handle class_loader, FailureMode failure_mode, TRAPS) { if (!is_reference()) { return nullptr; @@ -573,7 +589,6 @@ void ResolvingSignatureStream::cache_handles() { } #ifdef ASSERT - extern bool signature_constants_sane(); // called from basic_types_init() bool signature_constants_sane() { @@ -592,7 +607,7 @@ bool signature_constants_sane() { return true; } -bool SignatureVerifier::is_valid_method_signature(Symbol* sig) { +bool SignatureVerifier::is_valid_method_signature(const Symbol* sig) { const char* method_sig = (const char*)sig->bytes(); ssize_t len = sig->utf8_length(); ssize_t index = 0; @@ -615,7 +630,7 @@ bool SignatureVerifier::is_valid_method_signature(Symbol* sig) { return false; } -bool SignatureVerifier::is_valid_type_signature(Symbol* sig) { +bool SignatureVerifier::is_valid_type_signature(const Symbol* sig) { const char* type_sig = (const char*)sig->bytes(); ssize_t len = sig->utf8_length(); return (type_sig != nullptr && len >= 1 && @@ -662,3 +677,59 @@ ssize_t SignatureVerifier::is_valid_type(const char* type, ssize_t limit) { } #endif // ASSERT + +// Adds an argument to the signature +void SigEntry::add_entry(GrowableArray* sig, BasicType bt, Symbol* name, int offset) { + sig->append(SigEntry(bt, offset, name, false)); + if (bt == T_LONG || bt == T_DOUBLE) { + sig->append(SigEntry(T_VOID, offset, name, false)); // Longs and doubles take two stack slots + } +} +void SigEntry::add_null_marker(GrowableArray* sig, Symbol* name, int offset) { + sig->append(SigEntry(T_BOOLEAN, offset, name, true)); +} + +// Returns true if the argument at index 'i' is not an inline type delimiter +bool SigEntry::skip_value_delimiters(const GrowableArray* sig, int i) { + return (sig->at(i)._bt != T_METADATA && + (sig->at(i)._bt != T_VOID || sig->at(i-1)._bt == T_LONG || sig->at(i-1)._bt == T_DOUBLE)); +} + +// Fill basic type array from signature array +int SigEntry::fill_sig_bt(const GrowableArray* sig, BasicType* sig_bt) { + int count = 0; + for (int i = 0; i < sig->length(); i++) { + if (skip_value_delimiters(sig, i)) { + sig_bt[count++] = sig->at(i)._bt; + } + } + return count; +} + +// Create a temporary symbol from the signature array +TempNewSymbol SigEntry::create_symbol(const GrowableArray* sig) { + ResourceMark rm; + int length = sig->length(); + char* sig_str = NEW_RESOURCE_ARRAY(char, 2*length + 3); + int idx = 0; + sig_str[idx++] = '('; + for (int i = 0; i < length; i++) { + BasicType bt = sig->at(i)._bt; + if (bt == T_METADATA || bt == T_VOID) { + // Ignore + } else { + if (bt == T_ARRAY) { + bt = T_OBJECT; // We don't know the element type, treat as Object + } + sig_str[idx++] = type2char(bt); + if (bt == T_OBJECT) { + sig_str[idx++] = ';'; + } + } + } + sig_str[idx++] = ')'; + // Add a dummy return type. It won't be used but SignatureStream needs it. + sig_str[idx++] = 'V'; + sig_str[idx++] = '\0'; + return SymbolTable::new_symbol(sig_str); +} diff --git a/src/hotspot/share/runtime/signature.hpp b/src/hotspot/share/runtime/signature.hpp index eddc21d2444..fbf58a22190 100644 --- a/src/hotspot/share/runtime/signature.hpp +++ b/src/hotspot/share/runtime/signature.hpp @@ -26,6 +26,7 @@ #ifndef SHARE_RUNTIME_SIGNATURE_HPP #define SHARE_RUNTIME_SIGNATURE_HPP +#include "classfile/symbolTable.hpp" #include "memory/allocation.hpp" #include "oops/method.hpp" @@ -562,10 +563,43 @@ class SignatureStream : public StackObj { // free-standing lookups (bring your own CL/PD pair) enum FailureMode { ReturnNull, NCDFError, CachedOrNull }; + Klass* as_klass(Handle class_loader, FailureMode failure_mode, TRAPS); + InlineKlass* as_inline_klass(InstanceKlass* holder); oop as_java_mirror(Handle class_loader, FailureMode failure_mode, TRAPS); }; +class SigEntryFilter; +typedef GrowableArrayFilterIterator ExtendedSignature; + +// Used for adapter generation. One SigEntry is used per element of +// the signature of the method. Inline type arguments are treated +// specially. See comment for InlineKlass::collect_fields(). +class SigEntry { + public: + BasicType _bt; // Basic type of the argument + int _offset; // Offset of the field in its value class holder for scalarized arguments (-1 otherwise). Used for packing and unpacking. + Symbol* _name; // Symbol for printing + bool _null_marker; // Is it a null marker? For printing + + SigEntry() + : _bt(T_ILLEGAL), _offset(-1), _name(nullptr) {} + + SigEntry(BasicType bt, int offset, Symbol* name, bool null_marker) + : _bt(bt), _offset(offset), _name(name), _null_marker(null_marker) {} + + static void add_entry(GrowableArray* sig, BasicType bt, Symbol* name = nullptr, int offset = -1); + static void add_null_marker(GrowableArray* sig, Symbol* name, int offset); + static bool skip_value_delimiters(const GrowableArray* sig, int i); + static int fill_sig_bt(const GrowableArray* sig, BasicType* sig_bt); + static TempNewSymbol create_symbol(const GrowableArray* sig); +}; + +class SigEntryFilter { +public: + bool operator()(const SigEntry& entry) { return entry._bt != T_METADATA && entry._bt != T_VOID; } +}; + // Specialized SignatureStream: used for invoking SystemDictionary to either find // or resolve the underlying type when iterating over a // Java descriptor (or parts of it). @@ -628,11 +662,11 @@ void SignatureIterator::do_parameters_on(T* callback) { } } - #ifdef ASSERT +#ifdef ASSERT class SignatureVerifier : public StackObj { public: - static bool is_valid_method_signature(Symbol* sig); - static bool is_valid_type_signature(Symbol* sig); + static bool is_valid_method_signature(const Symbol* sig); + static bool is_valid_type_signature(const Symbol* sig); private: static ssize_t is_valid_type(const char*, ssize_t); }; diff --git a/src/hotspot/share/runtime/signature_cc.hpp b/src/hotspot/share/runtime/signature_cc.hpp new file mode 100644 index 00000000000..afe61d7d67e --- /dev/null +++ b/src/hotspot/share/runtime/signature_cc.hpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_RUNTIME_SIGNATURE_CC_HPP +#define SHARE_RUNTIME_SIGNATURE_CC_HPP + +#include "runtime/signature.hpp" + +// Stream that iterates over a scalarized signature +class ScalarizedInlineArgsStream : public StackObj { + const GrowableArray* _sig; + int _sig_idx; + const VMRegPair* _regs; + int _regs_count; + int _regs_idx; + int _depth; + int _step; + DEBUG_ONLY(bool _finished); + +public: + ScalarizedInlineArgsStream(const GrowableArray* sig, int sig_idx, VMRegPair* regs, int regs_count, int regs_idx, int step = 1) + : _sig(sig), _sig_idx(sig_idx), _regs(regs), _regs_count(regs_count), _regs_idx(regs_idx), _step(step) { + reset(sig_idx, regs_idx); + } + + bool next(VMReg& reg, BasicType& bt) { + assert(!_finished, "sanity"); + do { + _sig_idx += _step; + bt = _sig->at(_sig_idx)._bt; + if (bt == T_METADATA) { + _depth += _step; + } else if (bt == T_VOID && + _sig->at(_sig_idx-1)._bt != T_LONG && + _sig->at(_sig_idx-1)._bt != T_DOUBLE) { + _depth -= _step; + } else { + assert(_regs_idx >= 0 && _regs_idx < _regs_count, "out of bounds"); + const VMRegPair pair = _regs[_regs_idx]; + _regs_idx += _step; + reg = pair.first(); + if (!reg->is_valid()) { + assert(!pair.second()->is_valid(), "must be invalid"); + } else { + return true; + } + } + } while (_depth != 0); + + DEBUG_ONLY(_finished = true); + return false; + } + + void reset(int sig_idx, int regs_idx) { + _sig_idx = sig_idx; + _regs_idx = regs_idx; + assert(_sig->at(_sig_idx)._bt == (_step > 0) ? T_METADATA : T_VOID, "should be at inline type delimiter"); + _depth = 1; + DEBUG_ONLY(_finished = false); + } + + int sig_index() { return _sig_idx; } + int regs_index() { return _regs_idx; } +}; + +#endif // SHARE_RUNTIME_SIGNATURE_CC_HPP diff --git a/src/hotspot/share/runtime/stackChunkFrameStream.hpp b/src/hotspot/share/runtime/stackChunkFrameStream.hpp index 207203dbb35..3f72d9a1210 100644 --- a/src/hotspot/share/runtime/stackChunkFrameStream.hpp +++ b/src/hotspot/share/runtime/stackChunkFrameStream.hpp @@ -47,6 +47,7 @@ class StackChunkFrameStream : public StackObj { intptr_t* _unextended_sp; // used only when mixed CodeBlob* _cb; mutable const ImmutableOopMap* _oopmap; + bool _callee_augmented; #ifndef PRODUCT stackChunkOop _chunk; @@ -68,7 +69,7 @@ class StackChunkFrameStream : public StackObj { intptr_t* sp() const { return _sp; } inline address pc() const { return get_pc(); } inline intptr_t* fp() const; - inline intptr_t* unextended_sp() const { return frame_kind == ChunkFrames::Mixed ? _unextended_sp : _sp; } + inline intptr_t* unextended_sp() const { return _unextended_sp; } inline address orig_pc() const; inline bool is_interpreted() const; diff --git a/src/hotspot/share/runtime/stackChunkFrameStream.inline.hpp b/src/hotspot/share/runtime/stackChunkFrameStream.inline.hpp index 09c267b408b..18a02cf14df 100644 --- a/src/hotspot/share/runtime/stackChunkFrameStream.inline.hpp +++ b/src/hotspot/share/runtime/stackChunkFrameStream.inline.hpp @@ -55,14 +55,16 @@ StackChunkFrameStream::StackChunkFrameStream(stackChunkOop chunk) DE _end = chunk->bottom_address(); _sp = chunk->start_address() + chunk->sp(); assert(_sp <= chunk->end_address() + frame::metadata_words, ""); + _callee_augmented = false; get_cb(); if (frame_kind == ChunkFrames::Mixed) { _unextended_sp = (!is_done() && is_interpreted()) ? unextended_sp_for_interpreter_frame() : _sp; assert(_unextended_sp >= _sp - frame::metadata_words, ""); + } else { + _unextended_sp = _sp; } - DEBUG_ONLY(else _unextended_sp = nullptr;) if (is_stub()) { get_oopmap(pc(), 0); @@ -86,10 +88,12 @@ StackChunkFrameStream::StackChunkFrameStream(stackChunkOop chunk, co if (frame_kind == ChunkFrames::Mixed) { _unextended_sp = f.unextended_sp(); assert(_unextended_sp >= _sp - frame::metadata_words, ""); + } else { + _unextended_sp = _sp; } - DEBUG_ONLY(else _unextended_sp = nullptr;) assert(_sp >= chunk->start_address(), ""); assert(_sp <= chunk->end_address() + frame::metadata_words, ""); + _callee_augmented = false; if (f.cb() != nullptr) { _oopmap = nullptr; @@ -216,6 +220,7 @@ template inline void StackChunkFrameStream::next(RegisterMapT* map, bool stop) { update_reg_map(map); bool is_runtime_stub = is_stub(); + _callee_augmented = false; if (frame_kind == ChunkFrames::Mixed) { if (is_interpreted()) { next_for_interpreter_frame(); @@ -224,11 +229,24 @@ inline void StackChunkFrameStream::next(RegisterMapT* map, bool stop if (_sp >= _end - frame::metadata_words) { _sp = _end; } - _unextended_sp = is_interpreted() ? unextended_sp_for_interpreter_frame() : _sp; + if (is_interpreted()) { + _unextended_sp = unextended_sp_for_interpreter_frame(); + } else if (cb()->is_nmethod() && cb()->as_nmethod()->needs_stack_repair()) { + _unextended_sp = frame::repair_sender_sp(cb()->as_nmethod(), _unextended_sp, (intptr_t**)(_sp - frame::sender_sp_offset)); + _callee_augmented = _unextended_sp != _sp; + } else { + _unextended_sp = _sp; + } } assert(_unextended_sp >= _sp - frame::metadata_words, ""); } else { - _sp += cb()->frame_size(); + _sp = _unextended_sp + cb()->frame_size(); + if (cb()->is_nmethod() && cb()->as_nmethod()->needs_stack_repair()) { + _unextended_sp = frame::repair_sender_sp(cb()->as_nmethod(), _unextended_sp, (intptr_t**)(_sp - frame::sender_sp_offset)); + _callee_augmented = _unextended_sp != _sp; + } else { + _unextended_sp = _sp; + } } assert(!is_interpreted() || _unextended_sp == unextended_sp_for_interpreter_frame(), ""); diff --git a/src/hotspot/share/runtime/stackValue.cpp b/src/hotspot/share/runtime/stackValue.cpp index 5e01efc1622..87394d1599d 100644 --- a/src/hotspot/share/runtime/stackValue.cpp +++ b/src/hotspot/share/runtime/stackValue.cpp @@ -40,13 +40,6 @@ class RegisterMap; class SmallRegisterMap; -template StackValue* StackValue::create_stack_value(const frame* fr, const RegisterMap* reg_map, ScopeValue* sv); -template StackValue* StackValue::create_stack_value(const frame* fr, const SmallRegisterMap* reg_map, ScopeValue* sv); - -template -StackValue* StackValue::create_stack_value(const frame* fr, const RegisterMapT* reg_map, ScopeValue* sv) { - return create_stack_value(sv, stack_value_address(fr, reg_map, sv), reg_map); -} static oop oop_from_oop_location(stackChunkOop chunk, void* addr) { if (addr == nullptr) { @@ -147,8 +140,13 @@ StackValue* StackValue::create_stack_value_from_narrowOop_location(stackChunkOop return new StackValue(h); } + +template StackValue* StackValue::create_stack_value(const frame* fr, const RegisterMap* reg_map, ScopeValue* sv); +template StackValue* StackValue::create_stack_value(const frame* fr, const SmallRegisterMap* reg_map, ScopeValue* sv); + template -StackValue* StackValue::create_stack_value(ScopeValue* sv, address value_addr, const RegisterMapT* reg_map) { +StackValue* StackValue::create_stack_value(const frame* fr, const RegisterMapT* reg_map, ScopeValue* sv) { + address value_addr = stack_value_address(fr, reg_map, sv); stackChunkOop chunk = reg_map->stack_chunk()(); if (sv->is_location()) { // Stack or register value @@ -249,7 +247,16 @@ StackValue* StackValue::create_stack_value(ScopeValue* sv, address value_addr, c } else if (sv->is_object()) { // Scalar replaced object in compiled frame ObjectValue* ov = (ObjectValue *)sv; Handle hdl = ov->value(); - return new StackValue(hdl, hdl.is_null() && ov->is_scalar_replaced() ? 1 : 0); + bool scalar_replaced = hdl.is_null() && ov->is_scalar_replaced(); + if (ov->has_properties()) { + Klass* k = java_lang_Class::as_Klass(ov->klass()->as_ConstantOopReadValue()->value()()); + if (!k->is_array_klass()) { + // Don't treat inline type as scalar replaced if it is null + jint null_marker = StackValue::create_stack_value(fr, reg_map, ov->properties())->get_jint(); + scalar_replaced &= (null_marker != 0); + } + } + return new StackValue(hdl, scalar_replaced ? 1 : 0); } else if (sv->is_marker()) { // Should never need to directly construct a marker. ShouldNotReachHere(); diff --git a/src/hotspot/share/runtime/stubDeclarations.hpp b/src/hotspot/share/runtime/stubDeclarations.hpp index c79caa12a6c..a554df7a0a3 100644 --- a/src/hotspot/share/runtime/stubDeclarations.hpp +++ b/src/hotspot/share/runtime/stubDeclarations.hpp @@ -135,13 +135,21 @@ do_blob(fast_new_instance_init_check) \ do_blob(new_type_array) \ do_blob(new_object_array) \ + do_blob(new_null_free_array) \ do_blob(new_multi_array) \ + do_blob(load_flat_array) \ + do_blob(store_flat_array) \ + do_blob(substitutability_check) \ + do_blob(buffer_inline_args) \ + do_blob(buffer_inline_args_no_receiver) \ do_blob(handle_exception_nofpu) /* optimized version that does not preserve fpu registers */ \ do_blob(handle_exception) \ do_blob(handle_exception_from_callee) \ do_blob(throw_array_store_exception) \ do_blob(throw_class_cast_exception) \ do_blob(throw_incompatible_class_change_error) \ + do_blob(throw_illegal_monitor_state_exception) \ + do_blob(throw_identity_exception) \ do_blob(slow_subtype_check) \ do_blob(is_instance_of) \ do_blob(monitorenter) \ @@ -246,6 +254,8 @@ do_stub(rethrow, 2, true, true) \ do_stub(slow_arraycopy, 0, false, false) \ do_stub(register_finalizer, 0, false, false) \ + do_stub(load_unknown_inline, 0, true, false) \ + do_stub(store_unknown_inline, 0, true, false) \ #else #define C2_STUBS_DO(do_blob, do_stub, do_jvmti_stub) diff --git a/src/hotspot/share/runtime/stubRoutines.cpp b/src/hotspot/share/runtime/stubRoutines.cpp index 5246613738e..fff46f76885 100644 --- a/src/hotspot/share/runtime/stubRoutines.cpp +++ b/src/hotspot/share/runtime/stubRoutines.cpp @@ -100,6 +100,11 @@ BlobId StubRoutines::stub_to_blob(StubId id) { #endif // ASSERT +// TODO: update with 8343767 +address StubRoutines::_load_inline_type_fields_in_regs = nullptr; +address StubRoutines::_store_inline_type_fields_to_buf = nullptr; + + // Initialization extern void StubGenerator_generate(CodeBuffer* code, BlobId blob_id); // only interface to generators diff --git a/src/hotspot/share/runtime/stubRoutines.hpp b/src/hotspot/share/runtime/stubRoutines.hpp index 97e3e46b870..a83d702ed0b 100644 --- a/src/hotspot/share/runtime/stubRoutines.hpp +++ b/src/hotspot/share/runtime/stubRoutines.hpp @@ -225,6 +225,9 @@ class StubRoutines: AllStatic { #define DECLARE_BLOB_INIT_METHOD(blob_name) \ static void initialize_ ## blob_name ## _stubs(); + static address _load_inline_type_fields_in_regs; + static address _store_inline_type_fields_to_buf; + STUBGEN_BLOBS_DO(DECLARE_BLOB_INIT_METHOD) #undef DECLARE_BLOB_INIT_METHOD @@ -362,6 +365,9 @@ class StubRoutines: AllStatic { static void arrayof_oop_copy (HeapWord* src, HeapWord* dest, size_t count); static void arrayof_oop_copy_uninit(HeapWord* src, HeapWord* dest, size_t count); + static address load_inline_type_fields_in_regs() { return _load_inline_type_fields_in_regs; } + static address store_inline_type_fields_to_buf() { return _store_inline_type_fields_to_buf; } + }; #endif // SHARE_RUNTIME_STUBROUTINES_HPP diff --git a/src/hotspot/share/runtime/synchronizer.cpp b/src/hotspot/share/runtime/synchronizer.cpp index f3e3072978b..477c58ed74f 100644 --- a/src/hotspot/share/runtime/synchronizer.cpp +++ b/src/hotspot/share/runtime/synchronizer.cpp @@ -314,6 +314,22 @@ jlong ObjectSynchronizer::_last_async_deflation_time_ns = 0; static uintx _no_progress_cnt = 0; static bool _no_progress_skip_increment = false; +// These checks are required for wait, notify and exit to avoid inflating the monitor to +// find out this inline type object cannot be locked. +#define CHECK_THROW_NOSYNC_IMSE(obj) \ + if (EnableValhalla && (obj)->mark().is_inline_type()) { \ + JavaThread* THREAD = current; \ + ResourceMark rm(THREAD); \ + THROW_MSG(vmSymbols::java_lang_IllegalMonitorStateException(), obj->klass()->external_name()); \ + } + +#define CHECK_THROW_NOSYNC_IMSE_0(obj) \ + if (EnableValhalla && (obj)->mark().is_inline_type()) { \ + JavaThread* THREAD = current; \ + ResourceMark rm(THREAD); \ + THROW_MSG_0(vmSymbols::java_lang_IllegalMonitorStateException(), obj->klass()->external_name()); \ + } + // =====================> Quick functions // The quick_* forms are special fast-path variants used to improve @@ -340,6 +356,7 @@ bool ObjectSynchronizer::quick_notify(oopDesc* obj, JavaThread* current, bool al assert(current->thread_state() == _thread_in_Java, "invariant"); NoSafepointVerifier nsv; if (obj == nullptr) return false; // slow-path for invalid obj + assert(!EnableValhalla || !obj->klass()->is_inline_klass(), "monitor op on inline type"); const markWord mark = obj->mark(); if (LockingMode == LM_LIGHTWEIGHT) { @@ -397,6 +414,7 @@ static bool useHeavyMonitors() { bool ObjectSynchronizer::quick_enter_legacy(oop obj, BasicLock* lock, JavaThread* current) { assert(current->thread_state() == _thread_in_Java, "invariant"); + assert(!EnableValhalla || !obj->klass()->is_inline_klass(), "monitor op on inline type"); if (useHeavyMonitors()) { return false; // Slow path @@ -512,6 +530,7 @@ void ObjectSynchronizer::enter_for(Handle obj, BasicLock* lock, JavaThread* lock // the locking_thread with respect to the current thread. Currently only used when // deoptimizing and re-locking locks. See Deoptimization::relock_objects assert(locking_thread == Thread::current() || locking_thread->is_obj_deopt_suspend(), "must be"); + assert(!EnableValhalla || !obj->klass()->is_inline_klass(), "JITed code should never have locked an instance of a value class"); if (LockingMode == LM_LIGHTWEIGHT) { return LightweightSynchronizer::enter_for(obj, lock, locking_thread); @@ -534,6 +553,7 @@ void ObjectSynchronizer::enter_for(Handle obj, BasicLock* lock, JavaThread* lock } void ObjectSynchronizer::enter_legacy(Handle obj, BasicLock* lock, JavaThread* current) { + assert(!EnableValhalla || !obj->klass()->is_inline_klass(), "This method should never be called on an instance of an inline class"); if (!enter_fast_impl(obj, lock, current)) { // Inflated ObjectMonitor::enter is required @@ -553,6 +573,7 @@ void ObjectSynchronizer::enter_legacy(Handle obj, BasicLock* lock, JavaThread* c // of this algorithm. Make sure to update that code if the following function is // changed. The implementation is extremely sensitive to race condition. Be careful. bool ObjectSynchronizer::enter_fast_impl(Handle obj, BasicLock* lock, JavaThread* locking_thread) { + guarantee(!EnableValhalla || !obj->klass()->is_inline_klass(), "Attempt to inflate inline type"); assert(LockingMode != LM_LIGHTWEIGHT, "Use LightweightSynchronizer"); if (obj->klass()->is_value_based()) { @@ -600,6 +621,9 @@ void ObjectSynchronizer::exit_legacy(oop object, BasicLock* lock, JavaThread* cu if (!useHeavyMonitors()) { markWord mark = object->mark(); + if (EnableValhalla && mark.is_inline_type()) { + return; + } if (LockingMode == LM_LEGACY) { markWord dhw = lock->displaced_header(); if (dhw.value() == 0) { @@ -656,6 +680,7 @@ void ObjectSynchronizer::exit_legacy(oop object, BasicLock* lock, JavaThread* cu // JNI locks on java objects // NOTE: must use heavy weight monitor to handle jni monitor enter void ObjectSynchronizer::jni_enter(Handle obj, JavaThread* current) { + JavaThread* THREAD = current; // Top native frames in the stack will not be seen if we attempt // preemption, since we start walking from the last Java anchor. NoPreemptMark npm(current); @@ -664,6 +689,16 @@ void ObjectSynchronizer::jni_enter(Handle obj, JavaThread* current) { handle_sync_on_value_based_class(obj, current); } + if (EnableValhalla && obj->klass()->is_inline_klass()) { + ResourceMark rm(THREAD); + const char* desc = "Cannot synchronize on an instance of value class "; + const char* className = obj->klass()->external_name(); + size_t msglen = strlen(desc) + strlen(className) + 1; + char* message = NEW_RESOURCE_ARRAY(char, msglen); + assert(message != nullptr, "NEW_RESOURCE_ARRAY should have called vm_exit_out_of_memory and not return nullptr"); + THROW_MSG(vmSymbols::java_lang_IdentityException(), className); + } + // the current locking is from JNI instead of Java code current->set_current_pending_monitor_is_from_java(false); // An async deflation can race after the inflate() call and before @@ -691,6 +726,7 @@ void ObjectSynchronizer::jni_enter(Handle obj, JavaThread* current) { // NOTE: must use heavy weight monitor to handle jni monitor exit void ObjectSynchronizer::jni_exit(oop obj, TRAPS) { JavaThread* current = THREAD; + CHECK_THROW_NOSYNC_IMSE(obj); ObjectMonitor* monitor; if (LockingMode == LM_LIGHTWEIGHT) { @@ -735,6 +771,7 @@ ObjectLocker::~ObjectLocker() { int ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) { JavaThread* current = THREAD; + CHECK_THROW_NOSYNC_IMSE_0(obj); if (millis < 0) { THROW_MSG_0(vmSymbols::java_lang_IllegalArgumentException(), "timeout value is negative"); } @@ -777,6 +814,7 @@ void ObjectSynchronizer::waitUninterruptibly(Handle obj, jlong millis, TRAPS) { void ObjectSynchronizer::notify(Handle obj, TRAPS) { JavaThread* current = THREAD; + CHECK_THROW_NOSYNC_IMSE(obj); markWord mark = obj->mark(); if (LockingMode == LM_LIGHTWEIGHT) { @@ -805,6 +843,7 @@ void ObjectSynchronizer::notify(Handle obj, TRAPS) { // NOTE: see comment of notify() void ObjectSynchronizer::notifyall(Handle obj, TRAPS) { JavaThread* current = THREAD; + CHECK_THROW_NOSYNC_IMSE(obj); markWord mark = obj->mark(); if (LockingMode == LM_LIGHTWEIGHT) { @@ -986,6 +1025,10 @@ static intptr_t install_hash_code(Thread* current, oop obj) { } intptr_t ObjectSynchronizer::FastHashCode(Thread* current, oop obj) { + if (EnableValhalla && obj->klass()->is_inline_klass()) { + // VM should be calling bootstrap method + ShouldNotReachHere(); + } if (UseObjectMonitorTable) { // Since the monitor isn't in the object header, the hash can simply be // installed in the object header. @@ -1112,6 +1155,9 @@ intptr_t ObjectSynchronizer::FastHashCode(Thread* current, oop obj) { bool ObjectSynchronizer::current_thread_holds_lock(JavaThread* current, Handle h_obj) { + if (EnableValhalla && h_obj->mark().is_inline_type()) { + return false; + } assert(current == JavaThread::current(), "Can only be called on current thread"); oop obj = h_obj(); @@ -1455,6 +1501,9 @@ ObjectMonitor* ObjectSynchronizer::inflate_for(JavaThread* thread, oop obj, cons } ObjectMonitor* ObjectSynchronizer::inflate_impl(JavaThread* locking_thread, oop object, const InflateCause cause) { + if (EnableValhalla) { + guarantee(!object->klass()->is_inline_klass(), "Attempt to inflate inline type"); + } // The JavaThread* locking_thread requires that the locking_thread == Thread::current() or // is suspended throughout the call by some other mechanism. // The thread might be nullptr when called from a non JavaThread. (As may still be diff --git a/src/hotspot/share/runtime/threads.cpp b/src/hotspot/share/runtime/threads.cpp index 5ba3499efbe..7d8f54f3c45 100644 --- a/src/hotspot/share/runtime/threads.cpp +++ b/src/hotspot/share/runtime/threads.cpp @@ -408,6 +408,7 @@ void Threads::initialize_java_lang_classes(JavaThread* main_thread, TRAPS) { initialize_class(vmSymbols::java_lang_ArrayIndexOutOfBoundsException(), CHECK); initialize_class(vmSymbols::java_lang_StackOverflowError(), CHECK); initialize_class(vmSymbols::java_lang_IllegalMonitorStateException(), CHECK); + initialize_class(vmSymbols::java_lang_IdentityException(), CHECK); initialize_class(vmSymbols::java_lang_IllegalArgumentException(), CHECK); initialize_class(vmSymbols::java_lang_InternalError(), CHECK); } diff --git a/src/hotspot/share/runtime/vmOperation.hpp b/src/hotspot/share/runtime/vmOperation.hpp index 89a806bb75d..607dd977773 100644 --- a/src/hotspot/share/runtime/vmOperation.hpp +++ b/src/hotspot/share/runtime/vmOperation.hpp @@ -113,6 +113,7 @@ template(GTestExecuteAtSafepoint) \ template(GTestStopSafepoint) \ template(JFROldObject) \ + template(ClassPrintLayout) \ template(JvmtiPostObjectFree) \ template(RendezvousGCThreads) \ template(JFRInitializeCPUTimeSampler) \ diff --git a/src/hotspot/share/runtime/vmOperations.cpp b/src/hotspot/share/runtime/vmOperations.cpp index c5539fd5e50..533fe30aebe 100644 --- a/src/hotspot/share/runtime/vmOperations.cpp +++ b/src/hotspot/share/runtime/vmOperations.cpp @@ -642,4 +642,8 @@ void VM_PrintCompileQueue::doit() { void VM_PrintClassHierarchy::doit() { KlassHierarchy::print_class_hierarchy(_out, _print_interfaces, _print_subclasses, _classname); } + +void VM_PrintClassLayout::doit() { + PrintClassLayout::print_class_layout(_out, _class_name); +} #endif diff --git a/src/hotspot/share/runtime/vmOperations.hpp b/src/hotspot/share/runtime/vmOperations.hpp index 2ed9626652c..d517c6c8ad9 100644 --- a/src/hotspot/share/runtime/vmOperations.hpp +++ b/src/hotspot/share/runtime/vmOperations.hpp @@ -274,6 +274,16 @@ class VM_PrintCompileQueue: public VM_Operation { void doit(); }; +class VM_PrintClassLayout: public VM_Operation { + private: + outputStream* _out; + char* _class_name; + public: + VM_PrintClassLayout(outputStream* st, char* class_name): _out(st), _class_name(class_name) {} + VMOp_Type type() const { return VMOp_PrintClassHierarchy; } + void doit(); +}; + #if INCLUDE_SERVICES class VM_PrintClassHierarchy: public VM_Operation { private: diff --git a/src/hotspot/share/runtime/vmStructs.cpp b/src/hotspot/share/runtime/vmStructs.cpp index 1fba55cde99..e85352faf16 100644 --- a/src/hotspot/share/runtime/vmStructs.cpp +++ b/src/hotspot/share/runtime/vmStructs.cpp @@ -59,6 +59,8 @@ #include "oops/constMethod.hpp" #include "oops/cpCache.hpp" #include "oops/fieldInfo.hpp" +#include "oops/flatArrayKlass.hpp" +#include "oops/inlineKlass.hpp" #include "oops/instanceClassLoaderKlass.hpp" #include "oops/instanceKlass.hpp" #include "oops/instanceMirrorKlass.hpp" @@ -937,9 +939,12 @@ declare_type(Metadata, MetaspaceObj) \ declare_type(Klass, Metadata) \ declare_type(ArrayKlass, Klass) \ - declare_type(ObjArrayKlass, ArrayKlass) \ declare_type(TypeArrayKlass, ArrayKlass) \ + declare_type(ObjArrayKlass, ArrayKlass) \ + declare_type(FlatArrayKlass, ArrayKlass) \ + declare_type(RefArrayKlass, ArrayKlass) \ declare_type(InstanceKlass, Klass) \ + declare_type(InlineKlass, InstanceKlass) \ declare_type(InstanceClassLoaderKlass, InstanceKlass) \ declare_type(InstanceMirrorKlass, InstanceKlass) \ declare_type(InstanceRefKlass, InstanceKlass) \ @@ -1405,7 +1410,7 @@ declare_constant(Klass::_lh_header_size_mask) \ declare_constant(Klass::_lh_array_tag_shift) \ declare_constant(Klass::_lh_array_tag_type_value) \ - declare_constant(Klass::_lh_array_tag_obj_value) \ + declare_constant(Klass::_lh_array_tag_ref_value) \ \ declare_constant(Method::nonvirtual_vtable_index) \ declare_constant(Method::extra_stack_entries_for_jsr292) \ diff --git a/src/hotspot/share/services/diagnosticCommand.cpp b/src/hotspot/share/services/diagnosticCommand.cpp index 5bef650891d..86dece6c90c 100644 --- a/src/hotspot/share/services/diagnosticCommand.cpp +++ b/src/hotspot/share/services/diagnosticCommand.cpp @@ -119,6 +119,7 @@ void DCmd::register_dcmds(){ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); #if INCLUDE_JVMTI // Both JVMTI and SERVICES have to be enabled to have this dcmd DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); @@ -949,7 +950,31 @@ void ClassHierarchyDCmd::execute(DCmdSource source, TRAPS) { _print_subclasses.value(), _classname.value()); VMThread::execute(&printClassHierarchyOp); } -#endif + +PrintClassLayoutDCmd::PrintClassLayoutDCmd(outputStream* output, bool heap) : + DCmdWithParser(output, heap), + _classname("classname", "Name of class whose layout should be printed. ", + "STRING", true) { + _dcmdparser.add_dcmd_argument(&_classname); +} + +void PrintClassLayoutDCmd::execute(DCmdSource source, TRAPS) { + VM_PrintClassLayout printClassLayoutOp(output(), _classname.value()); + VMThread::execute(&printClassLayoutOp); +} + +int PrintClassLayoutDCmd::num_arguments() { + ResourceMark rm; + PrintClassLayoutDCmd* dcmd = new PrintClassLayoutDCmd(nullptr, false); + if (dcmd != nullptr) { + DCmdMark mark(dcmd); + return dcmd->_dcmdparser.num_arguments(); + } else { + return 0; + } +} + +#endif // INCLUDE_SERVICES ClassesDCmd::ClassesDCmd(outputStream* output, bool heap) : DCmdWithParser(output, heap), diff --git a/src/hotspot/share/services/diagnosticCommand.hpp b/src/hotspot/share/services/diagnosticCommand.hpp index 001d89a5aef..ba390d3af26 100644 --- a/src/hotspot/share/services/diagnosticCommand.hpp +++ b/src/hotspot/share/services/diagnosticCommand.hpp @@ -325,6 +325,26 @@ class ClassHierarchyDCmd : public DCmdWithParser { virtual void execute(DCmdSource source, TRAPS); }; +class PrintClassLayoutDCmd : public DCmdWithParser { +protected: + DCmdArgument _classname; // lass name whose layout should be printed. +public: + PrintClassLayoutDCmd(outputStream* output, bool heap); + static const char* name() { + return "VM.class_print_layout"; + } + static const char* description() { + return "Print the layout of an instance of a class, including flat fields. " + "The name of each class is followed by the ClassLoaderData* of its ClassLoader, " + "or \"null\" if loaded by the bootstrap class loader."; + } + static const char* impact() { + return "Medium: Depends on number of loaded classes."; + } + static int num_arguments(); + virtual void execute(DCmdSource source, TRAPS); +}; + #if INCLUDE_CDS class DumpSharedArchiveDCmd: public DCmdWithParser { protected: diff --git a/src/hotspot/share/services/heapDumper.cpp b/src/hotspot/share/services/heapDumper.cpp index cf62972ca16..0bc1787c5fb 100644 --- a/src/hotspot/share/services/heapDumper.cpp +++ b/src/hotspot/share/services/heapDumper.cpp @@ -38,6 +38,8 @@ #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "oops/fieldStreams.inline.hpp" +#include "oops/flatArrayKlass.hpp" +#include "oops/flatArrayOop.inline.hpp" #include "oops/klass.inline.hpp" #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" @@ -45,6 +47,7 @@ #include "oops/typeArrayOop.inline.hpp" #include "runtime/arguments.hpp" #include "runtime/continuationWrapper.inline.hpp" +#include "runtime/fieldDescriptor.inline.hpp" #include "runtime/frame.inline.hpp" #include "runtime/handles.inline.hpp" #include "runtime/javaCalls.hpp" @@ -316,6 +319,28 @@ * 0x00000002: cpu sampling on/off * u2 stack trace depth * + * HPROF_FLAT_ARRAYS list of flat arrays + * + * [flat array sub-records]* + * + * HPROF_FLAT_ARRAY flat array + * + * id array object ID (dumped as HPROF_GC_PRIM_ARRAY_DUMP) + * id element class ID (dumped by HPROF_GC_CLASS_DUMP) + * + * HPROF_INLINED_FIELDS decribes inlined fields + * + * [class with inlined fields sub-records]* + * + * HPROF_CLASS_WITH_INLINED_FIELDS + * + * id class ID (dumped as HPROF_GC_CLASS_DUMP) + * + * u2 number of instance inlined fields (not including super) + * [u2, inlined field index, + * u2, synthetic field count, + * id, original field name, + * id]* inlined field class ID (dumped by HPROF_GC_CLASS_DUMP) * * When the header is "JAVA PROFILE 1.0.2" a heap dump can optionally * be generated as a sequence of heap dump segments. This sequence is @@ -353,6 +378,13 @@ enum hprofTag : u1 { HPROF_HEAP_DUMP_SEGMENT = 0x1C, HPROF_HEAP_DUMP_END = 0x2C, + // inlined object support + HPROF_FLAT_ARRAYS = 0x12, + HPROF_INLINED_FIELDS = 0x13, + // inlined object subrecords + HPROF_FLAT_ARRAY = 0x01, + HPROF_CLASS_WITH_INLINED_FIELDS = 0x01, + // field types HPROF_ARRAY_OBJECT = 0x01, HPROF_NORMAL_OBJECT = 0x02, @@ -387,6 +419,71 @@ enum { INITIAL_CLASS_COUNT = 200 }; + +class AbstractDumpWriter; + +class InlinedObjects { + + struct ClassInlinedFields { + const Klass *klass; + uintx base_index; // base index of the inlined field names (1st field has index base_index+1). + ClassInlinedFields(const Klass *klass = nullptr, uintx base_index = 0) : klass(klass), base_index(base_index) {} + + // For GrowableArray::find_sorted(). + static int compare(const ClassInlinedFields& a, const ClassInlinedFields& b) { + return a.klass - b.klass; + } + // For GrowableArray::sort(). + static int compare(ClassInlinedFields* a, ClassInlinedFields* b) { + return compare(*a, *b); + } + }; + + uintx _min_string_id; + uintx _max_string_id; + + GrowableArray *_inlined_field_map; + + // counters for classes with inlined fields and for the fields + int _classes_count; + int _inlined_fields_count; + + static InlinedObjects *_instance; + + static void inlined_field_names_callback(InlinedObjects* _this, const Klass *klass, uintx base_index, int count); + + GrowableArray *_flat_arrays; + +public: + InlinedObjects() + : _min_string_id(0), _max_string_id(0), + _inlined_field_map(nullptr), + _classes_count(0), _inlined_fields_count(0), + _flat_arrays(nullptr) { + } + + static InlinedObjects* get_instance() { + return _instance; + } + + void init(); + void release(); + + void dump_inlined_field_names(AbstractDumpWriter *writer); + + uintx get_base_index_for(Klass* k); + uintx get_next_string_id(uintx id); + + void dump_classed_with_inlined_fields(AbstractDumpWriter* writer); + + void add_flat_array(oop array); + void dump_flat_arrays(AbstractDumpWriter* writer); + +}; + +InlinedObjects *InlinedObjects::_instance = nullptr; + + // Supports I/O operations for a dump // Base class for dump and parallel dump class AbstractDumpWriter : public CHeapObj { @@ -743,7 +840,7 @@ class DumperSupport : AllStatic { // Returns the size of the data to write. static u4 sig2size(Symbol* sig); - // returns the size of the instance of the given class + // calculates the total size of the all fields of the given class. static u4 instance_size(InstanceKlass* ik, DumperClassCacheTableEntry* class_cache_entry = nullptr); // dump a jfloat @@ -756,12 +853,18 @@ class DumperSupport : AllStatic { static u4 get_static_fields_size(InstanceKlass* ik, u2& field_count); // dumps static fields of the given class static void dump_static_fields(AbstractDumpWriter* writer, Klass* k); - // dump the raw values of the instance fields of the given object - static void dump_instance_fields(AbstractDumpWriter* writer, oop o, DumperClassCacheTableEntry* class_cache_entry); + // dump the raw values of the instance fields of the given identity or inlined object; + // for identity objects offset is 0 and 'klass' is o->klass(), + // for inlined objects offset is the offset in the holder object, 'klass' is inlined object class + static void dump_instance_fields(AbstractDumpWriter* writer, oop o, int offset, DumperClassCacheTable* class_cache, DumperClassCacheTableEntry* class_cache_entry); + // dump the raw values of the instance fields of the given inlined object; + // dump_instance_fields wrapper for inlined objects + static void dump_inlined_object_fields(AbstractDumpWriter* writer, oop o, int offset, DumperClassCacheTable* class_cache, DumperClassCacheTableEntry* class_cache_entry); + // get the count of the instance fields for a given class static u2 get_instance_fields_count(InstanceKlass* ik); // dumps the definition of the instance fields for a given class - static void dump_instance_field_descriptors(AbstractDumpWriter* writer, Klass* k); + static void dump_instance_field_descriptors(AbstractDumpWriter* writer, InstanceKlass* k, uintx *inlined_fields_index = nullptr); // creates HPROF_GC_INSTANCE_DUMP record for the given object static void dump_instance(AbstractDumpWriter* writer, oop o, DumperClassCacheTable* class_cache); // creates HPROF_GC_CLASS_DUMP record for the given instance class @@ -771,6 +874,8 @@ class DumperSupport : AllStatic { // creates HPROF_GC_OBJ_ARRAY_DUMP record for the given object array static void dump_object_array(AbstractDumpWriter* writer, objArrayOop array); + // creates HPROF_GC_PRIM_ARRAY_DUMP record for the given flat array + static void dump_flat_array(AbstractDumpWriter* writer, flatArrayOop array, DumperClassCacheTable* class_cache); // creates HPROF_GC_PRIM_ARRAY_DUMP record for the given type array static void dump_prim_array(AbstractDumpWriter* writer, typeArrayOop array); // create HPROF_FRAME record for the given method and bci @@ -778,6 +883,9 @@ class DumperSupport : AllStatic { // check if we need to truncate an array static int calculate_array_max_length(AbstractDumpWriter* writer, arrayOop array, short header_size); + // extended version to dump flat arrays as primitive arrays; + // type_size specifies size of the inlined objects. + static int calculate_array_max_length(AbstractDumpWriter* writer, arrayOop array, int type_size, short header_size); // fixes up the current dump record and writes HPROF_HEAP_DUMP_END record static void end_of_dump(AbstractDumpWriter* writer); @@ -793,6 +901,16 @@ class DumperSupport : AllStatic { } } + // helper methods for inlined fields. + static bool is_inlined_field(const fieldDescriptor& fld) { + return fld.is_flat(); + } + static InlineKlass* get_inlined_field_klass(const fieldDescriptor& fld) { + assert(is_inlined_field(fld), "must be inlined field"); + InstanceKlass* holder_klass = fld.field_holder(); + return InlineKlass::cast(holder_klass->get_inline_type_field_klass(fld.index())); + } + static void report_dormant_archived_object(oop o, oop ref_obj) { if (log_is_enabled(Trace, aot, heap)) { ResourceMark rm; @@ -817,6 +935,7 @@ class DumperClassCacheTableEntry : public CHeapObj { private: GrowableArray _sigs_start; GrowableArray _offsets; + GrowableArray _inline_klasses; u4 _instance_size; int _entries; @@ -825,6 +944,9 @@ class DumperClassCacheTableEntry : public CHeapObj { int field_count() { return _entries; } char sig_start(int field_idx) { return _sigs_start.at(field_idx); } + void push_sig_start_inlined() { _sigs_start.push('Q'); } + bool is_inlined(int field_idx){ return _sigs_start.at(field_idx) == 'Q'; } + InlineKlass* inline_klass(int field_idx) { assert(is_inlined(field_idx), "Not inlined"); return _inline_klasses.at(field_idx); } int offset(int field_idx) { return _offsets.at(field_idx); } u4 instance_size() { return _instance_size; } }; @@ -873,11 +995,19 @@ class DumperClassCacheTable { entry = new DumperClassCacheTableEntry(); for (HierarchicalFieldStream fld(ik); !fld.done(); fld.next()) { if (!fld.access_flags().is_static()) { - Symbol* sig = fld.signature(); - entry->_sigs_start.push(sig->char_at(0)); + InlineKlass* inlineKlass = nullptr; + if (DumperSupport::is_inlined_field(fld.field_descriptor())) { + inlineKlass = DumperSupport::get_inlined_field_klass(fld.field_descriptor()); + entry->push_sig_start_inlined(); + entry->_instance_size += DumperSupport::instance_size(inlineKlass); + } else { + Symbol* sig = fld.signature(); + entry->_sigs_start.push(sig->char_at(0)); + entry->_instance_size += DumperSupport::sig2size(sig); + } + entry->_inline_klasses.push(inlineKlass); entry->_offsets.push(fld.offset()); entry->_entries++; - entry->_instance_size += DumperSupport::sig2size(sig); } } @@ -987,6 +1117,7 @@ void DumperSupport::dump_double(AbstractDumpWriter* writer, jdouble d) { } } + // dumps the raw value of the given field void DumperSupport::dump_field_value(AbstractDumpWriter* writer, char type, oop obj, int offset) { switch (type) { @@ -1045,7 +1176,7 @@ void DumperSupport::dump_field_value(AbstractDumpWriter* writer, char type, oop } } -// returns the size of the instance of the given class +// calculates the total size of the all fields of the given class. u4 DumperSupport::instance_size(InstanceKlass* ik, DumperClassCacheTableEntry* class_cache_entry) { if (class_cache_entry != nullptr) { return class_cache_entry->instance_size(); @@ -1053,7 +1184,11 @@ u4 DumperSupport::instance_size(InstanceKlass* ik, DumperClassCacheTableEntry* c u4 size = 0; for (HierarchicalFieldStream fld(ik); !fld.done(); fld.next()) { if (!fld.access_flags().is_static()) { - size += sig2size(fld.signature()); + if (is_inlined_field(fld.field_descriptor())) { + size += instance_size(get_inlined_field_klass(fld.field_descriptor())); + } else { + size += sig2size(fld.signature()); + } } } return size; @@ -1066,6 +1201,8 @@ u4 DumperSupport::get_static_fields_size(InstanceKlass* ik, u2& field_count) { for (JavaFieldStream fldc(ik); !fldc.done(); fldc.next()) { if (fldc.access_flags().is_static()) { + assert(!is_inlined_field(fldc.field_descriptor()), "static fields cannot be inlined"); + field_count++; size += sig2size(fldc.signature()); } @@ -1108,6 +1245,8 @@ void DumperSupport::dump_static_fields(AbstractDumpWriter* writer, Klass* k) { // dump the field descriptors and raw values for (JavaFieldStream fld(ik); !fld.done(); fld.next()) { if (fld.access_flags().is_static()) { + assert(!is_inlined_field(fld.field_descriptor()), "static fields cannot be inlined"); + Symbol* sig = fld.signature(); writer->write_symbolID(fld.name()); // name @@ -1144,36 +1283,86 @@ void DumperSupport::dump_static_fields(AbstractDumpWriter* writer, Klass* k) { } } -// dump the raw values of the instance fields of the given object -void DumperSupport::dump_instance_fields(AbstractDumpWriter* writer, oop o, DumperClassCacheTableEntry* class_cache_entry) { +// dump the raw values of the instance fields of the given identity or inlined object; +// for identity objects offset is 0 and 'klass' is o->klass(), +// for inlined objects offset is the offset in the holder object, 'klass' is inlined object class. +void DumperSupport::dump_instance_fields(AbstractDumpWriter* writer, oop o, int offset, DumperClassCacheTable* class_cache, DumperClassCacheTableEntry* class_cache_entry) { assert(class_cache_entry != nullptr, "Pre-condition: must be provided"); for (int idx = 0; idx < class_cache_entry->field_count(); idx++) { - dump_field_value(writer, class_cache_entry->sig_start(idx), o, class_cache_entry->offset(idx)); + if (class_cache_entry->is_inlined(idx)) { + InlineKlass* field_klass = class_cache_entry->inline_klass(idx); + int fields_offset = offset + (class_cache_entry->offset(idx) - field_klass->payload_offset()); + DumperClassCacheTableEntry* inline_class_cache_entry = class_cache->lookup_or_create(field_klass); + dump_inlined_object_fields(writer, o, fields_offset, class_cache, inline_class_cache_entry); + } else { + dump_field_value(writer, class_cache_entry->sig_start(idx), o, class_cache_entry->offset(idx)); + } } } -// dumps the definition of the instance fields for a given class +void DumperSupport::dump_inlined_object_fields(AbstractDumpWriter* writer, oop o, int offset, DumperClassCacheTable* class_cache, DumperClassCacheTableEntry* class_cache_entry) { + // the object is inlined, so all its fields are stored without headers. + dump_instance_fields(writer, o, offset, class_cache, class_cache_entry); +} + +// gets the count of the instance fields for a given class u2 DumperSupport::get_instance_fields_count(InstanceKlass* ik) { u2 field_count = 0; for (JavaFieldStream fldc(ik); !fldc.done(); fldc.next()) { - if (!fldc.access_flags().is_static()) field_count++; + if (!fldc.access_flags().is_static()) { + if (is_inlined_field(fldc.field_descriptor())) { + // add "synthetic" fields for inlined fields. + field_count += get_instance_fields_count(get_inlined_field_klass(fldc.field_descriptor())); + } else { + field_count++; + } + } } return field_count; } // dumps the definition of the instance fields for a given class -void DumperSupport::dump_instance_field_descriptors(AbstractDumpWriter* writer, Klass* k) { - InstanceKlass* ik = InstanceKlass::cast(k); +// inlined_fields_id is not-nullptr for inlined fields (to get synthetic field name IDs +// by using InlinedObjects::get_next_string_id()). +void DumperSupport::dump_instance_field_descriptors(AbstractDumpWriter* writer, InstanceKlass* ik, uintx* inlined_fields_id) { + // inlined_fields_id != nullptr means ik is a class of inlined field. + // Inlined field id pointer for this class; lazyly initialized + // if the class has inlined field(s) and the caller didn't provide inlined_fields_id. + uintx *this_klass_inlined_fields_id = inlined_fields_id; + uintx inlined_id = 0; // dump the field descriptors for (JavaFieldStream fld(ik); !fld.done(); fld.next()) { if (!fld.access_flags().is_static()) { - Symbol* sig = fld.signature(); + if (is_inlined_field(fld.field_descriptor())) { + // dump "synthetic" fields for inlined fields. + if (this_klass_inlined_fields_id == nullptr) { + inlined_id = InlinedObjects::get_instance()->get_base_index_for(ik); + this_klass_inlined_fields_id = &inlined_id; + } + dump_instance_field_descriptors(writer, get_inlined_field_klass(fld.field_descriptor()), this_klass_inlined_fields_id); + } else { + Symbol* sig = fld.signature(); + Symbol* name = nullptr; + // Use inlined_fields_id provided by caller. + if (inlined_fields_id != nullptr) { + uintx name_id = InlinedObjects::get_instance()->get_next_string_id(*inlined_fields_id); + + // name_id == 0 is returned on error. use original field signature. + if (name_id != 0) { + *inlined_fields_id = name_id; + name = reinterpret_cast(name_id); + } + } + if (name == nullptr) { + name = fld.name(); + } - writer->write_symbolID(fld.name()); // name - writer->write_u1(sig2tag(sig)); // type + writer->write_symbolID(name); // name + writer->write_u1(sig2tag(sig)); // type + } } } } @@ -1198,7 +1387,7 @@ void DumperSupport::dump_instance(AbstractDumpWriter* writer, oop o, DumperClass writer->write_u4(is); // field values - dump_instance_fields(writer, o, cache_entry); + dump_instance_fields(writer, o, 0, class_cache, cache_entry); writer->end_sub_record(); } @@ -1241,7 +1430,7 @@ void DumperSupport::dump_instance_class(AbstractDumpWriter* writer, InstanceKlas writer->write_objectID(oop(nullptr)); // instance size - writer->write_u4(DumperSupport::instance_size(ik)); + writer->write_u4(HeapWordSize * ik->size_helper()); // size of constant pool - ignored by HAT 1.1 writer->write_u2(0); @@ -1295,19 +1484,9 @@ void DumperSupport::dump_array_class(AbstractDumpWriter* writer, Klass* k) { // Hprof uses an u4 as record length field, // which means we need to truncate arrays that are too long. -int DumperSupport::calculate_array_max_length(AbstractDumpWriter* writer, arrayOop array, short header_size) { - BasicType type = ArrayKlass::cast(array->klass())->element_type(); - assert(type >= T_BOOLEAN && type <= T_OBJECT, "invalid array element type"); - +int DumperSupport::calculate_array_max_length(AbstractDumpWriter* writer, arrayOop array, int type_size, short header_size) { int length = array->length(); - int type_size; - if (type == T_OBJECT) { - type_size = sizeof(address); - } else { - type_size = type2aelembytes(type); - } - size_t length_in_bytes = (size_t)length * type_size; uint max_bytes = max_juint - header_size; @@ -1315,12 +1494,29 @@ int DumperSupport::calculate_array_max_length(AbstractDumpWriter* writer, arrayO length = max_bytes / type_size; length_in_bytes = (size_t)length * type_size; + BasicType type = ArrayKlass::cast(array->klass())->element_type(); warning("cannot dump array of type %s[] with length %d; truncating to length %d", type2name_tab[type], array->length(), length); } return length; } +int DumperSupport::calculate_array_max_length(AbstractDumpWriter* writer, arrayOop array, short header_size) { + BasicType type = ArrayKlass::cast(array->klass())->element_type(); + assert((type >= T_BOOLEAN && type <= T_OBJECT) || type == T_FLAT_ELEMENT, "invalid array element type"); + int type_size; + if (type == T_OBJECT) { + type_size = sizeof(address); + } else if (type == T_FLAT_ELEMENT) { + // TODO: FIXME + fatal("Not supported yet"); // FIXME: JDK-8325678 + } else { + type_size = type2aelembytes(type); + } + + return calculate_array_max_length(writer, array, type_size, header_size); +} + // creates HPROF_GC_OBJ_ARRAY_DUMP record for the given object array void DumperSupport::dump_object_array(AbstractDumpWriter* writer, objArrayOop array) { // sizeof(u1) + 2 * sizeof(u4) + sizeof(objectID) + sizeof(classID) @@ -1346,6 +1542,47 @@ void DumperSupport::dump_object_array(AbstractDumpWriter* writer, objArrayOop ar writer->end_sub_record(); } +// creates HPROF_GC_PRIM_ARRAY_DUMP record for the given flat array +void DumperSupport::dump_flat_array(AbstractDumpWriter* writer, flatArrayOop array, DumperClassCacheTable* class_cache) { + FlatArrayKlass* array_klass = FlatArrayKlass::cast(array->klass()); + InlineKlass* element_klass = array_klass->element_klass(); + int element_size = instance_size(element_klass); + /* id array object ID + * u4 stack trace serial number + * u4 number of elements + * u1 element type + */ + short header_size = 1 + sizeof(address) + 2 * 4 + 1; + + // TODO: use T_SHORT/T_INT/T_LONG if needed to avoid truncation + BasicType type = T_BYTE; + int type_size = type2aelembytes(type); + int length = calculate_array_max_length(writer, array, element_size, header_size); + u4 length_in_bytes = (u4)(length * element_size); + u4 size = header_size + length_in_bytes; + + writer->start_sub_record(HPROF_GC_PRIM_ARRAY_DUMP, size); + writer->write_objectID(array); + writer->write_u4(STACK_TRACE_ID); + // TODO: round up array length for T_SHORT/T_INT/T_LONG + writer->write_u4(length * element_size); + writer->write_u1(type2tag(type)); + + for (int index = 0; index < length; index++) { + // need offset in the holder to read inlined object. calculate it from flatArrayOop::value_at_addr() + int offset = (int)((address)array->value_at_addr(index, array_klass->layout_helper()) + - cast_from_oop
(array)); + DumperClassCacheTableEntry* class_cache_entry = class_cache->lookup_or_create(element_klass); + dump_inlined_object_fields(writer, array, offset, class_cache, class_cache_entry); + } + + // TODO: write padding bytes for T_SHORT/T_INT/T_LONG + + InlinedObjects::get_instance()->add_flat_array(array); + + writer->end_sub_record(); +} + #define WRITE_ARRAY(Array, Type, Size, Length) \ for (int i = 0; i < Length; i++) { writer->write_##Size((Size)Array->Type##_at(i)); } @@ -1467,6 +1704,270 @@ void DumperSupport::dump_stack_frame(AbstractDumpWriter* writer, } +class InlinedFieldNameDumper : public LockedClassesDo { +public: + typedef void (*Callback)(InlinedObjects *owner, const Klass *klass, uintx base_index, int count); + +private: + AbstractDumpWriter* _writer; + InlinedObjects *_owner; + Callback _callback; + uintx _index; + + void dump_inlined_field_names(GrowableArray* super_names, Symbol* field_name, InlineKlass* klass) { + super_names->push(field_name); + for (HierarchicalFieldStream fld(klass); !fld.done(); fld.next()) { + if (!fld.access_flags().is_static()) { + if (DumperSupport::is_inlined_field(fld.field_descriptor())) { + dump_inlined_field_names(super_names, fld.name(), DumperSupport::get_inlined_field_klass(fld.field_descriptor())); + } else { + // get next string ID. + uintx next_index = _owner->get_next_string_id(_index); + if (next_index == 0) { + // something went wrong (overflow?) + // stop generation; the rest of inlined objects will have original field names. + return; + } + _index = next_index; + + // Calculate length. + int len = fld.name()->utf8_length(); + for (GrowableArrayIterator it = super_names->begin(); it != super_names->end(); ++it) { + len += (*it)->utf8_length() + 1; // +1 for ".". + } + + DumperSupport::write_header(_writer, HPROF_UTF8, oopSize + len); + _writer->write_symbolID(reinterpret_cast(_index)); + // Write the string value. + // 1) super_names. + for (GrowableArrayIterator it = super_names->begin(); it != super_names->end(); ++it) { + _writer->write_raw((*it)->bytes(), (*it)->utf8_length()); + _writer->write_u1('.'); + } + // 2) field name. + _writer->write_raw(fld.name()->bytes(), fld.name()->utf8_length()); + } + } + } + super_names->pop(); + } + + void dump_inlined_field_names(Symbol* field_name, InlineKlass* field_klass) { + GrowableArray super_names(4, mtServiceability); + dump_inlined_field_names(&super_names, field_name, field_klass); + } + +public: + InlinedFieldNameDumper(AbstractDumpWriter* writer, InlinedObjects* owner, Callback callback) + : _writer(writer), _owner(owner), _callback(callback), _index(0) { + } + + void do_klass(Klass* k) { + if (!k->is_instance_klass()) { + return; + } + InstanceKlass* ik = InstanceKlass::cast(k); + // if (ik->has_inline_type_fields()) { + // return; + // } + + uintx base_index = _index; + int count = 0; + + for (HierarchicalFieldStream fld(ik); !fld.done(); fld.next()) { + if (!fld.access_flags().is_static()) { + if (DumperSupport::is_inlined_field(fld.field_descriptor())) { + dump_inlined_field_names(fld.name(), DumperSupport::get_inlined_field_klass(fld.field_descriptor())); + count++; + } + } + } + + if (count != 0) { + _callback(_owner, k, base_index, count); + } + } +}; + +class InlinedFieldsDumper : public LockedClassesDo { +private: + AbstractDumpWriter* _writer; + +public: + InlinedFieldsDumper(AbstractDumpWriter* writer) : _writer(writer) {} + + void do_klass(Klass* k) { + if (!k->is_instance_klass()) { + return; + } + InstanceKlass* ik = InstanceKlass::cast(k); + // if (ik->has_inline_type_fields()) { + // return; + // } + + // We can be at a point where java mirror does not exist yet. + // So we need to check that the class is at least loaded, to avoid crash from a null mirror. + if (!ik->is_loaded()) { + return; + } + + u2 inlined_count = 0; + for (HierarchicalFieldStream fld(ik); !fld.done(); fld.next()) { + if (!fld.access_flags().is_static()) { + if (DumperSupport::is_inlined_field(fld.field_descriptor())) { + inlined_count++; + } + } + } + if (inlined_count != 0) { + _writer->write_u1(HPROF_CLASS_WITH_INLINED_FIELDS); + + // class ID + _writer->write_classID(ik); + // number of inlined fields + _writer->write_u2(inlined_count); + u2 index = 0; + for (HierarchicalFieldStream fld(ik); !fld.done(); fld.next()) { + if (!fld.access_flags().is_static()) { + if (DumperSupport::is_inlined_field(fld.field_descriptor())) { + // inlined field index + _writer->write_u2(index); + // synthetic field count + u2 field_count = DumperSupport::get_instance_fields_count(DumperSupport::get_inlined_field_klass(fld.field_descriptor())); + _writer->write_u2(field_count); + // original field name + _writer->write_symbolID(fld.name()); + // inlined field class ID + _writer->write_classID(DumperSupport::get_inlined_field_klass(fld.field_descriptor())); + + index += field_count; + } else { + index++; + } + } + } + } + } +}; + + +void InlinedObjects::init() { + _instance = this; + + struct Closure : public SymbolClosure { + uintx _min_id = max_uintx; + uintx _max_id = 0; + Closure() : _min_id(max_uintx), _max_id(0) {} + + void do_symbol(Symbol** p) { + uintx val = reinterpret_cast(*p); + if (val < _min_id) { + _min_id = val; + } + if (val > _max_id) { + _max_id = val; + } + } + } closure; + + SymbolTable::symbols_do(&closure); + + _min_string_id = closure._min_id; + _max_string_id = closure._max_id; +} + +void InlinedObjects::release() { + _instance = nullptr; + + if (_inlined_field_map != nullptr) { + delete _inlined_field_map; + _inlined_field_map = nullptr; + } + if (_flat_arrays != nullptr) { + delete _flat_arrays; + _flat_arrays = nullptr; + } +} + +void InlinedObjects::inlined_field_names_callback(InlinedObjects* _this, const Klass* klass, uintx base_index, int count) { + if (_this->_inlined_field_map == nullptr) { + _this->_inlined_field_map = new (mtServiceability) GrowableArray(100, mtServiceability); + } + _this->_inlined_field_map->append(ClassInlinedFields(klass, base_index)); + + // counters for dumping classes with inlined fields + _this->_classes_count++; + _this->_inlined_fields_count += count; +} + +void InlinedObjects::dump_inlined_field_names(AbstractDumpWriter* writer) { + InlinedFieldNameDumper nameDumper(writer, this, inlined_field_names_callback); + ClassLoaderDataGraph::classes_do(&nameDumper); + + if (_inlined_field_map != nullptr) { + // prepare the map for get_base_index_for(). + _inlined_field_map->sort(ClassInlinedFields::compare); + } +} + +uintx InlinedObjects::get_base_index_for(Klass* k) { + if (_inlined_field_map != nullptr) { + bool found = false; + int idx = _inlined_field_map->find_sorted(ClassInlinedFields(k, 0), found); + if (found) { + return _inlined_field_map->at(idx).base_index; + } + } + + // return max_uintx, so get_next_string_id returns 0. + return max_uintx; +} + +uintx InlinedObjects::get_next_string_id(uintx id) { + if (++id == _min_string_id) { + return _max_string_id + 1; + } + return id; +} + +void InlinedObjects::dump_classed_with_inlined_fields(AbstractDumpWriter* writer) { + if (_classes_count != 0) { + // Record for each class contains tag(u1), class ID and count(u2) + // for each inlined field index(u2), synthetic fields count(u2), original field name and class ID + int size = _classes_count * (1 + sizeof(address) + 2) + + _inlined_fields_count * (2 + 2 + sizeof(address) + sizeof(address)); + DumperSupport::write_header(writer, HPROF_INLINED_FIELDS, (u4)size); + + InlinedFieldsDumper dumper(writer); + ClassLoaderDataGraph::classes_do(&dumper); + } +} + +void InlinedObjects::add_flat_array(oop array) { + if (_flat_arrays == nullptr) { + _flat_arrays = new (mtServiceability) GrowableArray(100, mtServiceability); + } + _flat_arrays->append(array); +} + +void InlinedObjects::dump_flat_arrays(AbstractDumpWriter* writer) { + if (_flat_arrays != nullptr) { + // For each flat array the record contains tag (u1), object ID and class ID. + int size = _flat_arrays->length() * (1 + sizeof(address) + sizeof(address)); + + DumperSupport::write_header(writer, HPROF_FLAT_ARRAYS, (u4)size); + for (GrowableArrayIterator it = _flat_arrays->begin(); it != _flat_arrays->end(); ++it) { + flatArrayOop array = flatArrayOop(*it); + FlatArrayKlass* array_klass = FlatArrayKlass::cast(array->klass()); + InlineKlass* element_klass = array_klass->element_klass(); + writer->write_u1(HPROF_FLAT_ARRAY); + writer->write_objectID(array); + writer->write_classID(element_klass); + } + } +} + + // Support class used to generate HPROF_UTF8 records from the entries in the // SymbolTable. @@ -1999,6 +2500,8 @@ void HeapObjectDumper::do_object(oop o) { } else if (o->is_objArray()) { // create a HPROF_GC_OBJ_ARRAY_DUMP record for each object array DumperSupport::dump_object_array(writer(), objArrayOop(o)); + } else if (o->is_flatArray()) { + DumperSupport::dump_flat_array(writer(), flatArrayOop(o), &_class_cache); } else if (o->is_typeArray()) { // create a HPROF_GC_PRIM_ARRAY_DUMP record for each type array DumperSupport::dump_prim_array(writer(), typeArrayOop(o)); @@ -2078,6 +2581,7 @@ class DumperController : public CHeapObj { class DumpMerger : public StackObj { private: DumpWriter* _writer; + InlinedObjects* _inlined_objects; const char* _path; bool _has_error; int _dump_seq; @@ -2088,8 +2592,9 @@ class DumpMerger : public StackObj { void set_error(const char* msg); public: - DumpMerger(const char* path, DumpWriter* writer, int dump_seq) : + DumpMerger(const char* path, DumpWriter* writer, InlinedObjects* inlined_objects, int dump_seq) : _writer(writer), + _inlined_objects(inlined_objects), _path(path), _has_error(_writer->has_error()), _dump_seq(dump_seq) {} @@ -2121,7 +2626,9 @@ void DumpMerger::merge_done() { // Writes the HPROF_HEAP_DUMP_END record. if (!_has_error) { DumperSupport::end_of_dump(_writer); + _inlined_objects->dump_flat_arrays(_writer); _writer->flush(); + _inlined_objects->release(); } _dump_seq = 0; //reset } @@ -2241,6 +2748,10 @@ class VM_HeapDumper : public VM_GC_Operation, public WorkerTask, public Unmounte volatile int _frame_serial_num; volatile int _dump_seq; + + // Inlined object support. + InlinedObjects _inlined_objects; + // parallel heap dump support uint _num_dumper_threads; DumperController* _dumper_controller; @@ -2321,6 +2832,8 @@ class VM_HeapDumper : public VM_GC_Operation, public WorkerTask, public Unmounte bool is_parallel_dump() { return _num_dumper_threads > 1; } void prepare_parallel_dump(WorkerThreads* workers); + InlinedObjects* inlined_objects() { return &_inlined_objects; } + VMOp_Type type() const { return VMOp_HeapDumper; } virtual bool doit_prologue(); void doit(); @@ -2457,6 +2970,13 @@ void VM_HeapDumper::work(uint worker_id) { SymbolTableDumper sym_dumper(writer()); SymbolTable::symbols_do(&sym_dumper); + // HPROF_UTF8 records for inlined field names. + inlined_objects()->init(); + inlined_objects()->dump_inlined_field_names(writer()); + + // HPROF_INLINED_FIELDS + inlined_objects()->dump_classed_with_inlined_fields(writer()); + // write HPROF_LOAD_CLASS records { LoadedClassDumper loaded_class_dumper(writer(), _klass_map); @@ -2659,7 +3179,7 @@ int HeapDumper::dump(const char* path, outputStream* out, int compression, bool // Phase 2: Merge multiple heap files into one complete heap dump file. // This is done by DumpMerger, which is performed outside safepoint - DumpMerger merger(path, &writer, dumper.dump_seq()); + DumpMerger merger(path, &writer, dumper.inlined_objects(), dumper.dump_seq()); // Perform heapdump file merge operation in the current thread prevents us // from occupying the VM Thread, which in turn affects the occurrence of // GC and other VM operations. diff --git a/src/hotspot/share/utilities/accessFlags.cpp b/src/hotspot/share/utilities/accessFlags.cpp index 4423e3619ea..60fb54c1cc2 100644 --- a/src/hotspot/share/utilities/accessFlags.cpp +++ b/src/hotspot/share/utilities/accessFlags.cpp @@ -41,6 +41,8 @@ void AccessFlags::print_on(outputStream* st) const { if (is_interface ()) st->print("interface " ); if (is_abstract ()) st->print("abstract " ); if (is_synthetic ()) st->print("synthetic " ); + if (is_identity_class()) st->print("identity " ); + if (!is_identity_class()) st->print("value " ); } #endif // !PRODUCT || INCLUDE_JVMTI diff --git a/src/hotspot/share/utilities/accessFlags.hpp b/src/hotspot/share/utilities/accessFlags.hpp index a752c09cb42..ee99b29db0a 100644 --- a/src/hotspot/share/utilities/accessFlags.hpp +++ b/src/hotspot/share/utilities/accessFlags.hpp @@ -50,13 +50,15 @@ class AccessFlags { bool is_protected () const { return (_flags & JVM_ACC_PROTECTED ) != 0; } bool is_static () const { return (_flags & JVM_ACC_STATIC ) != 0; } bool is_final () const { return (_flags & JVM_ACC_FINAL ) != 0; } + bool is_strict () const { return (_flags & JVM_ACC_STRICT ) != 0; } bool is_synchronized() const { return (_flags & JVM_ACC_SYNCHRONIZED) != 0; } - bool is_super () const { return (_flags & JVM_ACC_SUPER ) != 0; } bool is_volatile () const { return (_flags & JVM_ACC_VOLATILE ) != 0; } bool is_transient () const { return (_flags & JVM_ACC_TRANSIENT ) != 0; } bool is_native () const { return (_flags & JVM_ACC_NATIVE ) != 0; } bool is_interface () const { return (_flags & JVM_ACC_INTERFACE ) != 0; } bool is_abstract () const { return (_flags & JVM_ACC_ABSTRACT ) != 0; } + bool has_vararg () const { return (_flags & JVM_ACC_VARARGS ) != 0; } + bool is_identity_class () const { return (_flags & JVM_ACC_IDENTITY ) != 0; } // Attribute flags bool is_synthetic () const { return (_flags & JVM_ACC_SYNTHETIC ) != 0; } diff --git a/src/hotspot/share/utilities/constantTag.cpp b/src/hotspot/share/utilities/constantTag.cpp index 74f1b4c20d0..04c7f4bb79c 100644 --- a/src/hotspot/share/utilities/constantTag.cpp +++ b/src/hotspot/share/utilities/constantTag.cpp @@ -34,7 +34,7 @@ void constantTag::print_on(outputStream* st) const { #endif // PRODUCT BasicType constantTag::basic_type() const { - switch (_tag) { + switch (value()) { case JVM_CONSTANT_Integer : return T_INT; case JVM_CONSTANT_Float : @@ -68,7 +68,7 @@ BasicType constantTag::basic_type() const { jbyte constantTag::non_error_value() const { - switch (_tag) { + switch (value()) { case JVM_CONSTANT_UnresolvedClassInError: return JVM_CONSTANT_UnresolvedClass; case JVM_CONSTANT_MethodHandleInError: @@ -78,13 +78,13 @@ jbyte constantTag::non_error_value() const { case JVM_CONSTANT_DynamicInError: return JVM_CONSTANT_Dynamic; default: - return _tag; + return value(); } } jbyte constantTag::error_value() const { - switch (_tag) { + switch (value()) { case JVM_CONSTANT_UnresolvedClass: return JVM_CONSTANT_UnresolvedClassInError; case JVM_CONSTANT_MethodHandle: @@ -100,7 +100,7 @@ jbyte constantTag::error_value() const { } const char* constantTag::internal_name() const { - switch (_tag) { + switch (value()) { case JVM_CONSTANT_Invalid : return "Invalid index"; case JVM_CONSTANT_Class : diff --git a/src/hotspot/share/utilities/constantTag.hpp b/src/hotspot/share/utilities/constantTag.hpp index d826fc0acc0..02ece51d04f 100644 --- a/src/hotspot/share/utilities/constantTag.hpp +++ b/src/hotspot/share/utilities/constantTag.hpp @@ -39,21 +39,20 @@ enum { JVM_CONSTANT_Invalid = 0, // For bad value initialization JVM_CONSTANT_InternalMin = 100, // First implementation tag (aside from bad value of course) JVM_CONSTANT_UnresolvedClass = 100, // Temporary tag until actual use - JVM_CONSTANT_ClassIndex = 101, // Temporary tag while constructing constant pool - JVM_CONSTANT_StringIndex = 102, // Temporary tag while constructing constant pool + JVM_CONSTANT_ClassIndex = 101, // Temporary tag while constructing constant pool, class redefinition + JVM_CONSTANT_StringIndex = 102, // Temporary tag while constructing constant pool, class redefinition JVM_CONSTANT_UnresolvedClassInError = 103, // Error tag due to resolution error JVM_CONSTANT_MethodHandleInError = 104, // Error tag due to resolution error JVM_CONSTANT_MethodTypeInError = 105, // Error tag due to resolution error JVM_CONSTANT_DynamicInError = 106, // Error tag due to resolution error - JVM_CONSTANT_InternalMax = 106 // Last implementation tag + JVM_CONSTANT_InternalMax = 106, // Last implementation tag }; - class constantTag { private: jbyte _tag; public: - bool is_klass() const { return _tag == JVM_CONSTANT_Class; } + bool is_klass() const { return value() == JVM_CONSTANT_Class; } bool is_field () const { return _tag == JVM_CONSTANT_Fieldref; } bool is_method() const { return _tag == JVM_CONSTANT_Methodref; } bool is_interface_method() const { return _tag == JVM_CONSTANT_InterfaceMethodref; } @@ -68,11 +67,11 @@ class constantTag { bool is_invalid() const { return _tag == JVM_CONSTANT_Invalid; } bool is_unresolved_klass() const { - return _tag == JVM_CONSTANT_UnresolvedClass || _tag == JVM_CONSTANT_UnresolvedClassInError; + return value() == JVM_CONSTANT_UnresolvedClass || value() == JVM_CONSTANT_UnresolvedClassInError; } bool is_unresolved_klass_in_error() const { - return _tag == JVM_CONSTANT_UnresolvedClassInError; + return value() == JVM_CONSTANT_UnresolvedClassInError; } bool is_method_handle_in_error() const { @@ -149,6 +148,7 @@ class constantTag { } jbyte value() const { return _tag; } + jbyte tag() const { return _tag; } jbyte error_value() const; jbyte non_error_value() const; diff --git a/src/hotspot/share/utilities/copy.cpp b/src/hotspot/share/utilities/copy.cpp index 4dc1ef7b5bf..e617ac4be14 100644 --- a/src/hotspot/share/utilities/copy.cpp +++ b/src/hotspot/share/utilities/copy.cpp @@ -52,6 +52,46 @@ void Copy::conjoint_memory_atomic(const void* from, void* to, size_t size) { } } +#define COPY_ALIGNED_SEGMENT(t) \ + if (bits % sizeof(t) == 0) { \ + size_t segment = remain / sizeof(t); \ + if (segment > 0) { \ + Copy::conjoint_##t##s_atomic((const t*) cursor_from, (t*) cursor_to, segment); \ + remain -= segment * sizeof(t); \ + cursor_from = (void*)(((char*)cursor_from) + segment * sizeof(t)); \ + cursor_to = (void*)(((char*)cursor_to) + segment * sizeof(t)); \ + } \ + } \ + +void Copy::copy_value_content(const void* from, void* to, size_t size) { + // Simple cases first + uintptr_t bits = (uintptr_t) from | (uintptr_t) to | (uintptr_t) size; + if (bits % sizeof(jlong) == 0) { + Copy::conjoint_jlongs_atomic((const jlong*) from, (jlong*) to, size / sizeof(jlong)); + return; + } else if (bits % sizeof(jint) == 0) { + Copy::conjoint_jints_atomic((const jint*) from, (jint*) to, size / sizeof(jint)); + return; + } else if (bits % sizeof(jshort) == 0) { + Copy::conjoint_jshorts_atomic((const jshort*) from, (jshort*) to, size / sizeof(jshort)); + return; + } + + // Complex cases + bits = (uintptr_t) from | (uintptr_t) to; + const void* cursor_from = from; + void* cursor_to = to; + size_t remain = size; + COPY_ALIGNED_SEGMENT(jlong) + COPY_ALIGNED_SEGMENT(jint) + COPY_ALIGNED_SEGMENT(jshort) + if (remain > 0) { + Copy::conjoint_jbytes((const void*) cursor_from, (void*) cursor_to, remain); + } +} + +#undef COPY_ALIGNED_SEGMENT + class CopySwap : AllStatic { public: /** diff --git a/src/hotspot/share/utilities/copy.hpp b/src/hotspot/share/utilities/copy.hpp index c9a114588ed..7262bc61073 100644 --- a/src/hotspot/share/utilities/copy.hpp +++ b/src/hotspot/share/utilities/copy.hpp @@ -161,6 +161,8 @@ class Copy : AllStatic { // of two which divides all of from, to, and size, whichever is smaller. static void conjoint_memory_atomic(const void* from, void* to, size_t size); + static void copy_value_content(const void* from, void* to, size_t size); + // bytes, conjoint array, atomic on each byte (not that it matters) static void arrayof_conjoint_jbytes(const HeapWord* from, HeapWord* to, size_t count) { pd_arrayof_conjoint_bytes(from, to, count); diff --git a/src/hotspot/share/utilities/exceptions.hpp b/src/hotspot/share/utilities/exceptions.hpp index 94f4a04546d..f0c2e9fff10 100644 --- a/src/hotspot/share/utilities/exceptions.hpp +++ b/src/hotspot/share/utilities/exceptions.hpp @@ -254,6 +254,8 @@ class Exceptions { // with a TRAPS argument. #define THREAD_AND_LOCATION THREAD, __FILE__, __LINE__ +#define THREAD_AND_LOCATION_DECL TRAPS, const char* file, int line +#define THREAD_AND_LOCATION_ARGS THREAD, file, line #define THROW_OOP(e) \ { Exceptions::_throw_oop(THREAD_AND_LOCATION, e); return; } diff --git a/src/hotspot/share/utilities/globalDefinitions.cpp b/src/hotspot/share/utilities/globalDefinitions.cpp index 84a0730212f..29720b1ecf7 100644 --- a/src/hotspot/share/utilities/globalDefinitions.cpp +++ b/src/hotspot/share/utilities/globalDefinitions.cpp @@ -210,8 +210,8 @@ char type2char_tab[T_CONFLICT+1] = { JVM_SIGNATURE_BYTE, JVM_SIGNATURE_SHORT, JVM_SIGNATURE_INT, JVM_SIGNATURE_LONG, JVM_SIGNATURE_CLASS, JVM_SIGNATURE_ARRAY, - JVM_SIGNATURE_VOID, 0, - 0, 0, 0, 0 + JVM_SIGNATURE_VOID, + 0, 0, 0, 0, 0, 0 }; // Map BasicType to Java type name @@ -228,6 +228,7 @@ const char* type2name_tab[T_CONFLICT+1] = { "object", "array", "void", + "flat element", "*address*", "*narrowoop*", "*metadata*", @@ -257,7 +258,7 @@ BasicType name2type(const char* name) { } // Map BasicType to size in words -int type2size[T_CONFLICT+1]={ -1, 0, 0, 0, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 0, 1, 1, 1, 1, -1}; +int type2size[T_CONFLICT+1]={ -1, 0, 0, 0, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 0, -1, 1, 1, 1, 1, -1}; BasicType type2field[T_CONFLICT+1] = { (BasicType)0, // 0, @@ -275,11 +276,12 @@ BasicType type2field[T_CONFLICT+1] = { T_OBJECT, // T_OBJECT = 12, T_OBJECT, // T_ARRAY = 13, T_VOID, // T_VOID = 14, - T_ADDRESS, // T_ADDRESS = 15, - T_NARROWOOP, // T_NARROWOOP= 16, - T_METADATA, // T_METADATA = 17, - T_NARROWKLASS, // T_NARROWKLASS = 18, - T_CONFLICT // T_CONFLICT = 19, + (BasicType)0, // T_FLAT_ELEMENT = 15, + T_ADDRESS, // T_ADDRESS = 16, + T_NARROWOOP, // T_NARROWOOP= 17, + T_METADATA, // T_METADATA = 18, + T_NARROWKLASS, // T_NARROWKLASS = 19, + T_CONFLICT // T_CONFLICT = 20 }; @@ -299,11 +301,12 @@ BasicType type2wfield[T_CONFLICT+1] = { T_OBJECT, // T_OBJECT = 12, T_OBJECT, // T_ARRAY = 13, T_VOID, // T_VOID = 14, - T_ADDRESS, // T_ADDRESS = 15, - T_NARROWOOP, // T_NARROWOOP = 16, - T_METADATA, // T_METADATA = 17, - T_NARROWKLASS, // T_NARROWKLASS = 18, - T_CONFLICT // T_CONFLICT = 19, + (BasicType)0, // T_FLAT_ELEMENT = 15, + T_ADDRESS, // T_ADDRESS = 16, + T_NARROWOOP, // T_NARROWOOP = 17, + T_METADATA, // T_METADATA = 18, + T_NARROWKLASS, // T_NARROWKLASS = 19, + T_CONFLICT // T_CONFLICT = 20 }; @@ -323,11 +326,12 @@ int _type2aelembytes[T_CONFLICT+1] = { T_OBJECT_aelem_bytes, // T_OBJECT = 12, T_ARRAY_aelem_bytes, // T_ARRAY = 13, 0, // T_VOID = 14, - T_OBJECT_aelem_bytes, // T_ADDRESS = 15, - T_NARROWOOP_aelem_bytes, // T_NARROWOOP= 16, - T_OBJECT_aelem_bytes, // T_METADATA = 17, - T_NARROWKLASS_aelem_bytes, // T_NARROWKLASS= 18, - 0 // T_CONFLICT = 19, + 0, // T_FLAT_ELEMENT = 15, + T_OBJECT_aelem_bytes, // T_ADDRESS = 16, + T_NARROWOOP_aelem_bytes, // T_NARROWOOP= 17, + T_OBJECT_aelem_bytes, // T_METADATA = 18, + T_NARROWKLASS_aelem_bytes, // T_NARROWKLASS= 19, + 0 // T_CONFLICT = 20 }; #ifdef ASSERT diff --git a/src/hotspot/share/utilities/globalDefinitions.hpp b/src/hotspot/share/utilities/globalDefinitions.hpp index 64ccae539a3..32ef97f315a 100644 --- a/src/hotspot/share/utilities/globalDefinitions.hpp +++ b/src/hotspot/share/utilities/globalDefinitions.hpp @@ -602,6 +602,15 @@ const bool support_IRIW_for_not_multiple_copy_atomic_cpu = PPC64_ONLY(true) NOT_ #endif +//---------------------------------------------------------------------------------------------------- +// Prototyping +// "Code Missing Here" macro, un-define when integrating back from prototyping stage and break +// compilation on purpose (i.e. "forget me not") +#define PROTOTYPE +#ifdef PROTOTYPE +#define CMH(m) +#endif + //---------------------------------------------------------------------------------------------------- // Miscellaneous @@ -680,11 +689,12 @@ enum BasicType : u1 { T_OBJECT = 12, T_ARRAY = 13, T_VOID = 14, - T_ADDRESS = 15, - T_NARROWOOP = 16, - T_METADATA = 17, - T_NARROWKLASS = 18, - T_CONFLICT = 19, // for stack value type with conflicting contents + T_FLAT_ELEMENT = 15, // Not a true BasicType, only used in layout helpers of flat arrays + T_ADDRESS = 16, + T_NARROWOOP = 17, + T_METADATA = 18, + T_NARROWKLASS = 19, + T_CONFLICT = 20, // for stack value type with conflicting contents T_ILLEGAL = 99 }; @@ -728,6 +738,7 @@ inline bool is_double_word_type(BasicType t) { } inline bool is_reference_type(BasicType t, bool include_narrow_oop = false) { + assert(t != T_FLAT_ELEMENT, ""); // Strong assert to detect misuses of T_FLAT_ELEMENT return (t == T_OBJECT || t == T_ARRAY || (include_narrow_oop && t == T_NARROWOOP)); } @@ -802,7 +813,8 @@ enum BasicTypeSize { T_ARRAY_size = 1, T_NARROWOOP_size = 1, T_NARROWKLASS_size = 1, - T_VOID_size = 0 + T_VOID_size = 0, + T_FLAT_ELEMENT_size = 0 }; // this works on valid parameter types but not T_VOID, T_CONFLICT, etc. @@ -838,7 +850,8 @@ enum ArrayElementSize { #endif T_NARROWOOP_aelem_bytes = 4, T_NARROWKLASS_aelem_bytes = 4, - T_VOID_aelem_bytes = 0 + T_VOID_aelem_bytes = 0, + T_FLAT_ELEMENT_aelem_bytes = 0 }; extern int _type2aelembytes[T_CONFLICT+1]; // maps a BasicType to nof bytes used by its array element @@ -928,7 +941,7 @@ enum TosState { // describes the tos cache contents ftos = 6, // float tos cached dtos = 7, // double tos cached atos = 8, // object cached - vtos = 9, // tos not cached + vtos = 9, // tos not cached, number_of_states, ilgl // illegal state: should not occur }; @@ -945,7 +958,7 @@ inline TosState as_TosState(BasicType type) { case T_FLOAT : return ftos; case T_DOUBLE : return dtos; case T_VOID : return vtos; - case T_ARRAY : // fall through + case T_ARRAY : // fall through case T_OBJECT : return atos; default : return ilgl; } diff --git a/src/hotspot/share/utilities/growableArray.hpp b/src/hotspot/share/utilities/growableArray.hpp index 53403ca5cf1..5a7ad800775 100644 --- a/src/hotspot/share/utilities/growableArray.hpp +++ b/src/hotspot/share/utilities/growableArray.hpp @@ -27,6 +27,8 @@ #include "memory/allocation.hpp" #include "memory/iterator.hpp" +#include "oops/array.hpp" +#include "oops/oop.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/ostream.hpp" @@ -96,6 +98,7 @@ class GrowableArrayBase : public AnyObj { }; template class GrowableArrayIterator; +template class GrowableArrayFilterIterator; // Extends GrowableArrayBase with a typed data array. // @@ -424,6 +427,12 @@ class GrowableArrayWithAllocator : public GrowableArrayView { } } + void appendAll(const Array* l) { + for (int i = 0; i < l->length(); i++) { + this->at_put_grow(this->_len, l->at(i), E()); + } + } + // Binary search and insertion utility. Search array for element // matching key according to the static compare function. Insert // that element if not already in the list. Assumes the list is @@ -871,6 +880,7 @@ class GrowableArrayCHeap : public GrowableArrayWithAllocator class GrowableArrayIterator : public StackObj { friend class GrowableArrayView; + template friend class GrowableArrayFilterIterator; private: const GrowableArrayView* _array; // GrowableArray we iterate over @@ -897,6 +907,60 @@ class GrowableArrayIterator : public StackObj { } }; +// Custom STL-style iterator to iterate over elements of a GrowableArray that satisfy a given predicate +template +class GrowableArrayFilterIterator : public StackObj { + friend class GrowableArrayView; + + private: + const GrowableArrayView* _array; // GrowableArray we iterate over + int _position; // Current position in the GrowableArray + UnaryPredicate _predicate; // Unary predicate the elements of the GrowableArray should satisfy + + public: + GrowableArrayFilterIterator(const GrowableArray* array, UnaryPredicate filter_predicate) : + _array(array), _position(0), _predicate(filter_predicate) { + // Advance to first element satisfying the predicate + while(!at_end() && !_predicate(_array->at(_position))) { + ++_position; + } + } + + GrowableArrayFilterIterator& operator++() { + do { + // Advance to next element satisfying the predicate + ++_position; + } while(!at_end() && !_predicate(_array->at(_position))); + return *this; + } + + E operator*() { return _array->at(_position); } + + bool operator==(const GrowableArrayIterator& rhs) { + assert(_array == rhs._array, "iterator belongs to different array"); + return _position == rhs._position; + } + + bool operator!=(const GrowableArrayIterator& rhs) { + assert(_array == rhs._array, "iterator belongs to different array"); + return _position != rhs._position; + } + + bool operator==(const GrowableArrayFilterIterator& rhs) { + assert(_array == rhs._array, "iterator belongs to different array"); + return _position == rhs._position; + } + + bool operator!=(const GrowableArrayFilterIterator& rhs) { + assert(_array == rhs._array, "iterator belongs to different array"); + return _position != rhs._position; + } + + bool at_end() const { + return _array == nullptr || _position == _array->end()._position; + } +}; + // Arrays for basic types typedef GrowableArray intArray; typedef GrowableArray intStack; diff --git a/src/hotspot/share/utilities/stringUtils.cpp b/src/hotspot/share/utilities/stringUtils.cpp index cd4c2d6c33b..677b6566d2c 100644 --- a/src/hotspot/share/utilities/stringUtils.cpp +++ b/src/hotspot/share/utilities/stringUtils.cpp @@ -25,6 +25,7 @@ #include "jvm_io.h" #include "memory/allocation.hpp" #include "utilities/debug.hpp" +#include "utilities/ostream.hpp" #include "utilities/stringUtils.hpp" #include @@ -70,6 +71,221 @@ double StringUtils::similarity(const char* str1, size_t len1, const char* str2, return 2.0 * (double) hit / (double) total; } +class StringMatcher { + public: + typedef int getc_function_t(const char* &source, const char* limit); + + private: + // These do not get properly inlined. + // For full performance, this should be a template class + // parameterized by two function arguments. + getc_function_t* _pattern_getc; + getc_function_t* _string_getc; + + public: + StringMatcher(getc_function_t pattern_getc, + getc_function_t string_getc) + : _pattern_getc(pattern_getc), + _string_getc(string_getc) + { } + + enum { // special results from _pattern_getc + string_match_comma = -0x100 + ',', + string_match_star = -0x100 + '*', + string_match_eos = -0x100 + '\0' + }; + + private: + const char* + skip_anchor_word(const char* match, + const char* match_end, + int anchor_length, + const char* pattern, + const char* pattern_end) { + assert(pattern < pattern_end && anchor_length > 0, ""); + const char* begp = pattern; + int ch1 = _pattern_getc(begp, pattern_end); + // note that begp is now advanced over ch1 + assert(ch1 > 0, "regular char only"); + const char* matchp = match; + const char* limitp = match_end - anchor_length; + while (matchp <= limitp) { + int mch = _string_getc(matchp, match_end); + if (mch == ch1) { + const char* patp = begp; + const char* anchorp = matchp; + while (patp < pattern_end) { + char ch = _pattern_getc(patp, pattern_end); + char mch = _string_getc(anchorp, match_end); + if (mch != ch) { + anchorp = nullptr; + break; + } + } + if (anchorp != nullptr) { + return anchorp; // Found a full copy of the anchor. + } + // That did not work, so restart the search for ch1. + } + } + return nullptr; + } + + public: + bool string_match(const char* pattern, + const char* string) { + return string_match(pattern, pattern + strlen(pattern), + string, string + strlen(string)); + } + bool string_match(const char* pattern, const char* pattern_end, + const char* string, const char* string_end) { + const char* patp = pattern; + switch (_pattern_getc(patp, pattern_end)) { + case string_match_eos: + return false; // Empty pattern is always false. + case string_match_star: + if (patp == pattern_end) { + return true; // Lone star pattern is always true. + } + break; + } + patp = pattern; // Reset after lookahead. + const char* matchp = string; // nullptr if failing + for (;;) { + int ch = _pattern_getc(patp, pattern_end); + switch (ch) { + case string_match_eos: + case string_match_comma: + // End of a list item; see if it's a match. + if (matchp == string_end) { + return true; + } + if (ch == string_match_comma) { + // Get ready to match the next item. + matchp = string; + continue; + } + return false; // End of all items. + + case string_match_star: + if (matchp != nullptr) { + // Wildcard: Parse out following anchor word and look for it. + const char* begp = patp; + const char* endp = patp; + int anchor_len = 0; + for (;;) { + // get as many following regular characters as possible + endp = patp; + ch = _pattern_getc(patp, pattern_end); + if (ch <= 0) { + break; + } + anchor_len += 1; + } + // Anchor word [begp..endp) does not contain ch, so back up. + // Now do an eager match to the anchor word, and commit to it. + patp = endp; + if (ch == string_match_eos || + ch == string_match_comma) { + // Anchor word is at end of pattern, so treat it as a fixed pattern. + const char* limitp = string_end - anchor_len; + matchp = limitp; + patp = begp; + // Resume normal scanning at the only possible match position. + continue; + } + // Find a floating occurrence of the anchor and continue matching. + // Note: This is greedy; there is no backtrack here. Good enough. + matchp = skip_anchor_word(matchp, string_end, anchor_len, begp, endp); + } + continue; + } + // Normal character. + if (matchp != nullptr) { + int mch = _string_getc(matchp, string_end); + if (mch != ch) { + matchp = nullptr; + } + } + } + } +}; + +// Match a wildcarded class list to a proposed class name (in internal form). +// Commas or newlines separate multiple possible matches; stars are shell-style wildcards. +class ClassListMatcher : public StringMatcher { + public: + ClassListMatcher() + : StringMatcher(pattern_list_getc, class_name_getc) + { } + + private: + static int pattern_list_getc(const char* &pattern_ptr, + const char* pattern_end) { + if (pattern_ptr == pattern_end) { + return string_match_eos; + } + int ch = (unsigned char) *pattern_ptr++; + switch (ch) { + case ' ': case '\t': case '\n': case '\r': + case ',': + // End of list item. + for (;;) { + switch (*pattern_ptr) { + case ' ': case '\t': case '\n': case '\r': + case ',': + pattern_ptr += 1; // Collapse multiple commas or spaces. + continue; + } + break; + } + return string_match_comma; + + case '*': + // Wildcard, matching any number of chars. + while (*pattern_ptr == '*') { + pattern_ptr += 1; // Collapse multiple stars. + } + return string_match_star; + + case '.': + ch = '/'; // Look for internal form of package separator + break; + + case '\\': + // Superquote in pattern escapes * , whitespace, and itself. + if (pattern_ptr < pattern_end) { + ch = (unsigned char) *pattern_ptr++; + } + break; + } + + assert(ch > 0, "regular char only"); + return ch; + } + + static int class_name_getc(const char* &name_ptr, + const char* name_end) { + if (name_ptr == name_end) { + return string_match_eos; + } + int ch = (unsigned char) *name_ptr++; + if (ch == '.') { + ch = '/'; // Normalize to internal form of package separator + } + return ch; // plain character + } +}; + +bool StringUtils::class_list_match(const char* class_pattern_list, + const char* class_name) { + if (class_pattern_list == nullptr || class_name == nullptr || class_name[0] == '\0') + return false; + ClassListMatcher clm; + return clm.string_match(class_pattern_list, class_name); +} + + const char* StringUtils::strstr_nocase(const char* haystack, const char* needle) { if (needle[0] == '\0') { return haystack; // empty needle matches with anything diff --git a/src/hotspot/share/utilities/stringUtils.hpp b/src/hotspot/share/utilities/stringUtils.hpp index f30c9c3ea78..53d0b322a52 100644 --- a/src/hotspot/share/utilities/stringUtils.hpp +++ b/src/hotspot/share/utilities/stringUtils.hpp @@ -46,6 +46,10 @@ class StringUtils : AllStatic { // Compute string similarity based on Dice's coefficient static double similarity(const char* str1, size_t len1, const char* str2, size_t len2); + // Match a wildcarded class list to a proposed class name (in internal form). + // Commas separate multiple possible matches; stars are shell-style wildcards. + static bool class_list_match(const char* class_list, const char* class_name); + // Find needle in haystack, case insensitive. // Custom implementation of strcasestr, as it is not available on windows. static const char* strstr_nocase(const char* haystack, const char* needle); diff --git a/src/java.base/share/classes/java/io/Externalizable.java b/src/java.base/share/classes/java/io/Externalizable.java index 84029fc5f3a..137240ba089 100644 --- a/src/java.base/share/classes/java/io/Externalizable.java +++ b/src/java.base/share/classes/java/io/Externalizable.java @@ -51,9 +51,15 @@ * constructor, then the readExternal method called. Serializable * objects are restored by reading them from an ObjectInputStream.
* - * An Externalizable instance can designate a substitution object via + * An Externalizable object can designate a substitution object via * the writeReplace and readResolve methods documented in the Serializable - * interface.
+ * interface. To control the serialized form of Records and Externalizable value classes + * the record or class uses {@link Serializable} {@code writeReplace} + * to delegate to another serializable or externalizable class and that delegate + * uses {@code readResolve} to supply the replacement on deserialization. + * Value classes implementing {@link Externalizable} + * and not using {@code writeReplace} are not supported. + *
* * @see java.io.ObjectOutputStream * @see java.io.ObjectInputStream diff --git a/src/java.base/share/classes/java/io/InvalidClassException.java b/src/java.base/share/classes/java/io/InvalidClassException.java index be187597726..5045b0c4566 100644 --- a/src/java.base/share/classes/java/io/InvalidClassException.java +++ b/src/java.base/share/classes/java/io/InvalidClassException.java @@ -37,6 +37,9 @@ * an enum type *
  • Other conditions given in the Java Object Serialization * Specification + *
  • A {@linkplain Class#isValue()} value class implements {@linkplain Serializable} + * but does not delegate to a serialization proxy using {@code writeReplace()}. + *
  • A {@linkplain Class#isValue()} value class implements {@linkplain Externalizable}. * * * @since 1.1 diff --git a/src/java.base/share/classes/java/io/ObjectInputStream.java b/src/java.base/share/classes/java/io/ObjectInputStream.java index daed5f3cce5..3ae094bc4b0 100644 --- a/src/java.base/share/classes/java/io/ObjectInputStream.java +++ b/src/java.base/share/classes/java/io/ObjectInputStream.java @@ -26,15 +26,19 @@ package java.io; import java.io.ObjectInputFilter.Config; -import java.io.ObjectStreamClass.RecordSupport; +import java.io.ObjectStreamClass.ConstructorSupport; +import java.io.ObjectStreamClass.ClassDataSlot; import java.lang.System.Logger; import java.lang.invoke.MethodHandle; import java.lang.reflect.Array; import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.List; +import java.util.Locale; import java.util.Objects; import jdk.internal.access.JavaLangAccess; @@ -210,6 +214,8 @@ * implemented by a class they can write and read their own state using all of * the methods of ObjectOutput and ObjectInput. It is the responsibility of * the objects to handle any versioning that occurs. + * Value objects cannot be `java.io.Externalizable` because value objects are + * immutable and `Externalizable.readExternal` is unable to modify the fields of the value. * *

    Enum constants are deserialized differently than ordinary serializable or * externalizable objects. The serialized form of an enum constant consists @@ -235,6 +241,10 @@ * Java Object Serialization Specification, Section 1.13, * "Serialization of Records" for additional information. * + *

    Value classes are {@linkplain Serializable} through the use of the serialization proxy pattern. + * See {@linkplain ObjectOutputStream##valueclass-serialization value class serialization} for details. + * When the proxy is deserialized it re-constructs and returns the value object. + * * @spec serialization/index.html Java Object Serialization Specification * @author Mike Warres * @author Roger Riggs @@ -248,6 +258,16 @@ public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants { + private static final String TRACE_DEST = + System.getProperty("TRACE"); + + static void TRACE(String format, Object... args) { + if (TRACE_DEST != null) { + var ps = "OUT".equals(TRACE_DEST.toUpperCase(Locale.ROOT)) ? System.out : System.err; + ps.println(("TRACE " + format).formatted(args)); + } + } + /** handle value representing null */ private static final int NULL_HANDLE = -1; @@ -425,6 +445,14 @@ protected ObjectInputStream() throws IOException { * each object (regular or class) read to reconstruct the root object. * See {@link #setObjectInputFilter(ObjectInputFilter) setObjectInputFilter} for details. * + *

    Serialization and deserialization of value classes is described in + * {@linkplain ObjectOutputStream##valueclass-serialization value class serialization}. + * + * @implSpec + * When enabled with {@code --enable-preview}, serialization and deserialization of + * Core Library value classes migrated from pre-JEP 401 identity classes is + * implementation specific. + * *

    Exceptions are thrown for problems with the InputStream and for * classes that should not be deserialized. All exceptions are fatal to * the InputStream and leave it in an indeterminate state; it is up to the @@ -2107,67 +2135,130 @@ private Object readOrdinaryObject(boolean unshared) throw new InvalidClassException("invalid class descriptor"); } - Object obj; - try { - obj = desc.isInstantiable() ? desc.newInstance() : null; - } catch (Exception ex) { - throw new InvalidClassException(desc.forClass().getName(), - "unable to create instance", ex); - } - - passHandle = handles.assign(unshared ? unsharedMarker : obj); + // Assign the handle and initially set to null or the unsharedMarker + passHandle = handles.assign(unshared ? unsharedMarker : null); ClassNotFoundException resolveEx = desc.getResolveException(); if (resolveEx != null) { handles.markException(passHandle, resolveEx); } - final boolean isRecord = desc.isRecord(); - if (isRecord) { - assert obj == null; - obj = readRecord(desc); - if (!unshared) - handles.setObject(passHandle, obj); - } else if (desc.isExternalizable()) { - readExternalData((Externalizable) obj, desc); - } else { - readSerialData(obj, desc); - } + try { + // Dispatch on the factory mode to read an object from the stream. + Object obj = switch (desc.factoryMode()) { + case READ_OBJECT_DEFAULT -> readSerialDefaultObject(desc, unshared); + case READ_OBJECT_CUSTOM -> readSerialCustomData(desc, unshared); + case READ_RECORD -> readRecord(desc, unshared); + case READ_EXTERNALIZABLE -> readExternalObject(desc, unshared); + case READ_OBJECT_VALUE -> readObjectValue(desc, unshared); + case READ_NO_LOCAL_CLASS -> readAbsentLocalClass(desc, unshared); + case null -> throw new AssertionError("Unknown factoryMode for: " + desc.getName(), + resolveEx); + }; - handles.finish(passHandle); + handles.finish(passHandle); - if (obj != null && - handles.lookupException(passHandle) == null && - desc.hasReadResolveMethod()) - { - Object rep = desc.invokeReadResolve(obj); - if (unshared && rep.getClass().isArray()) { - rep = cloneArray(rep); - } - if (rep != obj) { - // Filter the replacement object - if (rep != null) { - if (rep.getClass().isArray()) { - filterCheck(rep.getClass(), Array.getLength(rep)); - } else { - filterCheck(rep.getClass(), -1); + if (obj != null && + handles.lookupException(passHandle) == null && + desc.hasReadResolveMethod()) + { + Object rep = desc.invokeReadResolve(obj); + if (unshared && rep.getClass().isArray()) { + rep = cloneArray(rep); + } + if (rep != obj) { + // Filter the replacement object + if (rep != null) { + if (rep.getClass().isArray()) { + filterCheck(rep.getClass(), Array.getLength(rep)); + } else { + filterCheck(rep.getClass(), -1); + } } + handles.setObject(passHandle, obj = rep); } - handles.setObject(passHandle, obj = rep); } + + return obj; + } catch (UncheckedIOException uioe) { + // Consistent re-throw for nested UncheckedIOExceptions + throw uioe.getCause(); } + } - return obj; + /** + * {@return a value class instance by invoking its constructor with field values read from the stream. + * The fields of the class in the stream are matched to the local fields and applied to + * the constructor. + * If the stream contains superclasses with serializable fields, + * an InvalidClassException is thrown with an incompatible class change message. + * + * @param desc the class descriptor read from the stream, the local class is a value class + * @param unshared if the object is not to be shared + * @throws InvalidClassException if the stream contains a superclass with serializable fields. + * @throws IOException if there are I/O errors while reading from the + * underlying {@code InputStream} + */ + private Object readObjectValue(ObjectStreamClass desc, boolean unshared) throws IOException { + final ObjectStreamClass localDesc = desc.getLocalDesc(); + TRACE("readObjectValue: %s, local class: %s", desc.getName(), localDesc.getName()); + // Check for un-expected fields in superclasses + List slots = desc.getClassDataLayout(); + for (int i = 0; i < slots.size()-1; i++) { + ClassDataSlot slot = slots.get(i); + if (slot.hasData && slot.desc.getFields(false).length > 0) { + throw new InvalidClassException("incompatible class change to value class: " + + "stream class has non-empty super type: " + desc.getName()); + } + } + // Read values for the value class fields + FieldValues fieldValues = new FieldValues(desc, true); + + // Get value object constructor adapted to take primitive value buffer and object array. + MethodHandle consMH = ConstructorSupport.deserializationValueCons(desc); + try { + Object obj = (Object) consMH.invokeExact(fieldValues.primValues, fieldValues.objValues); + if (!unshared) + handles.setObject(passHandle, obj); + return obj; + } catch (Exception e) { + throw new InvalidObjectException(e.getMessage(), e); + } catch (Error e) { + throw e; + } catch (Throwable t) { + throw new InvalidObjectException("ReflectiveOperationException " + + "during deserialization", t); + } } /** - * If obj is non-null, reads externalizable data by invoking readExternal() + * Creates a new object and invokes its readExternal method to read its contents. + * + * If the class is instantiable, read externalizable data by invoking readExternal() * method of obj; otherwise, attempts to skip over externalizable data. * Expects that passHandle is set to obj's handle before this method is - * called. + * called. The new object is entered in the handle table immediately, + * allowing it to leak before it is completely read. */ - private void readExternalData(Externalizable obj, ObjectStreamClass desc) + private Object readExternalObject(ObjectStreamClass desc, boolean unshared) throws IOException { + TRACE("readExternalObject: %s", desc.getName()); + + // For Externalizable objects, + // create the instance, publish the ref, and read the data + Externalizable obj = null; + try { + if (desc.isInstantiable()) { + obj = (Externalizable) desc.newInstance(); + } + } catch (Exception ex) { + throw new InvalidClassException(desc.getName(), + "unable to create instance", ex); + } + + if (!unshared) + handles.setObject(passHandle, obj); + SerialCallbackContext oldContext = curContext; if (oldContext != null) oldContext.check(); @@ -2211,6 +2302,7 @@ private void readExternalData(Externalizable obj, ObjectStreamClass desc) * externalizable data remains in the stream, a subsequent read will * most likely throw a StreamCorruptedException. */ + return obj; } /** @@ -2219,14 +2311,15 @@ private void readExternalData(Externalizable obj, ObjectStreamClass desc) * mechanism marks the record as having an exception. * Null is returned from readRecord and later the exception is thrown at * the exit of {@link #readObject(Class)}. - **/ - private Object readRecord(ObjectStreamClass desc) throws IOException { - ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout(); - if (slots.length != 1) { + */ + private Object readRecord(ObjectStreamClass desc, boolean unshared) throws IOException { + TRACE("invoking readRecord: %s", desc.getName()); + List slots = desc.getClassDataLayout(); + if (slots.size() != 1) { // skip any superclass stream field values - for (int i = 0; i < slots.length-1; i++) { - if (slots[i].hasData) { - new FieldValues(slots[i].desc, true); + for (int i = 0; i < slots.size()-1; i++) { + if (slots.get(i).hasData) { + new FieldValues(slots.get(i).desc, true); } } } @@ -2240,10 +2333,13 @@ private Object readRecord(ObjectStreamClass desc) throws IOException { // - byte[] primValues // - Object[] objValues // and return Object - MethodHandle ctrMH = RecordSupport.deserializationCtr(desc); + MethodHandle ctrMH = ConstructorSupport.deserializationCtr(desc); try { - return (Object) ctrMH.invokeExact(fieldValues.primValues, fieldValues.objValues); + Object obj = (Object) ctrMH.invokeExact(fieldValues.primValues, fieldValues.objValues); + if (!unshared) + handles.setObject(passHandle, obj); + return obj; } catch (Exception e) { throw new InvalidObjectException(e.getMessage(), e); } catch (Error e) { @@ -2255,114 +2351,207 @@ private Object readRecord(ObjectStreamClass desc) throws IOException { } /** - * Reads (or attempts to skip, if obj is null or is tagged with a + * Construct an object from the stream for a class that has only default read object behaviors. + * For each object, the fields are read before any are assigned. + * The new instance is entered in the handle table if it is unshared, + * allowing it to escape before it is initialized. + * The `readObject` and `readObjectNoData` methods are not present and are not called. + * + * @param desc the class descriptor + * @param unshared true if the object should be shared + * @return the object constructed from the stream data + * @throws IOException if there are I/O errors while reading from the + * underlying {@code InputStream} + * @throws InvalidClassException if the instance creation fails + */ + private Object readSerialDefaultObject(ObjectStreamClass desc, boolean unshared) + throws IOException, InvalidClassException { + if (!desc.isInstantiable()) { + // No local class to create, read and discard + return readAbsentLocalClass(desc, unshared); + } + TRACE("readSerialDefaultObject: %s", desc.getName()); + try { + final Object obj = desc.newInstance(); + if (!unshared) + handles.setObject(passHandle, obj); + + // Best effort Failure Atomicity; slotValues will be non-null if field + // values can be set after reading all field data in the hierarchy. + List slotValues = desc.getClassDataLayout().stream() + .filter(s -> s.hasData) + .map(s1 -> { + var values = new FieldValues(s1.desc, true); + finishBlockData(s1.desc); + return values; + }) + .toList(); + + if (handles.lookupException(passHandle) != null) { + return null; // some exception for a class, do not return the object + } + + // Check that the types are assignable for all slots before assigning. + slotValues.forEach(v -> v.defaultCheckFieldValues(obj)); + slotValues.forEach(v -> v.defaultSetFieldValues(obj)); + return obj; + } catch (InstantiationException | InvocationTargetException ex) { + throw new InvalidClassException(desc.forClass().getName(), + "unable to create instance", ex); + } + } + + + /** + * Reads (or attempts to skip, if not instantiatable or is tagged with a * ClassNotFoundException) instance data for each serializable class of - * object in stream, from superclass to subclass. Expects that passHandle - * is set to obj's handle before this method is called. + * object in stream, from superclass to subclass. + * Expects that passHandle is set to current handle before this method is called. */ - private void readSerialData(Object obj, ObjectStreamClass desc) + private Object readSerialCustomData(ObjectStreamClass desc, boolean unshared) throws IOException { - ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout(); - // Best effort Failure Atomicity; slotValues will be non-null if field - // values can be set after reading all field data in the hierarchy. - // Field values can only be set after reading all data if there are no - // user observable methods in the hierarchy, readObject(NoData). The - // top most Serializable class in the hierarchy can be skipped. - FieldValues[] slotValues = null; - - boolean hasSpecialReadMethod = false; - for (int i = 1; i < slots.length; i++) { - ObjectStreamClass slotDesc = slots[i].desc; - if (slotDesc.hasReadObjectMethod() - || slotDesc.hasReadObjectNoDataMethod()) { - hasSpecialReadMethod = true; - break; - } - } - // No special read methods, can store values and defer setting. - if (!hasSpecialReadMethod) - slotValues = new FieldValues[slots.length]; - - for (int i = 0; i < slots.length; i++) { - ObjectStreamClass slotDesc = slots[i].desc; - - if (slots[i].hasData) { - if (obj == null || handles.lookupException(passHandle) != null) { - // Read fields of the current descriptor into a new FieldValues and discard - new FieldValues(slotDesc, true); - } else if (slotDesc.hasReadObjectMethod()) { - SerialCallbackContext oldContext = curContext; - if (oldContext != null) - oldContext.check(); - try { - curContext = new SerialCallbackContext(obj, slotDesc); + if (!desc.isInstantiable()) { + // No local class to create, read and discard + return readAbsentLocalClass(desc, unshared); + } - bin.setBlockDataMode(true); - slotDesc.invokeReadObject(obj, this); - } catch (ClassNotFoundException ex) { - /* - * In most cases, the handle table has already - * propagated a CNFException to passHandle at this - * point; this mark call is included to address cases - * where the custom readObject method has cons'ed and - * thrown a new CNFException of its own. - */ - handles.markException(passHandle, ex); - } finally { - curContext.setUsed(); - if (oldContext!= null) - oldContext.check(); - curContext = oldContext; - } + TRACE("readSerialCustomData: %s, ex: %s", desc.getName(), handles.lookupException(passHandle)); + try { + Object obj = desc.newInstance(); + if (!unshared) + handles.setObject(passHandle, obj); + // Read data into each of the slots for the class + return readSerialCustomSlots(obj, desc.getClassDataLayout()); + } catch (InstantiationException | InvocationTargetException ex) { + throw new InvalidClassException(desc.forClass().getName(), + "unable to create instance", ex); + } + } - /* - * defaultDataEnd may have been set indirectly by custom - * readObject() method when calling defaultReadObject() or - * readFields(); clear it to restore normal read behavior. - */ - defaultDataEnd = false; + /** + * Reads from the stream using custom or default readObject methods appropriate. + * For each slot, either the custom readObject method or the default reader of fields + * is invoked. Unused slot specific custom data is discarded. + * This function is used by {@link #readSerialCustomData}. + * + * @param obj the object to assign the values to + * @param slots a list of slots to read from the stream + * @return the object being initialized + * @throws IOException if there are I/O errors while reading from the + * underlying {@code InputStream} + */ + private Object readSerialCustomSlots(Object obj, List slots) throws IOException { + TRACE(" readSerialCustomSlots: %s", slots); + + for (ClassDataSlot slot : slots) { + ObjectStreamClass slotDesc = slot.desc; + if (slot.hasData) { + if (slotDesc.hasReadObjectMethod() && + handles.lookupException(passHandle) == null) { + // Invoke slot custom readObject method + readSlotViaReadObject(obj, slotDesc); } else { // Read fields of the current descriptor into a new FieldValues FieldValues values = new FieldValues(slotDesc, true); - if (slotValues != null) { - slotValues[i] = values; - } else if (obj != null) { - if (handles.lookupException(passHandle) == null) { - // passHandle NOT marked with an exception; set field values - values.defaultCheckFieldValues(obj); - values.defaultSetFieldValues(obj); - } + if (handles.lookupException(passHandle) == null) { + // Set the instance fields if no previous exception + values.defaultCheckFieldValues(obj); + values.defaultSetFieldValues(obj); } - } - - if (slotDesc.hasWriteObjectData()) { - skipCustomData(); - } else { - bin.setBlockDataMode(false); + finishBlockData(slotDesc); } } else { - if (obj != null && - slotDesc.hasReadObjectNoDataMethod() && - handles.lookupException(passHandle) == null) - { + if (slotDesc.hasReadObjectNoDataMethod() && + handles.lookupException(passHandle) == null) { slotDesc.invokeReadObjectNoData(obj); } } } + return obj; + } - if (obj != null && slotValues != null && handles.lookupException(passHandle) == null) { - // passHandle NOT marked with an exception - // Check that the non-primitive types are assignable for all slots - // before assigning. - for (int i = 0; i < slots.length; i++) { - if (slotValues[i] != null) - slotValues[i].defaultCheckFieldValues(obj); - } - for (int i = 0; i < slots.length; i++) { - if (slotValues[i] != null) - slotValues[i].defaultSetFieldValues(obj); + /** + * Invoke the readObject method of the class to read and store the state from the stream. + * + * @param obj an instance of the class being created, only partially initialized. + * @param slotDesc the ObjectStreamDescriptor for the current class + * @throws IOException if there are I/O errors while reading from the + * underlying {@code InputStream} + */ + private void readSlotViaReadObject(Object obj, ObjectStreamClass slotDesc) throws IOException { + TRACE("readSlotViaReadObject: %s", slotDesc.getName()); + assert obj != null : "readSlotViaReadObject called when obj == null"; + + SerialCallbackContext oldContext = curContext; + if (oldContext != null) + oldContext.check(); + try { + curContext = new SerialCallbackContext(obj, slotDesc); + + bin.setBlockDataMode(true); + slotDesc.invokeReadObject(obj, this); + } catch (ClassNotFoundException ex) { + /* + * In most cases, the handle table has already + * propagated a CNFException to passHandle at this + * point; this mark call is included to address cases + * where the custom readObject method has cons'ed and + * thrown a new CNFException of its own. + */ + handles.markException(passHandle, ex); + } finally { + curContext.setUsed(); + if (oldContext!= null) + oldContext.check(); + curContext = oldContext; + } + + /* + * defaultDataEnd may have been set indirectly by custom + * readObject() method when calling defaultReadObject() or + * readFields(); clear it to restore normal read behavior. + */ + defaultDataEnd = false; + + finishBlockData(slotDesc); + } + + + /** + * Read and discard an entire object, leaving a null reference in the HandleTable. + * The descriptor of the class in the stream is used to read the fields from the stream. + * There is no instance in which to store the field values. + * Custom data following the fields of any slot is read and discarded. + * References to nested objects are read and retained in the + * handle table using the regular mechanism. + * Handles later in the stream may refer to the nested objects. + * + * @param desc the stream class descriptor + * @param unshared the unshared flag, ignored since no object is created + * @return null, no object is created + * @throws IOException if there are I/O errors while reading from the + * underlying {@code InputStream} + */ + private Object readAbsentLocalClass(ObjectStreamClass desc, boolean unshared) + throws IOException { + TRACE("readAbsentLocalClass: %s", desc.getName()); + desc.getClassDataLayout().stream() + .filter(s -> s.hasData) + .forEach(s2 -> {new FieldValues(s2.desc, true); finishBlockData(s2.desc);}); + return null; + } + + // Finish handling of block data by skipping any remaining and setting BlockDataMode = false + private void finishBlockData(ObjectStreamClass slotDesc) throws UncheckedIOException { + try { + if (slotDesc.hasWriteObjectData()) { + skipCustomData(); + } else { + bin.setBlockDataMode(false); } + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); } } @@ -2458,32 +2647,38 @@ private final class FieldValues extends GetField { * @param desc the ObjectStreamClass to read * @param recordDependencies if true, record the dependencies * from current PassHandle and the object's read. + * @throws UncheckedIOException if any IOException occurs */ - FieldValues(ObjectStreamClass desc, boolean recordDependencies) throws IOException { - this.desc = desc; - - int primDataSize = desc.getPrimDataSize(); - primValues = (primDataSize > 0) ? new byte[primDataSize] : null; - if (primDataSize > 0) { - bin.readFully(primValues, 0, primDataSize, false); - } - - int numObjFields = desc.getNumObjFields(); - objValues = (numObjFields > 0) ? new Object[numObjFields] : null; - objHandles = (numObjFields > 0) ? new int[numObjFields] : null; - if (numObjFields > 0) { - int objHandle = passHandle; - ObjectStreamField[] fields = desc.getFields(false); - int numPrimFields = fields.length - objValues.length; - for (int i = 0; i < objValues.length; i++) { - ObjectStreamField f = fields[numPrimFields + i]; - objValues[i] = readObject0(Object.class, f.isUnshared()); - objHandles[i] = passHandle; - if (recordDependencies && f.getField() != null) { - handles.markDependency(objHandle, passHandle); + FieldValues(ObjectStreamClass desc, boolean recordDependencies) throws UncheckedIOException { + try { + this.desc = desc; + TRACE(" reading FieldValues: %s", desc.getName()); + int primDataSize = desc.getPrimDataSize(); + primValues = (primDataSize > 0) ? new byte[primDataSize] : null; + if (primDataSize > 0) { + bin.readFully(primValues, 0, primDataSize, false); + } + + + int numObjFields = desc.getNumObjFields(); + objValues = (numObjFields > 0) ? new Object[numObjFields] : null; + objHandles = (numObjFields > 0) ? new int[numObjFields] : null; + if (numObjFields > 0) { + int objHandle = passHandle; + ObjectStreamField[] fields = desc.getFields(false); + int numPrimFields = fields.length - objValues.length; + for (int i = 0; i < objValues.length; i++) { + ObjectStreamField f = fields[numPrimFields + i]; + objValues[i] = readObject0(Object.class, f.isUnshared()); + objHandles[i] = passHandle; + if (recordDependencies && f.getField() != null) { + handles.markDependency(objHandle, passHandle); + } } + passHandle = objHandle; } - passHandle = objHandle; + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); } } diff --git a/src/java.base/share/classes/java/io/ObjectOutputStream.java b/src/java.base/share/classes/java/io/ObjectOutputStream.java index 31413bcf8ed..2c69682fb21 100644 --- a/src/java.base/share/classes/java/io/ObjectOutputStream.java +++ b/src/java.base/share/classes/java/io/ObjectOutputStream.java @@ -36,6 +36,8 @@ import jdk.internal.access.JavaLangAccess; import jdk.internal.access.SharedSecrets; +import static java.io.ObjectInputStream.TRACE; + import static jdk.internal.util.ModifiedUtf.putChar; import static jdk.internal.util.ModifiedUtf.utfLen; @@ -130,6 +132,12 @@ * implemented by a class they can write and read their own state using all of * the methods of ObjectOutput and ObjectInput. It is the responsibility of * the objects to handle any versioning that occurs. + * Value classes implementing {@link Externalizable} cannot be serialized + * or deserialized, the value object is immutable and the state cannot be restored. + * Use {@link Serializable} {@code writeReplace} to delegate to another serializable + * object such as a record. + * + * Value objects cannot be {@code java.io.Externalizable}. * *

    Enum constants are serialized differently than ordinary serializable or * externalizable objects. The serialized form of an enum constant consists @@ -155,9 +163,36 @@ * defaultWriteObject and writeFields initially terminate any existing * block-data record. * + * *

    Records are serialized differently than ordinary serializable or externalizable * objects, see record serialization. * + * + *

    Value classes are {@linkplain Serializable} through the use of the serialization proxy pattern. + * The serialization protocol does not support a standard serialized form for value classes. + * The value class delegates to a serialization proxy by supplying an alternate + * record or object to be serialized instead of the value class. + * When the proxy is deserialized it re-constructs the value object and returns the value object. + * For example, + * {@snippet lang="java" : + * value class ZipCode implements Serializable { // @highlight substring="value class" + * private static final long serialVersionUID = 1L; + * private int zipCode; + * public ZipCode(int zip) { this.zipCode = zip; } + * public int zipCode() { return zipCode; } + * + * public Object writeReplace() { // @highlight substring="writeReplace" + * return new ZipCodeProxy(zipCode); + * } + * + * private record ZipCodeProxy(int zipCode) implements Serializable { + * public Object readResolve() { // @highlight substring="readResolve" + * return new ZipCode(zipCode); + * } + * } + * } + * } + * * @spec serialization/index.html Java Object Serialization Specification * @author Mike Warres * @author Roger Riggs @@ -304,6 +339,9 @@ public void useProtocolVersion(int version) throws IOException { * object are written transitively so that a complete equivalent graph of * objects can be reconstructed by an ObjectInputStream. * + *

    Serialization and deserialization of value classes is described in + * {@linkplain ObjectOutputStream##valueclass-serialization value class serialization}. + * *

    Exceptions are thrown for problems with the OutputStream and for * classes that should not be serialized. All exceptions are fatal to the * OutputStream, which is left in an indeterminate state, and it is up to @@ -1312,6 +1350,9 @@ private void writeOrdinaryObject(Object obj, if (desc.isRecord()) { writeRecordData(obj, desc); } else if (desc.isExternalizable() && !desc.isProxy()) { + if (desc.isValue()) + throw new InvalidClassException("Externalizable not valid for value class " + + desc.forClass().getName()); writeExternalData((Externalizable) obj); } else { writeSerialData(obj, desc); @@ -1360,10 +1401,10 @@ private void writeRecordData(Object obj, ObjectStreamClass desc) throws IOException { assert obj.getClass().isRecord(); - ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout(); - if (slots.length != 1) { + List slots = desc.getClassDataLayout(); + if (slots.size() != 1) { throw new InvalidClassException( - "expected a single record slot length, but found: " + slots.length); + "expected a single record slot length, but found: " + slots.size()); } defaultWriteFields(obj, desc); // #### seems unnecessary to use the accessors @@ -1376,9 +1417,9 @@ private void writeRecordData(Object obj, ObjectStreamClass desc) private void writeSerialData(Object obj, ObjectStreamClass desc) throws IOException { - ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout(); - for (int i = 0; i < slots.length; i++) { - ObjectStreamClass slotDesc = slots[i].desc; + List slots = desc.getClassDataLayout(); + for (int i = 0; i < slots.size(); i++) { + ObjectStreamClass slotDesc = slots.get(i).desc; if (slotDesc.hasWriteObjectMethod()) { PutFieldImpl oldPut = curPut; curPut = null; diff --git a/src/java.base/share/classes/java/io/ObjectStreamClass.java b/src/java.base/share/classes/java/io/ObjectStreamClass.java index 4fb9847a4e8..4b4f989a764 100644 --- a/src/java.base/share/classes/java/io/ObjectStreamClass.java +++ b/src/java.base/share/classes/java/io/ObjectStreamClass.java @@ -30,6 +30,7 @@ import java.lang.invoke.MethodType; import java.lang.reflect.Constructor; import java.lang.reflect.Field; +import java.lang.reflect.InaccessibleObjectException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.RecordComponent; import java.lang.reflect.Member; @@ -43,14 +44,20 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; +import jdk.internal.MigratedValueClass; import jdk.internal.event.SerializationMisdeclarationEvent; import jdk.internal.misc.Unsafe; import jdk.internal.reflect.ReflectionFactory; import jdk.internal.util.ByteArray; +import jdk.internal.value.DeserializeConstructor; + +import static java.io.ObjectInputStream.TRACE; /** * Serialization's descriptor for classes. It contains the name and @@ -59,7 +66,7 @@ * *

    The algorithm to compute the SerialVersionUID is described in * - * Java Object Serialization Specification, Section 4.6, "Stream Unique Identifiers". + * Java Object Serialization Specification, Section 4.6, "Stream Unique Identifiers". * * @spec serialization/index.html Java Object Serialization Specification * @author Mike Warres @@ -84,6 +91,71 @@ public final class ObjectStreamClass implements Serializable { private static final ObjectStreamField[] serialPersistentFields = NO_FIELDS; + /** + * The mode of deserialization for a class depending on its type and interfaces. + * The markers used are {@linkplain java.io.Serializable}, {@linkplain java.io.Externalizable}, + * Class.isRecord(), Class.isValue(), constructors, and + * the presence of methods `readObject`, `writeObject`, `readObjectNoData`, `writeObject`. + * ObjectInputStream dispatches on the mode to construct objects from the stream. + */ + enum DeserializationMode { + /** + * Construct an object from the stream for a class that has only default read object behaviors. + * All classes and superclasses use defaultReadObject; no custom readObject or readObjectNoData. + * The new instance is entered in the handle table if it is unshared, + * allowing it to escape before it is initialized. + * For each object, all the fields are read before any are assigned. + * The `readObject` and `readObjectNoData` methods are not present and are not called. + */ + READ_OBJECT_DEFAULT, + /** + * Creates a new object and invokes its readExternal method to read its contents. + * If the class is instantiable, read externalizable data by invoking readExternal() + * method of obj; otherwise, attempts to skip over externalizable data. + * Expects that passHandle is set to obj's handle before this method is + * called. The new object is entered in the handle table immediately, + * allowing it to leak before it is completely read. + */ + READ_EXTERNALIZABLE, + /** + * Read all the record fields and invoke its canonical constructor. + * Construct the record using its canonical constructor. + * The new record is entered in the handle table only after the constructor returns. + */ + READ_RECORD, + /** + * Fully custom read from the stream to create an instance. + * If the class is not instantiatable or is tagged with ClassNotFoundException + * the data in the stream for the class is read and discarded. {@link #READ_NO_LOCAL_CLASS} + * The instance is created and set in the handle table, allowing it to leak before it is initialized. + * For each serializable class in the stream, from superclass to subclass the + * stream values are read by the `readObject` method, if present, or defaultReadObject. + * Custom inline data is discarded if not consumed by the class `readObject` method. + */ + READ_OBJECT_CUSTOM, + /** + * Construct an object by reading the values of all fields and + * invoking a constructor or static factory method. + * The constructor or static factory method is selected by matching its parameters with the + * sequence of field types of the serializable fields of the local class and superclasses. + * Invoke the constructor with all the values from the stream, inserting + * defaults and dropping extra values as necessary. + * This is very similar to the reading of records, except for the identification of + * the constructor or static factory. + */ + READ_OBJECT_VALUE, + /** + * Read and discard an entire object, leaving a null reference in the HandleTable. + * The descriptor of the class in the stream is used to read the fields from the stream. + * There is no instance in which to store the field values. + * Custom data following the fields of any slot is read and discarded. + * References to nested objects are read and retained in the + * handle table using the regular mechanism. + * Handles later in the stream may refer to the nested objects. + */ + READ_NO_LOCAL_CLASS, + } + private static class Caches { /** cache mapping local classes -> descriptors */ static final ClassCache localDescs = @@ -117,6 +189,10 @@ protected Map computeValue(Class type) { private boolean isEnum; /** true if represents record type */ private boolean isRecord; + /** true if represents a value class */ + private boolean isValue; + /** The DeserializationMode for this class. */ + private DeserializationMode factoryMode; /** true if represented class implements Serializable */ private boolean serializable; /** true if represented class implements Externalizable */ @@ -174,7 +250,7 @@ InvalidClassException newInvalidClassException() { /** reflector for setting/getting serializable field values */ private FieldReflector fieldRefl; /** data layout of serialized objects described by this class desc */ - private volatile ClassDataSlot[] dataLayout; + private volatile List dataLayout; /** serialization-appropriate constructor, or null if none */ private Constructor cons; @@ -338,6 +414,7 @@ private ObjectStreamClass(final Class cl) { isProxy = Proxy.isProxyClass(cl); isEnum = Enum.class.isAssignableFrom(cl); isRecord = cl.isRecord(); + isValue = cl.isValue(); serializable = Serializable.class.isAssignableFrom(cl); externalizable = Externalizable.class.isAssignableFrom(cl); @@ -363,10 +440,34 @@ private ObjectStreamClass(final Class cl) { } if (isRecord) { + factoryMode = DeserializationMode.READ_RECORD; canonicalCtr = canonicalRecordCtr(cl); deserializationCtrs = new DeserializationConstructorsCache(); } else if (externalizable) { - cons = getExternalizableConstructor(cl); + factoryMode = DeserializationMode.READ_EXTERNALIZABLE; + if (cl.isIdentity()) { + cons = getExternalizableConstructor(cl); + } else { + serializeEx = deserializeEx = new ExceptionInfo(cl.getName(), + "Externalizable not valid for value class"); + } + } else if (cl.isValue()) { + factoryMode = DeserializationMode.READ_OBJECT_VALUE; + if (!cl.isAnnotationPresent(MigratedValueClass.class)) { + serializeEx = deserializeEx = new ExceptionInfo(cl.getName(), + "Value class serialization is only supported with `writeReplace`"); + } else if (Modifier.isAbstract(cl.getModifiers())) { + serializeEx = deserializeEx = new ExceptionInfo(cl.getName(), + "value class is abstract"); + } else { + // Value classes should have constructor(s) annotated with {@link DeserializeConstructor} + canonicalCtr = getDeserializingValueCons(cl, fields); + deserializationCtrs = new DeserializationConstructorsCache(); factoryMode = DeserializationMode.READ_OBJECT_VALUE; + if (canonicalCtr == null) { + serializeEx = deserializeEx = new ExceptionInfo(cl.getName(), + "no constructor or factory found for migrated value class"); + } + } } else { cons = getSerializableConstructor(cl); writeObjectMethod = getPrivateMethod(cl, "writeObject", @@ -378,6 +479,10 @@ private ObjectStreamClass(final Class cl) { readObjectNoDataMethod = getPrivateMethod( cl, "readObjectNoData", null, Void.TYPE); hasWriteObjectData = (writeObjectMethod != null); + factoryMode = ((superDesc == null || superDesc.factoryMode() == DeserializationMode.READ_OBJECT_DEFAULT) + && readObjectMethod == null && readObjectNoDataMethod == null) + ? DeserializationMode.READ_OBJECT_DEFAULT + : DeserializationMode.READ_OBJECT_CUSTOM; } writeReplaceMethod = getInheritableMethod( cl, "writeReplace", null, Object.class); @@ -399,7 +504,7 @@ private ObjectStreamClass(final Class cl) { if (deserializeEx == null) { if (isEnum) { deserializeEx = new ExceptionInfo(name, "enum type"); - } else if (cons == null && !isRecord) { + } else if (cons == null && !(isRecord | isValue)) { deserializeEx = new ExceptionInfo(name, "no valid constructor"); } } @@ -458,6 +563,9 @@ void initProxy(Class cl, readResolveMethod = localDesc.readResolveMethod; deserializeEx = localDesc.deserializeEx; cons = localDesc.cons; + factoryMode = localDesc.factoryMode; + } else { + factoryMode = DeserializationMode.READ_OBJECT_DEFAULT; } fieldRefl = getReflector(fields, localDesc); initialized = true; @@ -536,6 +644,7 @@ void initNonProxy(ObjectStreamClass model, if (osc != null) { localDesc = osc; isRecord = localDesc.isRecord; + isValue = localDesc.isValue; // canonical record constructor is shared canonicalCtr = localDesc.canonicalCtr; // cache of deserialization constructors is shared @@ -550,6 +659,12 @@ void initNonProxy(ObjectStreamClass model, } assert cl.isRecord() ? localDesc.cons == null : true; cons = localDesc.cons; + factoryMode = localDesc.factoryMode; + } else { + // No local class, read data using only the schema from the stream + factoryMode = (externalizable) + ? DeserializationMode.READ_EXTERNALIZABLE + : DeserializationMode.READ_NO_LOCAL_CLASS; } fieldRefl = getReflector(fields, localDesc); @@ -605,7 +720,7 @@ void readNonProxy(ObjectInputStream in) String signature = ((tcode == 'L') || (tcode == '[')) ? in.readTypeString() : String.valueOf(tcode); try { - fields[i] = new ObjectStreamField(fname, signature, false); + fields[i] = new ObjectStreamField(fname, signature, false, -1); } catch (RuntimeException e) { throw new InvalidClassException(name, "invalid descriptor for field " + @@ -816,6 +931,22 @@ boolean isSerializable() { return serializable; } + /** + * {@return {code true} if the class is a value class, {@code false} otherwise} + */ + boolean isValue() { + requireInitialized(); + return isValue; + } + + /** + * {@return the factory mode for deserialization} + */ + DeserializationMode factoryMode() { + requireInitialized(); + return factoryMode; + } + /** * Returns true if class descriptor represents externalizable class that * has written its data in 1.2 (block data) format, false otherwise. @@ -838,13 +969,15 @@ boolean hasWriteObjectData() { /** * Returns true if represented class is serializable/externalizable and can * be instantiated by the serialization runtime--i.e., if it is - * externalizable and defines a public no-arg constructor, or if it is + * externalizable and defines a public no-arg constructor, if it is * non-externalizable and its first non-serializable superclass defines an - * accessible no-arg constructor. Otherwise, returns false. + * accessible no-arg constructor, or if the class is a value class with a @DeserializeConstructor + * constructor or static factory. + * Otherwise, returns false. */ boolean isInstantiable() { requireInitialized(); - return (cons != null); + return (cons != null || (isValue() && canonicalCtr != null)); } /** @@ -1101,23 +1234,18 @@ static class ClassDataSlot { } /** - * Returns array of ClassDataSlot instances representing the data layout + * Returns a List of ClassDataSlot instances representing the data layout * (including superclass data) for serialized objects described by this * class descriptor. ClassDataSlots are ordered by inheritance with those * containing "higher" superclasses appearing first. The final * ClassDataSlot contains a reference to this descriptor. */ - ClassDataSlot[] getClassDataLayout() throws InvalidClassException { + List getClassDataLayout() throws InvalidClassException { // REMIND: synchronize instead of relying on volatile? - if (dataLayout == null) { - dataLayout = getClassDataLayout0(); - } - return dataLayout; - } + List layout = dataLayout; + if (layout != null) + return layout; - private ClassDataSlot[] getClassDataLayout0() - throws InvalidClassException - { ArrayList slots = new ArrayList<>(); Class start = cl, end = cl; @@ -1166,7 +1294,8 @@ private ClassDataSlot[] getClassDataLayout0() // order slots from superclass -> subclass Collections.reverse(slots); - return slots.toArray(new ClassDataSlot[slots.size()]); + dataLayout = slots; + return slots; } /** @@ -1294,6 +1423,66 @@ private ObjectStreamClass getVariantFor(Class cl) return desc; } + /** + * Return a method handle for the static method or constructor(s) that matches the + * serializable fields and annotated with {@link DeserializeConstructor}. + * The descriptor for the class is still being initialized, so is passed the fields needed. + * @param clazz The class to query + * @param fields the serializable fields of the class + * @return a MethodHandle, null if none found + */ + @SuppressWarnings("unchecked") + private static MethodHandle getDeserializingValueCons(Class clazz, + ObjectStreamField[] fields) { + // Search for annotated static factory in methods or constructors + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodHandle mh = Stream.concat( + Arrays.stream(clazz.getDeclaredMethods()).filter(m -> Modifier.isStatic(m.getModifiers())), + Arrays.stream(clazz.getDeclaredConstructors())) + .filter(m -> m.isAnnotationPresent(DeserializeConstructor.class)) + .map(m -> { + try { + m.setAccessible(true); + return (m instanceof Constructor cons) + ? lookup.unreflectConstructor(cons) + : lookup.unreflect(((Method) m)); + } catch (IllegalAccessException iae) { + throw new InternalError(iae); // should not occur after setAccessible + }}) + .filter(m -> matchFactoryParamTypes(clazz, m, fields)) + .findFirst().orElse(null); + TRACE("DeserializeConstructor for %s, mh: %s", clazz, mh); + return mh; + } + + /** + * Check that the parameters of the factory method match the fields of this class. + * + * @param mh a MethodHandle for a constructor or factory + * @return true if all fields match the parameters, false if not + */ + private static boolean matchFactoryParamTypes(Class clazz, + MethodHandle mh, + ObjectStreamField[] fields) { + TRACE(" matchFactoryParams checking class: %s, mh: %s", clazz, mh); + var params = mh.type().parameterList(); + if (params.size() != fields.length) { + TRACE(" matchFactoryParams %s, arg count mismatch %d params != %d fields", + clazz, params.size(), fields.length); + return false; // Mismatch in count of fields and parameters + } + for (ObjectStreamField field : fields) { + int argIndex = field.getArgIndex(); + final Class paramtype = params.get(argIndex); + if (!field.getType().equals(paramtype)) { + TRACE(" matchFactoryParams %s: argIndex: %d type mismatch field: %s != param: %s", + clazz, argIndex, field.getType(), paramtype); + return false; + } + } + return true; + } + /** * Returns public no-arg constructor of given class, or null if none found. * Access checks are disabled on the returned constructor (if any), since @@ -1305,7 +1494,7 @@ private static Constructor getExternalizableConstructor(Class cl) { cons.setAccessible(true); return ((cons.getModifiers() & Modifier.PUBLIC) != 0) ? cons : null; - } catch (NoSuchMethodException ex) { + } catch (NoSuchMethodException | InaccessibleObjectException ex) { return null; } } @@ -1536,14 +1725,12 @@ private static ObjectStreamField[] getDeclaredSerialFields(Class cl) if ((f.getType() == spf.getType()) && ((f.getModifiers() & Modifier.STATIC) == 0)) { - boundFields[i] = - new ObjectStreamField(f, spf.isUnshared(), true); + boundFields[i] = new ObjectStreamField(f, spf.isUnshared(), true, i); } } catch (NoSuchFieldException ex) { } if (boundFields[i] == null) { - boundFields[i] = new ObjectStreamField( - fname, spf.getType(), spf.isUnshared()); + boundFields[i] = new ObjectStreamField(fname, spf.getType(), spf.isUnshared(), i); } } return boundFields; @@ -1560,9 +1747,9 @@ private static ObjectStreamField[] getDefaultSerialFields(Class cl) { ArrayList list = new ArrayList<>(); int mask = Modifier.STATIC | Modifier.TRANSIENT; - for (int i = 0; i < clFields.length; i++) { + for (int i = 0, argIndex = 0; i < clFields.length; i++) { if ((clFields[i].getModifiers() & mask) == 0) { - list.add(new ObjectStreamField(clFields[i], false, true)); + list.add(new ObjectStreamField(clFields[i], false, true, argIndex++)); } } int size = list.size(); @@ -1914,8 +2101,12 @@ void getObjFieldValues(Object obj, Object[] vals) { * in array should be equal to Unsafe.INVALID_FIELD_OFFSET. */ for (int i = numPrimFields; i < fields.length; i++) { + Field f = fields[i].getField(); vals[offsets[i]] = switch (typeCodes[i]) { - case 'L', '[' -> UNSAFE.getReference(obj, readKeys[i]); + case 'L', '[' -> + UNSAFE.isFlatField(f) + ? UNSAFE.getFlatValue(obj, readKeys[i], UNSAFE.fieldLayout(f), f.getType()) + : UNSAFE.getReference(obj, readKeys[i]); default -> throw new InternalError(); }; } @@ -1942,7 +2133,7 @@ void setObjFieldValues(Object obj, Object[] vals) { } private void setObjFieldValues(Object obj, Object[] vals, boolean dryRun) { - if (obj == null) { + if (obj == null && !dryRun) { throw new NullPointerException(); } for (int i = numPrimFields; i < fields.length; i++) { @@ -1952,11 +2143,11 @@ private void setObjFieldValues(Object obj, Object[] vals, boolean dryRun) { } switch (typeCodes[i]) { case 'L', '[' -> { + Field f = fields[i].getField(); Object val = vals[offsets[i]]; if (val != null && !types[i - numPrimFields].isInstance(val)) { - Field f = fields[i].getField(); throw new ClassCastException( "cannot assign instance of " + val.getClass().getName() + " to field " + @@ -1965,8 +2156,13 @@ private void setObjFieldValues(Object obj, Object[] vals, boolean dryRun) { f.getType().getName() + " in instance of " + obj.getClass().getName()); } - if (!dryRun) - UNSAFE.putReference(obj, key, val); + if (!dryRun) { + if (UNSAFE.isFlatField(f)) { + UNSAFE.putFlatValue(obj, key, UNSAFE.fieldLayout(f), f.getType(), val); + } else { + UNSAFE.putReference(obj, key, val); + } + } } default -> throw new InternalError(); } @@ -2078,16 +2274,16 @@ private static ObjectStreamField[] matchFields(ObjectStreamField[] fields, } if (lf.getField() != null) { m = new ObjectStreamField( - lf.getField(), lf.isUnshared(), false); + lf.getField(), lf.isUnshared(), true, lf.getArgIndex()); // Don't hide type } else { m = new ObjectStreamField( - lf.getName(), lf.getSignature(), lf.isUnshared()); + lf.getName(), lf.getSignature(), lf.isUnshared(), lf.getArgIndex()); } } } if (m == null) { m = new ObjectStreamField( - f.getName(), f.getSignature(), false); + f.getName(), f.getSignature(), false, -1); } m.setOffset(f.getOffset()); matches[i] = m; @@ -2209,7 +2405,7 @@ static final class Impl extends Key { } /** Record specific support for retrieving and binding stream field values. */ - static final class RecordSupport { + static final class ConstructorSupport { /** * Returns canonical record constructor adapted to take two arguments: * {@code (byte[] primValues, Object[] objValues)} @@ -2258,6 +2454,65 @@ static MethodHandle deserializationCtr(ObjectStreamClass desc) { desc.deserializationCtrs.putIfAbsentAndGet(desc.getFields(false), mh); } + /** + * Returns value object constructor adapted to take two arguments: + * {@code (byte[] primValues, Object[] objValues)} and return {@code Object} + */ + static MethodHandle deserializationValueCons(ObjectStreamClass desc) { + // check the cached value 1st + MethodHandle mh = desc.deserializationCtr; + if (mh != null) return mh; + mh = desc.deserializationCtrs.get(desc.getFields(false)); + if (mh != null) return desc.deserializationCtr = mh; + + // retrieve the selected constructor + // (T1, T2, ..., Tn):TR + ObjectStreamClass localDesc = desc.localDesc; + mh = localDesc.canonicalCtr; + MethodType mt = mh.type(); + + // change return type to Object + // (T1, T2, ..., Tn):TR -> (T1, T2, ..., Tn):Object + mh = mh.asType(mh.type().changeReturnType(Object.class)); + + // drop last 2 arguments representing primValues and objValues arrays + // (T1, T2, ..., Tn):Object -> (T1, T2, ..., Tn, byte[], Object[]):Object + mh = MethodHandles.dropArguments(mh, mh.type().parameterCount(), byte[].class, Object[].class); + + Class[] params = mt.parameterArray(); + for (int i = params.length-1; i >= 0; i--) { + // Get the name from the local descriptor matching the argIndex + var field = getFieldForArgIndex(localDesc, i); + String name = (field == null) ? "" : field.getName(); // empty string to supply default + Class type = params[i]; + // obtain stream field extractor that extracts argument at + // position i (Ti+1) from primValues and objValues arrays + // (byte[], Object[]):Ti+1 + MethodHandle combiner = streamFieldExtractor(name, type, desc); + // fold byte[] privValues and Object[] objValues into argument at position i (Ti+1) + // (..., Ti, Ti+1, byte[], Object[]):Object -> (..., Ti, byte[], Object[]):Object + mh = MethodHandles.foldArguments(mh, i, combiner); + } + // what we are left with is a MethodHandle taking just the primValues + // and objValues arrays and returning the constructed instance + // (byte[], Object[]):Object + + // store it into cache and return the 1st value stored + return desc.deserializationCtr = + desc.deserializationCtrs.putIfAbsentAndGet(desc.getFields(false), mh); + } + + // Find the ObjectStreamField for the argument index, otherwise null + private static ObjectStreamField getFieldForArgIndex(ObjectStreamClass desc, int argIndex) { + for (var field : desc.fields) { + if (field.getArgIndex() == argIndex) + return field; + } + TRACE("field for ArgIndex is null: %s, index: %d, fields: %s", + desc, argIndex, Arrays.toString(desc.fields)); + return null; + } + /** Returns the number of primitive fields for the given descriptor. */ private static int numberPrimValues(ObjectStreamClass desc) { ObjectStreamField[] fields = desc.getFields(); diff --git a/src/java.base/share/classes/java/io/ObjectStreamField.java b/src/java.base/share/classes/java/io/ObjectStreamField.java index 465c29c101c..962758054b8 100644 --- a/src/java.base/share/classes/java/io/ObjectStreamField.java +++ b/src/java.base/share/classes/java/io/ObjectStreamField.java @@ -54,6 +54,8 @@ public class ObjectStreamField private final Field field; /** offset of field value in enclosing field group */ private int offset; + /** index of the field in the class, retain the declaration order of serializable fields */ + private final int argIndex; /** * Create a Serializable field with the specified type. This field should @@ -84,6 +86,11 @@ public ObjectStreamField(String name, Class type) { * @since 1.4 */ public ObjectStreamField(String name, Class type, boolean unshared) { + this(name, type, unshared, -1); + } + + /* package-private */ + ObjectStreamField(String name, Class type, boolean unshared, int argIndex) { if (name == null) { throw new NullPointerException(); } @@ -92,13 +99,14 @@ public ObjectStreamField(String name, Class type, boolean unshared) { this.unshared = unshared; this.field = null; this.signature = null; + this.argIndex = argIndex; } /** * Creates an ObjectStreamField representing a field with the given name, * signature and unshared setting. */ - ObjectStreamField(String name, String signature, boolean unshared) { + ObjectStreamField(String name, String signature, boolean unshared, int argIndex) { if (name == null) { throw new NullPointerException(); } @@ -106,6 +114,7 @@ public ObjectStreamField(String name, Class type, boolean unshared) { this.signature = signature.intern(); this.unshared = unshared; this.field = null; + this.argIndex = argIndex; type = switch (signature.charAt(0)) { case 'Z' -> Boolean.TYPE; @@ -129,13 +138,14 @@ public ObjectStreamField(String name, Class type, boolean unshared) { * ObjectStreamField (if non-primitive) will return Object.class (as * opposed to a more specific reference type). */ - ObjectStreamField(Field field, boolean unshared, boolean showType) { + ObjectStreamField(Field field, boolean unshared, boolean showType, int argIndex) { this.field = field; this.unshared = unshared; name = field.getName(); Class ftype = field.getType(); type = (showType || ftype.isPrimitive()) ? ftype : Object.class; signature = ftype.descriptorString().intern(); + this.argIndex = argIndex; } /** @@ -216,6 +226,13 @@ protected void setOffset(int offset) { this.offset = offset; } + /** + * {@return Index of the field in the sequence of Serializable fields} + */ + int getArgIndex() { + return argIndex; + } + /** * Return true if this field has a primitive type. * diff --git a/src/java.base/share/classes/java/io/Serializable.java b/src/java.base/share/classes/java/io/Serializable.java index 53b427ef8e9..b348a000878 100644 --- a/src/java.base/share/classes/java/io/Serializable.java +++ b/src/java.base/share/classes/java/io/Serializable.java @@ -143,7 +143,16 @@ * by the * Java Object Serialization Specification, Section 1.13, * "Serialization of Records". Any declarations of the special - * handling methods discussed above are ignored for record types.

    + * handling methods discussed above, except {@code writeReplace}, + * are ignored for record types.

    + * + * Value classes can implement {@code Serializble} and receive the treatment defined + * by the + * Java Object Serialization Specification, Section 1.14, + * "Serialization of Value Objects". Any declarations of the special + * handling methods discussed above, except {@code writeReplace}, + * are ignored for value classes. Value classes implementing {@link Externalizable} + * and not using {@code writeReplace} are not supported.

    * * The serialization runtime associates with each serializable class a version * number, called a serialVersionUID, which is used during deserialization to diff --git a/src/java.base/share/classes/java/lang/Boolean.java b/src/java.base/share/classes/java/lang/Boolean.java index 4c24e98a549..9146df083d3 100644 --- a/src/java.base/share/classes/java/lang/Boolean.java +++ b/src/java.base/share/classes/java/lang/Boolean.java @@ -25,17 +25,14 @@ package java.lang; +import jdk.internal.value.DeserializeConstructor; import jdk.internal.vm.annotation.IntrinsicCandidate; import java.lang.constant.Constable; -import java.lang.constant.ConstantDesc; import java.lang.constant.ConstantDescs; import java.lang.constant.DynamicConstantDesc; import java.util.Optional; -import static java.lang.constant.ConstantDescs.BSM_GET_STATIC_FINAL; -import static java.lang.constant.ConstantDescs.CD_Boolean; - /** * The {@code Boolean} class is the {@linkplain * java.lang##wrapperClass wrapper class} for values of the primitive @@ -49,14 +46,23 @@ * {@code boolean}. * *

    This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code Boolean} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @author Arthur van Hoff * @since 1.0 */ +@jdk.internal.MigratedValueClass @jdk.internal.ValueBased public final class Boolean implements java.io.Serializable, Comparable, Constable @@ -174,6 +180,7 @@ public boolean booleanValue() { * @since 1.4 */ @IntrinsicCandidate + @DeserializeConstructor public static Boolean valueOf(boolean b) { return (b ? TRUE : FALSE); } diff --git a/src/java.base/share/classes/java/lang/Byte.java b/src/java.base/share/classes/java/lang/Byte.java index accd448a0cd..25725897380 100644 --- a/src/java.base/share/classes/java/lang/Byte.java +++ b/src/java.base/share/classes/java/lang/Byte.java @@ -26,6 +26,7 @@ package java.lang; import jdk.internal.misc.CDS; +import jdk.internal.value.DeserializeConstructor; import jdk.internal.vm.annotation.IntrinsicCandidate; import jdk.internal.vm.annotation.Stable; @@ -35,7 +36,6 @@ import static java.lang.constant.ConstantDescs.BSM_EXPLICIT_CAST; import static java.lang.constant.ConstantDescs.CD_byte; -import static java.lang.constant.ConstantDescs.CD_int; import static java.lang.constant.ConstantDescs.DEFAULT_NAME; /** @@ -50,16 +50,25 @@ * with a {@code byte}. * *

    This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code Byte} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @author Nakul Saraiya * @author Joseph D. Darcy * @see java.lang.Number * @since 1.1 */ +@jdk.internal.MigratedValueClass @jdk.internal.ValueBased public final class Byte extends Number implements Comparable, Constable { @@ -144,6 +153,7 @@ private ByteCache() {} * @since 1.5 */ @IntrinsicCandidate + @DeserializeConstructor public static Byte valueOf(byte b) { final int offset = 128; return ByteCache.cache[(int)b + offset]; diff --git a/src/java.base/share/classes/java/lang/Character.java b/src/java.base/share/classes/java/lang/Character.java index 72ff33651f9..493498bad13 100644 --- a/src/java.base/share/classes/java/lang/Character.java +++ b/src/java.base/share/classes/java/lang/Character.java @@ -26,6 +26,7 @@ package java.lang; import jdk.internal.misc.CDS; +import jdk.internal.value.DeserializeConstructor; import jdk.internal.vm.annotation.IntrinsicCandidate; import jdk.internal.vm.annotation.Stable; @@ -166,10 +167,18 @@ * Unicode Glossary. * *

    This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code Character} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @spec https://www.unicode.org/reports/tr44 Unicode Character Database * @author Lee Boynton @@ -179,9 +188,9 @@ * @author Ulf Zibis * @since 1.0 */ +@jdk.internal.MigratedValueClass @jdk.internal.ValueBased -public final -class Character implements java.io.Serializable, Comparable, Constable { +public final class Character implements java.io.Serializable, Comparable, Constable { /** * The minimum radix available for conversion to and from strings. * The constant value of this field is the smallest value permitted @@ -9277,6 +9286,7 @@ private CharacterCache(){} * @since 1.5 */ @IntrinsicCandidate + @DeserializeConstructor public static Character valueOf(char c) { if (c <= 127) { // must cache return CharacterCache.cache[(int)c]; diff --git a/src/java.base/share/classes/java/lang/Class.java b/src/java.base/share/classes/java/lang/Class.java index dfdf515f424..889b203e6ae 100644 --- a/src/java.base/share/classes/java/lang/Class.java +++ b/src/java.base/share/classes/java/lang/Class.java @@ -38,6 +38,7 @@ import java.lang.reflect.AnnotatedType; import java.lang.reflect.AccessFlag; import java.lang.reflect.Array; +import java.lang.reflect.ClassFileFormatVersion; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Field; @@ -59,6 +60,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -69,8 +71,10 @@ import java.util.stream.Collectors; import jdk.internal.constant.ConstantUtils; +import jdk.internal.javac.PreviewFeature; import jdk.internal.loader.BootLoader; import jdk.internal.loader.BuiltinClassLoader; +import jdk.internal.misc.PreviewFeatures; import jdk.internal.misc.Unsafe; import jdk.internal.module.Resources; import jdk.internal.reflect.CallerSensitive; @@ -219,9 +223,9 @@ public final class Class implements java.io.Serializable, AnnotatedElement, TypeDescriptor.OfField>, Constable { - private static final int ANNOTATION= 0x00002000; - private static final int ENUM = 0x00004000; - private static final int SYNTHETIC = 0x00001000; + private static final int ANNOTATION = 0x00002000; + private static final int ENUM = 0x00004000; + private static final int SYNTHETIC = 0x00001000; private static native void registerNatives(); static { @@ -320,6 +324,8 @@ public String toGenericString() { } else { // Class modifiers are a superset of interface modifiers int modifiers = getModifiers() & Modifier.classModifiers(); + // Modifier.toString() below mis-interprets SYNCHRONIZED, STRICT, and VOLATILE bits + modifiers &= ~(Modifier.SYNCHRONIZED | Modifier.STRICT | Modifier.VOLATILE); if (modifiers != 0) { sb.append(Modifier.toString(modifiers)); sb.append(' '); @@ -338,10 +344,15 @@ public String toGenericString() { } else { if (isEnum()) sb.append("enum"); - else if (isRecord()) - sb.append("record"); - else - sb.append("class"); + else { + if (isValue()) { + sb.append("value "); + } + if (isRecord()) + sb.append("record"); + else + sb.append("class"); + } } sb.append(' '); sb.append(getName()); @@ -607,6 +618,60 @@ public static Class forName(Module module, String name) { } } + /** + * {@return {@code true} if this {@code Class} object represents an identity class, + * otherwise {@code false}} + * + *
      + *
    • + * If this {@code Class} object represents an array type this method returns {@code true}. + *
    • + * If this {@code Class} object represents an interface, a primitive type, + * or {@code void} this method returns {@code false}. + *
    • + * For all other {@code Class} objects, this method returns {@code true} if either + * preview features are disabled or {@linkplain Modifier#IDENTITY} is set in the + * {@linkplain #getModifiers() class modifiers}. + *
    + * @see AccessFlag#IDENTITY + * @since Valhalla + */ + @PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS, reflective=true) + public boolean isIdentity() { + if (isPrimitive()) { + return false; + } else if (PreviewFeatures.isEnabled()) { + return isArray() || Modifier.isIdentity(modifiers); + } else { + return !isInterface(); + } + } + + /** + * {@return {@code true} if this {@code Class} object represents a value class, + * otherwise {@code false}} + *
      + *
    • + * If this {@code Class} object represents an array type this method returns {@code false}. + *
    • + * If this {@code Class} object represents an interface, a primitive type, + * or {@code void} this method returns {@code true} only if preview features are enabled. + *
    • + * For all other {@code Class} objects, this method returns {@code true} only if + * preview features are enabled and {@linkplain Modifier#IDENTITY} is not set in the + * {@linkplain #getModifiers() class modifiers}. + *
    + * @see AccessFlag#IDENTITY + * @since Valhalla + */ + @PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS, reflective=true) + public boolean isValue() { + if (!PreviewFeatures.isEnabled()) { + return false; + } + return !isIdentity(); + } + /** * {@return the {@code Class} object associated with the * {@linkplain #isPrimitive() primitive type} of the given name} @@ -1334,6 +1399,7 @@ private Class elementType() { * {@code true} *
  • its interface modifier is always {@code false}, even when * the component type is an interface + *
  • its {@code identity} modifier is always true * * If this {@code Class} object represents a primitive type or * void, its {@code public}, {@code abstract}, and {@code final} @@ -1358,9 +1424,10 @@ private Class elementType() { */ public int getModifiers() { return modifiers; } - /** + /** * {@return an unmodifiable set of the {@linkplain AccessFlag access * flags} for this class, possibly empty} + * The {@code AccessFlags} may depend on the class file format version of the class. * *

    If the underlying class is an array class: *

      @@ -1369,6 +1436,7 @@ private Class elementType() { *
    • its {@code ABSTRACT} and {@code FINAL} flags are present *
    • its {@code INTERFACE} flag is absent, even when the * component type is an interface + *
    • its {@code identity} modifier is always true *
    * If this {@code Class} object represents a primitive type or * void, the flags are {@code PUBLIC}, {@code ABSTRACT}, and @@ -1386,15 +1454,24 @@ public Set accessFlags() { // INNER_CLASS forbids. INNER_CLASS allows PRIVATE, PROTECTED, // and STATIC, which are not allowed on Location.CLASS. // Use getClassFileAccessFlags to expose SUPER status. + // Arrays need to use PRIVATE/PROTECTED from its component modifiers. var location = (isMemberClass() || isLocalClass() || isAnonymousClass() || isArray()) ? AccessFlag.Location.INNER_CLASS : AccessFlag.Location.CLASS; - return getReflectionFactory().parseAccessFlags((location == AccessFlag.Location.CLASS) ? - getClassFileAccessFlags() : getModifiers(), location, this); + int accessFlags = location == AccessFlag.Location.CLASS ? getClassFileAccessFlags() : getModifiers(); + var reflectionFactory = getReflectionFactory(); + var ans = reflectionFactory.parseAccessFlags(accessFlags, location, this); + if (PreviewFeatures.isEnabled() && reflectionFactory.classFileFormatVersion(this) != ClassFileFormatVersion.CURRENT_PREVIEW_FEATURES + && isIdentity()) { + var set = new HashSet<>(ans); + set.add(AccessFlag.IDENTITY); + return Set.copyOf(set); + } + return ans; } - /** + /** * Gets the signers of this class. * * @return the signers of this class, or null if there are no signers. In @@ -1402,6 +1479,7 @@ public Set accessFlags() { * a primitive type or void. * @since 1.1 */ + public Object[] getSigners() { var signers = this.signers; return signers == null ? null : signers.clone(); @@ -3789,7 +3867,7 @@ public AnnotatedType getAnnotatedSuperclass() { * @since 1.8 */ public AnnotatedType[] getAnnotatedInterfaces() { - return TypeAnnotationParser.buildAnnotatedInterfaces(getRawTypeAnnotations(), getConstantPool(), this); + return TypeAnnotationParser.buildAnnotatedInterfaces(getRawTypeAnnotations(), getConstantPool(), this); } private native Class getNestHost0(); @@ -4129,6 +4207,7 @@ public boolean isSealed() { * type is returned. If the class is a primitive type then the latest class * file major version is returned and zero is returned for the minor version. */ + /* package-private */ int getClassFileVersion() { Class c = isArray() ? elementType() : this; return c.getClassFileVersion0(); diff --git a/src/java.base/share/classes/java/lang/Double.java b/src/java.base/share/classes/java/lang/Double.java index 5ab6dce080b..27ab38d9d17 100644 --- a/src/java.base/share/classes/java/lang/Double.java +++ b/src/java.base/share/classes/java/lang/Double.java @@ -33,6 +33,7 @@ import jdk.internal.math.FloatingDecimal; import jdk.internal.math.DoubleConsts; import jdk.internal.math.DoubleToDecimal; +import jdk.internal.value.DeserializeConstructor; import jdk.internal.vm.annotation.IntrinsicCandidate; /** @@ -48,10 +49,18 @@ * {@code double}. * *

    This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code Double} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * *

    Floating-point Equality, Equivalence, * and Comparison

    @@ -357,6 +366,7 @@ * @author Joseph D. Darcy * @since 1.0 */ +@jdk.internal.MigratedValueClass @jdk.internal.ValueBased public final class Double extends Number implements Comparable, Constable, ConstantDesc { @@ -947,6 +957,7 @@ public static Double valueOf(String s) throws NumberFormatException { * @since 1.5 */ @IntrinsicCandidate + @DeserializeConstructor public static Double valueOf(double d) { return new Double(d); } diff --git a/src/java.base/share/classes/java/lang/Float.java b/src/java.base/share/classes/java/lang/Float.java index 4344d9657b4..5ce8c48e87d 100644 --- a/src/java.base/share/classes/java/lang/Float.java +++ b/src/java.base/share/classes/java/lang/Float.java @@ -33,6 +33,7 @@ import jdk.internal.math.FloatConsts; import jdk.internal.math.FloatingDecimal; import jdk.internal.math.FloatToDecimal; +import jdk.internal.value.DeserializeConstructor; import jdk.internal.vm.annotation.IntrinsicCandidate; /** @@ -48,10 +49,18 @@ * {@code float}. * *

    This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code Float} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * *

    Floating-point Equality, Equivalence, * and Comparison

    @@ -75,6 +84,7 @@ * @author Joseph D. Darcy * @since 1.0 */ +@jdk.internal.MigratedValueClass @jdk.internal.ValueBased public final class Float extends Number implements Comparable, Constable, ConstantDesc { @@ -574,6 +584,7 @@ public static Float valueOf(String s) throws NumberFormatException { * @since 1.5 */ @IntrinsicCandidate + @DeserializeConstructor public static Float valueOf(float f) { return new Float(f); } diff --git a/src/java.base/share/classes/java/lang/IdentityException.java b/src/java.base/share/classes/java/lang/IdentityException.java new file mode 100644 index 00000000000..e4b0fb026c1 --- /dev/null +++ b/src/java.base/share/classes/java/lang/IdentityException.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package java.lang; + + +import jdk.internal.javac.PreviewFeature; + +/** + * Thrown when an identity object is required but a value object is supplied. + *

    + * Identity objects are required for synchronization and locking. + * Value-based + * objects do not have identity and cannot be used for synchronization, locking, + * or any type of {@link java.lang.ref.Reference}. + * + * @since Valhalla + */ +@PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS) +public class IdentityException extends RuntimeException { + @java.io.Serial + private static final long serialVersionUID = 1L; + + /** + * Create an {@code IdentityException} with no message. + */ + public IdentityException() { + } + + /** + * Create an {@code IdentityException} with the class name and default message. + * + * @param clazz the class of the object + */ + public IdentityException(Class clazz) { + super(clazz.getName() + " is not an identity class"); + } + + /** + * Create an {@code IdentityException} with a message. + * + * @param message the detail message; can be {@code null} + */ + public IdentityException(String message) { + super(message); + } + + /** + * Create an {@code IdentityException} with a cause. + * + * @param cause the cause; {@code null} is permitted, and indicates + * that the cause is nonexistent or unknown. + */ + public IdentityException(Throwable cause) { + super(cause); + } + + /** + * Create an {@code IdentityException} with a message and cause. + * + * @param message the detail message; can be {@code null} + * @param cause the cause; {@code null} is permitted, and indicates + * that the cause is nonexistent or unknown. + */ + public IdentityException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/java.base/share/classes/java/lang/Integer.java b/src/java.base/share/classes/java/lang/Integer.java index 41487a469b6..d9cba6a7107 100644 --- a/src/java.base/share/classes/java/lang/Integer.java +++ b/src/java.base/share/classes/java/lang/Integer.java @@ -28,6 +28,7 @@ import jdk.internal.misc.CDS; import jdk.internal.misc.VM; import jdk.internal.util.DecimalDigits; +import jdk.internal.value.DeserializeConstructor; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.IntrinsicCandidate; import jdk.internal.vm.annotation.Stable; @@ -56,10 +57,18 @@ * dealing with an {@code int}. * *

    This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code Integer} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * *

    Implementation note: The implementations of the "bit twiddling" * methods (such as {@link #highestOneBit(int) highestOneBit} and @@ -74,6 +83,7 @@ * @author Joseph D. Darcy * @since 1.0 */ +@jdk.internal.MigratedValueClass @jdk.internal.ValueBased public final class Integer extends Number implements Comparable, Constable, ConstantDesc { @@ -1000,6 +1010,7 @@ private IntegerCache() {} * @since 1.5 */ @IntrinsicCandidate + @DeserializeConstructor public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; diff --git a/src/java.base/share/classes/java/lang/Long.java b/src/java.base/share/classes/java/lang/Long.java index 2fb2d18a78c..093e8916334 100644 --- a/src/java.base/share/classes/java/lang/Long.java +++ b/src/java.base/share/classes/java/lang/Long.java @@ -34,6 +34,7 @@ import java.util.Optional; import jdk.internal.misc.CDS; +import jdk.internal.value.DeserializeConstructor; import jdk.internal.util.DecimalDigits; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.IntrinsicCandidate; @@ -56,10 +57,19 @@ * with a {@code long}. * *

    This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code Long} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    + * * *

    Implementation note: The implementations of the "bit twiddling" * methods (such as {@link #highestOneBit(long) highestOneBit} and @@ -74,6 +84,7 @@ * @author Joseph D. Darcy * @since 1.0 */ +@jdk.internal.MigratedValueClass @jdk.internal.ValueBased public final class Long extends Number implements Comparable, Constable, ConstantDesc { @@ -992,6 +1003,7 @@ private LongCache() {} * @since 1.5 */ @IntrinsicCandidate + @DeserializeConstructor public static Long valueOf(long l) { final int offset = 128; if (l >= -128 && l <= 127) { // will cache diff --git a/src/java.base/share/classes/java/lang/Number.java b/src/java.base/share/classes/java/lang/Number.java index 94a4881c462..44a94c8673d 100644 --- a/src/java.base/share/classes/java/lang/Number.java +++ b/src/java.base/share/classes/java/lang/Number.java @@ -24,7 +24,6 @@ */ package java.lang; - /** * The abstract class {@code Number} is the superclass of platform * classes representing numeric values that are convertible to the @@ -46,12 +45,23 @@ * See the documentation of a given {@code Number} implementation for * conversion details. * + *

    + *
    + * When preview features are enabled, {@code Number} is + * an abstract {@linkplain Class#isValue value class}. + * Subclasses of {@code Number} can be either an {@linkplain Class#isIdentity identity class} + * or a {@linkplain Class#isValue value class}. + * See {@jls The Java Language Specification 8.1.1.5 Value Classes}. + *
    + *
    + * * @author Lee Boynton * @author Arthur van Hoff * @jls 5.1.2 Widening Primitive Conversion * @jls 5.1.3 Narrowing Primitive Conversion * @since 1.0 */ +@jdk.internal.MigratedValueClass public abstract class Number implements java.io.Serializable { /** * Constructor for subclasses to call. diff --git a/src/java.base/share/classes/java/lang/Object.java b/src/java.base/share/classes/java/lang/Object.java index 11dcab1b005..095047373f0 100644 --- a/src/java.base/share/classes/java/lang/Object.java +++ b/src/java.base/share/classes/java/lang/Object.java @@ -33,6 +33,17 @@ * Every class has {@code Object} as a superclass. All objects, * including arrays, implement the methods of this class. * + *
    + *
    + * When preview features are enabled, subclasses of {@code java.lang.Object} can be either + * an {@linkplain Class#isIdentity identity class} or a {@linkplain Class#isValue value class}. + * See {@jls The Java Language Specification 8.1.1.5 Value Classes}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    + * * @see java.lang.Class * @since 1.0 */ @@ -297,9 +308,16 @@ public String toString() { * *

    * Only one thread at a time can own an object's monitor. + *

    + *
    + * If this object is a {@linkplain Class#isValue() value object}, + * it does does not have a monitor, an {@code IllegalMonitorStateException} is thrown. + *
    + *
    * * @throws IllegalMonitorStateException if the current thread is not - * the owner of this object's monitor. + * the owner of this object's monitor or + * if this object is a {@linkplain Class#isValue() value object}. * @see java.lang.Object#notifyAll() * @see java.lang.Object#wait() */ @@ -323,8 +341,16 @@ public String toString() { * description of the ways in which a thread can become the owner of * a monitor. * + *
    + *
    + * If this object is a {@linkplain Class#isValue() value object}, + * it does does not have a monitor, an {@code IllegalMonitorStateException} is thrown. + *
    + *
    + * * @throws IllegalMonitorStateException if the current thread is not - * the owner of this object's monitor. + * the owner of this object's monitor or + * if this object is a {@linkplain Class#isValue() value object}. * @see java.lang.Object#notify() * @see java.lang.Object#wait() */ @@ -339,8 +365,16 @@ public String toString() { * had been called. See the specification of the {@link #wait(long, int)} method * for details. * + *
    + *
    + * If this object is a {@linkplain Class#isValue() value object}, + * it does does not have a monitor, an {@code IllegalMonitorStateException} is thrown. + *
    + *
    + * * @throws IllegalMonitorStateException if the current thread is not - * the owner of the object's monitor + * the owner of the object's monitor or + * if this object is a {@linkplain Class#isValue() value object}. * @throws InterruptedException if any thread interrupted the current thread before or * while the current thread was waiting. The interrupted status of the * current thread is cleared when this exception is thrown. @@ -362,10 +396,18 @@ public final void wait() throws InterruptedException { * had been called. See the specification of the {@link #wait(long, int)} method * for details. * + *
    + *
    + * If this object is a {@linkplain Class#isValue() value object}, + * it does does not have a monitor, an {@code IllegalMonitorStateException} is thrown. + *
    + *
    + * * @param timeoutMillis the maximum time to wait, in milliseconds * @throws IllegalArgumentException if {@code timeoutMillis} is negative * @throws IllegalMonitorStateException if the current thread is not - * the owner of the object's monitor + * the owner of the object's monitor or + * if this object is a {@linkplain Class#isValue() value object}. * @throws InterruptedException if any thread interrupted the current thread before or * while the current thread was waiting. The interrupted status of the * current thread is cleared when this exception is thrown. @@ -475,12 +517,19 @@ public final void wait(long timeoutMillis) throws InterruptedException { * } * } * + *
    + *
    + * If this object is a {@linkplain Class#isValue() value object}, + * it does does not have a monitor, an {@code IllegalMonitorStateException} is thrown. + *
    + *
    * @param timeoutMillis the maximum time to wait, in milliseconds * @param nanos additional time, in nanoseconds, in the range 0-999999 inclusive * @throws IllegalArgumentException if {@code timeoutMillis} is negative, * or if the value of {@code nanos} is out of range * @throws IllegalMonitorStateException if the current thread is not - * the owner of the object's monitor + * the owner of the object's monitor or + * if this object is a {@linkplain Class#isValue() value object}. * @throws InterruptedException if any thread interrupted the current thread before or * while the current thread was waiting. The interrupted status of the * current thread is cleared when this exception is thrown. diff --git a/src/java.base/share/classes/java/lang/ProcessHandleImpl.java b/src/java.base/share/classes/java/lang/ProcessHandleImpl.java index a7243b8b6f4..2105952522a 100644 --- a/src/java.base/share/classes/java/lang/ProcessHandleImpl.java +++ b/src/java.base/share/classes/java/lang/ProcessHandleImpl.java @@ -87,13 +87,17 @@ final class ProcessHandleImpl implements ProcessHandle { private static final Executor processReaperExecutor = initReaper(); private static Executor initReaper() { - // Initialize ThreadLocalRandom now to avoid using the smaller stack + // Initialize ThreadLocalRandom and ValueObjectMethods now to avoid using the smaller stack // of the processReaper threads. ThreadLocalRandom.current(); - + try { + Class.forName("java.lang.runtime.ValueObjectMethods$MethodHandleBuilder", true, null); + } catch (ClassNotFoundException cnfe) { + throw new InternalError("unable to initialize ValueObjectMethodds", cnfe); + } // For a debug build, the stack shadow zone is larger; // Increase the total stack size to avoid potential stack overflow. - int debugDelta = "release".equals(System.getProperty("jdk.debug")) ? 0 : (4 * 4096); + int debugDelta = "release".equals(System.getProperty("jdk.debug")) ? 0 : (4*4096); final long stackSize = Boolean.getBoolean("jdk.lang.processReaperUseDefaultStackSize") ? 0 : REAPER_DEFAULT_STACKSIZE + debugDelta; diff --git a/src/java.base/share/classes/java/lang/Record.java b/src/java.base/share/classes/java/lang/Record.java index 808bc7cc9cd..4076897e10c 100644 --- a/src/java.base/share/classes/java/lang/Record.java +++ b/src/java.base/share/classes/java/lang/Record.java @@ -67,6 +67,16 @@ * * then it must be the case that {@code r.equals(copy)}. * + *
    + *
    + * When preview features are enabled, {@code Record} is + * an abstract {@linkplain Class#isValue value class}. + * Subclasses of {@code Record} can be either an {@linkplain Class#isIdentity identity class} + * or a {@linkplain Class#isValue value class}. + * See {@jls The Java Language Specification 8.1.1.5 Value Classes}. + *
    + *
    + * * @apiNote * A record class that {@code implements} {@link java.io.Serializable} is said * to be a serializable record. Serializable records are serialized and @@ -87,6 +97,8 @@ * @jls 8.10 Record Classes * @since 16 */ +@jdk.internal.MigratedValueClass +@jdk.internal.ValueBased public abstract class Record { /** * Constructor for record classes to call. diff --git a/src/java.base/share/classes/java/lang/Short.java b/src/java.base/share/classes/java/lang/Short.java index f0ae8b28e45..8d3829d3202 100644 --- a/src/java.base/share/classes/java/lang/Short.java +++ b/src/java.base/share/classes/java/lang/Short.java @@ -26,6 +26,7 @@ package java.lang; import jdk.internal.misc.CDS; +import jdk.internal.value.DeserializeConstructor; import jdk.internal.vm.annotation.IntrinsicCandidate; import jdk.internal.vm.annotation.Stable; @@ -34,7 +35,6 @@ import java.util.Optional; import static java.lang.constant.ConstantDescs.BSM_EXPLICIT_CAST; -import static java.lang.constant.ConstantDescs.CD_int; import static java.lang.constant.ConstantDescs.CD_short; import static java.lang.constant.ConstantDescs.DEFAULT_NAME; @@ -50,16 +50,25 @@ * dealing with a {@code short}. * *

    This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code Short} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @author Nakul Saraiya * @author Joseph D. Darcy * @see java.lang.Number * @since 1.1 */ +@jdk.internal.MigratedValueClass @jdk.internal.ValueBased public final class Short extends Number implements Comparable, Constable { @@ -274,6 +283,7 @@ private ShortCache() {} * @since 1.5 */ @IntrinsicCandidate + @DeserializeConstructor public static Short valueOf(short s) { final int offset = 128; int sAsInt = s; diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java index a40c27bbf47..272abf80c2a 100644 --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -39,6 +39,7 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; import java.lang.module.ModuleDescriptor; +import java.lang.reflect.ClassFileFormatVersion; import java.lang.reflect.Executable; import java.lang.reflect.Method; import java.net.URI; @@ -487,6 +488,21 @@ public static native void arraycopy(Object src, int srcPos, * hashCode(). * The hash code for the null reference is zero. * + *
    + *
    + * The "identity hash code" of a {@linkplain Class#isValue() value object} + * is computed by combining the identity hash codes of the value object's fields recursively. + *
    + *
    + * @apiNote + *
    + *
    + * Note that, like ==, this hash code exposes information about a value object's + * private fields that might otherwise be hidden by an identity object. + * Developers should be cautious about storing sensitive secrets in value object fields. + *
    + *
    + * * @param x object for which the hashCode is to be calculated * @return the hashCode * @since 1.1 @@ -2318,6 +2334,10 @@ public StackWalker newStackWalkerInstance(Set options, return StackWalker.newInstance(options, null, contScope, continuation); } + public int classFileFormatVersion(Class clazz) { + return clazz.getClassFileVersion(); + } + public String getLoaderNameID(ClassLoader loader) { return loader != null ? loader.nameAndId() : "null"; } diff --git a/src/java.base/share/classes/java/lang/classfile/Attribute.java b/src/java.base/share/classes/java/lang/classfile/Attribute.java index 02b9332bb79..363967c5a90 100644 --- a/src/java.base/share/classes/java/lang/classfile/Attribute.java +++ b/src/java.base/share/classes/java/lang/classfile/Attribute.java @@ -65,7 +65,7 @@ public sealed interface Attribute> CharacterRangeTableAttribute, CodeAttribute, CompilationIDAttribute, ConstantValueAttribute, DeprecatedAttribute, EnclosingMethodAttribute, ExceptionsAttribute, InnerClassesAttribute, LineNumberTableAttribute, - LocalVariableTableAttribute, LocalVariableTypeTableAttribute, + LoadableDescriptorsAttribute, LocalVariableTableAttribute, LocalVariableTypeTableAttribute, MethodParametersAttribute, ModuleAttribute, ModuleHashesAttribute, ModuleMainClassAttribute, ModulePackagesAttribute, ModuleResolutionAttribute, ModuleTargetAttribute, NestHostAttribute, NestMembersAttribute, diff --git a/src/java.base/share/classes/java/lang/classfile/Attributes.java b/src/java.base/share/classes/java/lang/classfile/Attributes.java index eaa0fec5a4e..7daa4f10273 100644 --- a/src/java.base/share/classes/java/lang/classfile/Attributes.java +++ b/src/java.base/share/classes/java/lang/classfile/Attributes.java @@ -28,6 +28,7 @@ import java.lang.classfile.attribute.*; import jdk.internal.classfile.impl.AbstractAttributeMapper.*; +import jdk.internal.javac.PreviewFeature; /** * Attribute mappers for predefined (JVMS {@jvms 4.7}) and JDK-specific @@ -81,6 +82,9 @@ public final class Attributes { /** LineNumberTable */ public static final String NAME_LINE_NUMBER_TABLE = "LineNumberTable"; + /** LoadableDescriptors */ + public static final String NAME_LOADABLE_DESCRIPTORS = "LoadableDescriptors"; + /** LocalVariableTable */ public static final String NAME_LOCAL_VARIABLE_TABLE = "LocalVariableTable"; @@ -245,6 +249,15 @@ public static AttributeMapper lineNumberTable() { return LineNumberTableMapper.INSTANCE; } + /** + * {@return Attribute mapper for the {@code LoadableDescriptors} attribute} + * @since Valhalla + */ + @PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS) + public static AttributeMapper loadableDescriptors() { + return LoadableDescriptorsMapper.INSTANCE; + } + /** * {@return the mapper for the {@code LocalVariableTable} attribute} * The mapper permits multiple instances in a {@code Code} attribute. diff --git a/src/java.base/share/classes/java/lang/classfile/ClassElement.java b/src/java.base/share/classes/java/lang/classfile/ClassElement.java index 866d1bafcc4..139d20a17f1 100644 --- a/src/java.base/share/classes/java/lang/classfile/ClassElement.java +++ b/src/java.base/share/classes/java/lang/classfile/ClassElement.java @@ -50,6 +50,7 @@ public sealed interface ClassElement extends ClassFileElement FieldModel, MethodModel, CustomAttribute, CompilationIDAttribute, DeprecatedAttribute, EnclosingMethodAttribute, InnerClassesAttribute, + LoadableDescriptorsAttribute, ModuleAttribute, ModuleHashesAttribute, ModuleMainClassAttribute, ModulePackagesAttribute, ModuleResolutionAttribute, ModuleTargetAttribute, NestHostAttribute, NestMembersAttribute, PermittedSubclassesAttribute, diff --git a/src/java.base/share/classes/java/lang/classfile/ClassFile.java b/src/java.base/share/classes/java/lang/classfile/ClassFile.java index 216facbdddf..c2f1dd5b326 100644 --- a/src/java.base/share/classes/java/lang/classfile/ClassFile.java +++ b/src/java.base/share/classes/java/lang/classfile/ClassFile.java @@ -805,6 +805,9 @@ default List verify(Path path) throws IOException { /** The bit mask of {@link AccessFlag#SUPER} access and property modifier. */ int ACC_SUPER = 0x0020; + /** The bit mask of {@link AccessFlag#IDENTITY} access and property modifier. */ + int ACC_IDENTITY = 0x0020; + /** The bit mask of {@link AccessFlag#ABSTRACT} access and property modifier. */ int ACC_ABSTRACT = 0x0400; @@ -838,6 +841,9 @@ default List verify(Path path) throws IOException { /** The bit mask of {@link AccessFlag#STRICT} access and property modifier. */ int ACC_STRICT = 0x0800; + /** The bit mask of {@link AccessFlag#STRICT_INIT} access and property modifier. */ + int ACC_STRICT_INIT = 0x0800; + /** The bit mask of {@link AccessFlag#MODULE} access and property modifier. */ int ACC_MODULE = 0x8000; diff --git a/src/java.base/share/classes/java/lang/classfile/attribute/InnerClassInfo.java b/src/java.base/share/classes/java/lang/classfile/attribute/InnerClassInfo.java index df6b63ae692..b7a7c9e974e 100644 --- a/src/java.base/share/classes/java/lang/classfile/attribute/InnerClassInfo.java +++ b/src/java.base/share/classes/java/lang/classfile/attribute/InnerClassInfo.java @@ -28,6 +28,7 @@ import java.lang.classfile.constantpool.Utf8Entry; import java.lang.constant.ClassDesc; import java.lang.reflect.AccessFlag; +import java.lang.reflect.ClassFileFormatVersion; import java.util.Optional; import java.util.Set; @@ -84,7 +85,7 @@ public sealed interface InnerClassInfo * @see AccessFlag.Location#INNER_CLASS */ default Set flags() { - return AccessFlag.maskToAccessFlags(flagsMask(), AccessFlag.Location.INNER_CLASS); + return AccessFlag.maskToAccessFlags(flagsMask(), AccessFlag.Location.INNER_CLASS, ClassFileFormatVersion.CURRENT_PREVIEW_FEATURES); } /** diff --git a/src/java.base/share/classes/java/lang/classfile/attribute/LoadableDescriptorsAttribute.java b/src/java.base/share/classes/java/lang/classfile/attribute/LoadableDescriptorsAttribute.java new file mode 100644 index 00000000000..1c5493848a1 --- /dev/null +++ b/src/java.base/share/classes/java/lang/classfile/attribute/LoadableDescriptorsAttribute.java @@ -0,0 +1,94 @@ +/* + * 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 java.lang.classfile.attribute; + +import java.lang.classfile.AttributeMapper; +import java.lang.classfile.Attributes; +import java.lang.classfile.ClassFile; +import java.lang.classfile.constantpool.Utf8Entry; +import java.lang.constant.ClassDesc; +import java.util.Arrays; +import java.util.List; + +import java.lang.classfile.Attribute; +import java.lang.classfile.ClassElement; +import java.lang.classfile.constantpool.ClassEntry; + +import jdk.internal.classfile.impl.AbstractPoolEntry; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.UnboundAttribute; +import jdk.internal.classfile.impl.Util; +import jdk.internal.javac.PreviewFeature; + +/** + * Models the {@link Attributes#loadableDescriptors() LoadableDescriptors} + * attribute (JVMS {@jvms 4.7.32}), which suggests the JVM may load mentioned + * types before the {@code class} file carrying this attribute is loaded. + *

    + * This attribute only appears on classes, and does not permit {@linkplain + * AttributeMapper#allowMultiple multiple instances} in a class. It has a + * data dependency on the {@linkplain AttributeMapper.AttributeStability#CP_REFS + * constant pool}. + *

    + * The attribute was introduced in the Java SE Platform version XX, major + * version {@value ClassFile#JAVA_25_VERSION}. (FIXME) + * + * @see Attributes#loadableDescriptors() + * @jvms 4.7.32 The {@code LoadableDescriptors} Attribute + * @since Valhalla + */ +@PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS) +public sealed interface LoadableDescriptorsAttribute + extends Attribute, ClassElement + permits BoundAttribute.BoundLoadableDescriptorsAttribute, UnboundAttribute.UnboundLoadableDescriptorsAttribute { + + /** + * {@return the list of loadable descriptors} + */ + List loadableDescriptors(); + + /** + * {@return the list of loadable descriptors, as nominal descriptors} + */ + default List loadableDescriptorSymbols() { + return Util.mappedList(loadableDescriptors(), Util::fieldTypeSymbol); + } + + /** + * {@return a {@code LoadableDescriptors} attribute} + * @param loadableDescriptors the loadable descriptors + */ + static LoadableDescriptorsAttribute of(List loadableDescriptors) { + return new UnboundAttribute.UnboundLoadableDescriptorsAttribute(loadableDescriptors); + } + + /** + * {@return a {@code LoadableDescriptors} attribute} + * @param loadableDescriptors the loadable descriptors + */ + static LoadableDescriptorsAttribute of(Utf8Entry... loadableDescriptors) { + return of(List.of(loadableDescriptors)); + } +} diff --git a/src/java.base/share/classes/java/lang/classfile/attribute/StackMapFrameInfo.java b/src/java.base/share/classes/java/lang/classfile/attribute/StackMapFrameInfo.java index 06e9e6d585e..af9199c1b6a 100644 --- a/src/java.base/share/classes/java/lang/classfile/attribute/StackMapFrameInfo.java +++ b/src/java.base/share/classes/java/lang/classfile/attribute/StackMapFrameInfo.java @@ -30,6 +30,7 @@ import java.lang.classfile.Opcode; import java.lang.classfile.constantpool.ClassEntry; import java.lang.classfile.instruction.BranchInstruction; +import java.lang.classfile.constantpool.NameAndTypeEntry; import java.lang.constant.ClassDesc; import java.util.List; @@ -77,6 +78,13 @@ public sealed interface StackMapFrameInfo */ List stack(); + /** + * {@return the expanded unset fields} + * + * @see Specs + */ + List unsetFields(); + /** * {@return a new stack map frame} * @@ -89,7 +97,26 @@ public sealed interface StackMapFrameInfo public static StackMapFrameInfo of(Label target, List locals, List stack) { - return new StackMapDecoder.StackMapFrameImpl(255, target, locals, stack); + + return of(target, locals, stack, List.of()); + } + + /** + * {@return a new stack map frame} + * @param target the location of the frame + * @param locals the complete list of frame locals + * @param stack the complete frame stack + * @param unsetFields the complete list of unset fields + * @throws IllegalArgumentException if unset fields has entries but no + * {@link SimpleVerificationTypeInfo#UNINITIALIZED_THIS uninitializedThis} + * is present in {@code locals} + */ + public static StackMapFrameInfo of(Label target, + List locals, + List stack, + List unsetFields) { + + return new StackMapDecoder.StackMapFrameImpl(255, target, locals, stack, unsetFields); } /** diff --git a/src/java.base/share/classes/java/lang/classfile/package-info.java b/src/java.base/share/classes/java/lang/classfile/package-info.java index da9ad7fbf0d..92bd0877559 100644 --- a/src/java.base/share/classes/java/lang/classfile/package-info.java +++ b/src/java.base/share/classes/java/lang/classfile/package-info.java @@ -511,6 +511,7 @@ * | EnclosingMethodAttribute?(ClassEntry className, NameAndTypeEntry method) * | InnerClassesAttribute?(List classes) * | PermittedSubclassesAttribute?(List permittedSubclasses) + * | LoadableDescriptorsAttribute?(List loadableDescriptors) * | DeclarationElement* * } * diff --git a/src/java.base/share/classes/java/lang/doc-files/ValueBased.html b/src/java.base/share/classes/java/lang/doc-files/ValueBased.html index 6a935afe04b..1b55f034e29 100644 --- a/src/java.base/share/classes/java/lang/doc-files/ValueBased.html +++ b/src/java.base/share/classes/java/lang/doc-files/ValueBased.html @@ -30,43 +30,49 @@

    {@index "Value-based Classes"}

    -Some classes, such as java.lang.Integer and -java.time.LocalDate, are value-based. +Some classes, such as {@code java.lang.Integer} and +{@code java.time.LocalDate}, are value-based. +The compiler and runtime enforce the value based properties below if it is declared +as {@code value class} and preview features are enabled. A value-based class has the following properties:
    • the class declares only final instance fields (though these may contain references to mutable objects);
    • -
    • the class's implementations of equals, hashCode, - and toString compute their results solely from the values +
    • the class's implementations of {@code equals}, {@code hashCode}, + and {@code toString} compute their results solely from the values of the class's instance fields (and the members of the objects they reference), not from the instance's identity;
    • the class's methods treat instances as freely substitutable - when equal, meaning that interchanging any two instances x and - y that are equal according to equals() produces no + when equal, meaning that interchanging any two instances {@code x} and + {@code y} that are equal according to {@code equals()} produces no visible change in the behavior of the class's methods;
    • the class performs no synchronization using an instance's monitor;
    • -
    • the class does not declare (or has deprecated any) accessible constructors;
    • +
    • the class does not declare (or discourages use of) accessible constructors;
    • the class does not provide any instance creation mechanism that promises a unique identity on each method call—in particular, any factory method's contract must allow for the possibility that if two independently-produced - instances are equal according to equals(), they may also be - equal according to ==;
    • -
    • the class is final, and extends either Object or a hierarchy of - abstract classes that declare no instance fields or instance initializers - and whose constructors are empty.
    • + instances are equal according to {@code equals()}, they may also be + equal according to {@code ==}; +
    • the class is final, and extends either {@code Object} or a hierarchy of + abstract value classes.

    When two instances of a value-based class are equal (according to `equals`), a program should not attempt to distinguish between their identities, whether directly via reference - equality or indirectly via an appeal to synchronization, identity hashing, + equality {@code ==} or indirectly via an appeal to synchronization, identity hashing, serialization, or any other identity-sensitive mechanism.

    Synchronization on instances of value-based classes is strongly discouraged, because the programmer cannot guarantee exclusive ownership of the associated monitor.

    -

    Identity-related behavior of value-based classes may change in a future release. - For example, synchronization may fail.

    +

    Identity-related behavior of value-based classes may change when implemented as a value class. +

    +
      +
    • The class may choose to allocate/cache instances differently. +
    • The use of the value class for synchronization or with + {@linkplain java.lang.ref.Reference object references} result in {@link IdentityException}. +
    diff --git a/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java b/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java index c71e6499a5f..d473be64218 100644 --- a/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java +++ b/src/java.base/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -268,7 +268,7 @@ void validateMetafactoryArgs() throws LambdaConversionException { if (!implClass.isAssignableFrom(receiverClass)) { throw new LambdaConversionException( String.format("Invalid receiver type %s; not a subtype of implementation type %s", - receiverClass, implClass)); + receiverClass.descriptorString(), implClass.descriptorString())); } } else { // no receiver @@ -368,7 +368,7 @@ private boolean isAdaptableTo(Class fromType, Class toType, boolean strict return !strict; } } else { - // both are reference types: fromType should be a superclass of toType. + // fromType should be a superclass of toType return !strict || toType.isAssignableFrom(fromType); } } diff --git a/src/java.base/share/classes/java/lang/invoke/ArrayVarHandle.java b/src/java.base/share/classes/java/lang/invoke/ArrayVarHandle.java new file mode 100644 index 00000000000..835ddb6fa72 --- /dev/null +++ b/src/java.base/share/classes/java/lang/invoke/ArrayVarHandle.java @@ -0,0 +1,489 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. 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 java.lang.invoke; + +import java.util.Optional; + +import jdk.internal.misc.Unsafe; +import jdk.internal.util.Preconditions; +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.ForceInline; + +import static java.lang.invoke.MethodHandleStatics.UNSAFE; + +/// The var handle for polymorphic arrays. +final class ArrayVarHandle extends VarHandle { + static final int REFERENCE_BASE = Math.toIntExact(Unsafe.ARRAY_OBJECT_BASE_OFFSET); + static final int REFERENCE_SHIFT = Integer.numberOfTrailingZeros(Unsafe.ARRAY_OBJECT_INDEX_SCALE); + final Class arrayType; + final Class componentType; + + ArrayVarHandle(Class arrayType) { + this(arrayType, false); + } + + private ArrayVarHandle(Class arrayType, boolean exact) { + super(ArrayVarHandle.FORM, exact); + this.arrayType = arrayType; + this.componentType = arrayType.getComponentType(); + } + + @Override + public ArrayVarHandle withInvokeExactBehavior() { + return hasInvokeExactBehavior() + ? this + : new ArrayVarHandle(arrayType, true); + } + + @Override + public ArrayVarHandle withInvokeBehavior() { + return !hasInvokeExactBehavior() + ? this + : new ArrayVarHandle(arrayType, false); + } + + @Override + public Optional describeConstable() { + var arrayTypeRef = arrayType.describeConstable(); + if (arrayTypeRef.isEmpty()) + return Optional.empty(); + + return Optional.of(VarHandleDesc.ofArray(arrayTypeRef.get())); + } + + @Override + final MethodType accessModeTypeUncached(AccessType at) { + return at.accessModeType(arrayType, componentType, int.class); + } + + @ForceInline + static Object storeCheck(ArrayVarHandle handle, Object[] oarray, Object value) { + if (value == null && ValueClass.isNullRestrictedArray(oarray)) { + throw new NullPointerException("null not allowed for null-restricted array " + oarray.getClass().toGenericString()); + } + if (handle.arrayType == oarray.getClass()) { + // Fast path: static array type same as argument array type + return handle.componentType.cast(value); + } else { + // Slow path: check value against argument array component type + return reflectiveTypeCheck(oarray, value); + } + } + + @ForceInline + static Object reflectiveTypeCheck(Object[] oarray, Object value) { + try { + return oarray.getClass().getComponentType().cast(value); + } catch (ClassCastException e) { + throw new ArrayStoreException(); + } + } + + @ForceInline + static Object get(VarHandle ob, Object oarray, int index) { + ArrayVarHandle handle = (ArrayVarHandle) ob; + Object[] array = (Object[]) handle.arrayType.cast(oarray); + return array[index]; + } + + @ForceInline + static void set(VarHandle ob, Object oarray, int index, Object value) { + ArrayVarHandle handle = (ArrayVarHandle) ob; + Object[] array = (Object[]) handle.arrayType.cast(oarray); + array[index] = storeCheck(handle, array, value); + } + + @ForceInline + static Object getVolatile(VarHandle ob, Object oarray, int index) { + ArrayVarHandle handle = (ArrayVarHandle) ob; + Object[] array = (Object[]) handle.arrayType.cast(oarray); + Class arrayType = oarray.getClass(); + if (ValueClass.isFlatArray(oarray)) { + // delegate to flat access primitives + VarHandles.checkAtomicFlatArray(array); + int aoffset = (int) UNSAFE.arrayBaseOffset(array); + int ascale = UNSAFE.arrayIndexScale(array); + int ashift = 31 - Integer.numberOfLeadingZeros(ascale); + int layout = UNSAFE.arrayLayout(array); + return UNSAFE.getFlatValueVolatile(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << ashift) + aoffset, layout, arrayType.componentType()); + } + return UNSAFE.getReferenceVolatile(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << REFERENCE_SHIFT) + REFERENCE_BASE); + } + + @ForceInline + static void setVolatile(VarHandle ob, Object oarray, int index, Object value) { + ArrayVarHandle handle = (ArrayVarHandle) ob; + Object[] array = (Object[]) handle.arrayType.cast(oarray); + Class arrayType = oarray.getClass(); + if (ValueClass.isFlatArray(oarray)) { + // delegate to flat access primitives + VarHandles.checkAtomicFlatArray(array); + int aoffset = (int) UNSAFE.arrayBaseOffset(array); + int ascale = UNSAFE.arrayIndexScale(array); + int ashift = 31 - Integer.numberOfLeadingZeros(ascale); + int layout = UNSAFE.arrayLayout(array); + UNSAFE.putFlatValueVolatile(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << ashift) + aoffset, layout, arrayType.componentType(), + storeCheck(handle, array, value)); + return; + } + UNSAFE.putReferenceVolatile(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << REFERENCE_SHIFT) + REFERENCE_BASE, + storeCheck(handle, array, value)); + } + + @ForceInline + static Object getOpaque(VarHandle ob, Object oarray, int index) { + ArrayVarHandle handle = (ArrayVarHandle) ob; + Object[] array = (Object[]) handle.arrayType.cast(oarray); + Class arrayType = oarray.getClass(); + if (ValueClass.isFlatArray(oarray)) { + // delegate to flat access primitives + VarHandles.checkAtomicFlatArray(array); + int aoffset = (int) UNSAFE.arrayBaseOffset(array); + int ascale = UNSAFE.arrayIndexScale(array); + int ashift = 31 - Integer.numberOfLeadingZeros(ascale); + int layout = UNSAFE.arrayLayout(array); + return UNSAFE.getFlatValueOpaque(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << ashift) + aoffset, layout, arrayType.componentType()); + } + return UNSAFE.getReferenceOpaque(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << REFERENCE_SHIFT) + REFERENCE_BASE); + } + + @ForceInline + static void setOpaque(VarHandle ob, Object oarray, int index, Object value) { + ArrayVarHandle handle = (ArrayVarHandle) ob; + Object[] array = (Object[]) handle.arrayType.cast(oarray); + Class arrayType = oarray.getClass(); + if (ValueClass.isFlatArray(oarray)) { + // delegate to flat access primitives + VarHandles.checkAtomicFlatArray(array); + int aoffset = (int) UNSAFE.arrayBaseOffset(array); + int ascale = UNSAFE.arrayIndexScale(array); + int ashift = 31 - Integer.numberOfLeadingZeros(ascale); + int layout = UNSAFE.arrayLayout(array); + UNSAFE.putFlatValueOpaque(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << ashift) + aoffset, layout, arrayType.componentType(), + storeCheck(handle, array, value)); + return; + } + UNSAFE.putReferenceOpaque(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << REFERENCE_SHIFT) + REFERENCE_BASE, + storeCheck(handle, array, value)); + } + + @ForceInline + static Object getAcquire(VarHandle ob, Object oarray, int index) { + ArrayVarHandle handle = (ArrayVarHandle) ob; + Object[] array = (Object[]) handle.arrayType.cast(oarray); + Class arrayType = oarray.getClass(); + if (ValueClass.isFlatArray(oarray)) { + // delegate to flat access primitives + VarHandles.checkAtomicFlatArray(array); + int aoffset = (int) UNSAFE.arrayBaseOffset(array); + int ascale = UNSAFE.arrayIndexScale(array); + int ashift = 31 - Integer.numberOfLeadingZeros(ascale); + int layout = UNSAFE.arrayLayout(array); + return UNSAFE.getFlatValueAcquire(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << ashift) + aoffset, layout, arrayType.componentType()); + } + return UNSAFE.getReferenceAcquire(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << REFERENCE_SHIFT) + REFERENCE_BASE); + } + + @ForceInline + static void setRelease(VarHandle ob, Object oarray, int index, Object value) { + ArrayVarHandle handle = (ArrayVarHandle) ob; + Object[] array = (Object[]) handle.arrayType.cast(oarray); + Class arrayType = oarray.getClass(); + if (ValueClass.isFlatArray(oarray)) { + // delegate to flat access primitives + VarHandles.checkAtomicFlatArray(array); + int aoffset = (int) UNSAFE.arrayBaseOffset(array); + int ascale = UNSAFE.arrayIndexScale(array); + int ashift = 31 - Integer.numberOfLeadingZeros(ascale); + int layout = UNSAFE.arrayLayout(array); + UNSAFE.putFlatValueRelease(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << ashift) + aoffset, layout, arrayType.componentType(), + storeCheck(handle, array, value)); + return; + } + UNSAFE.putReferenceRelease(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << REFERENCE_SHIFT) + REFERENCE_BASE, + storeCheck(handle, array, value)); + } + + @ForceInline + static boolean compareAndSet(VarHandle ob, Object oarray, int index, Object expected, Object value) { + ArrayVarHandle handle = (ArrayVarHandle) ob; + Object[] array = (Object[]) handle.arrayType.cast(oarray); + Class arrayType = oarray.getClass(); + if (ValueClass.isFlatArray(oarray)) { + // delegate to flat access primitives + VarHandles.checkAtomicFlatArray(array); + int aoffset = (int) UNSAFE.arrayBaseOffset(array); + int ascale = UNSAFE.arrayIndexScale(array); + int ashift = 31 - Integer.numberOfLeadingZeros(ascale); + int layout = UNSAFE.arrayLayout(array); + return UNSAFE.compareAndSetFlatValue(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << ashift) + aoffset, layout, arrayType.componentType(), + arrayType.componentType().cast(expected), + storeCheck(handle, array, value)); + } + return UNSAFE.compareAndSetReference(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << REFERENCE_SHIFT) + REFERENCE_BASE, handle.componentType, + handle.componentType.cast(expected), + storeCheck(handle, array, value)); + } + + @ForceInline + static Object compareAndExchange(VarHandle ob, Object oarray, int index, Object expected, Object value) { + ArrayVarHandle handle = (ArrayVarHandle) ob; + Object[] array = (Object[]) handle.arrayType.cast(oarray); + Class arrayType = oarray.getClass(); + if (ValueClass.isFlatArray(oarray)) { + // delegate to flat access primitives + VarHandles.checkAtomicFlatArray(array); + int aoffset = (int) UNSAFE.arrayBaseOffset(array); + int ascale = UNSAFE.arrayIndexScale(array); + int ashift = 31 - Integer.numberOfLeadingZeros(ascale); + int layout = UNSAFE.arrayLayout(array); + return UNSAFE.compareAndExchangeFlatValue(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << ashift) + aoffset, layout, arrayType.componentType(), + arrayType.componentType().cast(expected), + storeCheck(handle, array, value)); + } + return UNSAFE.compareAndExchangeReference(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << REFERENCE_SHIFT) + REFERENCE_BASE, handle.componentType, + handle.componentType.cast(expected), + storeCheck(handle, array, value)); + } + + @ForceInline + static Object compareAndExchangeAcquire(VarHandle ob, Object oarray, int index, Object expected, Object value) { + ArrayVarHandle handle = (ArrayVarHandle) ob; + Object[] array = (Object[]) handle.arrayType.cast(oarray); + Class arrayType = oarray.getClass(); + if (ValueClass.isFlatArray(oarray)) { + // delegate to flat access primitives + VarHandles.checkAtomicFlatArray(array); + int aoffset = (int) UNSAFE.arrayBaseOffset(array); + int ascale = UNSAFE.arrayIndexScale(array); + int ashift = 31 - Integer.numberOfLeadingZeros(ascale); + int layout = UNSAFE.arrayLayout(array); + return UNSAFE.compareAndExchangeFlatValueAcquire(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << ashift) + aoffset, layout, arrayType.componentType(), + arrayType.componentType().cast(expected), + storeCheck(handle, array, value)); + } + return UNSAFE.compareAndExchangeReferenceAcquire(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << REFERENCE_SHIFT) + REFERENCE_BASE, handle.componentType, + handle.componentType.cast(expected), + storeCheck(handle, array, value)); + } + + @ForceInline + static Object compareAndExchangeRelease(VarHandle ob, Object oarray, int index, Object expected, Object value) { + ArrayVarHandle handle = (ArrayVarHandle) ob; + Object[] array = (Object[]) handle.arrayType.cast(oarray); + Class arrayType = oarray.getClass(); + if (ValueClass.isFlatArray(oarray)) { + // delegate to flat access primitives + VarHandles.checkAtomicFlatArray(array); + int aoffset = (int) UNSAFE.arrayBaseOffset(array); + int ascale = UNSAFE.arrayIndexScale(array); + int ashift = 31 - Integer.numberOfLeadingZeros(ascale); + int layout = UNSAFE.arrayLayout(array); + return UNSAFE.compareAndExchangeFlatValueRelease(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << ashift) + aoffset, layout, arrayType.componentType(), + arrayType.componentType().cast(expected), + storeCheck(handle, array, value)); + } + return UNSAFE.compareAndExchangeReferenceRelease(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << REFERENCE_SHIFT) + REFERENCE_BASE, handle.componentType, + handle.componentType.cast(expected), + storeCheck(handle, array, value)); + } + + @ForceInline + static boolean weakCompareAndSetPlain(VarHandle ob, Object oarray, int index, Object expected, Object value) { + ArrayVarHandle handle = (ArrayVarHandle) ob; + Object[] array = (Object[]) handle.arrayType.cast(oarray); + Class arrayType = oarray.getClass(); + if (ValueClass.isFlatArray(oarray)) { + // delegate to flat access primitives + VarHandles.checkAtomicFlatArray(array); + int aoffset = (int) UNSAFE.arrayBaseOffset(array); + int ascale = UNSAFE.arrayIndexScale(array); + int ashift = 31 - Integer.numberOfLeadingZeros(ascale); + int layout = UNSAFE.arrayLayout(array); + return UNSAFE.weakCompareAndSetFlatValuePlain(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << ashift) + aoffset, layout, arrayType.componentType(), + arrayType.componentType().cast(expected), + storeCheck(handle, array, value)); + } + return UNSAFE.weakCompareAndSetReferencePlain(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << REFERENCE_SHIFT) + REFERENCE_BASE, handle.componentType, + handle.componentType.cast(expected), + storeCheck(handle, array, value)); + } + + @ForceInline + static boolean weakCompareAndSet(VarHandle ob, Object oarray, int index, Object expected, Object value) { + ArrayVarHandle handle = (ArrayVarHandle) ob; + Object[] array = (Object[]) handle.arrayType.cast(oarray); + Class arrayType = oarray.getClass(); + if (ValueClass.isFlatArray(oarray)) { + // delegate to flat access primitives + VarHandles.checkAtomicFlatArray(array); + int aoffset = (int) UNSAFE.arrayBaseOffset(array); + int ascale = UNSAFE.arrayIndexScale(array); + int ashift = 31 - Integer.numberOfLeadingZeros(ascale); + int layout = UNSAFE.arrayLayout(array); + return UNSAFE.weakCompareAndSetFlatValue(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << ashift) + aoffset, layout, arrayType.componentType(), + arrayType.componentType().cast(expected), + storeCheck(handle, array, value)); + } + return UNSAFE.weakCompareAndSetReference(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << REFERENCE_SHIFT) + REFERENCE_BASE, handle.componentType, + handle.componentType.cast(expected), + storeCheck(handle, array, value)); + } + + @ForceInline + static boolean weakCompareAndSetAcquire(VarHandle ob, Object oarray, int index, Object expected, Object value) { + ArrayVarHandle handle = (ArrayVarHandle) ob; + Object[] array = (Object[]) handle.arrayType.cast(oarray); + Class arrayType = oarray.getClass(); + if (ValueClass.isFlatArray(oarray)) { + // delegate to flat access primitives + VarHandles.checkAtomicFlatArray(array); + int aoffset = (int) UNSAFE.arrayBaseOffset(array); + int ascale = UNSAFE.arrayIndexScale(array); + int ashift = 31 - Integer.numberOfLeadingZeros(ascale); + int layout = UNSAFE.arrayLayout(array); + return UNSAFE.weakCompareAndSetFlatValueAcquire(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << ashift) + aoffset, layout, arrayType.componentType(), + arrayType.componentType().cast(expected), + storeCheck(handle, array, value)); + } + return UNSAFE.weakCompareAndSetReferenceAcquire(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << REFERENCE_SHIFT) + REFERENCE_BASE, handle.componentType, + handle.componentType.cast(expected), + storeCheck(handle, array, value)); + } + + @ForceInline + static boolean weakCompareAndSetRelease(VarHandle ob, Object oarray, int index, Object expected, Object value) { + ArrayVarHandle handle = (ArrayVarHandle) ob; + Object[] array = (Object[]) handle.arrayType.cast(oarray); + Class arrayType = oarray.getClass(); + if (ValueClass.isFlatArray(oarray)) { + // delegate to flat access primitives + VarHandles.checkAtomicFlatArray(array); + int aoffset = (int) UNSAFE.arrayBaseOffset(array); + int ascale = UNSAFE.arrayIndexScale(array); + int ashift = 31 - Integer.numberOfLeadingZeros(ascale); + int layout = UNSAFE.arrayLayout(array); + return UNSAFE.weakCompareAndSetFlatValueRelease(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << ashift) + aoffset, layout, arrayType.componentType(), + arrayType.componentType().cast(expected), + storeCheck(handle, array, value)); + } + return UNSAFE.weakCompareAndSetReferenceRelease(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << REFERENCE_SHIFT) + REFERENCE_BASE, handle.componentType, + handle.componentType.cast(expected), + storeCheck(handle, array, value)); + } + + @ForceInline + static Object getAndSet(VarHandle ob, Object oarray, int index, Object value) { + ArrayVarHandle handle = (ArrayVarHandle) ob; + Object[] array = (Object[]) handle.arrayType.cast(oarray); + Class arrayType = oarray.getClass(); + if (ValueClass.isFlatArray(oarray)) { + // delegate to flat access primitives + VarHandles.checkAtomicFlatArray(array); + int aoffset = (int) UNSAFE.arrayBaseOffset(array); + int ascale = UNSAFE.arrayIndexScale(array); + int ashift = 31 - Integer.numberOfLeadingZeros(ascale); + int layout = UNSAFE.arrayLayout(array); + return UNSAFE.getAndSetFlatValue(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << ashift) + aoffset, layout, arrayType.componentType(), + storeCheck(handle, array, value)); + } + return UNSAFE.getAndSetReference(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << REFERENCE_SHIFT) + REFERENCE_BASE, + handle.componentType, storeCheck(handle, array, value)); + } + + @ForceInline + static Object getAndSetAcquire(VarHandle ob, Object oarray, int index, Object value) { + ArrayVarHandle handle = (ArrayVarHandle) ob; + Object[] array = (Object[]) handle.arrayType.cast(oarray); + Class arrayType = oarray.getClass(); + if (ValueClass.isFlatArray(oarray)) { + // delegate to flat access primitives + VarHandles.checkAtomicFlatArray(array); + int aoffset = (int) UNSAFE.arrayBaseOffset(array); + int ascale = UNSAFE.arrayIndexScale(array); + int ashift = 31 - Integer.numberOfLeadingZeros(ascale); + int layout = UNSAFE.arrayLayout(array); + return UNSAFE.getAndSetFlatValueAcquire(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << ashift) + aoffset, layout, arrayType.componentType(), + storeCheck(handle, array, value)); + } + return UNSAFE.getAndSetReferenceAcquire(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << REFERENCE_SHIFT) + REFERENCE_BASE, + handle.componentType, storeCheck(handle, array, value)); + } + + @ForceInline + static Object getAndSetRelease(VarHandle ob, Object oarray, int index, Object value) { + ArrayVarHandle handle = (ArrayVarHandle) ob; + Object[] array = (Object[]) handle.arrayType.cast(oarray); + Class arrayType = oarray.getClass(); + if (ValueClass.isFlatArray(oarray)) { + // delegate to flat access primitives + VarHandles.checkAtomicFlatArray(array); + int aoffset = (int) UNSAFE.arrayBaseOffset(array); + int ascale = UNSAFE.arrayIndexScale(array); + int ashift = 31 - Integer.numberOfLeadingZeros(ascale); + int layout = UNSAFE.arrayLayout(array); + return UNSAFE.getAndSetFlatValueRelease(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << ashift) + aoffset, layout, arrayType.componentType(), + storeCheck(handle, array, value)); + } + return UNSAFE.getAndSetReferenceRelease(array, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << REFERENCE_SHIFT) + REFERENCE_BASE, + handle.componentType, storeCheck(handle, array, value)); + } + + static final VarForm FORM = new VarForm(ArrayVarHandle.class, Object[].class, Object.class, int.class); +} diff --git a/src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java b/src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java index b60304dcd7b..a2a68ec3a4e 100644 --- a/src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java +++ b/src/java.base/share/classes/java/lang/invoke/DirectMethodHandle.java @@ -27,6 +27,7 @@ import jdk.internal.misc.CDS; import jdk.internal.misc.Unsafe; +import jdk.internal.value.ValueClass; import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; @@ -135,9 +136,9 @@ static DirectMethodHandle make(MemberName member) { return make(member.getDeclaringClass(), member); } static DirectMethodHandle makeAllocator(Class instanceClass, MemberName ctor) { - assert(ctor.isConstructor() && ctor.getName().equals("")); + assert(ctor.isConstructor()) : ctor; ctor = ctor.asConstructor(); - assert(ctor.isConstructor() && ctor.getReferenceKind() == REF_newInvokeSpecial) : ctor; + assert(ctor.getReferenceKind() == REF_newInvokeSpecial) : ctor; MethodType mtype = ctor.getMethodType().changeReturnType(instanceClass); LambdaForm lform = preparedLambdaForm(ctor); MemberName init = ctor.asSpecial(); @@ -369,7 +370,7 @@ static boolean shouldBeInitialized(MemberName member) { } private void ensureInitialized() { - if (checkInitialized(member)) { + if (checkInitialized()) { // The coast is clear. Delete the barrier. updateForm(new Function<>() { public LambdaForm apply(LambdaForm oldForm) { @@ -379,14 +380,19 @@ public LambdaForm apply(LambdaForm oldForm) { }); } } - private static boolean checkInitialized(MemberName member) { + private boolean checkInitialized() { Class defc = member.getDeclaringClass(); UNSAFE.ensureClassInitialized(defc); // Once we get here either defc was fully initialized by another thread, or // defc was already being initialized by the current thread. In the latter case // the barrier must remain. We can detect this simply by checking if initialization // is still needed. - return !UNSAFE.shouldBeInitialized(defc); + boolean initializingStill = UNSAFE.shouldBeInitialized(defc); + if (initializingStill && member.isStrict()) { + // while is running, we track access to strict static fields + UNSAFE.notifyStrictStaticAccess(defc, staticOffset(this), member.isSetter()); + } + return !initializingStill; } /*non-public*/ @@ -507,11 +513,13 @@ static Object allocateInstance(Object mh) throws InstantiationException { static final class Accessor extends DirectMethodHandle { final Class fieldType; final int fieldOffset; + final int layout; private Accessor(MethodType mtype, LambdaForm form, MemberName member, boolean crackable, int fieldOffset) { super(mtype, form, member, crackable); this.fieldType = member.getFieldType(); this.fieldOffset = fieldOffset; + this.layout = member.getLayout(); } @Override Object checkCast(Object obj) { @@ -601,6 +609,21 @@ static Object checkCast(Object mh, Object obj) { return ((DirectMethodHandle) mh).checkCast(obj); } + @ForceInline + /*non-public*/ static Class fieldType(Object accessorObj) { + return ((Accessor) accessorObj).fieldType; + } + + @ForceInline + static int fieldLayout(Object accessorObj) { + return ((Accessor) accessorObj).layout; + } + + @ForceInline + /*non-public*/ static Class staticFieldType(Object accessorObj) { + return ((StaticAccessor) accessorObj).fieldType; + } + Object checkCast(Object obj) { return member.getMethodType().returnType().cast(obj); } @@ -615,11 +638,28 @@ Object checkCast(Object obj) { AF_PUTSTATIC_INIT = 5, AF_LIMIT = 6; // Enumerate the different field kinds using Wrapper, - // with an extra case added for checked references. + // with an extra case added for checked references and value field access static final int - FT_UNCHECKED_REF = Wrapper.OBJECT.ordinal(), - FT_CHECKED_REF = Wrapper.VOID.ordinal(), - FT_LIMIT = Wrapper.COUNT; + FT_FIRST_REFERENCE = 8, + // Any oop, same sig (Runnable?) + FT_UNCHECKED_REF = FT_FIRST_REFERENCE, + // Oop with type checks (Number?) + FT_CHECKED_REF = FT_FIRST_REFERENCE + 1, + // Oop with null checks, (Runnable!) + FT_UNCHECKED_NR_REF = FT_FIRST_REFERENCE + 2, + // Oop with null and type checks, (Number!) + FT_CHECKED_NR_REF = FT_FIRST_REFERENCE + 3, + FT_FIRST_FLAT = FT_FIRST_REFERENCE + 4, + // nullable flat (must check type), (Integer?) + FT_NULLABLE_FLAT = FT_FIRST_FLAT, + // Null restricted flat (must check type), (Integer!) + FT_NR_FLAT = FT_FIRST_FLAT + 1, + FT_LIMIT = FT_FIRST_FLAT + 2; + + static { + assert FT_FIRST_REFERENCE == Wrapper.OBJECT.ordinal(); + } + private static int afIndex(byte formOp, boolean isVolatile, int ftypeKind) { return ((formOp * FT_LIMIT * 2) + (isVolatile ? FT_LIMIT : 0) @@ -628,15 +668,20 @@ private static int afIndex(byte formOp, boolean isVolatile, int ftypeKind) { @Stable private static final LambdaForm[] ACCESSOR_FORMS = new LambdaForm[afIndex(AF_LIMIT, false, 0)]; - static int ftypeKind(Class ftype) { + static int ftypeKind(Class ftype, boolean isFlat, boolean isNullRestricted) { if (ftype.isPrimitive()) { + assert !isFlat && !isNullRestricted : ftype; return Wrapper.forPrimitiveType(ftype).ordinal(); } else if (ftype.isInterface() || ftype.isAssignableFrom(Object.class)) { + assert !isFlat : ftype; // retyping can be done without a cast - return FT_UNCHECKED_REF; - } else { - return FT_CHECKED_REF; + return isNullRestricted ? FT_UNCHECKED_NR_REF : FT_UNCHECKED_REF; } + if (isFlat) { + assert ValueClass.isConcreteValueClass(ftype) : ftype; + return isNullRestricted ? FT_NR_FLAT : FT_NULLABLE_FLAT; + } + return isNullRestricted ? FT_CHECKED_NR_REF : FT_CHECKED_REF; } /** @@ -646,7 +691,6 @@ static int ftypeKind(Class ftype) { */ private static LambdaForm preparedFieldLambdaForm(MemberName m) { Class ftype = m.getFieldType(); - boolean isVolatile = m.isVolatile(); byte formOp = switch (m.getReferenceKind()) { case REF_getField -> AF_GETFIELD; case REF_putField -> AF_PUTFIELD; @@ -656,19 +700,25 @@ private static LambdaForm preparedFieldLambdaForm(MemberName m) { }; if (shouldBeInitialized(m)) { // precompute the barrier-free version: - preparedFieldLambdaForm(formOp, isVolatile, ftype); + preparedFieldLambdaForm(formOp, m.isVolatile(), m.isFlat(), m.isNullRestricted(), ftype); assert((AF_GETSTATIC_INIT - AF_GETSTATIC) == (AF_PUTSTATIC_INIT - AF_PUTSTATIC)); formOp += (AF_GETSTATIC_INIT - AF_GETSTATIC); } - LambdaForm lform = preparedFieldLambdaForm(formOp, isVolatile, ftype); + LambdaForm lform = preparedFieldLambdaForm(formOp, m.isVolatile(), m.isFlat(), m.isNullRestricted(), ftype); assert(lform.methodType().dropParameterTypes(0, 1) .equals(m.getInvocationType().basicType())) : Arrays.asList(m, m.getInvocationType().basicType(), lform, lform.methodType()); return lform; } - private static LambdaForm preparedFieldLambdaForm(byte formOp, boolean isVolatile, Class ftype) { - int ftypeKind = ftypeKind(ftype); + + private static LambdaForm preparedFieldLambdaForm(byte formOp, boolean isVolatile, + boolean isFlat, boolean isNullRestricted, Class ftype) { + int ftypeKind = ftypeKind(ftype, isFlat, isNullRestricted); + return preparedFieldLambdaForm(formOp, isVolatile, ftypeKind); + } + + private static LambdaForm preparedFieldLambdaForm(byte formOp, boolean isVolatile, int ftypeKind) { int afIndex = afIndex(formOp, isVolatile, ftypeKind); LambdaForm lform = ACCESSOR_FORMS[afIndex]; if (lform != null) return lform; @@ -680,47 +730,77 @@ private static LambdaForm preparedFieldLambdaForm(byte formOp, boolean isVolatil private static final @Stable Wrapper[] ALL_WRAPPERS = Wrapper.values(); // Names in kind may overload but differ from their basic type - private static Kind getFieldKind(boolean isVolatile, boolean needsInit, boolean needsCast, Wrapper wrapper) { + private static Kind getFieldKind(boolean isGetter, + boolean isVolatile, + boolean needsInit, + boolean needsCast, + boolean isFlat, + boolean isNullRestricted, + Wrapper wrapper) { + if (!wrapper.isOther()) { + // primitives + assert !isFlat && !isNullRestricted && !needsCast; + return switch (wrapper) { + case BYTE -> isVolatile + ? (needsInit ? VOLATILE_FIELD_ACCESS_INIT_B : VOLATILE_FIELD_ACCESS_B) + : (needsInit ? FIELD_ACCESS_INIT_B : FIELD_ACCESS_B); + case CHAR -> isVolatile + ? (needsInit ? VOLATILE_FIELD_ACCESS_INIT_C : VOLATILE_FIELD_ACCESS_C) + : (needsInit ? FIELD_ACCESS_INIT_C : FIELD_ACCESS_C); + case SHORT -> isVolatile + ? (needsInit ? VOLATILE_FIELD_ACCESS_INIT_S : VOLATILE_FIELD_ACCESS_S) + : (needsInit ? FIELD_ACCESS_INIT_S : FIELD_ACCESS_S); + case BOOLEAN -> isVolatile + ? (needsInit ? VOLATILE_FIELD_ACCESS_INIT_Z : VOLATILE_FIELD_ACCESS_Z) + : (needsInit ? FIELD_ACCESS_INIT_Z : FIELD_ACCESS_Z); + // basic types + default -> isVolatile + ? (needsInit ? VOLATILE_FIELD_ACCESS_INIT : VOLATILE_FIELD_ACCESS) + : (needsInit ? FIELD_ACCESS_INIT : FIELD_ACCESS); + }; + } + + assert !(isGetter && isNullRestricted); if (isVolatile) { - if (needsInit) { - return switch (wrapper) { - case BYTE -> VOLATILE_FIELD_ACCESS_INIT_B; - case CHAR -> VOLATILE_FIELD_ACCESS_INIT_C; - case SHORT -> VOLATILE_FIELD_ACCESS_INIT_S; - case BOOLEAN -> VOLATILE_FIELD_ACCESS_INIT_Z; - default -> needsCast ? VOLATILE_FIELD_ACCESS_INIT_CAST : VOLATILE_FIELD_ACCESS_INIT; - }; + if (isFlat) { + assert !needsInit && needsCast; + return isNullRestricted ? VOLATILE_PUT_NULL_RESTRICTED_FLAT_VALUE : VOLATILE_FIELD_ACCESS_FLAT; + } else if (needsCast) { + if (needsInit) { + return isNullRestricted ? VOLATILE_PUT_NULL_RESTRICTED_REFERENCE_CAST_INIT : VOLATILE_FIELD_ACCESS_INIT_CAST; + } else { + return isNullRestricted ? VOLATILE_PUT_NULL_RESTRICTED_REFERENCE_CAST : VOLATILE_FIELD_ACCESS_CAST; + } } else { - return switch (wrapper) { - case BYTE -> VOLATILE_FIELD_ACCESS_B; - case CHAR -> VOLATILE_FIELD_ACCESS_C; - case SHORT -> VOLATILE_FIELD_ACCESS_S; - case BOOLEAN -> VOLATILE_FIELD_ACCESS_Z; - default -> needsCast ? VOLATILE_FIELD_ACCESS_CAST : VOLATILE_FIELD_ACCESS; - }; + if (needsInit) { + return isNullRestricted ? VOLATILE_PUT_NULL_RESTRICTED_REFERENCE_INIT : VOLATILE_FIELD_ACCESS_INIT; + } else { + return isNullRestricted ? VOLATILE_PUT_NULL_RESTRICTED_REFERENCE : VOLATILE_FIELD_ACCESS; + } } } else { - if (needsInit) { - return switch (wrapper) { - case BYTE -> FIELD_ACCESS_INIT_B; - case CHAR -> FIELD_ACCESS_INIT_C; - case SHORT -> FIELD_ACCESS_INIT_S; - case BOOLEAN -> FIELD_ACCESS_INIT_Z; - default -> needsCast ? FIELD_ACCESS_INIT_CAST : FIELD_ACCESS_INIT; - }; + if (isFlat) { + assert !needsInit && needsCast; + return isNullRestricted ? PUT_NULL_RESTRICTED_FLAT_VALUE : FIELD_ACCESS_FLAT; + } else if (needsCast) { + if (needsInit) { + return isNullRestricted ? PUT_NULL_RESTRICTED_REFERENCE_CAST_INIT : FIELD_ACCESS_INIT_CAST; + } else { + return isNullRestricted ? PUT_NULL_RESTRICTED_REFERENCE_CAST : FIELD_ACCESS_CAST; + } } else { - return switch (wrapper) { - case BYTE -> FIELD_ACCESS_B; - case CHAR -> FIELD_ACCESS_C; - case SHORT -> FIELD_ACCESS_S; - case BOOLEAN -> FIELD_ACCESS_Z; - default -> needsCast ? FIELD_ACCESS_CAST : FIELD_ACCESS; - }; + if (needsInit) { + return isNullRestricted ? PUT_NULL_RESTRICTED_REFERENCE_INIT : FIELD_ACCESS_INIT; + } else { + return isNullRestricted ? PUT_NULL_RESTRICTED_REFERENCE : FIELD_ACCESS; + } } } } - private static String unsafeMethodName(boolean isGetter, boolean isVolatile, Wrapper wrapper) { + private static String unsafeMethodName(boolean isGetter, + boolean isVolatile, + Wrapper wrapper) { var name = switch (wrapper) { case BOOLEAN -> "Boolean"; case BYTE -> "Byte"; @@ -731,7 +811,7 @@ private static String unsafeMethodName(boolean isGetter, boolean isVolatile, Wra case LONG -> "Long"; case DOUBLE -> "Double"; case OBJECT -> "Reference"; - case VOID -> throw new InternalError(); + case VOID -> "FlatValue"; }; var sb = new StringBuilder(3 + name.length() + (isVolatile ? 8 : 0)) .append(isGetter ? "get" : "put") @@ -746,21 +826,45 @@ static LambdaForm makePreparedFieldLambdaForm(byte formOp, boolean isVolatile, i boolean isGetter = (formOp & 1) == (AF_GETFIELD & 1); boolean isStatic = (formOp >= AF_GETSTATIC); boolean needsInit = (formOp >= AF_GETSTATIC_INIT); - boolean needsCast = (ftypeKind == FT_CHECKED_REF); - Wrapper fw = (needsCast ? Wrapper.OBJECT : ALL_WRAPPERS[ftypeKind]); - Class ft = fw.primitiveType(); - assert(ftypeKind(needsCast ? String.class : ft) == ftypeKind); + boolean isFlat = (ftypeKind >= FT_FIRST_FLAT); + boolean isNullRestricted = (ftypeKind == FT_NR_FLAT || ftypeKind == FT_CHECKED_NR_REF || ftypeKind == FT_UNCHECKED_NR_REF); + boolean needsCast = (isFlat || ftypeKind == FT_CHECKED_REF || ftypeKind == FT_CHECKED_NR_REF); + + if (isGetter && isNullRestricted) { + int newKind = switch (ftypeKind) { + case FT_NR_FLAT -> FT_NULLABLE_FLAT; + case FT_CHECKED_NR_REF -> FT_CHECKED_REF; + case FT_UNCHECKED_NR_REF -> FT_UNCHECKED_REF; + default -> throw new InternalError(); + }; + return preparedFieldLambdaForm(formOp, isVolatile, newKind); + } + + if (isFlat && isStatic) + throw new InternalError("Static flat not supported yet"); + + // primitives, reference, and void for flat + Wrapper fw = ftypeKind < FT_FIRST_REFERENCE ? ALL_WRAPPERS[ftypeKind] : + isFlat ? Wrapper.VOID : Wrapper.OBJECT; // getObject, putIntVolatile, etc. String unsafeMethodName = unsafeMethodName(isGetter, isVolatile, fw); - // isGetter and isStatic is reflected in field type; basic type clash for subwords - Kind kind = getFieldKind(isVolatile, needsInit, needsCast, fw); + // isGetter and isStatic is reflected in field type; + // flat, NR distinguished + // basic type clash for subwords + Kind kind = getFieldKind(isGetter, isVolatile, needsInit, needsCast, isFlat, isNullRestricted, fw); + Class ft = ftypeKind < FT_FIRST_REFERENCE ? fw.primitiveType() : Object.class; MethodType linkerType; - if (isGetter) - linkerType = MethodType.methodType(ft, Object.class, long.class); - else - linkerType = MethodType.methodType(void.class, Object.class, long.class, ft); + if (isGetter) { + linkerType = isFlat + ? MethodType.methodType(ft, Object.class, long.class, int.class, Class.class) + : MethodType.methodType(ft, Object.class, long.class); + } else { + linkerType = isFlat + ? MethodType.methodType(void.class, Object.class, long.class, int.class, Class.class, ft) + : MethodType.methodType(void.class, Object.class, long.class, ft); + } MemberName linker = new MemberName(Unsafe.class, unsafeMethodName, linkerType, REF_invokeVirtual); try { linker = IMPL_NAMES.resolveOrFail(REF_invokeVirtual, linker, null, LM_TRUSTED, @@ -791,17 +895,24 @@ static LambdaForm makePreparedFieldLambdaForm(byte formOp, boolean isVolatile, i final int OBJ_CHECK = (OBJ_BASE >= 0 ? nameCursor++ : -1); final int U_HOLDER = nameCursor++; // UNSAFE holder final int INIT_BAR = (needsInit ? nameCursor++ : -1); + final int LAYOUT = (isFlat ? nameCursor++ : -1); // field must be instance + final int VALUE_TYPE = (isFlat ? nameCursor++ : -1); + final int NULL_CHECK = (isNullRestricted && !isGetter ? nameCursor++ : -1); final int PRE_CAST = (needsCast && !isGetter ? nameCursor++ : -1); final int LINKER_CALL = nameCursor++; final int POST_CAST = (needsCast && isGetter ? nameCursor++ : -1); - final int RESULT = nameCursor-1; // either the call or the cast + final int RESULT = nameCursor-1; // either the call, or the cast Name[] names = invokeArguments(nameCursor - ARG_LIMIT, mtype); if (needsInit) names[INIT_BAR] = new Name(getFunction(NF_ensureInitialized), names[DMH_THIS]); - if (needsCast && !isGetter) - names[PRE_CAST] = new Name(getFunction(NF_checkCast), names[DMH_THIS], names[SET_VALUE]); + if (!isGetter) { + if (isNullRestricted) + names[NULL_CHECK] = new Name(getFunction(NF_nullCheck), names[SET_VALUE]); + if (needsCast) + names[PRE_CAST] = new Name(getFunction(NF_checkCast), names[DMH_THIS], names[SET_VALUE]); + } Object[] outArgs = new Object[1 + linkerType.parameterCount()]; - assert(outArgs.length == (isGetter ? 3 : 4)); + assert (outArgs.length == (isGetter ? 3 : 4) + (isFlat ? 2 : 0)); outArgs[0] = names[U_HOLDER] = new Name(getFunction(NF_UNSAFE)); if (isStatic) { outArgs[1] = names[F_HOLDER] = new Name(getFunction(NF_staticBase), names[DMH_THIS]); @@ -810,8 +921,14 @@ static LambdaForm makePreparedFieldLambdaForm(byte formOp, boolean isVolatile, i outArgs[1] = names[OBJ_CHECK] = new Name(getFunction(NF_checkBase), names[OBJ_BASE]); outArgs[2] = names[F_OFFSET] = new Name(getFunction(NF_fieldOffset), names[DMH_THIS]); } + int x = 3; + if (isFlat) { + outArgs[x++] = names[LAYOUT] = new Name(getFunction(NF_fieldLayout), names[DMH_THIS]); + outArgs[x++] = names[VALUE_TYPE] = isStatic ? new Name(getFunction(NF_staticFieldType), names[DMH_THIS]) + : new Name(getFunction(NF_fieldType), names[DMH_THIS]); + } if (!isGetter) { - outArgs[3] = (needsCast ? names[PRE_CAST] : names[SET_VALUE]); + outArgs[x] = (needsCast ? names[PRE_CAST] : names[SET_VALUE]); } for (Object a : outArgs) assert(a != null); names[LINKER_CALL] = new Name(linker, outArgs); @@ -830,6 +947,9 @@ static LambdaForm makePreparedFieldLambdaForm(byte formOp, boolean isVolatile, i } else { nameBuilder.append("Field"); } + if (isNullRestricted) { + nameBuilder.append("NullRestricted"); + } if (needsCast) { nameBuilder.append("Cast"); } @@ -859,7 +979,11 @@ static LambdaForm makePreparedFieldLambdaForm(byte formOp, boolean isVolatile, i NF_constructorMethod = 9, NF_UNSAFE = 10, NF_checkReceiver = 11, - NF_LIMIT = 12; + NF_fieldType = 12, + NF_staticFieldType = 13, + NF_fieldLayout = 14, + NF_nullCheck = 15, + NF_LIMIT = 16; private static final @Stable NamedFunction[] NFS = new NamedFunction[NF_LIMIT]; @@ -874,6 +998,9 @@ private static NamedFunction getFunction(byte func) { return nf; } + private static final MethodType CLS_OBJ_TYPE = MethodType.methodType(Class.class, Object.class); + private static final MethodType INT_OBJ_TYPE = MethodType.methodType(int.class, Object.class); + private static final MethodType OBJ_OBJ_TYPE = MethodType.methodType(Object.class, Object.class); private static final MethodType LONG_OBJ_TYPE = MethodType.methodType(long.class, Object.class); @@ -913,6 +1040,14 @@ private static NamedFunction createFunction(byte func) { MemberName.getFactory().resolveOrFail(REF_invokeVirtual, member, DirectMethodHandle.class, LM_TRUSTED, NoSuchMethodException.class)); + case NF_fieldType: + return getNamedFunction("fieldType", CLS_OBJ_TYPE); + case NF_staticFieldType: + return getNamedFunction("staticFieldType", CLS_OBJ_TYPE); + case NF_nullCheck: + return getNamedFunction("nullCheck", OBJ_OBJ_TYPE); + case NF_fieldLayout: + return getNamedFunction("fieldLayout", INT_OBJ_TYPE); default: throw newInternalError("Unknown function: " + func); } 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 6d9f32575c8..6b1ec3629f8 100644 --- a/src/java.base/share/classes/java/lang/invoke/GenerateJLIClassesHelper.java +++ b/src/java.base/share/classes/java/lang/invoke/GenerateJLIClassesHelper.java @@ -35,6 +35,7 @@ import java.lang.constant.ClassDesc; import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -433,24 +434,43 @@ static byte[] generateDirectMethodHandleHolderClassBytes(String className, forms.add(form); names.add(form.kind.defaultLambdaName); } - for (Wrapper wrapper : Wrapper.values()) { - int ftype = wrapper == Wrapper.VOID ? DirectMethodHandle.FT_CHECKED_REF : DirectMethodHandle.ftypeKind(wrapper.primitiveType()); - for (byte b = DirectMethodHandle.AF_GETFIELD; b < DirectMethodHandle.AF_LIMIT; b++) { - LambdaForm form = DirectMethodHandle - .makePreparedFieldLambdaForm(b, /*isVolatile*/false, ftype); - if (form.kind == GENERIC) - throw new InternalError(b + " non-volatile " + ftype); - forms.add(form); - names.add(form.kind.defaultLambdaName); - // volatile - form = DirectMethodHandle - .makePreparedFieldLambdaForm(b, /*isVolatile*/true, ftype); - if (form.kind == GENERIC) - throw new InternalError(b + " volatile " + ftype); - forms.add(form); - names.add(form.kind.defaultLambdaName); + record FieldLfToken(byte formOp, int ftypeKind) {} + List tokens = new ArrayList<>(); + for (int i = 0; i <= DirectMethodHandle.FT_CHECKED_REF; i++) { + for (byte formOp = DirectMethodHandle.AF_GETFIELD; formOp < DirectMethodHandle.AF_LIMIT; formOp++) { + tokens.add(new FieldLfToken(formOp, i)); } } + for (int i : new int[] {DirectMethodHandle.FT_UNCHECKED_NR_REF, DirectMethodHandle.FT_CHECKED_NR_REF}) { + for (byte formOp = DirectMethodHandle.AF_GETFIELD; formOp < DirectMethodHandle.AF_LIMIT; formOp++) { + boolean isGetter = (formOp & 1) == (DirectMethodHandle.AF_GETFIELD & 1); + if (!isGetter) { + tokens.add(new FieldLfToken(formOp, i)); + } + } + } + // Only legal flat combinations; no static + tokens.add(new FieldLfToken(DirectMethodHandle.AF_GETFIELD, DirectMethodHandle.FT_NULLABLE_FLAT)); + tokens.add(new FieldLfToken(DirectMethodHandle.AF_PUTFIELD, DirectMethodHandle.FT_NULLABLE_FLAT)); + tokens.add(new FieldLfToken(DirectMethodHandle.AF_PUTFIELD, DirectMethodHandle.FT_NR_FLAT)); + // Compile + for (var token : tokens) { + byte b = token.formOp; + int ftype = token.ftypeKind; + LambdaForm form = DirectMethodHandle + .makePreparedFieldLambdaForm(b, /*isVolatile*/false, ftype); + if (form.kind == GENERIC) + throw new InternalError(b + " non-volatile " + ftype); + forms.add(form); + names.add(form.kind.defaultLambdaName); + // volatile + form = DirectMethodHandle + .makePreparedFieldLambdaForm(b, /*isVolatile*/true, ftype); + if (form.kind == GENERIC) + throw new InternalError(b + " volatile " + ftype); + forms.add(form); + names.add(form.kind.defaultLambdaName); + } return generateCodeBytesForLFs(className, names.toArray(new String[0]), forms.toArray(new LambdaForm[0])); 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 4dac59771e8..0d4524170da 100644 --- a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java +++ b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,8 +26,10 @@ package java.lang.invoke; import jdk.internal.constant.ClassOrInterfaceDescImpl; +import jdk.internal.misc.PreviewFeatures; import jdk.internal.misc.CDS; import jdk.internal.util.ClassFileDumper; +import jdk.internal.value.ValueClass; import sun.invoke.util.VerifyAccess; import java.io.Serializable; @@ -37,9 +39,13 @@ import java.lang.classfile.MethodBuilder; import java.lang.classfile.Opcode; import java.lang.classfile.TypeKind; + import java.lang.constant.ClassDesc; import java.lang.constant.MethodTypeDesc; +import java.lang.reflect.ClassFileFormatVersion; import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -47,8 +53,10 @@ import static java.lang.classfile.ClassFile.*; import java.lang.classfile.attribute.ExceptionsAttribute; +import java.lang.classfile.attribute.LoadableDescriptorsAttribute; import java.lang.classfile.constantpool.ClassEntry; import java.lang.classfile.constantpool.ConstantPoolBuilder; +import java.lang.classfile.constantpool.Utf8Entry; import static java.lang.constant.ConstantDescs.*; import static java.lang.invoke.MethodHandleNatives.Constants.NESTMATE_CLASS; @@ -310,8 +318,15 @@ private Class generateInnerClass() throws LambdaConversionException { final byte[] classBytes = ClassFile.of().build(lambdaClassEntry, pool, new Consumer() { @Override public void accept(ClassBuilder clb) { - clb.withFlags(ACC_SUPER | ACC_FINAL | ACC_SYNTHETIC) + clb.withVersion(ClassFileFormatVersion.latest().major(), (PreviewFeatures.isEnabled() ? ClassFile.PREVIEW_MINOR_VERSION : 0)) + .withFlags(ACC_SUPER | ACC_FINAL | ACC_SYNTHETIC) .withInterfaceSymbols(interfaces); + + // generate LoadableDescriptors attribute if it references any value class + if (PreviewFeatures.isEnabled()) { + generateLoadableDescriptors(clb); + } + // Generate final fields to be filled in by constructor for (int i = 0; i < argDescs.length; i++) { clb.withField(argName(i), argDescs[i], ACC_PRIVATE | ACC_FINAL); @@ -544,6 +559,73 @@ private void convertArgumentTypes(CodeBuilder cob, MethodType samType) { } } + /* + * LoadableDescriptors attribute builder + */ + static class LoadableDescriptorsAttributeBuilder { + private final Set loadableDescriptors = new HashSet<>(); + LoadableDescriptorsAttributeBuilder(Class targetClass) { + if (requiresLoadableDescriptors(targetClass)) { + loadableDescriptors.add(targetClass.descriptorString()); + } + } + + /* + * Add the value types referenced in the given MethodType. + */ + LoadableDescriptorsAttributeBuilder add(MethodType mt) { + // parameter types + for (Class paramType : mt.ptypes()) { + if (requiresLoadableDescriptors(paramType)) { + loadableDescriptors.add(paramType.descriptorString()); + } + } + // return type + if (requiresLoadableDescriptors(mt.returnType())) { + loadableDescriptors.add(mt.returnType().descriptorString()); + } + return this; + } + + LoadableDescriptorsAttributeBuilder add(MethodType... mtypes) { + for (MethodType mt : mtypes) { + add(mt); + } + return this; + } + + boolean requiresLoadableDescriptors(Class cls) { + return ValueClass.isConcreteValueClass(cls); + } + + boolean isEmpty() { + return loadableDescriptors.isEmpty(); + } + + void build(ClassBuilder clb) { + if (!isEmpty()) { + List lds = new ArrayList(loadableDescriptors.size()); + for (String ld : loadableDescriptors) { + lds.add(clb.constantPool().utf8Entry(ld)); + } + clb.with(LoadableDescriptorsAttribute.of(lds)); + } + } + } + + /** + * Generate LoadableDescriptors attribute if it references any value class + */ + private void generateLoadableDescriptors(ClassBuilder clb) { + LoadableDescriptorsAttributeBuilder builder = new LoadableDescriptorsAttributeBuilder(targetClass); + builder.add(factoryType) + .add(interfaceMethodType) + .add(implMethodType) + .add(dynamicMethodType) + .add(altMethods) + .build(clb); + } + private Opcode invocationOpcode() throws InternalError { return switch (implKind) { case MethodHandleInfo.REF_invokeStatic -> Opcode.INVOKESTATIC; 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 9030a063fad..60be996104d 100644 --- a/src/java.base/share/classes/java/lang/invoke/LambdaForm.java +++ b/src/java.base/share/classes/java/lang/invoke/LambdaForm.java @@ -265,10 +265,14 @@ enum Kind { DIRECT_NEW_INVOKE_SPECIAL("DMH.newInvokeSpecial", "newInvokeSpecial"), DIRECT_INVOKE_INTERFACE("DMH.invokeInterface", "invokeInterface"), DIRECT_INVOKE_STATIC_INIT("DMH.invokeStaticInit", "invokeStaticInit"), + // Start field forms + // IJFDL, instance/static differ in method type, can share form + // init form only applicable to static FIELD_ACCESS("fieldAccess"), FIELD_ACCESS_INIT("fieldAccessInit"), VOLATILE_FIELD_ACCESS("volatileFieldAccess"), VOLATILE_FIELD_ACCESS_INIT("volatileFieldAccessInit"), + // BCSZ need own forms to avoid clashing with basic type I, +-init/volatile FIELD_ACCESS_B("fieldAccessB"), FIELD_ACCESS_INIT_B("fieldAccessInitB"), VOLATILE_FIELD_ACCESS_B("volatileFieldAccessB"), @@ -285,10 +289,30 @@ enum Kind { FIELD_ACCESS_INIT_Z("fieldAccessInitZ"), VOLATILE_FIELD_ACCESS_Z("volatileFieldAccessZ"), VOLATILE_FIELD_ACCESS_INIT_Z("volatileFieldAccessInitZ"), + // cast, nr, flat need their own forms to avoid clashing with L FIELD_ACCESS_CAST("fieldAccessCast"), FIELD_ACCESS_INIT_CAST("fieldAccessInitCast"), VOLATILE_FIELD_ACCESS_CAST("volatileFieldAccessCast"), VOLATILE_FIELD_ACCESS_INIT_CAST("volatileFieldAccessInitCast"), + // null-check and put reference, +-cast, +-init/volatile + // non-cast forms serve bytecode emulation purpose, which always enforces null checks + PUT_NULL_RESTRICTED_REFERENCE("putNullRestrictedReference"), + PUT_NULL_RESTRICTED_REFERENCE_INIT("putNullRestrictedReferenceInit"), + VOLATILE_PUT_NULL_RESTRICTED_REFERENCE("volatilePutNullRestrictedReference"), + VOLATILE_PUT_NULL_RESTRICTED_REFERENCE_INIT("volatilePutNullRestrictedReferenceInit"), + PUT_NULL_RESTRICTED_REFERENCE_CAST("putNullRestrictedReferenceCast"), + PUT_NULL_RESTRICTED_REFERENCE_CAST_INIT("putNullRestrictedReferenceInitCast"), + VOLATILE_PUT_NULL_RESTRICTED_REFERENCE_CAST("volatilePutNullRestrictedReferenceCast"), + VOLATILE_PUT_NULL_RESTRICTED_REFERENCE_CAST_INIT("volatilePutNullRestrictedReferenceCastInit"), + // flat implies cast, +-init/volatile + FIELD_ACCESS_FLAT("fieldAccessFlat"), + FIELD_ACCESS_INIT_FLAT("fieldAccessInitFlat"), + VOLATILE_FIELD_ACCESS_FLAT("volatileFieldAccessFlat"), + VOLATILE_FIELD_ACCESS_INIT_FLAT("volatileFieldAccessInitFlat"), + // write guard NR flat, implies cast; +-volatile; no init forms - no flat in static fields yet + PUT_NULL_RESTRICTED_FLAT_VALUE("putNullRestrictedFlatValue"), + VOLATILE_PUT_NULL_RESTRICTED_FLAT_VALUE("volatilePutNullRestrictedFlatValue"), + // End fields TRY_FINALLY("tryFinally"), TABLE_SWITCH("tableSwitch"), COLLECTOR("collector"), diff --git a/src/java.base/share/classes/java/lang/invoke/MemberName.java b/src/java.base/share/classes/java/lang/invoke/MemberName.java index 918d1b10791..4c6107252d4 100644 --- a/src/java.base/share/classes/java/lang/invoke/MemberName.java +++ b/src/java.base/share/classes/java/lang/invoke/MemberName.java @@ -389,6 +389,10 @@ public boolean isProtected() { public boolean isFinal() { return Modifier.isFinal(flags); } + /** Utility method to query the modifier flags of this member. */ + public boolean isStrict() { + return Modifier.isStrict(flags); + } /** Utility method to query whether this member or its defining class is final. */ public boolean canBeStaticallyBound() { return Modifier.isFinal(flags | clazz.getModifiers()); @@ -408,11 +412,12 @@ public boolean isNative() { // let the rest (native, volatile, transient, etc.) be tested via Modifier.isFoo // unofficial modifier flags, used by HotSpot: - static final int BRIDGE = 0x00000040; - static final int VARARGS = 0x00000080; - static final int SYNTHETIC = 0x00001000; - static final int ANNOTATION= 0x00002000; - static final int ENUM = 0x00004000; + static final int BRIDGE = 0x00000040; + static final int VARARGS = 0x00000080; + static final int SYNTHETIC = 0x00001000; + static final int ANNOTATION = 0x00002000; + static final int ENUM = 0x00004000; + /** Utility method to query the modifier flags of this member; returns false if the member is not a method. */ public boolean isBridge() { return allFlagsSet(IS_METHOD | BRIDGE); @@ -426,19 +431,30 @@ public boolean isSynthetic() { return allFlagsSet(SYNTHETIC); } - static final String CONSTRUCTOR_NAME = ""; // the ever-popular + /** Query whether this member is a flat field */ + public boolean isFlat() { return getLayout() != 0; } + + /** Query whether this member is a null-restricted field */ + public boolean isNullRestricted() { return (flags & MN_NULL_RESTRICTED) == MN_NULL_RESTRICTED; } + + /** + * VM-internal layout code for this field, 0 if this field is not flat. + */ + public int getLayout() { return (flags >>> MN_LAYOUT_SHIFT) & MN_LAYOUT_MASK; } + + static final String CONSTRUCTOR_NAME = ""; // modifiers exported by the JVM: static final int RECOGNIZED_MODIFIERS = 0xFFFF; // private flags, not part of RECOGNIZED_MODIFIERS: static final int - IS_METHOD = MN_IS_METHOD, // method (not constructor) - IS_CONSTRUCTOR = MN_IS_CONSTRUCTOR, // constructor - IS_FIELD = MN_IS_FIELD, // field - IS_TYPE = MN_IS_TYPE, // nested type - CALLER_SENSITIVE = MN_CALLER_SENSITIVE, // @CallerSensitive annotation detected - TRUSTED_FINAL = MN_TRUSTED_FINAL; // trusted final field + IS_METHOD = MN_IS_METHOD, // method (not constructor) + IS_CONSTRUCTOR = MN_IS_CONSTRUCTOR, // constructor + IS_FIELD = MN_IS_FIELD, // field + IS_TYPE = MN_IS_TYPE, // nested type + CALLER_SENSITIVE = MN_CALLER_SENSITIVE, // @CallerSensitive annotation detected + TRUSTED_FINAL = MN_TRUSTED_FINAL; // trusted final field static final int ALL_ACCESS = Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED; static final int ALL_KINDS = IS_METHOD | IS_CONSTRUCTOR | IS_FIELD | IS_TYPE; @@ -755,7 +771,7 @@ public MemberName(Class defClass, String name, Class type, byte refKind) { * The resulting name will in an unresolved state. */ public MemberName(Class defClass, String name, MethodType type, byte refKind) { - int initFlags = (name != null && name.equals(CONSTRUCTOR_NAME) ? IS_CONSTRUCTOR : IS_METHOD); + int initFlags = CONSTRUCTOR_NAME.equals(name) ? IS_CONSTRUCTOR : IS_METHOD; init(defClass, name, type, flagsMods(initFlags, 0, refKind)); initResolved(false); } 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 cb1bf8294d2..2c10ae39047 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java @@ -1561,6 +1561,12 @@ public boolean isHiddenMember(int flags) { return (flags & MN_HIDDEN_MEMBER) == MN_HIDDEN_MEMBER; } + public boolean isNullRestrictedField(MethodHandle mh) { + var memberName = mh.internalMemberName(); + assert memberName.isField(); + return memberName.isNullRestricted(); + } + @Override public Map generateHolderClasses(Stream traces) { return GenerateJLIClassesHelper.generateHolderClasses(traces); @@ -1651,6 +1657,10 @@ public MethodHandle serializableConstructor(Class decl, Constructor ctorTo return IMPL_LOOKUP.serializableConstructor(decl, ctorToCall); } + @Override + public MethodHandle assertAsType(MethodHandle original, MethodType assertedType) { + return original.viewAsType(assertedType, false); + } }); } diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java b/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java index 0db7a6a8ddb..70c46f316a3 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleNatives.java @@ -85,15 +85,18 @@ static class Constants { Constants() { } // static only static final int - MN_IS_METHOD = 0x00010000, // method (not constructor) - MN_IS_CONSTRUCTOR = 0x00020000, // constructor - MN_IS_FIELD = 0x00040000, // field - MN_IS_TYPE = 0x00080000, // nested type - MN_CALLER_SENSITIVE = 0x00100000, // @CallerSensitive annotation detected - MN_TRUSTED_FINAL = 0x00200000, // trusted final field - MN_HIDDEN_MEMBER = 0x00400000, // members defined in a hidden class or with @Hidden - MN_REFERENCE_KIND_SHIFT = 24, // refKind - MN_REFERENCE_KIND_MASK = 0x0F000000 >> MN_REFERENCE_KIND_SHIFT; + MN_IS_METHOD = 0x00010000, // method (not object constructor) + MN_IS_CONSTRUCTOR = 0x00020000, // object constructor + MN_IS_FIELD = 0x00040000, // field + MN_IS_TYPE = 0x00080000, // nested type + MN_CALLER_SENSITIVE = 0x00100000, // @CallerSensitive annotation detected + MN_TRUSTED_FINAL = 0x00200000, // trusted final field + MN_HIDDEN_MEMBER = 0x00400000, // members defined in a hidden class or with @Hidden + MN_NULL_RESTRICTED = 0x00800000, // null-restricted field + MN_REFERENCE_KIND_SHIFT = 24, // refKind + MN_REFERENCE_KIND_MASK = 0x0F000000 >>> MN_REFERENCE_KIND_SHIFT, // 4 bits + MN_LAYOUT_SHIFT = 28, // field layout + MN_LAYOUT_MASK = 0x70000000 >>> MN_LAYOUT_SHIFT; // 3 bits /** * Constant pool reference-kind codes, as used by CONSTANT_MethodHandle CP entries. diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java b/src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java index 70592351827..8131fcc4164 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java @@ -51,6 +51,7 @@ import jdk.internal.constant.ConstantUtils; import jdk.internal.loader.ClassLoaders; +import jdk.internal.misc.PreviewFeatures; import jdk.internal.module.Modules; import jdk.internal.util.ClassFileDumper; import jdk.internal.util.ReferencedKeySet; @@ -344,7 +345,7 @@ private static byte[] createTemplate(ClassLoader loader, ClassDesc proxyDesc, Cl ClassLoaders.platformClassLoader() : loader))) .build(proxyDesc, clb -> { clb.withSuperclass(CD_Object) - .withFlags(ACC_FINAL | ACC_SYNTHETIC) + .withFlags(ACC_SUPER | ACC_FINAL | ACC_SYNTHETIC) .withInterfaceSymbols(ifaceDesc) // static and instance fields .withField(TYPE_NAME, CD_Class, ACC_PRIVATE | ACC_STATIC | ACC_FINAL) 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 92a37587926..e7b5f942996 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java @@ -2663,6 +2663,8 @@ private MethodHandle findVirtualForVH(String name, MethodType type) { MH_newProcessBuilder.invoke("x", "y", "z"); assertEquals("[x, y, z]", pb.command().toString()); * } + * + * * @param refc the class or interface from which the method is accessed * @param type the type of the method, with the receiver argument omitted, and a void return type * @return the desired method handle @@ -2676,6 +2678,9 @@ public MethodHandle findConstructor(Class refc, MethodType type) throws NoSuc if (refc.isArray()) { throw new NoSuchMethodException("no constructor for array class: " + refc.getName()); } + if (type.returnType() != void.class) { + throw new NoSuchMethodException("Constructors must have void return type: " + refc.getName()); + } String name = ConstantDescs.INIT_NAME; MemberName ctor = resolveOrFail(REF_newInvokeSpecial, refc, name, type); return getDirectConstructor(refc, ctor); @@ -3842,7 +3847,6 @@ private MethodHandle getDirectMethodCommon(byte refKind, Class refc, MemberNa Lookup boundCaller) throws IllegalAccessException { checkMethod(refKind, refc, method); assert(!method.isMethodHandleInvoke()); - if (refKind == REF_invokeSpecial && refc != lookupClass() && !refc.isInterface() && !lookupClass().isInterface() && diff --git a/src/java.base/share/classes/java/lang/invoke/VarHandle.java b/src/java.base/share/classes/java/lang/invoke/VarHandle.java index 9246fdc0395..da08793abd0 100644 --- a/src/java.base/share/classes/java/lang/invoke/VarHandle.java +++ b/src/java.base/share/classes/java/lang/invoke/VarHandle.java @@ -473,6 +473,7 @@ */ public abstract sealed class VarHandle implements Constable permits IndirectVarHandle, LazyInitializingVarHandle, SegmentVarHandle, + ArrayVarHandle, VarHandleByteArrayAsChars.ByteArrayViewVarHandle, VarHandleByteArrayAsDoubles.ByteArrayViewVarHandle, VarHandleByteArrayAsFloats.ByteArrayViewVarHandle, @@ -500,12 +501,15 @@ public abstract sealed class VarHandle implements Constable VarHandleLongs.Array, VarHandleLongs.FieldInstanceReadOnly, VarHandleLongs.FieldStaticReadOnly, - VarHandleReferences.Array, VarHandleReferences.FieldInstanceReadOnly, VarHandleReferences.FieldStaticReadOnly, VarHandleShorts.Array, VarHandleShorts.FieldInstanceReadOnly, - VarHandleShorts.FieldStaticReadOnly { + VarHandleShorts.FieldStaticReadOnly, + VarHandleFlatValues.FieldInstanceReadOnly, + VarHandleNonAtomicReferences.FieldInstanceReadOnly, + VarHandleNonAtomicReferences.FieldStaticReadOnly, + VarHandleNonAtomicFlatValues.FieldInstanceReadOnly { final VarForm vform; final boolean exact; diff --git a/src/java.base/share/classes/java/lang/invoke/VarHandles.java b/src/java.base/share/classes/java/lang/invoke/VarHandles.java index 571587ab42f..971d05cdd33 100644 --- a/src/java.base/share/classes/java/lang/invoke/VarHandles.java +++ b/src/java.base/share/classes/java/lang/invoke/VarHandles.java @@ -26,6 +26,8 @@ package java.lang.invoke; import jdk.internal.misc.CDS; +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; import sun.invoke.util.Wrapper; import java.lang.foreign.MemoryLayout; @@ -51,9 +53,36 @@ static VarHandle makeFieldHandle(MemberName f, Class refc, boolean isWriteAll long foffset = MethodHandleNatives.objectFieldOffset(f); Class type = f.getFieldType(); if (!type.isPrimitive()) { - return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields - ? new VarHandleReferences.FieldInstanceReadOnly(refc, foffset, type) - : new VarHandleReferences.FieldInstanceReadWrite(refc, foffset, type)); + if (type.isValue()) { + int layout = f.getLayout(); + boolean isAtomic = isAtomicFlat(f); + boolean isFlat = f.isFlat(); + if (isFlat) { + if (isAtomic) { + return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields + ? new VarHandleFlatValues.FieldInstanceReadOnly(refc, foffset, type, f.isNullRestricted(), layout) + : new VarHandleFlatValues.FieldInstanceReadWrite(refc, foffset, type, f.isNullRestricted(), layout)); + } else { + return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields + ? new VarHandleNonAtomicFlatValues.FieldInstanceReadOnly(refc, foffset, type, f.isNullRestricted(), layout) + : new VarHandleNonAtomicFlatValues.FieldInstanceReadWrite(refc, foffset, type, f.isNullRestricted(), layout)); + } + } else { + if (isAtomic) { + return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields + ? new VarHandleReferences.FieldInstanceReadOnly(refc, foffset, type, f.isNullRestricted()) + : new VarHandleReferences.FieldInstanceReadWrite(refc, foffset, type, f.isNullRestricted())); + } else { + return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields + ? new VarHandleNonAtomicReferences.FieldInstanceReadOnly(refc, foffset, type, f.isNullRestricted()) + : new VarHandleNonAtomicReferences.FieldInstanceReadWrite(refc, foffset, type, f.isNullRestricted())); + } + } + } else { + return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields + ? new VarHandleReferences.FieldInstanceReadOnly(refc, foffset, type, f.isNullRestricted()) + : new VarHandleReferences.FieldInstanceReadWrite(refc, foffset, type, f.isNullRestricted())); + } } else if (type == boolean.class) { return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields @@ -113,9 +142,22 @@ static VarHandle makeStaticFieldVarHandle(Class decl, MemberName f, boolean i long foffset = MethodHandleNatives.staticFieldOffset(f); Class type = f.getFieldType(); if (!type.isPrimitive()) { - return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields - ? new VarHandleReferences.FieldStaticReadOnly(decl, base, foffset, type) - : new VarHandleReferences.FieldStaticReadWrite(decl, base, foffset, type)); + assert !f.isFlat() : ("static field is flat in " + decl + "." + f.getName()); + if (type.isValue()) { + if (isAtomicFlat(f)) { + return f.isFinal() && !isWriteAllowedOnFinalFields + ? new VarHandleReferences.FieldStaticReadOnly(decl, base, foffset, type, f.isNullRestricted()) + : new VarHandleReferences.FieldStaticReadWrite(decl, base, foffset, type, f.isNullRestricted()); + } else { + return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields + ? new VarHandleNonAtomicReferences.FieldStaticReadOnly(decl, base, foffset, type, f.isNullRestricted()) + : new VarHandleNonAtomicReferences.FieldStaticReadWrite(decl, base, foffset, type, f.isNullRestricted())); + } + } else { + return f.isFinal() && !isWriteAllowedOnFinalFields + ? new VarHandleReferences.FieldStaticReadOnly(decl, base, foffset, type, f.isNullRestricted()) + : new VarHandleReferences.FieldStaticReadWrite(decl, base, foffset, type, f.isNullRestricted()); + } } else if (type == boolean.class) { return maybeAdapt(f.isFinal() && !isWriteAllowedOnFinalFields @@ -162,6 +204,36 @@ else if (type == double.class) { } } + static boolean isAtomicFlat(MemberName field) { + boolean hasAtomicAccess = (field.getModifiers() & Modifier.VOLATILE) != 0 || + !(field.isNullRestricted()) || + !field.getFieldType().isAnnotationPresent(LooselyConsistentValue.class); + return hasAtomicAccess && !HAS_OOPS.get(field.getFieldType()); + } + + static boolean isAtomicFlat(Object[] array) { + Class componentType = array.getClass().componentType(); + boolean hasAtomicAccess = ValueClass.isAtomicArray(array) || + !ValueClass.isNullRestrictedArray(array) || + !componentType.isAnnotationPresent(LooselyConsistentValue.class); + return hasAtomicAccess && !HAS_OOPS.get(componentType); + } + + static final ClassValue HAS_OOPS = new ClassValue<>() { + @Override + protected Boolean computeValue(Class c) { + for (Field f : c.getDeclaredFields()) { + Class ftype = f.getType(); + if (UNSAFE.isFlatField(f) && HAS_OOPS.get(ftype)) { + return true; + } else if (!ftype.isPrimitive()) { + return true; + } + } + return false; + } + }; + // Required by instance field handles static Field getFieldFromReceiverAndOffset(Class receiverType, long offset, @@ -192,42 +264,48 @@ static Field getStaticFieldFromBaseAndOffset(Class declaringClass, throw new InternalError("Static field not found at offset"); } + // This is invoked by non-flat array var handle code when attempting to access a flat array + public static void checkAtomicFlatArray(Object[] array) { + if (!isAtomicFlat(array)) { + throw new IllegalArgumentException("Attempt to perform a non-plain access on a non-atomic array"); + } + } + static VarHandle makeArrayElementHandle(Class arrayClass) { if (!arrayClass.isArray()) throw new IllegalArgumentException("not an array: " + arrayClass); Class componentType = arrayClass.getComponentType(); - - int aoffset = (int) UNSAFE.arrayBaseOffset(arrayClass); - int ascale = UNSAFE.arrayIndexScale(arrayClass); - int ashift = 31 - Integer.numberOfLeadingZeros(ascale); - if (!componentType.isPrimitive()) { - return maybeAdapt(new VarHandleReferences.Array(aoffset, ashift, arrayClass)); + // Here we always return a reference array element var handle. This is because + // the access semantics is determined at runtime, when an actual array object is passed + // to the var handle. The var handle implementation will switch to use flat access + // primitives if it sees a flat array. + return maybeAdapt(new ArrayVarHandle(arrayClass)); } else if (componentType == boolean.class) { - return maybeAdapt(new VarHandleBooleans.Array(aoffset, ashift)); + return maybeAdapt(VarHandleBooleans.Array.NON_EXACT_INSTANCE); } else if (componentType == byte.class) { - return maybeAdapt(new VarHandleBytes.Array(aoffset, ashift)); + return maybeAdapt(VarHandleBytes.Array.NON_EXACT_INSTANCE); } else if (componentType == short.class) { - return maybeAdapt(new VarHandleShorts.Array(aoffset, ashift)); + return maybeAdapt(VarHandleShorts.Array.NON_EXACT_INSTANCE); } else if (componentType == char.class) { - return maybeAdapt(new VarHandleChars.Array(aoffset, ashift)); + return maybeAdapt(VarHandleChars.Array.NON_EXACT_INSTANCE); } else if (componentType == int.class) { - return maybeAdapt(new VarHandleInts.Array(aoffset, ashift)); + return maybeAdapt(VarHandleInts.Array.NON_EXACT_INSTANCE); } else if (componentType == long.class) { - return maybeAdapt(new VarHandleLongs.Array(aoffset, ashift)); + return maybeAdapt(VarHandleLongs.Array.NON_EXACT_INSTANCE); } else if (componentType == float.class) { - return maybeAdapt(new VarHandleFloats.Array(aoffset, ashift)); + return maybeAdapt(VarHandleFloats.Array.NON_EXACT_INSTANCE); } else if (componentType == double.class) { - return maybeAdapt(new VarHandleDoubles.Array(aoffset, ashift)); + return maybeAdapt(VarHandleDoubles.Array.NON_EXACT_INSTANCE); } else { throw new UnsupportedOperationException(); diff --git a/src/java.base/share/classes/java/lang/invoke/X-VarHandle.java.template b/src/java.base/share/classes/java/lang/invoke/X-VarHandle.java.template index 4bc9daf8433..65897f29bff 100644 --- a/src/java.base/share/classes/java/lang/invoke/X-VarHandle.java.template +++ b/src/java.base/share/classes/java/lang/invoke/X-VarHandle.java.template @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,10 +24,14 @@ */ package java.lang.invoke; +#if[Object] +import jdk.internal.value.ValueClass; +#end[Object] import jdk.internal.util.Preconditions; import jdk.internal.vm.annotation.ForceInline; import java.lang.invoke.VarHandle.VarHandleDesc; +import java.lang.reflect.Field; import java.util.Objects; import java.util.Optional; @@ -35,41 +39,49 @@ import static java.lang.invoke.MethodHandleStatics.UNSAFE; #warn -final class VarHandle$Type$s { +final class VarHandle$InputType$s { static sealed class FieldInstanceReadOnly extends VarHandle { final long fieldOffset; final Class receiverType; #if[Object] final Class fieldType; + final boolean nullRestricted; #end[Object] +#if[FlatValue] + final int layout; // Unsafe.fieldLayout +#end[FlatValue] - FieldInstanceReadOnly(Class receiverType, long fieldOffset{#if[Object]?, Class fieldType}) { - this(receiverType, fieldOffset{#if[Object]?, fieldType}, FieldInstanceReadOnly.FORM, false); + FieldInstanceReadOnly(Class receiverType, long fieldOffset{#if[Object]?, Class fieldType, boolean nullRestricted}{#if[FlatValue]?, int layout}) { + this(receiverType, fieldOffset{#if[Object]?, fieldType, nullRestricted}{#if[FlatValue]?, layout}, FieldInstanceReadOnly.FORM, false); } - protected FieldInstanceReadOnly(Class receiverType, long fieldOffset{#if[Object]?, Class fieldType}, + protected FieldInstanceReadOnly(Class receiverType, long fieldOffset{#if[Object]?, Class fieldType, boolean nullRestricted}{#if[FlatValue]?, int layout}, VarForm form, boolean exact) { super(form, exact); this.fieldOffset = fieldOffset; this.receiverType = receiverType; #if[Object] this.fieldType = fieldType; + this.nullRestricted = nullRestricted; #end[Object] +#if[FlatValue] + this.layout = layout; +#end[FlatValue] } @Override public FieldInstanceReadOnly withInvokeExactBehavior() { return hasInvokeExactBehavior() ? this - : new FieldInstanceReadOnly(receiverType, fieldOffset{#if[Object]?, fieldType}, vform, true); + : new FieldInstanceReadOnly(receiverType, fieldOffset{#if[Object]?, fieldType, nullRestricted}{#if[FlatValue]?, layout}, vform, true); } @Override public FieldInstanceReadOnly withInvokeBehavior() { return !hasInvokeExactBehavior() ? this - : new FieldInstanceReadOnly(receiverType, fieldOffset{#if[Object]?, fieldType}, vform, false); + : new FieldInstanceReadOnly(receiverType, fieldOffset{#if[Object]?, fieldType, nullRestricted}{#if[FlatValue]?, layout}, vform, false); } @Override @@ -93,89 +105,127 @@ final class VarHandle$Type$s { @ForceInline static $type$ get(VarHandle ob, Object holder) { FieldInstanceReadOnly handle = (FieldInstanceReadOnly)ob; - return UNSAFE.get$Type$(Objects.requireNonNull(handle.receiverType.cast(holder)), - handle.fieldOffset); + $type$ value = UNSAFE.get$Type$(Objects.requireNonNull(handle.receiverType.cast(holder)), + handle.fieldOffset{#if[FlatValue]?, handle.layout, handle.fieldType}); +#if[Reference] + if (value == null && handle.nullRestricted) { + throw new NullPointerException("Uninitialized null-restricted field"); + } +#end[Reference] + return value; } +#if[NonPlainAccess] @ForceInline static $type$ getVolatile(VarHandle ob, Object holder) { FieldInstanceReadOnly handle = (FieldInstanceReadOnly)ob; - return UNSAFE.get$Type$Volatile(Objects.requireNonNull(handle.receiverType.cast(holder)), - handle.fieldOffset); + $type$ value = UNSAFE.get$Type$Volatile(Objects.requireNonNull(handle.receiverType.cast(holder)), + handle.fieldOffset{#if[FlatValue]?, handle.layout, handle.fieldType}); +#if[Reference] + if (value == null && handle.nullRestricted) { + throw new NullPointerException("Uninitialized null-restricted field"); + } +#end[Reference] + return value; } @ForceInline static $type$ getOpaque(VarHandle ob, Object holder) { FieldInstanceReadOnly handle = (FieldInstanceReadOnly)ob; - return UNSAFE.get$Type$Opaque(Objects.requireNonNull(handle.receiverType.cast(holder)), - handle.fieldOffset); + $type$ value = UNSAFE.get$Type$Opaque(Objects.requireNonNull(handle.receiverType.cast(holder)), + handle.fieldOffset{#if[FlatValue]?, handle.layout, handle.fieldType}); +#if[Reference] + if (value == null && handle.nullRestricted) { + throw new NullPointerException("Uninitialized null-restricted field"); + + } +#end[Reference] + return value; } @ForceInline static $type$ getAcquire(VarHandle ob, Object holder) { FieldInstanceReadOnly handle = (FieldInstanceReadOnly)ob; - return UNSAFE.get$Type$Acquire(Objects.requireNonNull(handle.receiverType.cast(holder)), - handle.fieldOffset); + $type$ value = UNSAFE.get$Type$Acquire(Objects.requireNonNull(handle.receiverType.cast(holder)), + handle.fieldOffset{#if[FlatValue]?, handle.layout, handle.fieldType}); +#if[Reference] + if (value == null && handle.nullRestricted) { + throw new NullPointerException("Uninitialized null-restricted field"); + } +#end[Reference] + return value; } +#end[NonPlainAccess] static final VarForm FORM = new VarForm(FieldInstanceReadOnly.class, Object.class, $type$.class); } static final class FieldInstanceReadWrite extends FieldInstanceReadOnly { - - FieldInstanceReadWrite(Class receiverType, long fieldOffset{#if[Object]?, Class fieldType}) { - this(receiverType, fieldOffset{#if[Object]?, fieldType}, false); + FieldInstanceReadWrite(Class receiverType, long fieldOffset{#if[Object]?, Class fieldType, boolean nullRestricted}{#if[FlatValue]?, int layout}) { + this(receiverType, fieldOffset{#if[Object]?, fieldType, nullRestricted}{#if[FlatValue]?, layout}, false); } - private FieldInstanceReadWrite(Class receiverType, long fieldOffset{#if[Object]?, Class fieldType}, + private FieldInstanceReadWrite(Class receiverType, long fieldOffset{#if[Object]?, Class fieldType, boolean nullRestricted}{#if[FlatValue]?, int layout}, boolean exact) { - super(receiverType, fieldOffset{#if[Object]?, fieldType}, FieldInstanceReadWrite.FORM, exact); + super(receiverType, fieldOffset{#if[Object]?, fieldType, nullRestricted}{#if[FlatValue]?, layout}, FieldInstanceReadWrite.FORM, exact); } @Override public FieldInstanceReadWrite withInvokeExactBehavior() { return hasInvokeExactBehavior() ? this - : new FieldInstanceReadWrite(receiverType, fieldOffset{#if[Object]?, fieldType}, true); + : new FieldInstanceReadWrite(receiverType, fieldOffset{#if[Object]?, fieldType, nullRestricted}{#if[FlatValue]?, layout}, true); } @Override public FieldInstanceReadWrite withInvokeBehavior() { return !hasInvokeExactBehavior() ? this - : new FieldInstanceReadWrite(receiverType, fieldOffset{#if[Object]?, fieldType}, false); + : new FieldInstanceReadWrite(receiverType, fieldOffset{#if[Object]?, fieldType, nullRestricted}{#if[FlatValue]?, layout}, false); + } + +#if[Object] + @ForceInline + static Object checkCast(FieldInstanceReadWrite handle, $type$ value) { +#if[Reference] + if (value == null && handle.nullRestricted) + throw new NullPointerException("Uninitialized null-restricted field"); +#end[Reference] + return handle.fieldType.cast(value); } +#end[Object] @ForceInline static void set(VarHandle ob, Object holder, $type$ value) { FieldInstanceReadWrite handle = (FieldInstanceReadWrite)ob; UNSAFE.put$Type$(Objects.requireNonNull(handle.receiverType.cast(holder)), - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout, handle.fieldType}, + {#if[Object]?checkCast(handle, value):value}); } +#if[NonPlainAccess] @ForceInline static void setVolatile(VarHandle ob, Object holder, $type$ value) { FieldInstanceReadWrite handle = (FieldInstanceReadWrite)ob; UNSAFE.put$Type$Volatile(Objects.requireNonNull(handle.receiverType.cast(holder)), - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout, handle.fieldType}, + {#if[Object]?checkCast(handle, value):value}); } @ForceInline static void setOpaque(VarHandle ob, Object holder, $type$ value) { FieldInstanceReadWrite handle = (FieldInstanceReadWrite)ob; UNSAFE.put$Type$Opaque(Objects.requireNonNull(handle.receiverType.cast(holder)), - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout, handle.fieldType}, + {#if[Object]?checkCast(handle, value):value}); } @ForceInline static void setRelease(VarHandle ob, Object holder, $type$ value) { FieldInstanceReadWrite handle = (FieldInstanceReadWrite)ob; UNSAFE.put$Type$Release(Objects.requireNonNull(handle.receiverType.cast(holder)), - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout, handle.fieldType}, + {#if[Object]?checkCast(handle, value):value}); } #if[CAS] @@ -183,96 +233,96 @@ final class VarHandle$Type$s { static boolean compareAndSet(VarHandle ob, Object holder, $type$ expected, $type$ value) { FieldInstanceReadWrite handle = (FieldInstanceReadWrite)ob; return UNSAFE.compareAndSet$Type$(Objects.requireNonNull(handle.receiverType.cast(holder)), - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(expected):expected}, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout}{#if[Object]?, handle.fieldType}, + {#if[Object]?checkCast(handle, expected):expected}, + {#if[Object]?checkCast(handle, value):value}); } @ForceInline static $type$ compareAndExchange(VarHandle ob, Object holder, $type$ expected, $type$ value) { FieldInstanceReadWrite handle = (FieldInstanceReadWrite)ob; return UNSAFE.compareAndExchange$Type$(Objects.requireNonNull(handle.receiverType.cast(holder)), - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(expected):expected}, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout}{#if[Object]?, handle.fieldType}, + {#if[Object]?checkCast(handle, expected):expected}, + {#if[Object]?checkCast(handle, value):value}); } @ForceInline static $type$ compareAndExchangeAcquire(VarHandle ob, Object holder, $type$ expected, $type$ value) { FieldInstanceReadWrite handle = (FieldInstanceReadWrite)ob; return UNSAFE.compareAndExchange$Type$Acquire(Objects.requireNonNull(handle.receiverType.cast(holder)), - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(expected):expected}, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout}{#if[Object]?, handle.fieldType}, + {#if[Object]?checkCast(handle, expected):expected}, + {#if[Object]?checkCast(handle, value):value}); } @ForceInline static $type$ compareAndExchangeRelease(VarHandle ob, Object holder, $type$ expected, $type$ value) { FieldInstanceReadWrite handle = (FieldInstanceReadWrite)ob; return UNSAFE.compareAndExchange$Type$Release(Objects.requireNonNull(handle.receiverType.cast(holder)), - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(expected):expected}, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout}{#if[Object]?, handle.fieldType}, + {#if[Object]?checkCast(handle, expected):expected}, + {#if[Object]?checkCast(handle, value):value}); } @ForceInline static boolean weakCompareAndSetPlain(VarHandle ob, Object holder, $type$ expected, $type$ value) { FieldInstanceReadWrite handle = (FieldInstanceReadWrite)ob; return UNSAFE.weakCompareAndSet$Type$Plain(Objects.requireNonNull(handle.receiverType.cast(holder)), - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(expected):expected}, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout}{#if[Object]?, handle.fieldType}, + {#if[Object]?checkCast(handle, expected):expected}, + {#if[Object]?checkCast(handle, value):value}); } @ForceInline static boolean weakCompareAndSet(VarHandle ob, Object holder, $type$ expected, $type$ value) { FieldInstanceReadWrite handle = (FieldInstanceReadWrite)ob; return UNSAFE.weakCompareAndSet$Type$(Objects.requireNonNull(handle.receiverType.cast(holder)), - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(expected):expected}, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout}{#if[Object]?, handle.fieldType}, + {#if[Object]?checkCast(handle, expected):expected}, + {#if[Object]?checkCast(handle, value):value}); } @ForceInline static boolean weakCompareAndSetAcquire(VarHandle ob, Object holder, $type$ expected, $type$ value) { FieldInstanceReadWrite handle = (FieldInstanceReadWrite)ob; return UNSAFE.weakCompareAndSet$Type$Acquire(Objects.requireNonNull(handle.receiverType.cast(holder)), - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(expected):expected}, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout}{#if[Object]?, handle.fieldType}, + {#if[Object]?checkCast(handle, expected):expected}, + {#if[Object]?checkCast(handle, value):value}); } @ForceInline static boolean weakCompareAndSetRelease(VarHandle ob, Object holder, $type$ expected, $type$ value) { FieldInstanceReadWrite handle = (FieldInstanceReadWrite)ob; return UNSAFE.weakCompareAndSet$Type$Release(Objects.requireNonNull(handle.receiverType.cast(holder)), - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(expected):expected}, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout}{#if[Object]?, handle.fieldType}, + {#if[Object]?checkCast(handle, expected):expected}, + {#if[Object]?checkCast(handle, value):value}); } @ForceInline static $type$ getAndSet(VarHandle ob, Object holder, $type$ value) { FieldInstanceReadWrite handle = (FieldInstanceReadWrite)ob; return UNSAFE.getAndSet$Type$(Objects.requireNonNull(handle.receiverType.cast(holder)), - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout}{#if[Object]?, handle.fieldType}, + {#if[Object]?checkCast(handle, value):value}); } @ForceInline static $type$ getAndSetAcquire(VarHandle ob, Object holder, $type$ value) { FieldInstanceReadWrite handle = (FieldInstanceReadWrite)ob; return UNSAFE.getAndSet$Type$Acquire(Objects.requireNonNull(handle.receiverType.cast(holder)), - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout}{#if[Object]?, handle.fieldType}, + {#if[Object]?checkCast(handle, value):value}); } @ForceInline static $type$ getAndSetRelease(VarHandle ob, Object holder, $type$ value) { FieldInstanceReadWrite handle = (FieldInstanceReadWrite)ob; return UNSAFE.getAndSet$Type$Release(Objects.requireNonNull(handle.receiverType.cast(holder)), - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout}{#if[Object]?, handle.fieldType}, + {#if[Object]?checkCast(handle, value):value}); } #end[CAS] #if[AtomicAdd] @@ -376,24 +426,29 @@ final class VarHandle$Type$s { value); } #end[Bitwise] +#end[NonPlainAccess] static final VarForm FORM = new VarForm(FieldInstanceReadWrite.class, Object.class, $type$.class); } - +#if[Static] static sealed class FieldStaticReadOnly extends VarHandle { final Class declaringClass; final Object base; final long fieldOffset; #if[Object] final Class fieldType; + final boolean nullRestricted; #end[Object] +#if[FlatValue] + final int layout; +#end[FlatValue] - FieldStaticReadOnly(Class declaringClass, Object base, long fieldOffset{#if[Object]?, Class fieldType}) { - this(declaringClass, base, fieldOffset{#if[Object]?, fieldType}, FieldStaticReadOnly.FORM, false); + FieldStaticReadOnly(Class declaringClass, Object base, long fieldOffset{#if[Object]?, Class fieldType, boolean nullRestricted}{#if[FlatValue]?, int layout}) { + this(declaringClass, base, fieldOffset{#if[Object]?, fieldType, nullRestricted}{#if[FlatValue]?, layout}, FieldStaticReadOnly.FORM, false); } - protected FieldStaticReadOnly(Class declaringClass, Object base, long fieldOffset{#if[Object]?, Class fieldType}, + protected FieldStaticReadOnly(Class declaringClass, Object base, long fieldOffset{#if[Object]?, Class fieldType, boolean nullRestricted}{#if[FlatValue]?, int layout}, VarForm form, boolean exact) { super(form, exact); this.declaringClass = declaringClass; @@ -401,21 +456,25 @@ final class VarHandle$Type$s { this.fieldOffset = fieldOffset; #if[Object] this.fieldType = fieldType; + this.nullRestricted = nullRestricted; #end[Object] +#if[FlatValue] + this.layout = layout; +#end[FlatValue] } @Override public FieldStaticReadOnly withInvokeExactBehavior() { return hasInvokeExactBehavior() ? this - : new FieldStaticReadOnly(declaringClass, base, fieldOffset{#if[Object]?, fieldType}, vform, true); + : new FieldStaticReadOnly(declaringClass, base, fieldOffset{#if[Object]?, fieldType, nullRestricted}{#if[FlatValue]?, layout}, vform, true); } @Override public FieldStaticReadOnly withInvokeBehavior() { return !hasInvokeExactBehavior() ? this - : new FieldStaticReadOnly(declaringClass, base, fieldOffset{#if[Object]?, fieldType}, vform, false); + : new FieldStaticReadOnly(declaringClass, base, fieldOffset{#if[Object]?, fieldType, nullRestricted}{#if[FlatValue]?, layout}, vform, false); } @Override @@ -441,89 +500,123 @@ final class VarHandle$Type$s { @ForceInline static $type$ get(VarHandle ob) { FieldStaticReadOnly handle = (FieldStaticReadOnly) ob.target(); - return UNSAFE.get$Type$(handle.base, - handle.fieldOffset); + $type$ value = UNSAFE.get$Type$(handle.base, + handle.fieldOffset{#if[FlatValue]?, handle.layout, handle.fieldType}); +#if[Reference] + if (value == null && handle.nullRestricted) { + throw new NullPointerException("Uninitialized null-restricted field"); + } +#end[Reference] + return value; } +#if[NonPlainAccess] @ForceInline static $type$ getVolatile(VarHandle ob) { FieldStaticReadOnly handle = (FieldStaticReadOnly) ob.target(); - return UNSAFE.get$Type$Volatile(handle.base, - handle.fieldOffset); + $type$ value = UNSAFE.get$Type$Volatile(handle.base, + handle.fieldOffset{#if[FlatValue]?, handle.layout, handle.fieldType}); +#if[Reference] + if (value == null && handle.nullRestricted) { + throw new NullPointerException("Uninitialized null-restricted field"); + } +#end[Reference] + return value; } @ForceInline static $type$ getOpaque(VarHandle ob) { FieldStaticReadOnly handle = (FieldStaticReadOnly) ob.target(); - return UNSAFE.get$Type$Opaque(handle.base, - handle.fieldOffset); + $type$ value = UNSAFE.get$Type$Opaque(handle.base, + handle.fieldOffset{#if[FlatValue]?, handle.layout, handle.fieldType}); +#if[Reference] + if (value == null && handle.nullRestricted) { + throw new NullPointerException("Uninitialized null-restricted field"); + } +#end[Reference] + return value; } @ForceInline static $type$ getAcquire(VarHandle ob) { FieldStaticReadOnly handle = (FieldStaticReadOnly) ob.target(); - return UNSAFE.get$Type$Acquire(handle.base, - handle.fieldOffset); + $type$ value = UNSAFE.get$Type$Acquire(handle.base, + handle.fieldOffset{#if[FlatValue]?, handle.layout, handle.fieldType}); +#if[Reference] + if (value == null && handle.nullRestricted) { + throw new NullPointerException("Uninitialized null-restricted field"); + } +#end[Reference] + return value; } +#end[NonPlainAccess] static final VarForm FORM = new VarForm(FieldStaticReadOnly.class, null, $type$.class); } static final class FieldStaticReadWrite extends FieldStaticReadOnly { - FieldStaticReadWrite(Class declaringClass, Object base, long fieldOffset{#if[Object]?, Class fieldType}) { - this(declaringClass, base, fieldOffset{#if[Object]?, fieldType}, false); + FieldStaticReadWrite(Class declaringClass, Object base, long fieldOffset{#if[Object]?, Class fieldType, boolean nullRestricted}{#if[FlatValue]?, int layout}) { + this(declaringClass, base, fieldOffset{#if[Object]?, fieldType, nullRestricted}{#if[FlatValue]?, layout}, false); } - private FieldStaticReadWrite(Class declaringClass, Object base, long fieldOffset{#if[Object]?, Class fieldType}, + private FieldStaticReadWrite(Class declaringClass, Object base, long fieldOffset{#if[Object]?, Class fieldType, boolean nullRestricted}{#if[FlatValue]?, int layout}, boolean exact) { - super(declaringClass, base, fieldOffset{#if[Object]?, fieldType}, FieldStaticReadWrite.FORM, exact); + super(declaringClass, base, fieldOffset{#if[Object]?, fieldType, nullRestricted}{#if[FlatValue]?, layout}, FieldStaticReadWrite.FORM, exact); } @Override public FieldStaticReadWrite withInvokeExactBehavior() { return hasInvokeExactBehavior() ? this - : new FieldStaticReadWrite(declaringClass, base, fieldOffset{#if[Object]?, fieldType}, true); + : new FieldStaticReadWrite(declaringClass, base, fieldOffset{#if[Object]?, fieldType, nullRestricted}{#if[FlatValue]?, layout}, true); } @Override public FieldStaticReadWrite withInvokeBehavior() { return !hasInvokeExactBehavior() ? this - : new FieldStaticReadWrite(declaringClass, base, fieldOffset{#if[Object]?, fieldType}, false); + : new FieldStaticReadWrite(declaringClass, base, fieldOffset{#if[Object]?, fieldType, nullRestricted}{#if[FlatValue]?, layout}, false); } +#if[Object] + @ForceInline + static Object checkCast(FieldStaticReadWrite handle, $type$ value) { + return handle.fieldType.cast(value); + } +#end[Object] + @ForceInline static void set(VarHandle ob, $type$ value) { FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target(); UNSAFE.put$Type$(handle.base, - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout, handle.fieldType}, + {#if[Object]?checkCast(handle, value):value}); } +#if[NonPlainAccess] @ForceInline static void setVolatile(VarHandle ob, $type$ value) { FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target(); UNSAFE.put$Type$Volatile(handle.base, - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout, handle.fieldType}, + {#if[Object]?checkCast(handle, value):value}); } @ForceInline static void setOpaque(VarHandle ob, $type$ value) { FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target(); UNSAFE.put$Type$Opaque(handle.base, - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout, handle.fieldType}, + {#if[Object]?checkCast(handle, value):value}); } @ForceInline static void setRelease(VarHandle ob, $type$ value) { FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target(); UNSAFE.put$Type$Release(handle.base, - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout, handle.fieldType}, + {#if[Object]?checkCast(handle, value):value}); } #if[CAS] @@ -531,9 +624,9 @@ final class VarHandle$Type$s { static boolean compareAndSet(VarHandle ob, $type$ expected, $type$ value) { FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target(); return UNSAFE.compareAndSet$Type$(handle.base, - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(expected):expected}, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout}{#if[Object]?, handle.fieldType}, + {#if[Object]?checkCast(handle, expected):expected}, + {#if[Object]?checkCast(handle, value):value}); } @@ -541,87 +634,87 @@ final class VarHandle$Type$s { static $type$ compareAndExchange(VarHandle ob, $type$ expected, $type$ value) { FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target(); return UNSAFE.compareAndExchange$Type$(handle.base, - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(expected):expected}, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout}{#if[Object]?, handle.fieldType}, + {#if[Object]?checkCast(handle, expected):expected}, + {#if[Object]?checkCast(handle, value):value}); } @ForceInline static $type$ compareAndExchangeAcquire(VarHandle ob, $type$ expected, $type$ value) { FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target(); return UNSAFE.compareAndExchange$Type$Acquire(handle.base, - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(expected):expected}, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout}{#if[Object]?, handle.fieldType}, + {#if[Object]?checkCast(handle, expected):expected}, + {#if[Object]?checkCast(handle, value):value}); } @ForceInline static $type$ compareAndExchangeRelease(VarHandle ob, $type$ expected, $type$ value) { FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target(); return UNSAFE.compareAndExchange$Type$Release(handle.base, - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(expected):expected}, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout}{#if[Object]?, handle.fieldType}, + {#if[Object]?checkCast(handle, expected):expected}, + {#if[Object]?checkCast(handle, value):value}); } @ForceInline static boolean weakCompareAndSetPlain(VarHandle ob, $type$ expected, $type$ value) { FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target(); return UNSAFE.weakCompareAndSet$Type$Plain(handle.base, - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(expected):expected}, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout}{#if[Object]?, handle.fieldType}, + {#if[Object]?checkCast(handle, expected):expected}, + {#if[Object]?checkCast(handle, value):value}); } @ForceInline static boolean weakCompareAndSet(VarHandle ob, $type$ expected, $type$ value) { FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target(); return UNSAFE.weakCompareAndSet$Type$(handle.base, - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(expected):expected}, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout}{#if[Object]?, handle.fieldType}, + {#if[Object]?checkCast(handle, expected):expected}, + {#if[Object]?checkCast(handle, value):value}); } @ForceInline static boolean weakCompareAndSetAcquire(VarHandle ob, $type$ expected, $type$ value) { FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target(); return UNSAFE.weakCompareAndSet$Type$Acquire(handle.base, - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(expected):expected}, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout}{#if[Object]?, handle.fieldType}, + {#if[Object]?checkCast(handle, expected):expected}, + {#if[Object]?checkCast(handle, value):value}); } @ForceInline static boolean weakCompareAndSetRelease(VarHandle ob, $type$ expected, $type$ value) { FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target(); return UNSAFE.weakCompareAndSet$Type$Release(handle.base, - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(expected):expected}, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout}{#if[Object]?, handle.fieldType}, + {#if[Object]?checkCast(handle, expected):expected}, + {#if[Object]?checkCast(handle, value):value}); } @ForceInline static $type$ getAndSet(VarHandle ob, $type$ value) { FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target(); return UNSAFE.getAndSet$Type$(handle.base, - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout}{#if[Object]?, handle.fieldType}, + {#if[Object]?checkCast(handle, value):value}); } @ForceInline static $type$ getAndSetAcquire(VarHandle ob, $type$ value) { FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target(); return UNSAFE.getAndSet$Type$Acquire(handle.base, - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout}{#if[Object]?, handle.fieldType}, + {#if[Object]?checkCast(handle, value):value}); } @ForceInline static $type$ getAndSetRelease(VarHandle ob, $type$ value) { FieldStaticReadWrite handle = (FieldStaticReadWrite) ob.target(); return UNSAFE.getAndSet$Type$Release(handle.base, - handle.fieldOffset, - {#if[Object]?handle.fieldType.cast(value):value}); + handle.fieldOffset{#if[FlatValue]?, handle.layout}{#if[Object]?, handle.fieldType}, + {#if[Object]?checkCast(handle, value):value}); } #end[CAS] #if[AtomicAdd] @@ -724,358 +817,223 @@ final class VarHandle$Type$s { value); } #end[Bitwise] +#end[NonPlainAccess] static final VarForm FORM = new VarForm(FieldStaticReadWrite.class, null, $type$.class); } +#end[Static] - +#if[Array] static final class Array extends VarHandle { - final int abase; - final int ashift; -#if[Object] - final Class<{#if[Object]??:$type$[]}> arrayType; - final Class componentType; -#end[Object] - - Array(int abase, int ashift{#if[Object]?, Class arrayType}) { - this(abase, ashift{#if[Object]?, arrayType}, false); - } + static final long BASE = UNSAFE.arrayBaseOffset($type$[].class); + static final int SHIFT = Integer.numberOfTrailingZeros(UNSAFE.arrayIndexScale($type$[].class)); - private Array(int abase, int ashift{#if[Object]?, Class arrayType}, boolean exact) { + private Array(boolean exact) { super(Array.FORM, exact); - this.abase = abase; - this.ashift = ashift; -#if[Object] - this.arrayType = {#if[Object]?arrayType:$type$[].class}; - this.componentType = arrayType.getComponentType(); -#end[Object] } @Override public Array withInvokeExactBehavior() { return hasInvokeExactBehavior() ? this - : new Array(abase, ashift{#if[Object]?, arrayType}, true); + : EXACT_INSTANCE; } @Override public Array withInvokeBehavior() { return !hasInvokeExactBehavior() ? this - : new Array(abase, ashift{#if[Object]?, arrayType}, false); + : NON_EXACT_INSTANCE; } @Override public Optional describeConstable() { - var arrayTypeRef = {#if[Object]?arrayType:$type$[].class}.describeConstable(); - if (!arrayTypeRef.isPresent()) - return Optional.empty(); - - return Optional.of(VarHandleDesc.ofArray(arrayTypeRef.get())); + return Optional.of(VarHandleDesc.ofArray($type$[].class.describeConstable().orElseThrow())); } @Override final MethodType accessModeTypeUncached(AccessType at) { - return at.accessModeType({#if[Object]?arrayType:$type$[].class}, {#if[Object]?arrayType.getComponentType():$type$.class}, int.class); + return at.accessModeType($type$[].class, $type$.class, int.class); } -#if[Object] - @ForceInline - static Object runtimeTypeCheck(Array handle, Object[] oarray, Object value) { - if (handle.arrayType == oarray.getClass()) { - // Fast path: static array type same as argument array type - return handle.componentType.cast(value); - } else { - // Slow path: check value against argument array component type - return reflectiveTypeCheck(oarray, value); - } - } - - @ForceInline - static Object reflectiveTypeCheck(Object[] oarray, Object value) { - try { - return oarray.getClass().getComponentType().cast(value); - } catch (ClassCastException e) { - throw new ArrayStoreException(); - } - } -#end[Object] - @ForceInline static $type$ get(VarHandle ob, Object oarray, int index) { - Array handle = (Array)ob; -#if[Object] - Object[] array = (Object[]) handle.arrayType.cast(oarray); -#else[Object] $type$[] array = ($type$[]) oarray; -#end[Object] return array[index]; } @ForceInline static void set(VarHandle ob, Object oarray, int index, $type$ value) { - Array handle = (Array)ob; -#if[Object] - Object[] array = (Object[]) handle.arrayType.cast(oarray); -#else[Object] $type$[] array = ($type$[]) oarray; -#end[Object] - array[index] = {#if[Object]?handle.componentType.cast(value):value}; + array[index] = value; } +#if[NonPlainAccess] @ForceInline static $type$ getVolatile(VarHandle ob, Object oarray, int index) { - Array handle = (Array)ob; -#if[Object] - Object[] array = (Object[]) handle.arrayType.cast(oarray); -#else[Object] $type$[] array = ($type$[]) oarray; -#end[Object] return UNSAFE.get$Type$Volatile(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase); + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE); } @ForceInline static void setVolatile(VarHandle ob, Object oarray, int index, $type$ value) { - Array handle = (Array)ob; -#if[Object] - Object[] array = (Object[]) handle.arrayType.cast(oarray); -#else[Object] $type$[] array = ($type$[]) oarray; -#end[Object] UNSAFE.put$Type$Volatile(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, - {#if[Object]?runtimeTypeCheck(handle, array, value):value}); + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, + value); } @ForceInline static $type$ getOpaque(VarHandle ob, Object oarray, int index) { - Array handle = (Array)ob; -#if[Object] - Object[] array = (Object[]) handle.arrayType.cast(oarray); -#else[Object] $type$[] array = ($type$[]) oarray; -#end[Object] return UNSAFE.get$Type$Opaque(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase); + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE); } @ForceInline static void setOpaque(VarHandle ob, Object oarray, int index, $type$ value) { - Array handle = (Array)ob; -#if[Object] - Object[] array = (Object[]) handle.arrayType.cast(oarray); -#else[Object] $type$[] array = ($type$[]) oarray; -#end[Object] UNSAFE.put$Type$Opaque(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, - {#if[Object]?runtimeTypeCheck(handle, array, value):value}); + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, + value); } @ForceInline static $type$ getAcquire(VarHandle ob, Object oarray, int index) { - Array handle = (Array)ob; -#if[Object] - Object[] array = (Object[]) handle.arrayType.cast(oarray); -#else[Object] $type$[] array = ($type$[]) oarray; -#end[Object] return UNSAFE.get$Type$Acquire(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase); + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE); } @ForceInline static void setRelease(VarHandle ob, Object oarray, int index, $type$ value) { - Array handle = (Array)ob; -#if[Object] - Object[] array = (Object[]) handle.arrayType.cast(oarray); -#else[Object] $type$[] array = ($type$[]) oarray; -#end[Object] UNSAFE.put$Type$Release(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, - {#if[Object]?runtimeTypeCheck(handle, array, value):value}); + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, + value); } #if[CAS] @ForceInline static boolean compareAndSet(VarHandle ob, Object oarray, int index, $type$ expected, $type$ value) { - Array handle = (Array)ob; -#if[Object] - Object[] array = (Object[]) handle.arrayType.cast(oarray); -#else[Object] $type$[] array = ($type$[]) oarray; -#end[Object] return UNSAFE.compareAndSet$Type$(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, - {#if[Object]?handle.componentType.cast(expected):expected}, - {#if[Object]?runtimeTypeCheck(handle, array, value):value}); + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, + expected, + value); } @ForceInline static $type$ compareAndExchange(VarHandle ob, Object oarray, int index, $type$ expected, $type$ value) { - Array handle = (Array)ob; -#if[Object] - Object[] array = (Object[]) handle.arrayType.cast(oarray); -#else[Object] $type$[] array = ($type$[]) oarray; -#end[Object] return UNSAFE.compareAndExchange$Type$(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, - {#if[Object]?handle.componentType.cast(expected):expected}, - {#if[Object]?runtimeTypeCheck(handle, array, value):value}); + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, + expected, + value); } @ForceInline static $type$ compareAndExchangeAcquire(VarHandle ob, Object oarray, int index, $type$ expected, $type$ value) { - Array handle = (Array)ob; -#if[Object] - Object[] array = (Object[]) handle.arrayType.cast(oarray); -#else[Object] $type$[] array = ($type$[]) oarray; -#end[Object] return UNSAFE.compareAndExchange$Type$Acquire(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, - {#if[Object]?handle.componentType.cast(expected):expected}, - {#if[Object]?runtimeTypeCheck(handle, array, value):value}); + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, + expected, + value); } @ForceInline static $type$ compareAndExchangeRelease(VarHandle ob, Object oarray, int index, $type$ expected, $type$ value) { - Array handle = (Array)ob; -#if[Object] - Object[] array = (Object[]) handle.arrayType.cast(oarray); -#else[Object] $type$[] array = ($type$[]) oarray; -#end[Object] return UNSAFE.compareAndExchange$Type$Release(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, - {#if[Object]?handle.componentType.cast(expected):expected}, - {#if[Object]?runtimeTypeCheck(handle, array, value):value}); + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, + expected, + value); } @ForceInline static boolean weakCompareAndSetPlain(VarHandle ob, Object oarray, int index, $type$ expected, $type$ value) { - Array handle = (Array)ob; -#if[Object] - Object[] array = (Object[]) handle.arrayType.cast(oarray); -#else[Object] $type$[] array = ($type$[]) oarray; -#end[Object] return UNSAFE.weakCompareAndSet$Type$Plain(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, - {#if[Object]?handle.componentType.cast(expected):expected}, - {#if[Object]?runtimeTypeCheck(handle, array, value):value}); + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, + expected, + value); } @ForceInline static boolean weakCompareAndSet(VarHandle ob, Object oarray, int index, $type$ expected, $type$ value) { - Array handle = (Array)ob; -#if[Object] - Object[] array = (Object[]) handle.arrayType.cast(oarray); -#else[Object] $type$[] array = ($type$[]) oarray; -#end[Object] return UNSAFE.weakCompareAndSet$Type$(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, - {#if[Object]?handle.componentType.cast(expected):expected}, - {#if[Object]?runtimeTypeCheck(handle, array, value):value}); + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, + expected, + value); } @ForceInline static boolean weakCompareAndSetAcquire(VarHandle ob, Object oarray, int index, $type$ expected, $type$ value) { - Array handle = (Array)ob; -#if[Object] - Object[] array = (Object[]) handle.arrayType.cast(oarray); -#else[Object] $type$[] array = ($type$[]) oarray; -#end[Object] return UNSAFE.weakCompareAndSet$Type$Acquire(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, - {#if[Object]?handle.componentType.cast(expected):expected}, - {#if[Object]?runtimeTypeCheck(handle, array, value):value}); + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, + expected, + value); } @ForceInline static boolean weakCompareAndSetRelease(VarHandle ob, Object oarray, int index, $type$ expected, $type$ value) { - Array handle = (Array)ob; -#if[Object] - Object[] array = (Object[]) handle.arrayType.cast(oarray); -#else[Object] $type$[] array = ($type$[]) oarray; -#end[Object] return UNSAFE.weakCompareAndSet$Type$Release(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, - {#if[Object]?handle.componentType.cast(expected):expected}, - {#if[Object]?runtimeTypeCheck(handle, array, value):value}); + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, + expected, + value); } @ForceInline static $type$ getAndSet(VarHandle ob, Object oarray, int index, $type$ value) { - Array handle = (Array)ob; -#if[Object] - Object[] array = (Object[]) handle.arrayType.cast(oarray); -#else[Object] $type$[] array = ($type$[]) oarray; -#end[Object] return UNSAFE.getAndSet$Type$(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, - {#if[Object]?runtimeTypeCheck(handle, array, value):value}); + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, + value); } @ForceInline static $type$ getAndSetAcquire(VarHandle ob, Object oarray, int index, $type$ value) { - Array handle = (Array)ob; -#if[Object] - Object[] array = (Object[]) handle.arrayType.cast(oarray); -#else[Object] $type$[] array = ($type$[]) oarray; -#end[Object] return UNSAFE.getAndSet$Type$Acquire(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, - {#if[Object]?runtimeTypeCheck(handle, array, value):value}); + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, + value); } @ForceInline static $type$ getAndSetRelease(VarHandle ob, Object oarray, int index, $type$ value) { - Array handle = (Array)ob; -#if[Object] - Object[] array = (Object[]) handle.arrayType.cast(oarray); -#else[Object] $type$[] array = ($type$[]) oarray; -#end[Object] return UNSAFE.getAndSet$Type$Release(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, - {#if[Object]?runtimeTypeCheck(handle, array, value):value}); + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, + value); } #end[CAS] #if[AtomicAdd] @ForceInline static $type$ getAndAdd(VarHandle ob, Object oarray, int index, $type$ value) { - Array handle = (Array)ob; $type$[] array = ($type$[]) oarray; return UNSAFE.getAndAdd$Type$(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, value); } @ForceInline static $type$ getAndAddAcquire(VarHandle ob, Object oarray, int index, $type$ value) { - Array handle = (Array)ob; $type$[] array = ($type$[]) oarray; return UNSAFE.getAndAdd$Type$Acquire(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, value); } @ForceInline static $type$ getAndAddRelease(VarHandle ob, Object oarray, int index, $type$ value) { - Array handle = (Array)ob; $type$[] array = ($type$[]) oarray; return UNSAFE.getAndAdd$Type$Release(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, value); } #end[AtomicAdd] @@ -1083,86 +1041,81 @@ final class VarHandle$Type$s { @ForceInline static $type$ getAndBitwiseOr(VarHandle ob, Object oarray, int index, $type$ value) { - Array handle = (Array)ob; $type$[] array = ($type$[]) oarray; return UNSAFE.getAndBitwiseOr$Type$(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, value); } @ForceInline static $type$ getAndBitwiseOrRelease(VarHandle ob, Object oarray, int index, $type$ value) { - Array handle = (Array)ob; $type$[] array = ($type$[]) oarray; return UNSAFE.getAndBitwiseOr$Type$Release(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, value); } @ForceInline static $type$ getAndBitwiseOrAcquire(VarHandle ob, Object oarray, int index, $type$ value) { - Array handle = (Array)ob; $type$[] array = ($type$[]) oarray; return UNSAFE.getAndBitwiseOr$Type$Acquire(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, value); } @ForceInline static $type$ getAndBitwiseAnd(VarHandle ob, Object oarray, int index, $type$ value) { - Array handle = (Array)ob; $type$[] array = ($type$[]) oarray; return UNSAFE.getAndBitwiseAnd$Type$(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, value); } @ForceInline static $type$ getAndBitwiseAndRelease(VarHandle ob, Object oarray, int index, $type$ value) { - Array handle = (Array)ob; $type$[] array = ($type$[]) oarray; return UNSAFE.getAndBitwiseAnd$Type$Release(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, value); } @ForceInline static $type$ getAndBitwiseAndAcquire(VarHandle ob, Object oarray, int index, $type$ value) { - Array handle = (Array)ob; $type$[] array = ($type$[]) oarray; return UNSAFE.getAndBitwiseAnd$Type$Acquire(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, value); } @ForceInline static $type$ getAndBitwiseXor(VarHandle ob, Object oarray, int index, $type$ value) { - Array handle = (Array)ob; $type$[] array = ($type$[]) oarray; return UNSAFE.getAndBitwiseXor$Type$(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, value); } @ForceInline static $type$ getAndBitwiseXorRelease(VarHandle ob, Object oarray, int index, $type$ value) { - Array handle = (Array)ob; $type$[] array = ($type$[]) oarray; return UNSAFE.getAndBitwiseXor$Type$Release(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, value); } @ForceInline static $type$ getAndBitwiseXorAcquire(VarHandle ob, Object oarray, int index, $type$ value) { - Array handle = (Array)ob; $type$[] array = ($type$[]) oarray; return UNSAFE.getAndBitwiseXor$Type$Acquire(array, - (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << handle.ashift) + handle.abase, + (((long) Preconditions.checkIndex(index, array.length, Preconditions.AIOOBE_FORMATTER)) << SHIFT) + BASE, value); } #end[Bitwise] +#end[NonPlainAccess] - static final VarForm FORM = new VarForm(Array.class, {#if[Object]?Object[].class:$type$[].class}, {#if[Object]?Object.class:$type$.class}, int.class); + static final VarForm FORM = new VarForm(Array.class, $type$[].class, $type$.class, int.class); + static final Array NON_EXACT_INSTANCE = new Array(false); + static final Array EXACT_INSTANCE = new Array(true); } +#end[Array] } diff --git a/src/java.base/share/classes/java/lang/ref/PhantomReference.java b/src/java.base/share/classes/java/lang/ref/PhantomReference.java index a6e144dc7b6..9a780105228 100644 --- a/src/java.base/share/classes/java/lang/ref/PhantomReference.java +++ b/src/java.base/share/classes/java/lang/ref/PhantomReference.java @@ -25,6 +25,7 @@ package java.lang.ref; +import java.util.Objects; import jdk.internal.vm.annotation.IntrinsicCandidate; /** @@ -32,6 +33,14 @@ * determines that their referents may otherwise be reclaimed. Phantom * references are most often used to schedule post-mortem cleanup actions. * + *
    + *
    + * The referent must have {@linkplain Objects#hasIdentity(Object) object identity}. + * When preview features are enabled, attempts to create a reference + * to a {@linkplain Class#isValue value object} result in an {@link IdentityException}. + *
    + *
    + * *

    Suppose the garbage collector determines at a certain point in time * that an object is * phantom reachable. At that time it will atomically clear @@ -100,6 +109,8 @@ void clearImpl() { * @param referent the object the new phantom reference will refer to * @param q the queue with which the reference is to be registered, * or {@code null} if registration is not required + * @throws IdentityException if the referent is not an + * {@link java.util.Objects#hasIdentity(Object) identity object} */ public PhantomReference(@jdk.internal.RequiresIdentity T referent, ReferenceQueue q) { super(referent, q); diff --git a/src/java.base/share/classes/java/lang/ref/Reference.java b/src/java.base/share/classes/java/lang/ref/Reference.java index 43d9f414b3c..365e78b432e 100644 --- a/src/java.base/share/classes/java/lang/ref/Reference.java +++ b/src/java.base/share/classes/java/lang/ref/Reference.java @@ -32,11 +32,21 @@ import jdk.internal.access.JavaLangRefAccess; import jdk.internal.access.SharedSecrets; +import java.util.Objects; + /** * Abstract base class for reference objects. This class defines the * operations common to all reference objects. Because reference objects are * implemented in close cooperation with the garbage collector, this class may * not be subclassed directly. + * + *

    + *
    + * The referent must have {@linkplain Objects#hasIdentity(Object) object identity}. + * When preview features are enabled, attempts to create a reference + * to a {@linkplain Class#isValue value object} result in an {@link IdentityException}. + *
    + *
    * @param the type of the referent * * @author Mark Reinhold @@ -535,6 +545,9 @@ protected Object clone() throws CloneNotSupportedException { } Reference(T referent, ReferenceQueue queue) { + if (referent != null) { + Objects.requireIdentity(referent); + } this.referent = referent; this.queue = (queue == null) ? ReferenceQueue.NULL_QUEUE : queue; } diff --git a/src/java.base/share/classes/java/lang/ref/SoftReference.java b/src/java.base/share/classes/java/lang/ref/SoftReference.java index 0bc372eb105..79133add6e7 100644 --- a/src/java.base/share/classes/java/lang/ref/SoftReference.java +++ b/src/java.base/share/classes/java/lang/ref/SoftReference.java @@ -26,11 +26,21 @@ package java.lang.ref; +import java.util.Objects; + /** * Soft reference objects, which are cleared at the discretion of the garbage * collector in response to memory demand. Soft references are most often used * to implement memory-sensitive caches. * + *
    + *
    + * The referent must have {@linkplain Objects#hasIdentity(Object) object identity}. + * When preview features are enabled, attempts to create a reference + * to a {@linkplain Class#isValue value object} result in an {@link IdentityException}. + *
    + *
    + * *

    Suppose that the garbage collector determines at a certain point in time * that an object is softly * reachable. At that time it may choose to clear atomically all soft @@ -81,6 +91,8 @@ public non-sealed class SoftReference<@jdk.internal.RequiresIdentity T> extends * reference is not registered with any queue. * * @param referent object the new soft reference will refer to + * @throws IdentityException if the referent is not an + * {@link java.util.Objects#hasIdentity(Object) identity object} */ public SoftReference(@jdk.internal.RequiresIdentity T referent) { super(referent); @@ -94,7 +106,8 @@ public SoftReference(@jdk.internal.RequiresIdentity T referent) { * @param referent object the new soft reference will refer to * @param q the queue with which the reference is to be registered, * or {@code null} if registration is not required - * + * @throws IdentityException if the referent is not an + * {@link java.util.Objects#hasIdentity(Object) identity object} */ public SoftReference(@jdk.internal.RequiresIdentity T referent, ReferenceQueue q) { super(referent, q); diff --git a/src/java.base/share/classes/java/lang/ref/WeakReference.java b/src/java.base/share/classes/java/lang/ref/WeakReference.java index 4b4f834485e..76b2ba866df 100644 --- a/src/java.base/share/classes/java/lang/ref/WeakReference.java +++ b/src/java.base/share/classes/java/lang/ref/WeakReference.java @@ -26,11 +26,21 @@ package java.lang.ref; +import java.util.Objects; + /** * Weak reference objects, which do not prevent their referents from being * made finalizable, finalized, and then reclaimed. Weak references are most * often used to implement canonicalizing mappings. * + *

    + *
    + * The referent must have {@linkplain Objects#hasIdentity(Object) object identity}. + * When preview features are enabled, attempts to create a reference + * to a {@linkplain Class#isValue value object} result in an {@link IdentityException}. + *
    + *
    + * *

    Suppose that the garbage collector determines at a certain point in time * that an object is weakly * reachable. At that time it will atomically clear all weak references to @@ -53,6 +63,8 @@ public non-sealed class WeakReference<@jdk.internal.RequiresIdentity T> extends * reference is not registered with any queue. * * @param referent object the new weak reference will refer to + * @throws IdentityException if the referent is not an + * {@link java.util.Objects#hasIdentity(Object) identity object} */ public WeakReference(@jdk.internal.RequiresIdentity T referent) { super(referent); @@ -65,6 +77,8 @@ public WeakReference(@jdk.internal.RequiresIdentity T referent) { * @param referent object the new weak reference will refer to * @param q the queue with which the reference is to be registered, * or {@code null} if registration is not required + * @throws IdentityException if the referent is not an + * {@link java.util.Objects#hasIdentity(Object) identity object} */ public WeakReference(@jdk.internal.RequiresIdentity T referent, ReferenceQueue q) { super(referent, q); diff --git a/src/java.base/share/classes/java/lang/reflect/AccessFlag.java b/src/java.base/share/classes/java/lang/reflect/AccessFlag.java index 4314c8c410d..da7147eb5dc 100644 --- a/src/java.base/share/classes/java/lang/reflect/AccessFlag.java +++ b/src/java.base/share/classes/java/lang/reflect/AccessFlag.java @@ -25,6 +25,8 @@ package java.lang.reflect; +import jdk.internal.javac.PreviewFeature; + import java.lang.classfile.ClassModel; import java.lang.classfile.FieldModel; import java.lang.classfile.MethodModel; @@ -162,8 +164,25 @@ public enum AccessFlag { * @apiNote * In Java SE 8 and above, the JVM treats the {@code ACC_SUPER} * flag as set in every class file (JVMS {@jvms 4.1}). + * If preview feature is enabled, + * the {@code 0x0020} access flag bit is {@linkplain #IDENTITY IDENTITY access flag}. + */ + SUPER(0x0000_0020, false, + Location.EMPTY_SET, + List.of(Map.entry(latest(), Location.SET_CLASS))), + + /** + * The access flag {@code ACC_IDENTITY}, corresponding to the + * modifier {@link Modifier#IDENTITY identity}, with a mask + * value of {@value "0x%04x" Modifier#IDENTITY}. + * @jvms 4.1 -B. Class access and property modifiers + * + * @since Valhalla */ - SUPER(0x0000_0020, false, Location.SET_CLASS, List.of()), + @PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS, reflective=true) + IDENTITY(Modifier.IDENTITY, false, + Location.SET_CLASS_INNER_CLASS, + List.of(Map.entry(latest(), Location.EMPTY_SET))), /** * The module flag {@code ACC_OPEN} with a mask value of {@code @@ -264,6 +283,18 @@ public enum AccessFlag { List.of(Map.entry(RELEASE_16, Location.SET_METHOD), Map.entry(RELEASE_1, Location.EMPTY_SET))), + /** + * The access flag {@code ACC_STRICT_INIT}, with a mask value of + * {@value "0x%04x" java.lang.classfile.ClassFile#ACC_STRICT_INIT}. + * + * @jvms 4.5 Fields + * @since Valhalla + */ + @PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS, reflective=true) + STRICT_INIT(ACC_STRICT_INIT, false, + Location.SET_FIELD, + List.of(Map.entry(latest(), Location.EMPTY_SET))), + /** * The access flag {@code ACC_SYNTHETIC} with a mask value of * {@value "0x%04x" Modifier#SYNTHETIC}. @@ -353,7 +384,7 @@ public boolean sourceModifier() { * the current class file format version. */ public Set locations() { - return locations; + return locations(latest()); } /** @@ -381,14 +412,7 @@ public Set locations(ClassFileFormatVersion cffv) { * @throws NullPointerException if {@code location} is {@code null} */ public static Set maskToAccessFlags(int mask, Location location) { - var definition = findDefinition(location); // null checks location - int unmatchedMask = mask & (~location.flagsMask()); - if (unmatchedMask != 0) { - throw new IllegalArgumentException("Unmatched bit position 0x" + - Integer.toHexString(unmatchedMask) + - " for location " + location); - } - return new AccessFlagSet(definition, mask); + return maskToAccessFlags(mask, location, latest()); } /** @@ -404,7 +428,7 @@ public static Set maskToAccessFlags(int mask, Location location) { * @since 25 */ public static Set maskToAccessFlags(int mask, Location location, ClassFileFormatVersion cffv) { - var definition = findDefinition(location); // null checks location + var definition = findDefinition(location, cffv); // null checks location int unmatchedMask = mask & (~location.flagsMask(cffv)); // null checks cffv if (unmatchedMask != 0) { throw new IllegalArgumentException("Unmatched bit position 0x" + @@ -433,7 +457,7 @@ public enum Location { * @see Modifier#interfaceModifiers() * @jvms 4.1 The {@code ClassFile} Structure */ - CLASS(ACC_PUBLIC | ACC_FINAL | ACC_SUPER | + CLASS(ACC_PUBLIC | ACC_FINAL | ACC_IDENTITY | ACC_INTERFACE | ACC_ABSTRACT | ACC_SYNTHETIC | ACC_ANNOTATION | ACC_ENUM | ACC_MODULE, @@ -455,8 +479,12 @@ public enum Location { */ FIELD(ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL | ACC_VOLATILE | - ACC_TRANSIENT | ACC_SYNTHETIC | ACC_ENUM, - List.of(Map.entry(RELEASE_4, // no synthetic, enum + ACC_TRANSIENT | ACC_SYNTHETIC | ACC_ENUM | ACC_STRICT_INIT, + List.of(Map.entry(latest(), // no strict_init + ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | + ACC_STATIC | ACC_FINAL | ACC_VOLATILE | + ACC_TRANSIENT | ACC_SYNTHETIC | ACC_ENUM), + Map.entry(RELEASE_4, // no synthetic, enum ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL | ACC_VOLATILE | ACC_TRANSIENT))), @@ -497,13 +525,17 @@ public enum Location { * @see Modifier#interfaceModifiers() * @jvms 4.7.6 The {@code InnerClasses} Attribute */ - INNER_CLASS(ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | + INNER_CLASS(ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_IDENTITY | ACC_STATIC | ACC_FINAL | ACC_INTERFACE | ACC_ABSTRACT | ACC_SYNTHETIC | ACC_ANNOTATION | ACC_ENUM, - List.of(Map.entry(RELEASE_4, // no synthetic, annotation, enum - ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | - ACC_STATIC | ACC_FINAL | ACC_INTERFACE | - ACC_ABSTRACT), + List.of(Map.entry(latest(), // no identity + ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | + ACC_STATIC | ACC_FINAL | ACC_INTERFACE | ACC_ABSTRACT | + ACC_SYNTHETIC | ACC_ANNOTATION | ACC_ENUM), + Map.entry(RELEASE_4, // no synthetic, annotation, enum + ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | + ACC_STATIC | ACC_FINAL | ACC_INTERFACE | + ACC_ABSTRACT), Map.entry(RELEASE_0, 0))), // did not exist /** @@ -620,7 +652,7 @@ public enum Location { // These 2 utilities reside in Location because Location must be initialized before AccessFlag private static List> ensureHistoryOrdered( List> history) { - ClassFileFormatVersion lastVersion = ClassFileFormatVersion.latest(); + ClassFileFormatVersion lastVersion = CURRENT_PREVIEW_FEATURES; for (var e : history) { var historyVersion = e.getKey(); if (lastVersion.compareTo(historyVersion) <= 0) { @@ -654,7 +686,7 @@ private static T findInHistory(T candidate, List flags() { - return new AccessFlagSet(findDefinition(this), flagsMask()); + return flags(latest()); } /** @@ -698,7 +730,7 @@ public Set flags() { */ public Set flags(ClassFileFormatVersion cffv) { // implicit null check cffv - return new AccessFlagSet(findDefinition(this), flagsMask(cffv)); + return new AccessFlagSet(findDefinition(this, cffv), flagsMask(cffv)); } } @@ -713,13 +745,12 @@ private static AccessFlag[] createDefinition(AccessFlag... known) { return ret; } - // Will take extra args in the future for valhalla switch - private static AccessFlag[] findDefinition(Location location) { + private static AccessFlag[] findDefinition(Location location, ClassFileFormatVersion cffv) { return switch (location) { - case CLASS -> CLASS_FLAGS; - case FIELD -> FIELD_FLAGS; + case CLASS -> cffv == CURRENT_PREVIEW_FEATURES ? CLASS_PREVIEW_FLAGS : CLASS_FLAGS; + case FIELD -> cffv == CURRENT_PREVIEW_FEATURES ? FIELD_PREVIEW_FLAGS : FIELD_FLAGS; case METHOD -> METHOD_FLAGS; - case INNER_CLASS -> INNER_CLASS_FLAGS; + case INNER_CLASS -> cffv == CURRENT_PREVIEW_FEATURES ? INNER_CLASS_PREVIEW_FLAGS : INNER_CLASS_FLAGS; case METHOD_PARAMETER -> METHOD_PARAMETER_FLAGS; case MODULE -> MODULE_FLAGS; case MODULE_REQUIRES -> MODULE_REQUIRES_FLAGS; @@ -729,10 +760,13 @@ private static AccessFlag[] findDefinition(Location location) { } private static final @Stable AccessFlag[] // Can use stable array and lazy init in the future - CLASS_FLAGS = createDefinition(PUBLIC, FINAL, SUPER, INTERFACE, ABSTRACT, SYNTHETIC, ANNOTATION, ENUM, MODULE), - FIELD_FLAGS = createDefinition(PUBLIC, PRIVATE, PROTECTED, STATIC, FINAL, VOLATILE, TRANSIENT, SYNTHETIC, ENUM), + CLASS_FLAGS = createDefinition(PUBLIC, FINAL, SUPER, INTERFACE, ABSTRACT, SYNTHETIC, ANNOTATION, ENUM, MODULE), + CLASS_PREVIEW_FLAGS = createDefinition(PUBLIC, FINAL, IDENTITY, INTERFACE, ABSTRACT, SYNTHETIC, ANNOTATION, ENUM, MODULE), // identity + FIELD_FLAGS = createDefinition(PUBLIC, PRIVATE, PROTECTED, STATIC, FINAL, VOLATILE, TRANSIENT, SYNTHETIC, ENUM), + FIELD_PREVIEW_FLAGS = createDefinition(PUBLIC, PRIVATE, PROTECTED, STATIC, FINAL, VOLATILE, TRANSIENT, SYNTHETIC, ENUM, STRICT_INIT), // strict METHOD_FLAGS = createDefinition(PUBLIC, PRIVATE, PROTECTED, STATIC, FINAL, SYNCHRONIZED, BRIDGE, VARARGS, NATIVE, ABSTRACT, STRICT, SYNTHETIC), - INNER_CLASS_FLAGS = createDefinition(PUBLIC, PRIVATE, PROTECTED, STATIC, FINAL, INTERFACE, ABSTRACT, SYNTHETIC, ANNOTATION, ENUM), + INNER_CLASS_FLAGS = createDefinition(PUBLIC, PRIVATE, PROTECTED, STATIC, FINAL, INTERFACE, ABSTRACT, SYNTHETIC, ANNOTATION, ENUM), + INNER_CLASS_PREVIEW_FLAGS = createDefinition(PUBLIC, PRIVATE, PROTECTED, IDENTITY, STATIC, FINAL, INTERFACE, ABSTRACT, SYNTHETIC, ANNOTATION, ENUM), // identity METHOD_PARAMETER_FLAGS = createDefinition(FINAL, SYNTHETIC, MANDATED), MODULE_FLAGS = createDefinition(OPEN, SYNTHETIC, MANDATED), MODULE_REQUIRES_FLAGS = createDefinition(TRANSITIVE, STATIC_PHASE, SYNTHETIC, MANDATED), diff --git a/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java b/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java index a045f9c196a..1a8175ad7bb 100644 --- a/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java +++ b/src/java.base/share/classes/java/lang/reflect/AccessibleObject.java @@ -166,6 +166,7 @@ public static void setAccessible(AccessibleObject[] array, boolean flag) { *

      *
    • static final fields declared in any class or interface
    • *
    • final fields declared in a {@linkplain Class#isHidden() hidden class}
    • + *
    • fields declared in a {@linkplain Class#isValue() value class}
    • *
    • final fields declared in a {@linkplain Class#isRecord() record}
    • *
    *

    The {@code accessible} flag when {@code true} suppresses Java language access diff --git a/src/java.base/share/classes/java/lang/reflect/Array.java b/src/java.base/share/classes/java/lang/reflect/Array.java index 2fbad52c374..9bb93e1b992 100644 --- a/src/java.base/share/classes/java/lang/reflect/Array.java +++ b/src/java.base/share/classes/java/lang/reflect/Array.java @@ -140,8 +140,18 @@ public static native int getLength(Object array) * argument is negative, or if it is greater than or equal to the * length of the specified array */ - public static native Object get(Object array, int index) - throws IllegalArgumentException, ArrayIndexOutOfBoundsException; + public static Object get(Object array, int index) + throws IllegalArgumentException, ArrayIndexOutOfBoundsException { + Class componentType = array.getClass().getComponentType(); + if (componentType != null && !componentType.isPrimitive()) { + Object[] objArray = (Object[]) array.getClass().cast(array); + return objArray[index]; + } else { + return getReferenceOrPrimitive(array, index); + } + } + + private static native Object getReferenceOrPrimitive(Object array, int index); /** * Returns the value of the indexed component in the specified @@ -312,8 +322,18 @@ public static native double getDouble(Object array, int index) * argument is negative, or if it is greater than or equal to * the length of the specified array */ - public static native void set(Object array, int index, Object value) - throws IllegalArgumentException, ArrayIndexOutOfBoundsException; + public static void set(Object array, int index, Object value) + throws IllegalArgumentException, ArrayIndexOutOfBoundsException { + Class componentType = array.getClass().getComponentType(); + if (componentType != null && !componentType.isPrimitive()) { + Object[] objArray = (Object[]) array.getClass().cast(array); + objArray[index] = componentType.cast(value); + } else { + setReferenceOrPrimitive(array, index, value); + } + } + + private static native void setReferenceOrPrimitive(Object array, int index, Object value); /** * Sets the value of the indexed component of the specified array diff --git a/src/java.base/share/classes/java/lang/reflect/ClassFileFormatVersion.java b/src/java.base/share/classes/java/lang/reflect/ClassFileFormatVersion.java index 4a63dd157f8..7fbbfb7136b 100644 --- a/src/java.base/share/classes/java/lang/reflect/ClassFileFormatVersion.java +++ b/src/java.base/share/classes/java/lang/reflect/ClassFileFormatVersion.java @@ -25,6 +25,8 @@ package java.lang.reflect; +import java.lang.classfile.ClassFile; + /** * Class file format versions of the Java virtual machine. * @@ -383,11 +385,16 @@ public enum ClassFileFormatVersion { * The Java Virtual Machine Specification, Java SE 26 Edition */ RELEASE_26(70), - ; // Reduce code churn when appending new constants + + // Reduce code churn when appending new constants // Note to maintainers: when adding constants for newer releases, // the implementation of latest() must be updated too. + /// The preview features of Valhalla. + /// @since 26 + CURRENT_PREVIEW_FEATURES(ClassFile.latestMajorVersion()); + private final int major; private ClassFileFormatVersion(int major) { diff --git a/src/java.base/share/classes/java/lang/reflect/Executable.java b/src/java.base/share/classes/java/lang/reflect/Executable.java index 2f6f6cca89f..4c6e85f530c 100644 --- a/src/java.base/share/classes/java/lang/reflect/Executable.java +++ b/src/java.base/share/classes/java/lang/reflect/Executable.java @@ -214,6 +214,7 @@ String sharedToGenericString(int modifierMask, boolean isDefault) { * {@return an unmodifiable set of the {@linkplain AccessFlag * access flags} for the executable represented by this object, * possibly empty} + * The {@code AccessFlags} may depend on the class file format version of the class. * * @see #getModifiers() * @jvms 4.6 Methods diff --git a/src/java.base/share/classes/java/lang/reflect/Field.java b/src/java.base/share/classes/java/lang/reflect/Field.java index e26d8b03ff8..4cb72f41a9e 100644 --- a/src/java.base/share/classes/java/lang/reflect/Field.java +++ b/src/java.base/share/classes/java/lang/reflect/Field.java @@ -73,7 +73,7 @@ class Field extends AccessibleObject implements Member { private final String name; private final Class type; private final int modifiers; - private final boolean trustedFinal; + private final int flags; // Generics and annotations support private final transient String signature; private final byte[] annotations; @@ -123,7 +123,7 @@ private FieldRepository getGenericInfo() { String name, Class type, int modifiers, - boolean trustedFinal, + int flags, int slot, String signature, byte[] annotations) @@ -132,7 +132,7 @@ private FieldRepository getGenericInfo() { this.name = name; this.type = type; this.modifiers = modifiers; - this.trustedFinal = trustedFinal; + this.flags = flags; this.slot = slot; this.signature = signature; this.annotations = annotations; @@ -154,7 +154,7 @@ Field copy() { if (this.root != null) throw new IllegalArgumentException("Can not copy a non-root Field"); - Field res = new Field(clazz, name, type, modifiers, trustedFinal, slot, signature, annotations); + Field res = new Field(clazz, name, type, modifiers, flags, slot, signature, annotations); res.root = this; // Might as well eagerly propagate this if already present res.fieldAccessor = fieldAccessor; @@ -212,6 +212,8 @@ public int getModifiers() { /** * {@return an unmodifiable set of the {@linkplain AccessFlag * access flags} for this field, possibly empty} + * The {@code AccessFlags} may depend on the class file format version of the class. + * * @see #getModifiers() * @jvms 4.5 Fields * @since 20 @@ -769,7 +771,9 @@ public double getDouble(Object obj) * this {@code Field} object;

  • *
  • the field is non-static; and
  • *
  • the field's declaring class is not a {@linkplain Class#isHidden() - * hidden class}; and
  • + * hidden class}; + *
  • the field's declaring class is not a {@linkplain Class#isValue() + * value class}; and
  • *
  • the field's declaring class is not a {@linkplain Class#isRecord() * record class}.
  • * @@ -1225,8 +1229,15 @@ private void setOverrideFieldAccessor(FieldAccessor accessor) { return root; } + private static final int TRUST_FINAL = 0x0010; + private static final int NULL_RESTRICTED = 0x0020; + /* package-private */ boolean isTrustedFinal() { - return trustedFinal; + return (flags & TRUST_FINAL) == TRUST_FINAL; + } + + /* package-private */ boolean isNullRestricted() { + return (flags & NULL_RESTRICTED) == NULL_RESTRICTED; } /** diff --git a/src/java.base/share/classes/java/lang/reflect/Modifier.java b/src/java.base/share/classes/java/lang/reflect/Modifier.java index 624f32b4e04..4c237aa1615 100644 --- a/src/java.base/share/classes/java/lang/reflect/Modifier.java +++ b/src/java.base/share/classes/java/lang/reflect/Modifier.java @@ -25,12 +25,15 @@ package java.lang.reflect; +import jdk.internal.javac.PreviewFeature; + import java.util.StringJoiner; /** * The Modifier class provides {@code static} methods and - * constants to decode class and member access modifiers. The sets of - * modifiers are represented as integers with distinct bit positions + * constants to decode class and member access modifiers. + * The {@link AccessFlag} class should be used instead of this class. + * The sets of modifiers are represented as integers with non-distinct bit positions * representing different modifiers. The values for the constants * representing the modifiers are taken from the tables in sections * {@jvms 4.1}, {@jvms 4.4}, {@jvms 4.5}, and {@jvms 4.7} of @@ -40,8 +43,8 @@ * Not all modifiers that are syntactic Java language modifiers are * represented in this class, only those modifiers that also * have a corresponding JVM {@linkplain AccessFlag access flag} are - * included. In particular the {@code default} method modifier (JLS - * {@jls 9.4.3}) and the {@code sealed} and {@code non-sealed} class + * included. In particular, the {@code default} method modifier (JLS + * {@jls 9.4.3}) and the {@code value}, {@code sealed} and {@code non-sealed} class * (JLS {@jls 8.1.1.2}) and interface (JLS {@jls 9.1.1.4}) modifiers * are not represented in this class. * @@ -123,6 +126,9 @@ public static boolean isFinal(int mod) { * Return {@code true} if the integer argument includes the * {@code synchronized} modifier, {@code false} otherwise. * + * @apiNote {@code isSynchronized} should only be called with the modifiers + * of a {@linkplain Method#getModifiers() method}. + * * @param mod a set of modifiers * @return {@code true} if {@code mod} includes the * {@code synchronized} modifier; {@code false} otherwise. @@ -131,6 +137,24 @@ public static boolean isSynchronized(int mod) { return (mod & SYNCHRONIZED) != 0; } + /** + * Return {@code true} if the integer argument includes the + * {@code identity} modifier, {@code false} otherwise. + * + * @apiNote {@code isIdentity} should only be called with the modifiers + * of a {@linkplain Class#getModifiers() class}. + * + * @param mod a set of modifiers + * @return {@code true} if {@code mod} includes the + * {@code identity} modifier; {@code false} otherwise. + * + * @since Valhalla + */ + @PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS, reflective=true) + public static boolean isIdentity(int mod) { + return (mod & IDENTITY) != 0; + } + /** * Return {@code true} if the integer argument includes the * {@code volatile} modifier, {@code false} otherwise. @@ -207,7 +231,7 @@ public static boolean isStrict(int mod) { * Return a string describing the access modifier flags in * the specified modifier. For example: *
    -     *    public final synchronized strictfp
    +     *    public final synchronized
          * 
    * The modifier names are returned in an order consistent with the * suggested modifier orderings given in sections 8.1.1, 8.3.1, 8.4.3, 8.8.3, and 9.1.1 of @@ -319,6 +343,15 @@ public static String toString(int mod) { */ public static final int SYNCHRONIZED = 0x00000020; + /** + * The {@code int} value representing the {@code ACC_IDENTITY} + * modifier. + * + * @since Valhalla + */ + @PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS, reflective=true) + public static final int IDENTITY = 0x00000020; + /** * The {@code int} value representing the {@code volatile} * modifier. @@ -396,7 +429,7 @@ static boolean isMandated(int mod) { private static final int CLASS_MODIFIERS = Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE | Modifier.ABSTRACT | Modifier.STATIC | Modifier.FINAL | - Modifier.STRICT; + Modifier.IDENTITY | Modifier.STRICT; /** * The Java source modifiers that can be applied to an interface. diff --git a/src/java.base/share/classes/java/lang/reflect/Proxy.java b/src/java.base/share/classes/java/lang/reflect/Proxy.java index dc512e86590..17b35357a05 100644 --- a/src/java.base/share/classes/java/lang/reflect/Proxy.java +++ b/src/java.base/share/classes/java/lang/reflect/Proxy.java @@ -445,7 +445,7 @@ private record ProxyClassContext(Module module, String packageName, int accessFl } } - if ((accessFlags & ~Modifier.PUBLIC) != 0) { + if ((accessFlags & ~(Modifier.PUBLIC | Modifier.IDENTITY)) != 0) { throw new InternalError("proxy access flags must be Modifier.PUBLIC or 0"); } } @@ -467,7 +467,7 @@ private static Class defineProxyClass(ProxyClassContext context, List pc = JLA.defineClass(loader, proxyName, proxyClassFile, null, "__dynamic_proxy__"); 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 0d90cedd5c5..08bb6781896 100644 --- a/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java +++ b/src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java @@ -435,6 +435,12 @@ private static void collectCompatibleTypes(Class[] from, /** * Generate a class file for the proxy class. This method drives the * class file generation process. + * + * If a proxy interface references any value classes, the value classes + * are listed in the loadable descriptors attribute of the interface class. The + * classes that are referenced by the proxy interface have already + * been loaded before the proxy class. Hence the proxy class is + * generated with no loadable descriptors attributes as it essentially has no effect. */ private byte[] generateClassFile() { /* diff --git a/src/java.base/share/classes/java/lang/reflect/ReflectAccess.java b/src/java.base/share/classes/java/lang/reflect/ReflectAccess.java index 835ffef616e..bf963868aac 100644 --- a/src/java.base/share/classes/java/lang/reflect/ReflectAccess.java +++ b/src/java.base/share/classes/java/lang/reflect/ReflectAccess.java @@ -73,6 +73,10 @@ public boolean isTrustedFinalField(Field f) { return f.isTrustedFinal(); } + public boolean isNullRestrictedField(Field f) { + return f.isNullRestricted(); + } + public T newInstance(Constructor ctor, Object[] args, Class caller) throws IllegalAccessException, InstantiationException, InvocationTargetException { diff --git a/src/java.base/share/classes/java/lang/runtime/ObjectMethods.java b/src/java.base/share/classes/java/lang/runtime/ObjectMethods.java index 24b55600954..611ef4e0e5d 100644 --- a/src/java.base/share/classes/java/lang/runtime/ObjectMethods.java +++ b/src/java.base/share/classes/java/lang/runtime/ObjectMethods.java @@ -64,7 +64,9 @@ private ObjectMethods() { } private static final MethodHandle OBJECT_EQ; private static final MethodHandle HASH_COMBINER; - private static final HashMap, MethodHandle> primitiveEquals = new HashMap<>(); + /* package-private */ + static final HashMap, MethodHandle> primitiveEquals = new HashMap<>(); + private static final HashMap, MethodHandle> primitiveHashers = new HashMap<>(); private static final HashMap, MethodHandle> primitiveToString = new HashMap<>(); @@ -391,14 +393,22 @@ public static Object bootstrap(MethodHandles.Lookup lookup, String methodName, T requireNonNull(getters); Arrays.stream(getters).forEach(Objects::requireNonNull); MethodType methodType; - if (type instanceof MethodType mt) + if (type instanceof MethodType mt) { methodType = mt; - else { + if (mt.parameterType(0) != recordClass) { + throw new IllegalArgumentException("Bad method type: " + mt); + } + } else { methodType = null; if (!MethodHandle.class.equals(type)) throw new IllegalArgumentException(type.toString()); } List getterList = List.of(getters); + for (MethodHandle getter : getterList) { + if (getter.type().parameterType(0) != recordClass) { + throw new IllegalArgumentException("Bad receiver type: " + getter); + } + } MethodHandle handle = switch (methodName) { case "equals" -> { if (methodType != null && !methodType.equals(MethodType.methodType(boolean.class, recordClass, Object.class))) diff --git a/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java index f4d82595842..2b5912f02cb 100644 --- a/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java +++ b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java @@ -736,7 +736,7 @@ private static MethodHandle generateTypeSwitch(MethodHandles.Lookup caller, Clas byte[] classBytes = ClassFile.of(ClassFile.StackMapsOption.DROP_STACK_MAPS).build(ConstantUtils.binaryNameToDesc(typeSwitchClassName(caller.lookupClass())), clb -> { - clb.withFlags(AccessFlag.FINAL, AccessFlag.SUPER, AccessFlag.SYNTHETIC) + clb.withFlags(AccessFlag.FINAL, (PreviewFeatures.isEnabled()) ? AccessFlag.IDENTITY : AccessFlag.SUPER, AccessFlag.SYNTHETIC) .withMethodBody("typeSwitch", addExtraInfo ? MTD_TYPE_SWITCH_EXTRA : MTD_TYPE_SWITCH, ClassFile.ACC_FINAL | ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC, diff --git a/src/java.base/share/classes/java/lang/runtime/ValueObjectMethods.java b/src/java.base/share/classes/java/lang/runtime/ValueObjectMethods.java new file mode 100644 index 00000000000..5f7247fb48e --- /dev/null +++ b/src/java.base/share/classes/java/lang/runtime/ValueObjectMethods.java @@ -0,0 +1,1405 @@ +/* + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. 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 java.lang.runtime; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.invoke.VarHandle; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import jdk.internal.access.JavaLangInvokeAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.value.LayoutIteration; +import jdk.internal.value.ValueClass; +import sun.invoke.util.Wrapper; + +import static java.lang.invoke.MethodHandles.constant; +import static java.lang.invoke.MethodHandles.dropArguments; +import static java.lang.invoke.MethodHandles.filterArguments; +import static java.lang.invoke.MethodHandles.foldArguments; +import static java.lang.invoke.MethodHandles.guardWithTest; +import static java.lang.invoke.MethodHandles.permuteArguments; +import static java.lang.invoke.MethodType.methodType; +import static java.lang.runtime.ObjectMethods.primitiveEquals; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; + +/** + * Implementation for Object::equals and Object::hashCode for value objects. + * + * ValueObjectMethods::isSubstitutable and valueObjectHashCode are + * private entry points called by VM. + */ +final class ValueObjectMethods { + private ValueObjectMethods() {} + private static final boolean VERBOSE = + System.getProperty("value.bsm.debug") != null; + private static final int MAX_NODE_VISITS = + Integer.getInteger("jdk.value.recursion.threshold", Integer.MAX_VALUE); + private static final JavaLangInvokeAccess JLIA = SharedSecrets.getJavaLangInvokeAccess(); + + static class MethodHandleBuilder { + private static final HashMap, MethodHandle> primitiveSubstitutable = new HashMap<>(); + + static { + primitiveSubstitutable.putAll(primitiveEquals); // adopt all the primitive eq methods + primitiveSubstitutable.put(float.class, + findStatic("eqValue", methodType(boolean.class, float.class, float.class))); + primitiveSubstitutable.put(double.class, + findStatic("eqValue", methodType(boolean.class, double.class, double.class))); + } + + static Stream getterStream(Class type, Comparator comparator) { + // filter static fields + List mhs = LayoutIteration.ELEMENTS.get(type); + if (comparator != null) { + mhs = new ArrayList<>(mhs); + mhs.sort(comparator); + } + return mhs.stream(); + } + + static MethodHandle hashCodeForType(Class type) { + if (type.isPrimitive()) { + int index = Wrapper.forPrimitiveType(type).ordinal(); + return HASHCODE[index]; + } else { + return HASHCODE[Wrapper.OBJECT.ordinal()].asType(methodType(int.class, type)); + } + } + + static MethodHandle builtinPrimitiveSubstitutable(Class type) { + return primitiveSubstitutable.get(type); + } + + /* + * Produces a MethodHandle that returns boolean if two instances + * of the given reference type are substitutable. + * + * Two values of reference type are substitutable i== iff + * 1. if o1 and o2 are both reference objects then o1 r== o2; or + * 2. if o1 and o2 are both values then o1 v== o2 + * + * At invocation time, it needs a dynamic check on the objects and + * do the substitutability test if they are of a value class. + */ + static MethodHandle referenceTypeEquals(Class type) { + return OBJECT_EQUALS.asType(methodType(boolean.class, type, type)); + } + + static Class fieldType(MethodHandle getter) { + Class ftype = getter.type().returnType(); + return ftype; + } + + private static List> valueTypeFields(Class type) { + return LayoutIteration.ELEMENTS.get(type).stream() + .>map(mh -> mh.type().returnType()) + .filter(ValueClass::isConcreteValueClass) + .distinct() + .toList(); + } + + /* + * Produces a MethodHandle that returns boolean if two value objects + * are substitutable. The method type is (V, V)boolean. + */ + static MethodHandle valueTypeEquals(Class type) { + var builder = METHOD_HANDLE_BUILDERS.get(type); + if (builder == null) { + builder = newBuilder(type); + } + return builder.equalsTarget(); + } + + /* + * Produces a MethodHandle that computes the hash code of a value object. + * The method type of the return MethodHandle is (V)int. + */ + static MethodHandle valueTypeHashCode(Class type) { + var builder = METHOD_HANDLE_BUILDERS.get(type); + if (builder == null) { + builder = newBuilder(type); + } + return builder.hashCodeTarget(); + } + + /* + * Produces a MethodHandle that returns boolean if the given non-recursively typed + * fields of the two value objects are substitutable. The method type is (V, V)boolean + */ + static MethodHandle valueTypeEquals(Class type, List getters) { + assert ValueClass.isConcreteValueClass(type); + + MethodType mt = methodType(boolean.class, type, type); + MethodHandle instanceTrue = dropArguments(TRUE, 0, type, Object.class).asType(mt); + MethodHandle instanceFalse = dropArguments(FALSE, 0, type, Object.class).asType(mt); + MethodHandle accumulator = dropArguments(TRUE, 0, type, type); + for (MethodHandle getter : getters) { + Class ftype = fieldType(getter); + var eq = substitutableInvoker(ftype).asType(methodType(boolean.class, ftype, ftype)); + var thisFieldEqual = filterArguments(eq, 0, getter, getter); + accumulator = guardWithTest(thisFieldEqual, accumulator, instanceFalse); + } + + // if both arguments are null, return true; + // otherwise return accumulator; + return guardWithTest(IS_NULL.asType(mt), + instanceTrue, + guardWithTest(IS_SAME_VALUE_CLASS.asType(mt), + accumulator, + instanceFalse)); + } + + /* + * Produces a MethodHandle that returns the hash code computed from + * the given non-recursively-typed fields of a value object. + * The method type is (V)int. + */ + static MethodHandle valueTypeHashCode(Class type, List getters) { + assert ValueClass.isConcreteValueClass(type); + + MethodHandle target = dropArguments(constant(int.class, SALT), 0, type); + MethodHandle classHasher = dropArguments(hashCodeForType(Class.class).bindTo(type), 0, type); + MethodHandle hashCombiner = dropArguments(HASH_COMBINER, 2, type); + MethodHandle accumulator = foldArguments(foldArguments(hashCombiner, 1, classHasher), 0, target); + for (MethodHandle getter : getters) { + Class ft = fieldType(getter); + // For primitive types or reference types, this calls Objects::hashCode. + // For value objects and the hashCode method is not overridden, + // VM will call valueObjectHashCode to compute the hash code. + var hasher = hashCodeForType(ft); + var hashThisField = filterArguments(hasher, 0, getter); // (R)I + var combineHashes = foldArguments(hashCombiner, 1, hashThisField); + accumulator = foldArguments(combineHashes, 0, accumulator); + } + return accumulator; + } + + // ------ utility methods ------ + private static boolean eq(Object a, Object b) { + if (a == null && b == null) return true; + if (a == null || b == null) return false; + if (a.getClass() != b.getClass()) return false; + return a.getClass().isValue() ? valueEquals(a, b) : (a == b); + } + + /* + * Returns true if two value objects are substitutable. + */ + private static boolean valueEquals(Object a, Object b) { + assert a != null && b != null && isSameValueClass(a, b); + try { + Class type = a.getClass(); + return (boolean) substitutableInvoker(type).invoke(type.cast(a), type.cast(b)); + } catch (Error|RuntimeException e) { + throw e; + } catch (Throwable e) { + throw new InternalError(e); + } + } + + private static boolean isNull(Object a, Object b) { + // avoid acmp that will call isSubstitutable + if (a != null) return false; + if (b != null) return false; + return true; + } + + /* + * Returns true if the given objects are of the same value class. + * + * Two objects are of the same value class iff: + * 1. a != null and b != null + * 2. the declaring class of a and b is the same value class + */ + private static boolean isSameValueClass(Object a, Object b) { + if (a == null || b == null) return false; + + return a.getClass().isValue() && a.getClass() == b.getClass(); + } + + private static int hashCombiner(int accumulator, int value) { + return accumulator * 31 + value; + } + + private static int[] newCounter(int[] counter) { + return new int[] { counter[0] }; + } + + private static boolean recurValueEq(MethodHandle target, Object o1, Object o2, int[] counter) { + assert counter[0] > 0; + + if (o1 == null && o2 == null) return true; + if (o1 == null || o2 == null) return false; + if (o1.getClass() != o2.getClass()) return false; + + if (--counter[0] == 0) { + throw new StackOverflowError("fail to evaluate == for value class " + o1.getClass().getName()); + } + + try { + return (boolean) target.invoke(o1, o2, counter); + } catch (Error|RuntimeException e) { + throw e; + } catch (Throwable e) { + throw new InternalError(e); + } + } + + private static int recurValueHashCode(MethodHandle target, Object o, int[] counter) { + assert counter[0] > 0; + + if (o == null) return 0; + + if (--counter[0] == 0) { + throw new StackOverflowError("fail to evaluate hashCode for value class " + o.getClass().getName()); + } + + try { + return (int) target.invoke(o, counter); + } catch (Error|RuntimeException e) { + throw e; + } catch (Throwable e) { + throw new InternalError(e); + } + } + + private static final MethodHandle FALSE = constant(boolean.class, false); + private static final MethodHandle TRUE = constant(boolean.class, true); + // Substitutability test for float + private static boolean eqValue(float a, float b) { + return Float.floatToRawIntBits(a) == Float.floatToRawIntBits(b); + } + // Substitutability test for double + private static boolean eqValue(double a, double b) { + return Double.doubleToRawLongBits(a) == Double.doubleToRawLongBits(b); + } + private static final MethodHandle OBJECT_EQUALS = + findStatic("eq", methodType(boolean.class, Object.class, Object.class)); + private static final MethodHandle IS_SAME_VALUE_CLASS = + findStatic("isSameValueClass", methodType(boolean.class, Object.class, Object.class)); + private static final MethodHandle IS_NULL = + findStatic("isNull", methodType(boolean.class, Object.class, Object.class)); + private static final MethodHandle HASH_COMBINER = + findStatic("hashCombiner", methodType(int.class, int.class, int.class)); + private static final MethodHandle[] HASHCODE = initHashCode(); + private static final MethodHandle RECUR_VALUE_EQ = + findStatic("recurValueEq", methodType(boolean.class, MethodHandle.class, Object.class, Object.class, int[].class)); + private static final MethodHandle RECUR_VALUE_HASHCODE = + findStatic("recurValueHashCode", methodType(int.class, MethodHandle.class, Object.class, int[].class)); + private static final MethodHandle NEW_COUNTER = + findStatic("newCounter", methodType(int[].class, int[].class)); + + private static MethodHandle[] initHashCode() { + MethodHandle[] mhs = new MethodHandle[Wrapper.COUNT]; + for (Wrapper wrapper : Wrapper.values()) { + if (wrapper == Wrapper.VOID) continue; + Class cls = wrapper == Wrapper.OBJECT ? Objects.class : wrapper.wrapperType(); + mhs[wrapper.ordinal()] = findStatic(cls, "hashCode", + methodType(int.class, wrapper.primitiveType())); + } + return mhs; + } + + private static MethodHandle findStatic(String name, MethodType methodType) { + return findStatic(MethodHandleBuilder.class, name, methodType); + } + private static MethodHandle findStatic(Class cls, String name, MethodType methodType) { + try { + return JLIA.findStatic(cls, name, methodType); + } catch (IllegalAccessException e) { + throw newLinkageError(e); + } + } + + /** + * A "salt" value used for this internal hashcode implementation that + * needs to vary sufficiently from one run to the next so that + * the default hashcode for value classes will vary between JVM runs. + */ + static final int SALT; + static { + long nt = System.nanoTime(); + int value = (int)((nt >>> 32) ^ nt); + SALT = Integer.getInteger("value.bsm.salt", value); + } + + static MethodHandleBuilder newBuilder(Class type) { + assert ValueClass.isConcreteValueClass(type); + + Deque> deque = new ArrayDeque<>(); + deque.add(type); + Map, MethodHandleBuilder> visited = new HashMap<>(); + var builder = new MethodHandleBuilder(type, deque, visited); + visited.put(type, builder); + return builder; + } + + enum Status { + NOT_START, + IN_PROGRESS, + TRAVERSAL_DONE, + READY + } + + final Class type; + final List> fieldValueTypes; + // a map of the type of a field T to a cycle of T -> ... -> V + // where V is this builder's value type + final Map, List>> cyclicMembers = new HashMap<>(); + // recursively-typed fields declared in this builder's value type + final Set> recurFieldTypes = new HashSet<>(); + final Deque> path; + final Map, MethodHandleBuilder> visited;; + volatile Status status = Status.NOT_START; + volatile MethodHandle equalsTarget; + volatile MethodHandle hashCodeTarget; + + static final VarHandle STATUS_HANDLE; + static { + VarHandle vh = null; + try { + vh = MethodHandles.lookup().findVarHandle(MethodHandleBuilder.class, "status", Status.class); + } catch (ReflectiveOperationException e) { + throw new InternalError(e); + } + STATUS_HANDLE = vh; + } + + /** + * Constructs a new MethodHandleBuilder for the given value type. + * + * @param type a value class + * @param path the graph traversal + * @param visited a map of a visited type to a builder + */ + private MethodHandleBuilder(Class type, Deque> path, Map, MethodHandleBuilder> visited) { + assert ValueClass.isConcreteValueClass(type) : type; + this.type = type; + this.fieldValueTypes = valueTypeFields(type); + this.path = path; + this.visited = visited; + if (VERBOSE) { + System.out.println("New builder for " + type.getName() + " " + path); + } + } + + /* + * Returns a method handle that implements equals method for this builder's value class. + */ + MethodHandle equalsTarget() { + if (status != Status.READY) + throw new IllegalStateException(type.getName() + " not ready"); + + var mh = equalsTarget; + if (mh != null) return mh; + + generateMethodHandle(); + return equalsTarget; + } + + /* + * Returns a method handle that implements hashCode method for this builder's value class. + */ + MethodHandle hashCodeTarget() { + if (status != Status.READY) + throw new IllegalStateException(type.getName() + " not ready"); + + var mh = hashCodeTarget; + if (mh != null) return mh; + + generateMethodHandle(); + return hashCodeTarget; + } + + /* + * Build the graph for this builder's value type. Detect all cycles. + * This builder after this method returns is in DONE_TRAVERSAL or READY status. + * + * A builder for type V will change to READY status when the entire graph for V + * is traversed (i.e. all builders in this graph are in DONE_TRAVERSAL or READY + * status). + */ + MethodHandleBuilder build() { + if (status == Status.READY) return this; + + if (!STATUS_HANDLE.compareAndSet(this, Status.NOT_START, Status.IN_PROGRESS)) { + throw new RuntimeException(type.getName() + " in progress"); + } + + // traverse the graph and find all cycles + detectCycles(); + + if (!STATUS_HANDLE.compareAndSet(this, Status.IN_PROGRESS, Status.TRAVERSAL_DONE)) { + throw new RuntimeException(type.getName() + " failed to set done traversal. " + status); + } + + // Check if this node V is ready for building equals/hashCode method handles. + // V is ready if the types of all its fields are done traversal. + if (ready()) { + // Do a pass on all the cycles containing V. V is ready. + // If a node N in the cycle has completed the traversal (i.e. cycles are detected), + // call ready() on N to update its status if ready. + for (List> cycle : cyclicMembers.values()) { + cycle.stream().filter(c -> c != type) + .map(visited::get) + .filter(b -> b.status == Status.TRAVERSAL_DONE) + .forEach(MethodHandleBuilder::ready); + } + } + return this; + } + + /* + * Traverses the graph and finds all cycles. + */ + private void detectCycles() { + LinkedList> deque = new LinkedList<>(); + deque.addAll(fieldValueTypes); + while (!deque.isEmpty()) { + Class n = deque.pop(); + // detect cyclic membership + if (path.contains(n)) { + List> cycle = new ArrayList<>(); + Iterator> iter = path.iterator(); + while (iter.hasNext()) { + Class c = iter.next(); + cycle.add(c); + if (c == n) break; + } + cyclicMembers.put(n, cycle); + path.pop(); + continue; + } + + try { + path.push(n); + if (!visited.containsKey(n)) { + // Duplicate the path and pass it to an unvisited node + Deque> newPath = new ArrayDeque<>(); + newPath.addAll(path); + visited.computeIfAbsent(n, c -> new MethodHandleBuilder(n, newPath, visited)); + } + + var builder = visited.get(n); + switch (builder.status) { + case NOT_START -> builder.build(); + case TRAVERSAL_DONE -> builder.ready(); + } + } finally { + path.pop(); + } + } + + // propagate the cycles to the recursively-typed value classes + // For each cycle A -> B -> C -> A, the cycle is recorded in all + // the nodes (A, B, and C) in this cycle. + for (Map.Entry, List>> e : cyclicMembers.entrySet()) { + Class c = e.getKey(); + List> cycle = e.getValue(); + var builder = visited.get(c); + for (Class ft : cycle) { + if (ft != c && builder.fieldValueTypes.contains(ft)) { + var v = builder.cyclicMembers.put(ft, cycle); + assert v == null || cycle.equals(v) : "mismatched cycle: " + v + " vs " + cycle; + } + } + } + } + + /* + * Tests if this builder is ready for generating equals and hashCode + * method handles for the value class. + * + * This builder is ready if and only if the type graph of all its fields + * are traversed and all cycles are detected. + * + * Before setting to READY, the recursively-typed fields are recorded + * that includes all types in the cycles and the field types which + * references recursive types + */ + private boolean ready() { + if (status == Status.READY) return true; + + boolean inProgress = fieldValueTypes.stream().map(visited::get) + .anyMatch(b -> b.status == Status.IN_PROGRESS); + if (inProgress) + return false; + + // collect the recursively-typed value classes required by this method handle + // all types in the cycles and the field types which references recursive types + recurFieldTypes.addAll(cyclicMembers.keySet()); + for (Class c : fieldValueTypes) { + if (c == type) continue; + + // if this field type references a recursive type + var b = visited.get(c); + if (b.cyclicMembers.size() > 0 || b.recurFieldTypes.size() > 0) + recurFieldTypes.add(c); + }; + + // Finished recording recursively-typed fields. Set to READY. + if (!STATUS_HANDLE.compareAndSet(this, Status.TRAVERSAL_DONE, Status.READY)) { + throw new RuntimeException(type.getName() + " failed to set READY. " + status); + } + return true; + } + + void generateMethodHandle() { + if (status != Status.READY) + throw new IllegalStateException(type.getName() + " not ready"); + + // non-recursive value type + if (recurFieldTypes.isEmpty()) { + if (cyclicMembers.size() > 0) + throw new RuntimeException(type.getName() + " should not reach here"); + + this.equalsTarget = valueTypeEquals(type, getterStream(type, TYPE_SORTER).toList()); + this.hashCodeTarget = valueTypeHashCode(type, getterStream(type, null).toList()); + return; + } + + if (VERBOSE) { + System.out.println(debugString()); + } + + // generate the base function for each recursive type + // boolean base1(MethodHandle entry, MethodHandle base1, MethodHandle base2,....., Object o1, Object o2, int[] counter) + // : + // boolean baseN(MethodHandle entry, MethodHandle base1, MethodHandle base2,....., Object o1, Object o2, int[] counter) + // + List> recursiveTypes = aggregateRecursiveTypes(); + Map, MethodHandle> bases = new LinkedHashMap<>(); + Map, MethodHandle> hashCodeBases = new LinkedHashMap<>(); + for (Class c : recursiveTypes) { + bases.put(c, visited.get(c).generateSubstBase(recursiveTypes)); + hashCodeBases.put(c, visited.get(c).generateHashCodeBase(recursiveTypes)); + } + + var handles = bases.values().stream().toArray(MethodHandle[]::new); + var hashCodeHandles = hashCodeBases.values().stream().toArray(MethodHandle[]::new); + + // The entry point for equals for this value type T looks like this: + // + // boolean entry(MethodHandle entry, MethodHandle base1, MethodHandle base2,....., Object o1, Object o2, int[] counter) { + // int[] newCounter = new int[] { counter[0] } ; + // return baseT(o1, o2, newCounter); + // } + this.equalsTarget = newValueEquals(recursiveTypes, handles); + this.hashCodeTarget = newValueHashCode(recursiveTypes, hashCodeHandles); + + // Precompute the method handles for all recursive data types in the cycles + // They share the generated base method handles. + var cycles = cyclicMembers.values().stream().flatMap(List::stream) + .filter(c -> c != type) + .collect(toMap(Function.identity(), visited::get)); + for (Class n : cycles.keySet()) { + var builder = cycles.get(n); + if (builder.status != Status.READY) { + throw new InternalError(type.getName() + " is not ready: " + status); + } + + var mh = builder.equalsTarget; + var mh2 = builder.hashCodeTarget; + if (mh != null && mh2 != null) { + continue; + } + + // precompute the method handles for each recursive type in the cycle + if (mh == null) { + builder.equalsTarget = builder.newValueEquals(recursiveTypes, handles); + } + if (mh2 == null) { + builder.hashCodeTarget = builder.newValueHashCode(recursiveTypes, hashCodeHandles); + } + } + + // cache the builders with precomputed method handles in the cache + synchronized (CACHED_METHOD_HANDLE_BUILDERS) { + for (Class n : cycles.keySet()) { + try { + // the builder is added to the builder cache and propapate to + // the class value + CACHED_METHOD_HANDLE_BUILDERS.computeIfAbsent(n, cycles::get); + METHOD_HANDLE_BUILDERS.get(n); + } finally { + // Remove it from the builder cache once it's in class value + CACHED_METHOD_HANDLE_BUILDERS.remove(n); + } + } + } + + // equals and hashCode are generated. Clear the path and visited builders. + clear(); + } + + private void clear() { + path.clear(); + visited.clear(); + } + + /* + * Aggregates all recursive data types for this builder's value types. + * The result is used in generating a recursive method handle + * for this builder's value type. + * + * A graph of V: + * V -> P -> V + * -> N -> N (self-recursive) + * -> E -> F -> E + * + * V, P, N, E, F are the mutual recursive types for V. The recursive method handle + * for V is created with the base functions for V, P, N, E, F and it can mutually + * call the recursive method handle for these types. Specifically, MH_V calls + * MH_P which calls MH_V, MH_N which calls itself, and MH_E which calls MH_F. + */ + private List> aggregateRecursiveTypes() { + boolean ready = true; + for (List> cycle : cyclicMembers.values()) { + // ensure all nodes in all cycles that are done traversal and ready for + // method handle generation + cycle.stream().filter(c -> c != type) + .map(visited::get) + .filter(b -> b.status == Status.TRAVERSAL_DONE) + .forEach(MethodHandleBuilder::ready); + + // check the status + ready = ready && cycle.stream().filter(c -> c != type) + .map(visited::get) + .allMatch(b -> b.status == Status.READY); + } + + if (!ready) { + throw new IllegalStateException(type.getName() + " " + status); + } + + /* + * Traverse the graph for V to find all mutual recursive types for V. + * + * Node T is a mutual recursive type for V if any of the following: + * 1. T is a recursively-typed field in V + * 2. T is a type involved the cycles from V ... -> T ... -> V + * 3. T is a mutual recursive type for N where N is a mutual recursive type for V. + */ + Deque> deque = new ArrayDeque<>(); + List> recurTypes = new ArrayList<>(); + recurTypes.add(type); + Stream.concat(recurFieldTypes.stream(), + cyclicMembers.values().stream().flatMap(List::stream)) + .filter(Predicate.not(deque::contains)).forEach(deque::add); + while (!deque.isEmpty()) { + Class c = deque.pop(); + if (recurTypes.contains(c)) continue; + + recurTypes.add(c); + + var builder = visited.get(c); + Stream.concat(builder.recurFieldTypes.stream(), + builder.cyclicMembers.values().stream().flatMap(List::stream)) + .filter(n -> !recurTypes.contains(n) && !deque.contains(n)) + .forEach(deque::push); + } + return recurTypes; + } + + /* + * Create a new method handle that implements equals(T, Object) for value class T + * for this builder using the given base method handles. The return method handle + * is capable of recursively calling itself for value class T whose entry point: + * boolean entry(MethodHandle entry, MethodHandle base1, MethodHandle base2, ..., Object o1, Object o2, int[] counter) { + * int[] newCounter = new int[] { counter[0] }; + * return baseT(o1, o2, newCounter); + * } + * + * The counter is used to keep of node visits and throw StackOverflowError + * if the counter gets "unreasonably" large of a cyclic value graph + * (regardless of how many real stack frames were consumed.) + */ + MethodHandle newValueEquals(List> recursiveTypes, MethodHandle[] bases) { + var entry = equalsEntry(recursiveTypes); + var mh = MethodHandles.insertArguments(recursive(entry, bases), 2, new int[] {MAX_NODE_VISITS}); + return mh.asType(methodType(boolean.class, type, type)); + } + + /* + * Create a new method handle that implements hashCode(T) for value class T + * for this builder using the given base method handles. The return method handle + * is capable of recursively calling itself for value class T whose entry point: + * boolean entry(MethodHandle entry, MethodHandle base1, MethodHandle base2, ..., Object o, int[] counter) { + * int[] newCounter = new int[] { counter[0] }; + * return baseT(o, newCounter); + * } + * + * The counter is used to keep of node visits and throw StackOverflowError + * if the counter gets "unreasonably" large of a cyclic value graph + * (regardless of how many real stack frames were consumed.) + */ + MethodHandle newValueHashCode(List> recursiveTypes, MethodHandle[] bases) { + var entry = hashCodeEntry(recursiveTypes); + var mh = MethodHandles.insertArguments(recursive(entry, bases), 1, new int[] {MAX_NODE_VISITS}); + return mh.asType(methodType(int.class, type)); + } + + /* + * Create a method handle where the first N+1 parameters are MethodHandle and + * N is the number of the recursive value types and followed with + * Object, Object and int[] parameters. The pseudo code looks like this: + * + * boolean eq(MethodHandle entry, MethodHandle base1, MethodHandle base2, ..., Object o1, Object o2, int[] counter) { + * if (o1 == null && o2 == null) return true; + * if (o1 == null || o2 == null) return false; + * if (o1.getClass() != o2. getClass()) return false; + * + * int[] newCounter = new int[] { counter[0]; } + * return (boolean) baseT.invoke(o1, o2, newCounter); + * } + */ + MethodHandle equalsEntry(List> recursiveTypes) { + List> leadingMHParams = new ArrayList<>(); + // the first MethodHandle parameter is this entry point + // followed with MethodHandle parameter for each mutual exclusive value class + int mhParamCount = recursiveTypes.size()+1; + for (int i=0; i < mhParamCount; i++) { + leadingMHParams.add(MethodHandle.class); + } + + MethodType mt = methodType(boolean.class, Stream.concat(leadingMHParams.stream(), Stream.of(type, type, int[].class)) + .collect(toList())); + var allParameters = mt.parameterList(); + MethodHandle instanceTrue = dropArguments(TRUE, 0, allParameters).asType(mt); + MethodHandle instanceFalse = dropArguments(FALSE, 0, allParameters).asType(mt); + MethodHandle isNull = dropArguments(dropArguments(IS_NULL, 0, leadingMHParams), mhParamCount+2, int[].class).asType(mt); + MethodHandle isSameValueType = dropArguments(dropArguments(IS_SAME_VALUE_CLASS, 0, leadingMHParams), mhParamCount+2, int[].class).asType(mt); + + int index = recursiveTypes.indexOf(type); + var mtype = methodType(boolean.class, Stream.concat(leadingMHParams.stream(), Stream.of(type, type, int[].class)).collect(toList())); + var recurEq = RECUR_VALUE_EQ.asType(methodType(boolean.class, MethodHandle.class, type, type, int[].class)); + var eq = permuteArguments(recurEq, mtype, index+1, mhParamCount, mhParamCount+1, mhParamCount+2); + eq = filterArguments(eq, mhParamCount+2, NEW_COUNTER); + + // if both arguments are null, return true; + // otherwise return the method handle corresponding to this type + return guardWithTest(isNull, + instanceTrue, + guardWithTest(isSameValueType, eq, instanceFalse)); + } + + /* + * A base method for substitutability test for a recursive data type, + * a value class with cyclic membership. + * + * The signature of this base method is: + * boolean base(MethodHandle entry, MethodHandle base1, MethodHandle base2, ..., V o1, V o2, int[] counter) + * + * where the first N+1 parameters are MethodHandle and N is the number of + * the recursive value types and followed with Object, Object and int[] parameters. + * + * This method first calls the method handle that tests the substitutability + * of all fields that are not recursively-typed, if any, and then test + * the substitutability of the fields that are of each recursive value type. + * The pseudo code looks like this: + * + * boolean base(MethodHandle entry, MethodHandle base1, MethodHandle base2, ..., V o1, V o2, int[] counter) { + * if (o1 == null && o2 == null) return true; + * if (o1 == null || o2 == null) return false; + * if (o1.getClass() != o2. getClass()) return false; + * + * for each non-recursively-typed field { + * if (field value of o1 != field value of o2) return false; + * } + * + * for each recursively-typed field of type T { + * if (--counter[0] == 0) throw new StackOverflowError(); + * // baseT is the method handle corresponding to the recursive type T + * boolean rc = (boolean) baseT.invoke(o1, o2, counter); + * if (!rc) return false; + * } + * return true; + * } + */ + MethodHandle generateSubstBase(List> recursiveTypes) { + List nonRecurGetters = new ArrayList<>(); + Map, List> recurGetters = new LinkedHashMap<>(); + getterStream(type, TYPE_SORTER).forEach(mh -> { + Class ft = fieldType(mh); + if (!this.recurFieldTypes.contains(ft)) { + nonRecurGetters.add(mh); + } else { + assert recursiveTypes.contains(ft); + recurGetters.computeIfAbsent(ft, t -> new ArrayList<>()).add(mh); + } + }); + + // The first parameter is the method handle of the entry point + // followed with one MethodHandle for each recursive value type + List> leadingMHParams = new ArrayList<>(); + int mhParamCount = recursiveTypes.size()+1; + for (int i=0; i < mhParamCount; i++) { + leadingMHParams.add(MethodHandle.class); + } + + MethodType mt = methodType(boolean.class, + Stream.concat(leadingMHParams.stream(), Stream.of(type, type, int[].class)).collect(toList())); + var allParameters = mt.parameterList(); + + var instanceTrue = dropArguments(TRUE, 0, allParameters).asType(mt); + var instanceFalse = dropArguments(FALSE, 0, allParameters).asType(mt); + var accumulator = dropArguments(TRUE, 0, allParameters).asType(mt); + var isNull = dropArguments(dropArguments(IS_NULL, 0, leadingMHParams), mhParamCount+2, int[].class).asType(mt); + var isSameValueType = dropArguments(dropArguments(IS_SAME_VALUE_CLASS, 0, leadingMHParams), mhParamCount+2, int[].class).asType(mt); + + // This value class contains cyclic membership. + // Create a method handle that first calls the method handle that tests + // the substitutability of all fields that are not recursively-typed, if any, + // and then test the substitutability of the fields that are of each recursive + // value type. + // + // Method handle for the substitutability test for recursive types is built + // before that for non-recursive types. + for (Map.Entry, List> e : recurGetters.entrySet()) { + Class ft = e.getKey(); + int index = recursiveTypes.indexOf(ft); + var mtype = methodType(boolean.class, + Stream.concat(leadingMHParams.stream(), Stream.of(ft, ft, int[].class)).collect(toList())); + var recurEq = RECUR_VALUE_EQ.asType(methodType(boolean.class, MethodHandle.class, ft, ft, int[].class)); + var eq = permuteArguments(recurEq, mtype, index+1, mhParamCount, mhParamCount+1, mhParamCount+2); + for (MethodHandle getter : e.getValue()) { + assert ft == fieldType(getter); + var thisFieldEqual = filterArguments(eq, mhParamCount, getter, getter); + accumulator = guardWithTest(thisFieldEqual, accumulator, instanceFalse); + } + } + + if (nonRecurGetters.isEmpty()) { + // if both arguments are null, return true; + // otherwise return accumulator; + return guardWithTest(isNull, + instanceTrue, + guardWithTest(isSameValueType, accumulator, instanceFalse)); + } else { + // method handle for substitutability test of the non-recursive-typed fields + var mh = valueTypeEquals(type, nonRecurGetters); + mh = dropArguments(dropArguments(mh, 0, leadingMHParams), mhParamCount+2, int[].class).asType(mt); + return guardWithTest(mh, accumulator, instanceFalse); + } + } + + /* + * Create a method handle where the first N+1 parameters are MethodHandle and + * N is the number of the recursive value types and followed with + * Object and int[] parameters. The pseudo code looks like this: + * + * int hashCode(MethodHandle entry, MethodHandle base1, MethodHandle base2, ..., Object o, int[] counter) { + * int[] newCounter = new int[] { counter[0]; } + * return (int) baseT.invoke(o, newCounter); + * } + */ + MethodHandle hashCodeEntry(List> recursiveTypes) { + List> leadingMHParams = new ArrayList<>(); + int mhParamCount = recursiveTypes.size()+1; + // the first MethodHandle parameter is this entry point + // followed with MethodHandle parameter for each mutual exclusive value class + for (int i=0; i < mhParamCount; i++) { + leadingMHParams.add(MethodHandle.class); + } + + int index = recursiveTypes.indexOf(type); + var mtype = methodType(int.class, Stream.concat(leadingMHParams.stream(), Stream.of(type, int[].class)).collect(toList())); + var recurHashCode = RECUR_VALUE_HASHCODE.asType(methodType(int.class, MethodHandle.class, type, int[].class)); + var mh = permuteArguments(recurHashCode, mtype, index+1, mhParamCount, mhParamCount+1); + return filterArguments(mh, mhParamCount+1, NEW_COUNTER); + } + + /** + * A base method for computing the hashcode for a recursive data type, + * a value class with cyclic membership. + * + * The signature of this base method is: + * int base(MethodHandle entry, MethodHandle base1, MethodHandle base2, ..., V o, int[] counter) + * + * where the first N+1 parameters are MethodHandle and N is the number of + * the recursive value types and followed with Object and int[] parameters. + * + * This method will first invoke a method handle to compute the hash code + * of the not recursively-typed fields. Then compute the hash code of the + * remaining recursively-typed fields. + */ + MethodHandle generateHashCodeBase(List> recursiveTypes) { + assert status == Status.READY; + + List nonRecurGetters = new ArrayList<>(); + Map, List> recurGetters = new LinkedHashMap<>(); + getterStream(type, null).forEach(mh -> { + Class ft = fieldType(mh); + if (!this.recurFieldTypes.contains(ft)) { + nonRecurGetters.add(mh); + } else { + assert recursiveTypes.contains(ft); + recurGetters.computeIfAbsent(ft, t -> new ArrayList<>()).add(mh); + } + }); + + int mhParamCount = recursiveTypes.size()+1; + List> leadingMHParams = new ArrayList<>(); + for (int i=0; i < mhParamCount; i++) { // include entry point + leadingMHParams.add(MethodHandle.class); + } + + MethodType mt = methodType(int.class, + Stream.concat(leadingMHParams.stream(), Stream.of(type, int[].class)).collect(toList())); + var allParameters = mt.parameterList(); + var hashCombiner = dropArguments(HASH_COMBINER, 2, allParameters); + var salt = dropArguments(constant(int.class, SALT), 0, allParameters); + var classHasher = dropArguments(hashCodeForType(Class.class).bindTo(type), 0, allParameters); + var accumulator = foldArguments(foldArguments(hashCombiner, 1, classHasher), 0, salt); + for (MethodHandle getter : nonRecurGetters) { + Class ft = fieldType(getter); + var hasher = dropArguments(hashCodeForType(ft), 0, leadingMHParams); + var hashThisField = filterArguments(hasher, mhParamCount, getter); + var combineHashes = foldArguments(hashCombiner, 1, hashThisField); + accumulator = foldArguments(combineHashes, 0, accumulator); + } + + for (Map.Entry, List> e : recurGetters.entrySet()) { + Class ft = e.getKey(); + int index = recursiveTypes.indexOf(ft); + var mtype = methodType(int.class, Stream.concat(leadingMHParams.stream(), Stream.of(ft, int[].class)).collect(toList())); + var recurHashCode = RECUR_VALUE_HASHCODE.asType(methodType(int.class, MethodHandle.class, ft, int[].class)); + var hasher = permuteArguments(recurHashCode, mtype, index + 1, mhParamCount, mhParamCount + 1); + for (MethodHandle getter : e.getValue()) { + assert ft == fieldType(getter); + var hashThisField = filterArguments(hasher, mhParamCount, getter); + var combineHashes = foldArguments(hashCombiner, 1, hashThisField); + accumulator = foldArguments(combineHashes, 0, accumulator); + } + } + return accumulator; + } + + private String debugString() { + StringBuilder sb = new StringBuilder(); + sb.append(type.getName()).append(" ").append(status).append("\n"); + sb.append(fieldValueTypes.stream().filter(c -> !recurFieldTypes.contains(c)) + .map(Class::getName) + .collect(joining(" ", " non-recursive types: ", "\n"))); + sb.append(recurFieldTypes.stream().map(Class::getName) + .collect(joining(" ", " recursive types: ", "\n"))); + for (var n : cyclicMembers.keySet()) { + List> cycle = cyclicMembers.get(n); + sb.append(" cycle: "); + int start = cycle.indexOf(n); + for (int i=start; i < cycle.size(); i++ ) { + sb.append(cycle.get(i).getName()).append(" -> "); + } + for (int i=0; i < start; i++) { + sb.append(cycle.get(i).getName()).append(" -> "); + } + sb.append(n.getName()).append("\n"); + }; + return sb.toString(); + } + } + + private static LinkageError newLinkageError(Throwable e) { + return (LinkageError) new LinkageError().initCause(e); + } + + /** + * Returns {@code true} if the arguments are substitutable to each + * other and {@code false} otherwise. + * Substitutability means that they cannot be distinguished from + * each other in any data-dependent way, meaning that it is safe to substitute + * one for the other. + * + *
      + *
    • If {@code a} and {@code b} are both {@code null}, this method returns + * {@code true}. + *
    • If {@code a} and {@code b} are both instances of the same value class + * {@code V}, this method returns {@code true} if, for all fields {@code f} + * declared in {@code V}, {@code a.f} and {@code b.f} are substitutable. + *
    • If {@code a} and {@code b} are both values of the same builtin primitive type, + * this method returns {@code a == b} with the following exception: + *
        + *
      • For primitive types {@code float} and {@code double} the + * comparison uses the raw bits corresponding to {@link Float#floatToRawIntBits(float)} + * and {@link Double#doubleToRawLongBits(double)} respectively. + *
      + *
    • If {@code a} and {@code b} are both instances of the same reference type, + * this method returns {@code a == b}. + *
    • Otherwise this method returns {@code false}. + *
    + * + *

    For example, + *

    {@code interface Number { ... }
    +     * // ordinary reference class
    +     * class IntNumber implements Number { ... }
    +     * // value class
    +     * value class IntValue implements Number {
    +     *     int i;
    +     *     :
    +     *     public static IntValue of(int i) {...}     // IntValue::of creates a new value instance
    +     * }
    +     * // value class with an Object field
    +     * value class RefValue {
    +     *     Object o;
    +     *     :
    +     * }
    +     *
    +     * var val1 = IntValue.of(10);
    +     * var val2 = IntValue.of(10);                    // val1 and val2 have the same value
    +     * var ref1 = new IntNumber(10);                  // ref1 and ref2 are two reference instances
    +     * var ref2 = new IntNumber(10);
    +     * assertTrue(isSubstitutable(val1, val2));       // val1 and val2 are both value instances of IntValue
    +     * assertFalse(isSubstitutable(ref1, ref2));      // ref1 and ref2 are two reference instances that are not substitutable
    +     * assertTrue(isSubstitutable(ref1, ref1));       // a reference instance is substitutable with itself
    +     *
    +     * var rval1 = RefValue.of(List.of("list"));      // rval1.o and rval2.o both contain a list of one-single element "list"
    +     * var rval2 = RefValue.of(List.of("list");
    +     * var rval3 = RefValue.of(rval1.o);
    +     *
    +     * assertFalse(isSubstitutable(rval1, rval2));    // rval1.o and rval2.o are two different List instances and hence not substitutable
    +     * assertTrue(isSubstitutable(rval1, rval3 ));    // rval1.o and rval3.o are the same reference instance
    +     * }
    + * + * @param a an object + * @param b an object to be compared with {@code a} for substitutability + * @return {@code true} if the arguments are substitutable to each other; + * {@code false} otherwise. + * @param type + * @see Float#floatToRawIntBits(float) + * @see Double#doubleToRawLongBits(double) + */ + private static boolean isSubstitutable(T a, Object b) { + if (VERBOSE) { + System.out.println("substitutable " + a.getClass() + ": " + a + " vs " + b); + } + + // Called directly from the VM. + // + // DO NOT use "==" or "!=" on args "a" and "b", with this code or any of + // its callees. Could be inside of if_acmp bytecode implementation. + + if (a == null && b == null) return true; + if (a == null || b == null) return false; + if (a.getClass() != b.getClass()) return false; + + try { + Class type = a.getClass(); + return (boolean) substitutableInvoker(type).invoke(a, b); + } catch (Error|RuntimeException e) { + if (VERBOSE) e.printStackTrace(); + throw e; + } catch (Throwable e) { + if (VERBOSE) e.printStackTrace(); + throw new InternalError(e); + } + } + + /** + * Produces a method handle which tests if two arguments are + * {@linkplain #isSubstitutable(Object, Object) substitutable}. + * + *
      + *
    • If {@code T} is a non-floating point primitive type, this method + * returns a method handle testing the two arguments are the same value, + * i.e. {@code a == b}. + *
    • If {@code T} is {@code float} or {@code double}, this method + * returns a method handle representing {@link Float#floatToRawIntBits(float)} or + * {@link Double#doubleToRawLongBits(double)} respectively. + *
    • If {@code T} is a reference type that is not {@code Object} and not an + * interface, this method returns a method handle testing + * the two arguments are the same reference, i.e. {@code a == b}. + *
    • If {@code T} is a value class, this method returns + * a method handle that returns {@code true} if + * for all fields {@code f} declared in {@code T}, where {@code U} is + * the type of {@code f}, if {@code a.f} and {@code b.f} are substitutable + * with respect to {@code U}. + *
    • If {@code T} is an interface or {@code Object}, and + * {@code a} and {@code b} are of the same value class {@code V}, + * this method returns a method handle that returns {@code true} if + * {@code a} and {@code b} are substitutable with respect to {@code V}. + *
    + * + * @param type class type + * @param type + * @return a method handle for substitutability test + */ + private static MethodHandle substitutableInvoker(Class type) { + if (type.isPrimitive()) { + return MethodHandleBuilder.builtinPrimitiveSubstitutable(type); + } + if (ValueClass.isConcreteValueClass(type)) { + return SUBST_TEST_METHOD_HANDLES.get(type); + } + return MethodHandleBuilder.referenceTypeEquals(type); + } + + private static final ClassValue METHOD_HANDLE_BUILDERS = new ClassValue<>() { + @Override protected MethodHandleBuilder computeValue(Class type) { + var builder = CACHED_METHOD_HANDLE_BUILDERS.get(type); + if (builder == null) { + builder = MethodHandleBuilder.newBuilder(type).build(); + } + return builder; + } + }; + + // This cache is only used to propagate the builders of mutual recursive types + // A -> B -> C -> A as method handles for equals/hashCode for A, B, C are + // all precomputed. This map should only be non-empty only during the short + // window propagating to the method handle builder class value. + private static Map, MethodHandleBuilder> CACHED_METHOD_HANDLE_BUILDERS = new ConcurrentHashMap<>(); + + // store the method handle for value classes in ClassValue + private static final ClassValue SUBST_TEST_METHOD_HANDLES = new ClassValue<>() { + @Override protected MethodHandle computeValue(Class type) { + return MethodHandleBuilder.valueTypeEquals(type); + } + }; + + /** + * Invoke the hashCode method for the given value object. + * @param o the instance to hash. + * @return the hash code of the given value object. + */ + private static int valueObjectHashCode(Object o) { + Class type = o.getClass(); + try { + // Note: javac disallows user to call super.hashCode if user implemented + // risk for recursion for experts crafting byte-code + if (!type.isValue()) + throw new InternalError("must be value class: " + type.getName()); + + return (int) HASHCODE_METHOD_HANDLES.get(type).invoke(o); + } catch (Error|RuntimeException e) { + throw e; + } catch (Throwable e) { + if (VERBOSE) e.printStackTrace(); + throw new InternalError(e); + } + } + + private static final ClassValue HASHCODE_METHOD_HANDLES = new ClassValue<>() { + @Override protected MethodHandle computeValue(Class type) { + return MethodHandleBuilder.valueTypeHashCode(type); + } + }; + + private static final Comparator TYPE_SORTER = (mh1, mh2) -> { + // sort the getters with the return type + Class t1 = mh1.type().returnType(); + Class t2 = mh2.type().returnType(); + if (t1 == t2) return 0; + + if (t1.isPrimitive()) { + if (!t2.isPrimitive()) { + return 1; + } + } else { + if (t2.isPrimitive()) { + return -1; + } + } + return -1; + }; + + + /** + * Constructs a method handle that is capable of recursively + * calling itself, whose behavior is determined by a non-recursive + * base method handle which gets both the original arguments and a + * reference to the recursive method handle. + *

    + * Here is pseudocode for the resulting loop handle, plus a sketch + * of the behavior of the base function. The symbols {@code A}, + * {@code a}, and {@code R} represent arguments and return value + * for both the recursive function and the base function. + * + *

    {@code
    +     * R recursive(A... a) {
    +     *   MethodHandle recur = &recursive;
    +     *   return base(recur, a...);
    +     * }
    +     * R base(MethodHandle recur, A... a) {
    +     *   ... if (no recursion)  return f(a);  ...
    +     *   var r2 = recur.invokeExact(a2...);
    +     *   var r3 = recur.invokeExact(a3...);
    +     *   ... do stuff with r2, r3, etc. ...
    +     * }
    +     * }
    + *

    + * To make several functions mutually recursive, additional base + * arguments can be passed to this combinator. For each base + * function, a recursive adapter is formed (like {@code recur} + * above). The sequence of recursive adapters is passed as + * initial arguments to each base function. Here is pseudocode + * that corresponds to three base functions: + *

    {@code
    +     * R recursive(A... a) {
    +     *   return base(&recursive, &recursive2, &recursive3, a...);
    +     * }
    +     * R2 recursive2(A2... a2) {
    +     *   return base2(&recursive, &recursive2, &recursive3, a2...);
    +     * }
    +     * R3 recursive3(A3... a3) {
    +     *   return base3(&recursive, &recursive2, &recursive3, a3...);
    +     * }
    +     * R base(MethodHandle recur, MethodHandle recur2,
    +     *        MethodHandle recur3, A... a) {
    +     *   ... if (no recursion)  return f(a);  ...
    +     *   var r2 = recur2.invokeExact(a2...);
    +     *   var r3 = recur3.invokeExact(a3...);
    +     *   ... do stuff with r2, r3, etc. ...
    +     * }
    +     * R2 base2(MethodHandle recur, MethodHandle recur2,
    +     *        MethodHandle recur3, A2... a2) { ... }
    +     * R3 base3(MethodHandle recur, MethodHandle recur2,
    +     *        MethodHandle recur3, A3... a3) { ... }
    +     * }
    + * + * @apiNote Example: + * {@snippet lang="java" : + * // classic recursive implementation of the factorial function + * static int base(MethodHandle recur, int k) throws Throwable { + * if (k <= 1) return 1; + * return k * (int) recur.invokeExact(k - 1); + * } + * // assume MH_base is a handle to the above method + * MethodHandle recur = MethodHandles.recursive(MH_base); + * assertEquals(120, recur.invoke(5)); + * } + *

    + * A constructed recursive method handle is made varargs + * if its corresponding base method handle is varargs. + * @implSpec + * For a single base function, this produces a result equivalent to: + *

    {@code
    +     * class Holder {
    +     *   final MethodHandle recur;
    +     *   static final MH_recur = ...;  //field getter
    +     *   Holder(MethodHandle base) {
    +     *     recur = filterArguments(base, 0, MH_recur).bindTo(this);
    +     *   }
    +     * }
    +     * return new Holder(base).recur;
    +     * }
    + * @param base the logic of the function to make recursive + * @param moreBases additional base functions to be made mutually recursive + * @throws NullPointerException if any argument is null + * @throws IllegalArgumentException if any base function does not accept + * the required leading arguments of type {@code MethodHandle} + * + * @return a method handle which invokes the (first) base function + * on the incoming arguments, with recursive versions of the + * base function (or functions) prepended as extra arguments + * + * @since Valhalla + */ + static MethodHandle recursive(MethodHandle base, MethodHandle... moreBases) { + // freeze the varargs and check for nulls: + List bases2 = List.of(moreBases); + int baseCount = 1 + bases2.size(); + recursiveChecks(base, baseCount); + for (var base2 : bases2) { recursiveChecks(base2, baseCount); } + class Holder { + final MethodHandle recur; + final List recurs2; + MethodHandle recurs2(int i) { return recurs2.get(i); } + Holder() { + // Feed the first baseCount parameters of each base + // with a fetch of each recur, so we can bind to this: + var fetchers = new MethodHandle[baseCount]; + fetchers[0] = MH_recur; + for (int pos = 1; pos < fetchers.length; pos++) { + int i = pos-1; // index into recurs2 + fetchers[pos] = MethodHandles.insertArguments(MH_recurs2, 1, i); + } + this.recur = makeRecur(base, fetchers); + if (baseCount == 1) { + this.recurs2 = List.of(); + } else { + var recurs2 = new MethodHandle[baseCount-1]; + for (int i = 0; i < recurs2.length; i++) { + recurs2[i] = makeRecur(bases2.get(i), fetchers); + } + this.recurs2 = List.of(recurs2); + } + } + MethodHandle makeRecur(MethodHandle base, MethodHandle[] fetchers) { + var adapt = filterArguments(base, 0, fetchers); + for (int pos = 0; pos < fetchers.length; pos++) { + adapt = adapt.bindTo(this); + } + return adapt.withVarargs(base.isVarargsCollector()); + } + static final MethodHandle MH_recur, MH_recurs2; + static { + try { + MH_recur = MethodHandles.lookup() + .findGetter(Holder.class, "recur", MethodHandle.class); + MH_recurs2 = MethodHandles.lookup() + .findVirtual(Holder.class, "recurs2", + methodType(MethodHandle.class, int.class)); + } catch (ReflectiveOperationException ex) { + throw new InternalError(ex); + } + } + } + return new Holder().recur; + } + + private static void recursiveChecks(MethodHandle base, int baseCount) { + MethodType mt = base.type(); // implicit null check + boolean wrong = (mt.parameterCount() < baseCount); + for (int i = 0; i < baseCount && !wrong; i++) { + if (mt.parameterType(i) != MethodHandle.class) { + wrong = true; + } + } + if (!wrong) return; + throw new IllegalArgumentException("missing leading MethodHandle parameters: " + mt); + } + +} diff --git a/src/java.base/share/classes/java/time/Duration.java b/src/java.base/share/classes/java/time/Duration.java index 88d49fa9e45..e7434217ffb 100644 --- a/src/java.base/share/classes/java/time/Duration.java +++ b/src/java.base/share/classes/java/time/Duration.java @@ -119,11 +119,18 @@ * See {@link Instant} for a discussion as to the meaning of the second and time-scales. *

    * This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. - * The {@code equals} method should be used for comparisons. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code Duration} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @implSpec * This class is immutable and thread-safe. @@ -131,6 +138,7 @@ * @since 1.8 */ @jdk.internal.ValueBased +@jdk.internal.MigratedValueClass public final class Duration implements TemporalAmount, Comparable, Serializable { diff --git a/src/java.base/share/classes/java/time/Instant.java b/src/java.base/share/classes/java/time/Instant.java index 1c7a41d7f0e..29acc27c343 100644 --- a/src/java.base/share/classes/java/time/Instant.java +++ b/src/java.base/share/classes/java/time/Instant.java @@ -195,11 +195,18 @@ * {@code ZonedDateTime} and {@code Duration}. *

    * This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. - * The {@code equals} method should be used for comparisons. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code Instant} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @implSpec * This class is immutable and thread-safe. @@ -207,6 +214,7 @@ * @since 1.8 */ @jdk.internal.ValueBased +@jdk.internal.MigratedValueClass public final class Instant implements Temporal, TemporalAdjuster, Comparable, Serializable { diff --git a/src/java.base/share/classes/java/time/LocalDate.java b/src/java.base/share/classes/java/time/LocalDate.java index 6724410da2b..e05add16c0b 100644 --- a/src/java.base/share/classes/java/time/LocalDate.java +++ b/src/java.base/share/classes/java/time/LocalDate.java @@ -127,11 +127,18 @@ * to be accurate will find the ISO-8601 approach unsuitable. *

    * This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. - * The {@code equals} method should be used for comparisons. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code LocalDate} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @implSpec * This class is immutable and thread-safe. @@ -139,6 +146,7 @@ * @since 1.8 */ @jdk.internal.ValueBased +@jdk.internal.MigratedValueClass public final class LocalDate implements Temporal, TemporalAdjuster, ChronoLocalDate, Serializable { diff --git a/src/java.base/share/classes/java/time/LocalDateTime.java b/src/java.base/share/classes/java/time/LocalDateTime.java index 37932fe7a65..450561ea4ab 100644 --- a/src/java.base/share/classes/java/time/LocalDateTime.java +++ b/src/java.base/share/classes/java/time/LocalDateTime.java @@ -123,11 +123,18 @@ * to be accurate will find the ISO-8601 approach unsuitable. *

    * This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. - * The {@code equals} method should be used for comparisons. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code LocalDateTime} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @implSpec * This class is immutable and thread-safe. @@ -135,6 +142,7 @@ * @since 1.8 */ @jdk.internal.ValueBased +@jdk.internal.MigratedValueClass public final class LocalDateTime implements Temporal, TemporalAdjuster, ChronoLocalDateTime, Serializable { diff --git a/src/java.base/share/classes/java/time/LocalTime.java b/src/java.base/share/classes/java/time/LocalTime.java index de2669752ec..ec7c3e19359 100644 --- a/src/java.base/share/classes/java/time/LocalTime.java +++ b/src/java.base/share/classes/java/time/LocalTime.java @@ -113,11 +113,18 @@ * representation, this class, for time-of-day. *

    * This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. - * The {@code equals} method should be used for comparisons. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code LocalTime} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @implSpec * This class is immutable and thread-safe. @@ -125,6 +132,7 @@ * @since 1.8 */ @jdk.internal.ValueBased +@jdk.internal.MigratedValueClass public final class LocalTime implements Temporal, TemporalAdjuster, Comparable, Serializable { diff --git a/src/java.base/share/classes/java/time/MonthDay.java b/src/java.base/share/classes/java/time/MonthDay.java index 6244c14e6e1..2bf5479196d 100644 --- a/src/java.base/share/classes/java/time/MonthDay.java +++ b/src/java.base/share/classes/java/time/MonthDay.java @@ -113,11 +113,18 @@ * to be accurate will find the ISO-8601 approach unsuitable. *

    * This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. - * The {@code equals} method should be used for comparisons. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code MonthDay} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @implSpec * This class is immutable and thread-safe. @@ -125,6 +132,7 @@ * @since 1.8 */ @jdk.internal.ValueBased +@jdk.internal.MigratedValueClass public final class MonthDay implements TemporalAccessor, TemporalAdjuster, Comparable, Serializable { diff --git a/src/java.base/share/classes/java/time/OffsetDateTime.java b/src/java.base/share/classes/java/time/OffsetDateTime.java index 62ff8f23a15..9b83659ea26 100644 --- a/src/java.base/share/classes/java/time/OffsetDateTime.java +++ b/src/java.base/share/classes/java/time/OffsetDateTime.java @@ -116,11 +116,18 @@ * more detail, or when communicating to a database or in a network protocol. *

    * This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. - * The {@code equals} method should be used for comparisons. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code OffsetDateTime} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @implSpec * This class is immutable and thread-safe. @@ -128,6 +135,7 @@ * @since 1.8 */ @jdk.internal.ValueBased +@jdk.internal.MigratedValueClass public final class OffsetDateTime implements Temporal, TemporalAdjuster, Comparable, Serializable { diff --git a/src/java.base/share/classes/java/time/OffsetTime.java b/src/java.base/share/classes/java/time/OffsetTime.java index 831043c47df..b9cd809d246 100644 --- a/src/java.base/share/classes/java/time/OffsetTime.java +++ b/src/java.base/share/classes/java/time/OffsetTime.java @@ -106,11 +106,18 @@ * in an {@code OffsetTime}. *

    * This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. - * The {@code equals} method should be used for comparisons. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code OffsetTime} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @implSpec * This class is immutable and thread-safe. @@ -118,6 +125,7 @@ * @since 1.8 */ @jdk.internal.ValueBased +@jdk.internal.MigratedValueClass public final class OffsetTime implements Temporal, TemporalAdjuster, Comparable, Serializable { diff --git a/src/java.base/share/classes/java/time/Period.java b/src/java.base/share/classes/java/time/Period.java index 5ee80710edb..55c5d5eb5c1 100644 --- a/src/java.base/share/classes/java/time/Period.java +++ b/src/java.base/share/classes/java/time/Period.java @@ -119,11 +119,18 @@ * period may be negative. *

    * This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. - * The {@code equals} method should be used for comparisons. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code Period} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @implSpec * This class is immutable and thread-safe. @@ -131,6 +138,7 @@ * @since 1.8 */ @jdk.internal.ValueBased +@jdk.internal.MigratedValueClass public final class Period implements ChronoPeriod, Serializable { diff --git a/src/java.base/share/classes/java/time/Year.java b/src/java.base/share/classes/java/time/Year.java index 97264ab8c1c..e9549f95103 100644 --- a/src/java.base/share/classes/java/time/Year.java +++ b/src/java.base/share/classes/java/time/Year.java @@ -121,11 +121,18 @@ * to be accurate will find the ISO-8601 approach unsuitable. *

    * This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. - * The {@code equals} method should be used for comparisons. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code Year} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @implSpec * This class is immutable and thread-safe. @@ -133,6 +140,7 @@ * @since 1.8 */ @jdk.internal.ValueBased +@jdk.internal.MigratedValueClass public final class Year implements Temporal, TemporalAdjuster, Comparable, Serializable { diff --git a/src/java.base/share/classes/java/time/YearMonth.java b/src/java.base/share/classes/java/time/YearMonth.java index b24151de3f0..c88af82d0ba 100644 --- a/src/java.base/share/classes/java/time/YearMonth.java +++ b/src/java.base/share/classes/java/time/YearMonth.java @@ -117,11 +117,18 @@ * to be accurate will find the ISO-8601 approach unsuitable. *

    * This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. - * The {@code equals} method should be used for comparisons. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code YearMonth} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @implSpec * This class is immutable and thread-safe. @@ -129,6 +136,7 @@ * @since 1.8 */ @jdk.internal.ValueBased +@jdk.internal.MigratedValueClass public final class YearMonth implements Temporal, TemporalAdjuster, Comparable, Serializable { diff --git a/src/java.base/share/classes/java/time/ZoneId.java b/src/java.base/share/classes/java/time/ZoneId.java index 47758b64e54..ffb9bf068c0 100644 --- a/src/java.base/share/classes/java/time/ZoneId.java +++ b/src/java.base/share/classes/java/time/ZoneId.java @@ -162,11 +162,9 @@ * queried, but not modified, on a Java Runtime with incomplete time-zone information. *

    * This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. - * The {@code equals} method should be used for comparisons. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. * * @implSpec * This abstract sealed class permits two implementations, both of which are immutable and diff --git a/src/java.base/share/classes/java/time/ZonedDateTime.java b/src/java.base/share/classes/java/time/ZonedDateTime.java index 962469b3225..2ccf6fd052a 100644 --- a/src/java.base/share/classes/java/time/ZonedDateTime.java +++ b/src/java.base/share/classes/java/time/ZonedDateTime.java @@ -146,11 +146,18 @@ * represents an instant, especially during a daylight savings overlap. *

    * This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. - * The {@code equals} method should be used for comparisons. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code ZonedDateTime} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @implSpec * A {@code ZonedDateTime} holds state equivalent to three separate objects, @@ -164,6 +171,7 @@ * @since 1.8 */ @jdk.internal.ValueBased +@jdk.internal.MigratedValueClass public final class ZonedDateTime implements Temporal, ChronoZonedDateTime, Serializable { diff --git a/src/java.base/share/classes/java/time/chrono/ChronoLocalDateImpl.java b/src/java.base/share/classes/java/time/chrono/ChronoLocalDateImpl.java index ca226b70d24..6a9f71bb1f7 100644 --- a/src/java.base/share/classes/java/time/chrono/ChronoLocalDateImpl.java +++ b/src/java.base/share/classes/java/time/chrono/ChronoLocalDateImpl.java @@ -140,6 +140,7 @@ * @param the ChronoLocalDate of this date-time * @since 1.8 */ +@jdk.internal.MigratedValueClass abstract class ChronoLocalDateImpl implements ChronoLocalDate, Temporal, TemporalAdjuster, Serializable { diff --git a/src/java.base/share/classes/java/time/chrono/HijrahDate.java b/src/java.base/share/classes/java/time/chrono/HijrahDate.java index 2d3e4f93e69..daea821411a 100644 --- a/src/java.base/share/classes/java/time/chrono/HijrahDate.java +++ b/src/java.base/share/classes/java/time/chrono/HijrahDate.java @@ -105,11 +105,18 @@ * to a new HijrahChronology. *

    * This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. - * The {@code equals} method should be used for comparisons. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code HijrahDate} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @implSpec * This class is immutable and thread-safe. @@ -117,6 +124,7 @@ * @since 1.8 */ @jdk.internal.ValueBased +@jdk.internal.MigratedValueClass public final class HijrahDate extends ChronoLocalDateImpl implements ChronoLocalDate, Serializable { diff --git a/src/java.base/share/classes/java/time/chrono/JapaneseDate.java b/src/java.base/share/classes/java/time/chrono/JapaneseDate.java index d19504462b3..18228dea090 100644 --- a/src/java.base/share/classes/java/time/chrono/JapaneseDate.java +++ b/src/java.base/share/classes/java/time/chrono/JapaneseDate.java @@ -112,11 +112,18 @@ * {@code JapaneseChronology.ERA_HEISEI}.
    *

    * This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. - * The {@code equals} method should be used for comparisons. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code JapaneseDate} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @implSpec * This class is immutable and thread-safe. @@ -124,6 +131,7 @@ * @since 1.8 */ @jdk.internal.ValueBased +@jdk.internal.MigratedValueClass public final class JapaneseDate extends ChronoLocalDateImpl implements ChronoLocalDate, Serializable { diff --git a/src/java.base/share/classes/java/time/chrono/MinguoDate.java b/src/java.base/share/classes/java/time/chrono/MinguoDate.java index 5c5874af29d..acb74d0586e 100644 --- a/src/java.base/share/classes/java/time/chrono/MinguoDate.java +++ b/src/java.base/share/classes/java/time/chrono/MinguoDate.java @@ -92,11 +92,18 @@ * Dates are aligned such that {@code 0001-01-01 (Minguo)} is {@code 1912-01-01 (ISO)}. *

    * This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. - * The {@code equals} method should be used for comparisons. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code MinguoDate} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @implSpec * This class is immutable and thread-safe. @@ -104,6 +111,7 @@ * @since 1.8 */ @jdk.internal.ValueBased +@jdk.internal.MigratedValueClass public final class MinguoDate extends ChronoLocalDateImpl implements ChronoLocalDate, Serializable { diff --git a/src/java.base/share/classes/java/time/chrono/ThaiBuddhistDate.java b/src/java.base/share/classes/java/time/chrono/ThaiBuddhistDate.java index bc17c35ed63..2b531bbe9be 100644 --- a/src/java.base/share/classes/java/time/chrono/ThaiBuddhistDate.java +++ b/src/java.base/share/classes/java/time/chrono/ThaiBuddhistDate.java @@ -92,11 +92,18 @@ * Dates are aligned such that {@code 2484-01-01 (Buddhist)} is {@code 1941-01-01 (ISO)}. *

    * This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. - * The {@code equals} method should be used for comparisons. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code ThaiBuddistDate} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @implSpec * This class is immutable and thread-safe. @@ -104,6 +111,7 @@ * @since 1.8 */ @jdk.internal.ValueBased +@jdk.internal.MigratedValueClass public final class ThaiBuddhistDate extends ChronoLocalDateImpl implements ChronoLocalDate, Serializable { diff --git a/src/java.base/share/classes/java/util/Arrays.java b/src/java.base/share/classes/java/util/Arrays.java index 1bba844f791..352290bb8e2 100644 --- a/src/java.base/share/classes/java/util/Arrays.java +++ b/src/java.base/share/classes/java/util/Arrays.java @@ -26,6 +26,7 @@ package java.util; import jdk.internal.util.ArraysSupport; +import jdk.internal.value.ValueClass; import jdk.internal.vm.annotation.IntrinsicCandidate; import java.io.Serializable; @@ -3504,12 +3505,19 @@ public static T[] copyOf(T[] original, int newLength) { */ @IntrinsicCandidate public static T[] copyOf(U[] original, int newLength, Class newType) { + Class componentType = newType.getComponentType(); + Object tmp = null; + if (original.getClass() == newType && ValueClass.isConcreteValueClass(componentType)) { + tmp = ValueClass.copyOfSpecialArray((Object[])original, 0, newLength); + } else { + tmp = ((Object)newType == (Object)Object[].class) + ? new Object[newLength] + : Array.newInstance(componentType, newLength); + System.arraycopy(original, 0, tmp, 0, + Math.min(original.length, newLength)); + } @SuppressWarnings("unchecked") - T[] copy = ((Object)newType == (Object)Object[].class) - ? (T[]) new Object[newLength] - : (T[]) Array.newInstance(newType.getComponentType(), newLength); - System.arraycopy(original, 0, copy, 0, - Math.min(original.length, newLength)); + T[] copy = (T[])tmp; return copy; } @@ -3803,12 +3811,19 @@ public static T[] copyOfRange(U[] original, int from, int to, Class " + to); } + Class componentType = newType.getComponentType(); + Object tmp = null; + if (original.getClass() == newType && ValueClass.isConcreteValueClass(componentType)) { + tmp = ValueClass.copyOfSpecialArray((Object[])original, from, to); + } else { + tmp = ((Object)newType == (Object)Object[].class) + ? new Object[newLength] + : Array.newInstance(componentType, newLength); + System.arraycopy(original, from, tmp, 0, + Math.min(original.length - from, newLength)); + } @SuppressWarnings("unchecked") - T[] copy = ((Object)newType == (Object)Object[].class) - ? (T[]) new Object[newLength] - : (T[]) Array.newInstance(newType.getComponentType(), newLength); - System.arraycopy(original, from, copy, 0, - Math.min(original.length - from, newLength)); + T[] copy = (T[])tmp; return copy; } diff --git a/src/java.base/share/classes/java/util/HashMap.java b/src/java.base/share/classes/java/util/HashMap.java index 3320b394e6c..445429abb74 100644 --- a/src/java.base/share/classes/java/util/HashMap.java +++ b/src/java.base/share/classes/java/util/HashMap.java @@ -576,14 +576,14 @@ final Node getNode(Object key) { if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & (hash = hash(key))]) != null) { if (first.hash == hash && // always check first node - ((k = first.key) == key || (key != null && key.equals(k)))) + Objects.equals(key, first.key)) return first; if ((e = first.next) != null) { if (first instanceof TreeNode) return ((TreeNode)first).getTreeNode(hash, key); do { if (e.hash == hash && - ((k = e.key) == key || (key != null && key.equals(k)))) + Objects.equals(key, e.key)) return e; } while ((e = e.next) != null); } @@ -639,7 +639,7 @@ final V putVal(int hash, K key, V value, boolean onlyIfAbsent, else { Node e; K k; if (p.hash == hash && - ((k = p.key) == key || (key != null && key.equals(k)))) + Objects.equals(key, p.key)) e = p; else if (p instanceof TreeNode) e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); @@ -652,7 +652,7 @@ else if (p instanceof TreeNode) break; } if (e.hash == hash && - ((k = e.key) == key || (key != null && key.equals(k)))) + Objects.equals(key, e.key)) break; p = e; } @@ -824,7 +824,7 @@ final Node removeNode(int hash, Object key, Object value, (p = tab[index = (n - 1) & hash]) != null) { Node node = null, e; K k; V v; if (p.hash == hash && - ((k = p.key) == key || (key != null && key.equals(k)))) + Objects.equals(key, p.key)) node = p; else if ((e = p.next) != null) { if (p instanceof TreeNode) @@ -832,8 +832,7 @@ else if ((e = p.next) != null) { else { do { if (e.hash == hash && - ((k = e.key) == key || - (key != null && key.equals(k)))) { + Objects.equals(key, e.key)) { node = e; break; } @@ -1212,7 +1211,7 @@ public V computeIfAbsent(K key, Node e = first; K k; do { if (e.hash == hash && - ((k = e.key) == key || (key != null && key.equals(k)))) { + Objects.equals(key, e.key)) { old = e; break; } @@ -1312,7 +1311,7 @@ public V compute(K key, Node e = first; K k; do { if (e.hash == hash && - ((k = e.key) == key || (key != null && key.equals(k)))) { + Objects.equals(key, e.key)) { old = e; break; } @@ -1377,7 +1376,7 @@ public V merge(K key, V value, Node e = first; K k; do { if (e.hash == hash && - ((k = e.key) == key || (key != null && key.equals(k)))) { + Objects.equals(key, e.key)) { old = e; break; } @@ -2024,7 +2023,7 @@ final TreeNode find(int h, Object k, Class kc) { p = pl; else if (ph < h) p = pr; - else if ((pk = p.key) == k || (k != null && k.equals(pk))) + else if (Objects.equals(k, (pk = p.key))) return p; else if (pl == null) p = pr; @@ -2142,7 +2141,7 @@ final TreeNode putTreeVal(HashMap map, Node[] tab, dir = -1; else if (ph < h) dir = 1; - else if ((pk = p.key) == k || (k != null && k.equals(pk))) + else if (Objects.equals(k, (pk = p.key))) return p; else if ((kc == null && (kc = comparableClassFor(k)) == null) || diff --git a/src/java.base/share/classes/java/util/IdentityHashMap.java b/src/java.base/share/classes/java/util/IdentityHashMap.java index 48a6d7b28df..5a11bada32a 100644 --- a/src/java.base/share/classes/java/util/IdentityHashMap.java +++ b/src/java.base/share/classes/java/util/IdentityHashMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,8 +35,8 @@ /** * This class implements the {@code Map} interface with a hash table, using - * reference-equality in place of object-equality when comparing keys (and - * values). In other words, in an {@code IdentityHashMap}, two keys + * `==` in place of object-equality when comparing keys (and values). + * In other words, in an {@code IdentityHashMap}, two keys * {@code k1} and {@code k2} are considered equal if and only if * {@code (k1==k2)}. (In normal {@code Map} implementations (like * {@code HashMap}) two keys {@code k1} and {@code k2} are considered equal diff --git a/src/java.base/share/classes/java/util/Objects.java b/src/java.base/share/classes/java/util/Objects.java index d42a41915c9..885a8891343 100644 --- a/src/java.base/share/classes/java/util/Objects.java +++ b/src/java.base/share/classes/java/util/Objects.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,8 @@ package java.util; +import jdk.internal.javac.PreviewFeature; +import jdk.internal.misc.PreviewFeatures; import jdk.internal.util.Preconditions; import jdk.internal.vm.annotation.ForceInline; @@ -58,8 +60,14 @@ private Objects() { * @param b an object to be compared with {@code a} for equality * @see Object#equals(Object) */ + @ForceInline public static boolean equals(Object a, Object b) { - return (a == b) || (a != null && a.equals(b)); + if (PreviewFeatures.isEnabled()) { + // With --enable-preview avoid acmp + return (a == null) ? b == null : a.equals(b); + } else { + return (a == b) || (a != null && a.equals(b)); + } } /** @@ -177,6 +185,103 @@ public static String toIdentityString(Object o) { return o.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(o)); } + /** + * {@return {@code true} if the object is a non-null reference + * to an {@linkplain Class#isIdentity() identity object}, otherwise {@code false}} + * + * @apiNote + * If the parameter is {@code null}, there is no object + * and hence no class to check for identity; the return is {@code false}. + * To test for a {@linkplain Class#isValue() value object} use: + * {@snippet type="java" : + * if (obj != null && !Objects.hasIdentity(obj)) { + * // obj is a non-null value object + * } + * } + * @param obj an object or {@code null} + * @since Valhalla + */ + @PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS) +// @IntrinsicCandidate + public static boolean hasIdentity(Object obj) { + return (obj == null) ? false : obj.getClass().isIdentity(); + } + + /** + * {@return {@code true} if the object is a non-null reference + * to a {@linkplain Class#isValue() value object}, otherwise {@code false}} + * + * @param obj an object or {@code null} + * @since Valhalla + */ + @PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS) +// @IntrinsicCandidate + public static boolean isValueObject(Object obj) { + return (obj == null) ? false : obj.getClass().isValue(); + } + + /** + * Checks that the specified object reference is an identity object. + * + * @param obj the object reference to check for identity + * @param the type of the reference + * @return {@code obj} if {@code obj} is an identity object + * @throws NullPointerException if {@code obj} is {@code null} + * @throws IdentityException if {@code obj} is not an identity object + * @since Valhalla + */ + @PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS) + @ForceInline + public static T requireIdentity(T obj) { + Objects.requireNonNull(obj); + if (!hasIdentity(obj)) + throw new IdentityException(obj.getClass()); + return obj; + } + + /** + * Checks that the specified object reference is an identity object. + * + * @param obj the object reference to check for identity + * @param message detail message to be used in the event that an + * {@code IdentityException} is thrown; may be null + * @param the type of the reference + * @return {@code obj} if {@code obj} is an identity object + * @throws NullPointerException if {@code obj} is {@code null} + * @throws IdentityException if {@code obj} is not an identity object + * @since Valhalla + */ + @PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS) + @ForceInline + public static T requireIdentity(T obj, String message) { + Objects.requireNonNull(obj); + if (!hasIdentity(obj)) + throw new IdentityException(message); + return obj; + } + + /** + * Checks that the specified object reference is an identity object. + * + * @param obj the object reference to check for identity + * @param messageSupplier supplier of the detail message to be + * used in the event that an {@code IdentityException} is thrown; may be null + * @param the type of the reference + * @return {@code obj} if {@code obj} is an identity object + * @throws NullPointerException if {@code obj} is {@code null} + * @throws IdentityException if {@code obj} is not an identity object + * @since Valhalla + */ + @PreviewFeature(feature = PreviewFeature.Feature.VALUE_OBJECTS) + @ForceInline + public static T requireIdentity(T obj, Supplier messageSupplier) { + Objects.requireNonNull(obj); + if (!hasIdentity(obj)) + throw new IdentityException(messageSupplier == null ? + null : messageSupplier.get()); + return obj; + } + /** * {@return 0 if the arguments are identical and {@code * c.compare(a, b)} otherwise} diff --git a/src/java.base/share/classes/java/util/Optional.java b/src/java.base/share/classes/java/util/Optional.java index e19dde6383e..d1b92f1153f 100644 --- a/src/java.base/share/classes/java/util/Optional.java +++ b/src/java.base/share/classes/java/util/Optional.java @@ -43,10 +43,18 @@ * action if a value is present). * *

    This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code Optional} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @apiNote * {@code Optional} is primarily intended for use as a method return type where @@ -58,6 +66,7 @@ * @param the type of value * @since 1.8 */ +@jdk.internal.MigratedValueClass @jdk.internal.ValueBased public final class Optional { /** diff --git a/src/java.base/share/classes/java/util/OptionalDouble.java b/src/java.base/share/classes/java/util/OptionalDouble.java index 752aaac1e54..ccf5fad9301 100644 --- a/src/java.base/share/classes/java/util/OptionalDouble.java +++ b/src/java.base/share/classes/java/util/OptionalDouble.java @@ -42,10 +42,18 @@ * an action if a value is present). * *

    This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code OptionalDouble} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @apiNote * {@code OptionalDouble} is primarily intended for use as a method return type where @@ -56,6 +64,7 @@ * @since 1.8 */ @jdk.internal.ValueBased +@jdk.internal.MigratedValueClass public final class OptionalDouble { /** * Common instance for {@code empty()}. diff --git a/src/java.base/share/classes/java/util/OptionalInt.java b/src/java.base/share/classes/java/util/OptionalInt.java index c1e62090c25..89135ddbf5b 100644 --- a/src/java.base/share/classes/java/util/OptionalInt.java +++ b/src/java.base/share/classes/java/util/OptionalInt.java @@ -42,10 +42,18 @@ * action if a value is present). * *

    This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code OptionalInt} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @apiNote * {@code OptionalInt} is primarily intended for use as a method return type where @@ -56,6 +64,7 @@ * @since 1.8 */ @jdk.internal.ValueBased +@jdk.internal.MigratedValueClass public final class OptionalInt { /** * Common instance for {@code empty()}. diff --git a/src/java.base/share/classes/java/util/OptionalLong.java b/src/java.base/share/classes/java/util/OptionalLong.java index 2c1171f86e9..94c0fde5cf0 100644 --- a/src/java.base/share/classes/java/util/OptionalLong.java +++ b/src/java.base/share/classes/java/util/OptionalLong.java @@ -42,10 +42,18 @@ * action if a value is present). * *

    This is a value-based - * class; programmers should treat instances that are - * {@linkplain #equals(Object) equal} as interchangeable and should not - * use instances for synchronization, or unpredictable behavior may - * occur. For example, in a future release, synchronization may fail. + * class; programmers should treat instances that are {@linkplain #equals(Object) equal} + * as interchangeable and should not use instances for synchronization, mutexes, or + * with {@linkplain java.lang.ref.Reference object references}. + * + *

    + *
    + * When preview features are enabled, {@code OptionalLong} is a {@linkplain Class#isValue value class}. + * Use of value class instances for synchronization, mutexes, or with + * {@linkplain java.lang.ref.Reference object references} result in + * {@link IdentityException}. + *
    + *
    * * @apiNote * {@code OptionalLong} is primarily intended for use as a method return type where @@ -56,6 +64,7 @@ * @since 1.8 */ @jdk.internal.ValueBased +@jdk.internal.MigratedValueClass public final class OptionalLong { /** * Common instance for {@code empty()}. diff --git a/src/java.base/share/classes/java/util/WeakHashMap.java b/src/java.base/share/classes/java/util/WeakHashMap.java index 7f2960eb2ad..7aee9ec8d83 100644 --- a/src/java.base/share/classes/java/util/WeakHashMap.java +++ b/src/java.base/share/classes/java/util/WeakHashMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,8 +25,8 @@ package java.util; -import java.lang.ref.WeakReference; import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; @@ -122,6 +122,22 @@ * * Java Collections Framework. * + * @apiNote + *
    + *
    + * Objects that are {@linkplain Class#isValue() value objects} do not have identity + * and can not be used as keys in a {@code WeakHashMap}. {@linkplain java.lang.ref.Reference References} + * such as {@linkplain WeakReference WeakReference} used by {@code WeakhashMap} + * to hold the key cannot refer to a value object. + * Methods such as {@linkplain #get get} or {@linkplain #containsKey containsKey} + * will always return {@code null} or {@code false} respectively. + * The methods such as {@linkplain #put put}, {@linkplain #putAll putAll}, + * {@linkplain #compute(Object, BiFunction) compute}, and + * {@linkplain #computeIfAbsent(Object, Function) computeIfAbsent} or any method putting + * a value object, as a key, throw {@link IdentityException}. + *
    + *
    + * * @param the type of keys maintained by this map * @param the type of mapped values * @@ -288,6 +304,8 @@ static Object unmaskNull(Object key) { /** * Checks for equality of non-null reference x and possibly-null y. By * default uses Object.equals. + * The key may be a value object, but it will never be equal to the referent + * so does not need a separate Objects.hasIdentity check. */ private boolean matchesKey(Entry e, Object key) { // check if the given entry refers to the given key without @@ -456,9 +474,11 @@ Entry getEntry(Object key) { * {@code null} if there was no mapping for {@code key}. * (A {@code null} return can also indicate that the map * previously associated {@code null} with {@code key}.) + * @throws IdentityException if {@code key} is a value object */ public V put(@jdk.internal.RequiresIdentity K key, V value) { Object k = maskNull(key); + Objects.requireIdentity(k); int h = hash(k); Entry[] tab = getTable(); int i = indexFor(h, tab.length); @@ -546,8 +566,13 @@ private void transfer(Entry[] src, Entry[] dest) { * These mappings will replace any mappings that this map had for any * of the keys currently in the specified map. * + * @apiNote If the specified map contains keys that are {@linkplain Objects#isValueObject value objects} + * an {@linkplain IdentityException } is thrown when the first value object key is encountered. + * Zero or more mappings may have already been copied to this map. + * * @param m mappings to be stored in this map. * @throws NullPointerException if the specified map is null. + * @throws IdentityException if any of the {@code keys} is a value object */ public void putAll(Map m) { int numKeysToBeAdded = m.size(); diff --git a/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java b/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java index 4cb7048a798..4cbb11c7937 100644 --- a/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java +++ b/src/java.base/share/classes/java/util/concurrent/ConcurrentHashMap.java @@ -48,6 +48,7 @@ import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Set; import java.util.Spliterator; import java.util.concurrent.atomic.AtomicReference; @@ -668,8 +669,8 @@ public final boolean equals(Object o) { return ((o instanceof Map.Entry) && (k = (e = (Map.Entry)o).getKey()) != null && (v = e.getValue()) != null && - (k == key || k.equals(key)) && - (v == (u = val) || v.equals(u))); + (Objects.equals(k, key)) && + v.equals(val)); } /** @@ -681,7 +682,7 @@ Node find(int h, Object k) { do { K ek; if (e.hash == h && - ((ek = e.key) == k || (ek != null && k.equals(ek)))) + (ek = e.key) != null && Objects.equals(k, ek)) return e; } while ((e = e.next) != null); } @@ -952,14 +953,14 @@ public V get(Object key) { if ((tab = table) != null && (n = tab.length) > 0 && (e = tabAt(tab, (n - 1) & h)) != null) { if ((eh = e.hash) == h) { - if ((ek = e.key) == key || (ek != null && key.equals(ek))) + if ((ek = e.key) != null && Objects.equals(key, ek)) return e.val; } else if (eh < 0) return (p = e.find(h, key)) != null ? p.val : null; while ((e = e.next) != null) { if (e.hash == h && - ((ek = e.key) == key || (ek != null && key.equals(ek)))) + ((ek = e.key) != null && Objects.equals(key, ek))) return e.val; } } @@ -997,7 +998,7 @@ public boolean containsValue(Object value) { Traverser it = new Traverser(t, t.length, 0, t.length); for (Node p; (p = it.advance()) != null; ) { V v; - if ((v = p.val) == value || (v != null && value.equals(v))) + if ((v = p.val) != null && Objects.equals(value, v)) return true; } } @@ -1038,7 +1039,7 @@ else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); else if (onlyIfAbsent // check first node without acquiring lock && fh == hash - && ((fk = f.key) == key || (fk != null && key.equals(fk))) + && (fk = f.key) != null && Objects.equals(key, fk) && (fv = f.val) != null) return fv; else { @@ -1050,8 +1051,7 @@ else if (onlyIfAbsent // check first node without acquiring lock for (Node e = f;; ++binCount) { K ek; if (e.hash == hash && - ((ek = e.key) == key || - (ek != null && key.equals(ek)))) { + (ek = e.key) != null && Objects.equals(key, ek)) { oldVal = e.val; if (!onlyIfAbsent) e.val = value; @@ -1143,11 +1143,10 @@ else if ((fh = f.hash) == MOVED) for (Node e = f, pred = null;;) { K ek; if (e.hash == hash && - ((ek = e.key) == key || - (ek != null && key.equals(ek)))) { + ((ek = e.key) != null && Objects.equals(key, ek))) { V ev = e.val; - if (cv == null || cv == ev || - (ev != null && cv.equals(ev))) { + if (cv == null || + (ev != null && Objects.equals(cv, ev))) { oldVal = ev; if (value != null) e.val = value; @@ -1170,8 +1169,8 @@ else if (f instanceof TreeBin) { if ((r = t.root) != null && (p = r.findTreeNode(hash, key, null)) != null) { V pv = p.val; - if (cv == null || cv == pv || - (pv != null && cv.equals(pv))) { + if (cv == null || + (pv != null && Objects.equals(cv, pv))) { oldVal = pv; if (value != null) p.val = value; @@ -1375,7 +1374,7 @@ public boolean equals(Object o) { for (Node p; (p = it.advance()) != null; ) { V val = p.val; Object v = m.get(p.key); - if (v == null || (v != val && !v.equals(val))) + if (!Objects.equals(val, v)) return false; } for (Map.Entry e : m.entrySet()) { @@ -1383,7 +1382,7 @@ public boolean equals(Object o) { if ((mk = e.getKey()) == null || (mv = e.getValue()) == null || (v = get(mk)) == null || - (mv != v && !mv.equals(v))) + !Objects.equals(mv, v)) return false; } } @@ -1507,8 +1506,7 @@ private void readObject(java.io.ObjectInputStream s) Node q; K qk; for (q = first; q != null; q = q.next) { if (q.hash == h && - ((qk = q.key) == k || - (qk != null && k.equals(qk)))) { + ((qk = q.key) != null && Objects.equals(k, qk))) { insertAtFront = false; break; } @@ -1737,7 +1735,7 @@ else if ((f = tabAt(tab, i = (n - 1) & h)) == null) { else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); else if (fh == h // check first node without acquiring lock - && ((fk = f.key) == key || (fk != null && key.equals(fk))) + && ((fk = f.key) != null && Objects.equals(key, fk)) && (fv = f.val) != null) return fv; else { @@ -1842,8 +1840,7 @@ else if ((fh = f.hash) == MOVED) for (Node e = f, pred = null;; ++binCount) { K ek; if (e.hash == h && - ((ek = e.key) == key || - (ek != null && key.equals(ek)))) { + ((ek = e.key) != null && Objects.equals(key, ek))) { val = remappingFunction.apply(key, e.val); if (val != null) e.val = val; @@ -1954,8 +1951,7 @@ else if ((fh = f.hash) == MOVED) for (Node e = f, pred = null;; ++binCount) { K ek; if (e.hash == h && - ((ek = e.key) == key || - (ek != null && key.equals(ek)))) { + ((ek = e.key) != null && Objects.equals(key, ek))) { val = remappingFunction.apply(key, e.val); if (val != null) e.val = val; @@ -2070,8 +2066,7 @@ else if ((fh = f.hash) == MOVED) for (Node e = f, pred = null;; ++binCount) { K ek; if (e.hash == h && - ((ek = e.key) == key || - (ek != null && key.equals(ek)))) { + ((ek = e.key) != null && Objects.equals(key, ek))) { val = remappingFunction.apply(e.val, value); if (val != null) e.val = val; @@ -2264,7 +2259,7 @@ Node find(int h, Object k) { for (;;) { int eh; K ek; if ((eh = e.hash) == h && - ((ek = e.key) == k || (ek != null && k.equals(ek)))) + ((ek = e.key) != null && Objects.equals(k, ek))) return e; if (eh < 0) { if (e instanceof ForwardingNode) { @@ -2759,7 +2754,7 @@ final TreeNode findTreeNode(int h, Object k, Class kc) { p = pl; else if (ph < h) p = pr; - else if ((pk = p.key) == k || (pk != null && k.equals(pk))) + else if ((pk = p.key) != null && Objects.equals(k, pk)) return p; else if (pl == null) p = pr; @@ -2910,7 +2905,7 @@ final Node find(int h, Object k) { int s; K ek; if (((s = lockState) & (WAITER|WRITER)) != 0) { if (e.hash == h && - ((ek = e.key) == k || (ek != null && k.equals(ek)))) + ((ek = e.key) != null && Objects.equals(k, ek))) return e; e = e.next; } @@ -2950,7 +2945,7 @@ else if ((ph = p.hash) > h) dir = -1; else if (ph < h) dir = 1; - else if ((pk = p.key) == k || (pk != null && k.equals(pk))) + else if ((pk = p.key) != null && Objects.equals(k, pk)) return p; else if ((kc == null && (kc = comparableClassFor(k)) == null) || @@ -3549,8 +3544,8 @@ public boolean equals(Object o) { return ((o instanceof Map.Entry) && (k = (e = (Map.Entry)o).getKey()) != null && (v = e.getValue()) != null && - (k == key || k.equals(key)) && - (v == val || v.equals(val))); + Objects.equals(k, key) && + Objects.equals(v, val)); } /** diff --git a/src/java.base/share/classes/jdk/internal/MigratedValueClass.java b/src/java.base/share/classes/jdk/internal/MigratedValueClass.java new file mode 100644 index 00000000000..4852a7a6bcd --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/MigratedValueClass.java @@ -0,0 +1,46 @@ +/* + * 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.internal; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; + +/** + * Indicates the API declaration in question is associated with a migrated value class. + * + * Note this internal annotation is handled specially by the javac compiler. + * To work properly with {@code --release older-release}, it requires special + * handling in {@code make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java} + * and {@code src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java}. + * + * @since 23 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value={TYPE}) +public @interface MigratedValueClass { +} diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java index aa5b6e438f5..05e048aa61e 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java @@ -33,6 +33,7 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; import java.lang.module.ModuleDescriptor; +import java.lang.reflect.ClassFileFormatVersion; import java.lang.reflect.Executable; import java.lang.reflect.Method; import java.net.URI; @@ -621,6 +622,12 @@ public interface JavaLangAccess { StackWalker newStackWalkerInstance(Set options, ContinuationScope contScope, Continuation continuation); + + /** + * Returns the class file format version of the class. + */ + int classFileFormatVersion(Class klass); + /** * Returns '' @ if classloader has a name * explicitly set otherwise @ diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangInvokeAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangInvokeAccess.java index 722447eece6..33e2a30b317 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaLangInvokeAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangInvokeAccess.java @@ -62,6 +62,12 @@ public interface JavaLangInvokeAccess { */ boolean isHiddenMember(int flags); + /** + * Returns true if the member of the given method handle is a null-restricted + * field. + */ + boolean isNullRestrictedField(MethodHandle mh); + /** * Returns a map of class name in internal forms to its corresponding * class bytes per the given stream of LF_RESOLVE and SPECIES_RESOLVE @@ -170,4 +176,14 @@ public interface JavaLangInvokeAccess { * This method should only be used by ReflectionFactory::newConstructorForSerialization. */ MethodHandle serializableConstructor(Class decl, Constructor ctorToCall) throws IllegalAccessException; + + /** + * Asserts a method handle to be another type without the conversion adaptions. + * Useful to avoid many redundant casts. + * + * @param original original MH + * @param assertedType the asserted type the origina MH can execute as + * @return the cheap view without extra adaptions + */ + MethodHandle assertAsType(MethodHandle original, MethodType assertedType); } diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangReflectAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangReflectAccess.java index d0c415d2dc6..e3749f73700 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaLangReflectAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangReflectAccess.java @@ -64,6 +64,10 @@ public interface JavaLangReflectAccess { /** Tests if this is a trusted final field */ public boolean isTrustedFinalField(Field f); + + /** Tests if this is a null-restricted field */ + public boolean isNullRestrictedField(Field f); + /** Returns a new instance created by the given constructor with access check */ public T newInstance(Constructor ctor, Object[] args, Class caller) throws IllegalAccessException, InstantiationException, InvocationTargetException; diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractAttributeMapper.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractAttributeMapper.java index 648582b8a7d..5f4357a225c 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractAttributeMapper.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractAttributeMapper.java @@ -311,6 +311,24 @@ protected void writeBody(BufWriter bufWriter, LineNumberTableAttribute attr) { } } + public static final class LoadableDescriptorsMapper extends AbstractAttributeMapper { + public static final LoadableDescriptorsMapper INSTANCE = new LoadableDescriptorsMapper(); + + private LoadableDescriptorsMapper() { + super(NAME_LOADABLE_DESCRIPTORS, AttributeStability.CP_REFS); + } + + @Override + public LoadableDescriptorsAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundLoadableDescriptorsAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, LoadableDescriptorsAttribute attr) { + Util.writeListIndices(buf, attr.loadableDescriptors()); + } + } + public static final class LocalVariableTableMapper extends AbstractAttributeMapper { public static final LocalVariableTableMapper INSTANCE = new LocalVariableTableMapper(); diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AccessFlagsImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AccessFlagsImpl.java index c290dd438c5..87089efb01f 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/AccessFlagsImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AccessFlagsImpl.java @@ -26,6 +26,7 @@ import java.lang.classfile.AccessFlags; import java.lang.reflect.AccessFlag; +import java.lang.reflect.ClassFileFormatVersion; import java.util.Set; public final class AccessFlagsImpl extends AbstractElement @@ -54,7 +55,7 @@ public int flagsMask() { @Override public Set flags() { if (flags == null) - flags = AccessFlag.maskToAccessFlags(flagsMask, location); + flags = AccessFlag.maskToAccessFlags(flagsMask, location, ClassFileFormatVersion.CURRENT_PREVIEW_FEATURES); return flags; } diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/BoundAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/impl/BoundAttribute.java index 2027d94b2a8..bb5bc90e9ec 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/BoundAttribute.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/BoundAttribute.java @@ -206,7 +206,8 @@ public BoundStackMapTableAttribute(CodeImpl code, ClassReader cf, AttributeMappe @Override public List entries() { if (entries == null) { - entries = new StackMapDecoder(classReader, payloadStart, ctx, StackMapDecoder.initFrameLocals(method)).entries(); + entries = new StackMapDecoder(classReader, payloadStart, ctx, StackMapDecoder.initFrameLocals(method), + StackMapDecoder.initFrameUnsets(method)).entries(); } return entries; } @@ -944,6 +945,23 @@ public List permittedSubclasses() { } } + public static final class BoundLoadableDescriptorsAttribute extends BoundAttribute + implements LoadableDescriptorsAttribute { + private List loadableDescriptors = null; + + public BoundLoadableDescriptorsAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List loadableDescriptors() { + if (loadableDescriptors == null) { + loadableDescriptors = readEntryList(payloadStart, Utf8Entry.class); + } + return loadableDescriptors; + } + } + public abstract static sealed class BoundCodeAttribute extends BoundAttribute implements CodeAttribute @@ -1023,6 +1041,8 @@ public static AttributeMapper standardAttribute(Utf8Entry name) { name.equalsString(NAME_INNER_CLASSES) ? innerClasses() : null; case 0x653f0551 -> name.equalsString(NAME_LINE_NUMBER_TABLE) ? lineNumberTable() : null; + case 0x5f348b64 -> + name.equalsString(NAME_LOADABLE_DESCRIPTORS) ? loadableDescriptors() : null; case 0x64c75927 -> name.equalsString(NAME_LOCAL_VARIABLE_TABLE) ? localVariableTable() : null; case 0x6697f98d -> diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/BufWriterImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/BufWriterImpl.java index dda9accd8b9..3d370f45c9a 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/BufWriterImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/BufWriterImpl.java @@ -26,16 +26,19 @@ package jdk.internal.classfile.impl; import java.lang.classfile.BufWriter; +import java.lang.classfile.ClassModel; import java.lang.classfile.constantpool.ClassEntry; import java.lang.classfile.constantpool.ConstantPool; import java.lang.classfile.constantpool.ConstantPoolBuilder; import java.lang.classfile.constantpool.PoolEntry; import java.util.Arrays; +import java.util.HashSet; import jdk.internal.access.JavaLangAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.vm.annotation.ForceInline; +import static java.lang.classfile.ClassFile.*; import static java.lang.classfile.constantpool.PoolEntry.TAG_UTF8; import static jdk.internal.util.ModifiedUtf.putChar; import static jdk.internal.util.ModifiedUtf.utfLen; @@ -46,6 +49,9 @@ public final class BufWriterImpl implements BufWriter { private final ConstantPoolBuilder constantPool; private final ClassFileImpl context; private LabelContext labelContext; + private WritableField.UnsetField[] strictInstanceFields; // do not modify array contents + private ClassModel lastStrictCheckClass; // buf writer has short life, so do not need weak here + private boolean lastStrictCheckResult; private boolean labelsMatch; private final ClassEntry thisClass; private final int majorVersion; @@ -68,6 +74,33 @@ public BufWriterImpl(ConstantPoolBuilder constantPool, ClassFileImpl context, in this.majorVersion = majorVersion; } + public boolean strictFieldsMatch(ClassModel cm) { + // We have a cache because this check will be called multiple times + // if a MethodModel is sent wholesale + if (lastStrictCheckClass == cm) { + return lastStrictCheckResult; + } + + var result = doStrictFieldsMatchCheck(cm); + lastStrictCheckClass = cm; + lastStrictCheckResult = result; + return result; + } + + private boolean doStrictFieldsMatchCheck(ClassModel cm) { + // TODO only check for preview class files? + // UTF8 Entry can be used as equality objects + var checks = new HashSet<>(Arrays.asList(getStrictInstanceFields())); + for (var f : cm.fields()) { + if ((f.flags().flagsMask() & (ACC_STATIC | ACC_STRICT_INIT)) == ACC_STRICT_INIT) { + if (!checks.remove(new WritableField.UnsetField(f.fieldName(), f.fieldType()))) { + return false; // Field mismatch! + } + } + } + return checks.isEmpty(); + } + @Override public ConstantPoolBuilder constantPool() { return constantPool; @@ -82,6 +115,16 @@ public void setLabelContext(LabelContext labelContext, boolean labelsMatch) { this.labelsMatch = labelsMatch; } + public WritableField.UnsetField[] getStrictInstanceFields() { + assert strictInstanceFields != null : "should access only after setter call in DirectClassBuilder"; + return strictInstanceFields; + } + + public void setStrictInstanceFields(WritableField.UnsetField[] strictInstanceFields) { + this.strictInstanceFields = strictInstanceFields; + } + + public boolean labelsMatch(LabelContext lc) { return labelsMatch && labelContext instanceof DirectCodeBuilder dcb diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/ClassPrinterImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassPrinterImpl.java index 8a44890fe66..994d9891e22 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/ClassPrinterImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassPrinterImpl.java @@ -939,6 +939,9 @@ private static Node[] attributesToTree(List> attributes, Verbosity case PermittedSubclassesAttribute psa -> nodes.add(list("permitted subclasses", "subclass", psa.permittedSubclasses().stream() .map(e -> e.name().stringValue()))); + case LoadableDescriptorsAttribute pa -> + nodes.add(list("loadable descriptors", "descriptor", pa.loadableDescriptors().stream() + .map(e -> e.stringValue()))); default -> {} } if (verbosity == Verbosity.TRACE_ALL) switch (attr) { diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/ClassRemapperImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassRemapperImpl.java index 6dbde898ee5..fef3b75f2ac 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/ClassRemapperImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassRemapperImpl.java @@ -84,6 +84,8 @@ public void accept(ClassBuilder clb, ClassElement cle) { clb.with(PermittedSubclassesAttribute.ofSymbols( psa.permittedSubclasses().stream().map(ps -> map(ps.asSymbol())).toList())); + case LoadableDescriptorsAttribute pa -> + clb.with(LoadableDescriptorsAttribute.of(pa.loadableDescriptors())); case RuntimeVisibleAnnotationsAttribute aa -> clb.with(RuntimeVisibleAnnotationsAttribute.of( mapAnnotations(aa.annotations()))); diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/CodeImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/CodeImpl.java index c8c6d254650..68dbfea306d 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/CodeImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/CodeImpl.java @@ -142,11 +142,12 @@ public List> attributes() { @Override public void writeTo(BufWriterImpl buf) { - if (buf.canWriteDirect(classReader)) { + var methodInfo = (MethodInfo) enclosingMethod; + if (Util.canSkipMethodInflation(classReader, methodInfo, buf)) { super.writeTo(buf); } else { - DirectCodeBuilder.build((MethodInfo) enclosingMethod, + DirectCodeBuilder.build(methodInfo, Util.writingAll(this), (SplitConstantPool)buf.constantPool(), buf.context(), @@ -291,7 +292,7 @@ private void inflateJumpTargets() { int p = stackMapPos + 2; for (int i = 0; i < nEntries; ++i) { int frameType = classReader.readU1(p); - int offsetDelta; + int offsetDelta = -1; if (frameType < 64) { offsetDelta = frameType; ++p; @@ -300,8 +301,15 @@ else if (frameType < 128) { offsetDelta = frameType & 0x3f; p = adjustForObjectOrUninitialized(p + 1); } - else + else { switch (frameType) { + case StackMapDecoder.EARLY_LARVAL -> { + int numberOfUnsetFields = classReader.readU2(p + 1); + p += 3; + p += 2 * numberOfUnsetFields; + i--; // one more enclosed frame + continue; + } case 247 -> { offsetDelta = classReader.readU2(p + 1); p = adjustForObjectOrUninitialized(p + 3); @@ -334,6 +342,7 @@ else if (frameType < 128) { } default -> throw new IllegalArgumentException("Bad frame type: " + frameType); } + } bci += offsetDelta + 1; inflateLabel(bci); } diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/DirectClassBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/DirectClassBuilder.java index 0e82c545359..25ffb15e7f6 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/DirectClassBuilder.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/DirectClassBuilder.java @@ -35,6 +35,8 @@ import java.util.List; import java.util.function.Consumer; +import static java.lang.classfile.ClassFile.PREVIEW_MINOR_VERSION; +import static java.lang.classfile.ClassFile.latestMajorVersion; import static java.util.Objects.requireNonNull; public final class DirectClassBuilder @@ -44,9 +46,10 @@ public final class DirectClassBuilder /** The value of default class access flags */ static final int DEFAULT_CLASS_FLAGS = ClassFile.ACC_PUBLIC | ClassFile.ACC_SUPER; static final Util.Writable[] EMPTY_WRITABLE_ARRAY = {}; + static final WritableField[] EMPTY_WRITABLE_FIELD_ARRAY = {}; static final ClassEntry[] EMPTY_CLASS_ENTRY_ARRAY = {}; final ClassEntry thisClassEntry; - private Util.Writable[] fields = EMPTY_WRITABLE_ARRAY; + private WritableField[] fields = EMPTY_WRITABLE_FIELD_ARRAY; private Util.Writable[] methods = EMPTY_WRITABLE_ARRAY; private int fieldsCount = 0; private int methodsCount = 0; @@ -129,7 +132,7 @@ public ClassBuilder transformMethod(MethodModel method, MethodTransform transfor // internal / for use by elements - ClassBuilder withField(Util.Writable field) { + ClassBuilder withField(WritableField field) { if (fieldsCount >= fields.length) { int newCapacity = fieldsCount + 8; this.fields = Arrays.copyOf(fields, newCapacity); @@ -168,7 +171,6 @@ public void setSizeHint(int sizeHint) { this.sizeHint = sizeHint; } - public byte[] build() { // The logic of this is very carefully ordered. We want to avoid @@ -195,6 +197,13 @@ else if ((flags & ClassFile.ACC_MODULE) == 0 && !"java/lang/Object".equals(thisC // The tail consists of fields and methods, and attributes // This should trigger all the CP/BSM mutation Util.writeList(tail, fields, fieldsCount); + WritableField.UnsetField[] strictInstanceFields; + if (minorVersion == PREVIEW_MINOR_VERSION && majorVersion >= Util.VALUE_OBJECTS_MAJOR) { + strictInstanceFields = WritableField.filterStrictInstanceFields(constantPool, fields, fieldsCount); + } else { + strictInstanceFields = WritableField.UnsetField.EMPTY_ARRAY; + } + tail.setStrictInstanceFields(strictInstanceFields); Util.writeList(tail, methods, methodsCount); int attributesOffset = tail.size(); attributes.writeTo(tail); diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/DirectCodeBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/DirectCodeBuilder.java index 5bdbb571b68..10ad0149508 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/DirectCodeBuilder.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/DirectCodeBuilder.java @@ -40,6 +40,7 @@ import java.util.function.Consumer; import java.util.function.Function; +import static java.lang.constant.ConstantDescs.INIT_NAME; import static java.util.Objects.requireNonNull; import static jdk.internal.classfile.impl.BytecodeHelpers.*; import static jdk.internal.classfile.impl.RawBytecodeHelper.*; @@ -375,7 +376,7 @@ public void writeBody(BufWriterImpl buf) { dcb.methodInfo.methodTypeSymbol().displayDescriptor())); } - boolean codeMatch = dcb.original != null && codeAndExceptionsMatch(codeLength); + boolean codeMatch = dcb.codeAndExceptionsMatch(codeLength, buf); buf.setLabelContext(dcb, codeMatch); var context = dcb.context; if (context.stackMapsWhenRequired()) { @@ -454,7 +455,7 @@ public Utf8Entry attributeName() { } } - private boolean codeAndExceptionsMatch(int codeLength) { + private boolean codeAndExceptionsMatch(int codeLength, BufWriterImpl buf) { boolean codeAttributesMatch; if (original instanceof CodeImpl cai && canWriteDirect(cai.constantPool())) { codeAttributesMatch = cai.codeLength == curPc() @@ -464,6 +465,22 @@ private boolean codeAndExceptionsMatch(int codeLength) { writeExceptionHandlers(bw); codeAttributesMatch = cai.classReader.compare(bw, 0, cai.exceptionHandlerPos, bw.size()); } + + if (codeAttributesMatch) { + var thisIsConstructor = methodInfo.methodName().equalsString(INIT_NAME); + var originalIsConstructor = cai.enclosingMethod.methodName().equalsString(INIT_NAME); + if (thisIsConstructor || originalIsConstructor) { + if (thisIsConstructor != originalIsConstructor) { + codeAttributesMatch = false; + } + } + + if (codeAttributesMatch && thisIsConstructor) { + if (!buf.strictFieldsMatch(cai.classReader.getContainedClass())) { + codeAttributesMatch = false; + } + } + } } else codeAttributesMatch = false; diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/DirectFieldBuilder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/DirectFieldBuilder.java index 8dae9665f83..8089e8e5116 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/DirectFieldBuilder.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/DirectFieldBuilder.java @@ -36,7 +36,7 @@ public final class DirectFieldBuilder extends AbstractDirectBuilder - implements TerminalFieldBuilder, Util.Writable { + implements TerminalFieldBuilder, WritableField { private final Utf8Entry name; private final Utf8Entry desc; private int flags; @@ -84,4 +84,20 @@ public void writeTo(BufWriterImpl buf) { buf.writeU2U2U2(flags, buf.cpIndex(name), buf.cpIndex(desc)); attributes.writeTo(buf); } + + // These values must only be accessed after the field is definitely configured + @Override + public Utf8Entry fieldName() { + return name; + } + + @Override + public Utf8Entry fieldType() { + return desc; + } + + @Override + public int fieldFlags() { + return flags; + } } diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/FieldImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/FieldImpl.java index a322c08a55a..c4179ba0b66 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/FieldImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/FieldImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,7 +38,7 @@ public final class FieldImpl extends AbstractElement - implements FieldModel, Util.Writable { + implements FieldModel, WritableField { private final ClassReader reader; private final int startPos, endPos, attributesPos; @@ -53,7 +53,7 @@ public FieldImpl(ClassReader reader, int startPos, int endPos, int attributesPos @Override public AccessFlags flags() { - return new AccessFlagsImpl(AccessFlag.Location.FIELD, reader.readU2(startPos)); + return new AccessFlagsImpl(AccessFlag.Location.FIELD, fieldFlags()); } @Override @@ -74,6 +74,11 @@ public Utf8Entry fieldType() { return reader.readEntry(startPos + 4, Utf8Entry.class); } + @Override + public int fieldFlags() { + return reader.readU2(startPos); + } + @Override public List> attributes() { if (attributes == null) { diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/MethodImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/MethodImpl.java index 10341f79135..cfb59829863 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/MethodImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/MethodImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -98,7 +98,7 @@ public List> attributes() { @Override public void writeTo(BufWriterImpl buf) { - if (buf.canWriteDirect(reader)) { + if (Util.canSkipMethodInflation(reader, this, buf)) { reader.copyBytesTo(buf, startPos, endPos - startPos); } else { diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapDecoder.java b/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapDecoder.java index c5ce2204c09..af919d60ff5 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapDecoder.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/StackMapDecoder.java @@ -26,6 +26,7 @@ package jdk.internal.classfile.impl; import java.lang.classfile.BufWriter; +import java.lang.classfile.ClassModel; import java.lang.classfile.ClassReader; import java.lang.classfile.Label; import java.lang.classfile.MethodModel; @@ -35,36 +36,47 @@ import java.lang.classfile.attribute.StackMapFrameInfo.UninitializedVerificationTypeInfo; import java.lang.classfile.attribute.StackMapFrameInfo.VerificationTypeInfo; import java.lang.classfile.constantpool.ClassEntry; +import java.lang.classfile.constantpool.NameAndTypeEntry; +import java.lang.classfile.constantpool.PoolEntry; +import java.lang.classfile.constantpool.Utf8Entry; import java.lang.constant.ConstantDescs; import java.lang.constant.MethodTypeDesc; import java.lang.reflect.AccessFlag; +import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Objects; -import static java.lang.classfile.ClassFile.ACC_STATIC; +import jdk.internal.access.SharedSecrets; + +import static java.lang.classfile.ClassFile.*; import static java.lang.classfile.attribute.StackMapFrameInfo.VerificationTypeInfo.*; import static java.util.Objects.requireNonNull; public class StackMapDecoder { - private static final int + static final int + EARLY_LARVAL = 246, SAME_LOCALS_1_STACK_ITEM_EXTENDED = 247, SAME_EXTENDED = 251; + private static final int BASE_FRAMES_UPPER_LIMIT = SAME_LOCALS_1_STACK_ITEM_EXTENDED; // not inclusive private static final StackMapFrameInfo[] NO_STACK_FRAME_INFOS = {}; private final ClassReader classReader; private final int pos; private final LabelContext ctx; private final List initFrameLocals; + private final List initFrameUnsets; private int p; - StackMapDecoder(ClassReader classReader, int pos, LabelContext ctx, List initFrameLocals) { + StackMapDecoder(ClassReader classReader, int pos, LabelContext ctx, List initFrameLocals, + List initFrameUnsets) { this.classReader = classReader; this.pos = pos; this.ctx = ctx; this.initFrameLocals = initFrameLocals; + this.initFrameUnsets = initFrameUnsets; } static List initFrameLocals(MethodModel method) { @@ -101,6 +113,35 @@ public static List initFrameLocals(ClassEntry thisClass, S return List.of(vtis); } + static List initFrameUnsets(MethodModel method) { + return initFrameUnsets(method.parent().orElseThrow(), + method.methodName()); + } + + private static List initFrameUnsets(ClassModel clazz, Utf8Entry methodName) { + if (!methodName.equalsString(ConstantDescs.INIT_NAME)) + return List.of(); + if (clazz.minorVersion() != PREVIEW_MINOR_VERSION || clazz.majorVersion() < Util.VALUE_OBJECTS_MAJOR) + return List.of(); + var l = new ArrayList(clazz.fields().size()); + for (var field : clazz.fields()) { + if ((field.flags().flagsMask() & (ACC_STATIC | ACC_STRICT_INIT)) == ACC_STRICT_INIT) { // instance strict + l.add(TemporaryConstantPool.INSTANCE.nameAndTypeEntry(field.fieldName(), field.fieldType())); + } + } + return List.copyOf(l); + } + + private static List initFrameUnsets(MethodInfo mi, WritableField.UnsetField[] unsets) { + if (!mi.methodName().equalsString(ConstantDescs.INIT_NAME)) + return List.of(); + var l = new ArrayList(unsets.length); + for (var field : unsets) { + l.add(TemporaryConstantPool.INSTANCE.nameAndTypeEntry(field.name(), field.type())); + } + return List.copyOf(l); + } + public static void writeFrames(BufWriter b, List entries) { var buf = (BufWriterImpl)b; var dcb = (DirectCodeBuilder)buf.labelContext(); @@ -109,6 +150,7 @@ public static void writeFrames(BufWriter b, List entries) { mi.methodName().stringValue(), mi.methodTypeSymbol(), (mi.methodFlags() & ACC_STATIC) != 0); + var prevUnsets = initFrameUnsets(mi, buf.getStrictInstanceFields()); int prevOffset = -1; // avoid using method handles due to early bootstrap StackMapFrameInfo[] infos = entries.toArray(NO_STACK_FRAME_INFOS); @@ -124,14 +166,32 @@ public int compare(final StackMapFrameInfo o1, final StackMapFrameInfo o2) { if (offset == prevOffset) { throw new IllegalArgumentException("Duplicated stack frame bytecode index: " + offset); } - writeFrame(buf, offset - prevOffset - 1, prevLocals, fr); + writeFrame(buf, offset - prevOffset - 1, prevLocals, prevUnsets, fr); prevOffset = offset; prevLocals = fr.locals(); + prevUnsets = fr.unsetFields(); + } + } + + // In sync with StackMapGenerator::needsLarvalFrame + private static boolean needsLarvalFrameForTransition(List prevUnsets, StackMapFrameInfo fr) { + if (prevUnsets.equals(fr.unsetFields())) + return false; + if (!fr.locals().contains(SimpleVerificationTypeInfo.UNINITIALIZED_THIS)) { + assert fr.unsetFields().isEmpty() : fr; // should be checked in StackMapFrameInfo constructor + return false; } + return true; } - private static void writeFrame(BufWriterImpl out, int offsetDelta, List prevLocals, StackMapFrameInfo fr) { + private static void writeFrame(BufWriterImpl out, int offsetDelta, List prevLocals, List prevUnsets, StackMapFrameInfo fr) { if (offsetDelta < 0) throw new IllegalArgumentException("Invalid stack map frames order"); + // enclosing frames + if (needsLarvalFrameForTransition(prevUnsets, fr)) { + out.writeU1(EARLY_LARVAL); + Util.writeListIndices(out, fr.unsetFields()); + } + // base frame if (fr.stack().isEmpty()) { int commonLocalsSize = Math.min(prevLocals.size(), fr.locals().size()); int diffLocalsSize = fr.locals().size() - prevLocals.size(); @@ -181,13 +241,34 @@ private static void writeTypeInfo(BufWriterImpl bw, VerificationTypeInfo vti) { } } + // Copied from BoundAttribute + List readEntryList(int p, Class type) { + int cnt = classReader.readU2(p); + p += 2; + var entries = new Object[cnt]; + int end = p + (cnt * 2); + for (int i = 0; p < end; i++, p += 2) { + entries[i] = classReader.readEntry(p, type); + } + return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArray(entries); + } + List entries() { p = pos; List locals = initFrameLocals, stack = List.of(); + List unsetFields = initFrameUnsets; int bci = -1; var entries = new StackMapFrameInfo[u2()]; for (int ei = 0; ei < entries.length; ei++) { - int frameType = classReader.readU1(p++); + int actualFrameType = classReader.readU1(p++); + int frameType = actualFrameType; // effective frame type for parsing + // enclosing frames handling + if (frameType == EARLY_LARVAL) { + unsetFields = readEntryList(p, NameAndTypeEntry.class); + p += 2 + unsetFields.size() * 2; + frameType = classReader.readU1(p++); + } + // base frame handling if (frameType < 64) { bci += frameType + 1; stack = List.of(); @@ -195,8 +276,8 @@ List entries() { bci += frameType - 63; stack = List.of(readVerificationTypeInfo()); } else { - if (frameType < SAME_LOCALS_1_STACK_ITEM_EXTENDED) - throw new IllegalArgumentException("Invalid stackmap frame type: " + frameType); + if (frameType < BASE_FRAMES_UPPER_LIMIT) + throw new IllegalArgumentException("Invalid base frame type: " + frameType); bci += u2() + 1; if (frameType == SAME_LOCALS_1_STACK_ITEM_EXTENDED) { stack = List.of(readVerificationTypeInfo()); @@ -223,10 +304,15 @@ List entries() { stack = List.of(newStack); } } - entries[ei] = new StackMapFrameImpl(frameType, - ctx.getLabel(bci), - locals, - stack); + if (actualFrameType != EARLY_LARVAL && !unsetFields.isEmpty() && !locals.contains(SimpleVerificationTypeInfo.UNINITIALIZED_THIS)) { + // clear unsets post larval + unsetFields = List.of(); + } + entries[ei] = new StackMapFrameImpl(actualFrameType, + ctx.getLabel(bci), + locals, + stack, + unsetFields); } return List.of(entries); } @@ -299,12 +385,31 @@ private int u2() { public static record StackMapFrameImpl(int frameType, Label target, List locals, - List stack) + List stack, + List unsetFields) implements StackMapFrameInfo { public StackMapFrameImpl { requireNonNull(target); locals = Util.sanitizeU2List(locals); stack = Util.sanitizeU2List(stack); + unsetFields = Util.sanitizeU2List(unsetFields); + + uninitializedThisCheck: + if (!unsetFields.isEmpty()) { + for (var local : locals) { + if (local == SimpleVerificationTypeInfo.UNINITIALIZED_THIS) { + break uninitializedThisCheck; + } + } + throw new IllegalArgumentException("unset fields requires uninitializedThis in locals"); + } + } + + public StackMapFrameImpl(int frameType, + Label target, + List locals, + List stack) { + this(frameType, target, locals, stack, List.of()); } } } 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 eb17e99a94d..c6f033aa711 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 @@ -30,12 +30,7 @@ import java.lang.classfile.Attributes; 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.ConstantPoolBuilder; -import java.lang.classfile.constantpool.InvokeDynamicEntry; -import java.lang.classfile.constantpool.MemberRefEntry; -import java.lang.classfile.constantpool.Utf8Entry; +import java.lang.classfile.constantpool.*; import java.lang.constant.ClassDesc; import java.lang.constant.MethodTypeDesc; import java.util.ArrayList; @@ -44,6 +39,7 @@ import java.util.Objects; import java.util.stream.Collectors; +import jdk.internal.classfile.impl.WritableField.UnsetField; import jdk.internal.constant.ClassOrInterfaceDescImpl; import jdk.internal.util.Preconditions; @@ -157,6 +153,7 @@ static StackMapGenerator of(DirectCodeBuilder dcb, BufWriterImpl buf) { dcb.bytecodesBufWriter.bytecodeView(), dcb.constantPool, dcb.context, + buf.getStrictInstanceFields(), dcb.handlers); } @@ -198,6 +195,7 @@ static record RawExceptionCatch(int start, int end, int handler, Type catchType) private final List handlers; private final List rawHandlers; private final ClassHierarchyImpl classHierarchy; + private final UnsetField[] strictFieldsToPut; // exact-sized, do not modify this copy! private final boolean patchDeadCode; private final boolean filterDeadLabels; private Frame[] frames = EMPTY_FRAME_ARRAY; @@ -229,6 +227,7 @@ public StackMapGenerator(LabelContext labelContext, RawBytecodeHelper.CodeRange bytecode, SplitConstantPool cp, ClassFileImpl context, + UnsetField[] strictFields, List handlers) { this.thisType = Type.referenceType(thisClass); this.methodName = methodName; @@ -243,6 +242,11 @@ public StackMapGenerator(LabelContext labelContext, this.patchDeadCode = context.patchDeadCode(); this.filterDeadLabels = context.dropDeadLabels(); this.currentFrame = new Frame(classHierarchy); + if (OBJECT_INITIALIZER_NAME.equals(methodName)) { + this.strictFieldsToPut = strictFields; + } else { + this.strictFieldsToPut = UnsetField.EMPTY_ARRAY; + } generate(); } @@ -391,9 +395,13 @@ public Attribute stackMapTableAttribute() { return framesCount == 0 ? null : new UnboundAttribute.AdHocAttribute<>(Attributes.stackMapTable()) { @Override public void writeBody(BufWriterImpl b) { + int countPos = b.size(); + if (framesCount != (char) framesCount) { + throw generatorError("Too many frames: " + framesCount); + } b.writeU2(framesCount); Frame prevFrame = new Frame(classHierarchy); - prevFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType); + prevFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType, strictFieldsToPut); prevFrame.trimAndCompress(); for (int i = 0; i < framesCount; i++) { var fr = frames[i]; @@ -417,9 +425,8 @@ private static Type cpIndexToType(int index, ConstantPoolBuilder cp) { private void processMethod() { var frames = this.frames; var currentFrame = this.currentFrame; - currentFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType); + currentFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType, strictFieldsToPut); currentFrame.stackSize = 0; - currentFrame.flags = 0; currentFrame.offset = -1; int stackmapIndex = 0; var bcs = bytecode.start(); @@ -762,7 +769,8 @@ private void processSwitch(RawBytecodeHelper bcs) { } private void processFieldInstructions(RawBytecodeHelper bcs) { - var desc = Util.fieldTypeSymbol(cp.entryByIndex(bcs.getIndexU2(), MemberRefEntry.class).type()); + var nameAndType = cp.entryByIndex(bcs.getIndexU2(), MemberRefEntry.class).nameAndType(); + var desc = Util.fieldTypeSymbol(nameAndType.type()); var currentFrame = this.currentFrame; switch (bcs.opcode()) { case GETSTATIC -> @@ -775,6 +783,9 @@ private void processFieldInstructions(RawBytecodeHelper bcs) { currentFrame.pushStack(desc); } case PUTFIELD -> { + if (strictFieldsToPut.length > 0) { + currentFrame.putStrictField(nameAndType); + } currentFrame.decStack(Util.isDoubleSlot(desc) ? 3 : 2); } default -> throw new AssertionError("Should not reach here"); @@ -798,7 +809,14 @@ private boolean processInvokeInstructions(RawBytecodeHelper bcs, boolean inTryBl if (inTryBlock) { processExceptionHandlerTargets(bci, true); } + var owner = cp.entryByIndex(index, MemberRefEntry.class).owner(); + if (!owner.name().equalsString(((ClassOrInterfaceDescImpl) thisType.sym).internalName()) + && currentFrame.unsetFieldsSize != 0) { + throw generatorError("Unset fields mismatch"); + } currentFrame.initializeObject(type, thisType); + currentFrame.unsetFieldsSize = 0; + currentFrame.unsetFields = UnsetField.EMPTY_ARRAY; thisUninit = true; } else if (type.tag == ITEM_UNINITIALIZED) { Type new_class_type = cpIndexToType(bcs.getU2(type.bci + 1), cp); @@ -941,7 +959,7 @@ private void addFrame(int offset) { private final class Frame { int offset; - int localsSize, stackSize; + int localsSize, stackSize, unsetFieldsSize; int flags; int frameMaxStack = 0, frameMaxLocals = 0; boolean dirty = false; @@ -950,6 +968,7 @@ private final class Frame { private final ClassHierarchyImpl classHierarchy; private Type[] locals, stack; + private UnsetField[] unsetFields = UnsetField.EMPTY_ARRAY; // sorted, modifiable oversized array Frame(ClassHierarchyImpl classHierarchy) { this(-1, 0, 0, 0, null, null, classHierarchy); @@ -971,7 +990,10 @@ private final class Frame { @Override public String toString() { - return (dirty ? "frame* @" : "frame @") + offset + " with locals " + (locals == null ? "[]" : Arrays.asList(locals).subList(0, localsSize)) + " and stack " + (stack == null ? "[]" : Arrays.asList(stack).subList(0, stackSize)); + return (dirty ? "frame* @" : "frame @") + offset + + " with locals " + (locals == null ? "[]" : Arrays.asList(locals).subList(0, localsSize)) + + " and stack " + (stack == null ? "[]" : Arrays.asList(stack).subList(0, stackSize)) + + " and unset fields " + (unsetFields == null ? "[]" : Arrays.asList(unsetFields).subList(0, unsetFieldsSize)); } Frame pushStack(ClassDesc desc) { @@ -1026,7 +1048,8 @@ void initializeObject(Type old_object, Type new_object) { } } if (old_object == Type.UNITIALIZED_THIS_TYPE) { - flags = 0; + flags &= ~FLAG_THIS_UNINIT; + assert flags == 0 : flags; } } @@ -1043,6 +1066,24 @@ Frame checkLocal(int index) { return this; } + void putStrictField(NameAndTypeEntry nat) { + int shift = 0; + var array = unsetFields; + for (int i = 0; i < unsetFieldsSize; i++) { + var f = array[i]; + if (f.name().equals(nat.name()) && f.type().equals(nat.type())) { + shift++; + } else if (shift != 0) { + array[i - shift] = array[i]; + array[i] = null; + } + } + if (shift > 1) { + throw generatorError(nat + "; discovered " + shift); + } + unsetFieldsSize -= shift; + } + private void checkStack(int index) { if (index >= frameMaxStack) frameMaxStack = index + 1; if (stack == null) { @@ -1061,7 +1102,7 @@ private void setLocalRawInternal(int index, Type type) { locals[index] = type; } - void setLocalsFromArg(String name, MethodTypeDesc methodDesc, boolean isStatic, Type thisKlass) { + void setLocalsFromArg(String name, MethodTypeDesc methodDesc, boolean isStatic, Type thisKlass, UnsetField[] strictFieldsToPut) { int localsSize = 0; // Pre-emptively create a locals array that encompass all parameter slots checkLocal(Util.parameterSlots(methodDesc) + (isStatic ? -1 : 0)); @@ -1069,10 +1110,16 @@ void setLocalsFromArg(String name, MethodTypeDesc methodDesc, boolean isStatic, Type[] locals = this.locals; if (!isStatic) { if (OBJECT_INITIALIZER_NAME.equals(name) && !CD_Object.equals(thisKlass.sym)) { + int strictFieldCount = strictFieldsToPut.length; + this.unsetFields = UnsetField.copyArray(strictFieldsToPut, strictFieldCount); + this.unsetFieldsSize = strictFieldCount; type = Type.UNITIALIZED_THIS_TYPE; - flags |= FLAG_THIS_UNINIT; + this.flags = FLAG_THIS_UNINIT; } else { + this.unsetFields = UnsetField.EMPTY_ARRAY; + this.unsetFieldsSize = 0; type = thisKlass; + this.flags = 0; } locals[localsSize++] = type; } @@ -1109,6 +1156,8 @@ void copyFrom(Frame src) { stackSize = src.stackSize; checkStack(src.stackSize - 1); if (src.stackSize > 0) System.arraycopy(src.stack, 0, stack, 0, src.stackSize); + unsetFieldsSize = src.unsetFieldsSize; + unsetFields = UnsetField.copyArray(src.unsetFields, src.unsetFieldsSize); flags = src.flags; localsChanged = true; } @@ -1116,6 +1165,7 @@ void copyFrom(Frame src) { void checkAssignableTo(Frame target) { int localsSize = this.localsSize; int stackSize = this.stackSize; + int myUnsetFieldsSize = this.unsetFieldsSize; if (target.flags == -1) { target.locals = locals == null ? null : locals.clone(); target.localsSize = localsSize; @@ -1123,6 +1173,8 @@ void checkAssignableTo(Frame target) { target.stack = stack.clone(); target.stackSize = stackSize; } + target.unsetFields = UnsetField.copyArray(this.unsetFields, myUnsetFieldsSize); + target.unsetFieldsSize = myUnsetFieldsSize; target.flags = flags; target.dirty = true; } else { @@ -1141,6 +1193,9 @@ void checkAssignableTo(Frame target) { throw generatorError("Stack content mismatch"); } } + if (myUnsetFieldsSize != 0) { + mergeUnsetFields(target); + } } } @@ -1197,6 +1252,51 @@ private Type merge(Type me, Type[] toTypes, int i, Frame target) { return newTo; } + // Merge this frame's unset fields into the target frame + private void mergeUnsetFields(Frame target) { + int myUnsetSize = unsetFieldsSize; + int targetUnsetSize = target.unsetFieldsSize; + var myUnsets = unsetFields; + var targetUnsets = target.unsetFields; + if (UnsetField.matches(myUnsets, myUnsetSize, targetUnsets, targetUnsetSize)) { + return; // no merge + } + // merge sort + var merged = new UnsetField[StackMapGenerator.this.strictFieldsToPut.length]; + int mergedSize = 0; + int i = 0; + int j = 0; + while (i < myUnsetSize && j < targetUnsetSize) { + var myCandidate = myUnsets[i]; + var targetCandidate = targetUnsets[j]; + var cmp = myCandidate.compareTo(targetCandidate); + if (cmp == 0) { + merged[mergedSize++] = myCandidate; + i++; + j++; + } else if (cmp < 0) { + merged[mergedSize++] = myCandidate; + i++; + } else { + merged[mergedSize++] = targetCandidate; + j++; + } + } + if (i < myUnsetSize) { + int len = myUnsetSize - i; + System.arraycopy(myUnsets, i, merged, mergedSize, len); + mergedSize += len; + } else if (j < targetUnsetSize) { + int len = targetUnsetSize - j; + System.arraycopy(targetUnsets, j, merged, mergedSize, len); + mergedSize += len; + } + + target.unsetFieldsSize = mergedSize; + target.unsetFields = merged; + target.dirty = true; + } + private static int trimAndCompress(Type[] types, int count) { while (count > 0 && types[count - 1] == Type.TOP_TYPE) count--; int compressed = 0; @@ -1216,12 +1316,42 @@ void trimAndCompress() { stackSize = trimAndCompress(stack, stackSize); } + boolean hasUninitializedThis() { + int size = this.localsSize; + var localVars = this.locals; + for (int i = 0; i < size; i++) { + if (localVars[i] == Type.UNITIALIZED_THIS_TYPE) + return true; + } + return false; + } + private static boolean equals(Type[] l1, Type[] l2, int commonSize) { if (l1 == null || l2 == null) return commonSize == 0; return Arrays.equals(l1, 0, commonSize, l2, 0, commonSize); } + // In sync with StackMapDecoder::needsLarvalFrameForTransition + private boolean needsLarvalFrame(Frame prevFrame) { + if (UnsetField.matches(unsetFields, unsetFieldsSize, prevFrame.unsetFields, prevFrame.unsetFieldsSize)) + return false; + if (!hasUninitializedThis()) { + assert unsetFieldsSize == 0 : this; // Should have been handled by processInvokeInstructions + return false; + } + return true; + } + void writeTo(BufWriterImpl out, Frame prevFrame, ConstantPoolBuilder cp) { + // enclosing frames + if (needsLarvalFrame(prevFrame)) { + out.writeU1U2(StackMapDecoder.EARLY_LARVAL, unsetFieldsSize); + for (int i = 0; i < unsetFieldsSize; i++) { + var f = unsetFields[i]; + out.writeIndex(cp.nameAndTypeEntry(f.name(), f.type())); + } + } + // base frame int localsSize = this.localsSize; int stackSize = this.stackSize; int offsetDelta = offset - prevFrame.offset - 1; diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/UnboundAttribute.java b/src/java.base/share/classes/jdk/internal/classfile/impl/UnboundAttribute.java index 94ba782d808..36ec513eaf2 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/UnboundAttribute.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/UnboundAttribute.java @@ -512,6 +512,30 @@ public Utf8Entry attributeName() { } } + public static final class UnboundLoadableDescriptorsAttribute + extends UnboundAttribute + implements LoadableDescriptorsAttribute { + + private static final Utf8Entry NAME = TemporaryConstantPool.INSTANCE.utf8Entry(Attributes.NAME_LOADABLE_DESCRIPTORS); + + private final List loadableDescriptors; + + public UnboundLoadableDescriptorsAttribute(List loadableDescriptors) { + super(Attributes.loadableDescriptors()); + this.loadableDescriptors = List.copyOf(loadableDescriptors); + } + + @Override + public List loadableDescriptors() { + return loadableDescriptors; + } + + @Override + public Utf8Entry attributeName() { + return NAME; + } + } + public static final class UnboundNestMembersAttribute extends UnboundAttribute implements NestMembersAttribute { diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/Util.java b/src/java.base/share/classes/jdk/internal/classfile/impl/Util.java index 7e6384dd1a4..d74f93c503e 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/Util.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/Util.java @@ -28,6 +28,8 @@ import java.lang.classfile.attribute.CodeAttribute; import jdk.internal.classfile.components.ClassPrinter; import java.lang.classfile.constantpool.ClassEntry; +import java.lang.classfile.constantpool.ConstantPool; +import java.lang.classfile.constantpool.ConstantPoolBuilder; import java.lang.classfile.constantpool.ModuleEntry; import java.lang.classfile.constantpool.PoolEntry; import java.lang.classfile.constantpool.Utf8Entry; @@ -35,6 +37,7 @@ import java.lang.constant.MethodTypeDesc; import java.lang.constant.ModuleDesc; import java.lang.reflect.AccessFlag; +import java.lang.reflect.ClassFileFormatVersion; import java.util.AbstractList; import java.util.Collection; import java.util.List; @@ -47,6 +50,7 @@ import jdk.internal.vm.annotation.Stable; import static java.lang.classfile.ClassFile.ACC_STATIC; +import static java.lang.constant.ConstantDescs.INIT_NAME; import static jdk.internal.constant.PrimitiveClassDescImpl.CD_double; import static jdk.internal.constant.PrimitiveClassDescImpl.CD_long; import static jdk.internal.constant.PrimitiveClassDescImpl.CD_void; @@ -58,6 +62,8 @@ */ public final class Util { + public static final int VALUE_OBJECTS_MAJOR = ClassFile.latestMajorVersion(); + private Util() { } @@ -252,7 +258,7 @@ public static int flagsToBits(AccessFlag.Location location, Collection dump, byte[] bytes, int length) } } + public static boolean canSkipMethodInflation(ClassReader cr, MethodInfo method, BufWriterImpl buf) { + if (!buf.canWriteDirect(cr)) { + return false; + } + if (method.methodName().equalsString(INIT_NAME) && + !buf.strictFieldsMatch(((ClassReaderImpl) cr).getContainedClass())) { + return false; + } + return true; + } + public static void writeListIndices(BufWriter writer, List list) { writer.writeU2(list.size()); for (PoolEntry info : list) { diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/WritableField.java b/src/java.base/share/classes/jdk/internal/classfile/impl/WritableField.java new file mode 100644 index 00000000000..1fbfe08f043 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/WritableField.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.classfile.constantpool.ConstantPoolBuilder; +import java.lang.classfile.constantpool.Utf8Entry; +import java.util.Arrays; + +import static java.lang.classfile.ClassFile.*; + +/** + * An interface to obtain field properties for direct class builders. + * Required to filter strict instance fields for stack map generation. + * Public for benchmark access. + */ +public sealed interface WritableField extends Util.Writable + permits FieldImpl, DirectFieldBuilder { + Utf8Entry fieldName(); + Utf8Entry fieldType(); + int fieldFlags(); + + static WritableField.UnsetField[] filterStrictInstanceFields(ConstantPoolBuilder cpb, WritableField[] array, int count) { + // assume there's no toctou for trusted incoming array + int size = 0; + for (int i = 0; i < count; i++) { + var field = array[i]; + if ((field.fieldFlags() & (ACC_STATIC | ACC_STRICT_INIT)) == ACC_STRICT_INIT) { + size++; + } + } + if (size == 0) + return UnsetField.EMPTY_ARRAY; + UnsetField[] ret = new UnsetField[size]; + int j = 0; + for (int i = 0; i < count; i++) { + var field = array[i]; + if ((field.fieldFlags() & (ACC_STATIC | ACC_STRICT_INIT)) == ACC_STRICT_INIT) { + ret[j++] = new UnsetField(AbstractPoolEntry.maybeClone(cpb, field.fieldName()), + AbstractPoolEntry.maybeClone(cpb, field.fieldType())); + } + } + assert j == size : "toctou: " + j + " != " + size; + Arrays.sort(ret); + return ret; + } + + // The captured information of unset fields, pool entries localized to class writing context + // avoid creating NAT until we need to write the fields to stack maps + record UnsetField(Utf8Entry name, Utf8Entry type) implements Comparable { + public UnsetField { + assert Util.checkConstantPoolsCompatible(name.constantPool(), type.constantPool()); + } + public static final UnsetField[] EMPTY_ARRAY = new UnsetField[0]; + + public static UnsetField[] copyArray(UnsetField[] incoming, int resultLen) { + assert resultLen <= incoming.length : resultLen + " > " + incoming.length; + return resultLen == 0 ? EMPTY_ARRAY : Arrays.copyOf(incoming, resultLen, UnsetField[].class); + } + + public static boolean matches(UnsetField[] one, int sizeOne, UnsetField[] two, int sizeTwo) { + if (sizeOne != sizeTwo) + return false; + for (int i = 0; i < sizeOne; i++) { + if (!one[i].equals(two[i])) { + return false; + } + } + return true; + } + + // Warning: inconsistent with equals (which uses UTF8 object equality) + @Override + public int compareTo(UnsetField o) { + assert Util.checkConstantPoolsCompatible(name.constantPool(), o.name.constantPool()); + var ret = Integer.compare(name.index(), o.name.index()); + if (ret != 0) + return ret; + return Integer.compare(type.index(), o.type.index()); + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/ParserVerifier.java b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/ParserVerifier.java index 5c93d6da4c1..3b83b6d9625 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/ParserVerifier.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/ParserVerifier.java @@ -41,8 +41,7 @@ import jdk.internal.classfile.impl.BoundAttribute; import jdk.internal.classfile.impl.Util; -import static java.lang.constant.ConstantDescs.CLASS_INIT_NAME; -import static java.lang.constant.ConstantDescs.INIT_NAME; +import static java.lang.constant.ConstantDescs.*; /// ParserVerifier performs selected checks of the class file format according to /// {@jvms 4.8 Format Checking}. @@ -276,6 +275,14 @@ private void verifyAttribute(AttributedElement ae, Attribute a, List 2 + 4 * lta.lineNumbers().size(); + case LoadableDescriptorsAttribute lda -> { + for (var desc : lda.loadableDescriptorSymbols()) { + if (desc.equals(CD_void)) { + errors.add(new VerifyError("illegal signature %s".formatted(desc))); + } + } + yield 2 + 2 * lda.loadableDescriptors().size(); + } case LocalVariableTableAttribute lvta -> 2 + 10 * lvta.localVariables().size(); case LocalVariableTypeTableAttribute lvta -> @@ -346,8 +353,8 @@ private void verifyAttribute(AttributedElement ae, Attribute a, List - 2 + subSize(smta.entries(), frame -> stackMapFrameSize(frame)); + case StackMapTableAttribute _ -> + -1; // Not sufficient info for assert unset size case SyntheticAttribute _ -> 0; case UnknownAttribute _ -> diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationFrame.java b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationFrame.java index 07a3de21cc1..d096b78a67d 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationFrame.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationFrame.java @@ -24,7 +24,12 @@ */ package jdk.internal.classfile.impl.verifier; +import java.lang.classfile.constantpool.NameAndTypeEntry; +import java.lang.classfile.constantpool.Utf8Entry; import java.util.Arrays; +import java.util.Set; + +import jdk.internal.classfile.impl.TemporaryConstantPool; /// From `stackMapFrame.cpp`. class VerificationFrame { @@ -37,9 +42,11 @@ class VerificationFrame { private final int _max_locals, _max_stack; private int _flags; private final VerificationType[] _locals, _stack; + private Set _assert_unset_fields; private final VerifierImpl _verifier; - public VerificationFrame(int offset, int flags, int locals_size, int stack_size, int max_locals, int max_stack, VerificationType[] locals, VerificationType[] stack, VerifierImpl v) { + public VerificationFrame(int offset, int flags, int locals_size, int stack_size, int max_locals, int max_stack, + VerificationType[] locals, VerificationType[] stack, Set assert_unset_fields, VerifierImpl v) { this._offset = offset; this._locals_size = locals_size; this._stack_size = stack_size; @@ -49,6 +56,7 @@ public VerificationFrame(int offset, int flags, int locals_size, int stack_size, this._flags = flags; this._locals = locals; this._stack = stack; + this._assert_unset_fields = assert_unset_fields; this._verifier = v; } @@ -109,6 +117,50 @@ boolean flag_this_uninit() { return (_flags & FLAG_THIS_UNINIT) == FLAG_THIS_UNINIT; } + Set assert_unset_fields() { + return _assert_unset_fields; + } + + void set_assert_unset_fields(Set table) { + _assert_unset_fields = table; + } + + // Called when verifying putfields to mark strict instance fields as satisfied + boolean satisfy_unset_field(Utf8Entry name, Utf8Entry signature) { + var nat = TemporaryConstantPool.INSTANCE.nameAndTypeEntry(name, signature); + return _assert_unset_fields.remove(nat); + } + + // Verify that all strict fields have been initialized + // Strict fields must be initialized before the super constructor is called + boolean verify_unset_fields_satisfied() { + return _assert_unset_fields.isEmpty(); + } + + // Merge incoming unset strict fields from StackMapTable with + // initial strict instance fields + Set merge_unset_fields(Set new_fields) { + // ClassFile API: We track all strict fields in another structure, noop here + return new_fields; + } + + boolean verify_unset_fields_compatibility(Set target_table) { + for (var e : _assert_unset_fields) { + if (!target_table.contains(e)) + return false; + } + return true; + } + + void unsatisfied_strict_fields_error(VerificationWrapper klass, int bci) { + _verifier.verifyError("All strict final fields must be initialized before super(): %d field(s), %s" + .formatted(_assert_unset_fields.size(), _assert_unset_fields)); + } + + void print_strict_fields(Set table) { + // ignore, we don't do stdout/err + } + void reset() { for (int i = 0; i < _max_locals; i++) { _locals[i] = VerificationType.bogus_type; @@ -181,7 +233,7 @@ void pop_stack_2(VerificationType type1, VerificationType type2) { pop_stack_ex(type2); } - VerificationFrame(int max_locals, int max_stack, VerifierImpl verifier) { + VerificationFrame(int max_locals, int max_stack, Set initial_strict_fields, VerifierImpl verifier) { _offset = 0; _locals_size = 0; _stack_size = 0; @@ -198,12 +250,13 @@ void pop_stack_2(VerificationType type1, VerificationType type2) { for (int i = 0; i < max_stack; i++) { _stack[i] = VerificationType.bogus_type; } + _assert_unset_fields = initial_strict_fields; } VerificationFrame frame_in_exception_handler(int flags) { return new VerificationFrame(_offset, flags, _locals_size, 0, _max_locals, _max_stack, _locals, new VerificationType[1], - _verifier); + _assert_unset_fields, _verifier); } void initialize_object(VerificationType old_object, VerificationType new_object) { @@ -299,6 +352,16 @@ boolean is_assignable_to(VerificationFrame target) { _verifier.verifyError("Bad type", this, target); } + // Check that assert unset fields are compatible + boolean compatible = verify_unset_fields_compatibility(target.assert_unset_fields()); + if (!compatible) { + print_strict_fields(assert_unset_fields()); + print_strict_fields(target.assert_unset_fields()); + _verifier.verifyError("Strict fields mismatch from %s to %s".formatted( + assert_unset_fields(), target.assert_unset_fields()), this, target); + return false; + } + if ((_flags | target.flags()) == target.flags()) { return true; } else { diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationTable.java b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationTable.java index 85342e7106f..9ecce410eff 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationTable.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationTable.java @@ -24,8 +24,11 @@ */ package jdk.internal.classfile.impl.verifier; +import java.lang.classfile.constantpool.NameAndTypeEntry; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import static jdk.internal.classfile.impl.verifier.VerificationType.ITEM_Object; import static jdk.internal.classfile.impl.verifier.VerificationType.ITEM_Uninitialized; @@ -129,6 +132,7 @@ boolean match_stackmap(VerificationFrame frame, int target, int frame_index, boo frame.set_stack_size(ssize); frame.copy_stack(stackmap_frame); frame.set_flags(stackmap_frame.flags()); + frame.set_assert_unset_fields(stackmap_frame.assert_unset_fields()); } return result; } @@ -150,6 +154,8 @@ static class StackMapReader { private int _parsed_frame_count; private VerificationFrame _prev_frame; char _max_locals, _max_stack; + final Set strictFields; + Set _assert_unset_fields_buffer; boolean _first; void check_verification_type_array_size(int size, int max_size) { @@ -159,9 +165,11 @@ void check_verification_type_array_size(int size, int max_size) { } private static final int + EARLY_LARVAL = 246, SAME_LOCALS_1_STACK_ITEM_EXTENDED = 247, SAME_EXTENDED = 251, FULL = 255; + private static final int RESERVED_TAGS_UPPER_LIMIT = EARLY_LARVAL; // not inclusive public int get_frame_count() { return _frame_count; @@ -204,6 +212,7 @@ public void check_end() { public StackMapReader(byte[] stackmapData, byte[] code_data, int code_len, VerificationFrame init_frame, char max_locals, char max_stack, + Set initial_strict_fields, VerificationWrapper.ConstantPoolWrapper cp, VerifierImpl context) { this._verifier = context; _stream = new StackMapStream(stackmapData, _verifier); @@ -213,6 +222,8 @@ public StackMapReader(byte[] stackmapData, byte[] code_data, int code_len, _prev_frame = init_frame; _max_locals = max_locals; _max_stack = max_stack; + strictFields = Set.copyOf(initial_strict_fields); + _assert_unset_fields_buffer = initial_strict_fields; _first = true; if (stackmapData != null) { _cp = cp; @@ -285,6 +296,41 @@ VerificationFrame next_helper() { int offset; VerificationType[] locals = null; int frame_type = _stream.get_u1(); + if (frame_type == EARLY_LARVAL) { + int num_unset_fields = _stream.get_u2(); + Set new_fields = new HashSet<>(); + for (int i = 0; i < num_unset_fields; i++) { + int index = _stream.get_u2(); + if (!_cp.is_within_bounds(index) || _cp.tagAt(index) != VerifierImpl.JVM_CONSTANT_NameAndType) { + _prev_frame.verifier().verifyError("Invalid use of strict instance fields %d %s %s".formatted(_prev_frame.offset(), _prev_frame, + "Invalid constant pool index in early larval frame: %d".formatted(index))); + } + var tmp = _cp.cp.entryByIndex(index, NameAndTypeEntry.class); + if (!strictFields.contains(tmp)) { + _prev_frame.verifier().verifyError("Invalid use of strict instance fields %d %s %s".formatted(_prev_frame.offset(), _prev_frame, + "Strict fields not a subset of initial strict instance fields: %s".formatted(tmp))); + } else { + new_fields.add(tmp); + } + } + // Only modify strict instance fields the frame has uninitialized this + if (_prev_frame.flag_this_uninit()) { + _assert_unset_fields_buffer = _prev_frame.merge_unset_fields(new_fields); + } else if (!new_fields.isEmpty()) { + _prev_frame.verifier().verifyError("Invalid use of strict instance fields %d %s %s".formatted(_prev_frame.offset(), _prev_frame, + "Cannot have uninitialized strict fields after class initialization")); + } + // Continue reading frame data + if (at_end()) { + _prev_frame.verifier().verifyError("Invalid use of strict instance fields %d %s %s".formatted(_prev_frame.offset(), _prev_frame, + "Early larval frame must be followed by a base frame")); + } + frame_type = _stream.get_u1(); + if (frame_type == EARLY_LARVAL) { + _prev_frame.verifier().verifyError("Invalid use of strict instance fields %d %s %s".formatted(_prev_frame.offset(), _prev_frame, + "Early larval frame must be followed by a base frame")); + } + } if (frame_type < 64) { if (_first) { offset = frame_type; @@ -295,7 +341,7 @@ VerificationFrame next_helper() { offset = _prev_frame.offset() + frame_type + 1; locals = _prev_frame.locals(); } - frame = new VerificationFrame(offset, _prev_frame.flags(), _prev_frame.locals_size(), 0, _max_locals, _max_stack, locals, null, _verifier); + frame = new VerificationFrame(offset, _prev_frame.flags(), _prev_frame.locals_size(), 0, _max_locals, _max_stack, locals, null, _assert_unset_fields_buffer, _verifier); if (_first && locals != null) { frame.copy_locals(_prev_frame); } @@ -320,7 +366,7 @@ VerificationFrame next_helper() { stack_size = 2; } check_verification_type_array_size(stack_size, _max_stack); - frame = new VerificationFrame(offset, _prev_frame.flags(), _prev_frame.locals_size(), stack_size, _max_locals, _max_stack, locals, stack, _verifier); + frame = new VerificationFrame(offset, _prev_frame.flags(), _prev_frame.locals_size(), stack_size, _max_locals, _max_stack, locals, stack, _assert_unset_fields_buffer, _verifier); if (_first && locals != null) { frame.copy_locals(_prev_frame); } @@ -328,7 +374,7 @@ VerificationFrame next_helper() { return frame; } int offset_delta = _stream.get_u2(); - if (frame_type < SAME_LOCALS_1_STACK_ITEM_EXTENDED) { + if (frame_type < RESERVED_TAGS_UPPER_LIMIT) { _verifier.classError("reserved frame type"); } if (frame_type == SAME_LOCALS_1_STACK_ITEM_EXTENDED) { @@ -349,7 +395,7 @@ VerificationFrame next_helper() { stack_size = 2; } check_verification_type_array_size(stack_size, _max_stack); - frame = new VerificationFrame(offset, _prev_frame.flags(), _prev_frame.locals_size(), stack_size, _max_locals, _max_stack, locals, stack, _verifier); + frame = new VerificationFrame(offset, _prev_frame.flags(), _prev_frame.locals_size(), stack_size, _max_locals, _max_stack, locals, stack, _assert_unset_fields_buffer, _verifier); if (_first && locals != null) { frame.copy_locals(_prev_frame); } @@ -383,7 +429,7 @@ VerificationFrame next_helper() { } else { offset = _prev_frame.offset() + offset_delta + 1; } - frame = new VerificationFrame(offset, flags, new_length, 0, _max_locals, _max_stack, locals, null, _verifier); + frame = new VerificationFrame(offset, flags, new_length, 0, _max_locals, _max_stack, locals, null, _assert_unset_fields_buffer, _verifier); if (_first && locals != null) { frame.copy_locals(_prev_frame); } @@ -414,7 +460,7 @@ VerificationFrame next_helper() { } else { offset = _prev_frame.offset() + offset_delta + 1; } - frame = new VerificationFrame(offset, flags[0], real_length, 0, _max_locals, _max_stack, locals, null, _verifier); + frame = new VerificationFrame(offset, flags[0], real_length, 0, _max_locals, _max_stack, locals, null, _assert_unset_fields_buffer, _verifier); _first = false; return frame; } @@ -456,7 +502,7 @@ VerificationFrame next_helper() { } else { offset = _prev_frame.offset() + offset_delta + 1; } - frame = new VerificationFrame(offset, flags[0], real_locals_size, real_stack_size, _max_locals, _max_stack, locals, stack, _verifier); + frame = new VerificationFrame(offset, flags[0], real_locals_size, real_stack_size, _max_locals, _max_stack, locals, stack, _assert_unset_fields_buffer, _verifier); _first = false; return frame; } diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationWrapper.java b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationWrapper.java index c76d0789244..70dc4cd7a1f 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationWrapper.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerificationWrapper.java @@ -27,6 +27,7 @@ import java.lang.classfile.Attributes; import java.lang.classfile.ClassModel; +import java.lang.classfile.FieldModel; import java.lang.classfile.MethodModel; import java.lang.classfile.attribute.LocalVariableInfo; import java.lang.classfile.constantpool.ClassEntry; @@ -45,7 +46,7 @@ import jdk.internal.classfile.impl.Util; public final class VerificationWrapper { - private final ClassModel clm; + final ClassModel clm; private final ConstantPoolWrapper cp; public VerificationWrapper(ClassModel clm) { @@ -73,11 +74,11 @@ Iterable methods() { return clm.methods().stream().map(m -> new MethodWrapper(m)).toList(); } - boolean findField(String name, String sig) { + FieldModel findField(String name, String sig) { for (var f : clm.fields()) if (f.fieldName().stringValue().equals(name) && f.fieldType().stringValue().equals(sig)) - return true; - return false; + return f; + return null; } class MethodWrapper { @@ -161,7 +162,7 @@ byte[] stackMapTableRawData() { static class ConstantPoolWrapper { - private final ConstantPool cp; + final ConstantPool cp; ConstantPoolWrapper(ConstantPool cp) { this.cp = cp; @@ -200,5 +201,9 @@ String refSignatureAt(int index) { int refClassIndexAt(int index) { return cp.entryByIndex(index, MemberRefEntry.class).owner().index(); } + + boolean is_within_bounds(int i) { + return i >= 1 && i <= cp.size(); + } } } diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerifierImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerifierImpl.java index bb862578b34..fb9b5b403c8 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerifierImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerifierImpl.java @@ -24,16 +24,27 @@ */ package jdk.internal.classfile.impl.verifier; +import java.lang.classfile.ClassFile; import java.lang.classfile.ClassHierarchyResolver; import java.lang.classfile.ClassModel; import jdk.internal.classfile.components.ClassPrinter; + +import java.lang.classfile.FieldModel; +import java.lang.classfile.constantpool.NameAndTypeEntry; +import java.lang.constant.ConstantDescs; +import java.lang.reflect.AccessFlag; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Objects; +import java.util.Set; import java.util.function.Consumer; import jdk.internal.classfile.impl.ClassHierarchyImpl; import jdk.internal.classfile.impl.RawBytecodeHelper; +import jdk.internal.classfile.impl.TemporaryConstantPool; +import jdk.internal.classfile.impl.Util; import jdk.internal.classfile.impl.verifier.VerificationSignature.BasicType; import jdk.internal.classfile.impl.verifier.VerificationWrapper.ConstantPoolWrapper; @@ -105,6 +116,7 @@ void log_info(String messageFormat, Object... args) { static final int STACKMAP_ATTRIBUTE_MAJOR_VERSION = 50; static final int INVOKEDYNAMIC_MAJOR_VERSION = 51; static final int NOFAILOVER_MAJOR_VERSION = 51; + static final int VALUE_TYPES_MAJOR_VERSION = Util.VALUE_OBJECTS_MAJOR; static final int MAX_CODE_SIZE = 65535; public static List verify(ClassModel classModel, Consumer logger) { @@ -242,6 +254,12 @@ private VerificationType object_type() { return VerificationType.reference_type(java_lang_Object); } + static boolean supports_strict_fields(VerificationWrapper klass) { + int ver = klass.majorVersion(); + return ver > VALUE_TYPES_MAJOR_VERSION || + (ver == VALUE_TYPES_MAJOR_VERSION && klass.clm.minorVersion() == ClassFile.PREVIEW_MINOR_VERSION); + } + List verify_class() { log_info("Verifying class %s with new format", _klass.thisClassName()); var errors = new ArrayList(); @@ -303,7 +321,19 @@ void verify_method(VerificationWrapper.MethodWrapper m) { byte[] stackmap_data = m.stackMapTableRawData(); var cp = m.constantPool(); if (!VerificationSignature.isValidMethodSignature(m.descriptor())) verifyError("Invalid method signature"); - VerificationFrame current_frame = new VerificationFrame(max_locals, max_stack, this); + + // Collect the initial strict instance fields + Set strict_fields = new HashSet<>(); + if (m.name().equals(ConstantDescs.INIT_NAME)) { + for (var fs : current_class().clm.fields()) { + if (fs.flags().has(AccessFlag.STRICT_INIT) && !fs.flags().has(AccessFlag.STATIC)) { + var new_field = TemporaryConstantPool.INSTANCE.nameAndTypeEntry(fs.fieldName(), fs.fieldType()); + strict_fields.add(new_field); + } + } + } + + VerificationFrame current_frame = new VerificationFrame(max_locals, max_stack, strict_fields, this); VerificationType return_type = current_frame.set_locals_from_arg(m, current_type()); int stackmap_index = 0; int code_length = m.codeLength(); @@ -317,7 +347,7 @@ void verify_method(VerificationWrapper.MethodWrapper m) { verify_local_variable_table(code_length, code_data); var reader = new VerificationTable.StackMapReader(stackmap_data, code_data, code_length, current_frame, - (char) max_locals, (char) max_stack, cp, this); + (char) max_locals, (char) max_stack, strict_fields, cp, this); VerificationTable stackmap_table = new VerificationTable(reader, cp, this); var bcs = code.start(); @@ -1047,13 +1077,11 @@ void verify_method(VerificationWrapper.MethodWrapper m) { no_control_flow = false; break; case IF_ACMPEQ : case IF_ACMPNE : - current_frame.pop_stack( - VerificationType.reference_check); + current_frame.pop_stack(object_type()); // fall through case IFNULL : case IFNONNULL : - current_frame.pop_stack( - VerificationType.reference_check); + current_frame.pop_stack(object_type()); target = bcs.dest(); stackmap_table.check_jump_target (current_frame, target); @@ -1499,10 +1527,24 @@ void verify_field_instructions(RawBytecodeHelper bcs, VerificationFrame current_ current_frame.pop_stack(field_type[i]); } stack_object_type = current_frame.pop_stack(); - if (stack_object_type.is_uninitialized_this(this) && - target_class_type.equals(current_type()) && - _klass.findField(field_name, field_sig)) { - stack_object_type = current_type(); + FieldModel fd = _klass.findField(field_name, field_sig); + boolean is_local_field = fd != null && + target_class_type.equals(current_type()); + if (stack_object_type.is_uninitialized_this(this)) { + if (is_local_field) { + // Set the type to the current type so the is_assignable check passes. + stack_object_type = current_type(); + + if (fd.flags().has(AccessFlag.STRICT_INIT)) { + current_frame.satisfy_unset_field(fd.fieldName(), fd.fieldType()); + } + } + } else if (supports_strict_fields(_klass)) { + // `strict` fields are not writable, but only local fields produce verification errors + if (is_local_field && fd.flags().has(AccessFlag.STRICT_INIT) && fd.flags().has(AccessFlag.FINAL)) { + verifyError("Bad code %d %s".formatted(bci, + "Illegal use of putfield on a strict field: %s:%s".formatted(fd.fieldName(), fd.fieldType()))); + } } is_assignable = target_class_type.is_assignable_from(stack_object_type, this); if (!is_assignable) { @@ -1530,6 +1572,11 @@ boolean verify_invoke_init(RawBytecodeHelper bcs, int ref_class_index, Verificat if (!current_class().thisClassName().equals(ref_class_type.name()) && !superk_name.equals(ref_class_type.name())) { verifyError("Bad method call"); + } else if (ref_class_type.name().equals(superk_name)) { + // Strict final fields must be satisfied by this point + if (!current_frame.verify_unset_fields_satisfied()) { + current_frame.unsatisfied_strict_fields_error(current_class(), bci); + } } if (in_try_block) { for(var exhandler : _method.exceptionTable()) { diff --git a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java index e6c994a12b1..9de7a500a4a 100644 --- a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java +++ b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java @@ -64,9 +64,11 @@ * Values should be annotated with the feature's {@code JEP}. */ public enum Feature { + @JEP(number=401, title="Value Classes and Objects", status = "Preview") + VALUE_OBJECTS, + // while building the interim javac, the ClassReader will produce a warning when loading a class // keeping the constant of a feature that has been integrated or dropped, serves the purpose of muting such warnings. - //--- IMPLICIT_CLASSES, //to be removed when boot JDK is 25 SCOPED_VALUES, diff --git a/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java b/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java index 79e718c76e5..e062e1629ff 100644 --- a/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java +++ b/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java @@ -137,6 +137,45 @@ public Node findNode(String name) throws IOException { return reader.findNode(name); } + /** + * Returns a resource node in the given module, or null if no resource of + * that name exists. + * + *

    This is equivalent to: + *

    {@code
    +     * findNode("/modules/" + moduleName + "/" + resourcePath)
    +     * }
    + * but more performant, and returns {@code null} for directories. + * + * @param moduleName The module name of the requested resource. + * @param resourcePath Trailing module-relative resource path, not starting + * with {@code '/'}. + */ + public Node findResourceNode(String moduleName, String resourcePath) + throws IOException { + ensureOpen(); + return reader.findResourceNode(moduleName, resourcePath); + } + + /** + * Returns whether a resource exists in the given module. + * + *

    This is equivalent to: + *

    {@code
    +     * findResourceNode(moduleName, resourcePath) != null
    +     * }
    + * but more performant, and will not create or cache new nodes. + * + * @param moduleName The module name of the resource being tested for. + * @param resourcePath Trailing module-relative resource path, not starting + * with {@code '/'}. + */ + public boolean containsResource(String moduleName, String resourcePath) + throws IOException { + ensureOpen(); + return reader.containsResource(moduleName, resourcePath); + } + /** * Returns a copy of the content of a resource node. The buffer returned by * this method is not cached by the node, and each call returns a new array @@ -276,10 +315,7 @@ public void close(ImageReader image) throws IOException { * Returns a node with the given name, or null if no resource or directory of * that name exists. * - *

    This is the only public API by which anything outside this class can access - * {@code Node} instances either directly, or by resolving symbolic links. - * - *

    Note also that there is no reentrant calling back to this method from within + *

    Note that there is no reentrant calling back to this method from within * the node handling code. * * @param name an absolute, {@code /}-separated path string, prefixed with either @@ -291,6 +327,9 @@ synchronized Node findNode(String name) { // We cannot get the root paths ("/modules" or "/packages") here // because those nodes are already in the nodes cache. if (name.startsWith(MODULES_ROOT + "/")) { + // This may perform two lookups, one for a directory (in + // "/modules/...") and one for a non-prefixed resource + // (with "/modules" removed). node = buildModulesNode(name); } else if (name.startsWith(PACKAGES_ROOT + "/")) { node = buildPackagesNode(name); @@ -307,6 +346,55 @@ synchronized Node findNode(String name) { return node; } + /** + * Returns a resource node in the given module, or null if no resource of + * that name exists. + * + *

    Note that there is no reentrant calling back to this method from within + * the node handling code. + */ + Node findResourceNode(String moduleName, String resourcePath) { + // Unlike findNode(), this method makes only one lookup in the + // underlying jimage, but can only reliably return resource nodes. + if (moduleName.indexOf('/') >= 0) { + throw new IllegalArgumentException("invalid module name: " + moduleName); + } + String nodeName = MODULES_ROOT + "/" + moduleName + "/" + resourcePath; + // Synchronize as tightly as possible to reduce locking contention. + synchronized (this) { + Node node = nodes.get(nodeName); + if (node == null) { + ImageLocation loc = findLocation(moduleName, resourcePath); + if (loc != null && isResource(loc)) { + node = newResource(nodeName, loc); + nodes.put(node.getName(), node); + } + return node; + } else { + return node.isResource() ? node : null; + } + } + } + + /** + * Returns whether a resource exists in the given module. + * + *

    This method is expected to be called frequently for resources + * which do not exist in the given module (e.g. as part of classpath + * search). As such, it skips checking the nodes cache and only checks + * for an entry in the jimage file, as this is faster if the resource + * is not present. This also means it doesn't need synchronization. + */ + boolean containsResource(String moduleName, String resourcePath) { + if (moduleName.indexOf('/') >= 0) { + throw new IllegalArgumentException("invalid module name: " + moduleName); + } + // If the given module name is 'modules', then 'isResource()' + // returns false to prevent false positives. + ImageLocation loc = findLocation(moduleName, resourcePath); + return loc != null && isResource(loc); + } + /** * Builds a node in the "/modules/..." namespace. * diff --git a/src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java b/src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java index 87a00da4393..4fe6612a8ed 100644 --- a/src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java +++ b/src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,17 +27,15 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.DirectoryStream; -import java.nio.file.FileSystem; import java.nio.file.FileSystemException; -import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Stream; import jdk.internal.jimage.ImageReader.Node; @@ -56,16 +54,15 @@ class ExplodedImage extends SystemImage { private static final String MODULES = "/modules/"; private static final String PACKAGES = "/packages/"; - private static final int PACKAGES_LEN = PACKAGES.length(); - private final FileSystem defaultFS; + private final Path modulesDir; private final String separator; - private final Map nodes = Collections.synchronizedMap(new HashMap<>()); + private final Map nodes = new HashMap<>(); private final BasicFileAttributes modulesDirAttrs; ExplodedImage(Path modulesDir) throws IOException { - defaultFS = FileSystems.getDefault(); - String str = defaultFS.getSeparator(); + this.modulesDir = modulesDir; + String str = modulesDir.getFileSystem().getSeparator(); separator = str.equals("/") ? null : str; modulesDirAttrs = Files.readAttributes(modulesDir, BasicFileAttributes.class); initNodes(); @@ -79,21 +76,26 @@ private final class PathNode extends Node { private PathNode link; private List children; - PathNode(String name, Path path, BasicFileAttributes attrs) { // path + private PathNode(String name, Path path, BasicFileAttributes attrs) { // path super(name, attrs); this.path = path; } - PathNode(String name, Node link) { // link + private PathNode(String name, Node link) { // link super(name, link.getFileAttributes()); this.link = (PathNode)link; } - PathNode(String name, List children) { // dir + private PathNode(String name, List children) { // dir super(name, modulesDirAttrs); this.children = children; } + @Override + public boolean isResource() { + return link == null && !getFileAttributes().isDirectory(); + } + @Override public boolean isDirectory() { return children != null || @@ -112,7 +114,7 @@ public PathNode resolveLink(boolean recursive) { return recursive && link.isLink() ? link.resolveLink(true) : link; } - byte[] getContent() throws IOException { + private byte[] getContent() throws IOException { if (!getFileAttributes().isRegularFile()) throw new FileSystemException(getName() + " is not file"); return Files.readAllBytes(path); @@ -126,7 +128,7 @@ public Stream getChildNames() { List list = new ArrayList<>(); try (DirectoryStream stream = Files.newDirectoryStream(path)) { for (Path p : stream) { - p = explodedModulesDir.relativize(p); + p = modulesDir.relativize(p); String pName = MODULES + nativeSlashToFrontSlash(p.toString()); Node node = findNode(pName); if (node != null) { // findNode may choose to hide certain files! @@ -152,7 +154,7 @@ public long size() { } @Override - public void close() throws IOException { + public synchronized void close() throws IOException { nodes.clear(); } @@ -161,74 +163,78 @@ public byte[] getResource(Node node) throws IOException { return ((PathNode)node).getContent(); } - // find Node for the given Path @Override - public synchronized Node findNode(String str) { - Node node = findModulesNode(str); + public synchronized Node findNode(String name) { + PathNode node = nodes.get(name); if (node != null) { return node; } - // lazily created for paths like /packages///xyz - // For example /packages/java.lang/java.base/java/lang/ - if (str.startsWith(PACKAGES)) { - // pkgEndIdx marks end of part - int pkgEndIdx = str.indexOf('/', PACKAGES_LEN); - if (pkgEndIdx != -1) { - // modEndIdx marks end of part - int modEndIdx = str.indexOf('/', pkgEndIdx + 1); - if (modEndIdx != -1) { - // make sure we have such module link! - // ie., /packages// is valid - Node linkNode = nodes.get(str.substring(0, modEndIdx)); - if (linkNode == null || !linkNode.isLink()) { - return null; - } - // map to "/modules/zyz" path and return that node - // For example, "/modules/java.base/java/lang" for - // "/packages/java.lang/java.base/java/lang". - String mod = MODULES + str.substring(pkgEndIdx + 1); - return findModulesNode(mod); - } - } + // If null, this was not the name of "/modules/..." node, and since all + // "/packages/..." nodes were created and cached in advance, the name + // cannot reference a valid node. + Path path = underlyingModulesPath(name); + if (path == null) { + return null; } - return null; + // This can still return null for hidden files. + return createModulesNode(name, path); } - // find a Node for a path that starts like "/modules/..." - Node findModulesNode(String str) { - PathNode node = nodes.get(str); - if (node != null) { - return node; - } - // lazily created "/modules/xyz/abc/" Node - // This is mapped to default file system path "/xyz/abc" - Path p = underlyingPath(str); - if (p != null) { - try { - BasicFileAttributes attrs = Files.readAttributes(p, BasicFileAttributes.class); - if (attrs.isRegularFile()) { - Path f = p.getFileName(); - if (f.toString().startsWith("_the.")) - return null; + /** + * Lazily creates and caches a {@code Node} for the given "/modules/..." name + * and corresponding path to a file or directory. + * + * @param name a resource or directory node name, of the form "/modules/...". + * @param path the path of a file for a resource or directory. + * @return the newly created and cached node, or {@code null} if the given + * path references a file which must be hidden in the node hierarchy. + */ + private Node createModulesNode(String name, Path path) { + assert !nodes.containsKey(name) : "Node must not already exist: " + name; + assert isNonEmptyModulesPath(name) : "Invalid modules name: " + name; + + try { + // We only know if we're creating a resource of directory when we + // look up file attributes, and we only do that once. Thus, we can + // only reject "marker files" here, rather than by inspecting the + // given name string, since it doesn't apply to directories. + BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class); + if (attrs.isRegularFile()) { + Path f = path.getFileName(); + if (f.toString().startsWith("_the.")) { + return null; } - node = new PathNode(str, p, attrs); - nodes.put(str, node); - return node; - } catch (IOException x) { - // does not exists or unable to determine + } else if (!attrs.isDirectory()) { + return null; } + PathNode node = new PathNode(name, path, attrs); + nodes.put(name, node); + return node; + } catch (IOException x) { + // Since the path reference a file, any errors should not be ignored. + throw new UncheckedIOException(x); } - return null; } - Path underlyingPath(String str) { - if (str.startsWith(MODULES)) { - str = frontSlashToNativeSlash(str.substring("/modules".length())); - return defaultFS.getPath(explodedModulesDir.toString(), str); + /** + * Returns the expected file path for name in the "/modules/..." namespace, + * or {@code null} if the name is not in the "/modules/..." namespace or the + * path does not reference a file. + */ + private Path underlyingModulesPath(String name) { + if (isNonEmptyModulesPath(name)) { + Path path = modulesDir.resolve(frontSlashToNativeSlash(name.substring(MODULES.length()))); + return Files.exists(path) ? path : null; } return null; } + private static boolean isNonEmptyModulesPath(String name) { + // Don't just check the prefix, there must be something after it too + // (otherwise you end up with an empty string after trimming). + return name.startsWith(MODULES) && name.length() > MODULES.length(); + } + // convert "/" to platform path separator private String frontSlashToNativeSlash(String str) { return separator == null ? str : str.replace("/", separator); @@ -249,24 +255,21 @@ private void initNodes() throws IOException { // same package prefix may exist in multiple modules. This Map // is filled by walking "jdk modules" directory recursively! Map> packageToModules = new HashMap<>(); - try (DirectoryStream stream = Files.newDirectoryStream(explodedModulesDir)) { + try (DirectoryStream stream = Files.newDirectoryStream(modulesDir)) { for (Path module : stream) { if (Files.isDirectory(module)) { String moduleName = module.getFileName().toString(); // make sure "/modules/" is created - findModulesNode(MODULES + moduleName); + Objects.requireNonNull(createModulesNode(MODULES + moduleName, module)); try (Stream contentsStream = Files.walk(module)) { contentsStream.filter(Files::isDirectory).forEach((p) -> { p = module.relativize(p); String pkgName = slashesToDots(p.toString()); // skip META-INF and empty strings if (!pkgName.isEmpty() && !pkgName.startsWith("META-INF")) { - List moduleNames = packageToModules.get(pkgName); - if (moduleNames == null) { - moduleNames = new ArrayList<>(); - packageToModules.put(pkgName, moduleNames); - } - moduleNames.add(moduleName); + packageToModules + .computeIfAbsent(pkgName, k -> new ArrayList<>()) + .add(moduleName); } }); } @@ -275,8 +278,8 @@ private void initNodes() throws IOException { } // create "/modules" directory // "nodes" map contains only /modules/ nodes only so far and so add all as children of /modules - PathNode modulesDir = new PathNode("/modules", new ArrayList<>(nodes.values())); - nodes.put(modulesDir.getName(), modulesDir); + PathNode modulesRootNode = new PathNode("/modules", new ArrayList<>(nodes.values())); + nodes.put(modulesRootNode.getName(), modulesRootNode); // create children under "/packages" List packagesChildren = new ArrayList<>(packageToModules.size()); @@ -285,7 +288,7 @@ private void initNodes() throws IOException { List moduleNameList = entry.getValue(); List moduleLinkNodes = new ArrayList<>(moduleNameList.size()); for (String moduleName : moduleNameList) { - Node moduleNode = findModulesNode(MODULES + moduleName); + Node moduleNode = Objects.requireNonNull(nodes.get(MODULES + moduleName)); PathNode linkNode = new PathNode(PACKAGES + pkgName + "/" + moduleName, moduleNode); nodes.put(linkNode.getName(), linkNode); moduleLinkNodes.add(linkNode); @@ -295,13 +298,13 @@ private void initNodes() throws IOException { packagesChildren.add(pkgDir); } // "/packages" dir - PathNode packagesDir = new PathNode("/packages", packagesChildren); - nodes.put(packagesDir.getName(), packagesDir); + PathNode packagesRootNode = new PathNode("/packages", packagesChildren); + nodes.put(packagesRootNode.getName(), packagesRootNode); // finally "/" dir! List rootChildren = new ArrayList<>(); - rootChildren.add(packagesDir); - rootChildren.add(modulesDir); + rootChildren.add(packagesRootNode); + rootChildren.add(modulesRootNode); PathNode root = new PathNode("/", rootChildren); nodes.put(root.getName(), root); } diff --git a/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java b/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java index 6530bd1f90a..c405801506f 100644 --- a/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java +++ b/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/src/java.base/share/classes/jdk/internal/jrtfs/SystemImage.java b/src/java.base/share/classes/jdk/internal/jrtfs/SystemImage.java index 6813c7e627f..b38e953a5f9 100644 --- a/src/java.base/share/classes/jdk/internal/jrtfs/SystemImage.java +++ b/src/java.base/share/classes/jdk/internal/jrtfs/SystemImage.java @@ -78,13 +78,13 @@ void close() throws IOException { return new ExplodedImage(explodedModulesDir); } - static final String RUNTIME_HOME; + private static final String RUNTIME_HOME; // "modules" jimage file Path - static final Path moduleImageFile; + private static final Path moduleImageFile; // "modules" jimage exists or not? - static final boolean modulesImageExists; + private static final boolean modulesImageExists; // /modules directory Path - static final Path explodedModulesDir; + private static final Path explodedModulesDir; static { PrivilegedAction pa = SystemImage::findHome; diff --git a/src/java.base/share/classes/jdk/internal/misc/Unsafe.java b/src/java.base/share/classes/jdk/internal/misc/Unsafe.java index 016566ae659..96e96970e22 100644 --- a/src/java.base/share/classes/jdk/internal/misc/Unsafe.java +++ b/src/java.base/share/classes/jdk/internal/misc/Unsafe.java @@ -25,6 +25,7 @@ package jdk.internal.misc; +import jdk.internal.value.ValueClass; import jdk.internal.vm.annotation.AOTRuntimeSetup; import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.ForceInline; @@ -186,8 +187,91 @@ public static Unsafe getUnsafe() { @IntrinsicCandidate public native void putInt(Object o, long offset, int x); + + /** + * Returns true if the given field is flattened. + */ + public boolean isFlatField(Field f) { + if (f == null) { + throw new NullPointerException(); + } + return isFlatField0(f); + } + + private native boolean isFlatField0(Object o); + + /* Returns true if the given field has a null marker + *

    + * Nullable flat fields are stored in a flattened representation + * and have an associated null marker to indicate if the the field value is + * null or the one stored with the flat representation + */ + + public boolean hasNullMarker(Field f) { + if (f == null) { + throw new NullPointerException(); + } + return hasNullMarker0(f); + } + + private native boolean hasNullMarker0(Object o); + + /* Returns the offset of the null marker of the field, + * or -1 if the field doesn't have a null marker + */ + + public int nullMarkerOffset(Field f) { + if (f == null) { + throw new NullPointerException(); + } + return nullMarkerOffset0(f); + } + + private native int nullMarkerOffset0(Object o); + + public static final int NON_FLAT_LAYOUT = 0; + + /* Reports the kind of layout used for an element in the storage + * allocation of the given array. Do not expect to perform any logic + * or layout control with this value, it is just an opaque token + * used for performance reasons. + * + * A layout of 0 indicates this array is not flat. + */ + public int arrayLayout(Object[] array) { + if (array == null) { + throw new NullPointerException(); + } + return arrayLayout0(array); + } + + private native int arrayLayout0(Object[] array); + + + /* Reports the kind of layout used for a given field in the storage + * allocation of its class. Do not expect to perform any logic + * or layout control with this value, it is just an opaque token + * used for performance reasons. + * + * A layout of 0 indicates this field is not flat. + */ + public int fieldLayout(Field f) { + if (f == null) { + throw new NullPointerException(); + } + return fieldLayout0(f); + } + + private native int fieldLayout0(Object o); + + public native Object[] newSpecialArray(Class componentType, + int length, int layoutKind); + /** * Fetches a reference value from a given Java variable. + * This method can return a reference to either an object or value + * or a null reference. + * * @see #getInt(Object, long) */ @IntrinsicCandidate @@ -195,6 +279,8 @@ public static Unsafe getUnsafe() { /** * Stores a reference value into a given Java variable. + * This method can store a reference to either an object or value + * or a null reference. *

    * Unless the reference {@code x} being stored is either null * or matches the field type, the results are undefined. @@ -206,6 +292,121 @@ public static Unsafe getUnsafe() { @IntrinsicCandidate public native void putReference(Object o, long offset, Object x); + /** + * Fetches a value of type {@code } from a given Java variable. + * More specifically, fetches a field or array element within the given + * {@code o} object at the given offset, or (if {@code o} is null) + * from the memory address whose numerical value is the given offset. + * + * @param o Java heap object in which the variable resides, if any, else + * null + * @param offset indication of where the variable resides in a Java heap + * object, if any, else a memory address locating the variable + * statically + * @param valueType value type + * @param the type of a value + * @return the value fetched from the indicated Java variable + * @throws RuntimeException No defined exceptions are thrown, not even + * {@link NullPointerException} + */ + @IntrinsicCandidate + public native V getValue(Object o, long offset, Class valueType); + + /** + * Fetches a value of type {@code } from a given Java variable. + * More specifically, fetches a field or array element within the given + * {@code o} object at the given offset, or (if {@code o} is null) + * from the memory address whose numerical value is the given offset. + * + * @param o Java heap object in which the variable resides, if any, else + * null + * @param offset indication of where the variable resides in a Java heap + * object, if any, else a memory address locating the variable + * statically + * @param layoutKind opaque value used by the VM to know the layout + * the field or array element. This value must be retrieved with + * {@link #fieldLayout} or {@link #arrayLayout}. + * @param valueType value type + * @param the type of a value + * @return the value fetched from the indicated Java variable + * @throws RuntimeException No defined exceptions are thrown, not even + * {@link NullPointerException} + */ + @IntrinsicCandidate + public native V getFlatValue(Object o, long offset, int layoutKind, Class valueType); + + + /** + * Stores the given value into a given Java variable. + * + * Unless the reference {@code o} being stored is either null + * or matches the field type, the results are undefined. + * + * @param o Java heap object in which the variable resides, if any, else + * null + * @param offset indication of where the variable resides in a Java heap + * object, if any, else a memory address locating the variable + * statically + * @param valueType value type + * @param v the value to store into the indicated Java variable + * @param the type of a value + * @throws RuntimeException No defined exceptions are thrown, not even + * {@link NullPointerException} + */ + @IntrinsicCandidate + public native void putValue(Object o, long offset, Class valueType, V v); + + /** + * Stores the given value into a given Java variable. + * + * Unless the reference {@code o} being stored is either null + * or matches the field type, the results are undefined. + * + * @param o Java heap object in which the variable resides, if any, else + * null + * @param offset indication of where the variable resides in a Java heap + * object, if any, else a memory address locating the variable + * statically + * @param layoutKind opaque value used by the VM to know the layout + * the field or array element. This value must be retrieved with + * {@link #fieldLayout} or {@link #arrayLayout}. + * @param valueType value type + * @param v the value to store into the indicated Java variable + * @param the type of a value + * @throws RuntimeException No defined exceptions are thrown, not even + * {@link NullPointerException} + */ + @IntrinsicCandidate + public native void putFlatValue(Object o, long offset, int layoutKind, Class valueType, V v); + + /** + * Returns an object instance with a private buffered value whose layout + * and contents is exactly the given value instance. The return object + * is in the larval state that can be updated using the unsafe put operation. + * + * @param value a value instance + * @param the type of the given value instance + */ + @IntrinsicCandidate + public native V makePrivateBuffer(V value); + + /** + * Exits the larval state and returns a value instance. + * + * @param value a value instance + * @param the type of the given value instance + */ + @IntrinsicCandidate + public native V finishPrivateBuffer(V value); + + /** + * Returns the header size of the given value type. + * + * @param valueType value type + * @return the header size of the value type + */ + public native long valueHeaderSize(Class valueType); + /** @see #getInt(Object, long) */ @IntrinsicCandidate public native boolean getBoolean(Object o, long offset); @@ -1195,6 +1396,21 @@ public void ensureClassInitialized(Class c) { ensureClassInitialized0(c); } + /** + * The reading or writing of strict static fields may require + * special processing. Notify the VM that such an event is about + * to happen. The VM may respond by throwing an exception, in the + * case of a read of an uninitialized field. If the VM allows the + * method to return normally, no further calls are needed, with + * the same arguments. + */ + public void notifyStrictStaticAccess(Class c, long staticFieldOffset, boolean writing) { + if (c == null) { + throw new NullPointerException(); + } + notifyStrictStaticAccess0(c, staticFieldOffset, writing); + } + /** * Reports the offset of the first element in the storage allocation of a * given array class. If {@link #arrayIndexScale} returns a non-zero value @@ -1217,6 +1433,13 @@ public long arrayBaseOffset(Class arrayClass) { return arrayBaseOffset0(arrayClass); } + public long arrayBaseOffset(Object[] array) { + if (array == null) { + throw new NullPointerException(); + } + + return arrayBaseOffset1(array); + } /** The value of {@code arrayBaseOffset(boolean[].class)} */ public static final long ARRAY_BOOLEAN_BASE_OFFSET @@ -1276,6 +1499,25 @@ public int arrayIndexScale(Class arrayClass) { return arrayIndexScale0(arrayClass); } + public int arrayIndexScale(Object[] array) { + if (array == null) { + throw new NullPointerException(); + } + + return arrayIndexScale1(array); + } + + /** + * Return the size of the object in the heap. + * @param o an object + * @return the objects's size + * @since Valhalla + */ + public long getObjectSize(Object o) { + if (o == null) + throw new NullPointerException(); + return getObjectSize0(o); + } /** The value of {@code arrayIndexScale(boolean[].class)} */ public static final int ARRAY_BOOLEAN_INDEX_SCALE @@ -1454,11 +1696,93 @@ public final native boolean compareAndSetReference(Object o, long offset, Object expected, Object x); + private final boolean isValueObject(Object o) { + return o != null && o.getClass().isValue(); + } + + /* + * For value type, CAS should do substitutability test as opposed + * to two pointers comparison. + */ + @ForceInline + public final boolean compareAndSetReference(Object o, long offset, + Class type, + V expected, + V x) { + if (type.isValue() || isValueObject(expected)) { + while (true) { + Object witness = getReferenceVolatile(o, offset); + if (witness != expected) { + return false; + } + if (compareAndSetReference(o, offset, witness, x)) { + return true; + } + } + } else { + return compareAndSetReference(o, offset, expected, x); + } + } + + @ForceInline + public final boolean compareAndSetFlatValue(Object o, long offset, + int layout, + Class valueType, + V expected, + V x) { + while (true) { + Object witness = getFlatValueVolatile(o, offset, layout, valueType); + if (witness != expected) { + return false; + } + if (compareAndSetFlatValueAsBytes(o, offset, layout, valueType, witness, x)) { + return true; + } + } + } + @IntrinsicCandidate public final native Object compareAndExchangeReference(Object o, long offset, Object expected, Object x); + @ForceInline + public final Object compareAndExchangeReference(Object o, long offset, + Class valueType, + V expected, + V x) { + if (valueType.isValue() || isValueObject(expected)) { + while (true) { + Object witness = getReferenceVolatile(o, offset); + if (witness != expected) { + return witness; + } + if (compareAndSetReference(o, offset, witness, x)) { + return witness; + } + } + } else { + return compareAndExchangeReference(o, offset, expected, x); + } + } + + @ForceInline + public final Object compareAndExchangeFlatValue(Object o, long offset, + int layout, + Class valueType, + V expected, + V x) { + while (true) { + Object witness = getFlatValueVolatile(o, offset, layout, valueType); + if (witness != expected) { + return witness; + } + if (compareAndSetFlatValueAsBytes(o, offset, layout, valueType, witness, x)) { + return witness; + } + } + } + @IntrinsicCandidate public final Object compareAndExchangeReferenceAcquire(Object o, long offset, Object expected, @@ -1466,6 +1790,22 @@ public final Object compareAndExchangeReferenceAcquire(Object o, long offset, return compareAndExchangeReference(o, offset, expected, x); } + public final Object compareAndExchangeReferenceAcquire(Object o, long offset, + Class valueType, + V expected, + V x) { + return compareAndExchangeReference(o, offset, valueType, expected, x); + } + + @ForceInline + public final Object compareAndExchangeFlatValueAcquire(Object o, long offset, + int layout, + Class valueType, + V expected, + V x) { + return compareAndExchangeFlatValue(o, offset, layout, valueType, expected, x); + } + @IntrinsicCandidate public final Object compareAndExchangeReferenceRelease(Object o, long offset, Object expected, @@ -1473,6 +1813,22 @@ public final Object compareAndExchangeReferenceRelease(Object o, long offset, return compareAndExchangeReference(o, offset, expected, x); } + public final Object compareAndExchangeReferenceRelease(Object o, long offset, + Class valueType, + V expected, + V x) { + return compareAndExchangeReference(o, offset, valueType, expected, x); + } + + @ForceInline + public final Object compareAndExchangeFlatValueRelease(Object o, long offset, + int layout, + Class valueType, + V expected, + V x) { + return compareAndExchangeFlatValue(o, offset, layout, valueType, expected, x); + } + @IntrinsicCandidate public final boolean weakCompareAndSetReferencePlain(Object o, long offset, Object expected, @@ -1480,6 +1836,26 @@ public final boolean weakCompareAndSetReferencePlain(Object o, long offset, return compareAndSetReference(o, offset, expected, x); } + public final boolean weakCompareAndSetReferencePlain(Object o, long offset, + Class valueType, + V expected, + V x) { + if (valueType.isValue() || isValueObject(expected)) { + return compareAndSetReference(o, offset, valueType, expected, x); + } else { + return weakCompareAndSetReferencePlain(o, offset, expected, x); + } + } + + @ForceInline + public final boolean weakCompareAndSetFlatValuePlain(Object o, long offset, + int layout, + Class valueType, + V expected, + V x) { + return compareAndSetFlatValue(o, offset, layout, valueType, expected, x); + } + @IntrinsicCandidate public final boolean weakCompareAndSetReferenceAcquire(Object o, long offset, Object expected, @@ -1487,6 +1863,26 @@ public final boolean weakCompareAndSetReferenceAcquire(Object o, long offset, return compareAndSetReference(o, offset, expected, x); } + public final boolean weakCompareAndSetReferenceAcquire(Object o, long offset, + Class valueType, + V expected, + V x) { + if (valueType.isValue() || isValueObject(expected)) { + return compareAndSetReference(o, offset, valueType, expected, x); + } else { + return weakCompareAndSetReferencePlain(o, offset, expected, x); + } + } + + @ForceInline + public final boolean weakCompareAndSetFlatValueAcquire(Object o, long offset, + int layout, + Class valueType, + V expected, + V x) { + return compareAndSetFlatValue(o, offset, layout, valueType, expected, x); + } + @IntrinsicCandidate public final boolean weakCompareAndSetReferenceRelease(Object o, long offset, Object expected, @@ -1494,6 +1890,26 @@ public final boolean weakCompareAndSetReferenceRelease(Object o, long offset, return compareAndSetReference(o, offset, expected, x); } + public final boolean weakCompareAndSetReferenceRelease(Object o, long offset, + Class valueType, + V expected, + V x) { + if (valueType.isValue() || isValueObject(expected)) { + return compareAndSetReference(o, offset, valueType, expected, x); + } else { + return weakCompareAndSetReferencePlain(o, offset, expected, x); + } + } + + @ForceInline + public final boolean weakCompareAndSetFlatValueRelease(Object o, long offset, + int layout, + Class valueType, + V expected, + V x) { + return compareAndSetFlatValue(o, offset, layout, valueType, expected, x); + } + @IntrinsicCandidate public final boolean weakCompareAndSetReference(Object o, long offset, Object expected, @@ -1501,6 +1917,26 @@ public final boolean weakCompareAndSetReference(Object o, long offset, return compareAndSetReference(o, offset, expected, x); } + public final boolean weakCompareAndSetReference(Object o, long offset, + Class valueType, + V expected, + V x) { + if (valueType.isValue() || isValueObject(expected)) { + return compareAndSetReference(o, offset, valueType, expected, x); + } else { + return weakCompareAndSetReferencePlain(o, offset, expected, x); + } + } + + @ForceInline + public final boolean weakCompareAndSetFlatValue(Object o, long offset, + int layout, + Class valueType, + V expected, + V x) { + return compareAndSetFlatValue(o, offset, layout, valueType, expected, x); + } + /** * Atomically updates Java variable to {@code x} if it is currently * holding {@code expected}. @@ -2116,6 +2552,14 @@ public final boolean weakCompareAndSetLong(Object o, long offset, @IntrinsicCandidate public native Object getReferenceVolatile(Object o, long offset); + @ForceInline + public final Object getFlatValueVolatile(Object o, long offset, int layout, Class valueType) { + // we translate using fences (see: https://gee.cs.oswego.edu/dl/html/j9mm.html) + Object res = getFlatValue(o, offset, layout, valueType); + fullFence(); + return res; + } + /** * Stores a reference value into a given Java variable, with * volatile store semantics. Otherwise identical to {@link #putReference(Object, long, Object)} @@ -2123,6 +2567,13 @@ public final boolean weakCompareAndSetLong(Object o, long offset, @IntrinsicCandidate public native void putReferenceVolatile(Object o, long offset, Object x); + @ForceInline + public final void putFlatValueVolatile(Object o, long offset, int layout, Class valueType, V x) { + // we translate using fences (see: https://gee.cs.oswego.edu/dl/html/j9mm.html) + putFlatValueRelease(o, offset, layout, valueType, x); + fullFence(); + } + /** Volatile version of {@link #getInt(Object, long)} */ @IntrinsicCandidate public native int getIntVolatile(Object o, long offset); @@ -2195,6 +2646,14 @@ public final Object getReferenceAcquire(Object o, long offset) { return getReferenceVolatile(o, offset); } + @ForceInline + public final Object getFlatValueAcquire(Object o, long offset, int layout, Class valueType) { + // we translate using fences (see: https://gee.cs.oswego.edu/dl/html/j9mm.html) + Object res = getFlatValue(o, offset, layout, valueType); + loadFence(); + return res; + } + /** Acquire version of {@link #getBooleanVolatile(Object, long)} */ @IntrinsicCandidate public final boolean getBooleanAcquire(Object o, long offset) { @@ -2259,6 +2718,13 @@ public final void putReferenceRelease(Object o, long offset, Object x) { putReferenceVolatile(o, offset, x); } + @ForceInline + public final void putFlatValueRelease(Object o, long offset, int layout, Class valueType, V x) { + // we translate using fences (see: https://gee.cs.oswego.edu/dl/html/j9mm.html) + storeFence(); + putFlatValue(o, offset, layout, valueType, x); + } + /** Release version of {@link #putBooleanVolatile(Object, long, boolean)} */ @IntrinsicCandidate public final void putBooleanRelease(Object o, long offset, boolean x) { @@ -2315,6 +2781,12 @@ public final Object getReferenceOpaque(Object o, long offset) { return getReferenceVolatile(o, offset); } + @ForceInline + public final Object getFlatValueOpaque(Object o, long offset, int layout, Class valueType) { + // this is stronger than opaque semantics + return getFlatValueAcquire(o, offset, layout, valueType); + } + /** Opaque version of {@link #getBooleanVolatile(Object, long)} */ @IntrinsicCandidate public final boolean getBooleanOpaque(Object o, long offset) { @@ -2369,6 +2841,12 @@ public final void putReferenceOpaque(Object o, long offset, Object x) { putReferenceVolatile(o, offset, x); } + @ForceInline + public final void putFlatValueOpaque(Object o, long offset, int layout, Class valueType, V x) { + // this is stronger than opaque semantics + putFlatValueRelease(o, offset, layout, valueType, x); + } + /** Opaque version of {@link #putBooleanVolatile(Object, long, boolean)} */ @IntrinsicCandidate public final void putBooleanOpaque(Object o, long offset, boolean x) { @@ -2417,6 +2895,46 @@ public final void putDoubleOpaque(Object o, long offset, double x) { putDoubleVolatile(o, offset, x); } + @ForceInline + private boolean compareAndSetFlatValueAsBytes(Object o, long offset, int layout, Class valueType, Object expected, Object x) { + // We turn the payload of an atomic value into a numeric value (of suitable type) + // by storing the value into an array element (of matching layout) and by reading + // back the array element as an integral value. After which we can implement the CAS + // as a plain numeric CAS. Note: this only works if the payload contains no oops + // (see VarHandles::isAtomicFlat). + Object[] expectedArray = newSpecialArray(valueType, 1, layout); + Object xArray = newSpecialArray(valueType, 1, layout); + long base = arrayBaseOffset(expectedArray); + int scale = arrayIndexScale(expectedArray); + putFlatValue(expectedArray, base, layout, valueType, expected); + putFlatValue(xArray, base, layout, valueType, x); + switch (scale) { + case 1: { + byte expectedByte = getByte(expectedArray, base); + byte xByte = getByte(xArray, base); + return compareAndSetByte(o, offset, expectedByte, xByte); + } + case 2: { + short expectedShort = getShort(expectedArray, base); + short xShort = getShort(xArray, base); + return compareAndSetShort(o, offset, expectedShort, xShort); + } + case 4: { + int expectedInt = getInt(expectedArray, base); + int xInt = getInt(xArray, base); + return compareAndSetInt(o, offset, expectedInt, xInt); + } + case 8: { + long expectedLong = getLong(expectedArray, base); + long xLong = getLong(xArray, base); + return compareAndSetLong(o, offset, expectedLong, xLong); + } + default: { + throw new UnsupportedOperationException(); + } + } + } + /** * Unblocks the given thread blocked on {@code park}, or, if it is * not blocked, causes the subsequent call to {@code park} not to @@ -2803,6 +3321,24 @@ public final Object getAndSetReference(Object o, long offset, Object newValue) { return v; } + @ForceInline + public final Object getAndSetReference(Object o, long offset, Class valueType, Object newValue) { + Object v; + do { + v = getReferenceVolatile(o, offset); + } while (!compareAndSetReference(o, offset, valueType, v, newValue)); + return v; + } + + @ForceInline + public Object getAndSetFlatValue(Object o, long offset, int layoutKind, Class valueType, Object newValue) { + Object v; + do { + v = getFlatValueVolatile(o, offset, layoutKind, valueType); + } while (!compareAndSetFlatValue(o, offset, layoutKind, valueType, v, newValue)); + return v; + } + @ForceInline public final Object getAndSetReferenceRelease(Object o, long offset, Object newValue) { Object v; @@ -2812,6 +3348,16 @@ public final Object getAndSetReferenceRelease(Object o, long offset, Object newV return v; } + @ForceInline + public final Object getAndSetReferenceRelease(Object o, long offset, Class valueType, Object newValue) { + return getAndSetReference(o, offset, valueType, newValue); + } + + @ForceInline + public Object getAndSetFlatValueRelease(Object o, long offset, int layoutKind, Class valueType, Object x) { + return getAndSetFlatValue(o, offset, layoutKind, valueType, x); + } + @ForceInline public final Object getAndSetReferenceAcquire(Object o, long offset, Object newValue) { Object v; @@ -2821,6 +3367,16 @@ public final Object getAndSetReferenceAcquire(Object o, long offset, Object newV return v; } + @ForceInline + public final Object getAndSetReferenceAcquire(Object o, long offset, Class valueType, Object newValue) { + return getAndSetReference(o, offset, valueType, newValue); + } + + @ForceInline + public Object getAndSetFlatValueAcquire(Object o, long offset, int layoutKind, Class valueType, Object x) { + return getAndSetFlatValue(o, offset, layoutKind, valueType, x); + } + @IntrinsicCandidate public final byte getAndSetByte(Object o, long offset, byte newValue) { byte v; @@ -3876,8 +4432,12 @@ private void putShortParts(Object o, long offset, byte i0, byte i1) { private native Object staticFieldBase0(Field f); // throws IAE private native boolean shouldBeInitialized0(Class c); private native void ensureClassInitialized0(Class c); + private native void notifyStrictStaticAccess0(Class c, long staticFieldOffset, boolean writing); private native int arrayBaseOffset0(Class arrayClass); // public version returns long to promote correct arithmetic + private native int arrayBaseOffset1(Object[] array); private native int arrayIndexScale0(Class arrayClass); + private native int arrayIndexScale1(Object[] array); + private native long getObjectSize0(Object o); private native int getLoadAverage0(double[] loadavg, int nelems); diff --git a/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java b/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java index 39f433d4041..c4c455e42cd 100644 --- a/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java +++ b/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java @@ -414,26 +414,19 @@ private static class SystemModuleReader implements ModuleReader { * Returns {@code true} if the given resource exists, {@code false} * if not found. */ - private boolean containsResource(String resourcePath) throws IOException { - Objects.requireNonNull(resourcePath); + private boolean containsResource(String module, String name) throws IOException { + Objects.requireNonNull(name); if (closed) throw new IOException("ModuleReader is closed"); ImageReader imageReader = SystemImage.reader(); - if (imageReader != null) { - ImageReader.Node node = imageReader.findNode("/modules" + resourcePath); - return node != null && node.isResource(); - } else { - // not an images build - return false; - } + return imageReader != null && imageReader.containsResource(module, name); } @Override public Optional find(String name) throws IOException { Objects.requireNonNull(name); - String resourcePath = "/" + module + "/" + name; - if (containsResource(resourcePath)) { - URI u = JNUA.create("jrt", resourcePath); + if (containsResource(module, name)) { + URI u = JNUA.create("jrt", "/" + module + "/" + name); return Optional.of(u); } else { return Optional.empty(); @@ -465,9 +458,7 @@ private ImageReader.Node findResource(ImageReader reader, String name) throws IO if (closed) { throw new IOException("ModuleReader is closed"); } - String nodeName = "/modules/" + module + "/" + name; - ImageReader.Node node = reader.findNode(nodeName); - return (node != null && node.isResource()) ? node : null; + return reader.findResourceNode(module, name); } @Override diff --git a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleBooleanFieldAccessorImpl.java b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleBooleanFieldAccessorImpl.java index 2e0609264bd..60c8e6e0cd7 100644 --- a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleBooleanFieldAccessorImpl.java +++ b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleBooleanFieldAccessorImpl.java @@ -62,7 +62,7 @@ public boolean getBoolean(Object obj) throws IllegalArgumentException { } else { return (boolean) getter.invokeExact(obj); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { throw newGetIllegalArgumentException(obj); @@ -131,7 +131,7 @@ public void setBoolean(Object obj, boolean z) } else { setter.invokeExact(obj, z); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { // receiver is of invalid type diff --git a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleByteFieldAccessorImpl.java b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleByteFieldAccessorImpl.java index b56fbbcbcb2..d4ced504bbf 100644 --- a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleByteFieldAccessorImpl.java +++ b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleByteFieldAccessorImpl.java @@ -66,7 +66,7 @@ public byte getByte(Object obj) throws IllegalArgumentException { } else { return (byte) getter.invokeExact(obj); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { throw newGetIllegalArgumentException(obj); @@ -137,7 +137,7 @@ public void setByte(Object obj, byte b) } else { setter.invokeExact(obj, b); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { // receiver is of invalid type diff --git a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleCharacterFieldAccessorImpl.java b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleCharacterFieldAccessorImpl.java index c1f357326f4..308702718ef 100644 --- a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleCharacterFieldAccessorImpl.java +++ b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleCharacterFieldAccessorImpl.java @@ -70,7 +70,7 @@ public char getChar(Object obj) throws IllegalArgumentException { } else { return (char) getter.invokeExact(obj); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { throw newGetIllegalArgumentException(obj); @@ -143,7 +143,7 @@ public void setChar(Object obj, char c) } else { setter.invokeExact(obj, c); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { // receiver is of invalid type diff --git a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleDoubleFieldAccessorImpl.java b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleDoubleFieldAccessorImpl.java index 01652951e42..71e33622d2b 100644 --- a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleDoubleFieldAccessorImpl.java +++ b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleDoubleFieldAccessorImpl.java @@ -90,7 +90,7 @@ public double getDouble(Object obj) throws IllegalArgumentException { } else { return (double) getter.invokeExact(obj); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { throw newGetIllegalArgumentException(obj); @@ -192,7 +192,7 @@ public void setDouble(Object obj, double d) } else { setter.invokeExact(obj, d); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { // receiver is of invalid type diff --git a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleFloatFieldAccessorImpl.java b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleFloatFieldAccessorImpl.java index 5ac00ec5ea8..314a25a6506 100644 --- a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleFloatFieldAccessorImpl.java +++ b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleFloatFieldAccessorImpl.java @@ -86,7 +86,7 @@ public float getFloat(Object obj) throws IllegalArgumentException { } else { return (float) getter.invokeExact(obj); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { throw newGetIllegalArgumentException(obj); @@ -183,7 +183,7 @@ public void setFloat(Object obj, float f) } else { setter.invokeExact(obj, f); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { // receiver is of invalid type diff --git a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleIntegerFieldAccessorImpl.java b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleIntegerFieldAccessorImpl.java index 62e3ab083db..d1c887d5923 100644 --- a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleIntegerFieldAccessorImpl.java +++ b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleIntegerFieldAccessorImpl.java @@ -78,7 +78,7 @@ public int getInt(Object obj) throws IllegalArgumentException { } else { return (int) getter.invokeExact(obj); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { throw newGetIllegalArgumentException(obj); @@ -165,7 +165,7 @@ public void setInt(Object obj, int i) } else { setter.invokeExact(obj, i); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { // receiver is of invalid type diff --git a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleLongFieldAccessorImpl.java b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleLongFieldAccessorImpl.java index a0e02204b31..0090e7dc58c 100644 --- a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleLongFieldAccessorImpl.java +++ b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleLongFieldAccessorImpl.java @@ -82,7 +82,7 @@ public long getLong(Object obj) throws IllegalArgumentException { } else { return (long) getter.invokeExact(obj); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { throw newGetIllegalArgumentException(obj); @@ -174,7 +174,7 @@ public void setLong(Object obj, long l) } else { setter.invokeExact(obj, l); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { // receiver is of invalid type diff --git a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleObjectFieldAccessorImpl.java b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleObjectFieldAccessorImpl.java index 722d73d22a6..df94dc9bce1 100644 --- a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleObjectFieldAccessorImpl.java +++ b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleObjectFieldAccessorImpl.java @@ -55,7 +55,7 @@ static FieldAccessorImpl fieldAccessor(Field field, MethodHandle getter, MethodH public Object get(Object obj) throws IllegalArgumentException { try { return isStatic() ? getter.invokeExact() : getter.invokeExact(obj); - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { throw newGetIllegalArgumentException(obj); @@ -108,7 +108,7 @@ public void set(Object obj, Object value) throws IllegalAccessException { } else { setter.invokeExact(obj, value); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { // already ensure the receiver type. So this CCE is due to the value. diff --git a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleShortFieldAccessorImpl.java b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleShortFieldAccessorImpl.java index 265c555421a..6dad0f3ebbb 100644 --- a/src/java.base/share/classes/jdk/internal/reflect/MethodHandleShortFieldAccessorImpl.java +++ b/src/java.base/share/classes/jdk/internal/reflect/MethodHandleShortFieldAccessorImpl.java @@ -74,7 +74,7 @@ public short getShort(Object obj) throws IllegalArgumentException { } else { return (short) getter.invokeExact(obj); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { throw newGetIllegalArgumentException(obj); @@ -153,7 +153,7 @@ public void setShort(Object obj, short s) } else { setter.invokeExact(obj, s); } - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException|IllegalStateException|NullPointerException e) { throw e; } catch (ClassCastException e) { // receiver is of invalid type diff --git a/src/java.base/share/classes/jdk/internal/reflect/ReflectionFactory.java b/src/java.base/share/classes/jdk/internal/reflect/ReflectionFactory.java index 19be5e53798..2ceabb06ef8 100644 --- a/src/java.base/share/classes/jdk/internal/reflect/ReflectionFactory.java +++ b/src/java.base/share/classes/jdk/internal/reflect/ReflectionFactory.java @@ -199,6 +199,9 @@ public final Constructor newConstructorForExternalization(Class cl) { if (!Externalizable.class.isAssignableFrom(cl)) { return null; } + if (cl.isValue()) { + throw new UnsupportedOperationException("newConstructorForExternalization does not support value classes"); + } try { Constructor cons = cl.getConstructor(); cons.setAccessible(true); @@ -215,6 +218,9 @@ public final Constructor newConstructorForSerialization(Class cl, constructorToCall.setAccessible(true); return constructorToCall; } + if (cl.isValue()) { + throw new UnsupportedOperationException("newConstructorForSerialization does not support value classes"); + } return generateConstructor(cl, constructorToCall); } @@ -274,6 +280,10 @@ private boolean superHasAccessibleConstructor(Class cl) { * @return the generated constructor, or null if none is available */ public final Constructor newConstructorForSerialization(Class cl) { + if (cl.isValue()) { + throw new UnsupportedOperationException("newConstructorForSerialization does not support value classes: " + cl); + } + Class initCl = cl; while (Serializable.class.isAssignableFrom(initCl)) { Class prev = initCl; @@ -514,13 +524,12 @@ public final ObjectStreamField[] serialPersistentFields(Class cl) { } public final Set parseAccessFlags(int mask, AccessFlag.Location location, Class classFile) { - var cffv = classFileFormatVersion(classFile); - return cffv == null ? - AccessFlag.maskToAccessFlags(mask, location) : - AccessFlag.maskToAccessFlags(mask, location, cffv); + return AccessFlag.maskToAccessFlags(mask, location, classFileFormatVersion(classFile)); } - private final ClassFileFormatVersion classFileFormatVersion(Class cl) { + public final ClassFileFormatVersion classFileFormatVersion(Class cl) { + if (cl.isArray() || cl.isPrimitive()) + return ClassFileFormatVersion.CURRENT_PREVIEW_FEATURES; int raw = SharedSecrets.getJavaLangAccess().classFileVersion(cl); int major = raw & 0xFFFF; @@ -531,7 +540,7 @@ private final ClassFileFormatVersion classFileFormatVersion(Class cl) { if (major >= ClassFile.JAVA_12_VERSION) { if (minor == 0) return ClassFileFormatVersion.fromMajor(raw); - return null; // preview or old preview, fallback to default handling + return ClassFileFormatVersion.CURRENT_PREVIEW_FEATURES; } else if (major == ClassFile.JAVA_1_VERSION) { return minor < 3 ? ClassFileFormatVersion.RELEASE_0 : ClassFileFormatVersion.RELEASE_1; } diff --git a/src/java.base/share/classes/jdk/internal/util/ReferencedKeyMap.java b/src/java.base/share/classes/jdk/internal/util/ReferencedKeyMap.java index 150b9d752bb..f0a03a3ded6 100644 --- a/src/java.base/share/classes/jdk/internal/util/ReferencedKeyMap.java +++ b/src/java.base/share/classes/jdk/internal/util/ReferencedKeyMap.java @@ -49,6 +49,7 @@ * remove entries automatically when the key is garbage collected. This is * accomplished by using a backing map where the keys are either a * {@link WeakReference} or a {@link SoftReference}. + * Keys must be {@linkplain Class#isIdentity() identity objects.} *

    * To create a {@link ReferencedKeyMap} the user must provide a {@link Supplier} * of the backing map and whether {@link WeakReference} or diff --git a/src/java.base/share/classes/jdk/internal/value/DeserializeConstructor.java b/src/java.base/share/classes/jdk/internal/value/DeserializeConstructor.java new file mode 100644 index 00000000000..f1acff12831 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/value/DeserializeConstructor.java @@ -0,0 +1,43 @@ +/* + * 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. + */ +package jdk.internal.value; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; + +/** + * Indicates the constructor or static factory to + * construct a value object during deserialization. + * The annotation is used by java.io.ObjectStreamClass to select the constructor + * or factory method to create objects from a stream. + * + * @since 24 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value={CONSTRUCTOR, METHOD}) +public @interface DeserializeConstructor { +} diff --git a/src/java.base/share/classes/jdk/internal/value/LayoutIteration.java b/src/java.base/share/classes/jdk/internal/value/LayoutIteration.java new file mode 100644 index 00000000000..f971dcdff04 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/value/LayoutIteration.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.value; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +import jdk.internal.access.JavaLangInvokeAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.misc.Unsafe; +import sun.invoke.util.Wrapper; + +/** + * Iterates the layout elements of a value type. + *

    + * In the long run, we should do this: + * Iterate the layout, create a mask masking bytes used by Object/abstract value + * class reference fields. Do a byte-wise compare and get the mask of value + * mismatch; if the mask's all clear, fine; if the mask has bits beyond our + * mask, fail; otherwise, compare reference fields indicated by the mismatch + * mask. There may be paddings to ignore, too, depends... + */ +public final class LayoutIteration { + // Initializer in static initializers below, order dependent + public static final ClassValue> ELEMENTS; + + /** + * {@return a list of method handles accessing the basic elements} + * Basic elements are 8 primitives and pointers (to identity or value objects). + * Primitives and pointers are distinguished by the MH return types. + * The MH types are {@code flatType -> fieldType}. + * + * @param flatType the class that has a flat layout + * @return the accessors + * @throws IllegalArgumentException if argument has no flat layout + */ + public static List computeElementGetters(Class flatType) { + if (!ValueClass.isConcreteValueClass(flatType)) + throw new IllegalArgumentException(flatType + " cannot be flat"); + var sink = new Sink(flatType); + iterateFields(U.valueHeaderSize(flatType), flatType, sink); + return List.copyOf(sink.getters); + } + + private static final class Sink { + final Class receiverType; + final List getters = new ArrayList<>(); + + Sink(Class receiverType) { + this.receiverType = receiverType; + } + + void accept(long offsetNoHeader, Class itemType) { + Wrapper w = itemType.isPrimitive() ? Wrapper.forPrimitiveType(itemType) : Wrapper.OBJECT; + var mh = MethodHandles.insertArguments(FIELD_GETTERS.get(w.ordinal()), 1, offsetNoHeader); + assert mh.type() == MethodType.methodType(w.primitiveType(), Object.class); + mh = JLIA.assertAsType(mh, MethodType.methodType(itemType, receiverType)); + getters.add(mh); + } + } + + // Sink is good for one to many mappings + private static void iterateFields(long enclosingOffset, Class currentClass, Sink sink) { + assert ValueClass.isConcreteValueClass(currentClass) : currentClass + " cannot be flat"; + long memberOffsetDelta = enclosingOffset - U.valueHeaderSize(currentClass); + for (Field f : currentClass.getDeclaredFields()) { + if (Modifier.isStatic(f.getModifiers())) + continue; + var type = f.getType(); + long memberOffset = U.objectFieldOffset(f) + memberOffsetDelta; + if (!U.isFlatField(f)) { + sink.accept(memberOffset, type); + } else { + iterateFields(memberOffset, type, sink); + } + } + } + + private static boolean getBoolean(Object o, long offset) { + return U.getBoolean(o, offset); + } + private static byte getByte(Object o, long offset) { + return U.getByte(o, offset); + } + private static short getShort(Object o, long offset) { + return U.getShort(o, offset); + } + private static char getCharacter(Object o, long offset) { + return U.getChar(o, offset); + } + private static int getInteger(Object o, long offset) { + return U.getInt(o, offset); + } + private static long getLong(Object o, long offset) { + return U.getLong(o, offset); + } + private static float getFloat(Object o, long offset) { + return U.getFloat(o, offset); + } + private static double getDouble(Object o, long offset) { + return U.getDouble(o, offset); + } + public static Object getObject(Object o, long offset) { + return U.getReference(o, offset); + } + + private static final Unsafe U = Unsafe.getUnsafe(); + private static final JavaLangInvokeAccess JLIA = SharedSecrets.getJavaLangInvokeAccess(); + private static final List FIELD_GETTERS; + static { + MethodHandle[] fieldGetters = new MethodHandle[9]; + var lookup = MethodHandles.lookup(); + var type = MethodType.methodType(void.class, Object.class, long.class); + try { + for (Wrapper w : Wrapper.values()) { + if (w != Wrapper.VOID) { + fieldGetters[w.ordinal()] = lookup.findStatic(LayoutIteration.class, + "get" + w.wrapperSimpleName(), type.changeReturnType(w.primitiveType())); + } + } + } catch (ReflectiveOperationException ex) { + throw new ExceptionInInitializerError(ex); + } + FIELD_GETTERS = List.of(fieldGetters); + ELEMENTS = new ClassValue<>() { + @Override + protected List computeValue(Class type) { + return computeElementGetters(type); + } + }; + } + + private LayoutIteration() {} +} diff --git a/src/java.base/share/classes/jdk/internal/value/ValueClass.java b/src/java.base/share/classes/jdk/internal/value/ValueClass.java new file mode 100644 index 00000000000..4f7ccd7c19d --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/value/ValueClass.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.value; + +import jdk.internal.access.JavaLangReflectAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.misc.PreviewFeatures; +import jdk.internal.vm.annotation.IntrinsicCandidate; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +/** + * Utilities to access package private methods of java.lang.Class and related reflection classes. + */ +public final class ValueClass { + private static final JavaLangReflectAccess JLRA = SharedSecrets.getJavaLangReflectAccess(); + + /// {@return whether this field type may store value objects} + /// This excludes primitives and includes Object. + public static boolean isValueObjectCompatible(Class fieldType) { + return PreviewFeatures.isEnabled() + && !fieldType.isPrimitive() // non-primitive + && (!fieldType.isIdentity() || fieldType == Object.class); // AVC or Object + } + + /// {@return whether an object of this exact class is a value object} + /// This excludes abstract value classes and primitives. + public static boolean isConcreteValueClass(Class clazz) { + return clazz.isValue() && !Modifier.isAbstract(clazz.getModifiers()); + } + + /** + * {@return {@code true} if the field is NullRestricted} + */ + public static boolean isNullRestrictedField(Field f) { + return JLRA.isNullRestrictedField(f); + } + + /** + * Allocate an array of a value class type with components that behave in + * the same way as a {@link jdk.internal.vm.annotation.NullRestricted} + * field. + *

    + * Because these behaviors are not specified by Java SE, arrays created with + * this method should only be used by internal JDK code for experimental + * purposes and should not affect user-observable outcomes. + * + * @throws IllegalArgumentException if {@code componentType} is not a + * value class type. + */ + @IntrinsicCandidate + public static native Object[] newNullRestrictedAtomicArray(Class componentType, + int length, Object initVal); + + @IntrinsicCandidate + public static native Object[] newNullRestrictedNonAtomicArray(Class componentType, + int length, Object initVal); + + @IntrinsicCandidate + public static native Object[] newNullableAtomicArray(Class componentType, + int length); + + public static native boolean isFlatArray(Object array); + + public static Object[] copyOfSpecialArray(Object[] array, int from, int to) { + return copyOfSpecialArray0(array, from, to); + } + + private static native Object[] copyOfSpecialArray0(Object[] array, int from, int to); + + /** + * {@return true if the given array is a null-restricted array} + */ + public static native boolean isNullRestrictedArray(Object array); + + /** + * {@return true if the given array uses a layout designed for atomic accesses } + */ + public static native boolean isAtomicArray(Object array); +} diff --git a/src/java.base/share/classes/jdk/internal/vm/annotation/LooselyConsistentValue.java b/src/java.base/share/classes/jdk/internal/vm/annotation/LooselyConsistentValue.java new file mode 100644 index 00000000000..33efa935c4f --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/vm/annotation/LooselyConsistentValue.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.vm.annotation; + +import java.lang.annotation.*; + +/** + * A loosely-consistent value class is a class that is willing to tolerate + * data corruption when fields or arrays storing instances of the class are + * updated under race. Specifically, a value object read from such a field may + * contain combinations of field values that were never set by a previous + * constructor invocation. + *

    + * Users of a class with this annotation take responsibility for ensuring the + * integrity of their data by avoiding race conditions. + *

    + * The HotSpot VM uses this annotation to enable non-atomic strategies for + * reading and writing to flattened fields and arrays of the annotated class's + * type. + *

    + * Because these behaviors are not specified by Java SE, this annotation should + * only be used by internal JDK code for experimental purposes and should not + * affect user-observable outcomes. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface LooselyConsistentValue { +} diff --git a/src/java.base/share/classes/jdk/internal/vm/annotation/NullRestricted.java b/src/java.base/share/classes/jdk/internal/vm/annotation/NullRestricted.java new file mode 100644 index 00000000000..83ef43d9152 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/vm/annotation/NullRestricted.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.vm.annotation; + +import java.lang.annotation.*; + +/** + * A null-restricted field is a field that does not store {@code null}. + * The type of the field is expected to be a value class type with the + * {@link Strict} annotation. The field must be initialized according + * to the strict fields rules, and attempts to write {@code null} + * to the field will throw an exception. + *

    + * The HotSpot VM uses this annotation to enable flattened encodings for the + * field that would otherwise be impossible. + *

    + * Because these behaviors are not specified by Java SE, this annotation should + * only be used by internal JDK classes for experimental purposes and should not + * affect user-observable outcomes. + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface NullRestricted { +} diff --git a/src/java.base/share/classes/jdk/internal/vm/annotation/Strict.java b/src/java.base/share/classes/jdk/internal/vm/annotation/Strict.java new file mode 100644 index 00000000000..e5f29d85bbc --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/vm/annotation/Strict.java @@ -0,0 +1,37 @@ +/* + * 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.internal.vm.annotation; + +import java.lang.annotation.*; + +/** + * Annotation to indicate that the strict field semantic applies to this field. + * Internal and experimental use only + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.SOURCE) +public @interface Strict { +} diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java index 2a51a0af38d..10902e7dae5 100644 --- a/src/java.base/share/classes/module-info.java +++ b/src/java.base/share/classes/module-info.java @@ -135,7 +135,6 @@ exports javax.security.auth.x500; exports javax.security.cert; - // additional qualified exports may be inserted at build time // see make/gensrc/GenModuleInfo.gmk @@ -154,7 +153,8 @@ // module declaration be annotated with jdk.internal.javac.ParticipatesInPreview exports jdk.internal.javac to java.compiler, - jdk.compiler; + jdk.compiler, + jdk.jdeps; exports jdk.internal.access to java.desktop, java.logging, @@ -258,6 +258,8 @@ jdk.jfr; exports jdk.internal.util.xml.impl to jdk.jfr; + exports jdk.internal.value to // Needed by Unsafe + jdk.unsupported; exports jdk.internal.util to java.desktop, java.prefs, diff --git a/src/java.base/share/classes/sun/invoke/util/VerifyAccess.java b/src/java.base/share/classes/sun/invoke/util/VerifyAccess.java index ec768704f95..eb17430735e 100644 --- a/src/java.base/share/classes/sun/invoke/util/VerifyAccess.java +++ b/src/java.base/share/classes/sun/invoke/util/VerifyAccess.java @@ -228,6 +228,7 @@ public static boolean isClassAccessible(Class refc, Module prevLookupModule = prevLookupClass != null ? prevLookupClass.getModule() : null; assert refModule != lookupModule || refModule != prevLookupModule; + if (isModuleAccessible(refc, lookupModule, prevLookupModule)) return true; diff --git a/src/java.base/share/classes/sun/net/www/protocol/jrt/JavaRuntimeURLConnection.java b/src/java.base/share/classes/sun/net/www/protocol/jrt/JavaRuntimeURLConnection.java index 20b735fbdf3..71080950b80 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/jrt/JavaRuntimeURLConnection.java +++ b/src/java.base/share/classes/sun/net/www/protocol/jrt/JavaRuntimeURLConnection.java @@ -87,8 +87,8 @@ private synchronized Node connectResourceNode() throws IOException { if (module.isEmpty() || path == null) { throw new IOException("cannot connect to jrt:/" + module); } - Node node = READER.findNode("/modules/" + module + "/" + path); - if (node == null || !node.isResource()) { + Node node = READER.findResourceNode(module, path); + if (node == null) { throw new IOException(module + "/" + path + " not found"); } this.resourceNode = node; diff --git a/src/java.base/share/native/include/classfile_constants.h.template b/src/java.base/share/native/include/classfile_constants.h.template index fb022ec1fd4..ce02f9f7e1f 100644 --- a/src/java.base/share/native/include/classfile_constants.h.template +++ b/src/java.base/share/native/include/classfile_constants.h.template @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -44,6 +44,7 @@ enum { JVM_ACC_FINAL = 0x0010, JVM_ACC_SYNCHRONIZED = 0x0020, JVM_ACC_SUPER = 0x0020, + JVM_ACC_IDENTITY = 0x0020, JVM_ACC_VOLATILE = 0x0040, JVM_ACC_BRIDGE = 0x0040, JVM_ACC_TRANSIENT = 0x0080, @@ -51,6 +52,7 @@ enum { JVM_ACC_NATIVE = 0x0100, JVM_ACC_INTERFACE = 0x0200, JVM_ACC_ABSTRACT = 0x0400, + JVM_ACC_PRIMITIVE = 0x0800, JVM_ACC_STRICT = 0x0800, JVM_ACC_SYNTHETIC = 0x1000, JVM_ACC_ANNOTATION = 0x2000, @@ -111,7 +113,7 @@ enum { JVM_CONSTANT_InvokeDynamic = 18, JVM_CONSTANT_Module = 19, JVM_CONSTANT_Package = 20, - JVM_CONSTANT_ExternalMax = 20 + JVM_CONSTANT_ExternalMax = 20 }; /* JVM_CONSTANT_MethodHandle subtypes */ @@ -152,6 +154,7 @@ enum { JVM_SIGNATURE_BYTE = 'B', JVM_SIGNATURE_CHAR = 'C', JVM_SIGNATURE_CLASS = 'L', + JVM_SIGNATURE_FLAT_ELEMENT = 'Q', JVM_SIGNATURE_ENDCLASS = ';', JVM_SIGNATURE_ENUM = 'E', JVM_SIGNATURE_FLOAT = 'F', diff --git a/src/java.base/share/native/libjava/Array.c b/src/java.base/share/native/libjava/Array.c index 5ce0813fd31..720007b1735 100644 --- a/src/java.base/share/native/libjava/Array.c +++ b/src/java.base/share/native/libjava/Array.c @@ -46,8 +46,8 @@ Java_java_lang_reflect_Array_getLength(JNIEnv *env, jclass ignore, jobject arr) * */ JNIEXPORT jobject JNICALL -Java_java_lang_reflect_Array_get(JNIEnv *env, jclass ignore, jobject arr, - jint index) +Java_java_lang_reflect_Array_getReferenceOrPrimitive(JNIEnv *env, jclass ignore, jobject arr, + jint index) { return JVM_GetArrayElement(env, arr, index); } @@ -112,8 +112,8 @@ Java_java_lang_reflect_Array_getDouble(JNIEnv *env, jclass ignore, jobject arr, * */ JNIEXPORT void JNICALL -Java_java_lang_reflect_Array_set(JNIEnv *env, jclass ignore, jobject arr, - jint index, jobject val) +Java_java_lang_reflect_Array_setReferenceOrPrimitive(JNIEnv *env, jclass ignore, jobject arr, + jint index, jobject val) { JVM_SetArrayElement(env, arr, index, val); } diff --git a/src/java.base/share/native/libjava/VM.c b/src/java.base/share/native/libjava/VM.c index 099ad46fff1..bca826e90ec 100644 --- a/src/java.base/share/native/libjava/VM.c +++ b/src/java.base/share/native/libjava/VM.c @@ -54,4 +54,4 @@ Java_jdk_internal_misc_VM_initialize(JNIEnv *env, jclass cls) { JNIEXPORT jobjectArray JNICALL Java_jdk_internal_misc_VM_getRuntimeArguments(JNIEnv *env, jclass cls) { return JVM_GetVmArguments(env); -} +} \ No newline at end of file diff --git a/src/java.base/share/native/libjava/ValueClass.c b/src/java.base/share/native/libjava/ValueClass.c new file mode 100644 index 00000000000..f0a82db9201 --- /dev/null +++ b/src/java.base/share/native/libjava/ValueClass.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. 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. + */ + +#include "jni.h" +#include "jvm.h" + +#include "jdk_internal_value_ValueClass.h" + +JNIEXPORT jarray JNICALL +Java_jdk_internal_value_ValueClass_newNullRestrictedNonAtomicArray(JNIEnv *env, jclass cls, jclass elmClass, jint len, jobject initVal) +{ + return JVM_NewNullRestrictedNonAtomicArray(env, elmClass, len, initVal); +} + +JNIEXPORT jarray JNICALL +Java_jdk_internal_value_ValueClass_newNullRestrictedAtomicArray(JNIEnv *env, jclass cls, jclass elmClass, jint len, jobject initVal) +{ + return JVM_NewNullRestrictedAtomicArray(env, elmClass, len, initVal); +} + +JNIEXPORT jarray JNICALL +Java_jdk_internal_value_ValueClass_newNullableAtomicArray(JNIEnv *env, jclass cls, jclass elmClass, jint len) +{ + return JVM_NewNullableAtomicArray(env, elmClass, len); +} + +JNIEXPORT jboolean JNICALL +Java_jdk_internal_value_ValueClass_isFlatArray(JNIEnv *env, jclass cls, jobject obj) +{ + return JVM_IsFlatArray(env, obj); +} + +JNIEXPORT jarray JNICALL +Java_jdk_internal_value_ValueClass_copyOfSpecialArray0(JNIEnv *env, jclass cls, jarray array, jint from, jint to) +{ + return JVM_CopyOfSpecialArray(env, array, from, to); +} + +JNIEXPORT jboolean JNICALL +Java_jdk_internal_value_ValueClass_isNullRestrictedArray(JNIEnv *env, jclass cls, jobject obj) +{ + return JVM_IsNullRestrictedArray(env, obj); +} + +JNIEXPORT jboolean JNICALL +Java_jdk_internal_value_ValueClass_isAtomicArray(JNIEnv *env, jclass cls, jobject obj) +{ + return JVM_IsAtomicArray(env, obj); +} diff --git a/src/java.base/share/native/libverify/check_code.c b/src/java.base/share/native/libverify/check_code.c index 7266ac8f93c..cc6e96582d0 100644 --- a/src/java.base/share/native/libverify/check_code.c +++ b/src/java.base/share/native/libverify/check_code.c @@ -2145,8 +2145,7 @@ pop_stack(context_type *context, unsigned int inumber, stack_info_type *new_stac break; if ( (GET_ITEM_TYPE(top_type) == ITEM_NewObject || (GET_ITEM_TYPE(top_type) == ITEM_InitObject)) - && ((opcode == JVM_OPC_astore) || (opcode == JVM_OPC_aload) - || (opcode == JVM_OPC_ifnull) || (opcode == JVM_OPC_ifnonnull))) + && ((opcode == JVM_OPC_astore) || (opcode == JVM_OPC_aload))) break; /* The 2nd edition VM of the specification allows field * initializations before the superclass initializer, diff --git a/src/java.compiler/share/classes/javax/lang/model/element/Modifier.java b/src/java.compiler/share/classes/javax/lang/model/element/Modifier.java index da0195b6cf2..be7f92881fc 100644 --- a/src/java.compiler/share/classes/javax/lang/model/element/Modifier.java +++ b/src/java.compiler/share/classes/javax/lang/model/element/Modifier.java @@ -120,6 +120,12 @@ public String toString() { return "non-sealed"; } }, + + /** + * The modifier {@code value} + * @since Valhalla + */ + VALUE, /** * The modifier {@code final} * diff --git a/src/jdk.compiler/share/classes/com/sun/source/util/SimpleTreeVisitor.java b/src/jdk.compiler/share/classes/com/sun/source/util/SimpleTreeVisitor.java index 77305d56b1e..583f7b08e4b 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/util/SimpleTreeVisitor.java +++ b/src/jdk.compiler/share/classes/com/sun/source/util/SimpleTreeVisitor.java @@ -236,9 +236,7 @@ public R visitWhileLoop(WhileLoopTree node, P p) { } /** - * {@inheritDoc} - * - * @implSpec This implementation calls {@code defaultAction}. + * {@inheritDoc} This implementation calls {@code defaultAction}. * * @param node {@inheritDoc} * @param p {@inheritDoc} diff --git a/src/jdk.compiler/share/classes/com/sun/source/util/TreeScanner.java b/src/jdk.compiler/share/classes/com/sun/source/util/TreeScanner.java index 29e81c206e0..82bb6c43eb6 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/util/TreeScanner.java +++ b/src/jdk.compiler/share/classes/com/sun/source/util/TreeScanner.java @@ -304,9 +304,7 @@ public R visitWhileLoop(WhileLoopTree node, P p) { } /** - * {@inheritDoc} - * - * @implSpec This implementation scans the children in left to right order. + * {@inheritDoc} This implementation scans the children in left to right order. * * @param node {@inheritDoc} * @param p {@inheritDoc} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java index 5b59e47027e..a76908c280a 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java @@ -125,13 +125,16 @@ public static EnumSet asFlagSet(long flags) { // for example. @Use({FlagTarget.CLASS}) @NoToStringValue - public static final int ACC_SUPER = 1<<5; + public static final int ACC_IDENTITY = 1<<5; @Use({FlagTarget.METHOD}) @NoToStringValue public static final int ACC_BRIDGE = 1<<6; @Use({FlagTarget.METHOD}) @NoToStringValue public static final int ACC_VARARGS = 1<<7; + @Use({FlagTarget.VARIABLE}) + @NoToStringValue + public static final int ACC_STRICT = 1<<11; @Use({FlagTarget.CLASS}) @NoToStringValue public static final int ACC_MODULE = 1<<15; @@ -151,6 +154,14 @@ public static EnumSet asFlagSet(long flags) { @Use({FlagTarget.VARIABLE}) public static final int HASINIT = 1<<18; + /** Flag is set for a class or interface whose instances have identity + * i.e. any concrete class not declared with the modifier `value' + * (a) abstract class not declared `value' + * (b) older class files with ACC_SUPER bit set + */ + @Use({FlagTarget.CLASS}) + public static final int IDENTITY_TYPE = 1<<18; + /** Class is an implicitly declared top level class. */ @Use({FlagTarget.CLASS}) @@ -160,7 +171,11 @@ public static EnumSet asFlagSet(long flags) { * that `own' an initializer block. */ @Use({FlagTarget.METHOD}) - public static final int BLOCK = 1<<20; + public static final int BLOCK = 1<<21; + + /** Marks a type as a value class */ + @Use({FlagTarget.CLASS}) + public static final int VALUE_CLASS = 1<<20; /** Flag is set for ClassSymbols that are being compiled from source. */ @@ -396,6 +411,12 @@ public static EnumSet asFlagSet(long flags) { @Use({FlagTarget.CLASS}) public static final long VALUE_BASED = 1L<<53; + /** + * Flag to indicate the given ClassSymbol is a value based. + */ + @Use({FlagTarget.CLASS}) + public static final long MIGRATED_VALUE_CLASS = 1L<<57; //ClassSymbols only + /** * Flag to indicate the given symbol has a @Deprecated annotation. */ @@ -495,6 +516,18 @@ public static EnumSet asFlagSet(long flags) { @CustomToStringValue("non-sealed") public static final long NON_SEALED = 1L<<63; // part of ExtendedStandardFlags, cannot be reused + /** + * Flag to indicate that a class has at least one strict field + */ + @Use({FlagTarget.CLASS}) + public static final long HAS_STRICT = 1L<<52; // ClassSymbols, temporary hack + + /** + * Flag to indicate that a field is strict + */ + @Use({FlagTarget.VARIABLE}) + public static final long STRICT = 1L<<19; // VarSymbols + /** * Describe modifier flags as they might appear in source code, i.e., * separated by spaces and in the order suggested by JLS 8.1.1. @@ -510,8 +543,8 @@ public static String toSource(long flags) { @NotFlag public static final int AccessFlags = PUBLIC | PROTECTED | PRIVATE, - LocalClassFlags = FINAL | ABSTRACT | STRICTFP | ENUM | SYNTHETIC, - StaticLocalFlags = LocalClassFlags | STATIC | INTERFACE, + LocalClassFlags = FINAL | ABSTRACT | STRICTFP | ENUM | SYNTHETIC | IDENTITY_TYPE, + StaticLocalClassFlags = LocalClassFlags | STATIC | INTERFACE, MemberClassFlags = LocalClassFlags | INTERFACE | AccessFlags, MemberStaticClassFlags = MemberClassFlags | STATIC, ClassFlags = LocalClassFlags | INTERFACE | PUBLIC | ANNOTATION, @@ -527,11 +560,14 @@ public static String toSource(long flags) { @NotFlag public static final long //NOTE: flags in ExtendedStandardFlags cannot be overlayed across Symbol kinds: - ExtendedStandardFlags = (long)StandardFlags | DEFAULT | SEALED | NON_SEALED, - ExtendedMemberClassFlags = (long)MemberClassFlags | SEALED | NON_SEALED, - ExtendedMemberStaticClassFlags = (long) MemberStaticClassFlags | SEALED | NON_SEALED, - ExtendedClassFlags = (long)ClassFlags | SEALED | NON_SEALED, - ModifierFlags = ((long)StandardFlags & ~INTERFACE) | DEFAULT | SEALED | NON_SEALED, + ExtendedStandardFlags = (long)StandardFlags | DEFAULT | SEALED | NON_SEALED | VALUE_CLASS, + ExtendedMemberClassFlags = (long)MemberClassFlags | SEALED | NON_SEALED | VALUE_CLASS, + ExtendedMemberStaticClassFlags = (long) MemberStaticClassFlags | SEALED | NON_SEALED | VALUE_CLASS, + ExtendedClassFlags = (long)ClassFlags | SEALED | NON_SEALED | VALUE_CLASS, + ExtendedLocalClassFlags = (long) LocalClassFlags | VALUE_CLASS, + ExtendedStaticLocalClassFlags = (long) StaticLocalClassFlags | VALUE_CLASS, + ValueFieldFlags = (long) VarFlags | STRICT | FINAL, + ModifierFlags = ((long)StandardFlags & ~INTERFACE) | DEFAULT | SEALED | NON_SEALED | VALUE_CLASS, InterfaceMethodMask = ABSTRACT | PRIVATE | STATIC | PUBLIC | STRICTFP | DEFAULT, AnnotationTypeElementMask = ABSTRACT | PUBLIC, LocalVarFlags = FINAL | PARAMETER, @@ -557,6 +593,7 @@ public static Set asModifierSet(long flags) { if (0 != (flags & NATIVE)) modifiers.add(Modifier.NATIVE); if (0 != (flags & STRICTFP)) modifiers.add(Modifier.STRICTFP); if (0 != (flags & DEFAULT)) modifiers.add(Modifier.DEFAULT); + if (0 != (flags & VALUE_CLASS)) modifiers.add(Modifier.VALUE); modifiers = Collections.unmodifiableSet(modifiers); modifierSets.put(flags, modifiers); } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java index 8eab238f82a..9191f8cca95 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java @@ -278,6 +278,12 @@ public enum LintCategory { */ INCUBATING("incubating", false), + /** + * Warn about code in identity classes that wouldn't be allowed in early + * construction due to a this dependency. + */ + INITIALIZATION("initialization"), + /** * Warn about compiler possible lossy conversions. */ @@ -293,6 +299,11 @@ public enum LintCategory { */ MODULE("module"), + /** + * Warn about issues related to migration of JDK classes. + */ + MIGRATION("migration"), + /** * Warn about issues regarding module opens. */ diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java index 1c93c37698a..1d7caf25e10 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java @@ -212,6 +212,7 @@ public boolean isEnabled() { public boolean isPreview(Feature feature) { return switch (feature) { case PRIMITIVE_PATTERNS -> true; + case VALUE_CLASSES -> true; //Note: this is a backdoor which allows to optionally treat all features as 'preview' (for testing). //When real preview features will be added, this method can be implemented to return 'true' //for those selected features, and 'false' for all the others. diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java index 063d37be4e2..c87626d04eb 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java @@ -208,6 +208,19 @@ public boolean isSupported() { return this.compareTo(MIN) >= 0; } + public static boolean isSupported(Feature feature, int majorVersion) { + Source source = null; + for (Target target : Target.values()) { + if (majorVersion == target.majorVersion) { + source = lookup(target.name); + } + } + if (source != null) { + return feature.allowedInSource(source); + } + return false; + } + public Target requiredTarget() { return switch(this) { case JDK26 -> Target.JDK1_26; @@ -272,6 +285,7 @@ public enum Feature { WARN_ON_ILLEGAL_UTF8(MIN, JDK21), UNNAMED_VARIABLES(JDK22, Fragments.FeatureUnnamedVariables, DiagKind.PLURAL), PRIMITIVE_PATTERNS(JDK23, Fragments.FeaturePrimitivePatterns, DiagKind.PLURAL), + VALUE_CLASSES(JDK22, Fragments.FeatureValueClasses, DiagKind.PLURAL), FLEXIBLE_CONSTRUCTORS(JDK25, Fragments.FeatureFlexibleConstructors, DiagKind.NORMAL), MODULE_IMPORTS(JDK25, Fragments.FeatureModuleImports, DiagKind.PLURAL), JAVA_BASE_TRANSITIVE(JDK25, Fragments.FeatureJavaBaseTransitive, DiagKind.PLURAL), diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java index e5eac0786f6..0ed72ef004f 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java @@ -30,7 +30,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; -import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; @@ -404,8 +403,20 @@ public boolean isStatic() { name != name.table.names._this; } + public boolean isStrict() { + return (flags() & STRICT) != 0; + } + + public boolean isStrictInstance() { + return (flags() & STRICT) != 0 && (flags() & STATIC) == 0; + } + + public boolean hasStrict() { + return (flags() & HAS_STRICT) != 0; + } + public boolean isInterface() { - return (flags() & INTERFACE) != 0; + return (flags_field & INTERFACE) != 0; } public boolean isAbstract() { @@ -416,6 +427,14 @@ public boolean isPrivate() { return (flags_field & Flags.AccessFlags) == PRIVATE; } + public boolean isValueClass() { + return (flags_field & VALUE_CLASS) != 0; + } + + public boolean isIdentityClass() { + return !isInterface() && (flags_field & IDENTITY_TYPE) != 0; + } + public boolean isPublic() { return (flags_field & Flags.AccessFlags) == PUBLIC; } @@ -1342,7 +1361,7 @@ public ClassSymbol(long flags, Name name, Symbol owner) { this( flags, name, - new ClassType(Type.noType, null, null), + new ClassType(Type.noType, null, null, List.nil()), owner); this.type.tsym = this; } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java index 762932fe904..6d28f2ba80d 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java @@ -226,8 +226,6 @@ public static Symtab instance(Context context) { public final Type recordType; public final Type switchBootstrapsType; public final Type constantBootstrapsType; - public final Type valueBasedType; - public final Type valueBasedInternalType; public final Type requiresIdentityType; public final Type requiresIdentityInternalType; public final Type classDescType; @@ -244,6 +242,16 @@ public static Symtab instance(Context context) { public final Type objectInputType; public final Type objectOutputType; + // valhalla + public final Type valueBasedType; + public final Type valueBasedInternalType; + public final Type migratedValueClassType; + public final Type migratedValueClassInternalType; + public final Type strictType; + /** The symbol representing the finalize method on Object */ + public final MethodSymbol objectFinalize; + public final Type numberType; + /** The symbol representing the length field of an array. */ public final VarSymbol lengthVar; @@ -542,6 +550,12 @@ public R accept(ElementVisitor v, P p) { // Enter predefined classes. All are assumed to be in the java.base module. objectType = enterClass("java.lang.Object"); + throwableType = enterClass("java.lang.Throwable"); + objectFinalize = new MethodSymbol(PROTECTED, + names.finalize, + new MethodType(List.nil(), voidType, + List.of(throwableType), methodClass), + objectType.tsym); objectMethodsType = enterClass("java.lang.runtime.ObjectMethods"); exactConversionsSupportType = enterClass("java.lang.runtime.ExactConversionsSupport"); objectsType = enterClass("java.util.Objects"); @@ -550,7 +564,6 @@ public R accept(ElementVisitor v, P p) { stringBufferType = enterClass("java.lang.StringBuffer"); stringBuilderType = enterClass("java.lang.StringBuilder"); cloneableType = enterClass("java.lang.Cloneable"); - throwableType = enterClass("java.lang.Throwable"); serializableType = enterClass("java.io.Serializable"); serializedLambdaType = enterClass("java.lang.invoke.SerializedLambda"); varHandleType = enterClass("java.lang.invoke.VarHandle"); @@ -618,6 +631,9 @@ public R accept(ElementVisitor v, P p) { constantBootstrapsType = enterClass("java.lang.invoke.ConstantBootstraps"); valueBasedType = enterClass("jdk.internal.ValueBased"); valueBasedInternalType = enterSyntheticAnnotation("jdk.internal.ValueBased+Annotation"); + strictType = enterSyntheticAnnotation("jdk.internal.vm.annotation.Strict"); + migratedValueClassType = enterClass("jdk.internal.MigratedValueClass"); + migratedValueClassInternalType = enterSyntheticAnnotation("jdk.internal.MigratedValueClass+Annotation"); requiresIdentityType = enterClass("jdk.internal.RequiresIdentity"); requiresIdentityInternalType = enterSyntheticAnnotation(names.requiresIdentityInternal); classDescType = enterClass("java.lang.constant.ClassDesc"); @@ -641,6 +657,8 @@ public R accept(ElementVisitor v, P p) { synthesizeBoxTypeIfMissing(floatType); synthesizeBoxTypeIfMissing(voidType); + numberType = enterClass("java.lang.Number"); + // Enter a synthetic class that is used to mark internal // proprietary classes in ct.sym. This class does not have a // class file. diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java index fa0220b1e1f..e6cfbac7493 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java @@ -231,6 +231,21 @@ public Type(TypeSymbol tsym, List metadata) { this.metadata = metadata; } + public boolean isValueClass() { + return false; + } + + public boolean isIdentityClass() { + return false; + } + + // Does this type need to be preloaded in the context of the referring class ?? + public boolean requiresLoadableDescriptors(Symbol referringClass) { + if (this.tsym == referringClass) + return false; // pointless + return this.isValueClass() && this.isFinal(); + } + /** * A subclass of {@link Types.TypeMapping} which applies a mapping recursively to the subterms * of a given type expression. This mapping returns the original type is no changes occurred @@ -1177,6 +1192,16 @@ public boolean isReference() { return true; } + @Override + public boolean isValueClass() { + return tsym != null && tsym.isValueClass(); + } + + @Override + public boolean isIdentityClass() { + return tsym != null && tsym.isIdentityClass(); + } + @Override public boolean isNullOrReference() { return true; @@ -2335,8 +2360,7 @@ public ErrorType(ClassSymbol c, Type originalType) { } public ErrorType(Type originalType, TypeSymbol tsym) { - super(noType, List.nil(), null); - this.tsym = tsym; + super(noType, List.nil(), tsym, List.nil()); this.originalType = (originalType == null ? noType : originalType); } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java index 0c155caa56d..33d23eec103 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java @@ -33,6 +33,7 @@ import java.util.Optional; import java.util.Set; import java.util.WeakHashMap; +import java.util.function.BiFunction; import java.util.function.BiPredicate; import java.util.function.Function; import java.util.function.Predicate; @@ -120,7 +121,12 @@ protected Types(Context context) { capturedName = names.fromString(""); messages = JavacMessages.instance(context); diags = JCDiagnostic.Factory.instance(context); - noWarnings = new Warner(null); + noWarnings = new Warner(null) { + @Override + public String toString() { + return "NO_WARNINGS"; + } + }; Options options = Options.instance(context); dumpStacktraceOnError = options.isSet("dev") || options.isSet(DOE); } @@ -2140,10 +2146,18 @@ public int dimensions(Type t) { * @return the ArrayType for the given component */ public ArrayType makeArrayType(Type t) { + return makeArrayType(t, 1); + } + + public ArrayType makeArrayType(Type t, int dimensions) { if (t.hasTag(VOID) || t.hasTag(PACKAGE)) { Assert.error("Type t must not be a VOID or PACKAGE type, " + t.toString()); } - return new ArrayType(t, syms.arrayClass); + ArrayType result = new ArrayType(t, syms.arrayClass); + for (int i = 1; i < dimensions; i++) { + result = new ArrayType(result, syms.arrayClass); + } + return result; } // @@ -3917,7 +3931,7 @@ private Type merge(Type c1, Type c2) { // There is no spec detailing how type annotations are to // be inherited. So set it to noAnnotations for now return new ClassType(class1.getEnclosingType(), merged.toList(), - class1.tsym); + class1.tsym, List.nil()); } /** @@ -4899,10 +4913,16 @@ private WildcardType makeSuperWildcard(Type bound, TypeVar formal) { public static class UniqueType { public final Type type; final Types types; + private boolean encodeTypeSig; - public UniqueType(Type type, Types types) { + public UniqueType(Type type, Types types, boolean encodeTypeSig) { this.type = type; this.types = types; + this.encodeTypeSig = encodeTypeSig; + } + + public UniqueType(Type type, Types types) { + this(type, types, true); } public int hashCode() { @@ -4914,6 +4934,10 @@ public boolean equals(Object obj) { types.isSameType(type, uniqueType.type); } + public boolean encodeTypeSig() { + return encodeTypeSig; + } + public String toString() { return type.toString(); } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Annotate.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Annotate.java index 9f56bec4cca..8d338596f48 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Annotate.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Annotate.java @@ -97,6 +97,7 @@ public static Annotate instance(Context context) { private final Symtab syms; private final TypeEnvs typeEnvs; private final Types types; + private final Preview preview; private final Attribute theUnfinishedDefaultValue; private final String sourceName; @@ -116,6 +117,7 @@ protected Annotate(Context context) { syms = Symtab.instance(context); typeEnvs = TypeEnvs.instance(context); types = Types.instance(context); + preview = Preview.instance(context); theUnfinishedDefaultValue = new Attribute.Error(syms.errType); @@ -368,6 +370,22 @@ private void annotateNow(Symbol toAnnotate, toAnnotate.flags_field |= Flags.VALUE_BASED; } + if (!c.type.isErroneous() + && toAnnotate.kind == TYP + && types.isSameType(c.type, syms.migratedValueClassType)) { + toAnnotate.flags_field |= Flags.MIGRATED_VALUE_CLASS; + } + + if (!c.type.isErroneous() + && toAnnotate.kind == VAR + && toAnnotate.owner.kind == TYP + && types.isSameType(c.type, syms.strictType)) { + preview.checkSourceLevel(pos.get(c), Feature.VALUE_CLASSES); + toAnnotate.flags_field |= Flags.STRICT; + // temporary hack to indicate that a class has at least one strict field + toAnnotate.owner.flags_field |= Flags.HAS_STRICT; + } + if (!c.type.isErroneous() && types.isSameType(c.type, syms.restrictedType)) { toAnnotate.flags_field |= Flags.RESTRICTED; 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 45ece909ad7..187f5156ffa 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 @@ -124,6 +124,7 @@ public class Attr extends JCTree.Visitor { final ArgumentAttr argumentAttr; final MatchBindingsComputer matchBindingsComputer; final AttrRecover attrRecover; + final LocalProxyVarsGen localProxyVarsGen; public static Attr instance(Context context) { Attr instance = context.get(attrKey); @@ -163,6 +164,7 @@ protected Attr(Context context) { argumentAttr = ArgumentAttr.instance(context); matchBindingsComputer = MatchBindingsComputer.instance(context); attrRecover = AttrRecover.instance(context); + localProxyVarsGen = LocalProxyVarsGen.instance(context); Options options = Options.instance(context); @@ -184,6 +186,8 @@ protected Attr(Context context) { unknownTypeExprInfo = new ResultInfo(KindSelector.VAL_TYP, Type.noType); recoveryInfo = new RecoveryInfo(deferredAttr.emptyDeferredAttrContext); initBlockType = new MethodType(List.nil(), syms.voidType, List.nil(), syms.methodClass); + allowValueClasses = (!preview.isPreview(Feature.VALUE_CLASSES) || preview.isEnabled()) && + Feature.VALUE_CLASSES.allowedInSource(source); } /** Switch: reifiable types in instanceof enabled? @@ -202,6 +206,10 @@ protected Attr(Context context) { */ private final boolean allowUnconditionalPatternsInstanceOf; + /** Are value classes allowed + */ + private final boolean allowValueClasses; + /** * Switch: warn about use of variable before declaration? * RFE: 6425594 @@ -295,9 +303,7 @@ boolean isAssignableAsBlankFinal(VarSymbol v, Env env) { void checkAssignable(DiagnosticPosition pos, VarSymbol v, JCTree base, Env env) { if (v.name == names._this) { log.error(pos, Errors.CantAssignValToThis); - return; - } - if ((v.flags() & FINAL) != 0 && + } else if ((v.flags() & FINAL) != 0 && ((v.flags() & HASINIT) != 0 || !((base == null || @@ -308,23 +314,6 @@ void checkAssignable(DiagnosticPosition pos, VarSymbol v, JCTree base, Env prologueCode = new ListBuffer<>(); + for (JCTree stat : tree.body.stats) { + prologueCode.add(stat); + /* gather all the stats in the body until a `super` or `this` constructor invocation is found, + * including the constructor invocation, that way we don't need to worry in the visitor below if + * if we are dealing or not with prologue code + */ + if (stat instanceof JCExpressionStatement expStmt && + expStmt.expr instanceof JCMethodInvocation mi && + TreeInfo.isConstructorCall(mi)) { + thisInvocation = TreeInfo.name(mi.meth) == names._this; + if (!addedSuperInIdentityClass || !allowValueClasses) { + break; + } + } + } + if (!prologueCode.isEmpty()) { + CtorPrologueVisitor ctorPrologueVisitor = new CtorPrologueVisitor(localEnv, + addedSuperInIdentityClass && allowValueClasses ? + PrologueVisitorMode.WARNINGS_ONLY : + thisInvocation ? + PrologueVisitorMode.THIS_CONSTRUCTOR : + PrologueVisitorMode.SUPER_CONSTRUCTOR); + ctorPrologueVisitor.scan(prologueCode.toList()); + } + } } localEnv.info.scope.leave(); @@ -1250,6 +1273,327 @@ public void visitMethodDef(JCMethodDecl tree) { } } + enum PrologueVisitorMode { + WARNINGS_ONLY, + SUPER_CONSTRUCTOR, + THIS_CONSTRUCTOR + } + + class CtorPrologueVisitor extends TreeScanner { + Env localEnv; + PrologueVisitorMode mode; + + CtorPrologueVisitor(Env localEnv, PrologueVisitorMode mode) { + this.localEnv = localEnv; + currentClassSym = localEnv.enclClass.sym; + this.mode = mode; + } + + boolean insideLambdaOrClassDef = false; + + @Override + public void visitLambda(JCLambda lambda) { + boolean previousInsideLambdaOrClassDef = insideLambdaOrClassDef; + try { + insideLambdaOrClassDef = true; + super.visitLambda(lambda); + } finally { + insideLambdaOrClassDef = previousInsideLambdaOrClassDef; + } + } + + ClassSymbol currentClassSym; + + @Override + public void visitClassDef(JCClassDecl classDecl) { + boolean previousInsideLambdaOrClassDef = insideLambdaOrClassDef; + ClassSymbol previousClassSym = currentClassSym; + try { + insideLambdaOrClassDef = true; + currentClassSym = classDecl.sym; + super.visitClassDef(classDecl); + } finally { + insideLambdaOrClassDef = previousInsideLambdaOrClassDef; + currentClassSym = previousClassSym; + } + } + + private void reportPrologueError(JCTree tree, Symbol sym) { + reportPrologueError(tree, sym, false); + } + + private void reportPrologueError(JCTree tree, Symbol sym, boolean hasInit) { + preview.checkSourceLevel(tree, Feature.FLEXIBLE_CONSTRUCTORS); + if (mode != PrologueVisitorMode.WARNINGS_ONLY) { + if (hasInit) { + log.error(tree, Errors.CantAssignInitializedBeforeCtorCalled(sym)); + } else { + log.error(tree, Errors.CantRefBeforeCtorCalled(sym)); + } + } else if (allowValueClasses) { + // issue lint warning + log.warning(tree, LintWarnings.WouldNotBeAllowedInPrologue(sym)); + } + } + + @Override + public void visitApply(JCMethodInvocation tree) { + super.visitApply(tree); + Name name = TreeInfo.name(tree.meth); + boolean isConstructorCall = name == names._this || name == names._super; + Symbol msym = TreeInfo.symbolFor(tree.meth); + // is this an instance method call or an illegal constructor invocation like: `this.super()`? + if (msym != null && // for erroneous invocations msym can be null, ignore those + (!isConstructorCall || + isConstructorCall && tree.meth.hasTag(SELECT))) { + if (isEarlyReference(localEnv, tree.meth, msym)) + reportPrologueError(tree.meth, msym); + } + } + + @Override + public void visitIdent(JCIdent tree) { + analyzeSymbol(tree); + } + + boolean isIndexed = false; + + @Override + public void visitIndexed(JCArrayAccess tree) { + boolean previousIsIndexed = isIndexed; + try { + isIndexed = true; + scan(tree.indexed); + } finally { + isIndexed = previousIsIndexed; + } + scan(tree.index); + if (mode == PrologueVisitorMode.SUPER_CONSTRUCTOR && isInstanceField(tree.indexed)) { + localProxyVarsGen.addFieldReadInPrologue(localEnv.enclMethod, TreeInfo.symbolFor(tree.indexed)); + } + } + + @Override + public void visitSelect(JCFieldAccess tree) { + SelectScanner ss = new SelectScanner(); + ss.scan(tree); + if (ss.scanLater == null) { + analyzeSymbol(tree); + } else { + boolean prevLhs = isInLHS; + try { + isInLHS = false; + scan(ss.scanLater); + } finally { + isInLHS = prevLhs; + } + } + if (mode == PrologueVisitorMode.SUPER_CONSTRUCTOR) { + for (JCTree subtree : ss.selectorTrees) { + if (isInstanceField(subtree)) { + // we need to add a proxy for this one + localProxyVarsGen.addFieldReadInPrologue(localEnv.enclMethod, TreeInfo.symbolFor(subtree)); + } + } + } + } + + boolean isInstanceField(JCTree tree) { + Symbol sym = TreeInfo.symbolFor(tree); + return (sym != null && + !sym.isStatic() && + sym.kind == VAR && + sym.owner.kind == TYP && + sym.name != names._this && + sym.name != names._super && + isEarlyReference(localEnv, tree, sym)); + } + + @Override + public void visitNewClass(JCNewClass tree) { + super.visitNewClass(tree); + checkNewClassAndMethRefs(tree, tree.type); + } + + @Override + public void visitReference(JCMemberReference tree) { + super.visitReference(tree); + if (tree.getMode() == JCMemberReference.ReferenceMode.NEW) { + checkNewClassAndMethRefs(tree, tree.expr.type); + } + } + + void checkNewClassAndMethRefs(JCTree tree, Type t) { + if (t.tsym.isEnclosedBy(localEnv.enclClass.sym) && + !t.tsym.isStatic() && + !t.tsym.isDirectlyOrIndirectlyLocal()) { + reportPrologueError(tree, t.getEnclosingType().tsym); + } + } + + /* if a symbol is in the LHS of an assignment expression we won't consider it as a candidate + * for a proxy local variable later on + */ + boolean isInLHS = false; + + @Override + public void visitAssign(JCAssign tree) { + boolean previousIsInLHS = isInLHS; + try { + isInLHS = true; + scan(tree.lhs); + } finally { + isInLHS = previousIsInLHS; + } + scan(tree.rhs); + } + + @Override + public void visitMethodDef(JCMethodDecl tree) { + // ignore any declarative part, mainly to avoid scanning receiver parameters + scan(tree.body); + } + + void analyzeSymbol(JCTree tree) { + Symbol sym = TreeInfo.symbolFor(tree); + // make sure that there is a symbol and it is not static + if (sym == null || sym.isStatic()) { + return; + } + if (isInLHS && !insideLambdaOrClassDef) { + // Check instance field assignments that appear in constructor prologues + if (isEarlyReference(localEnv, tree, sym)) { + // Field may not be inherited from a superclass + if (sym.owner != localEnv.enclClass.sym) { + reportPrologueError(tree, sym); + return; + } + // Field may not have an initializer + if ((sym.flags() & HASINIT) != 0) { + if (!localEnv.enclClass.sym.isValueClass() || !sym.type.hasTag(ARRAY) || !isIndexed) { + reportPrologueError(tree, sym, true); + } + return; + } + // cant reference an instance field before a this constructor + if (allowValueClasses && mode == PrologueVisitorMode.THIS_CONSTRUCTOR) { + reportPrologueError(tree, sym); + return; + } + } + return; + } + tree = TreeInfo.skipParens(tree); + if (sym.kind == VAR && sym.owner.kind == TYP) { + if (sym.name == names._this || sym.name == names._super) { + // are we seeing something like `this` or `CurrentClass.this` or `SuperClass.super::foo`? + if (TreeInfo.isExplicitThisReference( + types, + (ClassType)localEnv.enclClass.sym.type, + tree)) { + reportPrologueError(tree, sym); + } + } else if (sym.kind == VAR && sym.owner.kind == TYP) { // now fields only + if (sym.owner != localEnv.enclClass.sym) { + if (localEnv.enclClass.sym.isSubClass(sym.owner, types) && + sym.isInheritedIn(localEnv.enclClass.sym, types)) { + /* if we are dealing with a field that doesn't belong to the current class, but the + * field is inherited, this is an error. Unless, the super class is also an outer + * class and the field's qualifier refers to the outer class + */ + if (tree.hasTag(IDENT) || + TreeInfo.isExplicitThisReference( + types, + (ClassType)localEnv.enclClass.sym.type, + ((JCFieldAccess)tree).selected)) { + reportPrologueError(tree, sym); + } + } + } else if (isEarlyReference(localEnv, tree, sym)) { + /* now this is a `proper` instance field of the current class + * references to fields of identity classes which happen to have initializers are + * not allowed in the prologue + */ + if (insideLambdaOrClassDef || + (!localEnv.enclClass.sym.isValueClass() && (sym.flags_field & HASINIT) != 0)) + reportPrologueError(tree, sym); + // we will need to generate a proxy for this field later on + if (!isInLHS) { + if (!allowValueClasses) { + reportPrologueError(tree, sym); + } else { + if (mode == PrologueVisitorMode.THIS_CONSTRUCTOR) { + reportPrologueError(tree, sym); + } else if (mode == PrologueVisitorMode.SUPER_CONSTRUCTOR) { + localProxyVarsGen.addFieldReadInPrologue(localEnv.enclMethod, sym); + } + /* we do nothing in warnings only mode, as in that mode we are simulating what + * the compiler would do in case the constructor code would be in the prologue + * phase + */ + } + } + } + } + } + } + + /** + * Determine if the symbol appearance constitutes an early reference to the current class. + * + *

    + * This means the symbol is an instance field, or method, of the current class and it appears + * in an early initialization context of it (i.e., one of its constructor prologues). + * + * @param env The current environment + * @param tree the AST referencing the variable + * @param sym The symbol + */ + private boolean isEarlyReference(Env env, JCTree tree, Symbol sym) { + if ((sym.flags() & STATIC) == 0 && + (sym.kind == VAR || sym.kind == MTH) && + sym.isMemberOf(env.enclClass.sym, types)) { + // Allow "Foo.this.x" when "Foo" is (also) an outer class, as this refers to the outer instance + if (tree instanceof JCFieldAccess fa) { + return TreeInfo.isExplicitThisReference(types, (ClassType)env.enclClass.type, fa.selected); + } else if (currentClassSym != env.enclClass.sym) { + /* so we are inside a class, CI, in the prologue of an outer class, CO, and the symbol being + * analyzed has no qualifier. So if the symbol is a member of CI the reference is allowed, + * otherwise it is not. + * It could be that the reference to CI's member happens inside CI's own prologue, but that + * will be checked separately, when CI's prologue is analyzed. + */ + return !sym.isMemberOf(currentClassSym, types); + } + return true; + } + return false; + } + + /* scanner for a select expression, anything that is not a select or identifier + * will be stored for further analysis + */ + class SelectScanner extends DeferredAttr.FilterScanner { + JCTree scanLater; + java.util.List selectorTrees = new ArrayList<>(); + + SelectScanner() { + super(Set.of(IDENT, SELECT, PARENS)); + } + + @Override + public void visitSelect(JCFieldAccess tree) { + super.visitSelect(tree); + selectorTrees.add(tree.selected); + } + + @Override + void skip(JCTree tree) { + scanLater = tree; + } + } + } + public void visitVarDef(JCVariableDecl tree) { // Local variables have not been entered yet, so we need to do it now: if (env.info.scope.owner.kind == MTH || env.info.scope.owner.kind == VAR) { @@ -1310,10 +1654,25 @@ public void visitVarDef(JCVariableDecl tree) { // declaration position to maximal possible value, effectively // marking the variable as undefined. initEnv.info.enclVar = v; - attribExpr(tree.init, initEnv, v.type); - if (tree.isImplicitlyTyped()) { - //fixup local variable type - v.type = chk.checkLocalVarType(tree, tree.init.type, tree.name); + boolean previousCtorPrologue = initEnv.info.ctorPrologue; + try { + if (v.owner.kind == TYP && !v.isStatic() && v.isStrict()) { + // strict instance initializer in a value class + initEnv.info.ctorPrologue = true; + } + attribExpr(tree.init, initEnv, v.type); + if (tree.isImplicitlyTyped()) { + //fixup local variable type + v.type = chk.checkLocalVarType(tree, tree.init.type, tree.name); + } + if (allowValueClasses && v.owner.kind == TYP && !v.isStatic()) { + // strict field initializers are inlined in constructor's prologues + CtorPrologueVisitor ctorPrologueVisitor = new CtorPrologueVisitor(initEnv, + !v.isStrict() ? PrologueVisitorMode.WARNINGS_ONLY : PrologueVisitorMode.SUPER_CONSTRUCTOR); + ctorPrologueVisitor.scan(tree.init); + } + } finally { + initEnv.info.ctorPrologue = previousCtorPrologue; } } if (tree.isImplicitlyTyped()) { @@ -1434,7 +1793,11 @@ public void visitBlock(JCBlock tree) { final Env localEnv = env.dup(tree, env.info.dup(env.info.scope.dupUnshared(fakeOwner))); - if ((tree.flags & STATIC) != 0) localEnv.info.staticLevel++; + if ((tree.flags & STATIC) != 0) { + localEnv.info.staticLevel++; + } else { + localEnv.info.instanceInitializerBlock = true; + } // Attribute all type annotations in the block annotate.queueScanTreeAndTypeAnnotate(tree, localEnv, localEnv.info.scope.owner); annotate.flush(); @@ -1947,8 +2310,8 @@ private Symbol enumConstant(JCTree tree, Type enumType) { } public void visitSynchronized(JCSynchronized tree) { - chk.checkRefType(tree.pos(), attribExpr(tree.lock, env)); - if (tree.lock.type != null && tree.lock.type.isValueBased()) { + boolean identityType = chk.checkIdentityType(tree.pos(), attribExpr(tree.lock, env)); + if (identityType && tree.lock.type != null && tree.lock.type.isValueBased()) { log.warning(tree.pos(), LintWarnings.AttemptToSynchronizeOnInstanceOfValueBasedClass); } attribStat(tree.body, env); @@ -4400,6 +4763,7 @@ public void visitSelect(JCFieldAccess tree) { // Attribute the qualifier expression, and determine its symbol (if any). Type site = attribTree(tree.selected, env, new ResultInfo(skind, Type.noType)); + Assert.check(site == tree.selected.type); if (!pkind().contains(KindSelector.TYP_PCK)) site = capture(site); // Capture field access @@ -5502,7 +5866,7 @@ void attribClass(ClassSymbol c) throws CompletionFailure { log.error(TreeInfo.diagnosticPositionFor(c, env.tree), Errors.NonSealedWithNoSealedSupertype(c)); } } - } else { + } else if ((c.flags_field & Flags.COMPOUND) == 0) { if (c.isDirectlyOrIndirectlyLocal() && !c.isEnum()) { log.error(TreeInfo.diagnosticPositionFor(c, env.tree), Errors.LocalClassesCantExtendSealed(c.isAnonymous() ? Fragments.Anonymous : Fragments.Local)); } @@ -5539,6 +5903,11 @@ void attribClass(ClassSymbol c) throws CompletionFailure { env.info.isSerializable = true; } + if (c.isValueClass()) { + Assert.check(env.tree.hasTag(CLASSDEF)); + chk.checkConstraintsOfValueClass((JCClassDecl) env.tree, c); + } + attribClassBody(env, c); chk.checkDeprecatedAnnotation(env.tree.pos(), c); @@ -5681,7 +6050,7 @@ private void attribClassBody(Env env, ClassSymbol c) { if (env.info.lint.isEnabled(LintCategory.SERIAL) && rs.isSerializable(c.type) && !c.isAnonymous()) { - chk.checkSerialStructure(tree, c); + chk.checkSerialStructure(env, tree, c); } // Correctly organize the positions of the type annotations typeAnnotations.organizeTypeAnnotationsBodies(tree); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/AttrContext.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/AttrContext.java index 092b624aee5..6f6da2829a7 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/AttrContext.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/AttrContext.java @@ -130,6 +130,8 @@ public class AttrContext { */ JCTree preferredTreeForDiagnostics; + boolean instanceInitializerBlock = false; + /** Duplicate this context, replacing scope field and copying all others. */ AttrContext dup(WriteableScope scope) { @@ -153,6 +155,7 @@ AttrContext dup(WriteableScope scope) { info.preferredTreeForDiagnostics = preferredTreeForDiagnostics; info.visitingServiceImplementation = visitingServiceImplementation; info.allowProtectedAccess = allowProtectedAccess; + info.instanceInitializerBlock = instanceInitializerBlock; info.isPermitsClause = isPermitsClause; return info; } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java index 3d9eff107da..2db9da3d117 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java @@ -167,6 +167,8 @@ protected Check(Context context) { allowModules = Feature.MODULES.allowedInSource(source); allowRecords = Feature.RECORDS.allowedInSource(source); allowSealed = Feature.SEALED_CLASSES.allowedInSource(source); + allowValueClasses = (!preview.isPreview(Feature.VALUE_CLASSES) || preview.isEnabled()) && + Feature.VALUE_CLASSES.allowedInSource(source); } /** Character for synthetic names @@ -190,6 +192,10 @@ protected Check(Context context) { */ private final boolean allowSealed; + /** Are value classes allowed + */ + private final boolean allowValueClasses; + /** Whether to force suppression of deprecation and preview warnings. * This happens when attributing import statements for JDK 9+. * @see Feature#DEPRECATION_ON_IMPORT @@ -667,6 +673,31 @@ private Object asTypeParam(Type t) { : t; } + void checkConstraintsOfValueClass(JCClassDecl tree, ClassSymbol c) { + DiagnosticPosition pos = tree.pos(); + for (Type st : types.closure(c.type)) { + if (st == null || st.tsym == null || st.tsym.kind == ERR) + continue; + if (st.tsym == syms.objectType.tsym || st.tsym == syms.recordType.tsym || st.isInterface()) + continue; + if (!st.tsym.isAbstract()) { + if (c != st.tsym) { + log.error(pos, Errors.ConcreteSupertypeForValueClass(c, st)); + } + continue; + } + // dealing with an abstract value or value super class below. + for (Symbol s : st.tsym.members().getSymbols(NON_RECURSIVE)) { + if (s.kind == MTH) { + if ((s.flags() & (SYNCHRONIZED | STATIC)) == SYNCHRONIZED) { + log.error(pos, Errors.SuperClassMethodCannotBeSynchronized(s, c, st)); + } + break; + } + } + } + } + /** Check that type is a valid qualifier for a constructor reference expression */ Type checkConstructorRefType(DiagnosticPosition pos, Type t) { @@ -724,6 +755,32 @@ Type checkRefType(DiagnosticPosition pos, Type t) { t); } + /** Check that type is an identity type, i.e. not a value type. + * When not discernible statically, give it the benefit of doubt + * and defer to runtime. + * + * @param pos Position to be used for error reporting. + * @param t The type to be checked. + */ + boolean checkIdentityType(DiagnosticPosition pos, Type t) { + if (t.hasTag(TYPEVAR)) { + t = types.skipTypeVars(t, false); + } + if (t.isIntersection()) { + IntersectionClassType ict = (IntersectionClassType)t; + boolean result = true; + for (Type component : ict.getExplicitComponents()) { + result &= checkIdentityType(pos, component); + } + return result; + } + if (t.isPrimitive() || (t.isValueClass() && !t.tsym.isAbstract())) { + typeTagError(pos, diags.fragment(Fragments.TypeReqIdentity), t); + return false; + } + return true; + } + /** Check that each type is a reference type, i.e. a class, interface or array type * or a type variable. * @param trees Original trees, used for error reporting. @@ -1115,8 +1172,17 @@ else if (sym.owner.kind != TYP) mask = LocalVarFlags; else if ((sym.owner.flags_field & INTERFACE) != 0) mask = implicit = InterfaceVarFlags; - else - mask = VarFlags; + else { + boolean isInstanceField = (flags & STATIC) == 0; + boolean isInstanceFieldOfValueClass = isInstanceField && sym.owner.type.isValueClass(); + boolean isRecordField = isInstanceField && (sym.owner.flags_field & RECORD) != 0; + if (allowValueClasses && (isInstanceFieldOfValueClass || isRecordField)) { + implicit |= FINAL | STRICT; + mask = ValueFieldFlags; + } else { + mask = VarFlags; + } + } break; case MTH: if (sym.name == names.init) { @@ -1142,9 +1208,12 @@ else if ((sym.owner.flags_field & INTERFACE) != 0) mask = implicit = InterfaceMethodFlags; } } else if ((sym.owner.flags_field & RECORD) != 0) { - mask = RecordMethodFlags; + mask = ((sym.owner.flags_field & VALUE_CLASS) != 0 && (flags & Flags.STATIC) == 0) ? + RecordMethodFlags & ~SYNCHRONIZED : RecordMethodFlags; } else { - mask = MethodFlags; + // value objects do not have an associated monitor/lock + mask = ((sym.owner.flags_field & VALUE_CLASS) != 0 && (flags & Flags.STATIC) == 0) ? + MethodFlags & ~SYNCHRONIZED : MethodFlags; } if ((flags & STRICTFP) != 0) { log.warning(tree.pos(), LintWarnings.Strictfp); @@ -1161,7 +1230,7 @@ else if ((sym.owner.flags_field & INTERFACE) != 0) ((flags & RECORD) != 0 || (flags & ENUM) != 0 || (flags & INTERFACE) != 0); boolean staticOrImplicitlyStatic = (flags & STATIC) != 0 || implicitlyStatic; // local statics are allowed only if records are allowed too - mask = staticOrImplicitlyStatic && allowRecords && (flags & ANNOTATION) == 0 ? StaticLocalFlags : LocalClassFlags; + mask = staticOrImplicitlyStatic && allowRecords && (flags & ANNOTATION) == 0 ? ExtendedStaticLocalClassFlags : ExtendedLocalClassFlags; implicit = implicitlyStatic ? STATIC : implicit; } else if (sym.owner.kind == TYP) { // statics in inner classes are allowed only if records are allowed too @@ -1177,12 +1246,20 @@ else if ((sym.owner.flags_field & INTERFACE) != 0) } else { mask = ExtendedClassFlags; } + if ((flags & (VALUE_CLASS | SEALED | ABSTRACT)) == (VALUE_CLASS | SEALED) || + (flags & (VALUE_CLASS | NON_SEALED | ABSTRACT)) == (VALUE_CLASS | NON_SEALED)) { + log.error(pos, Errors.NonAbstractValueClassCantBeSealedOrNonSealed); + } // Interfaces are always ABSTRACT if ((flags & INTERFACE) != 0) implicit |= ABSTRACT; + if ((flags & (INTERFACE | VALUE_CLASS)) == 0) { + implicit |= IDENTITY_TYPE; + } + if ((flags & ENUM) != 0) { - // enums can't be declared abstract, final, sealed or non-sealed - mask &= ~(ABSTRACT | FINAL | SEALED | NON_SEALED); + // enums can't be declared abstract, final, sealed or non-sealed or value + mask &= ~(ABSTRACT | FINAL | SEALED | NON_SEALED | VALUE_CLASS); implicit |= implicitEnumFinalFlag(tree); } if ((flags & RECORD) != 0) { @@ -1195,6 +1272,11 @@ else if ((sym.owner.flags_field & INTERFACE) != 0) } // Imply STRICTFP if owner has STRICTFP set. implicit |= sym.owner.flags_field & STRICTFP; + + // concrete value classes are implicitly final + if ((flags & (ABSTRACT | INTERFACE | VALUE_CLASS)) == VALUE_CLASS) { + implicit |= FINAL; + } break; default: throw new AssertionError(); @@ -1209,8 +1291,7 @@ else if ((sym.owner.flags_field & INTERFACE) != 0) log.error(pos, Errors.ModNotAllowedHere(asFlagSet(illegal))); } - } - else if ((sym.kind == TYP || + } else if ((sym.kind == TYP || // ISSUE: Disallowing abstract&private is no longer appropriate // in the presence of inner classes. Should it be deleted here? checkDisjoint(pos, flags, @@ -1233,7 +1314,8 @@ else if ((sym.kind == TYP || PRIVATE, PUBLIC | PROTECTED) && - checkDisjoint(pos, flags, + // we are using `implicit` here as instance fields of value classes are implicitly final + checkDisjoint(pos, flags | implicit, FINAL, VOLATILE) && @@ -1249,7 +1331,13 @@ && checkDisjoint(pos, flags, FINAL | NON_SEALED) && checkDisjoint(pos, flags, SEALED, - ANNOTATION)) { + ANNOTATION) + && checkDisjoint(pos, flags, + VALUE_CLASS, + ANNOTATION) + && checkDisjoint(pos, flags, + VALUE_CLASS, + INTERFACE) ) { // skip } return flags & (mask | ~ExtendedStandardFlags) | implicit; @@ -2046,6 +2134,11 @@ void checkOverride(Env env, JCMethodDecl tree, MethodSymbol m) { return; } } + if (allowValueClasses && origin.isValueClass() && names.finalize.equals(m.name)) { + if (m.overrides(syms.objectFinalize, origin, types, false)) { + log.warning(tree.pos(), Warnings.ValueFinalize); + } + } if (allowRecords && origin.isRecord()) { // let's find out if this is a user defined accessor in which case the @Override annotation is acceptable Optional recordComponent = origin.getRecordComponents().stream() @@ -2481,6 +2574,18 @@ void checkCompatibleSupertypes(DiagnosticPosition pos, Type c) { return; } checkCompatibleConcretes(pos, c); + + Type identitySuper = null; + for (Type t : types.closure(c)) { + if (t != c) { + if (t.isIdentityClass() && (t.tsym.flags() & VALUE_BASED) == 0) + identitySuper = t; + if (c.isValueClass() && identitySuper != null && identitySuper.tsym != syms.objectType.tsym) { // Object is special + log.error(pos, Errors.ValueTypeHasIdentitySuperType(c, identitySuper)); + break; + } + } + } } /** Check that all non-override equivalent methods accessible from 'site' @@ -4814,8 +4919,8 @@ boolean isExternalizable(Type t) { /** * Check structure of serialization declarations. */ - public void checkSerialStructure(JCClassDecl tree, ClassSymbol c) { - (new SerialTypeVisitor()).visit(c, tree); + public void checkSerialStructure(Env env, JCClassDecl tree, ClassSymbol c) { + (new SerialTypeVisitor(env)).visit(c, tree); } /** @@ -4846,8 +4951,10 @@ public void checkSerialStructure(JCClassDecl tree, ClassSymbol c) { * public void readExternal(ObjectInput) throws IOException */ private class SerialTypeVisitor extends ElementKindVisitor14 { - SerialTypeVisitor() { + Env env; + SerialTypeVisitor(Env env) { this.lint = Check.this.lint; + this.env = env; } private static final Set serialMethodNames = @@ -4907,6 +5014,7 @@ public Void visitTypeAsClass(TypeElement e, // Check declarations of serialization-related methods and // fields + final boolean[] hasWriteReplace = {false}; for(Symbol el : c.getEnclosedElements()) { runUnderLint(el, p, (enclosed, tree) -> { String name = null; @@ -4981,7 +5089,7 @@ public Void visitTypeAsClass(TypeElement e, if (serialMethodNames.contains(name)) { switch (name) { case "writeObject" -> checkWriteObject(tree, e, method); - case "writeReplace" -> checkWriteReplace(tree,e, method); + case "writeReplace" -> {hasWriteReplace[0] = true; hasAppropriateWriteReplace(tree, method, true);} case "readObject" -> checkReadObject(tree,e, method); case "readObjectNoData" -> checkReadObjectNoData(tree, e, method); case "readResolve" -> checkReadResolve(tree, e, method); @@ -4992,7 +5100,26 @@ public Void visitTypeAsClass(TypeElement e, } }); } - + if (!hasWriteReplace[0] && + (c.isValueClass() || hasAbstractValueSuperClass(c, Set.of(syms.numberType.tsym))) && + !c.isAbstract() && !c.isRecord() && + types.unboxedType(c.type) == Type.noType) { + // we need to check if the class is inheriting an appropriate writeReplace method + MethodSymbol ms = null; + Log.DiagnosticHandler discardHandler = log.new DiscardDiagnosticHandler(); + try { + ms = rs.resolveInternalMethod(env.tree, env, c.type, names.writeReplace, List.nil(), List.nil()); + } catch (FatalError fe) { + // ignore no method was found + } finally { + log.popDiagnosticHandler(discardHandler); + } + if (ms == null || !hasAppropriateWriteReplace(p, ms, false)) { + log.warning(p.pos(), + c.isValueClass() ? LintWarnings.SerializableValueClassWithoutWriteReplace1 : + LintWarnings.SerializableValueClassWithoutWriteReplace2); + } + } return null; } @@ -5000,6 +5127,22 @@ boolean canBeSerialized(Type type) { return type.isPrimitive() || rs.isSerializable(type); } + private boolean hasAbstractValueSuperClass(Symbol c, Set excluding) { + while (c.getKind() == ElementKind.CLASS) { + Type sup = ((ClassSymbol)c).getSuperclass(); + if (!sup.hasTag(CLASS) || sup.isErroneous() || + sup.tsym == syms.objectType.tsym) { + return false; + } + // if it is a value super class it has to be abstract + if (sup.isValueClass() && !excluding.contains(sup.tsym)) { + return true; + } + c = sup.tsym; + } + return false; + } + /** * Check that Externalizable class needs a public no-arg * constructor. @@ -5123,22 +5266,22 @@ private void checkWriteObject(JCClassDecl tree, Element e, MethodSymbol method) // private void writeObject(ObjectOutputStream stream) throws IOException checkPrivateNonStaticMethod(tree, method); - checkReturnType(tree, e, method, syms.voidType); + isExpectedReturnType(tree, method, syms.voidType, true); checkOneArg(tree, e, method, syms.objectOutputStreamType); - checkExceptions(tree, e, method, syms.ioExceptionType); + hasExpectedExceptions(tree, method, true, syms.ioExceptionType); checkExternalizable(tree, e, method); } - private void checkWriteReplace(JCClassDecl tree, Element e, MethodSymbol method) { + private boolean hasAppropriateWriteReplace(JCClassDecl tree, MethodSymbol method, boolean warn) { // ANY-ACCESS-MODIFIER Object writeReplace() throws // ObjectStreamException // Excluding abstract, could have a more complicated // rule based on abstract-ness of the class - checkConcreteInstanceMethod(tree, e, method); - checkReturnType(tree, e, method, syms.objectType); - checkNoArgs(tree, e, method); - checkExceptions(tree, e, method, syms.objectStreamExceptionType); + return isConcreteInstanceMethod(tree, method, warn) && + isExpectedReturnType(tree, method, syms.objectType, warn) && + hasNoArgs(tree, method, warn) && + hasExpectedExceptions(tree, method, warn, syms.objectStreamExceptionType); } private void checkReadObject(JCClassDecl tree, Element e, MethodSymbol method) { @@ -5149,18 +5292,18 @@ private void checkReadObject(JCClassDecl tree, Element e, MethodSymbol method) { // private void readObject(ObjectInputStream stream) // throws IOException, ClassNotFoundException checkPrivateNonStaticMethod(tree, method); - checkReturnType(tree, e, method, syms.voidType); + isExpectedReturnType(tree, method, syms.voidType, true); checkOneArg(tree, e, method, syms.objectInputStreamType); - checkExceptions(tree, e, method, syms.ioExceptionType, syms.classNotFoundExceptionType); + hasExpectedExceptions(tree, method, true, syms.ioExceptionType, syms.classNotFoundExceptionType); checkExternalizable(tree, e, method); } private void checkReadObjectNoData(JCClassDecl tree, Element e, MethodSymbol method) { // private void readObjectNoData() throws ObjectStreamException checkPrivateNonStaticMethod(tree, method); - checkReturnType(tree, e, method, syms.voidType); - checkNoArgs(tree, e, method); - checkExceptions(tree, e, method, syms.objectStreamExceptionType); + isExpectedReturnType(tree, method, syms.voidType, true); + hasNoArgs(tree, method, true); + hasExpectedExceptions(tree, method, true, syms.objectStreamExceptionType); checkExternalizable(tree, e, method); } @@ -5170,10 +5313,10 @@ private void checkReadResolve(JCClassDecl tree, Element e, MethodSymbol method) // Excluding abstract, could have a more complicated // rule based on abstract-ness of the class - checkConcreteInstanceMethod(tree, e, method); - checkReturnType(tree,e, method, syms.objectType); - checkNoArgs(tree, e, method); - checkExceptions(tree, e, method, syms.objectStreamExceptionType); + isConcreteInstanceMethod(tree, method, true); + isExpectedReturnType(tree, method, syms.objectType, true); + hasNoArgs(tree, method, true); + hasExpectedExceptions(tree, method, true, syms.objectStreamExceptionType); } private void checkWriteExternalRecord(JCClassDecl tree, Element e, MethodSymbol method, boolean isExtern) { @@ -5423,7 +5566,7 @@ public Void visitTypeAsRecord(TypeElement e, case METHOD -> { var method = (MethodSymbol)enclosed; switch(name) { - case "writeReplace" -> checkWriteReplace(tree, e, method); + case "writeReplace" -> hasAppropriateWriteReplace(tree, method, true); case "readResolve" -> checkReadResolve(tree, e, method); case "writeExternal" -> checkWriteExternalRecord(tree, e, method, isExtern); @@ -5441,20 +5584,24 @@ public Void visitTypeAsRecord(TypeElement e, return null; } - void checkConcreteInstanceMethod(JCClassDecl tree, - Element enclosing, - MethodSymbol method) { + boolean isConcreteInstanceMethod(JCClassDecl tree, + MethodSymbol method, + boolean warn) { if ((method.flags() & (STATIC | ABSTRACT)) != 0) { + if (warn) { log.warning( TreeInfo.diagnosticPositionFor(method, tree), LintWarnings.SerialConcreteInstanceMethod(method.getSimpleName())); + } + return false; } + return true; } - private void checkReturnType(JCClassDecl tree, - Element enclosing, - MethodSymbol method, - Type expectedReturnType) { + private boolean isExpectedReturnType(JCClassDecl tree, + MethodSymbol method, + Type expectedReturnType, + boolean warn) { // Note: there may be complications checking writeReplace // and readResolve since they return Object and could, in // principle, have covariant overrides and any synthetic @@ -5462,11 +5609,15 @@ private void checkReturnType(JCClassDecl tree, // checking. Type rtype = method.getReturnType(); if (!types.isSameType(expectedReturnType, rtype)) { - log.warning( - TreeInfo.diagnosticPositionFor(method, tree), + if (warn) { + log.warning( + TreeInfo.diagnosticPositionFor(method, tree), LintWarnings.SerialMethodUnexpectedReturnType(method.getSimpleName(), rtype, expectedReturnType)); + } + return false; } + return true; } private void checkOneArg(JCClassDecl tree, @@ -5504,13 +5655,17 @@ private boolean hasExactlyOneArgWithType(JCClassDecl tree, } - private void checkNoArgs(JCClassDecl tree, Element enclosing, MethodSymbol method) { + boolean hasNoArgs(JCClassDecl tree, MethodSymbol method, boolean warn) { var parameters = method.getParameters(); if (!parameters.isEmpty()) { - log.warning( - TreeInfo.diagnosticPositionFor(parameters.get(0), tree), + if (warn) { + log.warning( + TreeInfo.diagnosticPositionFor(parameters.get(0), tree), LintWarnings.SerialMethodNoArgs(method.getSimpleName())); + } + return false; } + return true; } private void checkExternalizable(JCClassDecl tree, Element enclosing, MethodSymbol method) { @@ -5523,10 +5678,10 @@ private void checkExternalizable(JCClassDecl tree, Element enclosing, MethodSymb return; } - private void checkExceptions(JCClassDecl tree, - Element enclosing, - MethodSymbol method, - Type... declaredExceptions) { + private boolean hasExpectedExceptions(JCClassDecl tree, + MethodSymbol method, + boolean warn, + Type... declaredExceptions) { for (Type thrownType: method.getThrownTypes()) { // For each exception in the throws clause of the // method, if not an Error and not a RuntimeException, @@ -5545,14 +5700,17 @@ private void checkExceptions(JCClassDecl tree, } } if (!declared) { - log.warning( - TreeInfo.diagnosticPositionFor(method, tree), + if (warn) { + log.warning( + TreeInfo.diagnosticPositionFor(method, tree), LintWarnings.SerialMethodUnexpectedException(method.getSimpleName(), thrownType)); + } + return false; } } } - return; + return true; } private Void runUnderLint(E symbol, JCClassDecl p, BiConsumer task) { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/CompileStates.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/CompileStates.java index 4896ecbe728..7cc506440ed 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/CompileStates.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/CompileStates.java @@ -63,7 +63,8 @@ public enum CompileState { TRANSPATTERNS(8), LOWER(9), UNLAMBDA(10), - GENERATE(11); + STRICT_FIELDS_PROXIES(11), + GENERATE(12); CompileState(int value) { this.value = value; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java index 3bbd007c66a..85775b84f93 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java @@ -27,6 +27,7 @@ package com.sun.tools.javac.comp; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Map.Entry; import java.util.HashMap; @@ -215,6 +216,8 @@ public class Flow { private final JCDiagnostic.Factory diags; private Env attrEnv; private final Infer infer; + private final UnsetFieldsInfo unsetFieldsInfo; + private final boolean allowValueClasses; public static Flow instance(Context context) { Flow instance = context.get(flowKey); @@ -339,7 +342,11 @@ protected Flow(Context context) { infer = Infer.instance(context); rs = Resolve.instance(context); diags = JCDiagnostic.Factory.instance(context); + unsetFieldsInfo = UnsetFieldsInfo.instance(context); + Preview preview = Preview.instance(context); Source source = Source.instance(context); + allowValueClasses = (!preview.isPreview(Source.Feature.VALUE_CLASSES) || preview.isEnabled()) && + Source.Feature.VALUE_CLASSES.allowedInSource(source); } /** @@ -476,8 +483,18 @@ protected void scanSyntheticBreak(TreeMaker make, JCTree swtch) { } } - // Do something with all static or non-static field initializers and initialization blocks. + // Do something with static or non-static field initializers and initialization blocks. protected void forEachInitializer(JCClassDecl classDef, boolean isStatic, Consumer handler) { + forEachInitializer(classDef, isStatic, false, handler); + } + + /* Do something with static or non-static field initializers and initialization blocks. + * the `earlyOnly` argument will determine if we will deal or not with early variable instance + * initializers we want to process only those before a super() invocation and ignore them after + * it. + */ + protected void forEachInitializer(JCClassDecl classDef, boolean isStatic, boolean earlyOnly, + Consumer handler) { if (classDef == initScanClass) // avoid infinite loops return; JCClassDecl initScanClassPrev = initScanClass; @@ -495,8 +512,18 @@ protected void forEachInitializer(JCClassDecl classDef, boolean isStatic, Consum * code */ boolean isDefStatic = ((TreeInfo.flags(def) | (TreeInfo.symbolFor(def) == null ? 0 : TreeInfo.symbolFor(def).flags_field)) & STATIC) != 0; - if (!def.hasTag(METHODDEF) && (isDefStatic == isStatic)) - handler.accept(def); + if (!def.hasTag(METHODDEF) && (isDefStatic == isStatic)) { + if (def instanceof JCVariableDecl varDecl) { + boolean isEarly = varDecl.init != null && + varDecl.sym.isStrict() && + !varDecl.sym.isStatic(); + if (isEarly == earlyOnly) { + handler.accept(def); + } + } else if (!earlyOnly) { + handler.accept(def); + } + } } } finally { initScanClass = initScanClassPrev; @@ -2141,6 +2168,7 @@ public AssignAnalyzer() { } private boolean isConstructor; + private boolean isCompactOrGeneratedRecordConstructor; @Override protected void markDead() { @@ -2157,12 +2185,13 @@ protected boolean trackable(VarSymbol sym) { return sym.pos >= startPos && ((sym.owner.kind == MTH || sym.owner.kind == VAR || - isFinalUninitializedField(sym))); + isFinalOrStrictUninitializedField(sym))); } - boolean isFinalUninitializedField(VarSymbol sym) { + boolean isFinalOrStrictUninitializedField(VarSymbol sym) { return sym.owner.kind == TYP && - ((sym.flags() & (FINAL | HASINIT | PARAMETER)) == FINAL && + (((sym.flags() & (FINAL | HASINIT | PARAMETER)) == FINAL || + (sym.flags() & (STRICT | HASINIT | PARAMETER)) == STRICT) && classDef.sym.isEnclosedBy((ClassSymbol)sym.owner)); } @@ -2234,11 +2263,21 @@ void uninit(VarSymbol sym) { * record an initialization of the variable. */ void letInit(JCTree tree) { + letInit(tree, (JCAssign) null); + } + + void letInit(JCTree tree, JCAssign assign) { tree = TreeInfo.skipParens(tree); if (tree.hasTag(IDENT) || tree.hasTag(SELECT)) { Symbol sym = TreeInfo.symbol(tree); if (sym.kind == VAR) { letInit(tree.pos(), (VarSymbol)sym); + if (isConstructor && sym.isStrict()) { + /* we are initializing a strict field inside of a constructor, we now need to find which fields + * haven't been initialized yet + */ + unsetFieldsInfo.addUnsetFieldsInfo(classDef.sym, assign != null ? assign : tree, findUninitStrictFields()); + } } } } @@ -2254,7 +2293,7 @@ void checkInit(DiagnosticPosition pos, VarSymbol sym, Error errkey) { trackable(sym) && !inits.isMember(sym.adr) && (sym.flags_field & CLASH) == 0) { - log.error(pos, errkey); + log.error(pos, errkey); inits.incl(sym.adr); } } @@ -2443,8 +2482,11 @@ public void visitMethodDef(JCMethodDecl tree) { Assert.check(pendingExits.isEmpty()); boolean isConstructorPrev = isConstructor; + boolean isCompactOrGeneratedRecordConstructorPrev = isCompactOrGeneratedRecordConstructor; try { isConstructor = TreeInfo.isConstructor(tree); + isCompactOrGeneratedRecordConstructor = isConstructor && ((tree.sym.flags() & Flags.COMPACT_RECORD_CONSTRUCTOR) != 0 || + (tree.sym.flags() & (GENERATEDCONSTR | RECORD)) == (GENERATEDCONSTR | RECORD)); // We only track field initialization inside constructors if (!isConstructor) { @@ -2461,12 +2503,17 @@ public void visitMethodDef(JCMethodDecl tree) { */ initParam(def); } + if (isConstructor) { + Set unsetFields = findUninitStrictFields(); + if (unsetFields != null && !unsetFields.isEmpty()) { + unsetFieldsInfo.addUnsetFieldsInfo(classDef.sym, tree.body, unsetFields); + } + } + // else we are in an instance initializer block; // leave caught unchanged. scan(tree.body); - boolean isCompactOrGeneratedRecordConstructor = (tree.sym.flags() & Flags.COMPACT_RECORD_CONSTRUCTOR) != 0 || - (tree.sym.flags() & (GENERATEDCONSTR | RECORD)) == (GENERATEDCONSTR | RECORD); if (isConstructor) { boolean isSynthesized = (tree.sym.flags() & GENERATEDCONSTR) != 0; @@ -2510,7 +2557,19 @@ public void visitMethodDef(JCMethodDecl tree) { firstadr = firstadrPrev; returnadr = returnadrPrev; isConstructor = isConstructorPrev; + isCompactOrGeneratedRecordConstructor = isCompactOrGeneratedRecordConstructorPrev; + } + } + + Set findUninitStrictFields() { + Set unsetFields = new LinkedHashSet<>(); + for (int i = uninits.nextBit(0); i >= 0; i = uninits.nextBit(i + 1)) { + JCVariableDecl variableDecl = vardecls[i]; + if (variableDecl.sym.isStrict()) { + unsetFields.add(variableDecl.sym); + } } + return unsetFields; } private void clearPendingExits(boolean inMethod) { @@ -2969,6 +3028,14 @@ public void visitThrow(JCThrow tree) { } public void visitApply(JCMethodInvocation tree) { + Name name = TreeInfo.name(tree.meth); + // let's process early initializers + if (name == names._super) { + forEachInitializer(classDef, false, true, def -> { + scan(def); + clearPendingExits(false); + }); + } scanExpr(tree.meth); scanExprs(tree.args); @@ -2976,8 +3043,16 @@ public void visitApply(JCMethodInvocation tree) { if (isConstructor) { // If super(): at this point all initialization blocks will execute - Name name = TreeInfo.name(tree.meth); + if (name == names._super) { + // strict fields should have been initialized at this point + for (int i = firstadr; i < nextadr; i++) { + JCVariableDecl vardecl = vardecls[i]; + VarSymbol var = vardecl.sym; + if (allowValueClasses && (var.owner == classDef.sym && !var.isStatic() && (var.isStrict() || ((var.flags_field & RECORD) != 0)) && !isCompactOrGeneratedRecordConstructor)) { + checkInit(TreeInfo.diagEndPos(tree), var, Errors.StrictFieldNotHaveBeenInitializedBeforeSuper(var)); + } + } forEachInitializer(classDef, false, def -> { scan(def); clearPendingExits(false); @@ -2988,7 +3063,7 @@ public void visitApply(JCMethodInvocation tree) { else if (name == names._this) { for (int address = firstadr; address < nextadr; address++) { VarSymbol sym = vardecls[address].sym; - if (isFinalUninitializedField(sym) && !sym.isStatic()) + if (isFinalOrStrictUninitializedField(sym) && !sym.isStatic()) letInit(tree.pos(), sym); } } @@ -3062,7 +3137,7 @@ public void visitAssign(JCAssign tree) { if (!TreeInfo.isIdentOrThisDotIdent(tree.lhs)) scanExpr(tree.lhs); scanExpr(tree.rhs); - letInit(tree.lhs); + letInit(tree.lhs, tree); } // check fields accessed through this. are definitely diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LocalProxyVarsGen.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LocalProxyVarsGen.java new file mode 100644 index 00000000000..951150629b7 --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LocalProxyVarsGen.java @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. 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 com.sun.tools.javac.comp; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Symbol.ClassSymbol; +import com.sun.tools.javac.code.Symbol.VarSymbol; +import com.sun.tools.javac.code.Symtab; +import com.sun.tools.javac.code.Types; +import com.sun.tools.javac.tree.JCTree.JCAssign; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.tree.TreeTranslator; +import com.sun.tools.javac.util.Assert; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; +import com.sun.tools.javac.util.Names; + +import static com.sun.tools.javac.code.Flags.FINAL; +import static com.sun.tools.javac.code.Flags.SYNTHETIC; +import static com.sun.tools.javac.code.TypeTag.BOT; +import static com.sun.tools.javac.tree.JCTree.Tag.VARDEF; + +import com.sun.tools.javac.jvm.Target; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.TreeInfo; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Options; + +/** This phase adds local variable proxies for fields that are read during the + * early construction phase (prologue) + * + * Assignments to the affected instance fields will be rewritten as assignments to a + * local proxy variable. Fields will be assigned to with its corresponding local variable + * proxy just before the super invocation and after its arguments, if any, have been evaluated. + * + *

    This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class LocalProxyVarsGen extends TreeTranslator { + + protected static final Context.Key valueInitializersKey = new Context.Key<>(); + + public static LocalProxyVarsGen instance(Context context) { + LocalProxyVarsGen instance = context.get(valueInitializersKey); + if (instance == null) + instance = new LocalProxyVarsGen(context); + return instance; + } + + private final Types types; + private final Names names; + private final Symtab syms; + private final Target target; + private TreeMaker make; + private final UnsetFieldsInfo unsetFieldsInfo; + private ClassSymbol currentClass = null; + private java.util.List instanceFields; + private Map> fieldsReadInPrologue = new HashMap<>(); + + private final boolean noLocalProxyVars; + + @SuppressWarnings("this-escape") + protected LocalProxyVarsGen(Context context) { + context.put(valueInitializersKey, this); + make = TreeMaker.instance(context); + types = Types.instance(context); + names = Names.instance(context); + syms = Symtab.instance(context); + target = Target.instance(context); + unsetFieldsInfo = UnsetFieldsInfo.instance(context); + Options options = Options.instance(context); + noLocalProxyVars = options.isSet("noLocalProxyVars"); + } + + public void addFieldReadInPrologue(JCMethodDecl constructor, Symbol sym) { + Assert.checkNonNull(sym, "parameter 'sym' is null"); + Set fieldSet = fieldsReadInPrologue.getOrDefault(constructor, new HashSet<>()); + fieldSet.add(sym); + fieldsReadInPrologue.put(constructor, fieldSet); + } + + public JCTree translateTopLevelClass(JCTree cdef, TreeMaker make) { + if (!noLocalProxyVars) { + try { + this.make = make; + return translate(cdef); + } finally { + // note that recursive invocations of this method fail hard + this.make = null; + } + } else { + return cdef; + } + } + + @Override + public void visitClassDef(JCClassDecl tree) { + ClassSymbol prevCurrentClass = currentClass; + java.util.List prevInstanceFields = instanceFields; + try { + currentClass = tree.sym; + instanceFields = tree.defs.stream() + .filter(t -> t.hasTag(VARDEF)) + .map(t -> (JCVariableDecl)t) + .filter(vd -> !vd.sym.isStatic()) + .collect(List.collector()); + super.visitClassDef(tree); + } finally { + currentClass = prevCurrentClass; + instanceFields = prevInstanceFields; + } + } + + public void visitMethodDef(JCMethodDecl tree) { + if (fieldsReadInPrologue.get(tree) != null) { + Set fieldSet = fieldsReadInPrologue.get(tree); + java.util.List fieldsRead = new ArrayList<>(); + for (JCVariableDecl field : instanceFields) { + if (fieldSet.contains(field.sym)) { + fieldsRead.add(field); + } + } + addLocalProxiesFor(tree, fieldsRead); + fieldsReadInPrologue.remove(tree); + } + super.visitMethodDef(tree); + } + + void addLocalProxiesFor(JCMethodDecl constructor, java.util.List fields) { + ListBuffer localDeclarations = new ListBuffer<>(); + Map fieldToLocalMap = new LinkedHashMap<>(); + + for (JCVariableDecl fieldDecl : fields) { + long flags = SYNTHETIC; + VarSymbol proxy = new VarSymbol(flags, newLocalName(fieldDecl.name.toString()), fieldDecl.sym.erasure(types), constructor.sym); + fieldToLocalMap.put(fieldDecl.sym, proxy); + JCVariableDecl localDecl; + JCExpression initializer = fieldDecl.init; + if (initializer == null && !fieldDecl.sym.isFinal() && !fieldDecl.sym.isStrict()) { + initializer = fieldDecl.vartype.type.isPrimitive() ? + make.at(constructor.pos).Literal(0) : + make.at(constructor.pos).Literal(BOT, null).setType(syms.botType); + } + localDecl = make.at(constructor.pos).VarDef(proxy, initializer); + localDecl.vartype = fieldDecl.vartype; + localDeclarations = localDeclarations.append(localDecl); + } + + FieldRewriter fieldRewriter = new FieldRewriter(constructor, fieldToLocalMap); + ListBuffer newBody = new ListBuffer<>(); + for (JCStatement st : constructor.body.stats) { + newBody = newBody.append(fieldRewriter.translate(st)); + } + localDeclarations.addAll(newBody); + ListBuffer assigmentsBeforeSuper = new ListBuffer<>(); + for (Symbol vsym : fieldToLocalMap.keySet()) { + Symbol local = fieldToLocalMap.get(vsym); + assigmentsBeforeSuper.append(make.at(constructor.pos()).Assignment(vsym, make.at(constructor.pos()).Ident(local))); + } + constructor.body.stats = localDeclarations.toList(); + JCTree.JCMethodInvocation constructorCall = TreeInfo.findConstructorCall(constructor); + if (constructorCall.args.isEmpty()) { + // this is just a super invocation with no arguments we can set the fields just before the invocation + // and let Gen do the rest + TreeInfo.mapSuperCalls(constructor.body, supercall -> make.Block(0, assigmentsBeforeSuper.toList().append(supercall))); + } else { + // we need to generate fresh local variables to catch the values of the arguments, then + // assign the proxy locals to the fields and finally invoke the super with the fresh local variables + int argPosition = 0; + ListBuffer superArgsProxies = new ListBuffer<>(); + for (JCExpression arg : constructorCall.args) { + long flags = SYNTHETIC | FINAL; + VarSymbol proxyForArgSym = new VarSymbol(flags, newLocalName("" + argPosition), types.erasure(arg.type), constructor.sym); + JCVariableDecl proxyForArgDecl = make.at(constructor.pos).VarDef(proxyForArgSym, arg); + superArgsProxies = superArgsProxies.append(proxyForArgDecl); + argPosition++; + } + List superArgsProxiesList = superArgsProxies.toList(); + ListBuffer newArgs = new ListBuffer<>(); + for (JCStatement argProxy : superArgsProxies) { + newArgs.add(make.at(argProxy.pos).Ident((JCVariableDecl) argProxy)); + } + constructorCall.args = newArgs.toList(); + TreeInfo.mapSuperCalls(constructor.body, + supercall -> make.Block(0, superArgsProxiesList.appendList(assigmentsBeforeSuper.toList()).append(supercall))); + } + } + + private Name newLocalName(String name) { + return names.fromString("local" + target.syntheticNameChar() + name); + } + + class FieldRewriter extends TreeTranslator { + JCMethodDecl md; + Map fieldToLocalMap; + boolean ctorPrologue = true; + + public FieldRewriter(JCMethodDecl md, Map fieldToLocalMap) { + this.md = md; + this.fieldToLocalMap = fieldToLocalMap; + } + + @Override + public void visitIdent(JCTree.JCIdent tree) { + if (ctorPrologue && fieldToLocalMap.get(tree.sym) != null) { + result = make.at(md).Ident(fieldToLocalMap.get(tree.sym)); + } else { + result = tree; + } + } + + @Override + public void visitSelect(JCTree.JCFieldAccess tree) { + super.visitSelect(tree); + if (ctorPrologue && fieldToLocalMap.get(tree.sym) != null) { + result = make.at(md).Ident(fieldToLocalMap.get(tree.sym)); + } else { + result = tree; + } + } + + @Override + public void visitAssign(JCAssign tree) { + JCExpression previousLHS = tree.lhs; + super.visitAssign(tree); + if (ctorPrologue && previousLHS != tree.lhs) { + unsetFieldsInfo.removeUnsetFieldInfo(currentClass, tree); + } + } + + @Override + public void visitApply(JCTree.JCMethodInvocation tree) { + super.visitApply(tree); + if (TreeInfo.isConstructorCall(tree)) { + ctorPrologue = false; + } + } + } +} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java index aee7f0afe39..9b6b8fe2707 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java @@ -108,6 +108,7 @@ public static Lower instance(Context context) { private final boolean nullCheckOuterThis; private final boolean useMatchException; private final HashMap typePairToName; + private final boolean allowValueClasses; private int variableIndex = 0; @SuppressWarnings("this-escape") @@ -143,6 +144,8 @@ protected Lower(Context context) { useMatchException = Feature.PATTERN_SWITCH.allowedInSource(source) && (preview.isEnabled() || !preview.isPreview(Feature.PATTERN_SWITCH)); typePairToName = TypePairs.initialize(syms); + this.allowValueClasses = (!preview.isPreview(Feature.VALUE_CLASSES) || preview.isEnabled()) && + Feature.VALUE_CLASSES.allowedInSource(source); } /** The currently enclosing class. @@ -199,6 +202,10 @@ protected Lower(Context context) { */ JCTree outermostMemberDef; + /** A hash table mapping local classes to a set of outer this fields + */ + public Map> initializerOuterThis = new WeakHashMap<>(); + /** A navigator class for assembling a mapping from local class symbols * to class definition trees. * There is only one case; all other cases simply traverse down the tree. @@ -777,7 +784,8 @@ private void checkAccessConstructorTags() { if (isTranslatedClassAvailable(c)) continue; // Create class definition tree. - JCClassDecl cdec = makeEmptyClass(STATIC | SYNTHETIC, + // IDENTITY_TYPE will be interpreted as ACC_SUPER for older class files so we are fine + JCClassDecl cdec = makeEmptyClass(STATIC | SYNTHETIC | IDENTITY_TYPE, c.outermostClass(), c.flatname, false); swapAccessConstructorTag(c, cdec.sym); translated.append(cdec); @@ -1249,7 +1257,8 @@ ClassSymbol accessConstructorTag() { i); ClassSymbol ctag = chk.getCompiled(topModle, flatname); if (ctag == null) - ctag = makeEmptyClass(STATIC | SYNTHETIC, topClass).sym; + // IDENTITY_TYPE will be interpreted as ACC_SUPER for older class files so we are fine + ctag = makeEmptyClass(STATIC | SYNTHETIC | IDENTITY_TYPE, topClass).sym; else if (!ctag.isAnonymous()) continue; // keep a record of all tags, to verify that all are generated as required @@ -1405,9 +1414,10 @@ Name proxyName(Name name, int index) { } /** Proxy definitions for all free variables in given list, in reverse order. - * @param pos The source code position of the definition. - * @param freevars The free variables. - * @param owner The class in which the definitions go. + * @param pos The source code position of the definition. + * @param freevars The free variables. + * @param owner The class in which the definitions go. + * @param additionalFlags Any additional flags */ List freevarDefs(int pos, List freevars, Symbol owner) { return freevarDefs(pos, freevars, owner, LOCAL_CAPTURE_FIELD); @@ -1498,7 +1508,7 @@ JCVariableDecl outerThisDef(int pos, MethodSymbol owner) { * @param owner The class in which the definition goes. */ JCVariableDecl outerThisDef(int pos, ClassSymbol owner) { - VarSymbol outerThis = makeOuterThisVarSymbol(owner, FINAL | SYNTHETIC); + VarSymbol outerThis = makeOuterThisVarSymbol(owner, FINAL | SYNTHETIC | (allowValueClasses && owner.isValueClass() ? STRICT : 0)); return makeOuterThisVarDecl(pos, outerThis); } @@ -1838,7 +1848,8 @@ private ClassSymbol outerCacheClass() { if (sym.kind == TYP && sym.name == names.empty && (sym.flags() & INTERFACE) == 0) return (ClassSymbol) sym; - return makeEmptyClass(STATIC | SYNTHETIC, clazz).sym; + // IDENTITY_TYPE will be interpreted as ACC_SUPER for older class files so we are fine + return makeEmptyClass(STATIC | SYNTHETIC | IDENTITY_TYPE, clazz).sym; } /** Create an attributed tree of the form left.name(). */ @@ -1890,7 +1901,8 @@ private JCExpression classOfType(Type type, DiagnosticPosition pos) { private ClassSymbol assertionsDisabledClass() { if (assertionsDisabledClassCache != null) return assertionsDisabledClassCache; - assertionsDisabledClassCache = makeEmptyClass(STATIC | SYNTHETIC, outermostClassDef.sym).sym; + // IDENTITY_TYPE will be interpreted as ACC_SUPER for older class files so we are fine + assertionsDisabledClassCache = makeEmptyClass(STATIC | SYNTHETIC | IDENTITY_TYPE, outermostClassDef.sym).sym; return assertionsDisabledClassCache; } @@ -2178,7 +2190,7 @@ public void visitClassDef(JCClassDecl tree) { // If this is a local class, define proxies for all its free variables. List fvdefs = freevarDefs( - tree.pos, freevars(currentClass), currentClass); + tree.pos, freevars(currentClass), currentClass, allowValueClasses && currentClass.isValueClass() ? STRICT : LOCAL_CAPTURE_FIELD); // Recursively translate superclass, interfaces. tree.extending = translate(tree.extending); @@ -2759,19 +2771,25 @@ private void visitMethodDefInternal(JCMethodDecl tree) { if (sym.kind == Kinds.Kind.VAR && ((sym.flags() & RECORD) != 0)) fields.append((VarSymbol) sym); } + ListBuffer initializers = new ListBuffer<>(); for (VarSymbol field: fields) { if ((field.flags_field & Flags.UNINITIALIZED_FIELD) != 0) { VarSymbol param = tree.params.stream().filter(p -> p.name == field.name).findFirst().get().sym; make.at(tree.pos); - tree.body.stats = tree.body.stats.append( - make.Exec( - make.Assign( - make.Select(make.This(field.owner.erasure(types)), field), - make.Ident(param)).setType(field.erasure(types)))); - // we don't need the flag at the field anymore + initializers.add(make.Exec( + make.Assign( + make.Select(make.This(field.owner.erasure(types)), field), + make.Ident(param)).setType(field.erasure(types)))); field.flags_field &= ~Flags.UNINITIALIZED_FIELD; } } + if (initializers.nonEmpty()) { + if (allowValueClasses && (tree.sym.owner.isValueClass() || tree.sym.owner.hasStrict() || ((ClassSymbol)tree.sym.owner).isRecord())) { + TreeInfo.mapSuperCalls(tree.body, supercall -> make.Block(0, initializers.toList().append(supercall))); + } else { + tree.body.stats = tree.body.stats.appendList(initializers); + } + } } result = tree; } @@ -2995,6 +3013,17 @@ public void visitNewClass(JCNewClass tree) { } else { // nested class thisArg = makeOwnerThis(tree.pos(), c, false); + if (currentMethodSym != null && + ((currentMethodSym.flags_field & (STATIC | BLOCK)) == BLOCK) && + currentMethodSym.owner.isValueClass()) { + // instance initializer in a value class + Set outerThisSet = initializerOuterThis.get(currentClass); + if (outerThisSet == null) { + outerThisSet = new HashSet<>(); + } + outerThisSet.add(thisArg); + initializerOuterThis.put(currentClass, outerThisSet); + } } tree.args = tree.args.prepend(thisArg); } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/MemberEnter.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/MemberEnter.java index d63ba1677d6..c1cb8b5d4ad 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/MemberEnter.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/MemberEnter.java @@ -217,7 +217,7 @@ public void visitMethodDef(JCMethodDecl tree) { localEnv.info.scope.leave(); if (chk.checkUnique(tree.pos(), m, enclScope)) { - enclScope.enter(m); + enclScope.enter(m); } annotate.annotateLater(tree.mods.annotations, localEnv, m); @@ -289,6 +289,8 @@ public void visitVarDef(JCVariableDecl tree) { needsLazyConstValue(tree.init)) { Env initEnv = getInitEnv(tree, env); initEnv.info.enclVar = v; + initEnv = initEnv(tree, initEnv); + initEnv.info.ctorPrologue = (v.owner.kind == TYP && v.owner.isValueClass() && !v.isStatic()); v.setLazyConstValue(initEnv(tree, initEnv), env, attr, tree); } } 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 eea766f57c1..da2c0514f70 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 @@ -115,6 +115,7 @@ public class Resolve { final EnumSet verboseResolutionMode; final boolean dumpMethodReferenceSearchResults; final boolean dumpStacktraceOnError; + private final LocalProxyVarsGen localProxyVarsGen; WriteableScope polymorphicSignatureScope; @@ -154,6 +155,7 @@ protected Resolve(Context context) { allowRecords = Feature.RECORDS.allowedInSource(source); dumpMethodReferenceSearchResults = options.isSet("debug.dumpMethodReferenceSearchResults"); dumpStacktraceOnError = options.isSet("dev") || options.isSet(DOE); + localProxyVarsGen = LocalProxyVarsGen.instance(context); } /** error symbols, which are returned when resolution fails @@ -421,45 +423,50 @@ public boolean isAccessible(Env env, Type site, Symbol sym, boolean return true; } - switch ((short)(sym.flags() & AccessFlags)) { - case PRIVATE: - return - (env.enclClass.sym == sym.owner // fast special case - || - env.enclClass.sym.outermostClass() == - sym.owner.outermostClass() - || - privateMemberInPermitsClauseIfAllowed(env, sym)) - && - sym.isInheritedIn(site.tsym, types); - case 0: - return - (env.toplevel.packge == sym.owner.owner // fast special case - || - env.toplevel.packge == sym.packge()) - && - isAccessible(env, site, checkInner) - && - sym.isInheritedIn(site.tsym, types) - && - notOverriddenIn(site, sym); - case PROTECTED: - return - (env.toplevel.packge == sym.owner.owner // fast special case - || - env.toplevel.packge == sym.packge() - || - isProtectedAccessible(sym, env.enclClass.sym, site) - || - // OK to select instance method or field from 'super' or type name - // (but type names should be disallowed elsewhere!) - env.info.selectSuper && (sym.flags() & STATIC) == 0 && sym.kind != TYP) - && - isAccessible(env, site, checkInner) - && - notOverriddenIn(site, sym); - default: // this case includes erroneous combinations as well - return isAccessible(env, site, checkInner) && notOverriddenIn(site, sym); + ClassSymbol enclosingCsym = env.enclClass.sym; + try { + switch ((short)(sym.flags() & AccessFlags)) { + case PRIVATE: + return + (env.enclClass.sym == sym.owner // fast special case + || + env.enclClass.sym.outermostClass() == + sym.owner.outermostClass() + || + privateMemberInPermitsClauseIfAllowed(env, sym)) + && + sym.isInheritedIn(site.tsym, types); + case 0: + return + (env.toplevel.packge == sym.owner.owner // fast special case + || + env.toplevel.packge == sym.packge()) + && + isAccessible(env, site, checkInner) + && + sym.isInheritedIn(site.tsym, types) + && + notOverriddenIn(site, sym); + case PROTECTED: + return + (env.toplevel.packge == sym.owner.owner // fast special case + || + env.toplevel.packge == sym.packge() + || + isProtectedAccessible(sym, env.enclClass.sym, site) + || + // OK to select instance method or field from 'super' or type name + // (but type names should be disallowed elsewhere!) + env.info.selectSuper && (sym.flags() & STATIC) == 0 && sym.kind != TYP) + && + isAccessible(env, site, checkInner) + && + notOverriddenIn(site, sym); + default: // this case includes erroneous combinations as well + return isAccessible(env, site, checkInner) && notOverriddenIn(site, sym); + } + } finally { + env.enclClass.sym = enclosingCsym; } } @@ -1530,8 +1537,6 @@ Symbol findVar(DiagnosticPosition pos, Env env, Name name) { (sym.flags() & STATIC) == 0) { if (staticOnly) return new StaticError(sym); - if (env1.info.ctorPrologue && !isAllowedEarlyReference(pos, env1, (VarSymbol)sym)) - return new RefBeforeCtorCalledError(sym); } return sym; } else { @@ -2039,8 +2044,6 @@ Symbol findFun(Env env, Name name, (sym.flags() & STATIC) == 0) { if (staticOnly) return new StaticError(sym); - if (env1.info.ctorPrologue && env1 == env) - return new RefBeforeCtorCalledError(sym); } return sym; } else { @@ -3811,9 +3814,6 @@ Symbol findSelfContaining(DiagnosticPosition pos, if (staticOnly) { // current class is not an inner class, stop search return new StaticError(sym); - } else if (env1.info.ctorPrologue && !isAllowedEarlyReference(pos, env1, (VarSymbol)sym)) { - // early construction context, stop search - return new RefBeforeCtorCalledError(sym); } else { // found it return sym; @@ -3873,10 +3873,6 @@ Symbol resolveSelf(DiagnosticPosition pos, if (sym != null) { if (staticOnly) sym = new StaticError(sym); - else if (env1.info.ctorPrologue && - !isReceiverParameter(env, tree) && - !isAllowedEarlyReference(pos, env1, (VarSymbol)sym)) - sym = new RefBeforeCtorCalledError(sym); return accessBase(sym, pos, env.enclClass.sym.type, name, true); } @@ -3890,8 +3886,6 @@ else if (env1.info.ctorPrologue && //this might be a default super call if one of the superinterfaces is 'c' for (Type t : pruneInterfaces(env.enclClass.type)) { if (t.tsym == c) { - if (env.info.ctorPrologue) - log.error(pos, Errors.CantRefBeforeCtorCalled(name)); env.info.defaultSuperCallSite = t; return new VarSymbol(0, names._super, types.asSuper(env.enclClass.type, c), env.enclClass.sym); @@ -3927,101 +3921,6 @@ private List pruneInterfaces(Type t) { } return result.toList(); } - private boolean isReceiverParameter(Env env, JCFieldAccess tree) { - if (env.tree.getTag() != METHODDEF) - return false; - JCMethodDecl method = (JCMethodDecl)env.tree; - return method.recvparam != null && tree == method.recvparam.nameexpr; - } - - /** - * Determine if an early instance field reference may appear in a constructor prologue. - * - *

    - * This is only allowed when: - * - The field is being assigned a value (i.e., written but not read) - * - The field is not inherited from a superclass - * - The assignment is not within a lambda, because that would require - * capturing 'this' which is not allowed prior to super(). - * - *

    - * Note, this method doesn't catch all such scenarios, because this method - * is invoked for symbol "x" only for "x = 42" but not for "this.x = 42". - * 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(DiagnosticPosition pos, Env env, VarSymbol v) { - - // Check assumptions - Assert.check(env.info.ctorPrologue); - Assert.check((v.flags_field & STATIC) == 0); - - // The symbol must appear in the LHS of an assignment statement - if (!(env.tree instanceof JCAssign assign)) - return false; - - // The assignment statement must not be within a lambda - if (env.info.isLambda) - return false; - - // Get the symbol's qualifier, if any - JCExpression lhs = TreeInfo.skipParens(assign.lhs); - 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; - } - - /** - * Determine if the variable appearance constitutes an early reference to the current class. - * - *

    - * This means the variable is an instance field of the current class and it appears - * in an early initialization context of it (i.e., one of its constructor prologues). - * - *

    - * Such a reference is only allowed for assignments to non-initialized fields that are - * not inherited from a superclass, though that is not enforced by this method. - * - * @param env The current environment - * @param base Variable qualifier, if any, otherwise null - * @param v The variable - */ - public boolean isEarlyReference(Env env, JCTree base, VarSymbol v) { - if (env.info.ctorPrologue && - (v.flags() & STATIC) == 0 && - v.isMemberOf(env.enclClass.sym, types)) { - - // Allow "Foo.this.x" when "Foo" is (also) an outer class, as this refers to the outer instance - if (base != null) { - return TreeInfo.isExplicitThisReference(types, (ClassType)env.enclClass.type, base); - } - - // It's an early reference to an instance field member of the current instance - return true; - } - return false; - } /* *************************************************************************** * ResolveError classes, indicating error situations when accessing symbols @@ -4331,7 +4230,6 @@ JCDiagnostic getDiagnostic(JCDiagnostic.DiagnosticType dkind, rewriter, kindName(ws), ws.name == names.init ? ws.owner.name : ws.name, - kindName(ws.owner), ws.owner.type, c.snd); default: @@ -5232,10 +5130,6 @@ boolean isApplicable() { DeferredAttr.AttrMode attrMode() { return attrMode; } - - boolean internal() { - return internalResolution; - } } MethodResolutionContext currentResolutionContext = null; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TypeEnter.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TypeEnter.java index 8648b929a04..e559a003240 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TypeEnter.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TypeEnter.java @@ -51,6 +51,7 @@ import static com.sun.tools.javac.code.Flags.*; import static com.sun.tools.javac.code.Flags.ANNOTATION; +import static com.sun.tools.javac.code.Flags.SYNCHRONIZED; import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE; import static com.sun.tools.javac.code.Kinds.Kind.*; import static com.sun.tools.javac.code.TypeTag.CLASS; @@ -1187,6 +1188,10 @@ void finishClass(JCClassDecl tree, JCTree defaultConstructor, Env e Assert.check(tree.sym.isCompleted()); tree.sym.setAnnotationTypeMetadata(new AnnotationTypeMetadata(tree.sym, annotate.annotationTypeSourceCompleter())); } + + if ((tree.sym.flags() & (INTERFACE | VALUE_CLASS)) == 0) { + tree.sym.flags_field |= IDENTITY_TYPE; + } } private void addAccessor(JCVariableDecl tree, Env env) { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/UnsetFieldsInfo.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/UnsetFieldsInfo.java new file mode 100644 index 00000000000..fea2b4f9c61 --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/UnsetFieldsInfo.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. 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 com.sun.tools.javac.comp; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; + +import com.sun.tools.javac.util.List; + +import com.sun.tools.javac.code.Symbol.ClassSymbol; +import com.sun.tools.javac.code.Symbol.VarSymbol; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.util.Context; + +/** + * A Context class, that can keep useful information about unset fields. + * This information will be produced during flow analysis and used during + * code generation. + * + *

    This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class UnsetFieldsInfo { + protected static final Context.Key unsetFieldsInfoKey = new Context.Key<>(); + + public static UnsetFieldsInfo instance(Context context) { + UnsetFieldsInfo instance = context.get(unsetFieldsInfoKey); + if (instance == null) + instance = new UnsetFieldsInfo(context); + return instance; + } + + @SuppressWarnings("this-escape") + protected UnsetFieldsInfo(Context context) { + context.put(unsetFieldsInfoKey, this); + } + + private WeakHashMap>> unsetFieldsMap = new WeakHashMap<>(); + + public void addUnsetFieldsInfo(ClassSymbol csym, JCTree tree, Set unsetFields) { + Map> treeToFieldsMap = unsetFieldsMap.get(csym); + if (treeToFieldsMap == null) { + treeToFieldsMap = new HashMap<>(); + treeToFieldsMap.put(tree, unsetFields); + unsetFieldsMap.put(csym, treeToFieldsMap); + } else { + if (!treeToFieldsMap.containsKey(tree)) { + // only add if there is no info for the given tree + treeToFieldsMap.put(tree, unsetFields); + } + } + } + + public Set getUnsetFields(ClassSymbol csym, JCTree tree) { + Map> treeToFieldsMap = unsetFieldsMap.get(csym); + if (treeToFieldsMap != null) { + Set result = treeToFieldsMap.get(tree); + if (result != null) { + return result; + } + } + return null; + } + + public void removeUnsetFieldInfo(ClassSymbol csym, JCTree tree) { + Map> treeToFieldsMap = unsetFieldsMap.get(csym); + if (treeToFieldsMap != null) { + treeToFieldsMap.remove(tree); + } + } +} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java index 6f1ad28586d..89989636e9a 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java @@ -61,10 +61,12 @@ import com.sun.tools.javac.jvm.ClassFile.Version; import com.sun.tools.javac.jvm.PoolConstant.NameAndType; import com.sun.tools.javac.main.Option; +import com.sun.tools.javac.resources.CompilerProperties; import com.sun.tools.javac.resources.CompilerProperties.Errors; import com.sun.tools.javac.resources.CompilerProperties.Fragments; import com.sun.tools.javac.resources.CompilerProperties.LintWarnings; import com.sun.tools.javac.resources.CompilerProperties.Warnings; +import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.util.*; import com.sun.tools.javac.util.ByteBuffer.UnderflowException; import com.sun.tools.javac.util.DefinedBy.Api; @@ -75,6 +77,7 @@ import com.sun.tools.javac.code.Scope.LookupKind; +import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE; import static com.sun.tools.javac.code.TypeTag.ARRAY; import static com.sun.tools.javac.code.TypeTag.CLASS; import static com.sun.tools.javac.code.TypeTag.TYPEVAR; @@ -110,6 +113,10 @@ public class ClassReader { */ boolean allowModules; + /** Switch: allow value classes. + */ + boolean allowValueClasses; + /** Switch: allow sealed */ boolean allowSealedTypes; @@ -289,6 +296,8 @@ protected ClassReader(Context context) { Source source = Source.instance(context); preview = Preview.instance(context); allowModules = Feature.MODULES.allowedInSource(source); + allowValueClasses = (!preview.isPreview(Feature.VALUE_CLASSES) || preview.isEnabled()) && + Feature.VALUE_CLASSES.allowedInSource(source); allowRecords = Feature.RECORDS.allowedInSource(source); allowSealedTypes = Feature.SEALED_CLASSES.allowedInSource(source); warnOnIllegalUtf8 = Feature.WARN_ON_ILLEGAL_UTF8.allowedInSource(source); @@ -581,9 +590,11 @@ Type classSigToType() { sbp - startSbp)); try { - return (outer == Type.noType) ? - t.erasure(types) : - new ClassType(outer, List.nil(), t); + if (outer == Type.noType) { + ClassType et = (ClassType) t.erasure(types); + return new ClassType(et.getEnclosingType(), List.nil(), et.tsym, et.getMetadata()); + } + return new ClassType(outer, List.nil(), t, List.nil()); } finally { sbp = startSbp; } @@ -605,7 +616,7 @@ Type classSigToType() { * could change in the future */ final List actualsCp = actuals; - outer = new ClassType(outer, actuals, t) { + outer = new ClassType(outer, actuals, t, List.nil()) { boolean completed = false; boolean typeArgsSet = false; @Override @DefinedBy(Api.LANGUAGE_MODEL) @@ -689,7 +700,7 @@ public List getTypeArguments() { t = enterClass(readName(signatureBuffer, startSbp, sbp - startSbp)); - outer = new ClassType(outer, List.nil(), t); + outer = new ClassType(outer, List.nil(), t, List.nil()); } signatureBuffer[sbp++] = (byte)'$'; continue; @@ -1546,6 +1557,13 @@ else if (proxy.type.tsym.flatName() == syms.profileType.tsym.flatName()) { } else if (proxy.type.tsym.flatName() == syms.valueBasedInternalType.tsym.flatName()) { Assert.check(sym.kind == TYP); sym.flags_field |= VALUE_BASED; + } else if (proxy.type.tsym.flatName() == syms.migratedValueClassInternalType.tsym.flatName()) { + Assert.check(sym.kind == TYP); + sym.flags_field |= MIGRATED_VALUE_CLASS; + if (needsValueFlag(sym, sym.flags_field)) { + sym.flags_field |= VALUE_CLASS; + sym.flags_field &= ~IDENTITY_TYPE; + } } else if (proxy.type.tsym.flatName() == syms.restrictedInternalType.tsym.flatName()) { Assert.check(sym.kind == MTH); sym.flags_field |= RESTRICTED; @@ -1565,6 +1583,12 @@ else if (proxy.type.tsym.flatName() == syms.profileType.tsym.flatName()) { setFlagIfAttributeTrue(proxy, sym, names.reflective, PREVIEW_REFLECTIVE); } else if (proxy.type.tsym == syms.valueBasedType.tsym && sym.kind == TYP) { sym.flags_field |= VALUE_BASED; + } else if (proxy.type.tsym == syms.migratedValueClassType.tsym && sym.kind == TYP) { + sym.flags_field |= MIGRATED_VALUE_CLASS; + if (needsValueFlag(sym, sym.flags_field)) { + sym.flags_field |= VALUE_CLASS; + sym.flags_field &= ~IDENTITY_TYPE; + } } else if (proxy.type.tsym == syms.restrictedType.tsym) { Assert.check(sym.kind == MTH); sym.flags_field |= RESTRICTED; @@ -3055,7 +3079,7 @@ void readClass(ClassSymbol c) { // read flags, or skip if this is an inner class long f = nextChar(); - long flags = adjustClassFlags(f); + long flags = adjustClassFlags(c, f); if ((flags & MODULE) == 0) { if (c.owner.kind == PCK || c.owner.kind == ERR) c.flags_field = flags; // read own class name and check that it matches @@ -3147,7 +3171,7 @@ void readInnerClasses(ClassSymbol c) { ClassSymbol outer = optPoolEntry(outerIdx, poolReader::getClass, null); Name name = optPoolEntry(nameIdx, poolReader::getName, names.empty); if (name == null) name = names.empty; - long flags = adjustClassFlags(nextChar()); + long flags = adjustClassFlags(c, nextChar()); if (outer != null) { // we have a member class if (name == names.empty) name = names.one; @@ -3289,6 +3313,11 @@ public void readClassFile(ClassSymbol c) { ***********************************************************************/ long adjustFieldFlags(long flags) { + boolean previewClassFile = minorVersion == ClassFile.PREVIEW_MINOR_VERSION; + if (allowValueClasses && previewClassFile && (flags & ACC_STRICT) != 0) { + flags &= ~ACC_STRICT; + flags |= STRICT; + } return flags; } @@ -3304,12 +3333,35 @@ long adjustMethodFlags(long flags) { return flags; } - long adjustClassFlags(long flags) { + long adjustClassFlags(ClassSymbol c, long flags) { if ((flags & ACC_MODULE) != 0) { flags &= ~ACC_MODULE; flags |= MODULE; } - return flags & ~ACC_SUPER; // SUPER and SYNCHRONIZED bits overloaded + if (((flags & ACC_IDENTITY) != 0 && !isMigratedValueClass(flags)) + || (majorVersion <= Version.MAX().major && minorVersion != PREVIEW_MINOR_VERSION && (flags & INTERFACE) == 0)) { + flags |= IDENTITY_TYPE; + } else if (needsValueFlag(c, flags)) { + flags |= VALUE_CLASS; + flags &= ~IDENTITY_TYPE; + } + flags &= ~ACC_IDENTITY; // ACC_IDENTITY and SYNCHRONIZED bits overloaded + return flags; + } + + private boolean needsValueFlag(Symbol c, long flags) { + boolean previewClassFile = minorVersion == ClassFile.PREVIEW_MINOR_VERSION; + if (allowValueClasses) { + if (previewClassFile && majorVersion >= V67.major && (flags & INTERFACE) == 0 || + majorVersion >= V67.major && isMigratedValueClass(flags)) { + return true; + } + } + return false; + } + + private boolean isMigratedValueClass(long flags) { + return allowValueClasses && ((flags & MIGRATED_VALUE_CLASS) != 0); } /** diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java index a24184e990b..8b3571083d2 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -146,6 +146,7 @@ public class ClassWriter extends ClassFile { /** The tags and constants used in compressed stackmap. */ static final int SAME_FRAME_SIZE = 64; + static final int EARLY_LARVAL = 246; static final int SAME_LOCALS_1_STACK_ITEM_EXTENDED = 247; static final int SAME_FRAME_EXTENDED = 251; static final int FULL_FRAME = 255; @@ -222,7 +223,7 @@ public static String flagNames(long flags) { int i = 0; long f = flags & StandardFlags; while (f != 0) { - if ((f & 1) != 0) { + if ((f & 1) != 0 && flagName[i] != "") { sbuf.append(" "); sbuf.append(flagName[i]); } @@ -234,7 +235,8 @@ public static String flagNames(long flags) { //where private static final String[] flagName = { "PUBLIC", "PRIVATE", "PROTECTED", "STATIC", "FINAL", - "SUPER", "VOLATILE", "TRANSIENT", "NATIVE", "INTERFACE", + // the empty position should be for synchronized but right now we don't have any test checking it + "", "VOLATILE", "TRANSIENT", "NATIVE", "INTERFACE", "ABSTRACT", "STRICTFP"}; /* **************************************************************** @@ -837,14 +839,18 @@ int writeModuleAttribute(ClassSymbol c) { /** Write "inner classes" attribute. */ - void writeInnerClasses() { + void writeInnerClasses(boolean markedPreview) { int alenIdx = writeAttr(names.InnerClasses); databuf.appendChar(poolWriter.innerClasses.size()); for (ClassSymbol inner : poolWriter.innerClasses) { inner.markAbstractIfNeeded(types); - int flags = adjustFlags(inner.flags_field); + int flags = adjustFlags(inner, inner.flags_field); if ((flags & INTERFACE) != 0) flags |= ABSTRACT; // Interfaces are always ABSTRACT - flags &= ~STRICTFP; //inner classes should not have the strictfp flag set. + if ((flags & ACC_IDENTITY) != 0) { + if (!markedPreview) { + flags &= ~ACC_IDENTITY; // No SUPER for InnerClasses + } + } if (dumpInnerClassModifiers) { PrintWriter pw = log.getWriter(Log.WriterKind.ERROR); pw.println("INNERCLASS " + inner.name); @@ -860,6 +866,17 @@ void writeInnerClasses() { endAttr(alenIdx); } + /** Write out "LoadableDescriptors" attribute by enumerating the value classes encountered in field/method descriptors during this compilation. + */ + void writeLoadableDescriptorsAttribute() { + int alenIdx = writeAttr(names.LoadableDescriptors); + databuf.appendChar(poolWriter.loadableDescriptors.size()); + for (Symbol c : poolWriter.loadableDescriptors) { + databuf.appendChar(poolWriter.putDescriptor(c)); + } + endAttr(alenIdx); + } + int writeRecordAttribute(ClassSymbol csym) { int alenIdx = writeAttr(names.Record); Scope s = csym.members(); @@ -973,7 +990,7 @@ void writeBootstrapMethods() { /** Write field symbol, entering all references into constant pool. */ void writeField(VarSymbol v) { - int flags = adjustFlags(v.flags()); + int flags = adjustFlags(v, v.flags()); databuf.appendChar(flags); if (dumpFieldModifiers) { PrintWriter pw = log.getWriter(Log.WriterKind.ERROR); @@ -982,6 +999,13 @@ void writeField(VarSymbol v) { } databuf.appendChar(poolWriter.putName(v.name)); databuf.appendChar(poolWriter.putDescriptor(v)); + Type fldType = v.erasure(types); + if (fldType.requiresLoadableDescriptors(v.owner)) { + poolWriter.enterLoadableDescriptorsClass(fldType.tsym); + if (preview.isPreview(Source.Feature.VALUE_CLASSES)) { + preview.markUsesPreview(null); + } + } int acountIdx = beginAttrs(); int acount = 0; if (v.getConstValue() != null) { @@ -998,7 +1022,7 @@ void writeField(VarSymbol v) { /** Write method symbol, entering all references into constant pool. */ void writeMethod(MethodSymbol m) { - int flags = adjustFlags(m.flags()); + int flags = adjustFlags(m, m.flags()); databuf.appendChar(flags); if (dumpMethodModifiers) { PrintWriter pw = log.getWriter(Log.WriterKind.ERROR); @@ -1007,6 +1031,22 @@ void writeMethod(MethodSymbol m) { } databuf.appendChar(poolWriter.putName(m.name)); databuf.appendChar(poolWriter.putDescriptor(m)); + MethodType mtype = (MethodType) m.externalType(types); + for (Type t : mtype.getParameterTypes()) { + if (t.requiresLoadableDescriptors(m.owner)) { + poolWriter.enterLoadableDescriptorsClass(t.tsym); + if (preview.isPreview(Source.Feature.VALUE_CLASSES)) { + preview.markUsesPreview(null); + } + } + } + Type returnType = mtype.getReturnType(); + if (returnType.requiresLoadableDescriptors(m.owner)) { + poolWriter.enterLoadableDescriptorsClass(returnType.tsym); + if (preview.isPreview(Source.Feature.VALUE_CLASSES)) { + preview.markUsesPreview(null); + } + } int acountIdx = beginAttrs(); int acount = 0; if (m.code != null) { @@ -1297,16 +1337,22 @@ else switch(t.getTag()) { /** An entry in the JSR202 StackMapTable */ abstract static class StackMapTableFrame { abstract int getFrameType(); + int pc; + + StackMapTableFrame(int pc) { + this.pc = pc; + } void write(ClassWriter writer) { int frameType = getFrameType(); writer.databuf.appendByte(frameType); - if (writer.debugstackmap) System.out.print(" frame_type=" + frameType); + if (writer.debugstackmap) System.out.println(" frame_type=" + frameType + " bytecode offset " + pc); } static class SameFrame extends StackMapTableFrame { final int offsetDelta; - SameFrame(int offsetDelta) { + SameFrame(int pc, int offsetDelta) { + super(pc); this.offsetDelta = offsetDelta; } int getFrameType() { @@ -1327,7 +1373,8 @@ void write(ClassWriter writer) { static class SameLocals1StackItemFrame extends StackMapTableFrame { final int offsetDelta; final Type stack; - SameLocals1StackItemFrame(int offsetDelta, Type stack) { + SameLocals1StackItemFrame(int pc, int offsetDelta, Type stack) { + super(pc); this.offsetDelta = offsetDelta; this.stack = stack; } @@ -1355,7 +1402,8 @@ void write(ClassWriter writer) { static class ChopFrame extends StackMapTableFrame { final int frameType; final int offsetDelta; - ChopFrame(int frameType, int offsetDelta) { + ChopFrame(int pc, int frameType, int offsetDelta) { + super(pc); this.frameType = frameType; this.offsetDelta = offsetDelta; } @@ -1374,7 +1422,8 @@ static class AppendFrame extends StackMapTableFrame { final int frameType; final int offsetDelta; final Type[] locals; - AppendFrame(int frameType, int offsetDelta, Type[] locals) { + AppendFrame(int pc, int frameType, int offsetDelta, Type[] locals) { + super(pc); this.frameType = frameType; this.offsetDelta = offsetDelta; this.locals = locals; @@ -1398,7 +1447,8 @@ static class FullFrame extends StackMapTableFrame { final int offsetDelta; final Type[] locals; final Type[] stack; - FullFrame(int offsetDelta, Type[] locals, Type[] stack) { + FullFrame(int pc, int offsetDelta, Type[] locals, Type[] stack) { + super(pc); this.offsetDelta = offsetDelta; this.locals = locals; this.stack = stack; @@ -1427,41 +1477,72 @@ void write(ClassWriter writer) { } } + static class EarlyLarvalFrame extends StackMapTableFrame { + final StackMapTableFrame base; + Set unsetFields; + + EarlyLarvalFrame(StackMapTableFrame base, Set unsetFields) { + super(base.pc); + Assert.check(!(base instanceof EarlyLarvalFrame)); + this.base = base; + this.unsetFields = unsetFields == null ? Set.of() : unsetFields; + } + + int getFrameType() { return EARLY_LARVAL; } + + @Override + void write(ClassWriter writer) { + super.write(writer); + writer.databuf.appendChar(unsetFields.size()); + if (writer.debugstackmap) { + System.out.println(" # writing: EarlyLarval stackmap frame with " + unsetFields.size() + " fields"); + } + for (VarSymbol vsym : unsetFields) { + int index = writer.poolWriter.putNameAndType(vsym); + writer.databuf.appendChar(index); + if (writer.debugstackmap) { + System.out.println(" #writing unset field: " + index + ", with name: " + vsym.name.toString()); + } + } + base.write(writer); + } + } + /** Compare this frame with the previous frame and produce * an entry of compressed stack map frame. */ static StackMapTableFrame getInstance(Code.StackMapFrame this_frame, - int prev_pc, - Type[] prev_locals, - Types types) { + Code.StackMapFrame prevFrame, + Types types, + int pc) { Type[] locals = this_frame.locals; Type[] stack = this_frame.stack; - int offset_delta = this_frame.pc - prev_pc - 1; + int offset_delta = this_frame.pc - prevFrame.pc - 1; if (stack.length == 1) { - if (locals.length == prev_locals.length - && compare(prev_locals, locals, types) == 0) { - return new SameLocals1StackItemFrame(offset_delta, stack[0]); + if (locals.length == prevFrame.locals.length + && compare(prevFrame.locals, locals, types) == 0) { + return new SameLocals1StackItemFrame(pc, offset_delta, stack[0]); } } else if (stack.length == 0) { - int diff_length = compare(prev_locals, locals, types); + int diff_length = compare(prevFrame.locals, locals, types); if (diff_length == 0) { - return new SameFrame(offset_delta); + return new SameFrame(pc, offset_delta); } else if (-MAX_LOCAL_LENGTH_DIFF < diff_length && diff_length < 0) { // APPEND Type[] local_diff = new Type[-diff_length]; - for (int i=prev_locals.length, j=0; i> cpToUnsetFieldsMap = new HashMap<>(); + + public Set initialUnsetFields; + + public Set currentUnsetFields; + + boolean generateEarlyLarvalFrame; + /** Construct a code object, given the settings of the fatcode, * debugging info switches and the CharacterRangeTable. */ @@ -209,7 +208,8 @@ public Code(MethodSymbol meth, CRTable crt, Symtab syms, Types types, - PoolWriter poolWriter) { + PoolWriter poolWriter, + boolean generateEarlyLarvalFrame) { this.meth = meth; this.fatcode = fatcode; this.lineMap = lineMap; @@ -231,6 +231,7 @@ public Code(MethodSymbol meth, } state = new State(); lvar = new LocalVar[20]; + this.generateEarlyLarvalFrame = generateEarlyLarvalFrame; } @@ -1081,7 +1082,6 @@ public void emitop2(int op, int od, PoolConstant data) { } // postop(); } - /** Emit an opcode with a four-byte operand field. */ public void emitop4(int op, int od) { @@ -1228,6 +1228,7 @@ static class StackMapFrame { int pc; Type[] locals; Type[] stack; + Set unsetFields; } /** A buffer of cldc stack map entries. */ @@ -1323,13 +1324,17 @@ void emitStackMapFrame(int pc, int localsSize) { StackMapFrame frame = new StackMapFrame(); frame.pc = pc; + boolean hasUninitalizedThis = false; int localCount = 0; Type[] locals = new Type[localsSize]; for (int i=0; i 1) i++; } @@ -1355,6 +1360,10 @@ void emitStackMapFrame(int pc, int localsSize) { } } + Set unsetFieldsAtPC = cpToUnsetFieldsMap.get(pc); + boolean encloseWithEarlyLarvalFrame = unsetFieldsAtPC != null && generateEarlyLarvalFrame && hasUninitalizedThis + && !lastFrame.unsetFields.equals(unsetFieldsAtPC); + if (stackMapTableBuffer == null) { stackMapTableBuffer = new StackMapTableFrame[20]; } else { @@ -1362,13 +1371,24 @@ void emitStackMapFrame(int pc, int localsSize) { stackMapTableBuffer, stackMapBufferSize); } - stackMapTableBuffer[stackMapBufferSize++] = - StackMapTableFrame.getInstance(frame, lastFrame.pc, lastFrame.locals, types); + + StackMapTableFrame tableFrame = StackMapTableFrame.getInstance(frame, lastFrame, types, pc); + if (encloseWithEarlyLarvalFrame) { + tableFrame = new StackMapTableFrame.EarlyLarvalFrame(tableFrame, unsetFieldsAtPC); + frame.unsetFields = unsetFieldsAtPC; + } else { + frame.unsetFields = lastFrame.unsetFields; + } + stackMapTableBuffer[stackMapBufferSize++] = tableFrame; frameBeforeLast = lastFrame; lastFrame = frame; } + public void addUnsetFieldsAtPC(int pc, Set unsetFields) { + cpToUnsetFieldsMap.put(pc, unsetFields); + } + StackMapFrame getInitialFrame() { StackMapFrame frame = new StackMapFrame(); List arg_types = ((MethodType)meth.externalType(types)).argtypes; @@ -1390,6 +1410,7 @@ StackMapFrame getInitialFrame() { } frame.pc = -1; frame.stack = null; + frame.unsetFields = initialUnsetFields; return frame; } @@ -1468,6 +1489,9 @@ public Chain branch(int opcode) { result = new Chain(emitJump(opcode), result, state.dup()); + if (currentUnsetFields != null) { + addUnsetFieldsAtPC(result.pc, currentUnsetFields); + } fixedPc = fatcode; if (opcode == goto_) alive = false; } @@ -1479,6 +1503,7 @@ public Chain branch(int opcode) { public void resolve(Chain chain, int target) { boolean changed = false; State newState = state; + int originalTarget = target; for (; chain != null; chain = chain.next) { Assert.check(state != chain.state && (target > chain.pc || isStatementStart())); @@ -1505,13 +1530,21 @@ public void resolve(Chain chain, int target) { break; } } else { - if (fatcode) + if (fatcode) { put4(chain.pc + 1, target - chain.pc); + if (cpToUnsetFieldsMap.get(chain.pc) != null) { + addUnsetFieldsAtPC(originalTarget, cpToUnsetFieldsMap.get(chain.pc)); + } + } else if (target - chain.pc < Short.MIN_VALUE || target - chain.pc > Short.MAX_VALUE) fatcode = true; - else + else { put2(chain.pc + 1, target - chain.pc); + if (cpToUnsetFieldsMap.get(chain.pc) != null) { + addUnsetFieldsAtPC(originalTarget, cpToUnsetFieldsMap.get(chain.pc)); + } + } Assert.check(!alive || chain.state.stacksize == newState.stacksize && chain.state.nlocks == newState.nlocks); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java index e40a2fbfcea..c1246e7a7e3 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java @@ -97,6 +97,8 @@ public static Gen instance(Context context) { */ final PoolWriter poolWriter; + private final UnsetFieldsInfo unsetFieldsInfo; + @SuppressWarnings("this-escape") protected Gen(Context context) { context.put(genKey, this); @@ -127,11 +129,16 @@ protected Gen(Context context) { debugCode = options.isSet("debug.code"); disableVirtualizedPrivateInvoke = options.isSet("disableVirtualizedPrivateInvoke"); poolWriter = new PoolWriter(types, names); + unsetFieldsInfo = UnsetFieldsInfo.instance(context); // ignore cldc because we cannot have both stackmap formats this.stackMap = StackMapFormat.JSR202; annotate = Annotate.instance(context); qualifiedSymbolCache = new HashMap<>(); + Preview preview = Preview.instance(context); + Source source = Source.instance(context); + allowValueClasses = (!preview.isPreview(Source.Feature.VALUE_CLASSES) || preview.isEnabled()) && + Source.Feature.VALUE_CLASSES.allowedInSource(source); } /** Switches @@ -141,6 +148,7 @@ protected Gen(Context context) { private final boolean genCrt; private final boolean debugCode; private boolean disableVirtualizedPrivateInvoke; + private final boolean allowValueClasses; /** Code buffer, set by genMethod. */ @@ -424,6 +432,8 @@ boolean hasFinally(JCTree target, Env env) { */ List normalizeDefs(List defs, ClassSymbol c) { ListBuffer initCode = new ListBuffer<>(); + // only used for value classes + ListBuffer initBlocks = new ListBuffer<>(); ListBuffer initTAs = new ListBuffer<>(); ListBuffer clinitCode = new ListBuffer<>(); ListBuffer clinitTAs = new ListBuffer<>(); @@ -439,8 +449,13 @@ List normalizeDefs(List defs, ClassSymbol c) { JCBlock block = (JCBlock)def; if ((block.flags & STATIC) != 0) clinitCode.append(block); - else if ((block.flags & SYNTHETIC) == 0) - initCode.append(block); + else if ((block.flags & SYNTHETIC) == 0) { + if (c.isValueClass() || c.hasStrict()) { + initBlocks.append(block); + } else { + initCode.append(block); + } + } break; case METHODDEF: methodDefs.append(def); @@ -479,12 +494,11 @@ else if ((block.flags & SYNTHETIC) == 0) } } // Insert any instance initializers into all constructors. - if (initCode.length() != 0) { - List inits = initCode.toList(); + if (initCode.length() != 0 || initBlocks.length() != 0) { initTAs.addAll(c.getInitTypeAttributes()); List initTAlist = initTAs.toList(); for (JCTree t : methodDefs) { - normalizeMethod((JCMethodDecl)t, inits, initTAlist); + normalizeMethod((JCMethodDecl)t, initCode.toList(), initBlocks.toList(), initTAlist); } } // If there are class initializers, create a method @@ -547,11 +561,17 @@ private void checkStringConstant(DiagnosticPosition pos, Object constValue) { * @param initCode The list of instance initializer statements. * @param initTAs Type annotations from the initializer expression. */ - void normalizeMethod(JCMethodDecl md, List initCode, List initTAs) { + void normalizeMethod(JCMethodDecl md, List initCode, List initBlocks, List initTAs) { if (TreeInfo.isConstructor(md) && TreeInfo.hasConstructorCall(md, names._super)) { // We are seeing a constructor that has a super() call. // Find the super() invocation and append the given initializer code. - TreeInfo.mapSuperCalls(md.body, supercall -> make.Block(0, initCode.prepend(supercall))); + if (allowValueClasses & (md.sym.owner.isValueClass() || md.sym.owner.hasStrict() || ((md.sym.owner.flags_field & RECORD) != 0))) { + rewriteInitializersIfNeeded(md, initCode); + md.body.stats = initCode.appendList(md.body.stats); + TreeInfo.mapSuperCalls(md.body, supercall -> make.Block(0, initBlocks.prepend(supercall))); + } else { + TreeInfo.mapSuperCalls(md.body, supercall -> make.Block(0, initCode.prepend(supercall))); + } if (md.body.bracePos == Position.NOPOS) md.body.bracePos = TreeInfo.endPos(md.body.stats.last()); @@ -560,6 +580,40 @@ void normalizeMethod(JCMethodDecl md, List initCode, List initCode) { + if (lower.initializerOuterThis.containsKey(md.sym.owner)) { + InitializerVisitor initializerVisitor = new InitializerVisitor(md, lower.initializerOuterThis.get(md.sym.owner)); + for (JCStatement init : initCode) { + initializerVisitor.scan(init); + } + } + } + + public static class InitializerVisitor extends TreeScanner { + JCMethodDecl md; + Set exprSet; + + public InitializerVisitor(JCMethodDecl md, Set exprSet) { + this.md = md; + this.exprSet = exprSet; + } + + @Override + public void visitTree(JCTree tree) {} + + @Override + public void visitIdent(JCIdent tree) { + if (exprSet.contains(tree)) { + for (JCVariableDecl param: md.params) { + if (param.name == tree.name && + ((param.sym.flags_field & (MANDATED | NOOUTERTHIS)) == (MANDATED | NOOUTERTHIS))) { + tree.sym = param.sym; + } + } + } + } + } + /* ************************************************************************ * Traversal methods *************************************************************************/ @@ -956,6 +1010,11 @@ void genMethod(JCMethodDecl tree, Env env, boolean fatcode) { else if (tree.body != null) { // Create a new code structure and initialize it. int startpcCrt = initCode(tree, env, fatcode); + Set prevUnsetFields = code.currentUnsetFields; + if (meth.isConstructor()) { + code.currentUnsetFields = unsetFieldsInfo.getUnsetFields(env.enclClass.sym, tree.body); + code.initialUnsetFields = unsetFieldsInfo.getUnsetFields(env.enclClass.sym, tree.body); + } try { genStat(tree.body, env); @@ -963,6 +1022,8 @@ else if (tree.body != null) { // Failed due to code limit, try again with jsr/ret startpcCrt = initCode(tree, env, fatcode); genStat(tree.body, env); + } finally { + code.currentUnsetFields = prevUnsetFields; } if (code.state.stacksize != 0) { @@ -1031,7 +1092,8 @@ private int initCode(JCMethodDecl tree, Env env, boolean fatcode) { : null, syms, types, - poolWriter); + poolWriter, + allowValueClasses); items = new Items(poolWriter, code, syms, types); if (code.debugCode) { System.err.println(meth + " for body " + tree); @@ -1167,6 +1229,19 @@ private void genLoop(JCStatement loop, JCExpression cond, List step, boolean testFirst) { + Set prevCodeUnsetFields = code.currentUnsetFields; + try { + genLoopHelper(loop, body, cond, step, testFirst); + } finally { + code.currentUnsetFields = prevCodeUnsetFields; + } + } + + private void genLoopHelper(JCStatement loop, + JCStatement body, + JCExpression cond, + List step, + boolean testFirst) { Env loopEnv = env.dup(loop, new GenContext()); int startpc = code.entryPoint(); if (testFirst) { //while or for loop @@ -1225,11 +1300,13 @@ public void visitSwitch(JCSwitch tree) { public void visitSwitchExpression(JCSwitchExpression tree) { code.resolvePending(); boolean prevInCondSwitchExpression = inCondSwitchExpression; + Set prevCodeUnsetFields = code.currentUnsetFields; try { inCondSwitchExpression = false; doHandleSwitchExpression(tree); } finally { inCondSwitchExpression = prevInCondSwitchExpression; + code.currentUnsetFields = prevCodeUnsetFields; } result = items.makeStackItem(pt); } @@ -1305,6 +1382,16 @@ public void visitLambda(JCLambda tree) { private void handleSwitch(JCTree swtch, JCExpression selector, List cases, boolean patternSwitch) { + Set prevCodeUnsetFields = code.currentUnsetFields; + try { + handleSwitchHelper(swtch, selector, cases, patternSwitch); + } finally { + code.currentUnsetFields = prevCodeUnsetFields; + } + } + + void handleSwitchHelper(JCTree swtch, JCExpression selector, List cases, + boolean patternSwitch) { int limit = code.nextreg; Assert.check(!selector.type.hasTag(CLASS)); int switchStart = patternSwitch ? code.entryPoint() : -1; @@ -1316,13 +1403,13 @@ private void handleSwitch(JCTree swtch, JCExpression selector, List case sel.load().drop(); if (genCrt) code.crt.put(TreeInfo.skipParens(selector), - CRT_FLOW_CONTROLLER, startpcCrt, code.curCP()); + CRT_FLOW_CONTROLLER, startpcCrt, code.curCP()); } else { // We are seeing a nonempty switch. sel.load(); if (genCrt) code.crt.put(TreeInfo.skipParens(selector), - CRT_FLOW_CONTROLLER, startpcCrt, code.curCP()); + CRT_FLOW_CONTROLLER, startpcCrt, code.curCP()); Env switchEnv = env.dup(swtch, new GenContext()); switchEnv.info.isSwitch = true; @@ -1358,11 +1445,11 @@ private void handleSwitch(JCTree swtch, JCExpression selector, List case long lookup_space_cost = 3 + 2 * (long) nlabels; long lookup_time_cost = nlabels; int opcode = - nlabels > 0 && - table_space_cost + 3 * table_time_cost <= - lookup_space_cost + 3 * lookup_time_cost - ? - tableswitch : lookupswitch; + nlabels > 0 && + table_space_cost + 3 * table_time_cost <= + lookup_space_cost + 3 * lookup_time_cost + ? + tableswitch : lookupswitch; int startpc = code.curCP(); // the position of the selector operation code.emitop0(opcode); @@ -1398,8 +1485,8 @@ private void handleSwitch(JCTree swtch, JCExpression selector, List case if (i != defaultIndex) { if (opcode == tableswitch) { code.put4( - tableBase + 4 * (labels[i] - lo + 3), - pc - startpc); + tableBase + 4 * (labels[i] - lo + 3), + pc - startpc); } else { offsets[i] = pc - startpc; } @@ -1449,8 +1536,8 @@ private void handleSwitch(JCTree swtch, JCExpression selector, List case } if (swtch instanceof JCSwitchExpression) { - // Emit line position for the end of a switch expression - code.statBegin(TreeInfo.endPos(swtch)); + // Emit line position for the end of a switch expression + code.statBegin(TreeInfo.endPos(swtch)); } } code.endScopes(limit); @@ -1553,6 +1640,15 @@ void afterBody() { * @param env The current environment of the body. */ void genTry(JCTree body, List catchers, Env env) { + Set prevCodeUnsetFields = code.currentUnsetFields; + try { + genTryHelper(body, catchers, env); + } finally { + code.currentUnsetFields = prevCodeUnsetFields; + } + } + + void genTryHelper(JCTree body, List catchers, Env env) { int limit = code.nextreg; int startpc = code.curCP(); Code.State stateTry = code.state.dup(); @@ -1572,8 +1668,8 @@ void genTry(JCTree body, List catchers, Env env) { endFinalizerGap(env); env.info.finalize.afterBody(); boolean hasFinalizer = - env.info.finalize != null && - env.info.finalize.hasFinalizer(); + env.info.finalize != null && + env.info.finalize.hasFinalizer(); if (startpc != endpc) for (List l = catchers; l.nonEmpty(); l = l.tail) { // start off with exception on stack code.entryPoint(stateTry, l.head.param.sym.type); @@ -1582,7 +1678,7 @@ void genTry(JCTree body, List catchers, Env env) { if (hasFinalizer || l.tail.nonEmpty()) { code.statBegin(TreeInfo.endPos(env.tree)); exitChain = Code.mergeChains(exitChain, - code.branch(goto_)); + code.branch(goto_)); } endFinalizerGap(env); } @@ -1605,7 +1701,7 @@ void genTry(JCTree body, List catchers, Env env) { while (env.info.gaps.nonEmpty()) { int endseg = env.info.gaps.next().intValue(); registerCatch(body.pos(), startseg, endseg, - catchallpc, 0); + catchallpc, 0); startseg = env.info.gaps.next().intValue(); } code.statBegin(TreeInfo.finalizerPos(env.tree, PosKind.FIRST_STAT_POS)); @@ -1620,8 +1716,8 @@ void genTry(JCTree body, List catchers, Env env) { excVar.load(); registerCatch(body.pos(), startseg, - env.info.gaps.next().intValue(), - catchallpc, 0); + env.info.gaps.next().intValue(), + catchallpc, 0); code.emitop0(athrow); code.markDead(); @@ -1764,11 +1860,20 @@ void registerCatch(DiagnosticPosition pos, } public void visitIf(JCIf tree) { + Set prevCodeUnsetFields = code.currentUnsetFields; + try { + visitIfHelper(tree); + } finally { + code.currentUnsetFields = prevCodeUnsetFields; + } + } + + public void visitIfHelper(JCIf tree) { int limit = code.nextreg; Chain thenExit = null; Assert.check(code.isStatementStart()); CondItem c = genCond(TreeInfo.skipParens(tree.cond), - CRT_FLOW_CONTROLLER); + CRT_FLOW_CONTROLLER); Chain elseChain = c.jumpFalse(); Assert.check(code.isStatementStart()); if (!c.isFalse()) { @@ -2092,6 +2197,8 @@ public void visitParens(JCParens tree) { public void visitAssign(JCAssign tree) { Item l = genExpr(tree.lhs, tree.lhs.type); genExpr(tree.rhs, tree.lhs.type).load(); + Set tmpUnsetSymbols = unsetFieldsInfo.getUnsetFields(env.enclClass.sym, tree); + code.currentUnsetFields = tmpUnsetSymbols != null ? tmpUnsetSymbols : code.currentUnsetFields; if (tree.rhs.type.hasTag(BOT)) { /* This is just a case of widening reference conversion that per 5.1.5 simply calls for "regarding a reference as having some other type in a manner that can be proved @@ -2370,7 +2477,7 @@ public void visitSelect(JCFieldAccess tree) { code.emitLdc((LoadableConstant)checkDimension(tree.pos(), tree.selected.type)); result = items.makeStackItem(pt); return; - } + } Symbol ssym = TreeInfo.symbol(tree.selected); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/PoolWriter.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/PoolWriter.java index c90fbab788c..15219bcee1b 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/PoolWriter.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/PoolWriter.java @@ -51,9 +51,11 @@ import java.io.OutputStream; import java.util.ArrayDeque; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; +import java.util.Set; import static com.sun.tools.javac.code.Kinds.Kind.TYP; import static com.sun.tools.javac.code.TypeTag.ARRAY; @@ -94,6 +96,8 @@ public class PoolWriter { /** The inner classes to be written, as an ordered set (enclosing first). */ LinkedHashSet innerClasses = new LinkedHashSet<>(); + Set loadableDescriptors = new HashSet<>(); + /** The list of entries in the BootstrapMethods attribute. */ Map bootstrapMethods = new LinkedHashMap<>(); @@ -232,6 +236,16 @@ void enterInner(ClassSymbol c) { } } + /** Enter a value class into the `loadableDescriptorsClasses' set. + */ + void enterLoadableDescriptorsClass(Symbol c) { + if (c.type.isCompound()) { + throw new AssertionError("Unexpected intersection type: " + c.type); + } + c.complete(); + loadableDescriptors.add(c); + } + /** * Create a new Utf8 entry representing a descriptor for given (member) symbol. */ @@ -512,6 +526,7 @@ private Name classSig(Type t) { void reset() { innerClasses.clear(); + loadableDescriptors.clear(); bootstrapMethods.clear(); pool.reset(); } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java index f60adcb3b80..86d832604aa 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java @@ -223,6 +223,12 @@ public boolean hasSealedClasses() { return compareTo(JDK1_15) >= 0; } + /** Does the target VM support value classes + */ + public boolean hasValueClasses() { + return compareTo(JDK1_23) >= 0; + } + /** Is the ACC_STRICT bit redundant and obsolete */ public boolean obsoleteAccStrict() { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java index ee11304dce9..4b119af12bd 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/JavaCompiler.java @@ -1619,8 +1619,8 @@ public void visitSwitchExpression(JCSwitchExpression tree) { ScanNested scanner = new ScanNested(); scanner.scan(env.tree); for (Env dep: scanner.dependencies) { - if (!compileStates.isDone(dep, CompileState.WARN)) - desugaredEnvs.put(dep, desugar(warn(flow(attribute(dep))))); + if (!compileStates.isDone(dep, CompileState.WARN)) + desugaredEnvs.put(dep, desugar(warn(flow(attribute(dep))))); } //We need to check for error another time as more classes might @@ -1700,6 +1700,13 @@ public void visitSwitchExpression(JCSwitchExpression tree) { compileStates.put(env, CompileState.UNLAMBDA); } + if (shouldStop(CompileState.STRICT_FIELDS_PROXIES)) + return; + for (JCTree def : cdefs) { + LocalProxyVarsGen.instance(context).translateTopLevelClass(def, localMake); + } + compileStates.put(env, CompileState.STRICT_FIELDS_PROXIES); + //generate code for each class for (List l = cdefs; l.nonEmpty(); l = l.tail) { JCClassDecl cdef = (JCClassDecl)l.head; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java index 4a990701315..e6a92f58823 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java @@ -37,9 +37,11 @@ import com.sun.source.tree.ModuleTree.ModuleKind; import com.sun.tools.javac.code.*; +import com.sun.tools.javac.code.FlagsEnum; import com.sun.tools.javac.code.Source.Feature; import com.sun.tools.javac.file.PathFileObject; import com.sun.tools.javac.parser.Tokens.*; +import com.sun.tools.javac.parser.Tokens.Comment.CommentStyle; import com.sun.tools.javac.resources.CompilerProperties.Errors; import com.sun.tools.javac.resources.CompilerProperties.Fragments; import com.sun.tools.javac.resources.CompilerProperties.LintWarnings; @@ -52,6 +54,7 @@ import com.sun.tools.javac.util.JCDiagnostic.Fragment; import com.sun.tools.javac.util.List; +import static com.sun.tools.javac.code.Flags.asFlagSet; import static com.sun.tools.javac.parser.Tokens.TokenKind.*; import static com.sun.tools.javac.parser.Tokens.TokenKind.ASSERT; import static com.sun.tools.javac.parser.Tokens.TokenKind.CASE; @@ -60,6 +63,7 @@ import static com.sun.tools.javac.parser.Tokens.TokenKind.GT; import static com.sun.tools.javac.parser.Tokens.TokenKind.IMPORT; import static com.sun.tools.javac.parser.Tokens.TokenKind.LT; +import static com.sun.tools.javac.parser.Tokens.TokenKind.SYNCHRONIZED; import com.sun.tools.javac.parser.VirtualParser.VirtualScanner; import static com.sun.tools.javac.tree.JCTree.Tag.*; import static com.sun.tools.javac.resources.CompilerProperties.Fragments.ImplicitAndExplicitNotAllowed; @@ -198,6 +202,8 @@ protected JavacParser(ParserFactory fac, this.allowYieldStatement = Feature.SWITCH_EXPRESSION.allowedInSource(source); this.allowRecords = Feature.RECORDS.allowedInSource(source); this.allowSealedTypes = Feature.SEALED_CLASSES.allowedInSource(source); + this.allowValueClasses = (!preview.isPreview(Feature.VALUE_CLASSES) || preview.isEnabled()) && + Feature.VALUE_CLASSES.allowedInSource(source); updateUnexpectedTopLevelDefinitionStartError(false); } @@ -222,6 +228,8 @@ protected JavacParser(JavacParser parser, this.allowYieldStatement = Feature.SWITCH_EXPRESSION.allowedInSource(source); this.allowRecords = Feature.RECORDS.allowedInSource(source); this.allowSealedTypes = Feature.SEALED_CLASSES.allowedInSource(source); + this.allowValueClasses = (!preview.isPreview(Feature.VALUE_CLASSES) || preview.isEnabled()) && + Feature.VALUE_CLASSES.allowedInSource(source); updateUnexpectedTopLevelDefinitionStartError(false); } @@ -260,6 +268,10 @@ protected DocCommentTable newDocCommentTable(boolean keepDocComments, ParserFact */ boolean allowRecords; + /** Switch: are value classes allowed in this source level? + */ + boolean allowValueClasses; + /** Switch: are sealed types allowed in this source level? */ boolean allowSealedTypes; @@ -1668,8 +1680,8 @@ protected JCExpression term3() { } break loop; case LT: - if (!isMode(TYPE) && isUnboundMemberRef()) { - //this is an unbound method reference whose qualifier + if (!isMode(TYPE) && isParameterizedTypePrefix()) { + //this is either an unbound method reference whose qualifier //is a generic type i.e. A::m int pos1 = token.pos; accept(LT); @@ -1911,7 +1923,7 @@ JCExpression term3Rest(JCExpression t, List typeArgs) { * matching '>' and see if the subsequent terminal is either '.' or '::'. */ @SuppressWarnings("fallthrough") - boolean isUnboundMemberRef() { + boolean isParameterizedTypePrefix() { int pos = 0, depth = 0; outer: for (Token t = S.token(pos) ; ; t = S.token(++pos)) { switch (t.kind) { @@ -3006,6 +3018,11 @@ List blockStatement() { } } } + if ((isValueModifier()) && allowValueClasses) { + checkSourceLevel(Feature.VALUE_CLASSES); + dc = token.docComment(); + return List.of(classOrRecordOrInterfaceOrEnumDeclaration(modifiersOpt(), dc)); + } dc = token.docComment(); if (isRecordStart() && allowRecords) { return List.of(recordDeclaration(F.at(pos).Modifiers(0), dc)); @@ -3611,6 +3628,11 @@ protected JCModifiers modifiersOpt(JCModifiers partial) { flag = Flags.SEALED; break; } + if (isValueModifier()) { + checkSourceLevel(Feature.VALUE_CLASSES); + flag = Flags.VALUE_CLASS; + break; + } break loop; } default: break loop; @@ -3876,6 +3898,13 @@ Source restrictedTypeNameStartingAtSource(Name name, int pos, boolean shouldWarn log.warning(pos, Warnings.RestrictedTypeNotAllowedPreview(name, Source.JDK14)); } } + if (name == names.value) { + if (allowValueClasses) { + return Source.JDK23; + } else if (shouldWarn) { + log.warning(pos, Warnings.RestrictedTypeNotAllowedPreview(name, Source.JDK23)); + } + } if (name == names.sealed) { if (allowSealedTypes) { return Source.JDK15; @@ -4993,6 +5022,32 @@ protected boolean isNonSealedIdentifier(Token someToken, int lookAheadOffset) { return false; } + protected boolean isValueModifier() { + if (token.kind == IDENTIFIER && token.name() == names.value) { + boolean isValueModifier = false; + Token next = S.token(1); + switch (next.kind) { + case PRIVATE: case PROTECTED: case PUBLIC: case STATIC: case TRANSIENT: + case FINAL: case ABSTRACT: case NATIVE: case VOLATILE: case SYNCHRONIZED: + case STRICTFP: case MONKEYS_AT: case DEFAULT: case BYTE: case SHORT: + case CHAR: case INT: case LONG: case FLOAT: case DOUBLE: case BOOLEAN: case VOID: + case CLASS: case INTERFACE: case ENUM: + isValueModifier = true; + break; + case IDENTIFIER: // value record R || value value || new value Comparable() {} ?? + if (next.name() == names.record || next.name() == names.value + || (mode & EXPR) != 0) + isValueModifier = true; + break; + } + if (isValueModifier) { + checkSourceLevel(Feature.VALUE_CLASSES); + return true; + } + } + return false; + } + protected boolean isSealedClassStart(boolean local) { if (token.name() == names.sealed) { Token next = S.token(1); @@ -5020,7 +5075,9 @@ private boolean allowedAfterSealedOrNonSealed(Token next, boolean local, boolean yield afterNext.kind != INTERFACE || currentIsNonSealed; } case PUBLIC, PROTECTED, PRIVATE, ABSTRACT, STATIC, FINAL, STRICTFP, CLASS, INTERFACE, ENUM -> true; - case IDENTIFIER -> isNonSealedIdentifier(next, currentIsNonSealed ? 3 : 1) || next.name() == names.sealed; + case IDENTIFIER -> isNonSealedIdentifier(next, currentIsNonSealed ? 3 : 1) || + next.name() == names.sealed || + allowValueClasses && next.name() == names.value; default -> false; }; } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java index b28f19bd3af..df364d0f195 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java @@ -1256,7 +1256,7 @@ private void newRound() { Kinds.Kind symKind = cs.kind; cs.reset(); if (symKind == ERR) { - cs.type = new ClassType(cs.type.getEnclosingType(), null, cs); + cs.type = new ClassType(cs.type.getEnclosingType(), null, cs, List.nil()); } if (cs.isCompleted()) { cs.completer = initialCompleter; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/PrintingProcessor.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/PrintingProcessor.java index 7aaf035fd36..6cb1c756fef 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/PrintingProcessor.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/PrintingProcessor.java @@ -500,12 +500,15 @@ private void printModifiers(Element e) { case METHOD: case FIELD: Element enclosingElement = e.getEnclosingElement(); - if (enclosingElement != null && - enclosingElement.getKind().isInterface()) { - modifiers.remove(Modifier.PUBLIC); - modifiers.remove(Modifier.ABSTRACT); // only for methods - modifiers.remove(Modifier.STATIC); // only for fields - modifiers.remove(Modifier.FINAL); // only for fields + if (enclosingElement != null) { + if (enclosingElement.getKind().isInterface()) { + modifiers.remove(Modifier.PUBLIC); + modifiers.remove(Modifier.ABSTRACT); // only for methods + modifiers.remove(Modifier.STATIC); // only for fields + modifiers.remove(Modifier.FINAL); // only for fields + } else if (enclosingElement.getKind() == RECORD) { + modifiers.remove(Modifier.STRICTFP); + } } break; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties index a226b2f5960..b7985a7f6ba 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties @@ -397,11 +397,11 @@ compiler.err.cant.inherit.from.final=\ # 0: symbol or name compiler.err.cant.ref.before.ctor.called=\ - cannot reference {0} before supertype constructor has been called + cannot reference {0} before constructor has been called # 0: symbol or name compiler.err.cant.assign.initialized.before.ctor.called=\ - cannot assign initialized field ''{0}'' before supertype constructor has been called + cannot assign initialized field ''{0}'' before constructor has been called compiler.err.cant.select.static.class.from.param.type=\ cannot select a static class from a parameterized type @@ -783,7 +783,7 @@ compiler.err.import.requires.canonical=\ import requires canonical name for {0} compiler.err.improperly.formed.type.param.missing=\ - improperly formed type, some parameters are missing + improperly formed type, some parameters are missing or misplaced compiler.err.improperly.formed.type.inner.raw.param=\ improperly formed type, type arguments given on a raw type @@ -2133,6 +2133,14 @@ compiler.warn.non.private.method.weaker.access=\ compiler.warn.default.ineffective=\ serialization-related default method from an interface will not be run by serialization for an implementing class +# lint: serial +compiler.warn.serializable.value.class.without.write.replace.1=\ + serializable value class does not declare, or inherits, a writeReplace method + +# lint: serial +compiler.warn.serializable.value.class.without.write.replace.2=\ + serializable class does not declare, or inherits, a writeReplace method + # 0: symbol, 1: symbol, 2: symbol, 3: symbol # lint: overloads compiler.warn.potentially.ambiguous.overload=\ @@ -2864,6 +2872,9 @@ compiler.misc.type.req.ref=\ compiler.misc.type.req.exact=\ class or interface without bounds +compiler.misc.type.req.identity=\ + a type with identity + # 0: type compiler.misc.type.parameter=\ type parameter {0} @@ -3924,6 +3935,9 @@ compiler.misc.cant.resolve.modules=\ compiler.misc.bad.requires.flag=\ invalid flag for "requires java.base": {0} +compiler.misc.bad.access.flags=\ + bad access flags combination: {0} + # 0: string compiler.err.invalid.module.specifier=\ module specifier not allowed: {0} @@ -4265,6 +4279,28 @@ compiler.err.preview.not.latest=\ compiler.err.preview.without.source.or.release=\ --enable-preview must be used with either -source or --release +compiler.misc.feature.value.classes=\ + value classes + +# 0: type, 1: type +compiler.err.value.type.has.identity.super.type=\ + The identity type {1} cannot be a supertype of the value type {0} + +# 0: symbol, 1: type +compiler.err.concrete.supertype.for.value.class=\ + The concrete class {1} is not allowed to be a super class of the value class {0} either directly or indirectly + +# 0: symbol, 1: symbol, 2: type +compiler.err.super.class.method.cannot.be.synchronized=\ + The method {0} in the super class {2} of the value class {1} is synchronized. This is disallowed + +compiler.err.non.abstract.value.class.cant.be.sealed.or.non.sealed=\ + ''sealed'' or ''non-sealed'' modifiers are only applicable to abstract value classes + +# 0: symbol +compiler.err.strict.field.not.have.been.initialized.before.super=\ + strict field {0} is not initialized before the supertype constructor has been called + # 0: symbol compiler.err.deconstruction.pattern.only.records=\ deconstruction patterns can only be applied to records, {0} is not a record @@ -4292,7 +4328,15 @@ compiler.warn.attempt.to.synchronize.on.instance.of.value.based.class=\ compiler.warn.attempt.to.use.value.based.where.identity.expected=\ use of a value-based class with an operation that expects reliable identity +# 0: symbol or name +# lint: initialization +compiler.warn.would.not.be.allowed.in.prologue=\ + reference to {0} would not be allowed in the prologue phase + # 0: type compiler.err.enclosing.class.type.non.denotable=\ enclosing class type: {0}\n\ is non-denotable, try casting to a denotable type + +compiler.warn.value.finalize=\ + value classes should not have finalize methods, they are not invoked diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties index 6c3238cfdfe..2ba6687ef47 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties @@ -219,12 +219,19 @@ javac.opt.Xlint.desc.finally=\ javac.opt.Xlint.desc.incubating=\ Warn about use of incubating modules. +javac.opt.Xlint.desc.initialization=\ + Warn about code in identity classes that wouldn''t be allowed in early\n\ +\ construction due to a this dependency. + javac.opt.Xlint.desc.lossy-conversions=\ Warn about possible lossy conversions in compound assignment. javac.opt.Xlint.desc.module=\ Warn about module system related issues. +javac.opt.Xlint.desc.migration=\ + Warn about issues related to migration of JDK classes. + javac.opt.Xlint.desc.opens=\ Warn about issues regarding module opens. diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java index ae946c5b6d6..4e8b2dcbf5e 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java @@ -25,13 +25,10 @@ package com.sun.tools.javac.tree; - - import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; import com.sun.tools.javac.code.*; import com.sun.tools.javac.code.Symbol.RecordComponent; -import com.sun.tools.javac.comp.AttrContext; import com.sun.tools.javac.comp.Env; import com.sun.tools.javac.tree.JCTree.*; import com.sun.tools.javac.tree.JCTree.JCPolyExpression.*; @@ -190,21 +187,24 @@ public static boolean isIdentOrThisDotIdent(JCTree tree) { * but also NOT an enclosing outer class of 'currentClass'. */ public static boolean isExplicitThisReference(Types types, Type.ClassType currentClass, JCTree tree) { + Symbol.ClassSymbol currentClassSym = (Symbol.ClassSymbol) types.erasure(currentClass).tsym; switch (tree.getTag()) { case PARENS: return isExplicitThisReference(types, currentClass, skipParens(tree)); case IDENT: { JCIdent ident = (JCIdent)tree; Names names = ident.name.table.names; - return ident.name == names._this || ident.name == names._super; + return ident.name == names._this && tree.type.tsym == currentClass.tsym || + ident.name == names._super && + (tree.type.tsym == currentClass.tsym || + currentClassSym.isSubClass(tree.type.tsym, types)); } case SELECT: { JCFieldAccess select = (JCFieldAccess)tree; Type selectedType = types.erasure(select.selected.type); if (!selectedType.hasTag(TypeTag.CLASS)) return false; - Symbol.ClassSymbol currentClassSym = (Symbol.ClassSymbol)((Type.ClassType)types.erasure(currentClass)).tsym; - Symbol.ClassSymbol selectedClassSym = (Symbol.ClassSymbol)((Type.ClassType)selectedType).tsym; + Symbol.ClassSymbol selectedClassSym = (Symbol.ClassSymbol)(selectedType).tsym; Names names = select.name.table.names; return currentClassSym.isSubClass(selectedClassSym, types) && (select.name == names._super || diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Bits.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Bits.java index fe795245610..3b73b1cbf01 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Bits.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Bits.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -88,7 +88,10 @@ static BitsState getState(int[] someBits, boolean reset) { private static final int wordshift = 5; private static final int wordmask = wordlen - 1; - public int[] bits = null; + /* every int in the bits array is used to represent 32 bits, so the bits array will have + * length == 1 until we need to represent the 33rd bit and so on. + */ + private int[] bits = null; // This field will store last version of bits after every change. private static final int[] unassignedBits = new int[0]; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java index f5df8baddbd..30374a40824 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java @@ -114,6 +114,7 @@ public static Names instance(Context context) { public final Name java_lang_Cloneable; public final Name java_lang_Enum; public final Name java_lang_Object; + public final Name java_lang_System; // names of builtin classes public final Name Array; @@ -152,6 +153,7 @@ public static Names instance(Context context) { public final Name ModuleResolution; public final Name NestHost; public final Name NestMembers; + public final Name LoadableDescriptors; public final Name Record; public final Name RuntimeInvisibleAnnotations; public final Name RuntimeInvisibleParameterAnnotations; @@ -206,6 +208,10 @@ public static Names instance(Context context) { public final Name makeConcat; public final Name makeConcatWithConstants; + // values + public final Name dollarValue; + + // record related // members of java.lang.runtime.ObjectMethods public final Name bootstrap; @@ -306,6 +312,7 @@ public Names(Context context) { java_lang_Cloneable = fromString("java.lang.Cloneable"); java_lang_Enum = fromString("java.lang.Enum"); java_lang_Object = fromString("java.lang.Object"); + java_lang_System = fromString("java.lang.System"); // names of builtin classes Array = fromString("Array"); @@ -344,6 +351,7 @@ public Names(Context context) { ModuleResolution = fromString("ModuleResolution"); NestHost = fromString("NestHost"); NestMembers = fromString("NestMembers"); + LoadableDescriptors = fromString("LoadableDescriptors"); Record = fromString("Record"); RuntimeInvisibleAnnotations = fromString("RuntimeInvisibleAnnotations"); RuntimeInvisibleParameterAnnotations = fromString("RuntimeInvisibleParameterAnnotations"); @@ -397,6 +405,8 @@ public Names(Context context) { makeConcat = fromString("makeConcat"); makeConcatWithConstants = fromString("makeConcatWithConstants"); + dollarValue = fromString("$value"); + bootstrap = fromString("bootstrap"); record = fromString("record"); non = fromString("non"); @@ -443,6 +453,10 @@ public UnsharedNameTable newUnsharedNameTable() { return UnsharedNameTable.create(this); } + public boolean isInit(Name name) { + return name == init; + } + public void dispose() { table.dispose(); } diff --git a/src/jdk.compiler/share/classes/module-info.java b/src/jdk.compiler/share/classes/module-info.java index 33cff9379f2..c8947abe7bf 100644 --- a/src/jdk.compiler/share/classes/module-info.java +++ b/src/jdk.compiler/share/classes/module-info.java @@ -166,6 +166,8 @@ * {@code finally} {@code finally} clauses that do not terminate normally * {@code identity} use of a value-based class where an identity class is expected * {@code incubating} use of incubating modules + * {@code initialization} code in identity classes that wouldn't be allowed in early + * construction due to a {@code this} dependency. * {@code lossy-conversions} possible lossy conversions in compound assignment * {@code missing-explicit-ctor} missing explicit constructors in public and protected classes * in exported packages @@ -243,6 +245,7 @@ exports com.sun.tools.javac.resources to jdk.jshell; exports com.sun.tools.javac.code to + jdk.jdeps, jdk.javadoc, jdk.jshell; exports com.sun.tools.javac.comp to @@ -252,6 +255,7 @@ jdk.jdeps, jdk.javadoc; exports com.sun.tools.javac.jvm to + jdk.jdeps, jdk.javadoc; exports com.sun.tools.javac.main to jdk.javadoc, diff --git a/src/jdk.compiler/share/man/javac.md b/src/jdk.compiler/share/man/javac.md index 997023487b0..50d4cf77cea 100644 --- a/src/jdk.compiler/share/man/javac.md +++ b/src/jdk.compiler/share/man/javac.md @@ -601,6 +601,9 @@ file system locations may be directories, JAR files or JMOD files. - `incubating`: Warns about the use of incubating modules. + - `initialization`: Warns about code in identity classes that wouldn't be + allowed in early construction due to a `this` dependency. + - `lossy-conversions`: Warns about possible lossy conversions in compound assignment. diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/interpreter/Bytecodes.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/interpreter/Bytecodes.java index 1e061568bc4..b21665d2f31 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/interpreter/Bytecodes.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/interpreter/Bytecodes.java @@ -241,50 +241,54 @@ public class Bytecodes { public static final int _goto_w = 200; // 0xc8 public static final int _jsr_w = 201; // 0xc9 public static final int _breakpoint = 202; // 0xca + public static final int _aconst_init = 203; // 0xcb + public static final int _withfield = 204; // 0xcc - public static final int number_of_java_codes = 203; + public static final int number_of_java_codes = 205; // JVM bytecodes public static final int _fast_agetfield = number_of_java_codes; - public static final int _fast_bgetfield = 204; - public static final int _fast_cgetfield = 205; - public static final int _fast_dgetfield = 206; - public static final int _fast_fgetfield = 207; - public static final int _fast_igetfield = 208; - public static final int _fast_lgetfield = 209; - public static final int _fast_sgetfield = 210; - public static final int _fast_aputfield = 211; - public static final int _fast_bputfield = 212; - public static final int _fast_zputfield = 213; - public static final int _fast_cputfield = 214; - public static final int _fast_dputfield = 215; - public static final int _fast_fputfield = 216; - public static final int _fast_iputfield = 217; - public static final int _fast_lputfield = 218; - public static final int _fast_sputfield = 219; - public static final int _fast_aload_0 = 220; - public static final int _fast_iaccess_0 = 221; - public static final int _fast_aaccess_0 = 222; - public static final int _fast_faccess_0 = 223; - public static final int _fast_iload = 224; - public static final int _fast_iload2 = 225; - public static final int _fast_icaload = 226; - public static final int _fast_invokevfinal = 227; - public static final int _fast_linearswitch = 228; - public static final int _fast_binaryswitch = 229; - public static final int _fast_aldc = 230; - public static final int _fast_aldc_w = 231; - public static final int _return_register_finalizer = 232; - public static final int _invokehandle = 233; + public static final int _fast_qgetfield = 206; + public static final int _fast_bgetfield = 207; + public static final int _fast_cgetfield = 208; + public static final int _fast_dgetfield = 209; + public static final int _fast_fgetfield = 210; + public static final int _fast_igetfield = 211; + public static final int _fast_lgetfield = 212; + public static final int _fast_sgetfield = 213; + public static final int _fast_aputfield = 214; + public static final int _fast_qputfield = 215; + public static final int _fast_bputfield = 216; + public static final int _fast_zputfield = 217; + public static final int _fast_cputfield = 218; + public static final int _fast_dputfield = 219; + public static final int _fast_fputfield = 220; + public static final int _fast_iputfield = 221; + public static final int _fast_lputfield = 222; + public static final int _fast_sputfield = 223; + public static final int _fast_aload_0 = 224; + public static final int _fast_iaccess_0 = 225; + public static final int _fast_aaccess_0 = 226; + public static final int _fast_faccess_0 = 227; + public static final int _fast_iload = 228; + public static final int _fast_iload2 = 229; + public static final int _fast_icaload = 230; + public static final int _fast_invokevfinal = 231; + public static final int _fast_linearswitch = 232; + public static final int _fast_binaryswitch = 233; + public static final int _fast_aldc = 234; + public static final int _fast_aldc_w = 235; + public static final int _return_register_finalizer = 236; + public static final int _invokehandle = 237; // Bytecodes rewritten at CDS dump time - public static final int _nofast_getfield = 234; - public static final int _nofast_putfield = 235; - public static final int _nofast_aload_0 = 236; - public static final int _nofast_iload = 237; - public static final int _shouldnotreachhere = 238; // For debugging + public static final int _nofast_getfield = 238; + public static final int _nofast_putfield = 239; + public static final int _nofast_aload_0 = 240; + public static final int _nofast_iload = 241; + public static final int _shouldnotreachhere = 242; // For debugging - public static final int number_of_codes = 239; + public static final int number_of_codes = 243; // Flag bits derived from format strings, can_trap, can_rewrite, etc.: // semantic flags: diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/memory/FileMapInfo.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/memory/FileMapInfo.java index 9d5c0dec9fb..a08eac85459 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/memory/FileMapInfo.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/memory/FileMapInfo.java @@ -116,7 +116,7 @@ private static void initialize(TypeDataBase db) { } private static void populateMetadataTypeArray(TypeDataBase db) { - metadataTypeArray = new Type[11]; + metadataTypeArray = new Type[14]; // The order needs to match up with CPP_VTABLE_TYPES_DO in src/hotspot/share/cds/cppVtables.cpp metadataTypeArray[0] = db.lookupType("ConstantPool"); @@ -130,6 +130,9 @@ private static void populateMetadataTypeArray(TypeDataBase db) { metadataTypeArray[8] = db.lookupType("MethodCounters"); metadataTypeArray[9] = db.lookupType("ObjArrayKlass"); metadataTypeArray[10] = db.lookupType("TypeArrayKlass"); + metadataTypeArray[11] = db.lookupType("FlatArrayKlass"); + metadataTypeArray[12] = db.lookupType("InlineKlass"); + metadataTypeArray[11] = db.lookupType("RefArrayKlass"); } public FileMapHeader getHeader() { diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/FlatArrayKlass.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/FlatArrayKlass.java new file mode 100644 index 00000000000..4cafad9e5ee --- /dev/null +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/FlatArrayKlass.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +package sun.jvm.hotspot.oops; + +import java.io.*; +import java.util.*; +import sun.jvm.hotspot.debugger.*; +import sun.jvm.hotspot.runtime.*; +import sun.jvm.hotspot.types.*; +import sun.jvm.hotspot.utilities.*; +import sun.jvm.hotspot.utilities.Observable; +import sun.jvm.hotspot.utilities.Observer; + +// FlatArrayKlass is a proxy for FlatArrayKlass in the JVM + +public class FlatArrayKlass extends ArrayKlass { + static { + VM.registerVMInitializedObserver(new Observer() { + public void update(Observable o, Object data) { + initialize(VM.getVM().getTypeDataBase()); + } + }); + } + + private static synchronized void initialize(TypeDataBase db) throws WrongTypeException { + Type t = db.lookupType("FlatArrayKlass"); + // TODO: implement similar features as in ObjArrayKlass + } + + public FlatArrayKlass(Address addr) { + super(addr); + } + + public void printValueOn(PrintStream tty) { + tty.print("FlatArrayKlass"); + } +} diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/InlineKlass.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/InlineKlass.java new file mode 100644 index 00000000000..6015fd94500 --- /dev/null +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/InlineKlass.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +package sun.jvm.hotspot.oops; + +import java.io.*; +import java.util.*; +import sun.jvm.hotspot.debugger.*; +import sun.jvm.hotspot.memory.*; +import sun.jvm.hotspot.runtime.*; +import sun.jvm.hotspot.types.*; +import sun.jvm.hotspot.utilities.*; +import sun.jvm.hotspot.utilities.Observable; +import sun.jvm.hotspot.utilities.Observer; + +// An InstanceKlass is the VM level representation of a Java class. + +public class InlineKlass extends InstanceKlass { + static { + VM.registerVMInitializedObserver(new Observer() { + public void update(Observable o, Object data) { + initialize(VM.getVM().getTypeDataBase()); + } + }); + } + + private static synchronized void initialize(TypeDataBase db) throws WrongTypeException { + // Just make sure it's there for now + Type type = db.lookupType("InlineKlass"); + } + + public InlineKlass(Address addr) { + super(addr); + } +} diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Klass.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Klass.java index 1b8a9d0ef69..7f8caabd655 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Klass.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Klass.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -49,7 +49,7 @@ public void update(Observable o, Object data) { public static int LH_HEADER_SIZE_SHIFT; public static int LH_ARRAY_TAG_SHIFT; public static int LH_ARRAY_TAG_TYPE_VALUE; - public static int LH_ARRAY_TAG_OBJ_VALUE; + private static synchronized void initialize(TypeDataBase db) throws WrongTypeException { Type type = db.lookupType("Klass"); @@ -74,7 +74,6 @@ private static synchronized void initialize(TypeDataBase db) throws WrongTypeExc LH_HEADER_SIZE_SHIFT = db.lookupIntConstant("Klass::_lh_header_size_shift").intValue(); LH_ARRAY_TAG_SHIFT = db.lookupIntConstant("Klass::_lh_array_tag_shift").intValue(); LH_ARRAY_TAG_TYPE_VALUE = db.lookupIntConstant("Klass::_lh_array_tag_type_value").intValue(); - LH_ARRAY_TAG_OBJ_VALUE = db.lookupIntConstant("Klass::_lh_array_tag_obj_value").intValue(); } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Metadata.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Metadata.java index dfbd67ae805..2542a72ebe1 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Metadata.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Metadata.java @@ -64,8 +64,10 @@ private static synchronized void initialize(TypeDataBase db) throws WrongTypeExc metadataConstructor.addMapping("InstanceRefKlass", InstanceRefKlass.class); metadataConstructor.addMapping("InstanceClassLoaderKlass", InstanceClassLoaderKlass.class); metadataConstructor.addMapping("InstanceStackChunkKlass", InstanceStackChunkKlass.class); + metadataConstructor.addMapping("InlineKlass", InlineKlass.class); metadataConstructor.addMapping("TypeArrayKlass", TypeArrayKlass.class); metadataConstructor.addMapping("ObjArrayKlass", ObjArrayKlass.class); + metadataConstructor.addMapping("FlatArrayKlass", FlatArrayKlass.class); metadataConstructor.addMapping("Method", Method.class); metadataConstructor.addMapping("MethodData", MethodData.class); metadataConstructor.addMapping("ConstMethod", ConstMethod.class); diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Method.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Method.java index 75dec8edbd1..28ee11a89eb 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Method.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Method.java @@ -68,6 +68,7 @@ private static synchronized void initialize(TypeDataBase db) throws WrongTypeExc */ objectInitializerName = null; + valueFactoryName = null; classInitializerName = null; } @@ -97,6 +98,7 @@ public Method(Address addr) { // constant method names - , // Initialized lazily to avoid initialization ordering dependencies between ArrayKlass and String private static String objectInitializerName; + private static String valueFactoryName; private static String classInitializerName; private static String objectInitializerName() { if (objectInitializerName == null) { @@ -104,6 +106,12 @@ private static String objectInitializerName() { } return objectInitializerName; } + private static String valueFactoryName() { + if (valueFactoryName == null) { + valueFactoryName = ""; + } + return classInitializerName; + } private static String classInitializerName() { if (classInitializerName == null) { classInitializerName = ""; @@ -253,7 +261,7 @@ public Symbol getGenericSignature() { public boolean isSynthetic() { return getAccessFlagsObj().isSynthetic(); } public boolean isConstructor() { - return (!isStatic()) && getName().equals(objectInitializerName()); + return (!isStatic()) && (getName().equals(objectInitializerName()) || getName().equals(valueFactoryName())); } public boolean isStaticInitializer() { diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/jcore/ClassWriter.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/jcore/ClassWriter.java index e1d40b586b4..af35b5ea947 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/jcore/ClassWriter.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/jcore/ClassWriter.java @@ -371,11 +371,12 @@ protected void writeSuperClass() throws IOException { protected void writeInterfaces() throws IOException { KlassArray interfaces = klass.getLocalInterfaces(); final int len = interfaces.length(); + int nb_interfaces = len; - if (DEBUG) debugMessage("number of interfaces = " + len); + if (DEBUG) debugMessage("number of interfaces = " + nb_interfaces); // write interfaces count - dos.writeShort((short) len); + dos.writeShort((short) nb_interfaces); for (int i = 0; i < len; i++) { Klass k = interfaces.getAt(i); Short index = classToIndex.get(k.getName().asString()); diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/ObjectReader.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/ObjectReader.java index acdd3d033e4..9de499154a2 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/ObjectReader.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/utilities/ObjectReader.java @@ -642,7 +642,7 @@ public Class readClass(InstanceKlass kls) throws ClassNotFoundException { public Object readMethodOrConstructor(sun.jvm.hotspot.oops.Method m) throws NoSuchMethodException, ClassNotFoundException { String name = m.getName().asString(); - if (name.equals("")) { + if (name.equals("") || name.equals("")) { return readConstructor(m); } else { return readMethod(m); diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotMethodData.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotMethodData.java index 2478b97a5b2..938b7d872c1 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotMethodData.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotMethodData.java @@ -104,6 +104,9 @@ static final class VMState { new VirtualCallTypeData(this, config.dataLayoutVirtualCallTypeDataTag), new UnknownProfileData(this, config.dataLayoutParametersTypeDataTag), new UnknownProfileData(this, config.dataLayoutSpeculativeTrapDataTag), + new UnknownProfileData(this, config.dataLayoutArrayStoreDataTag), + new UnknownProfileData(this, config.dataLayoutArrayLoadDataTag), + new UnknownProfileData(this, config.dataLayoutACmpDataTag), }; // @formatter:on diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotMethodDataAccessor.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotMethodDataAccessor.java index bde0ea3c5d5..b623c3d85d3 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotMethodDataAccessor.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotMethodDataAccessor.java @@ -57,7 +57,7 @@ int getTag() { static int readTag(HotSpotVMConfig config, HotSpotMethodData data, int position) { final int tag = data.readUnsignedByte(position, config.dataLayoutTagOffset); - assert tag >= config.dataLayoutNoTag && tag <= config.dataLayoutSpeculativeTrapDataTag : "profile data tag out of bounds: " + tag; + assert tag >= config.dataLayoutNoTag && tag <= config.dataLayoutACmpDataTag : "profile data tag out of bounds: " + tag; return tag; } diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotModifiers.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotModifiers.java index ba1ea96eb1c..d2067c14a56 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotModifiers.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotModifiers.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 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 @@ -22,6 +22,8 @@ */ package jdk.vm.ci.hotspot; +import jdk.internal.misc.PreviewFeatures; + import static java.lang.reflect.Modifier.ABSTRACT; import static java.lang.reflect.Modifier.FINAL; import static java.lang.reflect.Modifier.INTERFACE; @@ -53,7 +55,8 @@ public class HotSpotModifiers { // @formatter:on public static int jvmClassModifiers() { - return PUBLIC | FINAL | INTERFACE | ABSTRACT | ANNOTATION | ENUM | SYNTHETIC; + return PUBLIC | FINAL | INTERFACE | ABSTRACT | ANNOTATION | ENUM | SYNTHETIC | + (PreviewFeatures.isEnabled() ? 0x0020 : 0); // ACC_IDENTITY temp constant to avoid preview dependency } public static int jvmMethodModifiers() { diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java index 449b315e467..d463ace9181 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/HotSpotVMConfig.java @@ -99,7 +99,7 @@ static String getHostArchitectureName() { final int instanceKlassConstantsOffset = getFieldOffset("InstanceKlass::_constants", Integer.class, "ConstantPool*"); final int instanceKlassFieldInfoStreamOffset = getFieldOffset("InstanceKlass::_fieldinfo_stream", Integer.class, "Array*"); final int instanceKlassAnnotationsOffset = getFieldOffset("InstanceKlass::_annotations", Integer.class, "Annotations*"); - final int instanceKlassMiscFlagsOffset = getFieldOffset("InstanceKlass::_misc_flags._flags", Integer.class, "u2"); + final int instanceKlassMiscFlagsOffset = getFieldOffset("InstanceKlass::_misc_flags._flags", Integer.class, "u4"); final int klassMiscFlagsOffset = getFieldOffset("Klass::_misc_flags._flags", Integer.class, "u1"); final int klassVtableStartOffset = getFieldValue("CompilerToVM::Data::Klass_vtable_start_offset", Integer.class, "int"); final int klassVtableLengthOffset = getFieldValue("CompilerToVM::Data::Klass_vtable_length_offset", Integer.class, "int"); @@ -306,6 +306,9 @@ final int baseVtableLength() { final int dataLayoutVirtualCallTypeDataTag = getConstant("DataLayout::virtual_call_type_data_tag", Integer.class); final int dataLayoutParametersTypeDataTag = getConstant("DataLayout::parameters_type_data_tag", Integer.class); final int dataLayoutSpeculativeTrapDataTag = getConstant("DataLayout::speculative_trap_data_tag", Integer.class); + final int dataLayoutArrayLoadDataTag = getConstant("DataLayout::array_load_data_tag", Integer.class); + final int dataLayoutArrayStoreDataTag = getConstant("DataLayout::array_store_data_tag", Integer.class); + final int dataLayoutACmpDataTag = getConstant("DataLayout::acmp_data_tag", Integer.class); final int bciProfileWidth = getFlag("BciProfileWidth", Integer.class); final int typeProfileWidth = getFlag("TypeProfileWidth", Integer.class); diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaMethod.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaMethod.java index f401bc30f83..958133a6554 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaMethod.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/meta/ResolvedJavaMethod.java @@ -466,7 +466,7 @@ default boolean hasReceiver() { * Determines if this method is {@link java.lang.Object#Object()}. */ default boolean isJavaLangObjectInit() { - return getDeclaringClass().isJavaLangObject() && getName().equals(""); + return getDeclaringClass().isJavaLangObject() && (getName().equals("") || getName().equals("")); } /** diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/javap/AttributeWriter.java b/src/jdk.jdeps/share/classes/com/sun/tools/javap/AttributeWriter.java index 8b7ac158639..e68f1f09110 100644 --- a/src/jdk.jdeps/share/classes/com/sun/tools/javap/AttributeWriter.java +++ b/src/jdk.jdeps/share/classes/com/sun/tools/javap/AttributeWriter.java @@ -32,6 +32,7 @@ import java.lang.classfile.TypeAnnotation; import java.lang.classfile.attribute.*; import java.lang.classfile.constantpool.ModuleEntry; +import java.lang.classfile.constantpool.NameAndTypeEntry; import java.lang.classfile.constantpool.PoolEntry; import java.lang.classfile.constantpool.Utf8Entry; import java.lang.reflect.AccessFlag; @@ -46,6 +47,9 @@ import static java.lang.classfile.attribute.StackMapFrameInfo.*; import static java.lang.classfile.instruction.CharacterRange.*; +import com.sun.tools.javac.util.Assert; +import com.sun.tools.javac.util.StringUtils; + /* * A writer for writing Attributes as text. * @@ -66,6 +70,7 @@ public static AttributeWriter instance(Context context) { protected AttributeWriter(Context context) { super(context); context.put(AttributeWriter.class, this); + classWriter = ClassWriter.instance(context); annotationWriter = AnnotationWriter.instance(context); codeWriter = CodeWriter.instance(context); constantWriter = ConstantWriter.instance(context); @@ -218,7 +223,8 @@ public void write(Attribute a, CodeAttribute lr, ClassFileFormatVersion cffv) indent(+1); first = false; } - for (var flag : maskToAccessFlagsReportUnknown(access_flags, AccessFlag.Location.INNER_CLASS, cffv)) { + var flagSet = maskToAccessFlagsReportUnknown(access_flags, AccessFlag.Location.INNER_CLASS, cffv); + for (var flag : flagSet) { if (flag.sourceModifier() && (flag != AccessFlag.ABSTRACT || !info.has(AccessFlag.INTERFACE))) { print(Modifier.toString(flag.mask()) + " "); @@ -243,6 +249,12 @@ public void write(Attribute a, CodeAttribute lr, ClassFileFormatVersion cffv) constantWriter.write(info.outerClass().get().index()); } println(); + if (options.verbose) { + indent(1); + classWriter.writeList(String.format("flags: (0x%04x) ", access_flags), + flagSet, "\n"); + indent(-1); + } } } if (!first) @@ -534,6 +546,14 @@ public void write(Attribute a, CodeAttribute lr, ClassFileFormatVersion cffv) } indent(-1); } + case LoadableDescriptorsAttribute attr -> { + println("LoadableDescriptors:"); + indent(+1); + for (var sc : attr.loadableDescriptors()) { + println(constantWriter.stringValue(sc)); + } + indent(-1); + } case SignatureAttribute attr -> { print("Signature: #" + attr.signature().index()); tab(); @@ -569,8 +589,27 @@ public void write(Attribute a, CodeAttribute lr, ClassFileFormatVersion cffv) } else { int offsetDelta = lr.labelToBci(frame.target()) - lastOffset - 1; switch (frameType) { + case 246 -> { + printHeader(frameType, "/* early_larval */"); + indent(+1); + println("number of unset_fields = " + frame.unsetFields().size()); + indent(+1); + for (NameAndTypeEntry field : frame.unsetFields()) { + print("unset_field = #"); + constantWriter.write(field.index()); + println(); + } + // temporary: print the nested contents of early larval + indent(+1); + println("offset_delta = " + offsetDelta); + printMap("locals", frame.locals(), lr); + printMap("stack", frame.stack(), lr); + indent(-1); + indent(-1); + indent(-1); + } case 247 -> { - printHeader(frameType, "/* same_locals_1_stack_item_frame_extended */"); + printHeader(frameType, "/* same_locals_1_stack_item_entry_extended */"); indent(+1); println("offset_delta = " + offsetDelta); printMap("stack", frame.stack(), lr); @@ -583,7 +622,7 @@ public void write(Attribute a, CodeAttribute lr, ClassFileFormatVersion cffv) indent(-1); } case 251 -> { - printHeader(frameType, "/* same_frame_extended */"); + printHeader(frameType, "/* same_entry_extended */"); indent(+1); println("offset_delta = " + offsetDelta); indent(-1); @@ -598,7 +637,7 @@ public void write(Attribute a, CodeAttribute lr, ClassFileFormatVersion cffv) indent(-1); } case 255 -> { - printHeader(frameType, "/* full_frame */"); + printHeader(frameType, "/* full_entry */"); indent(+1); println("offset_delta = " + offsetDelta); printMap("locals", frame.locals(), lr); @@ -752,6 +791,7 @@ static String toHex(byte[] ba) { return sb.toString(); } + private final ClassWriter classWriter; private final AnnotationWriter annotationWriter; private final CodeWriter codeWriter; private final ConstantWriter constantWriter; diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/javap/BasicWriter.java b/src/jdk.jdeps/share/classes/com/sun/tools/javap/BasicWriter.java index 1780eb1297d..1807c650148 100644 --- a/src/jdk.jdeps/share/classes/com/sun/tools/javap/BasicWriter.java +++ b/src/jdk.jdeps/share/classes/com/sun/tools/javap/BasicWriter.java @@ -59,6 +59,8 @@ protected Set flagsReportUnknown(AccessFlags flags, ClassFileFormatV } protected Set maskToAccessFlagsReportUnknown(int mask, AccessFlag.Location location, ClassFileFormatVersion cffv) { + if (cffv == null) + cffv = ClassFileFormatVersion.CURRENT_PREVIEW_FEATURES; // Aggressive fallback try { return AccessFlag.maskToAccessFlags(mask, location, cffv); } catch (IllegalArgumentException ex) { diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/javap/ClassWriter.java b/src/jdk.jdeps/share/classes/com/sun/tools/javap/ClassWriter.java index c5c75d8848c..8708f3a4523 100644 --- a/src/jdk.jdeps/share/classes/com/sun/tools/javap/ClassWriter.java +++ b/src/jdk.jdeps/share/classes/com/sun/tools/javap/ClassWriter.java @@ -25,6 +25,8 @@ package com.sun.tools.javap; +import com.sun.tools.javac.code.Source; + import java.lang.classfile.AccessFlags; import java.lang.classfile.Attributes; import java.lang.classfile.ClassFile; @@ -121,8 +123,7 @@ protected ClassFileFormatVersion cffv() { // something not representable by CFFV, let's fall back return ClassFileFormatVersion.latest(); if (major >= JAVA_12_VERSION && classModel.minorVersion() != 0) { - // preview versions aren't explicitly supported, but latest is good enough for now - return ClassFileFormatVersion.latest(); + return ClassFileFormatVersion.CURRENT_PREVIEW_FEATURES; } return ClassFileFormatVersion.fromMajor(major); } @@ -166,7 +167,7 @@ public boolean write(ClassModel cm) { indent(-1); } - writeModifiers(getClassModifiers(cm.flags())); + writeModifiers(getClassModifiers(cm.flags(), classModel.majorVersion(), classModel.minorVersion())); if ((classModel.flags().flagsMask() & ACC_MODULE) != 0) { var attr = classModel.findAttribute(Attributes.module()); @@ -812,6 +813,20 @@ private Set getClassModifiers(AccessFlags flags) { return getModifiers(set); } + private Set getClassModifiers(AccessFlags flags, int majorVersion, int minorVersion) { + boolean previewClassFile = minorVersion == ClassFile.PREVIEW_MINOR_VERSION; + Set flagSet = flagsReportUnknown(flags, cffv()); + if (flagSet.contains(AccessFlag.INTERFACE)) { + flagSet = EnumSet.copyOf(flagSet); + flagSet.remove(AccessFlag.ABSTRACT); + } else if (Source.isSupported(Source.Feature.VALUE_CLASSES, majorVersion) && previewClassFile) { + Set classModifers = getModifiers(flagSet); + classModifers.add("value"); + return classModifers; + } + return getModifiers(flagSet); + } + private static Set getModifiers(Set flags) { Set s = new LinkedHashSet<>(); for (var f : flags) diff --git a/src/jdk.jdeps/share/classes/com/sun/tools/javap/StackMapWriter.java b/src/jdk.jdeps/share/classes/com/sun/tools/javap/StackMapWriter.java index 5b7de2a4565..fa7528ddaa0 100644 --- a/src/jdk.jdeps/share/classes/com/sun/tools/javap/StackMapWriter.java +++ b/src/jdk.jdeps/share/classes/com/sun/tools/javap/StackMapWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ package com.sun.tools.javap; +import java.lang.classfile.constantpool.NameAndTypeEntry; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -98,10 +99,29 @@ private void writeDetails(int pc) { if (m != null) { print("StackMap locals: ", m.locals(), true); print("StackMap stack: ", m.stack(), false); + if (m.locals().contains(StackMapFrameInfo.SimpleVerificationTypeInfo.UNINITIALIZED_THIS)) { + printFields("StackMap unset fields: ", m.unsetFields()); + } } } + void printFields(String label, List entries) { + print(label); + boolean first = true; + for (var e : entries) { + if (!first) { + print(", "); + } else { + first = false; + } + print(e::name); + print(":"); + print(e::type); + } + println(); + } + void print(String label, List entries, boolean firstThis) { print(label); diff --git a/src/jdk.jdi/share/classes/com/sun/tools/example/debug/tty/BreakpointSpec.java b/src/jdk.jdi/share/classes/com/sun/tools/example/debug/tty/BreakpointSpec.java index b2d86f7f9e3..eed4bc24e66 100644 --- a/src/jdk.jdi/share/classes/com/sun/tools/example/debug/tty/BreakpointSpec.java +++ b/src/jdk.jdi/share/classes/com/sun/tools/example/debug/tty/BreakpointSpec.java @@ -215,6 +215,7 @@ private Location location(ReferenceType refType) throws private boolean isValidMethodName(String s) { return isJavaIdentifier(s) || s.equals("") || + s.equals("") || s.equals(""); } diff --git a/src/jdk.jdi/share/classes/com/sun/tools/jdi/MethodImpl.java b/src/jdk.jdi/share/classes/com/sun/tools/jdi/MethodImpl.java index 27cfa481201..7714ce999d8 100644 --- a/src/jdk.jdi/share/classes/com/sun/tools/jdi/MethodImpl.java +++ b/src/jdk.jdi/share/classes/com/sun/tools/jdi/MethodImpl.java @@ -216,7 +216,7 @@ public boolean isBridge() { } public boolean isConstructor() { - return name().equals(""); + return name().equals("") || name().equals(""); } public boolean isStaticInitializer() { diff --git a/src/jdk.jdi/share/classes/com/sun/tools/jdi/ObjectReferenceImpl.java b/src/jdk.jdi/share/classes/com/sun/tools/jdi/ObjectReferenceImpl.java index f71591b8ef5..1f9024b7cd2 100644 --- a/src/jdk.jdi/share/classes/com/sun/tools/jdi/ObjectReferenceImpl.java +++ b/src/jdk.jdi/share/classes/com/sun/tools/jdi/ObjectReferenceImpl.java @@ -148,7 +148,7 @@ public boolean vmNotSuspended(VMAction action) { public boolean equals(Object obj) { if (obj instanceof ObjectReferenceImpl other) { return (ref() == other.ref()) && - super.equals(obj); + super.equals(obj); } else { return false; } diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/util.c b/src/jdk.jdwp.agent/share/native/libjdwp/util.c index 45de2ba7b7a..4d6d69ad408 100644 --- a/src/jdk.jdwp.agent/share/native/libjdwp/util.c +++ b/src/jdk.jdwp.agent/share/native/libjdwp/util.c @@ -351,7 +351,6 @@ writeFieldValue(JNIEnv *env, PacketOutputStream *out, jobject object, /* * For primitive types, the type key is bounced back as is. */ - (void)outStream_writeByte(out, typeKey); switch (typeKey) { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventClassBuilder.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventClassBuilder.java index ed9e1e43625..059792d057b 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventClassBuilder.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventClassBuilder.java @@ -39,6 +39,9 @@ import java.lang.classfile.ClassFile; import java.lang.classfile.Label; import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; + +import jdk.internal.misc.PreviewFeatures; + import jdk.jfr.AnnotationElement; import jdk.jfr.Event; import jdk.jfr.ValueDescriptor; @@ -108,9 +111,10 @@ private void buildConstructor(ClassBuilder builder) { })); } + @SuppressWarnings("preview") private void buildClassInfo(ClassBuilder builder) { builder.withSuperclass(Bytecode.classDesc(Event.class)); - builder.withFlags(AccessFlag.FINAL, AccessFlag.PUBLIC, AccessFlag.SUPER); + builder.withFlags(AccessFlag.FINAL, AccessFlag.PUBLIC, (PreviewFeatures.isEnabled() ? AccessFlag.IDENTITY : AccessFlag.SUPER)); List annotations = new ArrayList<>(); for (jdk.jfr.AnnotationElement a : annotationElements) { List list = new ArrayList<>(); diff --git a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java index b0a27d368ff..47caff7d2ad 100644 --- a/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java +++ b/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java @@ -891,6 +891,7 @@ public void freeMemory(long address) { */ @Deprecated(since="18", forRemoval=true) @ForceInline + @SuppressWarnings("preview") public long objectFieldOffset(Field f) { if (f == null) { throw new NullPointerException(); @@ -902,6 +903,9 @@ public long objectFieldOffset(Field f) { if (declaringClass.isRecord()) { throw new UnsupportedOperationException("can't get field offset on a record class: " + f); } + if (declaringClass.isValue()) { + throw new UnsupportedOperationException("can't get field offset on a value class: " + f); + } beforeMemoryAccess(); return theInternalUnsafe.objectFieldOffset(f); } @@ -931,6 +935,7 @@ public long objectFieldOffset(Field f) { */ @Deprecated(since="18", forRemoval=true) @ForceInline + @SuppressWarnings("preview") public long staticFieldOffset(Field f) { if (f == null) { throw new NullPointerException(); @@ -942,6 +947,9 @@ public long staticFieldOffset(Field f) { if (declaringClass.isRecord()) { throw new UnsupportedOperationException("can't get field offset on a record class: " + f); } + if (declaringClass.isValue()) { + throw new UnsupportedOperationException("can't get field offset on a value class: " + f); + } beforeMemoryAccess(); return theInternalUnsafe.staticFieldOffset(f); } @@ -963,6 +971,7 @@ public long staticFieldOffset(Field f) { */ @Deprecated(since="18", forRemoval=true) @ForceInline + @SuppressWarnings("preview") public Object staticFieldBase(Field f) { if (f == null) { throw new NullPointerException(); @@ -974,6 +983,9 @@ public Object staticFieldBase(Field f) { if (declaringClass.isRecord()) { throw new UnsupportedOperationException("can't get base address on a record class: " + f); } + if (declaringClass.isValue()) { + throw new UnsupportedOperationException("can't get field offset on a value class: " + f); + } beforeMemoryAccess(); return theInternalUnsafe.staticFieldBase(f); } diff --git a/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/showTypes.filter b/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/showTypes.filter index a50729080a8..d233c1443e7 100644 --- a/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/showTypes.filter +++ b/src/utils/IdealGraphVisualizer/ServerCompiler/src/main/resources/com/sun/hotspot/igv/servercompiler/filters/showTypes.filter @@ -9,7 +9,7 @@ // "[5:]Class" function simplify_reference_type(type) { // Clean up interface lists in reference types. - var m = /(.*)\(.*\)(.*)/.exec(type); + var m = /(.*) \([^\)]*\)(.*)/.exec(type); if (m != null && typeof m[1] != 'undefined' && typeof m[2] != 'undefined') { type = m[1] + m[2]; } @@ -23,6 +23,7 @@ function simplify_reference_type(type) { // Remove fixed input types for calls and simplify references. function simplifyType(type) { + var original_type = type; var callTypeStart = "{0:control, 1:abIO, 2:memory, 3:rawptr:BotPTR, 4:return_address"; if (type.startsWith(callTypeStart)) { // Exclude types of the first five outputs of call-like nodes. @@ -40,6 +41,10 @@ function simplifyType(type) { type = simplify_reference_type(type); } type = type.replace(">=", "≥").replace("<=", "≤"); + if (original_type.indexOf("(flat in array)") !== -1) { + // Always append "flat in array" property last if found in original type. + type += " (flat in array)"; + } return type; } diff --git a/test/docs/TEST.ROOT b/test/docs/TEST.ROOT index 5ca9b1f144f..9be915dd4ff 100644 --- a/test/docs/TEST.ROOT +++ b/test/docs/TEST.ROOT @@ -38,7 +38,7 @@ groups=TEST.groups # Minimum jtreg version -requiredVersion=7.5.2+1 +requiredVersion=7.5.1+1 # Use new module options useNewOptions=true diff --git a/test/hotspot/gtest/code/test_vtableStub.cpp b/test/hotspot/gtest/code/test_vtableStub.cpp index 74b39d017e7..60287ab5a06 100644 --- a/test/hotspot/gtest/code/test_vtableStub.cpp +++ b/test/hotspot/gtest/code/test_vtableStub.cpp @@ -32,24 +32,24 @@ TEST_VM(code, vtableStubs) { // Should be in VM to use locks ThreadInVMfromNative ThreadInVMfromNative(JavaThread::current()); - VtableStubs::find_vtable_stub(0); // min vtable index + VtableStubs::find_vtable_stub(0, false); // min vtable index for (int i = 0; i < 15; i++) { - VtableStubs::find_vtable_stub((1 << i) - 1); - VtableStubs::find_vtable_stub((1 << i)); + VtableStubs::find_vtable_stub((1 << i) - 1, false); + VtableStubs::find_vtable_stub((1 << i), false); } - VtableStubs::find_vtable_stub((1 << 15) - 1); // max vtable index + VtableStubs::find_vtable_stub((1 << 15) - 1, false); // max vtable index } TEST_VM(code, itableStubs) { // Should be in VM to use locks ThreadInVMfromNative ThreadInVMfromNative(JavaThread::current()); - VtableStubs::find_itable_stub(0); // min itable index + VtableStubs::find_itable_stub(0, false); // min itable index for (int i = 0; i < 15; i++) { - VtableStubs::find_itable_stub((1 << i) - 1); - VtableStubs::find_itable_stub((1 << i)); + VtableStubs::find_itable_stub((1 << i) - 1, false); + VtableStubs::find_itable_stub((1 << i), false); } - VtableStubs::find_itable_stub((1 << 15) - 1); // max itable index + VtableStubs::find_itable_stub((1 << 15) - 1, false); // max itable index } #endif diff --git a/test/hotspot/gtest/oops/test_markWord.cpp b/test/hotspot/gtest/oops/test_markWord.cpp index aef2ac14ebc..4139fa958be 100644 --- a/test/hotspot/gtest/oops/test_markWord.cpp +++ b/test/hotspot/gtest/oops/test_markWord.cpp @@ -40,12 +40,24 @@ // The test doesn't work for PRODUCT because it needs WizardMode #ifndef PRODUCT -static void assert_test_pattern(Handle object, const char* pattern) { +template +static void assert_test_pattern(Printable object, const char* pattern) { stringStream st; object->print_on(&st); ASSERT_THAT(st.base(), testing::HasSubstr(pattern)); } +template +static void assert_mark_word_print_pattern(Printable object, const char* pattern) { + if (LockingMode == LM_MONITOR) { + // With heavy monitors, we do not use the mark word. Printing the oop only shows "monitor" regardless of the + // locking state. + assert_test_pattern(object, "monitor"); + } else { + assert_test_pattern(object, pattern); + } +} + class LockerThread : public JavaTestThread { oop _obj; public: @@ -85,13 +97,13 @@ TEST_VM(markWord, printing) { // Thread tries to lock it. { ObjectLocker ol(h_obj, THREAD); - assert_test_pattern(h_obj, "locked"); + assert_mark_word_print_pattern(h_obj, "locked"); } - assert_test_pattern(h_obj, "is_unlocked no_hash"); + assert_mark_word_print_pattern(h_obj, "is_unlocked no_hash"); // Hash the object then print it. intx hash = h_obj->identity_hash(); - assert_test_pattern(h_obj, "is_unlocked hash=0x"); + assert_mark_word_print_pattern(h_obj, "is_unlocked hash=0x"); // Wait gets the lock inflated. { @@ -107,4 +119,152 @@ TEST_VM(markWord, printing) { done.wait_with_safepoint_check(THREAD); // wait till the thread is done. } } + +static void assert_unlocked_state(markWord mark) { + EXPECT_FALSE(mark.has_displaced_mark_helper()); + if (LockingMode == LM_LEGACY) { + EXPECT_FALSE(mark.has_locker()); + } else if (LockingMode == LM_LIGHTWEIGHT) { + EXPECT_FALSE(mark.is_fast_locked()); + } + EXPECT_FALSE(mark.has_monitor()); + EXPECT_FALSE(mark.is_being_inflated()); + EXPECT_FALSE(mark.is_locked()); + EXPECT_TRUE(mark.is_unlocked()); +} + +static void assert_copy_set_hash(markWord mark) { + const intptr_t hash = 4711; + EXPECT_TRUE(mark.has_no_hash()); + markWord copy = mark.copy_set_hash(hash); + EXPECT_EQ(hash, copy.hash()); + EXPECT_FALSE(copy.has_no_hash()); +} + +static void assert_type(markWord mark) { + EXPECT_FALSE(mark.is_flat_array()); + EXPECT_FALSE(mark.is_inline_type()); + EXPECT_FALSE(mark.is_larval_state()); + EXPECT_FALSE(mark.is_null_free_array()); +} + +TEST_VM(markWord, prototype) { + markWord mark = markWord::prototype(); + assert_unlocked_state(mark); + EXPECT_TRUE(mark.is_neutral()); + + assert_type(mark); + + EXPECT_TRUE(mark.has_no_hash()); + EXPECT_FALSE(mark.is_marked()); + EXPECT_TRUE(mark.decode_pointer() == nullptr); + + assert_copy_set_hash(mark); + assert_type(mark); +} + +static void assert_inline_type(markWord mark) { + EXPECT_FALSE(mark.is_flat_array()); + EXPECT_TRUE(mark.is_inline_type()); + EXPECT_FALSE(mark.is_null_free_array()); +} + +TEST_VM(markWord, inline_type_prototype) { + markWord mark = markWord::inline_type_prototype(); + assert_unlocked_state(mark); + EXPECT_FALSE(mark.is_neutral()); + assert_test_pattern(&mark, " inline_type"); + + assert_inline_type(mark); + EXPECT_FALSE(mark.is_larval_state()); + + EXPECT_TRUE(mark.has_no_hash()); + EXPECT_FALSE(mark.is_marked()); + EXPECT_TRUE(mark.decode_pointer() == nullptr); + + markWord larval = mark.enter_larval_state(); + EXPECT_TRUE(larval.is_larval_state()); + assert_inline_type(larval); + assert_test_pattern(&larval, " inline_type=larval"); + + mark = larval.exit_larval_state(); + EXPECT_FALSE(mark.is_larval_state()); + assert_inline_type(mark); + + EXPECT_TRUE(mark.has_no_hash()); + EXPECT_FALSE(mark.is_marked()); + EXPECT_TRUE(mark.decode_pointer() == nullptr); +} + +#if _LP64 + +static void assert_flat_array_type(markWord mark) { + EXPECT_TRUE(mark.is_flat_array()); + EXPECT_FALSE(mark.is_inline_type()); + EXPECT_FALSE(mark.is_larval_state()); +} + +TEST_VM(markWord, null_free_flat_array_prototype) { + markWord mark = markWord::flat_array_prototype(LayoutKind::NON_ATOMIC_FLAT); + assert_unlocked_state(mark); + EXPECT_TRUE(mark.is_neutral()); + + assert_flat_array_type(mark); + EXPECT_TRUE(mark.is_null_free_array()); + + EXPECT_TRUE(mark.has_no_hash()); + EXPECT_FALSE(mark.is_marked()); + EXPECT_TRUE(mark.decode_pointer() == nullptr); + + assert_copy_set_hash(mark); + assert_flat_array_type(mark); + EXPECT_TRUE(mark.is_null_free_array()); + + assert_test_pattern(&mark, " flat_null_free_array"); +} + +TEST_VM(markWord, nullable_flat_array_prototype) { + markWord mark = markWord::flat_array_prototype(LayoutKind::NULLABLE_ATOMIC_FLAT); + assert_unlocked_state(mark); + EXPECT_TRUE(mark.is_neutral()); + + assert_flat_array_type(mark); + EXPECT_FALSE(mark.is_null_free_array()); + + EXPECT_TRUE(mark.has_no_hash()); + EXPECT_FALSE(mark.is_marked()); + EXPECT_TRUE(mark.decode_pointer() == nullptr); + + assert_copy_set_hash(mark); + assert_flat_array_type(mark); + EXPECT_FALSE(mark.is_null_free_array()); + + assert_test_pattern(&mark, " flat_array"); +} + +static void assert_null_free_array_type(markWord mark) { + EXPECT_FALSE(mark.is_flat_array()); + EXPECT_FALSE(mark.is_inline_type()); + EXPECT_FALSE(mark.is_larval_state()); + EXPECT_TRUE(mark.is_null_free_array()); +} + +TEST_VM(markWord, null_free_array_prototype) { + markWord mark = markWord::null_free_array_prototype(); + assert_unlocked_state(mark); + EXPECT_TRUE(mark.is_neutral()); + + assert_null_free_array_type(mark); + + EXPECT_TRUE(mark.has_no_hash()); + EXPECT_FALSE(mark.is_marked()); + EXPECT_TRUE(mark.decode_pointer() == nullptr); + + assert_copy_set_hash(mark); + assert_null_free_array_type(mark); + + assert_test_pattern(&mark, " null_free_array"); +} +#endif // _LP64 + #endif // PRODUCT diff --git a/test/hotspot/gtest/oops/test_objArrayOop.cpp b/test/hotspot/gtest/oops/test_objArrayOop.cpp index 22c9b2efc11..112a08f9a53 100644 --- a/test/hotspot/gtest/oops/test_objArrayOop.cpp +++ b/test/hotspot/gtest/oops/test_objArrayOop.cpp @@ -22,9 +22,12 @@ */ #include "oops/objArrayOop.hpp" +#include "oops/refArrayOop.hpp" #include "unittest.hpp" #include "utilities/globalDefinitions.hpp" +// TODO FIXME This test needs to be rewritten after objArray/refArray/flatArray rework + TEST_VM(objArrayOop, osize) { static const struct { int objal; bool ccp; bool coops; bool coh; int result; @@ -57,7 +60,7 @@ TEST_VM(objArrayOop, osize) { for (int i = 0; x[i].result != -1; i++) { if (x[i].objal == (int)ObjectAlignmentInBytes && x[i].ccp == UseCompressedClassPointers && x[i].coops == UseCompressedOops && x[i].coh == UseCompactObjectHeaders) { - EXPECT_EQ(objArrayOopDesc::object_size(1), (size_t)x[i].result); + EXPECT_EQ(refArrayOopDesc::object_size(1), (size_t)x[i].result); } } } diff --git a/test/hotspot/jtreg/ProblemList-zgc.txt b/test/hotspot/jtreg/ProblemList-zgc.txt index 9571c717641..9e27f5d1d70 100644 --- a/test/hotspot/jtreg/ProblemList-zgc.txt +++ b/test/hotspot/jtreg/ProblemList-zgc.txt @@ -125,3 +125,4 @@ compiler/jvmci/jdk.vm.ci.code.test/src/jdk/vm/ci/code/test/NativeCallTest.java compiler/jvmci/jdk.vm.ci.code.test/src/jdk/vm/ci/code/test/SimpleCodeInstallationTest.java 8343233 generic-aarch64 compiler/jvmci/jdk.vm.ci.code.test/src/jdk/vm/ci/code/test/SimpleDebugInfoTest.java 8343233 generic-aarch64 compiler/jvmci/jdk.vm.ci.code.test/src/jdk/vm/ci/code/test/VirtualObjectDebugInfoTest.java 8343233 generic-aarch64 +compiler/jvmci/jdk.vm.ci.code.test/src/jdk/vm/ci/code/test/MethodTagTest.java 8343233 generic-aarch64 diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index 37986c67dd8..8a3f0689e79 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -79,6 +79,14 @@ compiler/c2/TestVerifyConstraintCasts.java 8355574 generic-all compiler/c2/aarch64/TestStaticCallStub.java 8359963 linux-aarch64,macosx-aarch64 +compiler/jvmci/jdk.vm.ci.hotspot.test/src/jdk/vm/ci/hotspot/test/MemoryAccessProviderTest.java 8350208 generic-all +compiler/jvmci/jdk.vm.ci.hotspot.test/src/jdk/vm/ci/hotspot/test/TestHotSpotResolvedJavaField.java 8350208 generic-all + +# Valhalla +compiler/regalloc/TestVerifyRegisterAllocator.java 8365895 windows-x64 +compiler/types/TestArrayManyDimensions.java 8365895 windows-x64 +compiler/types/correctness/OffTest.java 8365895 windows-x64 + ############################################################################# # :hotspot_gc @@ -97,6 +105,7 @@ gc/shenandoah/TestEvilSyncBug.java#generational 8345501 generic-all # :hotspot_runtime +runtime/cds/appcds/redefineClass/RedefineRunningMethods_Shared.java 8304168 generic-all runtime/cds/DeterministicDump.java 8363986 macosx-x64,macosx-aarch64 runtime/jni/terminatedThread/TestTerminatedThread.java 8317789 aix-ppc64 runtime/Monitor/SyncOnValueBasedClassTest.java 8340995 linux-s390x @@ -117,6 +126,18 @@ containers/docker/TestMemoryAwareness.java 8303470 linux-all containers/docker/TestJFREvents.java 8327723 linux-x64 containers/docker/TestJcmdWithSideCar.java 8341518 linux-x64 + +# Valhalla +runtime/valhalla/inlinetypes/verifier/StrictInstanceFieldsTest.java CODETOOLS-7904031 generic-all +runtime/valhalla/inlinetypes/verifier/StrictStaticFieldsTest.java CODETOOLS-7904031 generic-all + +runtime/cds/TestDefaultArchiveLoading.java#coops_nocoh 8366774 generic-all +runtime/cds/TestDefaultArchiveLoading.java#nocoops_nocoh 8366774 generic-all + +# Valhalla + AOT +runtime/cds/appcds/aotCache/HelloAOTCache.java 8369043 generic-aarch64 +runtime/cds/appcds/methodHandles/MethodHandlesGeneralTest.java#aot 8367408 generic-all + ############################################################################# # :hotspot_serviceability @@ -146,6 +167,64 @@ serviceability/sa/TestJhsdbJstackLock.java 8344261 generic-all serviceability/sa/sadebugd/DebugdConnectTest.java 8344261 generic-all serviceability/attach/RemovingUnixDomainSocketTest.java 8344261 generic-all +# Valhalla TODO: +serviceability/jvmti/valhalla/HeapDump/HeapDump.java 8317416 generic-all + +serviceability/sa/ClhsdbCDSCore.java 8190936 generic-all +serviceability/sa/ClhsdbCDSJstackPrintAll.java 8190936 generic-all +serviceability/sa/ClhsdbFindPC.java 8190936 generic-all +serviceability/sa/ClhsdbInspect.java 8190936 generic-all +serviceability/sa/ClhsdbLongConstant.java 8190936 generic-all +serviceability/sa/ClhsdbJdis.java 8190936 generic-all +serviceability/sa/ClhsdbJstack.java 8190936 generic-all +serviceability/sa/ClhsdbPrintAll.java 8190936 generic-all +serviceability/sa/ClhsdbPrintAs.java 8190936 generic-all +serviceability/sa/ClhsdbPrintStatics.java 8190936 generic-all +serviceability/sa/ClhsdbSource.java 8190936 generic-all +serviceability/sa/ClhsdbSymbol.java 8190936 generic-all +serviceability/sa/ClhsdbWhere.java 8190936 generic-all +serviceability/sa/JhsdbThreadInfoTest.java 8190936 generic-all +serviceability/sa/TestClassDump.java 8190936 generic-all +serviceability/sa/TestClhsdbJstackLock.java 8190936 generic-all +serviceability/sa/TestCpoolForInvokeDynamic.java 8190936 generic-all +serviceability/sa/TestHeapDumpForInvokeDynamic.java 8190936 generic-all +serviceability/sa/TestHeapDumpForLargeArray.java 8190936 generic-all +serviceability/sa/TestIntConstant.java 8190936 generic-all +serviceability/sa/TestJhsdbJstackLock.java 8190936 generic-all +serviceability/sa/TestJmapCore.java 8190936 generic-all +serviceability/sa/TestJmapCoreMetaspace.java 8190936 generic-all +serviceability/sa/TestPrintMdo.java 8190936 generic-all +serviceability/sa/jmap-hprof/JMapHProfLargeHeapTest.java 8190936 generic-all +serviceability/sa/ClhsdbDumpclass.java 8190936 generic-all + +compiler/stringopts/TestStackedConcatsAppendUncommonTrap.java 8367405 generic-all + +# Array Changes TODO +serviceability/sa/CDSJMapClstats.java 8365722 generic-all +serviceability/sa/ClhsdbClasses.java 8365722 generic-all +serviceability/sa/sadebugd/DisableRegistryTest.java 8365722 generic-all +serviceability/sa/ClhsdbDumpheap.java 8365722 generic-all +serviceability/sa/sadebugd/ClhsdbTestConnectArgument.java 8365722 generic-all +serviceability/sa/sadebugd/DebugdConnectTest.java 8365722 generic-all +serviceability/sa/ClhsdbJhisto.java 8365722 generic-all +serviceability/sa/ClhsdbJstack.java#id1 8365722 generic-all +serviceability/sa/ClhsdbJstackWithConcurrentLock.java 8365722 generic-all +serviceability/sa/ClhsdbJstackXcompStress.java 8365722 generic-all +serviceability/sa/ClhsdbPstack.java#process 8365722 generic-all +serviceability/sa/ClhsdbPstack.java#core 8365722 generic-all +serviceability/sa/ClhsdbScanOops.java#id0 8365722 generic-all +serviceability/sa/ClhsdbScanOops.java#id1 8365722 generic-all +serviceability/sa/DeadlockDetectionTest.java 8365722 generic-all +serviceability/sa/ClhsdbJstack.java#id0 8365722 generic-all +serviceability/sa/TestInstanceKlassSize.java 8365722 generic-all +serviceability/sa/TestSysProps.java 8365722 generic-all +serviceability/sa/sadebugd/ClhsdbAttachToDebugServer.java 8365722 generic-all +resourcehogs/serviceability/sa/TestHeapDumpForLargeArray.java 8365722 generic-all +serviceability/HeapDump/DuplicateArrayClassesTest.java 8365722 generic-all + +resourcehogs/serviceability/sa/ClhsdbRegionDetailsScanOopsForG1.java 8190936 generic-all +vmTestbase/nsk/jvmti/scenarios/events/EM04/em04t001/TestDescription.java 8367590 generic-all + ############################################################################# # :hotspot_misc @@ -184,4 +263,6 @@ vmTestbase/nsk/jdwp/ThreadReference/ForceEarlyReturn/forceEarlyReturn001/forceEa vmTestbase/nsk/monitoring/ThreadMXBean/ThreadInfo/Multi/Multi005/TestDescription.java 8076494 windows-x64 +vmTestbase/vm/mlvm/hiddenloader/stress/byteMutation/Test.java 8317172 generic-all + vmTestbase/nsk/monitoring/ThreadMXBean/findMonitorDeadlockedThreads/find006/TestDescription.java 8310144 macosx-aarch64 diff --git a/test/hotspot/jtreg/TEST.ROOT b/test/hotspot/jtreg/TEST.ROOT index 2d0d972744c..97f04fff98b 100644 --- a/test/hotspot/jtreg/TEST.ROOT +++ b/test/hotspot/jtreg/TEST.ROOT @@ -104,7 +104,7 @@ requires.properties= \ jdk.static # Minimum jtreg version -requiredVersion=7.5.2+1 +requiredVersion=7.5.1+1 # Path to libraries in the topmost test directory. This is needed so @library # does not need ../../../ notation to reach them diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups index eeb5110b077..cbef24ddee1 100644 --- a/test/hotspot/jtreg/TEST.groups +++ b/test/hotspot/jtreg/TEST.groups @@ -46,7 +46,7 @@ hotspot_gc = \ # By design this group should include ALL tests under runtime sub-directory hotspot_runtime = \ - runtime + runtime \ hotspot_runtime_no_cds = \ runtime \ @@ -62,6 +62,14 @@ hotspot_handshake = \ hotspot_serviceability = \ serviceability +hotspot_valhalla = \ + runtime/valhalla \ + compiler/valhalla \ + serviceability/jvmti/valhalla + +hotspot_valhalla_runtime = \ + runtime/valhalla + hotspot_resourcehogs = \ resourcehogs @@ -210,6 +218,7 @@ tier1_compiler_3 = \ compiler/types/ \ compiler/uncommontrap/ \ compiler/unsafe/ \ + compiler/valhalla/ \ compiler/vectorization/ \ -compiler/intrinsics/bmi \ -compiler/intrinsics/mathexact \ @@ -251,6 +260,13 @@ tier3_compiler = \ -:tier1_compiler \ -:tier2_compiler + +tier1_compiler_no_valhalla = \ + :tier1_compiler_1 \ + :tier1_compiler_2 \ + :tier1_compiler_3 \ + -compiler/valhalla + ctw_1 = \ applications/ctw/modules/ \ -:ctw_2 \ @@ -403,6 +419,10 @@ tier1_runtime = \ -runtime/signal \ -runtime/stack +tier1_runtime_no_valhalla = \ + :tier1_runtime \ + -runtime/valhalla + hotspot_cds = \ runtime/cds/ \ runtime/CompressedOops/ diff --git a/test/hotspot/jtreg/compiler/arraycopy/TestObjectArrayClone.java b/test/hotspot/jtreg/compiler/arraycopy/TestObjectArrayClone.java index b7e5b135c64..6471ea96387 100644 --- a/test/hotspot/jtreg/compiler/arraycopy/TestObjectArrayClone.java +++ b/test/hotspot/jtreg/compiler/arraycopy/TestObjectArrayClone.java @@ -43,6 +43,10 @@ * -XX:CompileCommand=compileonly,jdk.internal.reflect.GeneratedMethodAccessor*::invoke * -XX:CompileCommand=compileonly,*::invokeVirtual * compiler.arraycopy.TestObjectArrayClone + * @run main/othervm -Xbatch -XX:-UseTypeProfile -XX:-ReduceInitialCardMarks + * -XX:CompileCommand=compileonly,compiler.arraycopy.TestObjectArrayClone::testClone* + * -XX:CompileCommand=compileonly,jdk.internal.reflect.GeneratedMethodAccessor*::invoke + * compiler.arraycopy.TestObjectArrayClone */ package compiler.arraycopy; diff --git a/test/hotspot/jtreg/compiler/c1/CanonicalizeGetModifiers.java b/test/hotspot/jtreg/compiler/c1/CanonicalizeGetModifiers.java index d564b901bb2..a420b8b8453 100644 --- a/test/hotspot/jtreg/compiler/c1/CanonicalizeGetModifiers.java +++ b/test/hotspot/jtreg/compiler/c1/CanonicalizeGetModifiers.java @@ -26,7 +26,9 @@ * @test * @author Yi Yang * @summary Canonicalizes Foo.class.getModifiers() with interpreter mode + * @modules java.base/jdk.internal.misc * @library /test/lib + * @enablePreview * @run main/othervm -Xint * -XX:CompileCommand=compileonly,*CanonicalizeGetModifiers.test * compiler.c1.CanonicalizeGetModifiers @@ -36,8 +38,10 @@ * @test * @author Yi Yang * @summary Canonicalizes Foo.class.getModifiers() with C1 mode + * @modules java.base/jdk.internal.misc * @requires vm.compiler1.enabled * @library /test/lib + * @enablePreview * @run main/othervm -XX:TieredStopAtLevel=1 -XX:+TieredCompilation * -XX:CompileCommand=compileonly,*CanonicalizeGetModifiers.test * compiler.c1.CanonicalizeGetModifiers @@ -47,8 +51,10 @@ * @test * @author Yi Yang * @summary Canonicalizes Foo.class.getModifiers() with C2 mode + * @modules java.base/jdk.internal.misc * @requires vm.compiler2.enabled * @library /test/lib + * @enablePreview * @run main/othervm -XX:-TieredCompilation * -XX:CompileCommand=compileonly,*CanonicalizeGetModifiers.test * compiler.c1.CanonicalizeGetModifiers @@ -57,8 +63,10 @@ package compiler.c1; import java.lang.reflect.Modifier; +import java.lang.reflect.AccessFlag; import jdk.test.lib.Asserts; +import jdk.internal.misc.PreviewFeatures; public class CanonicalizeGetModifiers { public static class T1 { @@ -80,12 +88,12 @@ interface T6 { } static void test(Class poison) { - Asserts.assertEQ(CanonicalizeGetModifiers.class.getModifiers(), Modifier.PUBLIC); - Asserts.assertEQ(T1.class.getModifiers(), Modifier.PUBLIC | Modifier.STATIC); - Asserts.assertEQ(T2.class.getModifiers(), Modifier.PUBLIC | Modifier.FINAL | Modifier.STATIC); - Asserts.assertEQ(T3.class.getModifiers(), Modifier.PRIVATE | Modifier.STATIC); - Asserts.assertEQ(T4.class.getModifiers(), Modifier.PROTECTED | Modifier.STATIC); - Asserts.assertEQ(new CanonicalizeGetModifiers().new T5().getClass().getModifiers(), 0/* NONE */); + Asserts.assertEQ(CanonicalizeGetModifiers.class.getModifiers(), Modifier.PUBLIC | Modifier.IDENTITY); + Asserts.assertEQ(T1.class.getModifiers(), Modifier.PUBLIC | Modifier.STATIC | Modifier.IDENTITY); + Asserts.assertEQ(T2.class.getModifiers(), Modifier.PUBLIC | Modifier.FINAL | Modifier.STATIC | Modifier.IDENTITY); + Asserts.assertEQ(T3.class.getModifiers(), Modifier.PRIVATE | Modifier.STATIC | Modifier.IDENTITY); + Asserts.assertEQ(T4.class.getModifiers(), Modifier.PROTECTED | Modifier.STATIC | Modifier.IDENTITY); + Asserts.assertEQ(new CanonicalizeGetModifiers().new T5().getClass().getModifiers(), 0/* NONE */ | Modifier.IDENTITY); Asserts.assertEQ(T6.class.getModifiers(), Modifier.ABSTRACT | Modifier.STATIC | Modifier.INTERFACE); Asserts.assertEQ(int.class.getModifiers(), Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.FINAL); @@ -96,21 +104,21 @@ static void test(Class poison) { Asserts.assertEQ(byte.class.getModifiers(), Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.FINAL); Asserts.assertEQ(short.class.getModifiers(), Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.FINAL); Asserts.assertEQ(void.class.getModifiers(), Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.FINAL); - Asserts.assertEQ(int[].class.getModifiers(), Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.FINAL); - Asserts.assertEQ(long[].class.getModifiers(), Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.FINAL); - Asserts.assertEQ(double[].class.getModifiers(), Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.FINAL); - Asserts.assertEQ(float[].class.getModifiers(), Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.FINAL); - Asserts.assertEQ(char[].class.getModifiers(), Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.FINAL); - Asserts.assertEQ(byte[].class.getModifiers(), Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.FINAL); - Asserts.assertEQ(short[].class.getModifiers(), Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.FINAL); - Asserts.assertEQ(Object[].class.getModifiers(), Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.FINAL); - Asserts.assertEQ(CanonicalizeGetModifiers[].class.getModifiers(), Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.FINAL); - - Asserts.assertEQ(new CanonicalizeGetModifiers().getClass().getModifiers(), Modifier.PUBLIC); - Asserts.assertEQ(new T1().getClass().getModifiers(), Modifier.PUBLIC | Modifier.STATIC); - Asserts.assertEQ(new T2().getClass().getModifiers(), Modifier.PUBLIC | Modifier.FINAL | Modifier.STATIC); - Asserts.assertEQ(new T3().getClass().getModifiers(), Modifier.PRIVATE | Modifier.STATIC); - Asserts.assertEQ(new T4().getClass().getModifiers(), Modifier.PROTECTED | Modifier.STATIC); + Asserts.assertEQ(int[].class.getModifiers(), Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.FINAL | (PreviewFeatures.isEnabled() ? Modifier.IDENTITY : 0)); + Asserts.assertEQ(long[].class.getModifiers(), Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.FINAL | (PreviewFeatures.isEnabled() ? Modifier.IDENTITY : 0)); + Asserts.assertEQ(double[].class.getModifiers(), Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.FINAL | (PreviewFeatures.isEnabled() ? Modifier.IDENTITY : 0)); + Asserts.assertEQ(float[].class.getModifiers(), Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.FINAL | (PreviewFeatures.isEnabled() ? Modifier.IDENTITY : 0)); + Asserts.assertEQ(char[].class.getModifiers(), Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.FINAL | (PreviewFeatures.isEnabled() ? Modifier.IDENTITY : 0)); + Asserts.assertEQ(byte[].class.getModifiers(), Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.FINAL | (PreviewFeatures.isEnabled() ? Modifier.IDENTITY : 0)); + Asserts.assertEQ(short[].class.getModifiers(), Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.FINAL | (PreviewFeatures.isEnabled() ? Modifier.IDENTITY : 0)); + Asserts.assertEQ(Object[].class.getModifiers(), Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.FINAL | (PreviewFeatures.isEnabled() ? Modifier.IDENTITY : 0)); + Asserts.assertEQ(CanonicalizeGetModifiers[].class.getModifiers(), Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.FINAL | (PreviewFeatures.isEnabled() ? Modifier.IDENTITY : 0)); + + Asserts.assertEQ(new CanonicalizeGetModifiers().getClass().getModifiers(), Modifier.PUBLIC | Modifier.IDENTITY); + Asserts.assertEQ(new T1().getClass().getModifiers(), Modifier.PUBLIC | Modifier.STATIC | Modifier.IDENTITY); + Asserts.assertEQ(new T2().getClass().getModifiers(), Modifier.PUBLIC | Modifier.FINAL | Modifier.STATIC | Modifier.IDENTITY); + Asserts.assertEQ(new T3().getClass().getModifiers(), Modifier.PRIVATE | Modifier.STATIC | Modifier.IDENTITY); + Asserts.assertEQ(new T4().getClass().getModifiers(), Modifier.PROTECTED | Modifier.STATIC | Modifier.IDENTITY); try { // null_check poison.getModifiers(); diff --git a/test/hotspot/jtreg/compiler/c2/irTests/ProfileAtTypeCheck.java b/test/hotspot/jtreg/compiler/c2/irTests/ProfileAtTypeCheck.java index dfc1cb9d281..e541f845f83 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/ProfileAtTypeCheck.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/ProfileAtTypeCheck.java @@ -30,6 +30,7 @@ /* * @test * bug 8308869 + * @ignore TODO 8366668 * @summary C2: use profile data in subtype checks when profile has more than one class * @library /test/lib / * @build jdk.test.whitebox.WhiteBox diff --git a/test/hotspot/jtreg/compiler/c2/irTests/TestOptimizeUnstableIf.java b/test/hotspot/jtreg/compiler/c2/irTests/TestOptimizeUnstableIf.java index 2b4fab4a521..a26f7d0198c 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/TestOptimizeUnstableIf.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/TestOptimizeUnstableIf.java @@ -42,7 +42,8 @@ public static void main(String[] args) { @Test @Arguments(values = {Argument.MAX}) // the argument needs to be big enough to fall out of cache. - @IR(failOn = {IRNode.ALLOC_OF, "Integer"}) + // TODO 8328675 Re-enable + // @IR(failOn = {IRNode.ALLOC_OF, "Integer"}) public static int boxing_object(int value) { Integer ii = Integer.valueOf(value); int sum = 0; diff --git a/test/hotspot/jtreg/compiler/c2/irTests/scalarReplacement/ScalarReplacementWithGCBarrierTests.java b/test/hotspot/jtreg/compiler/c2/irTests/scalarReplacement/ScalarReplacementWithGCBarrierTests.java index fbf5cdd61cc..3db413ba911 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/scalarReplacement/ScalarReplacementWithGCBarrierTests.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/scalarReplacement/ScalarReplacementWithGCBarrierTests.java @@ -102,8 +102,7 @@ private void runner() { // Before the patch of JDK-8333334, both allocations of `Iter` and `Integer` // could not be eliminated. @Test - @IR(phase = { CompilePhase.AFTER_PARSING }, counts = { IRNode.ALLOC, "1" }) - @IR(phase = { CompilePhase.INCREMENTAL_BOXING_INLINE }, counts = { IRNode.ALLOC, "2" }) + @IR(phase = { CompilePhase.PHASEIDEAL_BEFORE_EA }, counts = { IRNode.ALLOC, "2" }) @IR(phase = { CompilePhase.ITER_GVN_AFTER_ELIMINATION }, counts = { IRNode.ALLOC, "1" }) private int testScalarReplacementWithGCBarrier(List list) { Iter iter = list.iter(); diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestInliningProtectionDomain.java b/test/hotspot/jtreg/compiler/ciReplay/TestInliningProtectionDomain.java index 339b6b59f4e..4931d3b7cda 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/TestInliningProtectionDomain.java +++ b/test/hotspot/jtreg/compiler/ciReplay/TestInliningProtectionDomain.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -92,9 +92,9 @@ public static void test() { bar(); } - // Integer should be resolved for the protection domain of this class because the separate compilation of bar() in + // Thread should be resolved for the protection domain of this class because the separate compilation of bar() in // the normal run will resolve all classes in the signature. Inlining succeeds. - private static Integer bar() { + private static Thread bar() { InliningFoo.foo(); return null; } @@ -111,10 +111,10 @@ public static void test() { bar(); // Not compiled before separately } - // Integer should be resolved for the protection domain of this class because getDeclaredMethods is called in normal run + // Thread should be resolved for the protection domain of this class because getDeclaredMethods is called in normal run // when validating main() method. In this process, all public methods of this class are visited and its signature classes // are resolved. Inlining of bar() succeeds. - public static Integer bar() { + public static Thread bar() { InliningFoo.foo(); return null; } @@ -131,12 +131,12 @@ public static void test() { bar(); // Not compiled before separately } - // Integer should be unresolved for the protection domain of this class even though getDeclaredMethods is called in normal + // Thread should be unresolved for the protection domain of this class even though getDeclaredMethods is called in normal // run when validating main() method. In this process, only public methods of this class are visited and its signature // classes are resolved. Since this method is private, the signature classes are not resolved for this protection domain. // Inlining of bar() should fail in normal run with "unresolved signature classes". Therefore, replay compilation should // also not inline bar(). - private static Integer bar() { + private static Thread bar() { InliningFoo.foo(); return null; } @@ -153,7 +153,7 @@ public static void test() { bar(); // Not compiled before separately } - // Integer should be resovled for the protection domain of this class because getDeclaredMethods is called in normal run + // String should be resolved for the protection domain of this class because getDeclaredMethods is called in normal run // when validating main() method. In this process, public methods of this class are visited and its signature classes // are resolved. bar() is private and not visited in this process (i.e. no resolution of String). But since main() // has String[] as parameter, the String class will be resolved for this protection domain. Inlining of bar() succeeds. diff --git a/test/hotspot/jtreg/compiler/codegen/TestRedundantLea.java b/test/hotspot/jtreg/compiler/codegen/TestRedundantLea.java index 0108df40704..af72ba6a52b 100644 --- a/test/hotspot/jtreg/compiler/codegen/TestRedundantLea.java +++ b/test/hotspot/jtreg/compiler/codegen/TestRedundantLea.java @@ -81,6 +81,16 @@ * @run driver compiler.codegen.TestRedundantLea StoreNParallel */ +/* + * @test id=Spill + * @bug 8020282 + * @summary Test that we do not generate redundant leas and remove related spills on x86. + * @requires os.simpleArch == "x64" + * @modules jdk.compiler/com.sun.tools.javac.util + * @library /test/lib / + * @run driver compiler.codegen.TestRedundantLea Spill + */ + package compiler.codegen; @@ -129,6 +139,9 @@ public static void main(String[] args) { framework = new TestFramework(StoreNTest.class); framework.addFlags("-XX:+UseParallelGC"); } + case "Spill" -> { + framework = new TestFramework(SpillTest.class); + } default -> { throw new IllegalArgumentException("Unknown test name \"" + testName +"\""); } @@ -274,9 +287,7 @@ public boolean test(Matcher m) { // The matcher generates leaP* rules for storing an object in an array of objects // at a constant offset, but only when using the Serial or Parallel GC. -// Here, we can also manipulate the offset such that we get a leaP32Narrow rule -// and we can demonstrate that the peephole also removes simple cases of unneeded -// spills. +// Here, we can also manipulate the offset such that we get a leaP32Narrow rule. class StoreNTest { private static final int SOME_SIZE = 42; private static final int OFFSET8BIT_IDX = 3; @@ -287,8 +298,6 @@ class StoreNTest { private StoreNTestHelper[] classArr8bit = new StoreNTestHelper[SOME_SIZE]; private StoreNTestHelper[] classArr32bit = new StoreNTestHelper[SOME_SIZE]; - private Object[] objArr8bit = new Object[SOME_SIZE]; - private Object[] objArr32bit = new Object[SOME_SIZE]; @Test @IR(counts = {IRNode.LEA_P, "=2"}, @@ -302,13 +311,6 @@ class StoreNTest { @IR(failOn = {IRNode.DECODE_HEAP_OOP_NOT_NULL}, phase = {CompilePhase.FINAL_CODE}, applyIf = {"OptoPeephole", "true"}) - // Test that the peephole removes a spill. - @IR(counts = {IRNode.MEM_TO_REG_SPILL_COPY, "=4"}, - phase = {CompilePhase.FINAL_CODE}, - applyIfAnd ={"OptoPeephole", "false", "UseCompactObjectHeaders", "false"}) - @IR(counts = {IRNode.MEM_TO_REG_SPILL_COPY, "=3"}, - phase = {CompilePhase.FINAL_CODE}, - applyIfAnd ={"OptoPeephole", "true", "UseCompactObjectHeaders", "false"}) public void testRemoveSpill() { this.classArr8bit[OFFSET8BIT_IDX] = new StoreNTestHelper(CURRENT, OTHER); this.classArr32bit[OFFSET32BIT_IDX] = new StoreNTestHelper(OTHER, CURRENT); @@ -331,48 +333,65 @@ public void testPhiSpill() { this.classArr8bit[OFFSET8BIT_IDX] = new StoreNTestHelper(CURRENT, OTHER); this.classArr8bit[OFFSET32BIT_IDX] = new StoreNTestHelper(CURRENT, OTHER); } +} + +class StoreNTestHelper { + Object o1; + Object o2; + + public StoreNTestHelper(Object o1, Object o2) { + this.o1 = o1; + this.o2 = o2; + } +} + +// This test validates that the peephole removes simple spills. +// The code for the test originates from compiler/escapeAnalysis/Test6775880.java. +class SpillTest { + int cnt; + int b[]; + String s; + + @Run(test = "test") + public static void run() { + SpillTest t = new SpillTest(); + t.cnt = 3; + t.b = new int[3]; + t.b[0] = 0; + t.b[1] = 1; + t.b[2] = 2; + int j = 0; + t.s = ""; + t.test(); + } @Test + // TODO: Make tests more precise @IR(counts = {IRNode.LEA_P, "=2"}, phase = {CompilePhase.FINAL_CODE}, applyIfPlatform = {"mac", "false"}) // Negative test - @IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=2"}, + @IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, ">=2"}, phase = {CompilePhase.FINAL_CODE}, applyIf = {"OptoPeephole", "false"}) - // Test that the peephole worked for leaPCompressedOopOffset - @IR(failOn = {IRNode.DECODE_HEAP_OOP_NOT_NULL}, + @IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "<=2"}, phase = {CompilePhase.FINAL_CODE}, applyIf = {"OptoPeephole", "true"}) - public void testNoAlloc() { - this.objArr8bit[OFFSET8BIT_IDX] = CURRENT; - this.objArr32bit[OFFSET32BIT_IDX] = OTHER; - } - - @Test - @IR(counts = {IRNode.LEA_P, "=2"}, - phase = {CompilePhase.FINAL_CODE}, - applyIfPlatform = {"mac", "false"}) - // Negative test - @IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=1"}, + // Test that the peephole removes a spill. + @IR(counts = {IRNode.MEM_TO_REG_SPILL_COPY, ">=18"}, phase = {CompilePhase.FINAL_CODE}, applyIf = {"OptoPeephole", "false"}) - // Test that the peephole worked for leaPCompressedOopOffset - @IR(failOn = {IRNode.DECODE_HEAP_OOP_NOT_NULL}, + @IR(counts = {IRNode.MEM_TO_REG_SPILL_COPY, ">=16"}, phase = {CompilePhase.FINAL_CODE}, applyIf = {"OptoPeephole", "true"}) - public void testNoAllocSameArray() { - this.objArr8bit[OFFSET8BIT_IDX] = CURRENT; - this.objArr8bit[OFFSET32BIT_IDX] = OTHER; - } -} - -class StoreNTestHelper { - Object o1; - Object o2; - - public StoreNTestHelper(Object o1, Object o2) { - this.o1 = o1; - this.o2 = o2; + String test() { + String res = ""; + for (int i = 0; i < cnt; i++) { + if (i != 0) { + res = res + "."; + } + res = res + b[i]; + } + return res; } } diff --git a/test/hotspot/jtreg/compiler/eliminateAutobox/TestIdentityWithEliminateBoxInDebugInfo.java b/test/hotspot/jtreg/compiler/eliminateAutobox/TestIdentityWithEliminateBoxInDebugInfo.java index c2e659ea988..4666faa349f 100644 --- a/test/hotspot/jtreg/compiler/eliminateAutobox/TestIdentityWithEliminateBoxInDebugInfo.java +++ b/test/hotspot/jtreg/compiler/eliminateAutobox/TestIdentityWithEliminateBoxInDebugInfo.java @@ -1,4 +1,5 @@ /* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021, Huawei Technologies Co., Ltd. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -25,7 +26,8 @@ * @test * @bug 8261137 * @requires vm.flavor == "server" - * @summary Verify that box object identity matches after deoptimization when it is eliminated. + * @enablePreview + * @summary Verify that box object content matches after deoptimization when it is eliminated. * @library /test/lib * * @run main/othervm -Xbatch compiler.eliminateAutobox.TestIdentityWithEliminateBoxInDebugInfo @@ -67,7 +69,7 @@ public static void runTest() throws Exception { if (!c) { Asserts.assertTrue(a == Long.valueOf(42L)); Asserts.assertTrue(b == Long.valueOf(-42L)); - Asserts.assertFalse(h == Long.valueOf(highBitsOnly)); + Asserts.assertTrue(h == Long.valueOf(highBitsOnly)); } }); diff --git a/test/hotspot/jtreg/compiler/exceptions/OptimizeImplicitExceptions.java b/test/hotspot/jtreg/compiler/exceptions/OptimizeImplicitExceptions.java index ca61e95a68f..8f248bd89d1 100644 --- a/test/hotspot/jtreg/compiler/exceptions/OptimizeImplicitExceptions.java +++ b/test/hotspot/jtreg/compiler/exceptions/OptimizeImplicitExceptions.java @@ -56,7 +56,8 @@ public enum ImplicitException { INVOKE_NULL_POINTER_EXCEPTION("null_check"), ARITHMETIC_EXCEPTION("div0_check"), ARRAY_INDEX_OUT_OF_BOUNDS_EXCEPTION("range_check"), - ARRAY_STORE_EXCEPTION("array_check"), + // TODO 8366668 This currently fails + // ARRAY_STORE_EXCEPTION("array_check"), CLASS_CAST_EXCEPTION("class_check"); private final String reason; ImplicitException(String reason) { @@ -102,9 +103,12 @@ public static Object throwImplicitException(ImplicitException type, Object[] obj case ARRAY_INDEX_OUT_OF_BOUNDS_EXCEPTION: { return object_a[5]; } + // TODO 8366668 Re-enable + /* case ARRAY_STORE_EXCEPTION: { return (object_a[0] = o); } + */ case CLASS_CAST_EXCEPTION: { return (ImplicitException[])object_a; } diff --git a/test/hotspot/jtreg/compiler/gcbarriers/TestG1BarrierGeneration.java b/test/hotspot/jtreg/compiler/gcbarriers/TestG1BarrierGeneration.java index a9df0019ab1..c9c7c6d2653 100644 --- a/test/hotspot/jtreg/compiler/gcbarriers/TestG1BarrierGeneration.java +++ b/test/hotspot/jtreg/compiler/gcbarriers/TestG1BarrierGeneration.java @@ -549,6 +549,7 @@ public static Object[] testStoreAllOnNewLargeArray(Object o1) { "testStoreOnNewArrayAtUnknownIndex", "testStoreAllOnNewSmallArray", "testStoreAllOnNewLargeArray"}) + @Warmup(5000) public void runArrayStoreTests() { { Object[] a = new Object[10]; @@ -728,10 +729,10 @@ static boolean testCompareAndSwap(Outer o, Object oldVal, Object newVal) { @Test @IR(applyIf = {"UseCompressedOops", "false"}, - counts = {IRNode.G1_GET_AND_SET_P_WITH_BARRIER_FLAG, PRE_AND_POST, "1"}, + counts = {IRNode.G1_COMPARE_AND_SWAP_P_WITH_BARRIER_FLAG, PRE_AND_POST, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIf = {"UseCompressedOops", "true"}, - counts = {IRNode.G1_GET_AND_SET_N_WITH_BARRIER_FLAG, PRE_AND_POST, "1"}, + counts = {IRNode.G1_COMPARE_AND_SWAP_N_WITH_BARRIER_FLAG, PRE_AND_POST, "1"}, phase = CompilePhase.FINAL_CODE) static Object testGetAndSet(Outer o, Object newVal) { return fVarHandle.getAndSet(o, newVal); @@ -786,16 +787,16 @@ static boolean testCompareAndSwapOnNewObject(Object oldVal, Object newVal) { @Test @IR(applyIfAnd = {"UseCompressedOops", "false", "ReduceInitialCardMarks", "false"}, - counts = {IRNode.G1_GET_AND_SET_P_WITH_BARRIER_FLAG, POST_ONLY, "1"}, + counts = {IRNode.G1_COMPARE_AND_SWAP_P_WITH_BARRIER_FLAG, POST_ONLY, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIfAnd = {"UseCompressedOops", "true", "ReduceInitialCardMarks", "false"}, - counts = {IRNode.G1_GET_AND_SET_N_WITH_BARRIER_FLAG, POST_ONLY, "1"}, + counts = {IRNode.G1_COMPARE_AND_SWAP_N_WITH_BARRIER_FLAG, POST_ONLY, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIfAnd = {"UseCompressedOops", "false", "ReduceInitialCardMarks", "true"}, - failOn = {IRNode.G1_GET_AND_SET_P_WITH_BARRIER_FLAG, ANY}, + failOn = {IRNode.G1_COMPARE_AND_SWAP_P_WITH_BARRIER_FLAG, ANY}, phase = CompilePhase.FINAL_CODE) @IR(applyIfAnd = {"UseCompressedOops", "true", "ReduceInitialCardMarks", "true"}, - failOn = {IRNode.G1_GET_AND_SET_N_WITH_BARRIER_FLAG, ANY}, + failOn = {IRNode.G1_COMPARE_AND_SWAP_N_WITH_BARRIER_FLAG, ANY}, phase = CompilePhase.FINAL_CODE) static Object testGetAndSetOnNewObject(Object oldVal, Object newVal) { Outer o = new Outer(); @@ -805,16 +806,16 @@ static Object testGetAndSetOnNewObject(Object oldVal, Object newVal) { @Test @IR(applyIfAnd = {"UseCompressedOops", "false", "ReduceInitialCardMarks", "false"}, - counts = {IRNode.G1_GET_AND_SET_P_WITH_BARRIER_FLAG, POST_ONLY, "1"}, + counts = {IRNode.G1_COMPARE_AND_SWAP_P_WITH_BARRIER_FLAG, POST_ONLY, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIfAnd = {"UseCompressedOops", "true", "ReduceInitialCardMarks", "false"}, - counts = {IRNode.G1_GET_AND_SET_N_WITH_BARRIER_FLAG, POST_ONLY, "1"}, + counts = {IRNode.G1_COMPARE_AND_SWAP_N_WITH_BARRIER_FLAG, POST_ONLY, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIfAnd = {"UseCompressedOops", "false", "ReduceInitialCardMarks", "true"}, - failOn = {IRNode.G1_GET_AND_SET_P_WITH_BARRIER_FLAG, ANY}, + failOn = {IRNode.G1_COMPARE_AND_SWAP_P_WITH_BARRIER_FLAG, ANY}, phase = CompilePhase.FINAL_CODE) @IR(applyIfAnd = {"UseCompressedOops", "true", "ReduceInitialCardMarks", "true"}, - failOn = {IRNode.G1_GET_AND_SET_N_WITH_BARRIER_FLAG, ANY}, + failOn = {IRNode.G1_COMPARE_AND_SWAP_N_WITH_BARRIER_FLAG, ANY}, phase = CompilePhase.FINAL_CODE) static Object testGetAndSetConditionallyOnNewObject(Object oldVal, Object newVal, boolean c) { Outer o = new Outer(); @@ -827,16 +828,16 @@ static Object testGetAndSetConditionallyOnNewObject(Object oldVal, Object newVal @Test @IR(applyIfAnd = {"UseCompressedOops", "false", "ReduceInitialCardMarks", "false"}, - counts = {IRNode.G1_GET_AND_SET_P_WITH_BARRIER_FLAG, POST_ONLY, "1"}, + counts = {IRNode.G1_COMPARE_AND_SWAP_P_WITH_BARRIER_FLAG, POST_ONLY, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIfAnd = {"UseCompressedOops", "true", "ReduceInitialCardMarks", "false"}, - counts = {IRNode.G1_GET_AND_SET_N_WITH_BARRIER_FLAG, POST_ONLY, "1"}, + counts = {IRNode.G1_COMPARE_AND_SWAP_N_WITH_BARRIER_FLAG, POST_ONLY, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIfAnd = {"UseCompressedOops", "false", "ReduceInitialCardMarks", "true"}, - failOn = {IRNode.G1_GET_AND_SET_P_WITH_BARRIER_FLAG, ANY}, + failOn = {IRNode.G1_COMPARE_AND_SWAP_P_WITH_BARRIER_FLAG, ANY}, phase = CompilePhase.FINAL_CODE) @IR(applyIfAnd = {"UseCompressedOops", "true", "ReduceInitialCardMarks", "true"}, - failOn = {IRNode.G1_GET_AND_SET_N_WITH_BARRIER_FLAG, ANY}, + failOn = {IRNode.G1_COMPARE_AND_SWAP_N_WITH_BARRIER_FLAG, ANY}, phase = CompilePhase.FINAL_CODE) static Object testGetAndSetOnNewObjectAfterException(Object oldVal, Object newVal, boolean c) throws Exception { Outer o = new Outer(); @@ -849,10 +850,10 @@ static Object testGetAndSetOnNewObjectAfterException(Object oldVal, Object newVa @Test @IR(applyIf = {"UseCompressedOops", "false"}, - counts = {IRNode.G1_GET_AND_SET_P_WITH_BARRIER_FLAG, PRE_AND_POST, "1"}, + counts = {IRNode.G1_COMPARE_AND_SWAP_P_WITH_BARRIER_FLAG, PRE_AND_POST, "1"}, phase = CompilePhase.FINAL_CODE) @IR(applyIf = {"UseCompressedOops", "true"}, - counts = {IRNode.G1_GET_AND_SET_N_WITH_BARRIER_FLAG, PRE_AND_POST, "1"}, + counts = {IRNode.G1_COMPARE_AND_SWAP_N_WITH_BARRIER_FLAG, PRE_AND_POST, "1"}, phase = CompilePhase.FINAL_CODE) static Object testGetAndSetOnNewObjectAfterCall(Object oldVal, Object newVal) { Outer o = new Outer(); diff --git a/test/hotspot/jtreg/compiler/gcbarriers/TestImplicitNullChecks.java b/test/hotspot/jtreg/compiler/gcbarriers/TestImplicitNullChecks.java index 34583b8fea9..9c92f97102f 100644 --- a/test/hotspot/jtreg/compiler/gcbarriers/TestImplicitNullChecks.java +++ b/test/hotspot/jtreg/compiler/gcbarriers/TestImplicitNullChecks.java @@ -24,19 +24,17 @@ package compiler.gcbarriers; import compiler.lib.ir_framework.*; -import java.lang.invoke.VarHandle; -import java.lang.invoke.MethodHandles; -import java.lang.ref.Reference; -import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; import jdk.test.lib.Asserts; +import jdk.internal.misc.Unsafe; /** * @test * @summary Test that implicit null checks are generated as expected for different GC memory accesses. * @library /test/lib / + * @modules java.base/jdk.internal.misc * @run driver compiler.gcbarriers.TestImplicitNullChecks */ @@ -51,18 +49,19 @@ static class OuterWithVolatileField { volatile Object f; } - static final VarHandle fVarHandle; + static final Unsafe UNSAFE = Unsafe.getUnsafe(); + static final long F_OFFSET; static { - MethodHandles.Lookup l = MethodHandles.lookup(); try { - fVarHandle = l.findVarHandle(Outer.class, "f", Object.class); + F_OFFSET = UNSAFE.objectFieldOffset(Outer.class.getDeclaredField("f")); } catch (Exception e) { throw new Error(e); } } public static void main(String[] args) { - TestFramework.runWithFlags("-XX:CompileCommand=inline,java.lang.ref.*::*", + TestFramework.runWithFlags("--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED", + "-XX:CompileCommand=inline,java.lang.ref.*::*", "-XX:-TieredCompilation"); } @@ -154,7 +153,7 @@ static void runStoreTests() { failOn = IRNode.NULL_CHECK, phase = CompilePhase.FINAL_CODE) static Object testCompareAndExchange(Outer o, Object oldVal, Object newVal) { - return fVarHandle.compareAndExchange(o, oldVal, newVal); + return UNSAFE.compareAndExchangeReference(o, F_OFFSET, oldVal, newVal); } @Test @@ -162,7 +161,7 @@ static Object testCompareAndExchange(Outer o, Object oldVal, Object newVal) { failOn = IRNode.NULL_CHECK, phase = CompilePhase.FINAL_CODE) static boolean testCompareAndSwap(Outer o, Object oldVal, Object newVal) { - return fVarHandle.compareAndSet(o, oldVal, newVal); + return UNSAFE.compareAndSetReference(o, F_OFFSET, oldVal, newVal); } @Test @@ -170,7 +169,7 @@ static boolean testCompareAndSwap(Outer o, Object oldVal, Object newVal) { failOn = IRNode.NULL_CHECK, phase = CompilePhase.FINAL_CODE) static Object testGetAndSet(Outer o, Object newVal) { - return fVarHandle.getAndSet(o, newVal); + return UNSAFE.getAndSetReference(o, F_OFFSET, newVal); } @Run(test = {"testCompareAndExchange", diff --git a/test/hotspot/jtreg/compiler/gcbarriers/TestZGCBarrierElision.java b/test/hotspot/jtreg/compiler/gcbarriers/TestZGCBarrierElision.java index 4c94cd62c42..0bb4b6f861d 100644 --- a/test/hotspot/jtreg/compiler/gcbarriers/TestZGCBarrierElision.java +++ b/test/hotspot/jtreg/compiler/gcbarriers/TestZGCBarrierElision.java @@ -29,7 +29,7 @@ import java.util.concurrent.ThreadLocalRandom; /** - * @test + * @test id=Z * @summary Test that the ZGC barrier elision optimization does not elide * necessary barriers. The tests use volatile memory accesses and * blackholes to prevent C2 from simply optimizing them away. @@ -39,7 +39,7 @@ */ /** - * @test + * @test id=ZGen * @summary Test that the ZGC barrier elision optimization elides unnecessary * barriers following simple allocation and domination rules. * @library /test/lib / @@ -99,7 +99,8 @@ public static void main(String[] args) { } String commonName = Common.class.getName(); TestFramework test = new TestFramework(testClass); - test.addFlags("-XX:+UseZGC", "-XX:+UnlockExperimentalVMOptions", + // TODO 8366668 Re-enable IR verification + test.addFlags("-DVerifyIR=false", "-XX:+UseZGC", "-XX:+UnlockExperimentalVMOptions", "-XX:CompileCommand=blackhole," + commonName + "::blackhole", "-XX:CompileCommand=dontinline," + commonName + "::nonInlinedMethod", "-XX:LoopMaxUnroll=0"); @@ -187,7 +188,7 @@ void runControlFlowTests() { } @Test - @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) + @IR(counts = { IRNode.Z_COMPARE_AND_SWAP_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) static void testAllocateThenAtomic(Inner i) { Outer o = new Outer(); Common.blackhole(o); @@ -196,21 +197,21 @@ static void testAllocateThenAtomic(Inner i) { @Test @IR(counts = { IRNode.Z_LOAD_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) - @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) + @IR(counts = { IRNode.Z_COMPARE_AND_SWAP_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) static void testLoadThenAtomic(Outer o, Inner i) { Common.blackhole(o.field1); Common.field1VarHandle.getAndSet(o, i); } @Test - @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.REMAINING, "2" }, phase = CompilePhase.FINAL_CODE) + @IR(counts = { IRNode.Z_COMPARE_AND_SWAP_P_WITH_BARRIER_FLAG, Common.REMAINING, "2" }, phase = CompilePhase.FINAL_CODE) static void testAtomicThenAtomicAnotherField(Outer o, Inner i) { Common.field1VarHandle.getAndSet(o, i); Common.field2VarHandle.getAndSet(o, i); } @Test - @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) + @IR(counts = { IRNode.Z_COMPARE_AND_SWAP_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) static void testAllocateArrayThenAtomicAtKnownIndex(Outer o) { Outer[] a = new Outer[42]; Common.blackhole(a); @@ -218,7 +219,7 @@ static void testAllocateArrayThenAtomicAtKnownIndex(Outer o) { } @Test - @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) + @IR(counts = { IRNode.Z_COMPARE_AND_SWAP_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) static void testAllocateArrayThenAtomicAtUnknownIndex(Outer o, int index) { Outer[] a = new Outer[42]; Common.blackhole(a); @@ -226,7 +227,7 @@ static void testAllocateArrayThenAtomicAtUnknownIndex(Outer o, int index) { } @Test - @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.REMAINING, "2" }, phase = CompilePhase.FINAL_CODE) + @IR(counts = { IRNode.Z_COMPARE_AND_SWAP_P_WITH_BARRIER_FLAG, Common.REMAINING, "2" }, phase = CompilePhase.FINAL_CODE) static void testArrayAtomicThenAtomicAtUnknownIndices(Outer[] a, Outer o, int index1, int index2) { Common.outerArrayVarHandle.getAndSet(a, index1, o); Common.outerArrayVarHandle.getAndSet(a, index2, o); @@ -387,14 +388,14 @@ void runControlFlowTests() { @Test @IR(counts = { IRNode.Z_STORE_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) - @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) + @IR(counts = { IRNode.Z_COMPARE_AND_SWAP_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) static void testStoreThenAtomic(Outer o, Inner i) { o.field1 = i; Common.field1VarHandle.getAndSet(o, i); } @Test - @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) + @IR(counts = { IRNode.Z_COMPARE_AND_SWAP_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) @IR(counts = { IRNode.Z_LOAD_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) static void testAtomicThenLoad(Outer o, Inner i) { Common.field1VarHandle.getAndSet(o, i); @@ -402,7 +403,7 @@ static void testAtomicThenLoad(Outer o, Inner i) { } @Test - @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) + @IR(counts = { IRNode.Z_COMPARE_AND_SWAP_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) @IR(counts = { IRNode.Z_STORE_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) static void testAtomicThenStore(Outer o, Inner i) { Common.field1VarHandle.getAndSet(o, i); @@ -410,16 +411,16 @@ static void testAtomicThenStore(Outer o, Inner i) { } @Test - @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) - @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) + @IR(counts = { IRNode.Z_COMPARE_AND_SWAP_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) + //@IR(counts = { IRNode.Z_COMPARE_AND_SWAP_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) static void testAtomicThenAtomic(Outer o, Inner i) { Common.field1VarHandle.getAndSet(o, i); Common.field1VarHandle.getAndSet(o, i); } @Test - @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) - @IR(counts = { IRNode.Z_GET_AND_SET_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) + @IR(counts = { IRNode.Z_COMPARE_AND_SWAP_P_WITH_BARRIER_FLAG, Common.REMAINING, "1" }, phase = CompilePhase.FINAL_CODE) + @IR(counts = { IRNode.Z_COMPARE_AND_SWAP_P_WITH_BARRIER_FLAG, Common.ELIDED, "1" }, phase = CompilePhase.FINAL_CODE) static void testArrayAtomicThenAtomic(Outer[] a, Outer o) { Common.outerArrayVarHandle.getAndSet(a, 0, o); Common.outerArrayVarHandle.getAndSet(a, 0, o); diff --git a/test/hotspot/jtreg/compiler/intrinsics/bmi/BMITestRunner.java b/test/hotspot/jtreg/compiler/intrinsics/bmi/BMITestRunner.java index 9d2a731c6e5..506405078df 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/bmi/BMITestRunner.java +++ b/test/hotspot/jtreg/compiler/intrinsics/bmi/BMITestRunner.java @@ -125,8 +125,7 @@ public static OutputAnalyzer runTest(Class expr, // Hide timestamps from warnings (e.g. due to potential AOT // saved/runtime state mismatch), to avoid false positives when // comparing output across runs. - vmOpts.add("-Xlog:all=warning:stdout:level,tags"); - vmOpts.add("-Xlog:aot=off"); + vmOpts.add("-Xlog:all=warning,aot=off:stdout:level,tags"); //setup mode-specific options switch (testVMMode) { diff --git a/test/hotspot/jtreg/compiler/intrinsics/klass/TestGetModifiers.java b/test/hotspot/jtreg/compiler/intrinsics/klass/TestGetModifiers.java index eb0bed6e012..b8946fb7d84 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/klass/TestGetModifiers.java +++ b/test/hotspot/jtreg/compiler/intrinsics/klass/TestGetModifiers.java @@ -25,6 +25,8 @@ /* * @test * @library /test/lib + * @enablePreview + * @modules java.base/jdk.internal.misc * @run main/othervm -Xint * -XX:CompileCommand=dontinline,*TestGetModifiers.test * compiler.intrinsics.klass.TestGetModifiers @@ -34,6 +36,8 @@ * @test * @requires vm.compiler1.enabled * @library /test/lib + * @enablePreview + * @modules java.base/jdk.internal.misc * @run main/othervm -XX:TieredStopAtLevel=1 -XX:+TieredCompilation * -XX:CompileCommand=dontinline,*TestGetModifiers.test * compiler.intrinsics.klass.TestGetModifiers @@ -43,6 +47,8 @@ * @test * @requires vm.compiler2.enabled * @library /test/lib + * @enablePreview + * @modules java.base/jdk.internal.misc * @run main/othervm -XX:-TieredCompilation * -XX:CompileCommand=dontinline,*TestGetModifiers.test * compiler.intrinsics.klass.TestGetModifiers @@ -54,6 +60,7 @@ import static java.lang.reflect.Modifier.*; import jdk.test.lib.Asserts; +import jdk.internal.misc.PreviewFeatures; public class TestGetModifiers { public static class T1 { @@ -78,17 +85,17 @@ static void test(Class cl, int expectedMods) { for (int i = 0; i < 100_000; i++) { int actualMods = cl.getModifiers(); if (actualMods != expectedMods) { - throw new IllegalStateException("Error with: " + cl); + throw new IllegalStateException("Error with: " + cl + " : " + actualMods + " vs " + expectedMods); } } } public static void main(String... args) { - test(T1.class, PUBLIC | STATIC); - test(T2.class, PUBLIC | FINAL | STATIC); - test(T3.class, PRIVATE | STATIC); - test(T4.class, PROTECTED | STATIC); - test(new TestGetModifiers().new T5().getClass(), 0); + test(T1.class, PUBLIC | STATIC | IDENTITY); + test(T2.class, PUBLIC | FINAL | STATIC | IDENTITY); + test(T3.class, PRIVATE | STATIC | IDENTITY); + test(T4.class, PROTECTED | STATIC | IDENTITY); + test(new TestGetModifiers().new T5().getClass(), IDENTITY); test(T6.class, ABSTRACT | STATIC | INTERFACE); test(int.class, PUBLIC | ABSTRACT | FINAL); @@ -99,20 +106,20 @@ public static void main(String... args) { test(byte.class, PUBLIC | ABSTRACT | FINAL); test(short.class, PUBLIC | ABSTRACT | FINAL); test(void.class, PUBLIC | ABSTRACT | FINAL); - test(int[].class, PUBLIC | ABSTRACT | FINAL); - test(long[].class, PUBLIC | ABSTRACT | FINAL); - test(double[].class, PUBLIC | ABSTRACT | FINAL); - test(float[].class, PUBLIC | ABSTRACT | FINAL); - test(char[].class, PUBLIC | ABSTRACT | FINAL); - test(byte[].class, PUBLIC | ABSTRACT | FINAL); - test(short[].class, PUBLIC | ABSTRACT | FINAL); - test(Object[].class, PUBLIC | ABSTRACT | FINAL); - test(TestGetModifiers[].class, PUBLIC | ABSTRACT | FINAL); - - test(new TestGetModifiers().getClass(), PUBLIC); - test(new T1().getClass(), PUBLIC | STATIC); - test(new T2().getClass(), PUBLIC | FINAL | STATIC); - test(new T3().getClass(), PRIVATE | STATIC); - test(new T4().getClass(), PROTECTED | STATIC); + test(int[].class, PUBLIC | ABSTRACT | FINAL | (PreviewFeatures.isEnabled() ? IDENTITY : 0)); + test(long[].class, PUBLIC | ABSTRACT | FINAL | (PreviewFeatures.isEnabled() ? IDENTITY : 0)); + test(double[].class, PUBLIC | ABSTRACT | FINAL | (PreviewFeatures.isEnabled() ? IDENTITY : 0)); + test(float[].class, PUBLIC | ABSTRACT | FINAL | (PreviewFeatures.isEnabled() ? IDENTITY : 0)); + test(char[].class, PUBLIC | ABSTRACT | FINAL | (PreviewFeatures.isEnabled() ? IDENTITY : 0)); + test(byte[].class, PUBLIC | ABSTRACT | FINAL | (PreviewFeatures.isEnabled() ? IDENTITY : 0)); + test(short[].class, PUBLIC | ABSTRACT | FINAL | (PreviewFeatures.isEnabled() ? IDENTITY : 0)); + test(Object[].class, PUBLIC | ABSTRACT | FINAL | (PreviewFeatures.isEnabled() ? IDENTITY : 0)); + test(TestGetModifiers[].class, PUBLIC | ABSTRACT | FINAL | (PreviewFeatures.isEnabled() ? IDENTITY : 0)); + + test(new TestGetModifiers().getClass(), PUBLIC | IDENTITY); + test(new T1().getClass(), PUBLIC | STATIC | IDENTITY); + test(new T2().getClass(), PUBLIC | FINAL | STATIC | IDENTITY); + test(new T3().getClass(), PRIVATE | STATIC | IDENTITY); + test(new T4().getClass(), PROTECTED | STATIC | IDENTITY); } } diff --git a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.code.test/src/jdk/vm/ci/code/test/MethodTagTest.java b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.code.test/src/jdk/vm/ci/code/test/MethodTagTest.java new file mode 100644 index 00000000000..58abb10c639 --- /dev/null +++ b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.code.test/src/jdk/vm/ci/code/test/MethodTagTest.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8360851 + * @summary Test that the PrintAssembly [Entry Point] gets annotated with "# {method}". + * @requires vm.jvmci + * @requires vm.simpleArch == "x64" | vm.simpleArch == "aarch64" | vm.simpleArch == "riscv64" + * @library /test/lib / + * @modules jdk.internal.vm.ci/jdk.vm.ci.hotspot + * jdk.internal.vm.ci/jdk.vm.ci.meta + * jdk.internal.vm.ci/jdk.vm.ci.code + * jdk.internal.vm.ci/jdk.vm.ci.code.site + * jdk.internal.vm.ci/jdk.vm.ci.runtime + * jdk.internal.vm.ci/jdk.vm.ci.aarch64 + * jdk.internal.vm.ci/jdk.vm.ci.amd64 + * jdk.internal.vm.ci/jdk.vm.ci.riscv64 + * @compile TestAssembler.java TestHotSpotVMConfig.java amd64/AMD64TestAssembler.java aarch64/AArch64TestAssembler.java riscv64/RISCV64TestAssembler.java + * @run junit/othervm -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:-UseJVMCICompiler jdk.vm.ci.code.test.MethodTagTest + */ + +package jdk.vm.ci.code.test; + +import java.lang.reflect.Method; + +import org.junit.Assert; +import org.junit.Test; + +import jdk.test.lib.Asserts; +import jdk.vm.ci.aarch64.AArch64; +import jdk.vm.ci.amd64.AMD64; +import jdk.vm.ci.code.Architecture; +import jdk.vm.ci.code.CodeCacheProvider; +import jdk.vm.ci.code.InstalledCode; +import jdk.vm.ci.code.Register; +import jdk.vm.ci.code.TargetDescription; +import jdk.vm.ci.code.test.aarch64.AArch64TestAssembler; +import jdk.vm.ci.code.test.amd64.AMD64TestAssembler; +import jdk.vm.ci.code.test.riscv64.RISCV64TestAssembler; +import jdk.vm.ci.hotspot.HotSpotCodeCacheProvider; +import jdk.vm.ci.hotspot.HotSpotCompiledCode; +import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime; +import jdk.vm.ci.hotspot.HotSpotNmethod; +import jdk.vm.ci.hotspot.HotSpotResolvedJavaMethod; +import jdk.vm.ci.meta.ConstantReflectionProvider; +import jdk.vm.ci.meta.MetaAccessProvider; +import jdk.vm.ci.riscv64.RISCV64; +import jdk.vm.ci.runtime.JVMCI; +import jdk.vm.ci.runtime.JVMCIBackend; + + +public final class MethodTagTest { + private static final boolean DEBUG = false; + + protected final MetaAccessProvider metaAccess; + protected final CodeCacheProvider codeCache; + protected final TargetDescription target; + protected final ConstantReflectionProvider constantReflection; + protected final TestHotSpotVMConfig config; + protected final Architecture arch; + + public MethodTagTest() { + JVMCIBackend backend = JVMCI.getRuntime().getHostJVMCIBackend(); + metaAccess = backend.getMetaAccess(); + codeCache = backend.getCodeCache(); + target = backend.getTarget(); + constantReflection = backend.getConstantReflection(); + arch = codeCache.getTarget().arch; + config = new TestHotSpotVMConfig(HotSpotJVMCIRuntime.runtime().getConfigStore(), arch); + } + + protected interface TestCompiler { + + void compile(TestAssembler asm); + } + + private TestAssembler createAssembler() { + if (arch instanceof AMD64) { + return new AMD64TestAssembler(codeCache, config); + } else if (arch instanceof AArch64) { + return new AArch64TestAssembler(codeCache, config); + } else if (arch instanceof RISCV64) { + return new RISCV64TestAssembler(codeCache, config); + } else { + Assert.fail("unsupported architecture"); + return null; + } + } + + protected Method getMethod(String name, Class... args) { + try { + return getClass().getMethod(name, args); + } catch (NoSuchMethodException e) { + Assert.fail("method not found"); + return null; + } + } + + public static int add(int a, int b) { + return a + b; + } + + private static void compileAdd(TestAssembler asm) { + Register arg0 = asm.emitIntArg0(); + Register arg1 = asm.emitIntArg1(); + Register ret = asm.emitIntAdd(arg0, arg1); + asm.emitIntRet(ret); + } + + protected HotSpotNmethod test(TestCompiler compiler, Method method, Object... args) { + try { + HotSpotResolvedJavaMethod resolvedMethod = (HotSpotResolvedJavaMethod) metaAccess.lookupJavaMethod(method); + TestAssembler asm = createAssembler(); + + asm.emitPrologue(); + compiler.compile(asm); + asm.emitEpilogue(); + + HotSpotCompiledCode code = asm.finish(resolvedMethod); + InstalledCode installed = codeCache.addCode(resolvedMethod, code, null, null, true); + + String str = ((HotSpotCodeCacheProvider) codeCache).disassemble(installed); + Asserts.assertTrue(str.contains("# {method}"), "\"# {method}\" tag not found"); + if (DEBUG) { + System.out.println(str); + } + + Object expected = method.invoke(null, args); + Object actual = installed.executeVarargs(args); + Assert.assertEquals(expected, actual); + return (HotSpotNmethod) installed; + } catch (Exception e) { + throw new AssertionError(e); + } + } + + @Test + public void test() { + HotSpotNmethod nmethod = test(MethodTagTest::compileAdd, getMethod("add", int.class, int.class), 5, 7); + + // Test code invalidation + Asserts.assertTrue(nmethod.isValid(), "code is not valid, i = " + nmethod); + Asserts.assertTrue(nmethod.isAlive(), "code is not alive, i = " + nmethod); + Asserts.assertNotEquals(nmethod.getStart(), 0L); + + // Make nmethod non-entrant but still alive + nmethod.invalidate(false); + Asserts.assertFalse(nmethod.isValid(), "code is valid, i = " + nmethod); + Asserts.assertTrue(nmethod.isAlive(), "code is not alive, i = " + nmethod); + Asserts.assertEquals(nmethod.getStart(), 0L); + + // Deoptimize the nmethod and cut the link to it from the HotSpotNmethod + nmethod.invalidate(true); + Asserts.assertFalse(nmethod.isValid(), "code is valid, i = " + nmethod); + Asserts.assertFalse(nmethod.isAlive(), "code is alive, i = " + nmethod); + Asserts.assertEquals(nmethod.getStart(), 0L); + } +} diff --git a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.code.test/src/jdk/vm/ci/code/test/VirtualObjectDebugInfoTest.java b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.code.test/src/jdk/vm/ci/code/test/VirtualObjectDebugInfoTest.java index 543128e932c..913a10a9261 100644 --- a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.code.test/src/jdk/vm/ci/code/test/VirtualObjectDebugInfoTest.java +++ b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.code.test/src/jdk/vm/ci/code/test/VirtualObjectDebugInfoTest.java @@ -70,6 +70,36 @@ private static class TestClass { this.arrayField = new Object[]{Integer.valueOf(58), this, null, Integer.valueOf(17), "Hello, World!"}; } + @Override + public String toString() { + var builder = new StringBuilder() + .append("{l: ") + .append(longField) + .append("; ") + .append("i: ") + .append(intField) + .append("; ") + .append("f: ") + .append(floatField) + .append("; ") + .append("a[") + .append(arrayField.length) + .append("]: "); + for (int i = 0; i < arrayField.length; ++i) { + if (i != 0) { + builder.append("; "); + } + builder.append("[").append(i).append("]="); + if (arrayField[i] == this) { + builder.append("this"); + } else { + builder.append(arrayField[i]); + } + } + builder.append("}"); + return builder.toString(); + } + @Override public boolean equals(Object o) { if (!(o instanceof TestClass)) { diff --git a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaType.java b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaType.java index 6501d082075..fafee2c126c 100644 --- a/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaType.java +++ b/test/hotspot/jtreg/compiler/jvmci/jdk.vm.ci.runtime.test/src/jdk/vm/ci/runtime/test/TestResolvedJavaType.java @@ -241,7 +241,8 @@ public void lambdaInternalNameTest() { assertEquals(javaName, internalNameToJava(typeName, true, true)); } - @Test + // TODO 8291719 + // @Test public void getModifiersTest() { for (Class c : classes) { ResolvedJavaType type = metaAccess.lookupJavaType(c); @@ -319,7 +320,9 @@ public void getInterfacesTest() { ResolvedJavaType type = metaAccess.lookupJavaType(c); Class[] expected = c.getInterfaces(); ResolvedJavaType[] actual = type.getInterfaces(); - assertEquals(expected.length, actual.length); + // With injection of the IdentityObject interface by the JVM, the number of + // interfaces visible through reflection and through JVMCI could differ by one + assertTrue(expected.length == actual.length || (actual.length - expected.length) == 1); for (int i = 0; i < expected.length; i++) { assertTrue(actual[i].equals(metaAccess.lookupJavaType(expected[i]))); } diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index 16c6d99a64f..1939b9b0bb6 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -28,6 +28,7 @@ import compiler.lib.ir_framework.shared.CheckedTestFrameworkException; import compiler.lib.ir_framework.shared.TestFormat; import compiler.lib.ir_framework.shared.TestFormatException; +import compiler.valhalla.inlinetypes.InlineTypeIRNode; import jdk.test.lib.Platform; import jdk.test.whitebox.WhiteBox; @@ -87,7 +88,7 @@ public class IRNode { /** * Prefix for normal IR nodes. */ - private static final String PREFIX = "_#"; + public static final String PREFIX = "_#"; /** * Prefix for composite IR nodes. */ @@ -150,6 +151,12 @@ public class IRNode { * } */ + // Valhalla: Make sure that all Valhalla specific IR nodes are also properly initialized. Doing it here also + // ensures that the Flag VM is able to pick up the correct compile phases. + static { + InlineTypeIRNode.forceStaticInitialization(); + } + public static final String ABS_D = PREFIX + "ABS_D" + POSTFIX; static { beforeMatchingNameRegex(ABS_D, "AbsD"); @@ -381,8 +388,12 @@ public class IRNode { public static final String ALLOC_OF = COMPOSITE_PREFIX + "ALLOC_OF" + POSTFIX; static { - String regex = START + "Allocate\\b" + MID + "allocationKlass:.*\\b" + IS_REPLACED + "\\s.*" + END; - macroNodes(ALLOC_OF, regex); + allocateOfNodes(ALLOC_OF, IS_REPLACED); + } + + public static void allocateOfNodes(String irNodePlaceholder, String allocatee) { + String regex = START + "Allocate\\b" + MID + "allocationKlass:.*\\b" + allocatee + "\\s.*" + END; + macroNodes(irNodePlaceholder, regex); } public static final String ALLOC_ARRAY = PREFIX + "ALLOC_ARRAY" + POSTFIX; @@ -393,6 +404,10 @@ public class IRNode { public static final String ALLOC_ARRAY_OF = COMPOSITE_PREFIX + "ALLOC_ARRAY_OF" + POSTFIX; static { + allocateArrayOfNodes(ALLOC_ARRAY_OF, IS_REPLACED); + } + + public static void allocateArrayOfNodes(String irNodePlaceholder, String allocatee) { // Assuming we are looking for an array of "some/package/MyClass". The printout is // [Lsome/package/MyClass; // or, with more dimensions @@ -411,9 +426,9 @@ public class IRNode { // but will eat the package path prefix in the cases described above // - the name we are looking for // - the final ";". - String name_part = "\\[+.(" + partial_name_prefix + ")?" + IS_REPLACED + ";"; + String name_part = "\\[+.(" + partial_name_prefix + ")?" + allocatee + ";"; String regex = START + "AllocateArray\\b" + MID + "allocationKlass:" + name_part + ".*" + END; - macroNodes(ALLOC_ARRAY_OF, regex); + macroNodes(irNodePlaceholder, regex); } public static final String OR = PREFIX + "OR" + POSTFIX; @@ -478,17 +493,40 @@ public class IRNode { public static final String CALL_OF = COMPOSITE_PREFIX + "CALL_OF" + POSTFIX; static { - callOfNodes(CALL_OF, "Call.*"); + callOfNodes(CALL_OF, "Call.*", IS_REPLACED + " " ); } public static final String CALL_OF_METHOD = COMPOSITE_PREFIX + "CALL_OF_METHOD" + POSTFIX; static { - callOfNodes(CALL_OF_METHOD, "Call.*Java"); + callOfNodes(CALL_OF_METHOD, "Call.*Java", IS_REPLACED + " "); + } + + public static final String STATIC_CALL = PREFIX + "STATIC_CALL" + POSTFIX; + static { + beforeMatchingNameRegex(STATIC_CALL, "CallStaticJava"); } public static final String STATIC_CALL_OF_METHOD = COMPOSITE_PREFIX + "STATIC_CALL_OF_METHOD" + POSTFIX; static { - callOfNodes(STATIC_CALL_OF_METHOD, "CallStaticJava"); + staticCallOfMethodNodes(STATIC_CALL_OF_METHOD, IS_REPLACED + " "); + } + + public static void staticCallOfMethodNodes(String irNodePlaceholder, String calleeRegex) { + callOfNodes(irNodePlaceholder, "CallStaticJava", calleeRegex); + } + + public static final String CALL_LEAF_NO_FP = PREFIX + "CALL_LEAF_NO_FP" + POSTFIX; + static { + beforeMatchingNameRegex(CALL_LEAF_NO_FP, "CallLeafNoFP"); + } + + public static final String CALL_LEAF_NO_FP_OF_METHOD = COMPOSITE_PREFIX + "CALL_LEAF_NO_FP_OF_METHOD" + POSTFIX; + static { + callLeafNoFpOfMethodNodes(CALL_LEAF_NO_FP_OF_METHOD, IS_REPLACED); + } + + public static void callLeafNoFpOfMethodNodes(String irNodePlaceholder, String calleeRegex) { + callOfNodes(irNodePlaceholder, "CallLeafNoFP", calleeRegex); } public static final String CAST_II = PREFIX + "CAST_II" + POSTFIX; @@ -585,6 +623,11 @@ public class IRNode { beforeMatchingNameRegex(CMP_P, "CmpP"); } + public static final String CMP_N = PREFIX + "CMP_N" + POSTFIX; + static { + beforeMatchingNameRegex(CMP_N, "CmpN"); + } + public static final String CMP_LT_MASK = PREFIX + "CMP_LT_MASK" + POSTFIX; static { beforeMatchingNameRegex(CMP_LT_MASK, "CmpLTMask"); @@ -719,7 +762,7 @@ public class IRNode { public static final String DYNAMIC_CALL_OF_METHOD = COMPOSITE_PREFIX + "DYNAMIC_CALL_OF_METHOD" + POSTFIX; static { - callOfNodes(DYNAMIC_CALL_OF_METHOD, "CallDynamicJava"); + callOfNodes(DYNAMIC_CALL_OF_METHOD, "CallDynamicJava", IS_REPLACED); } public static final String EXPAND_BITS = PREFIX + "EXPAND_BITS" + POSTFIX; @@ -855,6 +898,11 @@ public class IRNode { beforeMatchingNameRegex(IF, "If\\b"); } + public static final String INLINE_TYPE = PREFIX + "INLINE_TYPE" + POSTFIX; + static { + beforeMatchingNameRegex(INLINE_TYPE, "InlineType"); + } + // Does not work for VM builds without JVMCI like x86_32 (a rule containing this regex will be skipped without having JVMCI built). public static final String INTRINSIC_OR_TYPE_CHECKED_INLINING_TRAP = PREFIX + "INTRINSIC_OR_TYPE_CHECKED_INLINING_TRAP" + POSTFIX; static { @@ -901,7 +949,11 @@ public class IRNode { public static final String LOAD_OF_CLASS = COMPOSITE_PREFIX + "LOAD_OF_CLASS" + POSTFIX; static { - loadOfNodes(LOAD_OF_CLASS, "Load(B|UB|S|US|I|L|F|D|P|N)"); + anyLoadOfNodes(LOAD_OF_CLASS, IS_REPLACED); + } + + public static void anyLoadOfNodes(String irNodePlaceholder, String fieldHolder) { + loadOfNodes(irNodePlaceholder, "Load(B|UB|S|US|I|L|F|D|P|N)", fieldHolder); } public static final String LOAD_B = PREFIX + "LOAD_B" + POSTFIX; @@ -911,7 +963,7 @@ public class IRNode { public static final String LOAD_B_OF_CLASS = COMPOSITE_PREFIX + "LOAD_B_OF_CLASS" + POSTFIX; static { - loadOfNodes(LOAD_B_OF_CLASS, "LoadB"); + loadOfNodes(LOAD_B_OF_CLASS, "LoadB", IS_REPLACED); } public static final String LOAD_D = PREFIX + "LOAD_D" + POSTFIX; @@ -921,7 +973,7 @@ public class IRNode { public static final String LOAD_D_OF_CLASS = COMPOSITE_PREFIX + "LOAD_D_OF_CLASS" + POSTFIX; static { - loadOfNodes(LOAD_D_OF_CLASS, "LoadD"); + loadOfNodes(LOAD_D_OF_CLASS, "LoadD", IS_REPLACED); } public static final String LOAD_F = PREFIX + "LOAD_F" + POSTFIX; @@ -931,7 +983,7 @@ public class IRNode { public static final String LOAD_F_OF_CLASS = COMPOSITE_PREFIX + "LOAD_F_OF_CLASS" + POSTFIX; static { - loadOfNodes(LOAD_F_OF_CLASS, "LoadF"); + loadOfNodes(LOAD_F_OF_CLASS, "LoadF", IS_REPLACED); } public static final String LOAD_I = PREFIX + "LOAD_I" + POSTFIX; @@ -941,7 +993,7 @@ public class IRNode { public static final String LOAD_I_OF_CLASS = COMPOSITE_PREFIX + "LOAD_I_OF_CLASS" + POSTFIX; static { - loadOfNodes(LOAD_I_OF_CLASS, "LoadI"); + loadOfNodes(LOAD_I_OF_CLASS, "LoadI", IS_REPLACED); } public static final String LOAD_KLASS = PREFIX + "LOAD_KLASS" + POSTFIX; @@ -966,7 +1018,7 @@ public class IRNode { public static final String LOAD_L_OF_CLASS = COMPOSITE_PREFIX + "LOAD_L_OF_CLASS" + POSTFIX; static { - loadOfNodes(LOAD_L_OF_CLASS, "LoadL"); + loadOfNodes(LOAD_L_OF_CLASS, "LoadL", IS_REPLACED); } public static final String LOAD_N = PREFIX + "LOAD_N" + POSTFIX; @@ -976,7 +1028,7 @@ public class IRNode { public static final String LOAD_N_OF_CLASS = COMPOSITE_PREFIX + "LOAD_N_OF_CLASS" + POSTFIX; static { - loadOfNodes(LOAD_N_OF_CLASS, "LoadN"); + loadOfNodes(LOAD_N_OF_CLASS, "LoadN", IS_REPLACED); } public static final String LOAD_OF_FIELD = COMPOSITE_PREFIX + "LOAD_OF_FIELD" + POSTFIX; @@ -992,7 +1044,7 @@ public class IRNode { public static final String LOAD_P_OF_CLASS = COMPOSITE_PREFIX + "LOAD_P_OF_CLASS" + POSTFIX; static { - loadOfNodes(LOAD_P_OF_CLASS, "LoadP"); + loadOfNodes(LOAD_P_OF_CLASS, "LoadP", IS_REPLACED); } public static final String LOAD_S = PREFIX + "LOAD_S" + POSTFIX; @@ -1002,7 +1054,7 @@ public class IRNode { public static final String LOAD_S_OF_CLASS = COMPOSITE_PREFIX + "LOAD_S_OF_CLASS" + POSTFIX; static { - loadOfNodes(LOAD_S_OF_CLASS, "LoadS"); + loadOfNodes(LOAD_S_OF_CLASS, "LoadS", IS_REPLACED); } public static final String LOAD_UB = PREFIX + "LOAD_UB" + POSTFIX; @@ -1012,7 +1064,7 @@ public class IRNode { public static final String LOAD_UB_OF_CLASS = COMPOSITE_PREFIX + "LOAD_UB_OF_CLASS" + POSTFIX; static { - loadOfNodes(LOAD_UB_OF_CLASS, "LoadUB"); + loadOfNodes(LOAD_UB_OF_CLASS, "LoadUB", IS_REPLACED); } public static final String LOAD_US = PREFIX + "LOAD_US" + POSTFIX; @@ -1022,7 +1074,7 @@ public class IRNode { public static final String LOAD_US_OF_CLASS = COMPOSITE_PREFIX + "LOAD_US_OF_CLASS" + POSTFIX; static { - loadOfNodes(LOAD_US_OF_CLASS, "LoadUS"); + loadOfNodes(LOAD_US_OF_CLASS, "LoadUS", IS_REPLACED); } public static final String LOAD_VECTOR_B = VECTOR_PREFIX + "LOAD_VECTOR_B" + POSTFIX; @@ -1921,7 +1973,7 @@ public class IRNode { public static final String STORE_B_OF_CLASS = COMPOSITE_PREFIX + "STORE_B_OF_CLASS" + POSTFIX; static { - storeOfNodes(STORE_B_OF_CLASS, "StoreB"); + storeOfNodes(STORE_B_OF_CLASS, "StoreB", IS_REPLACED); } public static final String STORE_C = PREFIX + "STORE_C" + POSTFIX; @@ -1931,7 +1983,7 @@ public class IRNode { public static final String STORE_C_OF_CLASS = COMPOSITE_PREFIX + "STORE_C_OF_CLASS" + POSTFIX; static { - storeOfNodes(STORE_C_OF_CLASS, "StoreC"); + storeOfNodes(STORE_C_OF_CLASS, "StoreC", IS_REPLACED); } public static final String STORE_D = PREFIX + "STORE_D" + POSTFIX; @@ -1941,7 +1993,7 @@ public class IRNode { public static final String STORE_D_OF_CLASS = COMPOSITE_PREFIX + "STORE_D_OF_CLASS" + POSTFIX; static { - storeOfNodes(STORE_D_OF_CLASS, "StoreD"); + storeOfNodes(STORE_D_OF_CLASS, "StoreD", IS_REPLACED); } public static final String STORE_F = PREFIX + "STORE_F" + POSTFIX; @@ -1951,7 +2003,7 @@ public class IRNode { public static final String STORE_F_OF_CLASS = COMPOSITE_PREFIX + "STORE_F_OF_CLASS" + POSTFIX; static { - storeOfNodes(STORE_F_OF_CLASS, "StoreF"); + storeOfNodes(STORE_F_OF_CLASS, "StoreF", IS_REPLACED); } public static final String STORE_I = PREFIX + "STORE_I" + POSTFIX; @@ -1961,7 +2013,7 @@ public class IRNode { public static final String STORE_I_OF_CLASS = COMPOSITE_PREFIX + "STORE_I_OF_CLASS" + POSTFIX; static { - storeOfNodes(STORE_I_OF_CLASS, "StoreI"); + storeOfNodes(STORE_I_OF_CLASS, "StoreI", IS_REPLACED); } public static final String STORE_L = PREFIX + "STORE_L" + POSTFIX; @@ -1971,7 +2023,7 @@ public class IRNode { public static final String STORE_L_OF_CLASS = COMPOSITE_PREFIX + "STORE_L_OF_CLASS" + POSTFIX; static { - storeOfNodes(STORE_L_OF_CLASS, "StoreL"); + storeOfNodes(STORE_L_OF_CLASS, "StoreL", IS_REPLACED); } public static final String STORE_N = PREFIX + "STORE_N" + POSTFIX; @@ -1981,12 +2033,16 @@ public class IRNode { public static final String STORE_N_OF_CLASS = COMPOSITE_PREFIX + "STORE_N_OF_CLASS" + POSTFIX; static { - storeOfNodes(STORE_N_OF_CLASS, "StoreN"); + storeOfNodes(STORE_N_OF_CLASS, "StoreN", IS_REPLACED); } public static final String STORE_OF_CLASS = COMPOSITE_PREFIX + "STORE_OF_CLASS" + POSTFIX; static { - storeOfNodes(STORE_OF_CLASS, "Store(B|C|S|I|L|F|D|P|N)"); + anyStoreOfNodes(STORE_OF_CLASS, IS_REPLACED); + } + + public static void anyStoreOfNodes(String irNodePlaceholder, String fieldHolder) { + storeOfNodes(irNodePlaceholder, "Store(B|C|S|I|L|F|D|P|N)", fieldHolder); } public static final String STORE_OF_FIELD = COMPOSITE_PREFIX + "STORE_OF_FIELD" + POSTFIX; @@ -2002,7 +2058,7 @@ public class IRNode { public static final String STORE_P_OF_CLASS = COMPOSITE_PREFIX + "STORE_P_OF_CLASS" + POSTFIX; static { - storeOfNodes(STORE_P_OF_CLASS, "StoreP"); + storeOfNodes(STORE_P_OF_CLASS, "StoreP", IS_REPLACED); } public static final String STORE_VECTOR = PREFIX + "STORE_VECTOR" + POSTFIX; @@ -2092,7 +2148,8 @@ public class IRNode { public static final String SUBTYPE_CHECK = PREFIX + "SUBTYPE_CHECK" + POSTFIX; static { - beforeMatchingNameRegex(SUBTYPE_CHECK, "SubTypeCheck"); + String regex = START + "SubTypeCheck" + MID + END; + macroNodes(SUBTYPE_CHECK, regex); } public static final String TRAP = PREFIX + "TRAP" + POSTFIX; @@ -2914,7 +2971,7 @@ public class IRNode { * Apply {@code regex} on all machine independent ideal graph phases up to and including * {@link CompilePhase#BEFORE_MATCHING}. */ - private static void beforeMatching(String irNodePlaceholder, String regex) { + public static void beforeMatching(String irNodePlaceholder, String regex) { IR_NODE_MAPPINGS.put(irNodePlaceholder, new RegexTypeEntry(RegexType.IDEAL_INDEPENDENT, regex)); } @@ -2950,8 +3007,8 @@ private static void macroNodes(String irNodePlaceholder, String regex) { CompilePhase.BEFORE_MACRO_EXPANSION)); } - private static void callOfNodes(String irNodePlaceholder, String callRegex) { - String regex = START + callRegex + MID + IS_REPLACED + " " + END; + private static void callOfNodes(String irNodePlaceholder, String callRegex, String calleeRegex) { + String regex = START + callRegex + MID + calleeRegex + END; IR_NODE_MAPPINGS.put(irNodePlaceholder, new RegexTypeEntry(RegexType.IDEAL_INDEPENDENT, regex)); } @@ -2959,7 +3016,7 @@ private static void callOfNodes(String irNodePlaceholder, String callRegex) { * Apply {@code regex} on all machine dependant ideal graph phases (i.e. on the mach graph) starting from * {@link CompilePhase#MATCHING}. */ - private static void optoOnly(String irNodePlaceholder, String regex) { + public static void optoOnly(String irNodePlaceholder, String regex) { IR_NODE_MAPPINGS.put(irNodePlaceholder, new RegexTypeEntry(RegexType.OPTO_ASSEMBLY, regex)); } @@ -3044,13 +3101,13 @@ private static void parsePredicateNodes(String irNodePlaceholder, String label) private static final String LOAD_STORE_PREFIX = "@(\\w+: ?)*[\\w/\\$]*\\b"; private static final String LOAD_STORE_SUFFIX = "( \\([^\\)]+\\))?(:|\\+)\\S* \\*"; - private static void loadOfNodes(String irNodePlaceholder, String irNodeRegex) { - String regex = START + irNodeRegex + MID + LOAD_STORE_PREFIX + IS_REPLACED + LOAD_STORE_SUFFIX + END; + private static void loadOfNodes(String irNodePlaceholder, String irNodeRegex, String loadee) { + String regex = START + irNodeRegex + MID + LOAD_STORE_PREFIX + loadee + LOAD_STORE_SUFFIX + END; beforeMatching(irNodePlaceholder, regex); } - private static void storeOfNodes(String irNodePlaceholder, String irNodeRegex) { - String regex = START + irNodeRegex + MID + LOAD_STORE_PREFIX + IS_REPLACED + LOAD_STORE_SUFFIX + END; + private static void storeOfNodes(String irNodePlaceholder, String irNodeRegex, String storee) { + String regex = START + irNodeRegex + MID + LOAD_STORE_PREFIX + storee + LOAD_STORE_SUFFIX + END; beforeMatching(irNodePlaceholder, regex); } diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java b/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java index 85c52ef33da..0588f65ffd2 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java @@ -649,6 +649,18 @@ public static void assertDeoptimizedByC2(Method m) { TestVM.assertDeoptimizedByC2(m); } + /** + * Checks if deopt of {@code m} is stable at the specified {@code compLevel}. + * + * @param m the method to be checked. + * @param compLevel the compilation level. + * @return {@code true} if deopt of {@code m} is stable at {@code compLevel}; + * {@code false} otherwise. + */ + public static boolean isStableDeopt(Method m, CompLevel compLevel) { + return TestVM.isStableDeopt(m, compLevel); + } + /** * Returns a different boolean each time this method is invoked (switching between {@code false} and {@code true}). * The very first invocation returns {@code false}. Note that this method could be used by different tests and diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/test/TestVM.java b/test/hotspot/jtreg/compiler/lib/ir_framework/test/TestVM.java index 8ccd0faa013..89748bb97d6 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/test/TestVM.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/test/TestVM.java @@ -104,6 +104,7 @@ assertions from main() of your test! private static final boolean PRINT_VALID_IR_RULES = Boolean.getBoolean("ShouldDoIRVerification"); protected static final long PER_METHOD_TRAP_LIMIT = (Long)WHITE_BOX.getVMFlag("PerMethodTrapLimit"); protected static final boolean PROFILE_INTERPRETER = (Boolean)WHITE_BOX.getVMFlag("ProfileInterpreter"); + protected static final boolean DEOPT_BARRIERS_ALOT = (Boolean)WHITE_BOX.getVMFlag("DeoptimizeNMethodBarriersALot"); private static final boolean FLIP_C1_C2 = Boolean.getBoolean("FlipC1C2"); private static final boolean IGNORE_COMPILER_CONTROLS = Boolean.getBoolean("IgnoreCompilerControls"); @@ -256,8 +257,11 @@ private void start() { } private void setupTests() { - for (Class clazz : testClass.getDeclaredClasses()) { - checkAnnotationsInClass(clazz, "inner"); + // TODO remove this once JDK-8273591 is fixed + if (!IGNORE_COMPILER_CONTROLS) { + for (Class clazz : testClass.getDeclaredClasses()) { + checkAnnotationsInClass(clazz, "inner"); + } } if (DUMP_REPLAY) { addReplay(); @@ -939,24 +943,23 @@ public static boolean isCompiledAtLevel(Method m, CompLevel compLevel) { } public static void assertDeoptimizedByC1(Method m) { - if (notUnstableDeoptAssertion(m, CompLevel.C1_SIMPLE)) { - TestRun.check(compiledByC1(m) != TriState.Yes || PER_METHOD_TRAP_LIMIT == 0 || !PROFILE_INTERPRETER, - m + " should have been deoptimized by C1"); + if (isStableDeopt(m, CompLevel.C1_SIMPLE)) { + TestRun.check(compiledByC1(m) != TriState.Yes, m + " should have been deoptimized by C1"); } } public static void assertDeoptimizedByC2(Method m) { - if (notUnstableDeoptAssertion(m, CompLevel.C2)) { - TestRun.check(compiledByC2(m) != TriState.Yes || PER_METHOD_TRAP_LIMIT == 0 || !PROFILE_INTERPRETER, - m + " should have been deoptimized by C2"); + if (isStableDeopt(m, CompLevel.C2)) { + TestRun.check(compiledByC2(m) != TriState.Yes, m + " should have been deoptimized by C2"); } } /** * Some VM flags could make the deopt assertions unstable. */ - private static boolean notUnstableDeoptAssertion(Method m, CompLevel level) { + public static boolean isStableDeopt(Method m, CompLevel level) { return (USE_COMPILER && !XCOMP && !IGNORE_COMPILER_CONTROLS && !TEST_C1 && + PER_METHOD_TRAP_LIMIT != 0 && PROFILE_INTERPRETER && !DEOPT_BARRIERS_ALOT && (!EXCLUDE_RANDOM || WHITE_BOX.isMethodCompilable(m, level.getValue(), false))); } @@ -1012,7 +1015,7 @@ private static TriState compiledAtLevel(Method m, CompLevel level) { default -> throw new TestRunException("compiledAtLevel() should not be called with " + level); } } - if (!USE_COMPILER || XCOMP || TEST_C1 || IGNORE_COMPILER_CONTROLS || FLIP_C1_C2 || + if (!USE_COMPILER || XCOMP || TEST_C1 || IGNORE_COMPILER_CONTROLS || FLIP_C1_C2 || DEOPT_BARRIERS_ALOT || (EXCLUDE_RANDOM && !WHITE_BOX.isMethodCompilable(m, level.getValue(), false))) { return TriState.Maybe; } diff --git a/test/hotspot/jtreg/compiler/locks/TestLockElimination.java b/test/hotspot/jtreg/compiler/locks/TestLockElimination.java new file mode 100644 index 00000000000..2ee031ab8d9 --- /dev/null +++ b/test/hotspot/jtreg/compiler/locks/TestLockElimination.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.*; +import jdk.test.lib.Asserts; + +/* + * @test + * @bug 8366879 + * @summary Test that locks are successfully eliminated by C2. + * @library /test/lib / + * @run driver compiler.valhalla.inlinetypes.TestLockElimination + */ +public class TestLockElimination { + + static class MyClass { + public synchronized int val() { + return 42; + } + } + + @Test + @IR(failOn = { IRNode.ALLOC, IRNode.FAST_LOCK, IRNode.FAST_UNLOCK }) + public static int test(int max) { + MyClass obj = new MyClass(); + int res = 0; + for (int i = 0; i < max; i++) { + int x = obj.val(); + int y = obj.val(); + res += x + y; + } + return res; + } + + @Run(test = "test") + public static void test_runner() { + int res = test(3); + Asserts.assertEquals(res, 252); + } + + public static void main(String[] args) { + TestFramework.run(); + } +} diff --git a/test/hotspot/jtreg/compiler/loopopts/SplitIfSharedFastLockBehindCastPP.java b/test/hotspot/jtreg/compiler/loopopts/SplitIfSharedFastLockBehindCastPP.java index 628a5a4cb75..84c155031f1 100644 --- a/test/hotspot/jtreg/compiler/loopopts/SplitIfSharedFastLockBehindCastPP.java +++ b/test/hotspot/jtreg/compiler/loopopts/SplitIfSharedFastLockBehindCastPP.java @@ -1,4 +1,5 @@ /* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2019, Red Hat, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -70,6 +71,14 @@ private static void test1(boolean flag, Object obj) { } } + static class MyBox { + int val; + + public MyBox(int val) { + this.val = val; + } + } + private static Object test2(boolean flag) { int integer; if (flag) { @@ -80,7 +89,7 @@ private static Object test2(boolean flag) { integer = 2; } - Object obj = integer; + Object obj = new MyBox(integer); // This loop will be unswitched. The condition becomes candidate for split if for (int i = 0; i < 100; i++) { diff --git a/test/hotspot/jtreg/compiler/startup/StartupOutput.java b/test/hotspot/jtreg/compiler/startup/StartupOutput.java index 14897f7ab87..588e941386d 100644 --- a/test/hotspot/jtreg/compiler/startup/StartupOutput.java +++ b/test/hotspot/jtreg/compiler/startup/StartupOutput.java @@ -63,7 +63,7 @@ public static void main(String[] args) throws Exception { // On s390x, generated code is ~6x larger in fastdebug and ~1.4x in release builds vs. other archs, // hence we require slightly more minimum space. - int minInitialSize = 800 + (Platform.isS390x() ? 800 : 0); + int minInitialSize = 2000 + (Platform.isS390x() ? 800 : 0); for (int i = 0; i < 200; i++) { int initialCodeCacheSizeInKb = minInitialSize + rand.nextInt(400); int reservedCodeCacheSizeInKb = initialCodeCacheSizeInKb + rand.nextInt(200); diff --git a/test/hotspot/jtreg/compiler/tiered/ConstantGettersTransitionsTest.java b/test/hotspot/jtreg/compiler/tiered/ConstantGettersTransitionsTest.java index 8f5032b9c9f..fe9e518da10 100644 --- a/test/hotspot/jtreg/compiler/tiered/ConstantGettersTransitionsTest.java +++ b/test/hotspot/jtreg/compiler/tiered/ConstantGettersTransitionsTest.java @@ -24,6 +24,7 @@ /** * @test ConstantGettersTransitionsTest * @summary Test the correctness of compilation level transitions for constant getters methods + * @requires vm.opt.final.TieredCompilation * @library /test/lib / * @modules java.base/jdk.internal.misc * java.management diff --git a/test/hotspot/jtreg/compiler/types/TestMeetIncompatibleInterfaceArrays.java b/test/hotspot/jtreg/compiler/types/TestMeetIncompatibleInterfaceArrays.java index d4f4649eb83..ffab91f8b3a 100644 --- a/test/hotspot/jtreg/compiler/types/TestMeetIncompatibleInterfaceArrays.java +++ b/test/hotspot/jtreg/compiler/types/TestMeetIncompatibleInterfaceArrays.java @@ -25,6 +25,7 @@ * @test * @bug 8141551 * @summary C2 can not handle returns with inccompatible interface arrays + * @requires vm.opt.final.TieredCompilation * @requires vm.compMode == "Xmixed" & vm.flavor == "server" * @modules java.base/jdk.internal.misc * @library /testlibrary/asm diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/BlackholeTest.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/BlackholeTest.java new file mode 100644 index 00000000000..527dfc02002 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/BlackholeTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2021, 2024, Red Hat, Inc. All rights reserved. + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +/* + * @test BlackholeTest + * @summary Check that blackholes work with inline types + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm + * -Xbatch -XX:+UnlockExperimentalVMOptions + * -XX:CompileCommand=blackhole,compiler/valhalla/inlinetypes/BlackholeTest.blackhole + * compiler.valhalla.inlinetypes.BlackholeTest + */ + +public class BlackholeTest { + @LooselyConsistentValue + static value class MyValue { + int x = 0; + } + + @Strict + @NullRestricted + static MyValue v = new MyValue(); + @Strict + @NullRestricted + static volatile MyValue vv = new MyValue(); + + public static void main(String[] args) { + for (int c = 0; c < 5; c++) { + testNew(); + testField(); + testVolatileField(); + } + } + + private static void testNew() { + for (int c = 0; c < 100_000; c++) { + blackhole(new MyValue()); + } + } + + private static void testField() { + for (int c = 0; c < 100_000; c++) { + blackhole(v); + } + } + + private static void testVolatileField() { + for (int c = 0; c < 100_000; c++) { + blackhole(vv); + } + } + + public static void blackhole(MyValue v) { + // Should be empty + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/CorrectlyRestoreRfp.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/CorrectlyRestoreRfp.java new file mode 100644 index 00000000000..b1eb3c246d4 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/CorrectlyRestoreRfp.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @key randomness + * @summary When needing some stack extension for unpacking arguments from the non-scalarized entry point, + * on Aarch64, LR (x30) and RFP (x29) are duplicated on the stack. But x29 can be used as an ordinary + * register, and hold an oop whose value can be updated by the GC, which is aware of only one copy + * of x29 on the stack. When returning, one must correctly restore x29 using the value known by the GC. + * @library /test/lib /compiler/whitebox / + * @enablePreview + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch + * -XX:CompileCommand=dontinline,*::* + * -XX:CompileCommand=printcompilation,*::* + * -XX:CompileCommand=compileonly,compiler.valhalla.inlinetypes.CorrectlyRestoreRfp*::compile_me_* + * compiler.valhalla.inlinetypes.CorrectlyRestoreRfp + **/ + +package compiler.valhalla.inlinetypes; + +import jdk.test.whitebox.WhiteBox; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.concurrent.CountDownLatch; + +public class CorrectlyRestoreRfp { + static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + static final int COMP_LEVEL_SIMPLE = 1; // C1 + static final int COMP_LEVEL_FULL_OPTIMIZATION = 4; // C2 or JVMCI + + static value class SmallValue { + int x1; + int x2; + + public SmallValue(int i) { + this.x1 = i; + this.x2 = i; + } + + public String toString() { + return "x1 = " + x1 + ", x2 = " + x2; + } + } + + // Large value class with oops (and different number of fields) that requires stack extension/repair + static value class LargeValueWithOops { + Object x1; + Object x2; + Object x3; + Object x4; + Object x5; + + public LargeValueWithOops(Object obj) { + this.x1 = obj; + this.x2 = obj; + this.x3 = obj; + this.x4 = obj; + this.x5 = obj; + } + + public String toString() { + return "x1 = " + x1 + ", x2 = " + x2 + ", x3 = " + x3 + ", x4 = " + x4 + ", x5 = " + x5; + } + + public void verify(String loc, Object obj) { + if (x1 != obj || x2 != obj || x3 != obj || x4 != obj || x5 != obj) { + throw new RuntimeException("Incorrect result at " + loc + " for obj = " + obj + ": " + this); + } + } + + public static void compile_me_C2_verify(LargeValueWithOops val, String loc, Object obj, boolean useNull) { + val.verify(loc, obj); + } + } + + public static void dontInline() { } + + public static LargeValueWithOops testLargeValueWithOops(LargeValueWithOops val) { + dontInline(); // Prevent C2 from optimizing out below checks + return val; + } + + public static LargeValueWithOops compile_me_C1_testLargeValueWithOopsHelper(Object obj) { + LargeValueWithOops val = new LargeValueWithOops(obj); + val = testLargeValueWithOops(val); + LargeValueWithOops.compile_me_C2_verify(val, "helper", obj, false); + return val; + } + + static class GarbageProducerThread extends Thread { + public void run() { + for (;;) { + // Produce some garbage and then let the GC do its work + Object[] arrays = new Object[1024]; + for (int i = 0; i < arrays.length; i++) { + arrays[i] = new int[1024]; + } + System.gc(); + } + } + } + + public static void main(String[] args) throws Exception { + Method compile_me_C2_verify = LargeValueWithOops.class.getMethod("compile_me_C2_verify", LargeValueWithOops.class, String.class, Object.class, boolean.class); + WHITE_BOX.makeMethodNotCompilable(compile_me_C2_verify, COMP_LEVEL_SIMPLE, false); + Method compile_me_C1_testLargeValueWithOopsHelper = CorrectlyRestoreRfp.class.getMethod("compile_me_C1_testLargeValueWithOopsHelper", Object.class); + WHITE_BOX.makeMethodNotCompilable(compile_me_C1_testLargeValueWithOopsHelper, COMP_LEVEL_FULL_OPTIMIZATION, false); + + // Start another thread that does some allocations and calls System.gc() + // to trigger GCs while virtual threads are parked. + Thread garbage_producer = new GarbageProducerThread(); + garbage_producer.setDaemon(true); + garbage_producer.start(); + + CountDownLatch cdl = new CountDownLatch(1); + Thread.ofPlatform().start(() -> { + try { + // Trigger compilation + for (int i = 0; i < 500_000; i++) { + Object val = new SmallValue(i); + var v = compile_me_C1_testLargeValueWithOopsHelper(val); + LargeValueWithOops.compile_me_C2_verify(v, "return", val, false); + } + cdl.countDown(); + } catch (Exception e) { + System.out.println("Exception thrown: " + e); + e.printStackTrace(System.out); + System.exit(1); + } + }); + cdl.await(); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/GetfieldChains.jcod b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/GetfieldChains.jcod new file mode 100644 index 00000000000..a1af533bc33 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/GetfieldChains.jcod @@ -0,0 +1,749 @@ +/* + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// Generated from the following class files with PointN.x renamed and RectangleP.p1 made private +// java org.openjdk.asmtools.Main jdec NamedRectangleN.class NamedRectangleP.class PointN.class RectangleN.class RectangleP.class + +/* +package compiler.valhalla.inlinetypes; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +@LooselyConsistentValue +value class PointN { + int x; // Removed + int y; + + PointN(int i, int j) { + x = i; + y = j; + } +} + +@LooselyConsistentValue +value class RectangleN { + @Strict + @NullRestricted + PointN p1 = new PointN(4, 7); +} + +class NamedRectangleN { + @Strict + @NullRestricted + RectangleN rect = new RectangleN(); + String name = ""; + + static int getP1X(NamedRectangleN nr) { + return nr.rect.p1.x; + } +} + +@LooselyConsistentValue +value class RectangleP { + @Strict + @NullRestricted + PointN p1 = new PointN(4, 7); // Made private +} + +class NamedRectangleP { + @Strict + @NullRestricted + RectangleP rect = new RectangleP(); + String name = ""; + + static int getP1Y(NamedRectangleP nr) { + return nr.rect.p1.y; + } +} + +*/ + +class compiler/valhalla/inlinetypes/NamedRectangleN { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + class #2; // #1 + Utf8 "compiler/valhalla/inlinetypes/RectangleN"; // #2 + Method #1 #4; // #3 + NameAndType #5 #6; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Field #8 #9; // #7 + class #10; // #8 + NameAndType #11 #12; // #9 + Utf8 "compiler/valhalla/inlinetypes/NamedRectangleN"; // #10 + Utf8 "rect"; // #11 + Utf8 "Lcompiler/valhalla/inlinetypes/RectangleN;"; // #12 + String #14; // #13 + Utf8 ""; // #14 + Field #8 #16; // #15 + NameAndType #17 #18; // #16 + Utf8 "name"; // #17 + Utf8 "Ljava/lang/String;"; // #18 + Method #20 #4; // #19 + class #21; // #20 + Utf8 "java/lang/Object"; // #21 + Field #1 #23; // #22 + NameAndType #24 #25; // #23 + Utf8 "p1"; // #24 + Utf8 "Lcompiler/valhalla/inlinetypes/PointN;"; // #25 + Field #27 #28; // #26 + class #29; // #27 + NameAndType #30 #31; // #28 + Utf8 "compiler/valhalla/inlinetypes/PointN"; // #29 + Utf8 "x"; // #30 + Utf8 "I"; // #31 + Utf8 "RuntimeVisibleAnnotations"; // #32 + Utf8 "Ljdk/internal/vm/annotation/NullRestricted;"; // #33 + Utf8 "RuntimeInvisibleAnnotations"; // #34 + Utf8 "Ljdk/internal/vm/annotation/Strict;"; // #35 + Utf8 "Code"; // #36 + Utf8 "LineNumberTable"; // #37 + Utf8 "getP1X"; // #38 + Utf8 "(Lcompiler/valhalla/inlinetypes/NamedRectangleN;)I"; // #39 + Utf8 "SourceFile"; // #40 + Utf8 "PointN.java"; // #41 + Utf8 "LoadableDescriptors"; // #42 + } // Constant Pool + + 0x0020; // access + #8;// this_cpx + #20;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + { // field + 0x0800; // access + #11; // name_index + #12; // descriptor_index + [] { // Attributes + Attr(#32) { // RuntimeVisibleAnnotations + [] { // annotations + { // annotation + #33; + [] { // element_value_pairs + } // element_value_pairs + } // annotation + } + } // end RuntimeVisibleAnnotations + ; + Attr(#34) { // RuntimeInvisibleAnnotations + [] { // annotations + { // annotation + #35; + [] { // element_value_pairs + } // element_value_pairs + } // annotation + } + } // end RuntimeInvisibleAnnotations + } // Attributes + } + ; + { // field + 0x0000; // access + #17; // name_index + #18; // descriptor_index + [] { // Attributes + } // Attributes + } + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#36) { // Code + 3; // max_stack + 1; // max_locals + Bytes[]{ + 0x2ABB000159B70003; + 0xB500072A120DB500; + 0x0F2AB70013B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#37) { // LineNumberTable + [] { // line_number_table + 0 26; + 11 29; + 17 25; + 21 29; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0008; // access + #38; // name_index + #39; // descriptor_index + [] { // Attributes + Attr(#36) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB40007B40016B4; + 0x001AAC; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#37) { // LineNumberTable + [] { // line_number_table + 0 32; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#40) { // SourceFile + #41; + } // end SourceFile + ; + Attr(#42) { // LoadableDescriptors + 0x0001000C; + } // end LoadableDescriptors + } // Attributes +} // end class compiler/valhalla/inlinetypes/NamedRectangleN + +class compiler/valhalla/inlinetypes/NamedRectangleP { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + class #2; // #1 + Utf8 "compiler/valhalla/inlinetypes/RectangleP"; // #2 + Method #1 #4; // #3 + NameAndType #5 #6; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Field #8 #9; // #7 + class #10; // #8 + NameAndType #11 #12; // #9 + Utf8 "compiler/valhalla/inlinetypes/NamedRectangleP"; // #10 + Utf8 "rect"; // #11 + Utf8 "Lcompiler/valhalla/inlinetypes/RectangleP;"; // #12 + String #14; // #13 + Utf8 ""; // #14 + Field #8 #16; // #15 + NameAndType #17 #18; // #16 + Utf8 "name"; // #17 + Utf8 "Ljava/lang/String;"; // #18 + Method #20 #4; // #19 + class #21; // #20 + Utf8 "java/lang/Object"; // #21 + Field #1 #23; // #22 + NameAndType #24 #25; // #23 + Utf8 "p1"; // #24 + Utf8 "Lcompiler/valhalla/inlinetypes/PointN;"; // #25 + Field #27 #28; // #26 + class #29; // #27 + NameAndType #30 #31; // #28 + Utf8 "compiler/valhalla/inlinetypes/PointN"; // #29 + Utf8 "y"; // #30 + Utf8 "I"; // #31 + Utf8 "RuntimeVisibleAnnotations"; // #32 + Utf8 "Ljdk/internal/vm/annotation/NullRestricted;"; // #33 + Utf8 "RuntimeInvisibleAnnotations"; // #34 + Utf8 "Ljdk/internal/vm/annotation/Strict;"; // #35 + Utf8 "Code"; // #36 + Utf8 "LineNumberTable"; // #37 + Utf8 "getP1Y"; // #38 + Utf8 "(Lcompiler/valhalla/inlinetypes/NamedRectangleP;)I"; // #39 + Utf8 "SourceFile"; // #40 + Utf8 "PointN.java"; // #41 + Utf8 "LoadableDescriptors"; // #42 + } // Constant Pool + + 0x0020; // access + #8;// this_cpx + #20;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + { // field + 0x0800; // access + #11; // name_index + #12; // descriptor_index + [] { // Attributes + Attr(#32) { // RuntimeVisibleAnnotations + [] { // annotations + { // annotation + #33; + [] { // element_value_pairs + } // element_value_pairs + } // annotation + } + } // end RuntimeVisibleAnnotations + ; + Attr(#34) { // RuntimeInvisibleAnnotations + [] { // annotations + { // annotation + #35; + [] { // element_value_pairs + } // element_value_pairs + } // annotation + } + } // end RuntimeInvisibleAnnotations + } // Attributes + } + ; + { // field + 0x0000; // access + #17; // name_index + #18; // descriptor_index + [] { // Attributes + } // Attributes + } + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#36) { // Code + 3; // max_stack + 1; // max_locals + Bytes[]{ + 0x2ABB000159B70003; + 0xB500072A120DB500; + 0x0F2AB70013B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#37) { // LineNumberTable + [] { // line_number_table + 0 44; + 11 47; + 17 43; + 21 47; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0008; // access + #38; // name_index + #39; // descriptor_index + [] { // Attributes + Attr(#36) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB40007B40016B4; + 0x001AAC; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#37) { // LineNumberTable + [] { // line_number_table + 0 50; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#40) { // SourceFile + #41; + } // end SourceFile + ; + Attr(#42) { // LoadableDescriptors + 0x0001000C; + } // end LoadableDescriptors + } // Attributes +} // end class compiler/valhalla/inlinetypes/NamedRectangleP + +class compiler/valhalla/inlinetypes/PointN { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Field #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "compiler/valhalla/inlinetypes/PointN"; // #4 + Utf8 "x0"; // #5 + Utf8 "I"; // #6 + Field #2 #8; // #7 + NameAndType #9 #6; // #8 + Utf8 "y"; // #9 + Method #11 #12; // #10 + class #13; // #11 + NameAndType #14 #15; // #12 + Utf8 "java/lang/Object"; // #13 + Utf8 ""; // #14 + Utf8 "()V"; // #15 + Utf8 "(II)V"; // #16 + Utf8 "Code"; // #17 + Utf8 "LineNumberTable"; // #18 + Utf8 "SourceFile"; // #19 + Utf8 "PointN.java"; // #20 + Utf8 "RuntimeVisibleAnnotations"; // #21 + Utf8 "Ljdk/internal/vm/annotation/LooselyConsistentValue;"; // #22 + } // Constant Pool + + 0x0010; // access + #2;// this_cpx + #11;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + { // field + 0x0810; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + } // Attributes + } + ; + { // field + 0x0810; // access + #9; // name_index + #6; // descriptor_index + [] { // Attributes + } // Attributes + } + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #14; // name_index + #16; // descriptor_index + [] { // Attributes + Attr(#17) { // Code + 2; // max_stack + 3; // max_locals + Bytes[]{ + 0x2A1BB500012A1CB5; + 0x00072AB7000AB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#18) { // LineNumberTable + [] { // line_number_table + 0 13; + 5 14; + 10 12; + 14 15; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#19) { // SourceFile + #20; + } // end SourceFile + ; + Attr(#21) { // RuntimeVisibleAnnotations + [] { // annotations + { // annotation + #22; + [] { // element_value_pairs + } // element_value_pairs + } // annotation + } + } // end RuntimeVisibleAnnotations + } // Attributes +} // end class compiler/valhalla/inlinetypes/PointN + +class compiler/valhalla/inlinetypes/RectangleN { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + class #2; // #1 + Utf8 "compiler/valhalla/inlinetypes/PointN"; // #2 + Method #1 #4; // #3 + NameAndType #5 #6; // #4 + Utf8 ""; // #5 + Utf8 "(II)V"; // #6 + Field #8 #9; // #7 + class #10; // #8 + NameAndType #11 #12; // #9 + Utf8 "compiler/valhalla/inlinetypes/RectangleN"; // #10 + Utf8 "p1"; // #11 + Utf8 "Lcompiler/valhalla/inlinetypes/PointN;"; // #12 + Method #14 #15; // #13 + class #16; // #14 + NameAndType #5 #17; // #15 + Utf8 "java/lang/Object"; // #16 + Utf8 "()V"; // #17 + Utf8 "RuntimeVisibleAnnotations"; // #18 + Utf8 "Ljdk/internal/vm/annotation/NullRestricted;"; // #19 + Utf8 "RuntimeInvisibleAnnotations"; // #20 + Utf8 "Ljdk/internal/vm/annotation/Strict;"; // #21 + Utf8 "Code"; // #22 + Utf8 "LineNumberTable"; // #23 + Utf8 "SourceFile"; // #24 + Utf8 "PointN.java"; // #25 + Utf8 "Ljdk/internal/vm/annotation/LooselyConsistentValue;"; // #26 + Utf8 "LoadableDescriptors"; // #27 + } // Constant Pool + + 0x0010; // access + #8;// this_cpx + #14;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + { // field + 0x0810; // access + #11; // name_index + #12; // descriptor_index + [] { // Attributes + Attr(#18) { // RuntimeVisibleAnnotations + [] { // annotations + { // annotation + #19; + [] { // element_value_pairs + } // element_value_pairs + } // annotation + } + } // end RuntimeVisibleAnnotations + ; + Attr(#20) { // RuntimeInvisibleAnnotations + [] { // annotations + { // annotation + #21; + [] { // element_value_pairs + } // element_value_pairs + } // annotation + } + } // end RuntimeInvisibleAnnotations + } // Attributes + } + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #17; // descriptor_index + [] { // Attributes + Attr(#22) { // Code + 5; // max_stack + 1; // max_locals + Bytes[]{ + 0x2ABB000159071007; + 0xB70003B500072AB7; + 0x000DB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#23) { // LineNumberTable + [] { // line_number_table + 0 20; + 14 19; + 18 20; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#24) { // SourceFile + #25; + } // end SourceFile + ; + Attr(#18) { // RuntimeVisibleAnnotations + [] { // annotations + { // annotation + #26; + [] { // element_value_pairs + } // element_value_pairs + } // annotation + } + } // end RuntimeVisibleAnnotations + ; + Attr(#27) { // LoadableDescriptors + 0x0001000C; + } // end LoadableDescriptors + } // Attributes +} // end class compiler/valhalla/inlinetypes/RectangleN + +class compiler/valhalla/inlinetypes/RectangleP { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + class #2; // #1 + Utf8 "compiler/valhalla/inlinetypes/PointN"; // #2 + Method #1 #4; // #3 + NameAndType #5 #6; // #4 + Utf8 ""; // #5 + Utf8 "(II)V"; // #6 + Field #8 #9; // #7 + class #10; // #8 + NameAndType #11 #12; // #9 + Utf8 "compiler/valhalla/inlinetypes/RectangleP"; // #10 + Utf8 "p1"; // #11 + Utf8 "Lcompiler/valhalla/inlinetypes/PointN;"; // #12 + Method #14 #15; // #13 + class #16; // #14 + NameAndType #5 #17; // #15 + Utf8 "java/lang/Object"; // #16 + Utf8 "()V"; // #17 + Utf8 "RuntimeVisibleAnnotations"; // #18 + Utf8 "Ljdk/internal/vm/annotation/NullRestricted;"; // #19 + Utf8 "RuntimeInvisibleAnnotations"; // #20 + Utf8 "Ljdk/internal/vm/annotation/Strict;"; // #21 + Utf8 "Code"; // #22 + Utf8 "LineNumberTable"; // #23 + Utf8 "SourceFile"; // #24 + Utf8 "PointN.java"; // #25 + Utf8 "Ljdk/internal/vm/annotation/LooselyConsistentValue;"; // #26 + Utf8 "LoadableDescriptors"; // #27 + } // Constant Pool + + 0x0010; // access + #8;// this_cpx + #14;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + { // field + 0x0812; // access + #11; // name_index + #12; // descriptor_index + [] { // Attributes + Attr(#18) { // RuntimeVisibleAnnotations + [] { // annotations + { // annotation + #19; + [] { // element_value_pairs + } // element_value_pairs + } // annotation + } + } // end RuntimeVisibleAnnotations + ; + Attr(#20) { // RuntimeInvisibleAnnotations + [] { // annotations + { // annotation + #21; + [] { // element_value_pairs + } // element_value_pairs + } // annotation + } + } // end RuntimeInvisibleAnnotations + } // Attributes + } + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #17; // descriptor_index + [] { // Attributes + Attr(#22) { // Code + 5; // max_stack + 1; // max_locals + Bytes[]{ + 0x2ABB000159071007; + 0xB70003B500072AB7; + 0x000DB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#23) { // LineNumberTable + [] { // line_number_table + 0 38; + 14 37; + 18 38; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#24) { // SourceFile + #25; + } // end SourceFile + ; + Attr(#18) { // RuntimeVisibleAnnotations + [] { // annotations + { // annotation + #26; + [] { // element_value_pairs + } // element_value_pairs + } // annotation + } + } // end RuntimeVisibleAnnotations + ; + Attr(#27) { // LoadableDescriptors + 0x0001000C; + } // end LoadableDescriptors + } // Attributes +} // end class compiler/valhalla/inlinetypes/RectangleP diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/InlineTypeIRNode.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/InlineTypeIRNode.java new file mode 100644 index 00000000000..579926b7c16 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/InlineTypeIRNode.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.IRNode; +import static compiler.lib.ir_framework.IRNode.PREFIX; + +public class InlineTypeIRNode { + private static final String POSTFIX = "#I_"; + + public static final String CALL_UNSAFE = PREFIX + "CALL_UNSAFE" + POSTFIX; + static { + IRNode.staticCallOfMethodNodes(CALL_UNSAFE, InlineTypeRegexes.JDK_INTERNAL_MISC_UNSAFE); + } + + public static final String STORE_INLINE_FIELDS = PREFIX + "STORE_INLINE_FIELDS" + POSTFIX; + static { + IRNode.staticCallOfMethodNodes(STORE_INLINE_FIELDS, InlineTypeRegexes.STORE_INLINE_TYPE_FIELDS); + } + + public static final String LOAD_UNKNOWN_INLINE = PREFIX + "LOAD_UNKNOWN_INLINE" + POSTFIX; + static { + IRNode.staticCallOfMethodNodes(LOAD_UNKNOWN_INLINE, InlineTypeRegexes.LOAD_UNKNOWN_INLINE); + } + + public static final String STORE_UNKNOWN_INLINE = PREFIX + "STORE_UNKNOWN_INLINE" + POSTFIX; + static { + IRNode.staticCallOfMethodNodes(STORE_UNKNOWN_INLINE, InlineTypeRegexes.STORE_UNKNOWN_INLINE); + } + + public static final String INLINE_ARRAY_NULL_GUARD = PREFIX + "INLINE_ARRAY_NULL_GUARD" + POSTFIX; + static { + IRNode.staticCallOfMethodNodes(INLINE_ARRAY_NULL_GUARD, InlineTypeRegexes.INLINE_ARRAY_NULL_GUARD); + } + + public static final String CLONE_INTRINSIC_SLOW_PATH = PREFIX + "CLONE_INTRINSIC_SLOW_PATH" + POSTFIX; + static { + IRNode.staticCallOfMethodNodes(CLONE_INTRINSIC_SLOW_PATH, InlineTypeRegexes.JAVA_LANG_OBJECT_CLONE); + } + + public static final String CHECKCAST_ARRAYCOPY = PREFIX + "CHECKCAST_ARRAYCOPY" + POSTFIX; + static { + IRNode.callLeafNoFpOfMethodNodes(CHECKCAST_ARRAYCOPY, InlineTypeRegexes.CHECKCAST_ARRAYCOPY); + } + + public static final String JLONG_ARRAYCOPY = PREFIX + "JLONG_ARRAYCOPY" + POSTFIX; + static { + IRNode.callLeafNoFpOfMethodNodes(JLONG_ARRAYCOPY, InlineTypeRegexes.JLONG_DISJOINT_ARRAYCOPY); + } + + public static final String SUBSTITUTABILITY_TEST = PREFIX + "SUBSTITUTABILITY_TEST" + POSTFIX; + static { + IRNode.staticCallOfMethodNodes(SUBSTITUTABILITY_TEST, InlineTypeRegexes.VALUE_OBJECT_METHODS_IS_SUBSTITUTABLE); + } + + public static final String ALLOC_OF_MYVALUE_KLASS = PREFIX + "ALLOC_OF_MYVALUE_KLASS" + POSTFIX; + static { + IRNode.allocateOfNodes(ALLOC_OF_MYVALUE_KLASS, InlineTypeRegexes.MYVALUE_KLASS); + } + + public static final String ALLOC_ARRAY_OF_MYVALUE_KLASS = PREFIX + "ALLOC_ARRAY_OF_MYVALUE_KLASS" + POSTFIX; + static { + IRNode.allocateArrayOfNodes(ALLOC_ARRAY_OF_MYVALUE_KLASS, InlineTypeRegexes.MYVALUE_KLASS); + } + + public static final String LOAD_OF_ANY_KLASS = PREFIX + "LOAD_OF_ANY_KLASS" + POSTFIX; + static { + IRNode.anyLoadOfNodes(LOAD_OF_ANY_KLASS, InlineTypeRegexes.ANY_KLASS); + } + + public static final String STORE_OF_ANY_KLASS = PREFIX + "STORE_OF_ANY_KLASS" + POSTFIX; + static { + IRNode.anyStoreOfNodes(STORE_OF_ANY_KLASS, InlineTypeRegexes.ANY_KLASS); + } + + // Dummy method to call to force the static initializer blocks to be run before starting the IR framework. + public static void forceStaticInitialization() {} +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/InlineTypeRegexes.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/InlineTypeRegexes.java new file mode 100644 index 00000000000..dbce6d57c42 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/InlineTypeRegexes.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +public class InlineTypeRegexes { + public static final String MYVALUE_KLASS = "compiler/valhalla/inlinetypes/.*MyValue\\w*"; + public static final String ANY_KLASS = "compiler/valhalla/inlinetypes/[\\w/]*"; + public static final String VALUE_OBJECT_METHODS_IS_SUBSTITUTABLE = "java.lang.runtime.ValueObjectMethods::isSubstitutable"; + public static final String STORE_INLINE_TYPE_FIELDS = "store_inline_type_fields"; + public static final String JDK_INTERNAL_MISC_UNSAFE = "# Static jdk.internal.misc.Unsafe::"; + public static final String LOAD_UNKNOWN_INLINE = "C2 Runtime load_unknown_inline"; + public static final String STORE_UNKNOWN_INLINE = "C2 Runtime store_unknown_inline"; + public static final String INLINE_ARRAY_NULL_GUARD = "null_check' action='none'"; + public static final String JLONG_DISJOINT_ARRAYCOPY = "jlong_disjoint_arraycopy"; + public static final String CHECKCAST_ARRAYCOPY = "checkcast_arraycopy"; + public static final String JAVA_LANG_OBJECT_CLONE = "java.lang.Object::clone"; +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/InlineTypes.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/InlineTypes.java new file mode 100644 index 00000000000..12537165d68 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/InlineTypes.java @@ -0,0 +1,148 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import jdk.test.lib.Utils; +import compiler.lib.ir_framework.Scenario; +import compiler.lib.ir_framework.TestFramework; + +public class InlineTypes { + public static final int rI = Utils.getRandomInstance().nextInt() % 1000; + public static final long rL = Utils.getRandomInstance().nextLong() % 1000; + public static final double rD = Utils.getRandomInstance().nextDouble() % 1000; + + public static final Scenario[] DEFAULT_SCENARIOS = { + new Scenario(0, + "--enable-preview", + "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED", + "-XX:+IgnoreUnrecognizedVMOptions", + "-XX:-UseACmpProfile", + "-XX:+AlwaysIncrementalInline", + "-XX:FlatArrayElementMaxOops=5", + "-XX:+UseArrayFlattening", + "-XX:-UseArrayLoadStoreProfile", + "-XX:+UseFieldFlattening", + "-XX:+InlineTypePassFieldsAsArgs", + "-XX:+InlineTypeReturnedAsFields" + ), + new Scenario(1, + "--enable-preview", + "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED", + "-XX:+IgnoreUnrecognizedVMOptions", + "-XX:-UseACmpProfile", + "-XX:-UseCompressedOops", + "-XX:FlatArrayElementMaxOops=5", + "-XX:+UseArrayFlattening", + "-XX:-UseArrayLoadStoreProfile", + "-XX:+UseFieldFlattening", + "-XX:-InlineTypePassFieldsAsArgs", + "-XX:-InlineTypeReturnedAsFields" + ), + new Scenario(2, + "--enable-preview", + "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED", + "-XX:+IgnoreUnrecognizedVMOptions", + "-XX:-UseACmpProfile", + "-XX:-UseCompressedOops", + "-XX:FlatArrayElementMaxOops=0", + "-XX:-UseArrayFlattening", + "-XX:-UseArrayLoadStoreProfile", + "-XX:+UseFieldFlattening", + "-XX:+InlineTypePassFieldsAsArgs", + "-XX:+InlineTypeReturnedAsFields" + ), + new Scenario(3, + "--enable-preview", + "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED", + "-XX:+IgnoreUnrecognizedVMOptions", + "-DVerifyIR=false", + "-XX:+AlwaysIncrementalInline", + "-XX:FlatArrayElementMaxOops=0", + "-XX:-UseArrayFlattening", + "-XX:-UseFieldFlattening", + "-XX:+InlineTypePassFieldsAsArgs", + "-XX:+InlineTypeReturnedAsFields" + ), + new Scenario(4, + "--enable-preview", + "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED", + "-XX:+IgnoreUnrecognizedVMOptions", + "-DVerifyIR=false", + "-XX:FlatArrayElementMaxOops=-1", + "-XX:+UseArrayFlattening", + "-XX:-UseFieldFlattening", + "-XX:+InlineTypePassFieldsAsArgs", + "-XX:-InlineTypeReturnedAsFields", + "-XX:-ReduceInitialCardMarks" + ), + new Scenario(5, + "--enable-preview", + "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED", + "-XX:+IgnoreUnrecognizedVMOptions", + "-XX:-UseACmpProfile", + "-XX:+AlwaysIncrementalInline", + "-XX:FlatArrayElementMaxOops=5", + "-XX:+UseArrayFlattening", + "-XX:-UseArrayLoadStoreProfile", + "-XX:+UseFieldFlattening", + "-XX:-InlineTypePassFieldsAsArgs", + "-XX:-InlineTypeReturnedAsFields" + ), + new Scenario(6, + "--enable-preview", + "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED", + "-XX:+IgnoreUnrecognizedVMOptions", + "-XX:-UseACmpProfile", + "-XX:+AlwaysIncrementalInline", + "-XX:FlatArrayElementMaxOops=5", + "-XX:+UseArrayFlattening", + "-XX:-UseArrayLoadStoreProfile", + "-XX:+UseFieldFlattening", + "-XX:+UseNullableValueFlattening", + "-XX:+UseAtomicValueFlattening", + "-XX:+UseNonAtomicValueFlattening", + "-XX:+InlineTypePassFieldsAsArgs", + "-XX:+InlineTypeReturnedAsFields" + ), + }; + + public static TestFramework getFramework() { + StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); + return new TestFramework(walker.getCallerClass()).setDefaultWarmup(251); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/LarvalDetectionAboveOSR.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/LarvalDetectionAboveOSR.java new file mode 100644 index 00000000000..f4b8ac66269 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/LarvalDetectionAboveOSR.java @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8361352 + * @summary In OSR compilation, we must correctly determine the initialization + * state of value objects coming from above the OSR start, and not consider + * everything as potentially early larval. Value objects that are known to be + * unrestricted (late larval or fully initialized) are immutable, and can be + * scalarized. + * @library /test/jdk/java/lang/invoke/common + * @enablePreview + * @requires vm.debug + * @run main compiler.valhalla.inlinetypes.LarvalDetectionAboveOSR + */ + +package compiler.valhalla.inlinetypes; + +import test.java.lang.invoke.lib.InstructionHelper; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.lang.classfile.Label; +import java.lang.classfile.TypeKind; +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.nio.ByteBuffer; +import java.nio.file.FileSystems; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static test.java.lang.invoke.lib.InstructionHelper.classDesc; + +public class LarvalDetectionAboveOSR { + public static ArrayList runInSeparateVM(String scenario, String compile_pattern) throws Throwable { + String separator = FileSystems.getDefault().getSeparator(); + String path = System.getProperty("java.home") + separator + "bin" + separator + "java"; + + String javaFile = LarvalDetectionAboveOSR.class.getProtectionDomain().getCodeSource().getLocation().getPath(); + String classpath = javaFile.replace("LarvalDetectionAboveOSR.java", ""); + ProcessBuilder processBuilder = new ProcessBuilder( + path, "-cp", classpath, + "--enable-preview", "-XX:-TieredCompilation", + "-XX:CompileCommand=compileonly," + compile_pattern, + "-XX:CompileCommand=printcompilation,*::*", + "-XX:CompileCommand=PrintIdealPhase,*::*,BEFORE_MACRO_EXPANSION", + "-XX:+PrintEliminateAllocations", + LarvalDetectionAboveOSR.class.getCanonicalName(), scenario + ); + Process process = processBuilder.start(); + processBuilder.redirectErrorStream(true); + + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + BufferedReader error_reader = new BufferedReader(new InputStreamReader(process.getErrorStream())); + var lines = new ArrayList(); + var error_lines = new ArrayList(); + String line; + while ((line = reader.readLine()) != null) { + lines.add(line); + } + while ((line = error_reader.readLine()) != null) { + error_lines.add(line); + } + process.waitFor(); + + if (process.exitValue() != 0) { + System.out.println("stdout:"); + System.out.println(String.join("\n", lines)); + System.out.println("stderr:"); + System.out.println(String.join("\n", error_lines)); + throw new RuntimeException("Process exited with status: " + process.exitValue()); + } + + return lines; + } + + static class CompilationBlock { + public boolean is_osr; + public ArrayList lines = new ArrayList<>(); + + CompilationBlock(boolean osr) { + is_osr = osr; + } + } + + static Pattern print_compilation_regex = Pattern.compile("\\d+\\s+\\d+\\s+(%\\s+)?compiler\\.valhalla\\.inlinetypes\\.\\S*::[a-zA-Z0-9_]* (@ \\d+ )?\\(\\d+ bytes\\)"); + static Pattern allocate_elimination_regex = Pattern.compile("\\+{4} Eliminated: \\d+ Allocate"); + static Pattern allocate_regex = Pattern.compile("\\s*\\d+ {2}Allocate {2}={3}.*"); + + static ArrayList splitLines(ArrayList lines) { + var blocks = new ArrayList(); + for (String line : lines) { + Matcher m = print_compilation_regex.matcher(line); + if (m.matches()) { + blocks.add(new CompilationBlock(line.contains("%"))); + } else if (!blocks.isEmpty()) { + blocks.getLast().lines.add(line); + } + } + return blocks; + } + + static void analyzeLines(ArrayList lines) { + var blocks = splitLines(lines); + int blocks_actually_checked = 0; + for (CompilationBlock block : blocks) { + if (checkBlock(block)) { + blocks_actually_checked++; + } + } + if (blocks_actually_checked == 0) { + throw new RuntimeException("Found no OSR logging block to check."); + } + } + + static boolean checkBlock(CompilationBlock block) { + if (!block.is_osr) return false; // It's about OSR here! + + int eliminated_allocations = 0; + int i = 0; + while (i < block.lines.size()) { + String line = block.lines.get(i); + i++; + if (line.equals("AFTER: BEFORE_MACRO_EXPANSION")) { + break; + } + if (allocate_elimination_regex.matcher(line).matches()) { + eliminated_allocations++; + } + } + if (eliminated_allocations == 0) { + throw new RuntimeException("No allocation elimination found, there should be some."); + } + if (i >= block.lines.size()) { + throw new RuntimeException("Cannot find BEFORE_MACRO_EXPANSION printout"); + } + while (i < block.lines.size()) { + if (allocate_regex.matcher(block.lines.get(i)).matches()) { + throw new RuntimeException("Found allocation in line: " + block.lines.get(i)); + } + i++; + } + return true; + } + + public static short test() { + ByteBuffer bf = ByteBuffer.allocate(8); + return bf.getShort(0); + } + + public static void main(String[] args) throws Throwable { + if (args.length != 0) { + switch (args[0]) { + case "without_new" -> MyNumber.main_without_new(); + case "with_new" -> MyNumber.main_with_new(); + case "bytecode" -> Bytecode.test(); + default -> throw new RuntimeException("Wrong scenario: " + args[0]); + } + return; + } + analyzeLines(runInSeparateVM("without_new", "compiler.valhalla.inlinetypes.MyNumber::loop*")); + analyzeLines(runInSeparateVM("with_new", "compiler.valhalla.inlinetypes.MyNumber::loop*")); + analyzeLines(runInSeparateVM("bytecode", "compiler.valhalla.inlinetypes.Bytecode$Code_0::meth")); + } +} + +value class MyNumber { + public long l; + static int MANY = 1_000_000_000; + + MyNumber(long l) { + this.l = l; + } + + MyNumber add(long v) { + return new MyNumber(l + v); + } + + static long loop_without_new(MyNumber dec) { + for (int i = 0; i < MANY; ++i) { + dec = dec.add(i); + } + return dec.l; + } + + public static void main_without_new() { + for (int i = 0; i < 10; ++i) { + MyNumber dec = new MyNumber(123); + loop_without_new(dec); + } + } + + static long loop_with_new() { + MyNumber dec = new MyNumber(123); + for (int i = 0; i < MANY; ++i) { + dec = dec.add(i); + } + return dec.l; + } + + public static void main_with_new() { + for (int i = 0; i < 10; ++i) { + loop_with_new(); + } + } +} + +class Bytecode { + static MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + static void test() throws Throwable { + var myNumber = MyNumber.class; + final ClassDesc myNumberDesc = classDesc(myNumber); + + MethodHandle meth = InstructionHelper.buildMethodHandle( + LOOKUP, + "meth", + MethodType.methodType(myNumber, int.class), + CODE -> { + Label loop = CODE.newLabel(); + CODE + .new_(myNumberDesc) + .dup() + // stack: early larval (this one we init), early larval (this one we store in local 10) + .astore(10) + .iconst_0() + .i2l() + .invokespecial(myNumberDesc, "", MethodTypeDesc.ofDescriptor("(J)V")) + // local 10 should also be initialized, it is now not early larval, so scalarization is allowed + + // local(11) = 0 + .iconst_0() + .istore(11) + + .labelBinding(loop) + // local(10) = local(10).add((long)local(11)) + .aload(10) + .iload(11) + .i2l() + .invokevirtual(myNumberDesc, "add", MethodTypeDesc.ofDescriptor("(J)Lcompiler/valhalla/inlinetypes/MyNumber;")) + .astore(10) + + // local(11)++ + .iload(11) + .iconst_1() + .iadd() + .dup() + // if local(11) < param(0) goto loop + .istore(11) + .iload(0) + .if_icmplt(loop) + + .aload(10) + .return_(TypeKind.from(myNumberDesc)); + }); + var _ = (MyNumber)meth.invokeExact(MyNumber.MANY); + } +} \ No newline at end of file diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyAbstract.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyAbstract.java new file mode 100644 index 00000000000..1d11361113a --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyAbstract.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import jdk.internal.vm.annotation.LooselyConsistentValue; + +@LooselyConsistentValue +public abstract value class MyAbstract implements MyInterface { + +} + diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyInterface.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyInterface.java new file mode 100644 index 00000000000..d7db3af44f6 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyInterface.java @@ -0,0 +1,29 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +public interface MyInterface { + public long hash(); +} + diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyValue1.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyValue1.java new file mode 100644 index 00000000000..24c13278bc6 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyValue1.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.DontCompile; +import compiler.lib.ir_framework.DontInline; +import compiler.lib.ir_framework.ForceCompileClassInitializer; +import compiler.lib.ir_framework.ForceInline; + +import java.util.Arrays; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +@LooselyConsistentValue +@ForceCompileClassInitializer +public value class MyValue1 extends MyAbstract { + static int s; + static final long sf = InlineTypes.rL; + int x; + long y; + short z; + Integer o; + int[] oa; + @Strict + @NullRestricted + MyValue2 v1; + @Strict + @NullRestricted + MyValue2 v2; + @Strict + @NullRestricted + static final MyValue2 v3 = MyValue2.createWithFieldsInline(InlineTypes.rI, InlineTypes.rD); + MyValue2 v4; + @Strict + @NullRestricted + MyValue2 v5; + int c; + + static final MyValue1 DEFAULT = createDefaultInline(); + + @ForceInline + public MyValue1(int x, long y, short z, Integer o, int[] oa, MyValue2 v1, MyValue2 v2, MyValue2 v4, MyValue2 v5, int c) { + s = 0; + this.x = x; + this.y = y; + this.z = z; + this.o = o; + this.oa = oa; + this.v1 = v1; + this.v2 = v2; + this.v4 = v4; + this.v5 = v5; + this.c = c; + } + + @DontInline + static MyValue1 createDefaultDontInline() { + return createDefaultInline(); + } + + @ForceInline + static MyValue1 createDefaultInline() { + return new MyValue1(0, 0, (short)0, null, null, MyValue2.createDefaultInline(), MyValue2.createDefaultInline(), null, MyValue2.createDefaultInline(), 0); + } + + @DontInline + static MyValue1 createWithFieldsDontInline(int x, long y) { + return createWithFieldsInline(x, y); + } + + @ForceInline + static MyValue1 createWithFieldsInline(int x, long y) { + MyValue1 v = createDefaultInline(); + v = setX(v, x); + v = setY(v, y); + v = setZ(v, (short)x); + // Don't use Integer.valueOf here to avoid control flow added by Integer cache check + v = setO(v, new Integer(x)); + int[] oa = {x}; + v = setOA(v, oa); + v = setV1(v, MyValue2.createWithFieldsInline(x, y, InlineTypes.rD)); + v = setV2(v, MyValue2.createWithFieldsInline(x + 1, y + 1, InlineTypes.rD + 1)); + v = setV4(v, MyValue2.createWithFieldsInline(x + 2, y + 2, InlineTypes.rD + 2)); + v = setV5(v, MyValue2.createWithFieldsInline(x + 3, y + 3, InlineTypes.rD + 3)); + v = setC(v, (int)(x+y)); + return v; + } + + // Hash only primitive and inline type fields to avoid NullPointerException + @ForceInline + public long hashPrimitive() { + return s + sf + x + y + z + c + v1.hash() + v2.hash() + v3.hash() + v5.hash(); + } + + @ForceInline + public long hash() { + long res = hashPrimitive(); + try { + res += o; + } catch (NullPointerException npe) {} + try { + res += oa[0]; + } catch (NullPointerException npe) {} + try { + res += v4.hash(); + } catch (NullPointerException npe) {} + return res; + } + + @DontCompile + public long hashInterpreted() { + return s + sf + x + y + z + o + oa[0] + c + v1.hashInterpreted() + v2.hashInterpreted() + v3.hashInterpreted() + v4.hashInterpreted() + v5.hashInterpreted(); + } + + @ForceInline + static MyValue1 setX(MyValue1 v, int x) { + return new MyValue1(x, v.y, v.z, v.o, v.oa, v.v1, v.v2, v.v4, v.v5, v.c); + } + + @ForceInline + static MyValue1 setY(MyValue1 v, long y) { + return new MyValue1(v.x, y, v.z, v.o, v.oa, v.v1, v.v2, v.v4, v.v5, v.c); + } + + @ForceInline + static MyValue1 setZ(MyValue1 v, short z) { + return new MyValue1(v.x, v.y, z, v.o, v.oa, v.v1, v.v2, v.v4, v.v5, v.c); + } + + @ForceInline + static MyValue1 setO(MyValue1 v, Integer o) { + return new MyValue1(v.x, v.y, v.z, o, v.oa, v.v1, v.v2, v.v4, v.v5, v.c); + } + + @ForceInline + static MyValue1 setOA(MyValue1 v, int[] oa) { + return new MyValue1(v.x, v.y, v.z, v.o, oa, v.v1, v.v2, v.v4, v.v5, v.c); + } + + @ForceInline + static MyValue1 setC(MyValue1 v, int c) { + return new MyValue1(v.x, v.y, v.z, v.o, v.oa, v.v1, v.v2, v.v4, v.v5, c); + } + + @ForceInline + static MyValue1 setV1(MyValue1 v, MyValue2 v1) { + return new MyValue1(v.x, v.y, v.z, v.o, v.oa, v1, v.v2, v.v4, v.v5, v.c); + } + + @ForceInline + static MyValue1 setV2(MyValue1 v, MyValue2 v2) { + return new MyValue1(v.x, v.y, v.z, v.o, v.oa, v.v1, v2, v.v4, v.v5, v.c); + } + + @ForceInline + static MyValue1 setV4(MyValue1 v, MyValue2 v4) { + return new MyValue1(v.x, v.y, v.z, v.o, v.oa, v.v1, v.v2, v4, v.v5, v.c); + } + + @ForceInline + static MyValue1 setV5(MyValue1 v, MyValue2 v5) { + return new MyValue1(v.x, v.y, v.z, v.o, v.oa, v.v1, v.v2, v.v4, v5, v.c); + } + + @Override + public String toString() { + return "MyValue1[s=" + s + ", sf=" + sf + ", x=" + x + ", y=" + y + ", z=" + z + ", o=" + o + ", oa=" + Arrays.toString(oa) + + ", v1=" + v1 + ", v2=" + v2 + ", v4=" + v4 + ", c=" + c + "]"; + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyValue2.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyValue2.java new file mode 100644 index 00000000000..2c2a3c3888b --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyValue2.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.DontInline; +import compiler.lib.ir_framework.ForceInline; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +@LooselyConsistentValue +value class MyValue2Inline { + double d; + long l; + + @ForceInline + public MyValue2Inline(double d, long l) { + this.d = d; + this.l = l; + } + + @ForceInline + static MyValue2Inline setD(MyValue2Inline v, double d) { + return new MyValue2Inline(d, v.l); + } + + @ForceInline + static MyValue2Inline setL(MyValue2Inline v, long l) { + return new MyValue2Inline(v.d, l); + } + + @ForceInline + public static MyValue2Inline createDefault() { + return new MyValue2Inline(0, 0); + } + + @ForceInline + public static MyValue2Inline createWithFieldsInline(double d, long l) { + MyValue2Inline v = MyValue2Inline.createDefault(); + v = MyValue2Inline.setD(v, d); + v = MyValue2Inline.setL(v, l); + return v; + } + + @Override + public String toString() { + return "MyValue2Inline[d=" + d + ", l=" + l + "]"; + } +} + +@LooselyConsistentValue +public value class MyValue2 extends MyAbstract { + int x; + byte y; + @Strict + @NullRestricted + MyValue2Inline v; + + static final MyValue2 DEFAULT = createDefaultInline(); + + @ForceInline + public MyValue2(int x, byte y, MyValue2Inline v) { + this.x = x; + this.y = y; + this.v = v; + } + + @ForceInline + public static MyValue2 createDefaultInline() { + return new MyValue2(0, (byte)0, MyValue2Inline.createDefault()); + } + + @ForceInline + public static MyValue2 createWithFieldsInline(int x, long y, double d) { + MyValue2 v = createDefaultInline(); + v = setX(v, x); + v = setY(v, (byte)x); + v = setV(v, MyValue2Inline.createWithFieldsInline(d, y)); + return v; + } + + @ForceInline + public static MyValue2 createWithFieldsInline(int x, double d) { + MyValue2 v = createDefaultInline(); + v = setX(v, x); + v = setY(v, (byte)x); + v = setV(v, MyValue2Inline.createWithFieldsInline(d, InlineTypes.rL)); + return v; + } + + @DontInline + public static MyValue2 createWithFieldsDontInline(int x, double d) { + MyValue2 v = createDefaultInline(); + v = setX(v, x); + v = setY(v, (byte)x); + v = setV(v, MyValue2Inline.createWithFieldsInline(d, InlineTypes.rL)); + return v; + } + + @ForceInline + public long hash() { + return x + y + (long)v.d + v.l; + } + + @DontInline + public long hashInterpreted() { + return x + y + (long)v.d + v.l; + } + + @ForceInline + static MyValue2 setX(MyValue2 v, int x) { + return new MyValue2(x, v.y, v.v); + } + + @ForceInline + static MyValue2 setY(MyValue2 v, byte y) { + return new MyValue2(v.x, y, v.v); + } + + @ForceInline + static MyValue2 setV(MyValue2 v, MyValue2Inline vi) { + return new MyValue2(v.x, v.y, vi); + } + + @Override + public String toString() { + return "MyValue2[x=" + x + ", y=" + y + ", v=" + v + "]"; + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyValue3.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyValue3.java new file mode 100644 index 00000000000..ea450678faf --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyValue3.java @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import jdk.test.lib.Asserts; +import jdk.test.lib.Utils; +import compiler.lib.ir_framework.DontInline; +import compiler.lib.ir_framework.ForceInline; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +@LooselyConsistentValue +value class MyValue3Inline { + float f7; + double f8; + + @ForceInline + public MyValue3Inline(float f7, double f8) { + this.f7 = f7; + this.f8 = f8; + } + + @ForceInline + static MyValue3Inline setF7(MyValue3Inline v, float f7) { + return new MyValue3Inline(f7, v.f8); + } + + @ForceInline + static MyValue3Inline setF8(MyValue3Inline v, double f8) { + return new MyValue3Inline(v.f7, f8); + } + + @ForceInline + public static MyValue3Inline createDefault() { + return new MyValue3Inline(0, 0); + } + + @ForceInline + public static MyValue3Inline createWithFieldsInline(float f7, double f8) { + MyValue3Inline v = createDefault(); + v = setF7(v, f7); + v = setF8(v, f8); + return v; + } + + @Override + public String toString() { + return "MyValue3Inline[f7=" + f7 + ", f8=" + f8 + "]"; + } +} + +// Inline type definition to stress test return of an inline type in registers +// (uses all registers of calling convention on x86_64) +@LooselyConsistentValue +public value class MyValue3 extends MyAbstract { + char c; + byte bb; + short s; + int i; + long l; + Object o; + float f1; + double f2; + float f3; + double f4; + float f5; + double f6; + @Strict + @NullRestricted + MyValue3Inline v1; + + static final MyValue3 DEFAULT = new MyValue3((char)0, (byte)0, (short)0, 0, 0, null, + 0, 0, 0, 0, 0, 0, new MyValue3Inline(0, 0)); + + @ForceInline + public MyValue3(char c, byte bb, short s, int i, long l, Object o, + float f1, double f2, float f3, double f4, float f5, double f6, + MyValue3Inline v1) { + this.c = c; + this.bb = bb; + this.s = s; + this.i = i; + this.l = l; + this.o = o; + this.f1 = f1; + this.f2 = f2; + this.f3 = f3; + this.f4 = f4; + this.f5 = f5; + this.f6 = f6; + this.v1 = v1; + } + + @ForceInline + static MyValue3 setC(MyValue3 v, char c) { + return new MyValue3(c, v.bb, v.s, v.i, v.l, v.o, v.f1, v.f2, v.f3, v.f4, v.f5, v.f6, v.v1); + } + + @ForceInline + static MyValue3 setBB(MyValue3 v, byte bb) { + return new MyValue3(v.c, bb, v.s, v.i, v.l, v.o, v.f1, v.f2, v.f3, v.f4, v.f5, v.f6, v.v1); + } + + @ForceInline + static MyValue3 setS(MyValue3 v, short s) { + return new MyValue3(v.c, v.bb, s, v.i, v.l, v.o, v.f1, v.f2, v.f3, v.f4, v.f5, v.f6, v.v1); + } + + @ForceInline + static MyValue3 setI(MyValue3 v, int i) { + return new MyValue3(v.c, v.bb, v.s, i, v.l, v.o, v.f1, v.f2, v.f3, v.f4, v.f5, v.f6, v.v1); + } + + @ForceInline + static MyValue3 setL(MyValue3 v, long l) { + return new MyValue3(v.c, v.bb, v.s, v.i, l, v.o, v.f1, v.f2, v.f3, v.f4, v.f5, v.f6, v.v1); + } + + @ForceInline + static MyValue3 setO(MyValue3 v, Object o) { + return new MyValue3(v.c, v.bb, v.s, v.i, v.l, o, v.f1, v.f2, v.f3, v.f4, v.f5, v.f6, v.v1); + } + + @ForceInline + static MyValue3 setF1(MyValue3 v, float f1) { + return new MyValue3(v.c, v.bb, v.s, v.i, v.l, v.o, f1, v.f2, v.f3, v.f4, v.f5, v.f6, v.v1); + } + + @ForceInline + static MyValue3 setF2(MyValue3 v, double f2) { + return new MyValue3(v.c, v.bb, v.s, v.i, v.l, v.o, v.f1, f2, v.f3, v.f4, v.f5, v.f6, v.v1); + } + + @ForceInline + static MyValue3 setF3(MyValue3 v, float f3) { + return new MyValue3(v.c, v.bb, v.s, v.i, v.l, v.o, v.f1, v.f2, f3, v.f4, v.f5, v.f6, v.v1); + } + + @ForceInline + static MyValue3 setF4(MyValue3 v, double f4) { + return new MyValue3(v.c, v.bb, v.s, v.i, v.l, v.o, v.f1, v.f2, v.f3, f4, v.f5, v.f6, v.v1); + } + + @ForceInline + static MyValue3 setF5(MyValue3 v, float f5) { + return new MyValue3(v.c, v.bb, v.s, v.i, v.l, v.o, v.f1, v.f2, v.f3, v.f4, f5, v.f6, v.v1); + } + + @ForceInline + static MyValue3 setF6(MyValue3 v, double f6) { + return new MyValue3(v.c, v.bb, v.s, v.i, v.l, v.o, v.f1, v.f2, v.f3, v.f4, v.f5, f6, v.v1); + } + + @ForceInline + static MyValue3 setV1(MyValue3 v, MyValue3Inline v1) { + return new MyValue3(v.c, v.bb, v.s, v.i, v.l, v.o, v.f1, v.f2, v.f3, v.f4, v.f5, v.f6, v1); + } + + @ForceInline + public static MyValue3 createDefault() { + return new MyValue3((char)0, (byte)0, (short)0, 0, 0, null, 0, 0, 0, 0, 0, 0, MyValue3Inline.createDefault()); + } + + @ForceInline + public static MyValue3 create() { + java.util.Random r = Utils.getRandomInstance(); + MyValue3 v = createDefault(); + v = setC(v, (char)r.nextInt()); + v = setBB(v, (byte)r.nextInt()); + v = setS(v, (short)r.nextInt()); + v = setI(v, r.nextInt()); + v = setL(v, r.nextLong()); + v = setO(v, new Object()); + v = setF1(v, r.nextFloat()); + v = setF2(v, r.nextDouble()); + v = setF3(v, r.nextFloat()); + v = setF4(v, r.nextDouble()); + v = setF5(v, r.nextFloat()); + v = setF6(v, r.nextDouble()); + v = setV1(v, MyValue3Inline.createWithFieldsInline(r.nextFloat(), r.nextDouble())); + return v; + } + + @DontInline + public static MyValue3 createDontInline() { + return create(); + } + + @ForceInline + public static MyValue3 copy(MyValue3 other) { + MyValue3 v = createDefault(); + v = setC(v, other.c); + v = setBB(v, other.bb); + v = setS(v, other.s); + v = setI(v, other.i); + v = setL(v, other.l); + v = setO(v, other.o); + v = setF1(v, other.f1); + v = setF2(v, other.f2); + v = setF3(v, other.f3); + v = setF4(v, other.f4); + v = setF5(v, other.f5); + v = setF6(v, other.f6); + v = setV1(v, other.v1); + return v; + } + + @DontInline + public void verify(MyValue3 other) { + Asserts.assertEQ(c, other.c); + Asserts.assertEQ(bb, other.bb); + Asserts.assertEQ(s, other.s); + Asserts.assertEQ(i, other.i); + Asserts.assertEQ(l, other.l); + Asserts.assertEQ(o, other.o); + Asserts.assertEQ(f1, other.f1); + Asserts.assertEQ(f2, other.f2); + Asserts.assertEQ(f3, other.f3); + Asserts.assertEQ(f4, other.f4); + Asserts.assertEQ(f5, other.f5); + Asserts.assertEQ(f6, other.f6); + Asserts.assertEQ(v1.f7, other.v1.f7); + Asserts.assertEQ(v1.f8, other.v1.f8); + } + + @ForceInline + public long hash() { + return c + + bb + + s + + i + + l + + o.hashCode() + + Float.hashCode(f1) + + Double.hashCode(f2) + + Float.hashCode(f3) + + Double.hashCode(f4) + + Float.hashCode(f5) + + Double.hashCode(f6) + + Float.hashCode(v1.f7) + + Double.hashCode(v1.f8); + } + + @Override + public String toString() { + return "MyValue3[c=" + c + ", bb=" + bb + ", s=" + s + ", i=" + i + ", l=" + l + ", o=" + o + + ", f1=" + f1 + ", f2=" + f2 + ", f3=" + f3 + ", f4=" + f4 + ", f5=" + f5 + ", f6=" + f6 + ", v1=" + v1 + "]"; + } +} + diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyValue4.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyValue4.java new file mode 100644 index 00000000000..c80e630d00b --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyValue4.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.ForceInline; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +// Inline type definition with too many fields to return in registers +@LooselyConsistentValue +value class MyValue4 extends MyAbstract { + @Strict + @NullRestricted + MyValue3 v1; + @Strict + @NullRestricted + MyValue3 v2; + + @ForceInline + public MyValue4(MyValue3 v1, MyValue3 v2) { + this.v1 = v1; + this.v2 = v2; + } + + @ForceInline + static MyValue4 setV1(MyValue4 v, MyValue3 v1) { + return new MyValue4(v1, v.v2); + } + + @ForceInline + static MyValue4 setV2(MyValue4 v, MyValue3 v2) { + return new MyValue4(v.v1, v2); + } + + @ForceInline + public static MyValue4 createDefault() { + return new MyValue4(MyValue3.createDefault(), MyValue3.createDefault()); + } + + @ForceInline + public static MyValue4 create() { + MyValue4 v = createDefault(); + MyValue3 v1 = MyValue3.create(); + v = setV1(v, v1); + MyValue3 v2 = MyValue3.create(); + v = setV2(v, v2); + return v; + } + + public void verify(MyValue4 other) { + v1.verify(other.v1); + v2.verify(other.v2); + } + + @ForceInline + public long hash() { + return v1.hash() + v2.hash(); + } + + @Override + public String toString() { + return "MyValue4[v1=" + v1 + ", v2=" + v2 + "]"; + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyValueClass1.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyValueClass1.java new file mode 100644 index 00000000000..ae11021e67a --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyValueClass1.java @@ -0,0 +1,188 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.DontCompile; +import compiler.lib.ir_framework.DontInline; +import compiler.lib.ir_framework.ForceCompileClassInitializer; +import compiler.lib.ir_framework.ForceInline; + +import java.util.Arrays; + +@ForceCompileClassInitializer +public value class MyValueClass1 extends MyAbstract { + static int s; + static long sf = InlineTypes.rL; + int x; + long y; + short z; + Integer o; + int[] oa; + MyValueClass2 v1; + MyValueClass2 v2; + static MyValueClass2 v3 = MyValueClass2.createWithFieldsInline(InlineTypes.rI, InlineTypes.rD); + MyValueClass2 v4; + int c; + + @ForceInline + public MyValueClass1(int x, long y, short z, Integer o, int[] oa, MyValueClass2 v1, MyValueClass2 v2, MyValueClass2 v4, int c) { + s = 0; + this.x = x; + this.y = y; + this.z = z; + this.o = o; + this.oa = oa; + this.v1 = v1; + this.v2 = v2; + this.v4 = v4; + this.c = c; + } + + @DontInline + static MyValueClass1 createDefaultDontInline() { + return createDefaultInline(); + } + + @ForceInline + static MyValueClass1 createDefaultInline() { + return new MyValueClass1(0, 0, (short)0, null, null, null, null, null, 0); + } + + @DontInline + static MyValueClass1 createWithFieldsDontInline(int x, long y) { + return createWithFieldsInline(x, y); + } + + @ForceInline + static MyValueClass1 createWithFieldsInline(int x, long y) { + MyValueClass1 v = createDefaultInline(); + v = setX(v, x); + v = setY(v, y); + v = setZ(v, (short)x); + // Don't use Integer.valueOf here to avoid control flow added by Integer cache check + v = setO(v, new Integer(x)); + int[] oa = {x}; + v = setOA(v, oa); + v = setV1(v, MyValueClass2.createWithFieldsInline(x, y, InlineTypes.rD)); + v = setV2(v, MyValueClass2.createWithFieldsInline(x + 1, y + 1, InlineTypes.rD + 1)); + v = setV4(v, MyValueClass2.createWithFieldsInline(x + 2, y + 2, InlineTypes.rD + 2)); + v = setC(v, (int)(x+y)); + return v; + } + + // Hash only primitive and inline type fields to avoid NullPointerException + @ForceInline + public long hashPrimitive() { + return s + sf + x + y + z + c + v1.hash() + v2.hash() + v3.hash(); + } + + @ForceInline + public long hash() { + long res = hashPrimitive(); + try { + res += o; + } catch (NullPointerException npe) {} + try { + res += oa[0]; + } catch (NullPointerException npe) {} + try { + res += v4.hash(); + } catch (NullPointerException npe) {} + return res; + } + + @DontCompile + public long hashInterpreted() { + return s + sf + x + y + z + o + oa[0] + c + v1.hashInterpreted() + v2.hashInterpreted() + v3.hashInterpreted() + v4.hashInterpreted(); + } + + @ForceInline + public void print() { + System.out.print("s=" + s + ", sf=" + sf + ", x=" + x + ", y=" + y + ", z=" + z + ", o=" + (o != null ? (Integer)o : "NULL") + ", oa=" + (oa != null ? oa[0] : "NULL") + ", v1["); + v1.print(); + System.out.print("], v2["); + v2.print(); + System.out.print("], v3["); + v3.print(); + System.out.print("], v4["); + v4.print(); + System.out.print("], c=" + c); + } + + @ForceInline + static MyValueClass1 setX(MyValueClass1 v, int x) { + return new MyValueClass1(x, v.y, v.z, v.o, v.oa, v.v1, v.v2, v.v4, v.c); + } + + @ForceInline + static MyValueClass1 setY(MyValueClass1 v, long y) { + return new MyValueClass1(v.x, y, v.z, v.o, v.oa, v.v1, v.v2, v.v4, v.c); + } + + @ForceInline + static MyValueClass1 setZ(MyValueClass1 v, short z) { + return new MyValueClass1(v.x, v.y, z, v.o, v.oa, v.v1, v.v2, v.v4, v.c); + } + + @ForceInline + static MyValueClass1 setO(MyValueClass1 v, Integer o) { + return new MyValueClass1(v.x, v.y, v.z, o, v.oa, v.v1, v.v2, v.v4, v.c); + } + + @ForceInline + static MyValueClass1 setOA(MyValueClass1 v, int[] oa) { + return new MyValueClass1(v.x, v.y, v.z, v.o, oa, v.v1, v.v2, v.v4, v.c); + } + + @ForceInline + static MyValueClass1 setC(MyValueClass1 v, int c) { + return new MyValueClass1(v.x, v.y, v.z, v.o, v.oa, v.v1, v.v2, v.v4, c); + } + + @ForceInline + static MyValueClass1 setV1(MyValueClass1 v, MyValueClass2 v1) { + return new MyValueClass1(v.x, v.y, v.z, v.o, v.oa, v1, v.v2, v.v4, v.c); + } + + @ForceInline + static MyValueClass1 setV2(MyValueClass1 v, MyValueClass2 v2) { + return new MyValueClass1(v.x, v.y, v.z, v.o, v.oa, v.v1, v2, v.v4, v.c); + } + + @ForceInline + static MyValueClass1 setV4(MyValueClass1 v, MyValueClass2 v4) { + return new MyValueClass1(v.x, v.y, v.z, v.o, v.oa, v.v1, v.v2, v4, v.c); + } + + @DontInline + void dontInline(MyValueClass1 arg) { + + } + + @Override + public String toString() { + return "MyValueClass1[s=" + s + ", sf=" + sf + ", x=" + x + ", y=" + y + ", z=" + z + ", o=" + o + ", oa=" + Arrays.toString(oa) + + ", v1=" + v1 + ", v2=" + v2 + ", v4=" + v4 + ", c=" + c + "]"; + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyValueClass2.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyValueClass2.java new file mode 100644 index 00000000000..ce03d21a9aa --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyValueClass2.java @@ -0,0 +1,146 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.DontInline; +import compiler.lib.ir_framework.ForceInline; + +value class MyValueClass2Inline { + double d; + long l; + + @ForceInline + public MyValueClass2Inline(double d, long l) { + this.d = d; + this.l = l; + } + + @ForceInline + static MyValueClass2Inline setD(MyValueClass2Inline v, double d) { + return new MyValueClass2Inline(d, v.l); + } + + @ForceInline + static MyValueClass2Inline setL(MyValueClass2Inline v, long l) { + return new MyValueClass2Inline(v.d, l); + } + + @ForceInline + public static MyValueClass2Inline createDefault() { + return new MyValueClass2Inline(0, 0); + } + + @ForceInline + public static MyValueClass2Inline createWithFieldsInline(double d, long l) { + MyValueClass2Inline v = MyValueClass2Inline.createDefault(); + v = MyValueClass2Inline.setD(v, d); + v = MyValueClass2Inline.setL(v, l); + return v; + } + + @Override + public String toString() { + return "MyValueClass2Inline[d=" + d + ", l=" + l + "]"; + } +} + +public value class MyValueClass2 extends MyAbstract { + int x; + byte y; + MyValueClass2Inline v; + + @ForceInline + public MyValueClass2(int x, byte y, MyValueClass2Inline v) { + this.x = x; + this.y = y; + this.v = v; + } + + @ForceInline + public static MyValueClass2 createDefaultInline() { + return new MyValueClass2(0, (byte)0, null); + } + + @ForceInline + public static MyValueClass2 createWithFieldsInline(int x, long y, double d) { + MyValueClass2 v = createDefaultInline(); + v = setX(v, x); + v = setY(v, (byte)x); + v = setV(v, MyValueClass2Inline.createWithFieldsInline(d, y)); + return v; + } + + @ForceInline + public static MyValueClass2 createWithFieldsInline(int x, double d) { + MyValueClass2 v = createDefaultInline(); + v = setX(v, x); + v = setY(v, (byte)x); + v = setV(v, MyValueClass2Inline.createWithFieldsInline(d, InlineTypes.rL)); + return v; + } + + @DontInline + public static MyValueClass2 createWithFieldsDontInline(int x, double d) { + MyValueClass2 v = createDefaultInline(); + v = setX(v, x); + v = setY(v, (byte)x); + v = setV(v, MyValueClass2Inline.createWithFieldsInline(d, InlineTypes.rL)); + return v; + } + + @ForceInline + public long hash() { + return x + y + (long)v.d + v.l; + } + + @DontInline + public long hashInterpreted() { + return x + y + (long)v.d + v.l; + } + + @ForceInline + public void print() { + System.out.print("x=" + x + ", y=" + y + ", d=" + v.d + ", l=" + v.l); + } + + @ForceInline + static MyValueClass2 setX(MyValueClass2 v, int x) { + return new MyValueClass2(x, v.y, v.v); + } + + @ForceInline + static MyValueClass2 setY(MyValueClass2 v, byte y) { + return new MyValueClass2(v.x, y, v.v); + } + + @ForceInline + static MyValueClass2 setV(MyValueClass2 v, MyValueClass2Inline vi) { + return new MyValueClass2(v.x, v.y, vi); + } + + @Override + public String toString() { + return "MyValueClass2[x=" + x + ", y=" + y + ", v=" + v + "]"; + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyValueEmpty.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyValueEmpty.java new file mode 100644 index 00000000000..e1634160847 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/MyValueEmpty.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import jdk.internal.vm.annotation.LooselyConsistentValue; + +@LooselyConsistentValue +public value class MyValueEmpty extends MyAbstract { + public long hash() { return 0; } + + public MyValueEmpty copy(MyValueEmpty other) { return other; } + + @Override + public String toString() { + return "MyValueEmpty[]"; + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/NonValueClass.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/NonValueClass.java new file mode 100644 index 00000000000..f1536699e79 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/NonValueClass.java @@ -0,0 +1,35 @@ +/* + * 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. + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.ForceInline; + +public class NonValueClass { + public final int x; + + @ForceInline + public NonValueClass(int x) { + this.x = x; + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/PutFlatValueWithoutUseArrayFlattening.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/PutFlatValueWithoutUseArrayFlattening.java new file mode 100644 index 00000000000..4f22f11bf40 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/PutFlatValueWithoutUseArrayFlattening.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8365978 + * @summary Unsafe::compareAndSetFlatValue crashes with -XX:-UseArrayFlattening + * @enablePreview + * @library /test/lib + * @modules java.base/jdk.internal.misc + * @run main/othervm -XX:CompileCommand=compileonly,compiler.valhalla.inlinetypes.PutFlatValueWithoutUseArrayFlattening::test + * -XX:-TieredCompilation -Xcomp + * -XX:-UseArrayFlattening -XX:+UseFieldFlattening -XX:+IgnoreUnrecognizedVMOptions -XX:+PreloadClasses + * compiler.valhalla.inlinetypes.PutFlatValueWithoutUseArrayFlattening + * @run main/othervm -XX:+UseFieldFlattening -XX:+IgnoreUnrecognizedVMOptions -XX:+PreloadClasses + * compiler.valhalla.inlinetypes.PutFlatValueWithoutUseArrayFlattening + */ + +package compiler.valhalla.inlinetypes; + +import java.lang.reflect.Field; +import jdk.internal.misc.Unsafe; +import jdk.test.lib.Asserts; + +public class PutFlatValueWithoutUseArrayFlattening { + static public value class SmallValue { + byte a; + byte b; + SmallValue(int a, int b) { + this.a = (byte)a; + this.b = (byte)b; + } + } + + SmallValue f; + private static final long OFFSET; + private static final boolean IS_FLATTENED; + private static final int LAYOUT; + static private final Unsafe U = Unsafe.getUnsafe(); + static { + try { + Field f = PutFlatValueWithoutUseArrayFlattening.class.getDeclaredField("f"); + OFFSET = U.objectFieldOffset(f); + IS_FLATTENED = U.isFlatField(f); + Asserts.assertTrue(IS_FLATTENED, "Field f should be flat, the test makes no sense otherwise. And why isn't it?!"); + LAYOUT = U.fieldLayout(f); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public void test(boolean flag) { + var newVal = new SmallValue(1, 1); + var oldVal = new SmallValue(0, 0); + f = oldVal; + if (flag) { + U.compareAndSetFlatValue(this, OFFSET, LAYOUT, SmallValue.class, oldVal, newVal); + } + } + + static public void main(String args[]) { + new SmallValue(0, 0); + new PutFlatValueWithoutUseArrayFlattening().test(false); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/RepairStackWithBigFrame.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/RepairStackWithBigFrame.java new file mode 100644 index 00000000000..831b619b82b --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/RepairStackWithBigFrame.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import static compiler.valhalla.inlinetypes.InlineTypes.*; + +/* + * @test + * @bug 8367156 + * @summary On Aarch64, when the frame is very big and we need to repair it after + * scalarization of the arguments, we cannot use ldp to get the stack + * increment and rfp at the same time, since it only has a 7 bit offset. + * We use two ldr with 9-bit offsets instead. + * @library /test/lib / + * @requires (os.simpleArch == "x64" | os.simpleArch == "aarch64") + * @enablePreview + * @modules java.base/jdk.internal.vm.annotation + * @run main/othervm + * -Xcomp + * -XX:CompileCommand=compileonly,compiler.valhalla.inlinetypes.RepairStackWithBigFrame::test + * compiler.valhalla.inlinetypes.RepairStackWithBigFrame + * @run main compiler.valhalla.inlinetypes.RepairStackWithBigFrame + */ + +public class RepairStackWithBigFrame { + static final MyValue1 testValue1 = MyValue1.createWithFieldsInline(rI, rL); + + public static void main(String[] args) { + new RepairStackWithBigFrame().test(testValue1); + } + + long test(MyValue1 arg) { + MyAbstract vt1 = MyValue1.createWithFieldsInline(rI, rL); + MyAbstract vt2 = MyValue1.createWithFieldsDontInline(rI, rL); + MyAbstract vt3 = MyValue1.createWithFieldsInline(rI, rL); + MyAbstract vt4 = arg; + return ((MyValue1)vt1).hash() + ((MyValue1)vt2).hash() + + ((MyValue1)vt3).hash() + ((MyValue1)vt4).hash(); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/SimpleInlineType.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/SimpleInlineType.java new file mode 100644 index 00000000000..5eb6d4b3e20 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/SimpleInlineType.java @@ -0,0 +1,40 @@ +/* + * 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 + * 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. + */ + +value class SimpleInlineType { + int x; + + private SimpleInlineType() { + x = 0; + } + + static SimpleInlineType create() { + return new SimpleInlineType(); + } + + @Override + public String toString() { + return "SimpleInlineType[x=" + x + "]"; + } +} + diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestAcmpWithUnstableIf.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestAcmpWithUnstableIf.java new file mode 100644 index 00000000000..a2640bb9161 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestAcmpWithUnstableIf.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @key randomness + * @summary Test that deoptimization at unstable ifs in acmp works as expected. + * @library /test/lib + * @enablePreview + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:-StressUnstableIfTraps TestAcmpWithUnstableIf + * @run main/othervm -XX:CompileCommand=compileonly,TestAcmpWithUnstableIf::test* -Xbatch + * -XX:+UnlockDiagnosticVMOptions -XX:-StressUnstableIfTraps TestAcmpWithUnstableIf + */ + +// TODO 8367244: Remove -XX:-StressUnstableIfTraps again. + +import jdk.test.lib.Asserts; +import jdk.test.lib.Utils; + +public class TestAcmpWithUnstableIf { + + public static final int EQUAL = Utils.getRandomInstance().nextInt(); + public static final int NOT_EQUAL = Utils.getRandomInstance().nextInt(); + + static value class MyValue { + int x; + + public MyValue(int x) { + this.x = x; + } + } + + public static int test1(MyValue val1, MyValue val2, int resEqual, int resNotEqual) { + if (val1 == val2) { + return resEqual; + } + return resNotEqual; + } + + public static int test2(MyValue val1, MyValue val2, int resEqual, int resNotEqual) { + if (val1 == val2) { + return resEqual; + } + return resNotEqual; + } + + public static int test3(MyValue val1, MyValue val2, int resEqual, int resNotEqual) { + if (val1 == val2) { + return resEqual; + } + return resNotEqual; + } + + public static int test4(MyValue val1, MyValue val2, int resEqual, int resNotEqual) { + if (val1 == val2) { + return resEqual; + } + return resNotEqual; + } + + public static int test5(MyValue val1, MyValue val2, int resEqual, int resNotEqual) { + if (val1 != val2) { + return resNotEqual; + } + return resEqual; + } + + public static int test6(MyValue val1, MyValue val2, int resEqual, int resNotEqual) { + if (val1 != val2) { + return resNotEqual; + } + return resEqual; + } + + public static int test7(MyValue val1, MyValue val2, int resEqual, int resNotEqual) { + if (val1 != val2) { + return resNotEqual; + } + return resEqual; + } + + public static int test8(MyValue val1, MyValue val2, int resEqual, int resNotEqual) { + if (val1 != val2) { + return resNotEqual; + } + return resEqual; + } + + public static void main(String[] args) { + MyValue val = new MyValue(EQUAL); + MyValue val_copy = new MyValue(EQUAL); + MyValue val_diff = new MyValue(EQUAL + 1); + + // Warmup + for (int i = 0; i < 50_000; ++i) { + // Equal arguments, same oop + Asserts.assertEquals(test1(val, val, EQUAL, NOT_EQUAL), EQUAL); + Asserts.assertEquals(test5(val, val, EQUAL, NOT_EQUAL), EQUAL); + + // Equal arguments, different oop + Asserts.assertEquals(test2(val, val_copy, EQUAL, NOT_EQUAL), EQUAL); + Asserts.assertEquals(test6(val, val_copy, EQUAL, NOT_EQUAL), EQUAL); + + // Different arguments + Asserts.assertEquals(test3(val, val_diff, EQUAL, NOT_EQUAL), NOT_EQUAL); + Asserts.assertEquals(test4(val, val_diff, EQUAL, NOT_EQUAL), NOT_EQUAL); + + Asserts.assertEquals(test7(val, val_diff, EQUAL, NOT_EQUAL), NOT_EQUAL); + Asserts.assertEquals(test8(val, val_diff, EQUAL, NOT_EQUAL), NOT_EQUAL); + } + + // Now trigger deoptimization + + // Different arguments + Asserts.assertEquals(test1(val, val_diff, EQUAL, NOT_EQUAL), NOT_EQUAL); + Asserts.assertEquals(test2(val, val_diff, EQUAL, NOT_EQUAL), NOT_EQUAL); + + Asserts.assertEquals(test5(val, val_diff, EQUAL, NOT_EQUAL), NOT_EQUAL); + Asserts.assertEquals(test6(val, val_diff, EQUAL, NOT_EQUAL), NOT_EQUAL); + + // Equal arguments, same oop + Asserts.assertEquals(test3(val, val, EQUAL, NOT_EQUAL), EQUAL); + Asserts.assertEquals(test7(val, val, EQUAL, NOT_EQUAL), EQUAL); + + // Equal arguments, different oop + Asserts.assertEquals(test4(val, val_copy, EQUAL, NOT_EQUAL), EQUAL); + Asserts.assertEquals(test8(val, val_copy, EQUAL, NOT_EQUAL), EQUAL); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestAllocationMergeAndFolding.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestAllocationMergeAndFolding.java new file mode 100644 index 00000000000..a9c4ba454e5 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestAllocationMergeAndFolding.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8315003 + * @summary Test that removing allocation merges of non-value and value object at EA is working properly. + * @library /test/lib / + * @enablePreview + * @run main compiler.valhalla.inlinetypes.TestAllocationMergeAndFolding + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.*; +import jdk.test.lib.Utils; + +import java.util.Random; + +public class TestAllocationMergeAndFolding { + private static final Random RANDOM = Utils.getRandomInstance(); + + public static void main(String[] args) { + InlineTypes.getFramework() + .addScenarios(InlineTypes.DEFAULT_SCENARIOS) + .addScenarios(new Scenario(7, "--enable-preview", "-XX:-UseCompressedOops")) + .addScenarios(new Scenario(8, "--enable-preview", "-XX:+UseCompressedOops")) + .start(); + } + + @Test + @IR(failOn = IRNode.ALLOC) + static int test(boolean flag) { + Object o; + if (flag) { + o = new V(34); + } else { + o = new Object(); + } + dontInline(); // Not inlined and thus we have a safepoint where keep phi(o) = [V, Object]. + + // 'o' escapes as store to 'f'. However, 'f' does not escape and can be removed. As a result, we can also remove + // the allocations in both branches with EA after JDK-8287061. Since V has an inline type field v2, we put it + // on a list to scalarize it as well. The improved allocation merge was disabled in Valhalla but is now enabled + // and fixed with JDK-8315003. + Foo f = new Foo(o); + return f.i; + } + + @DontInline + static void dontInline() { + } + + @Run(test = "test") + static void run() { + test(RANDOM.nextBoolean()); + } + + static class Foo { + Object o; + int i; + + Foo(Object o) { + this.o = o; + } + } + + static value class V { + int i; + V2 v2; + + V(int i) { + this.i = i; + this.v2 = new V2(); + } + } + + static value class V2 { + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrayAccessDeopt.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrayAccessDeopt.java new file mode 100644 index 00000000000..7ea349bc5dd --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrayAccessDeopt.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @summary Verify that certain array accesses do not trigger deoptimization. + * @requires vm.debug == true + * @library /test/lib + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main TestArrayAccessDeopt + */ + +import java.io.File; +import java.util.Objects; + +import jdk.test.lib.Asserts; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; + +@LooselyConsistentValue +value class MyValue1 { + public int x = 0; +} + +public class TestArrayAccessDeopt { + + public static void test1(Object[] va, Object vt) { + va[0] = vt; + } + + public static void test2(Object[] va, MyValue1 vt) { + va[0] = vt; + } + + public static void test3(MyValue1[] va, Object vt) { + va[0] = (MyValue1)vt; + } + + public static void test4(MyValue1[] va, MyValue1 vt) { + va[0] = vt; + } + + public static void test5(Object[] va, MyValue1 vt) { + va[0] = vt; + } + + public static void test6(MyValue1[] va, Object vt) { + va[0] = (MyValue1)Objects.requireNonNull(vt); + } + + public static void test7(MyValue1[] va, MyValue1 vt) { + va[0] = vt; + } + + public static void test8(MyValue1[] va, MyValue1 vt) { + va[0] = vt; + } + + public static void test9(MyValue1[] va, MyValue1 vt) { + va[0] = Objects.requireNonNull(vt); + } + + public static void test10(Object[] va) { + va[0] = null; + } + + public static void test11(MyValue1[] va) { + va[0] = null; + } + + static public void main(String[] args) throws Exception { + if (args.length == 0) { + // Run test in new VM instance + String[] arg = {"--enable-preview", "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED", + "-XX:CompileCommand=quiet", "-XX:CompileCommand=compileonly,TestArrayAccessDeopt::test*", "-XX:-UseArrayLoadStoreProfile", + "-XX:+TraceDeoptimization", "-Xbatch", "-XX:-MonomorphicArrayCheck", "-Xmixed", "-XX:+ProfileInterpreter", "TestArrayAccessDeopt", "run"}; + OutputAnalyzer oa = ProcessTools.executeTestJava(arg); + oa.shouldHaveExitValue(0); + String output = oa.getOutput(); + oa.shouldNotContain("UNCOMMON TRAP"); + } else { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, new MyValue1()); + MyValue1[] vaB = new MyValue1[1]; + MyValue1 vt = new MyValue1(); + for (int i = 0; i < 10_000; ++i) { + test1(va, vt); + test1(vaB, vt); + test1(vaB, null); + test2(va, vt); + test2(vaB, vt); + test2(vaB, null); + test3(va, vt); + test3(vaB, vt); + test3(vaB, null); + test4(va, vt); + test4(vaB, vt); + test4(vaB, null); + test5(va, vt); + test5(vaB, vt); + test6(va, vt); + try { + test6(va, null); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException npe) { + // Expected + } + test7(va, vt); + test8(va, vt); + test8(vaB, vt); + test9(va, vt); + try { + test9(va, null); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException npe) { + // Expected + } + test10(vaB); + test11(vaB); + } + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrayCopyWithOops.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrayCopyWithOops.java new file mode 100644 index 00000000000..28d3446bd68 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrayCopyWithOops.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8252506 + * @summary Verify that arraycopy intrinsics properly handle flat value class arrays with oop fields. + * @library /test/lib + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm -XX:CompileCommand=dontinline,compiler.valhalla.inlinetypes.TestArrayCopyWithOops::test* + * -XX:CompileCommand=dontinline,compiler.valhalla.inlinetypes.TestArrayCopyWithOops::create* + * -Xbatch + * compiler.valhalla.inlinetypes.TestArrayCopyWithOops + * @run main/othervm -XX:CompileCommand=dontinline,compiler.valhalla.inlinetypes.TestArrayCopyWithOops::test* + * -XX:CompileCommand=dontinline,compiler.valhalla.inlinetypes.TestArrayCopyWithOops::create* + * -Xbatch -XX:-UseArrayFlattening + * compiler.valhalla.inlinetypes.TestArrayCopyWithOops + * @run main/othervm compiler.valhalla.inlinetypes.TestArrayCopyWithOops + */ + +package compiler.valhalla.inlinetypes; + +import java.util.Arrays; + +import jdk.test.lib.Asserts; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; + +public class TestArrayCopyWithOops { + static final int LEN = 200; + + static class MyObject { + long val = Integer.MAX_VALUE; + } + + @LooselyConsistentValue + static value class ManyOops { + MyObject o1 = new MyObject(); + MyObject o2 = new MyObject(); + MyObject o3 = new MyObject(); + MyObject o4 = new MyObject(); + + long hash() { + return o1.val + o2.val + o3.val + o4.val; + } + } + + static ManyOops[] createValueClassArray() { + return (ManyOops[])ValueClass.newNullRestrictedNonAtomicArray(ManyOops.class, LEN, new ManyOops()); + } + + static Object[] createObjectArray() { + return createValueClassArray(); + } + + static Object createObject() { + return createValueClassArray(); + } + + // System.arraycopy tests + + static void test1(ManyOops[] dst) { + System.arraycopy(createValueClassArray(), 0, dst, 0, LEN); + } + + static void test2(Object[] dst) { + System.arraycopy(createObjectArray(), 0, dst, 0, LEN); + } + + static void test3(ManyOops[] dst) { + System.arraycopy(createObjectArray(), 0, dst, 0, LEN); + } + + static void test4(Object[] dst) { + System.arraycopy(createValueClassArray(), 0, dst, 0, LEN); + } + + // System.arraycopy tests (tightly coupled with allocation of dst array) + + static Object[] test5() { + ManyOops[] dst = (ManyOops[])ValueClass.newNullRestrictedNonAtomicArray(ManyOops.class, LEN, new ManyOops()); + System.arraycopy(createValueClassArray(), 0, dst, 0, LEN); + return dst; + } + + static Object[] test6() { + Object[] dst = new Object[LEN]; + System.arraycopy(createObjectArray(), 0, dst, 0, LEN); + return dst; + } + + static Object[] test7() { + ManyOops[] dst = (ManyOops[])ValueClass.newNullRestrictedNonAtomicArray(ManyOops.class, LEN, new ManyOops()); + System.arraycopy(createObjectArray(), 0, dst, 0, LEN); + return dst; + } + + static Object[] test8() { + Object[] dst = new Object[LEN]; + System.arraycopy(createValueClassArray(), 0, dst, 0, LEN); + return dst; + } + + // Arrays.copyOf tests + + static Object[] test9() { + return Arrays.copyOf(createValueClassArray(), LEN, ManyOops[].class); + } + + static Object[] test10() { + return Arrays.copyOf(createObjectArray(), LEN, Object[].class); + } + + static Object[] test11() { + ManyOops[] src = createValueClassArray(); + return Arrays.copyOf(src, LEN, src.getClass()); + } + + static Object[] test12() { + Object[] src = createObjectArray(); + return Arrays.copyOf(createObjectArray(), LEN, src.getClass()); + } + + // System.arraycopy test using generic_copy stub + + static void test13(Object dst) { + System.arraycopy(createObject(), 0, dst, 0, LEN); + } + + static void produceGarbage() { + for (int i = 0; i < 100; ++i) { + Object[] arrays = new Object[1024]; + for (int j = 0; j < arrays.length; j++) { + arrays[j] = new int[1024]; + } + } + System.gc(); + } + + public static void main(String[] args) { + ManyOops[] dst1 = createValueClassArray(); + ManyOops[] dst2 = createValueClassArray(); + ManyOops[] dst3 = createValueClassArray(); + ManyOops[] dst4 = createValueClassArray(); + ManyOops[] dst13 = createValueClassArray(); + + // Warmup runs to trigger compilation + for (int i = 0; i < 50_000; ++i) { + test1(dst1); + test2(dst2); + test3(dst3); + test4(dst4); + test5(); + test6(); + test7(); + test8(); + test9(); + test10(); + test11(); + test12(); + test13(dst13); + } + + // Trigger GC to make sure dst arrays are moved to old gen + produceGarbage(); + + // Move data from flat src to flat dest + test1(dst1); + test2(dst2); + test3(dst3); + test4(dst4); + Object[] dst5 = test5(); + Object[] dst6 = test6(); + Object[] dst7 = test7(); + Object[] dst8 = test8(); + Object[] dst9 = test9(); + Object[] dst10 = test10(); + Object[] dst11 = test11(); + Object[] dst12 = test12(); + test13(dst13); + + // Trigger GC again to make sure that the now dead src arrays are collected. + // MyObjects should be kept alive via oop references from the dst array. + produceGarbage(); + + // Verify content + long expected = 4L*Integer.MAX_VALUE; + for (int i = 0; i < LEN; ++i) { + Asserts.assertEquals(dst1[i].hash(), expected); + Asserts.assertEquals(dst2[i].hash(), expected); + Asserts.assertEquals(dst3[i].hash(), expected); + Asserts.assertEquals(dst4[i].hash(), expected); + Asserts.assertEquals(((ManyOops)dst5[i]).hash(), expected); + Asserts.assertEquals(((ManyOops)dst7[i]).hash(), expected); + Asserts.assertEquals(((ManyOops)dst8[i]).hash(), expected); + Asserts.assertEquals(((ManyOops)dst8[i]).hash(), expected); + Asserts.assertEquals(((ManyOops)dst9[i]).hash(), expected); + Asserts.assertEquals(((ManyOops)dst10[i]).hash(), expected); + Asserts.assertEquals(((ManyOops)dst11[i]).hash(), expected); + Asserts.assertEquals(((ManyOops)dst12[i]).hash(), expected); + Asserts.assertEquals(dst13[i].hash(), expected); + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrayMetadata.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrayMetadata.java new file mode 100644 index 00000000000..619bd8145cb --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrayMetadata.java @@ -0,0 +1,465 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Stress test the VM internal metadata for arrays. + * @library /test/lib / + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm/timeout=300 compiler.valhalla.inlinetypes.TestArrayMetadata + * @run main/othervm/timeout=300 -XX:+IgnoreUnrecognizedVMOptions + * -XX:-MonomorphicArrayCheck -XX:-OmitStackTraceInFastThrow + * compiler.valhalla.inlinetypes.TestArrayMetadata + * @run main/othervm/timeout=300 -Xcomp + * compiler.valhalla.inlinetypes.TestArrayMetadata + * @run main/othervm/timeout=300 -XX:MultiArrayExpandLimit=0 + compiler.valhalla.inlinetypes.TestArrayMetadata + * @run main/othervm/timeout=300 -Xbatch + * -XX:CompileCommand=compileonly,*TestArrayMetadata::* -XX:CompileCommand=dontinline,*TestArrayMetadata::test* + * compiler.valhalla.inlinetypes.TestArrayMetadata + * @run main/othervm/timeout=300 -Xbatch + * -XX:CompileCommand=compileonly,*TestArrayMetadata::* -XX:CompileCommand=dontinline,*TestArrayMetadata::* + * compiler.valhalla.inlinetypes.TestArrayMetadata + * @run main/othervm/timeout=300 -Xbatch + * -XX:CompileCommand=compileonly,*TestArrayMetadata::main -XX:CompileCommand=dontinline,*TestArrayMetadata::test* + * compiler.valhalla.inlinetypes.TestArrayMetadata + */ + +package compiler.valhalla.inlinetypes; + +import java.lang.reflect.Array; +import java.util.Arrays; + +import jdk.test.lib.Asserts; + +public class TestArrayMetadata { + + static interface MyInterface { + + } + + public static Object[] testArrayAllocation1() { + return new Object[1]; + } + + public static Object[][] testArrayAllocation2() { + return new Object[1][1]; + } + + public static Class getClass1() { + return Object.class; + } + + public static Object[] testArrayAllocation3() { + return (Object[])Array.newInstance(getClass1(), 1); + } + + public static Class getClass2() { + return TestArrayMetadata.class; + } + + public static Object[] testArrayAllocation4() { + return (TestArrayMetadata[])Array.newInstance(getClass2(), 1); + } + + public static Object[] testArrayAllocation5() { + return new MyInterface[1]; + } + + public static Object[] testArrayAllocation6() { + return new Integer[1]; + } + + public static Object[] testCheckcast1(Object arg) { + return (Object[])arg; + } + + public static Object[] testCheckcast2(Object arg) { + return (TestArrayMetadata[])arg; + } + + public static Cloneable testCheckcast3(Object arg) { + return (Cloneable)arg; + } + + public static Object testCheckcast4(Object arg) { + return (Object)arg; + } + + public static Object[][] testCheckcast5(Object arg) { + return (Object[][])arg; + } + + public static MyInterface[] testCheckcast6(Object arg) { + return (MyInterface[])arg; + } + + public static Integer[] testCheckcast7(Object arg) { + return (Integer[])arg; + } + + public static Class getArrayClass1() { + return Object[].class; + } + + public static boolean testIsInstance1(Object arg) { + return getArrayClass1().isInstance(arg); + } + + public static Class getArrayClass2() { + return TestArrayMetadata[].class; + } + + public static boolean testIsInstance2(Object arg) { + return getArrayClass2().isInstance(arg); + } + + public static Class getArrayClass3() { + return Cloneable.class; + } + + public static boolean testIsInstance3(Object arg) { + return getArrayClass3().isInstance(arg); + } + + public static Class getArrayClass4() { + return Object.class; + } + + public static boolean testIsInstance4(Object arg) { + return getArrayClass4().isInstance(arg); + } + + public static Class getArrayClass5() { + return Object[][].class; + } + + public static boolean testIsInstance5(Object arg) { + return getArrayClass5().isInstance(arg); + } + + public static Object[] testCopyOf1(Object[] array, Class clazz) { + return Arrays.copyOf(array, 1, clazz); + } + + public static Object[] testCopyOf2(Object[] array) { + return Arrays.copyOf(array, array.length, array.getClass()); + } + + public static Class testGetSuperclass1(Object[] array) { + return array.getClass().getSuperclass(); + } + + public static Class testGetSuperclass2() { + return Object[].class.getSuperclass(); + } + + public static Class testGetSuperclass3() { + return TestArrayMetadata[].class.getSuperclass(); + } + + public static Object[] testClassCast1(Object array) { + return Object[].class.cast(array); + } + + public static Object[] testClassCast2(Object array) { + return TestArrayMetadata[].class.cast(array); + } + + public static Object testClassCast3(Class c, Object array) { + return c.cast(array); + } + + public static void test5(Object[] array, Object obj) { + array[0] = obj; + } + + public static void test6(Object[][] array, Object[] obj) { + array[0] = obj; + } + + public static void test7(Object[][][] array, Object[][] obj) { + array[0] = obj; + } + + public static void test8(Object[][][][] array, Object[][][] obj) { + array[0] = obj; + } + + public static void test9(Object[][] array) { + array[0] = (Object[]) new Object[0]; + } + + public static void test10(Object[][] array) { + array[0] = new String[0]; + } + + public static boolean testIsAssignableFrom1(Class clazz1, Class clazz2) { + return clazz1.isAssignableFrom(clazz2); + } + + public static boolean testIsAssignableFrom2(Object obj, Class clazz) { + return obj.getClass().isAssignableFrom(clazz); + } + + public static boolean testIsAssignableFrom3(Class clazz, Object obj) { + return clazz.isAssignableFrom(obj.getClass()); + } + + public static void main(String[] args) { + for (int i = 0; i < 100_000; ++i) { + Object[] array1 = testArrayAllocation1(); + Object[][] array2 = testArrayAllocation2(); + Object[] array3 = testArrayAllocation3(); + Object[] array4 = testArrayAllocation4(); + Object[] array5 = testArrayAllocation5(); + Object[] array6 = testArrayAllocation6(); + + testCheckcast1(new Object[0]); + testCheckcast1(new TestArrayMetadata[0]); + testCheckcast1(array1); + testCheckcast1(array3); + testCheckcast1(array4); + testCheckcast1(array5); + testCheckcast1(array6); + try { + testCheckcast1(42); + throw new RuntimeException("No exception thrown"); + } catch (ClassCastException e) { + // Expected + } + try { + testCheckcast2(new Object[0]); + throw new RuntimeException("No exception thrown"); + } catch (ClassCastException e) { + // Expected + } + try { + testCheckcast2(array1); + throw new RuntimeException("No exception thrown"); + } catch (ClassCastException e) { + // Expected + } + testCheckcast2(new TestArrayMetadata[0]); + testCheckcast2(array4); + try { + testCheckcast2(array5); + throw new RuntimeException("No exception thrown"); + } catch (ClassCastException e) { + // Expected + } + try { + testCheckcast2(42); + throw new RuntimeException("No exception thrown"); + } catch (ClassCastException e) { + // Expected + } + testCheckcast3(new Object[0]); + testCheckcast3(new TestArrayMetadata[0]); + testCheckcast3(array1); + testCheckcast3(array3); + testCheckcast3(array4); + testCheckcast3(array5); + testCheckcast3(array6); + try { + testCheckcast3(42); + throw new RuntimeException("No exception thrown"); + } catch (ClassCastException e) { + // Expected + } + + testCheckcast4(new Object[0]); + testCheckcast4(new TestArrayMetadata[0]); + testCheckcast4(array1); + testCheckcast4(array3); + testCheckcast4(array4); + testCheckcast4(array5); + testCheckcast4(array6); + testCheckcast4(42); + + testCheckcast5(new Object[0][0]); + testCheckcast5(new TestArrayMetadata[0][0]); + testCheckcast5(array2); + try { + testCheckcast5(42); + throw new RuntimeException("No exception thrown"); + } catch (ClassCastException e) { + // Expected + } + + testCheckcast6(array5); + + testCheckcast7(array6); + + testCopyOf1(new Object[1], Object[].class); + testCopyOf1(new TestArrayMetadata[1], Object[].class); + testCopyOf1(new Object[1], TestArrayMetadata[].class); + testCopyOf1(new TestArrayMetadata[1], TestArrayMetadata[].class); + try { + testCopyOf1(new TestArrayMetadata[]{new TestArrayMetadata()}, Integer[].class); + throw new RuntimeException("No exception thrown"); + } catch (ArrayStoreException e) { + // Expected + } + + testCopyOf2(new Object[1]); + testCopyOf2(new TestArrayMetadata[1]); + + testClassCast1(new Object[0]); + testClassCast1(new TestArrayMetadata[0]); + try { + testClassCast1(new int[0]); + throw new RuntimeException("No exception thrown"); + } catch (ClassCastException e) { + // Expected + } + + testClassCast2(new TestArrayMetadata[0]); + try { + testClassCast2(new Object[0]); + throw new RuntimeException("No exception thrown"); + } catch (ClassCastException e) { + // Expected + } + try { + testClassCast2(new int[0]); + throw new RuntimeException("No exception thrown"); + } catch (ClassCastException e) { + // Expected + } + + testClassCast3(TestArrayMetadata[].class, new TestArrayMetadata[0]); + testClassCast3(Object[].class, new TestArrayMetadata[0]); + testClassCast3(Object[].class, new Object[0]); + testClassCast3(int[].class, new int[0]); + try { + testClassCast3(TestArrayMetadata[].class, new int[0]); + throw new RuntimeException("No exception thrown"); + } catch (ClassCastException e) { + // Expected + } + + Asserts.assertEQ(testGetSuperclass1(new Object[1]), Object.class); + Asserts.assertEQ(testGetSuperclass1(new TestArrayMetadata[1]), Object.class); + Asserts.assertEQ(testGetSuperclass2(), Object.class); + Asserts.assertEQ(testGetSuperclass3(), Object.class); + + Asserts.assertTrue(testIsInstance1(new Object[0])); + Asserts.assertTrue(testIsInstance1(new TestArrayMetadata[0])); + Asserts.assertFalse(testIsInstance1(42)); + Asserts.assertTrue(testIsInstance1(array1)); + Asserts.assertTrue(testIsInstance1(array3)); + Asserts.assertTrue(testIsInstance1(array4)); + Asserts.assertTrue(testIsInstance1(array5)); + Asserts.assertTrue(testIsInstance1(array6)); + + Asserts.assertFalse(testIsInstance2(new Object[0])); + Asserts.assertTrue(testIsInstance2(new TestArrayMetadata[0])); + Asserts.assertFalse(testIsInstance2(42)); + Asserts.assertFalse(testIsInstance2(array1)); + Asserts.assertFalse(testIsInstance2(array3)); + Asserts.assertTrue(testIsInstance2(array4)); + Asserts.assertFalse(testIsInstance2(array5)); + Asserts.assertFalse(testIsInstance2(array6)); + + Asserts.assertTrue(testIsInstance3(new Object[0])); + Asserts.assertTrue(testIsInstance3(new TestArrayMetadata[0])); + Asserts.assertFalse(testIsInstance3(42)); + Asserts.assertTrue(testIsInstance3(array1)); + Asserts.assertTrue(testIsInstance3(array3)); + Asserts.assertTrue(testIsInstance3(array4)); + Asserts.assertTrue(testIsInstance3(array5)); + Asserts.assertTrue(testIsInstance3(array6)); + + Asserts.assertTrue(testIsInstance4(new Object[0])); + Asserts.assertTrue(testIsInstance4(new TestArrayMetadata[0])); + Asserts.assertTrue(testIsInstance4(42)); + Asserts.assertTrue(testIsInstance4(array1)); + Asserts.assertTrue(testIsInstance4(array3)); + Asserts.assertTrue(testIsInstance4(array4)); + Asserts.assertTrue(testIsInstance4(array5)); + Asserts.assertTrue(testIsInstance4(array6)); + + Asserts.assertTrue(testIsInstance5(new Object[0][0])); + Asserts.assertTrue(testIsInstance5(new TestArrayMetadata[0][0])); + Asserts.assertTrue(testIsInstance5(array2)); + Asserts.assertFalse(testIsInstance5(42)); + + test5(new Object[1], new TestArrayMetadata()); + test5((new Object[1][1])[0], (new TestArrayMetadata[1])[0]); + test5(new String[1], "42"); + test5((new String[1][1])[0], (new String[1])[0]); + test5(array1, new TestArrayMetadata()); + test5(array3, new TestArrayMetadata()); + test5(array4, new TestArrayMetadata()); + try { + test5(array5, new TestArrayMetadata()); + throw new RuntimeException("No exception thrown"); + } catch (ArrayStoreException e) { + // Expected + } + + test6(new Object[1][1], new TestArrayMetadata[0]); + test6((new Object[1][1][1])[0], (new TestArrayMetadata[1][0])[0]); + test6(new String[1][1], new String[0]); + test6((new String[1][1][1])[0], (new String[1][0])[0]); + test6(array2, new TestArrayMetadata[0]); + + test7(new Object[1][1][1], new TestArrayMetadata[0][0]); + test7((new Object[1][1][1][1])[0], (new TestArrayMetadata[1][0][0])[0]); + test7(new String[1][1][1], new String[0][0]); + test7((new String[1][1][1][1])[0], (new String[1][0][0])[0]); + + test8(new Object[1][1][1][1], new TestArrayMetadata[0][0][0]); + test8((new Object[1][1][1][1][1])[0], (new TestArrayMetadata[1][0][0][0])[0]); + test8(new String[1][1][1][1], new String[0][0][0]); + test8((new String[1][1][1][1][1])[0], (new String[1][0][0][0])[0]); + + test9(new Object[1][1]); + test9(array2); + + test10(new String[1][1]); + test10(array2); + + Asserts.assertTrue(testIsAssignableFrom1(Object[].class, Object[].class)); + Asserts.assertTrue(testIsAssignableFrom1(Object[].class, TestArrayMetadata[].class)); + Asserts.assertTrue(testIsAssignableFrom1(int[].class, int[].class)); + Asserts.assertFalse(testIsAssignableFrom1(Object[].class, int[].class)); + Asserts.assertFalse(testIsAssignableFrom1(Object[].class, TestArrayMetadata.class)); + + Asserts.assertTrue(testIsAssignableFrom2(new Object[0], Object[].class)); + Asserts.assertTrue(testIsAssignableFrom2(new Object[0], TestArrayMetadata[].class)); + Asserts.assertTrue(testIsAssignableFrom2(new int[0], int[].class)); + Asserts.assertFalse(testIsAssignableFrom2(new Object[0], int[].class)); + Asserts.assertFalse(testIsAssignableFrom2(new Object[0], TestArrayMetadata.class)); + + Asserts.assertTrue(testIsAssignableFrom3(Object[].class, new Object[0])); + Asserts.assertTrue(testIsAssignableFrom3(Object[].class, new TestArrayMetadata[0])); + Asserts.assertTrue(testIsAssignableFrom3(int[].class, new int[0])); + Asserts.assertFalse(testIsAssignableFrom3(Object[].class, new int[0])); + Asserts.assertFalse(testIsAssignableFrom3(Object[].class, new TestArrayMetadata())); + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrayNullMarkers.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrayNullMarkers.java new file mode 100644 index 00000000000..9550ffb7726 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrayNullMarkers.java @@ -0,0 +1,1282 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; + +import jdk.test.lib.Asserts; +import jdk.test.whitebox.WhiteBox; + +/* + * @test + * @summary Test support for null markers in (flat) arrays. + * @library /test/lib / + * @requires (os.simpleArch == "x64" | os.simpleArch == "aarch64") + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * compiler.valhalla.inlinetypes.TestArrayNullMarkers + * + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:-UseNullableValueFlattening -XX:-UseAtomicValueFlattening -XX:-UseNonAtomicValueFlattening + * compiler.valhalla.inlinetypes.TestArrayNullMarkers + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:-UseNullableValueFlattening -XX:-UseAtomicValueFlattening -XX:+UseNonAtomicValueFlattening + * compiler.valhalla.inlinetypes.TestArrayNullMarkers + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:-UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:-UseNonAtomicValueFlattening + * compiler.valhalla.inlinetypes.TestArrayNullMarkers + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:-UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseNonAtomicValueFlattening + * compiler.valhalla.inlinetypes.TestArrayNullMarkers + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:+UseNullableValueFlattening -XX:-UseAtomicValueFlattening -XX:-UseNonAtomicValueFlattening + * compiler.valhalla.inlinetypes.TestArrayNullMarkers + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:+UseNullableValueFlattening -XX:-UseAtomicValueFlattening -XX:+UseNonAtomicValueFlattening + * compiler.valhalla.inlinetypes.TestArrayNullMarkers + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:-UseNonAtomicValueFlattening + * compiler.valhalla.inlinetypes.TestArrayNullMarkers + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseNonAtomicValueFlattening + * compiler.valhalla.inlinetypes.TestArrayNullMarkers + * + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseNonAtomicValueFlattening + * -XX:CompileCommand=dontinline,*::test* -XX:CompileCommand=dontinline,*::check* + * compiler.valhalla.inlinetypes.TestArrayNullMarkers + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseNonAtomicValueFlattening + * -XX:+IgnoreUnrecognizedVMOptions -XX:-MonomorphicArrayCheck -XX:-UseArrayLoadStoreProfile + * compiler.valhalla.inlinetypes.TestArrayNullMarkers + */ + +public class TestArrayNullMarkers { + + private static final WhiteBox WHITEBOX = WhiteBox.getWhiteBox(); + private static final boolean UseArrayFlattening = WHITEBOX.getBooleanVMFlag("UseArrayFlattening"); + private static final boolean UseNullableValueFlattening = WHITEBOX.getBooleanVMFlag("UseNullableValueFlattening"); + private static final boolean UseNonAtomicValueFlattening = WHITEBOX.getBooleanVMFlag("UseNonAtomicValueFlattening"); + private static final boolean UseAtomicValueFlattening = WHITEBOX.getBooleanVMFlag("UseAtomicValueFlattening"); + private static final boolean ForceNonTearable = !WHITEBOX.getStringVMFlag("ForceNonTearable").equals(""); + + // Is naturally atomic and has null-free, non-atomic, flat (1 bytes), null-free, atomic, flat (1 bytes) and nullable, atomic, flat (4 bytes) layouts + @LooselyConsistentValue + static value class OneByte { + byte b; + + public OneByte(byte b) { + this.b = b; + } + + static final OneByte DEFAULT = new OneByte((byte)0); + } + + // Has null-free, non-atomic, flat (2 bytes), null-free, atomic, flat (2 bytes) and nullable, atomic, flat (4 bytes) layouts + @LooselyConsistentValue + static value class TwoBytes { + byte b1; + byte b2; + + public TwoBytes(byte b1, byte b2) { + this.b1 = b1; + this.b2 = b2; + } + + static final TwoBytes DEFAULT = new TwoBytes((byte)0, (byte)0); + } + + // Has null-free, non-atomic, flat (4 bytes), null-free, atomic, flat (4 bytes) and nullable, atomic, flat (8 bytes) layouts + @LooselyConsistentValue + static value class TwoShorts { + short s1; + short s2; + + public TwoShorts(short s1, short s2) { + this.s1 = s1; + this.s2 = s2; + } + + static final TwoShorts DEFAULT = new TwoShorts((short)0, (short)0); + } + + // Has null-free, non-atomic flat (8 bytes) and null-free, atomic, flat (8 bytes) layouts + @LooselyConsistentValue + static value class TwoInts { + int i1; + int i2; + + public TwoInts(int i1, int i2) { + this.i1 = i1; + this.i2 = i2; + } + + static final TwoInts DEFAULT = new TwoInts(0, 0); + } + + // Has null-free, non-atomic flat (16 bytes) layout + @LooselyConsistentValue + static value class TwoLongs { + long l1; + long l2; + + public TwoLongs(int l1, int l2) { + this.l1 = l1; + this.l2 = l2; + } + + static final TwoLongs DEFAULT = new TwoLongs(0, 0); + } + + // Has null-free, non-atomic, flat (5 bytes), null-free, atomic, flat (8 bytes) and nullable, atomic, flat (8 bytes) layouts + @LooselyConsistentValue + static value class ByteAndOop { + byte b; + MyClass obj; + + public ByteAndOop(byte b, MyClass obj) { + this.b = b; + this.obj = obj; + } + + static final ByteAndOop DEFAULT = new ByteAndOop((byte)0, null); + } + + static class MyClass { + int x; + + public MyClass(int x) { + this.x = x; + } + } + + @LooselyConsistentValue + static value class IntAndArrayOop { + int i; + MyClass[] array; + + public IntAndArrayOop(int i, MyClass[] array) { + this.i = i; + this.array = array; + } + + static final IntAndArrayOop DEFAULT = new IntAndArrayOop(0, null); + } + + public static void testWrite0(OneByte[] array, int i, OneByte val) { + array[i] = val; + } + + public static void testWrite1(TwoBytes[] array, int i, TwoBytes val) { + array[i] = val; + } + + public static void testWrite2(TwoShorts[] array, int i, TwoShorts val) { + array[i] = val; + } + + public static void testWrite3(TwoInts[] array, int i, TwoInts val) { + array[i] = val; + } + + public static void testWrite4(TwoLongs[] array, int i, TwoLongs val) { + array[i] = val; + } + + public static void testWrite5(ByteAndOop[] array, int i, ByteAndOop val) { + array[i] = val; + } + + public static void testWrite6(Object[] array, int i, Object val) { + array[i] = val; + } + + public static OneByte testRead0(OneByte[] array, int i) { + return array[i]; + } + + public static TwoBytes testRead1(TwoBytes[] array, int i) { + return array[i]; + } + + public static TwoShorts testRead2(TwoShorts[] array, int i) { + return array[i]; + } + + public static TwoInts testRead3(TwoInts[] array, int i) { + return array[i]; + } + + public static TwoLongs testRead4(TwoLongs[] array, int i) { + return array[i]; + } + + public static ByteAndOop testRead5(ByteAndOop[] array, int i) { + return array[i]; + } + + public static Object testRead6(Object[] array, int i) { + return array[i]; + } + + static final OneByte CANARY0 = new OneByte((byte)42); + + public static void checkCanary0(OneByte[] array) { + Asserts.assertEQ(array[0], CANARY0); + Asserts.assertEQ(array[2], CANARY0); + } + + static final TwoBytes CANARY1 = new TwoBytes((byte)42, (byte)42); + + public static void checkCanary1(TwoBytes[] array) { + Asserts.assertEQ(array[0], CANARY1); + Asserts.assertEQ(array[2], CANARY1); + } + + static final TwoShorts CANARY2 = new TwoShorts((short)42, (short)42); + + public static void checkCanary2(TwoShorts[] array) { + Asserts.assertEQ(array[0], CANARY2); + Asserts.assertEQ(array[2], CANARY2); + } + + static final TwoInts CANARY3 = new TwoInts(42, 42); + + public static void checkCanary3(TwoInts[] array) { + Asserts.assertEQ(array[0], CANARY3); + Asserts.assertEQ(array[2], CANARY3); + } + + static final TwoLongs CANARY4 = new TwoLongs(42, 42); + + public static void checkCanary4(TwoLongs[] array) { + Asserts.assertEQ(array[0], CANARY4); + Asserts.assertEQ(array[2], CANARY4); + } + + static final ByteAndOop CANARY5 = new ByteAndOop((byte)42, new MyClass(42)); + + public static void checkCanary5(ByteAndOop[] array) { + Asserts.assertEQ(array[0], CANARY5); + Asserts.assertEQ(array[2], CANARY5); + } + + public static TwoBytes[] testNullRestrictedArrayIntrinsic(int size, int idx, TwoBytes val) { + TwoBytes[] nullFreeArray = (TwoBytes[])ValueClass.newNullRestrictedNonAtomicArray(TwoBytes.class, size, TwoBytes.DEFAULT); + if (!ForceNonTearable) { + Asserts.assertEquals(ValueClass.isFlatArray(nullFreeArray), UseArrayFlattening && UseNonAtomicValueFlattening); + } + Asserts.assertTrue(ValueClass.isNullRestrictedArray(nullFreeArray)); + Asserts.assertTrue(!ValueClass.isFlatArray(nullFreeArray) || !ValueClass.isAtomicArray(nullFreeArray)); + Asserts.assertEquals(nullFreeArray[idx], TwoBytes.DEFAULT); + testWrite1(nullFreeArray, idx, val); + Asserts.assertEQ(testRead1(nullFreeArray, idx), val); + return nullFreeArray; + } + + // Non-final value to initialize null-restricted arrays + static Object initVal1 = CANARY1; + static TwoBytes initVal2 = CANARY1; + + public static TwoBytes[] testNullRestrictedArrayIntrinsicDynamic1(int size, int idx, TwoBytes val) { + TwoBytes[] nullFreeArray = (TwoBytes[])ValueClass.newNullRestrictedNonAtomicArray(TwoBytes.class, size, initVal1); + if (!ForceNonTearable) { + Asserts.assertEquals(ValueClass.isFlatArray(nullFreeArray), UseArrayFlattening && UseNonAtomicValueFlattening); + } + Asserts.assertTrue(ValueClass.isNullRestrictedArray(nullFreeArray)); + Asserts.assertTrue(!ValueClass.isFlatArray(nullFreeArray) || !ValueClass.isAtomicArray(nullFreeArray)); + Asserts.assertEquals(nullFreeArray[idx], CANARY1); + testWrite1(nullFreeArray, idx, val); + Asserts.assertEQ(testRead1(nullFreeArray, idx), val); + return nullFreeArray; + } + + public static TwoBytes[] testNullRestrictedArrayIntrinsicDynamic2(int size, int idx, TwoBytes val) { + TwoBytes[] nullFreeArray = (TwoBytes[])ValueClass.newNullRestrictedNonAtomicArray(TwoBytes.class, size, initVal2); + if (!ForceNonTearable) { + Asserts.assertEquals(ValueClass.isFlatArray(nullFreeArray), UseArrayFlattening && UseNonAtomicValueFlattening); + } + Asserts.assertTrue(ValueClass.isNullRestrictedArray(nullFreeArray)); + Asserts.assertTrue(!ValueClass.isFlatArray(nullFreeArray) || !ValueClass.isAtomicArray(nullFreeArray)); + Asserts.assertEquals(nullFreeArray[idx], CANARY1); + testWrite1(nullFreeArray, idx, val); + Asserts.assertEQ(testRead1(nullFreeArray, idx), val); + return nullFreeArray; + } + + static byte myByte = 0; + + public static TwoBytes[] testNullRestrictedArrayIntrinsicDynamic3(int size, int idx, TwoBytes val) { + TwoBytes[] nullFreeArray = (TwoBytes[])ValueClass.newNullRestrictedNonAtomicArray(TwoBytes.class, size, new TwoBytes(++myByte, myByte)); + if (!ForceNonTearable) { + Asserts.assertEquals(ValueClass.isFlatArray(nullFreeArray), UseArrayFlattening && UseNonAtomicValueFlattening); + } + Asserts.assertTrue(ValueClass.isNullRestrictedArray(nullFreeArray)); + Asserts.assertTrue(!ValueClass.isFlatArray(nullFreeArray) || !ValueClass.isAtomicArray(nullFreeArray)); + Asserts.assertEquals(nullFreeArray[idx], new TwoBytes(myByte, myByte)); + testWrite1(nullFreeArray, idx, val); + Asserts.assertEQ(testRead1(nullFreeArray, idx), val); + return nullFreeArray; + } + + public static TwoBytes[] testNullRestrictedAtomicArrayIntrinsic(int size, int idx, TwoBytes val) { + TwoBytes[] nullFreeAtomicArray = (TwoBytes[])ValueClass.newNullRestrictedAtomicArray(TwoBytes.class, size, TwoBytes.DEFAULT); + if (!ForceNonTearable) { + Asserts.assertEquals(ValueClass.isFlatArray(nullFreeAtomicArray), UseArrayFlattening && UseAtomicValueFlattening); + } + Asserts.assertTrue(ValueClass.isNullRestrictedArray(nullFreeAtomicArray)); + Asserts.assertTrue(ValueClass.isAtomicArray(nullFreeAtomicArray)); + Asserts.assertEquals(nullFreeAtomicArray[idx], TwoBytes.DEFAULT); + testWrite1(nullFreeAtomicArray, idx, val); + Asserts.assertEQ(testRead1(nullFreeAtomicArray, idx), val); + return nullFreeAtomicArray; + } + + public static TwoBytes[] testNullRestrictedAtomicArrayIntrinsicDynamic1(int size, int idx, TwoBytes val) { + TwoBytes[] nullFreeAtomicArray = (TwoBytes[])ValueClass.newNullRestrictedAtomicArray(TwoBytes.class, size, initVal1); + if (!ForceNonTearable) { + Asserts.assertEquals(ValueClass.isFlatArray(nullFreeAtomicArray), UseArrayFlattening && UseAtomicValueFlattening); + } + Asserts.assertTrue(ValueClass.isNullRestrictedArray(nullFreeAtomicArray)); + Asserts.assertTrue(ValueClass.isAtomicArray(nullFreeAtomicArray)); + Asserts.assertEquals(nullFreeAtomicArray[idx], CANARY1); + testWrite1(nullFreeAtomicArray, idx, val); + Asserts.assertEQ(testRead1(nullFreeAtomicArray, idx), val); + return nullFreeAtomicArray; + } + + public static TwoBytes[] testNullRestrictedAtomicArrayIntrinsicDynamic2(int size, int idx, TwoBytes val) { + TwoBytes[] nullFreeAtomicArray = (TwoBytes[])ValueClass.newNullRestrictedAtomicArray(TwoBytes.class, size, initVal2); + if (!ForceNonTearable) { + Asserts.assertEquals(ValueClass.isFlatArray(nullFreeAtomicArray), UseArrayFlattening && UseAtomicValueFlattening); + } + Asserts.assertTrue(ValueClass.isNullRestrictedArray(nullFreeAtomicArray)); + Asserts.assertTrue(ValueClass.isAtomicArray(nullFreeAtomicArray)); + Asserts.assertEquals(nullFreeAtomicArray[idx], CANARY1); + testWrite1(nullFreeAtomicArray, idx, val); + Asserts.assertEQ(testRead1(nullFreeAtomicArray, idx), val); + return nullFreeAtomicArray; + } + + public static TwoBytes[] testNullRestrictedAtomicArrayIntrinsicDynamic3(int size, int idx, TwoBytes val) { + TwoBytes[] nullFreeAtomicArray = (TwoBytes[])ValueClass.newNullRestrictedAtomicArray(TwoBytes.class, size, new TwoBytes(++myByte, myByte)); + if (!ForceNonTearable) { + Asserts.assertEquals(ValueClass.isFlatArray(nullFreeAtomicArray), UseArrayFlattening && UseAtomicValueFlattening); + } + Asserts.assertTrue(ValueClass.isNullRestrictedArray(nullFreeAtomicArray)); + Asserts.assertTrue(ValueClass.isAtomicArray(nullFreeAtomicArray)); + Asserts.assertEquals(nullFreeAtomicArray[idx], new TwoBytes(myByte, myByte)); + testWrite1(nullFreeAtomicArray, idx, val); + Asserts.assertEQ(testRead1(nullFreeAtomicArray, idx), val); + return nullFreeAtomicArray; + } + + public static TwoBytes[] testNullableAtomicArrayIntrinsic(int size, int idx, TwoBytes val) { + TwoBytes[] nullableAtomicArray = (TwoBytes[])ValueClass.newNullableAtomicArray(TwoBytes.class, size); + if (!ForceNonTearable) { + Asserts.assertEquals(ValueClass.isFlatArray(nullableAtomicArray), UseArrayFlattening && UseNullableValueFlattening); + } + Asserts.assertFalse(ValueClass.isNullRestrictedArray(nullableAtomicArray)); + Asserts.assertTrue(ValueClass.isAtomicArray(nullableAtomicArray)); + Asserts.assertEquals(nullableAtomicArray[idx], null); + testWrite1(nullableAtomicArray, idx, val); + Asserts.assertEQ(testRead1(nullableAtomicArray, idx), val); + return nullableAtomicArray; + } + + @LooselyConsistentValue + static value class MyValueEmpty { + static final MyValueEmpty DEFAULT = new MyValueEmpty(); + } + + @LooselyConsistentValue + static value class ValueHolder1 { + TwoBytes val; + + public ValueHolder1(TwoBytes val) { + this.val = val; + } + + static final ValueHolder1 DEFAULT = new ValueHolder1(null); + } + + // Test support for replaced arrays + public static void testScalarReplacement1(OneByte valNullFree, OneByte val, boolean trap) { + OneByte[] nullFreeArray = (OneByte[])ValueClass.newNullRestrictedNonAtomicArray(OneByte.class, 2, OneByte.DEFAULT); + nullFreeArray[0] = valNullFree; + nullFreeArray[1] = new OneByte((byte)42); + if (trap) { + Asserts.assertEQ(nullFreeArray[0], valNullFree); + Asserts.assertEQ(nullFreeArray[1], new OneByte((byte)42)); + } + + OneByte[] nullFreeAtomicArray = (OneByte[])ValueClass.newNullRestrictedAtomicArray(OneByte.class, 2, OneByte.DEFAULT); + nullFreeAtomicArray[0] = valNullFree; + nullFreeAtomicArray[1] = new OneByte((byte)42); + if (trap) { + Asserts.assertEQ(nullFreeAtomicArray[0], valNullFree); + Asserts.assertEQ(nullFreeAtomicArray[1], new OneByte((byte)42)); + } + + OneByte[] nullableAtomicArray = (OneByte[])ValueClass.newNullableAtomicArray(OneByte.class, 4); + nullableAtomicArray[0] = valNullFree; + nullableAtomicArray[1] = val; + nullableAtomicArray[2] = new OneByte((byte)42); + nullableAtomicArray[3] = null; + if (trap) { + Asserts.assertEQ(nullableAtomicArray[0], valNullFree); + Asserts.assertEQ(nullableAtomicArray[1], val); + Asserts.assertEQ(nullableAtomicArray[2], new OneByte((byte)42)); + Asserts.assertEQ(nullableAtomicArray[3], null); + } + + OneByte[] nullableArray = new OneByte[4]; + nullableArray[0] = valNullFree; + nullableArray[1] = val; + nullableArray[2] = new OneByte((byte)42); + nullableArray[3] = null; + if (trap) { + Asserts.assertEQ(nullableArray[0], valNullFree); + Asserts.assertEQ(nullableArray[1], val); + Asserts.assertEQ(nullableArray[2], new OneByte((byte)42)); + Asserts.assertEQ(nullableArray[3], null); + } + } + + // Test support for scalar replaced arrays + public static void testScalarReplacement2(TwoBytes val, boolean trap) { + ValueHolder1[] nullFreeArray = (ValueHolder1[])ValueClass.newNullRestrictedNonAtomicArray(ValueHolder1.class, 1, ValueHolder1.DEFAULT); + nullFreeArray[0] = new ValueHolder1(val); + if (trap) { + Asserts.assertEQ(nullFreeArray[0].val, val); + } + + ValueHolder1[] nullFreeAtomicArray = (ValueHolder1[])ValueClass.newNullRestrictedAtomicArray(ValueHolder1.class, 1, ValueHolder1.DEFAULT); + nullFreeAtomicArray[0] = new ValueHolder1(val); + if (trap) { + Asserts.assertEQ(nullFreeAtomicArray[0].val, val); + } + + ValueHolder1[] nullableAtomicArray = (ValueHolder1[])ValueClass.newNullableAtomicArray(ValueHolder1.class, 2); + nullableAtomicArray[0] = new ValueHolder1(val); + nullableAtomicArray[1] = ValueHolder1.DEFAULT; + if (trap) { + Asserts.assertEQ(nullableAtomicArray[0].val, val); + Asserts.assertEQ(nullableAtomicArray[1].val, null); + } + + ValueHolder1[] nullableArray = new ValueHolder1[2]; + nullableArray[0] = new ValueHolder1(val); + nullableArray[1] = ValueHolder1.DEFAULT; + if (trap) { + Asserts.assertEQ(nullableArray[0].val, val); + Asserts.assertEQ(nullableArray[1].val, null); + } + } + + static void produceGarbage() { + for (int i = 0; i < 100; ++i) { + Object[] arrays = new Object[1024]; + for (int j = 0; j < arrays.length; j++) { + arrays[j] = new int[1024]; + } + } + System.gc(); + } + + static TwoShorts[] array1 = (TwoShorts[])ValueClass.newNullRestrictedAtomicArray(TwoShorts.class, 1, TwoShorts.DEFAULT); + static TwoShorts[] array2 = (TwoShorts[])ValueClass.newNullableAtomicArray(TwoShorts.class, 1); + static { + array2[0] = TwoShorts.DEFAULT; + } + static TwoShorts[] array3 = new TwoShorts[] { TwoShorts.DEFAULT }; + + // Catches an issue with type speculation based on profiling + public static void testProfiling() { + array1[0] = new TwoShorts(array1[0].s1, (short)0); + array2[0] = new TwoShorts(array2[0].s1, (short)0); + array3[0] = new TwoShorts(array3[0].s1, (short)0); + } + + static final OneByte[] NULL_FREE_ARRAY_0 = (OneByte[])ValueClass.newNullRestrictedNonAtomicArray(OneByte.class, 2, OneByte.DEFAULT); + static final OneByte[] NULL_FREE_ATOMIC_ARRAY_0 = (OneByte[])ValueClass.newNullRestrictedAtomicArray(OneByte.class, 2, OneByte.DEFAULT); + static final OneByte[] NULLABLE_ARRAY_0 = new OneByte[2]; + static final OneByte[] NULLABLE_ATOMIC_ARRAY_0 = (OneByte[])ValueClass.newNullableAtomicArray(OneByte.class, 2); + + static final TwoBytes[] NULL_FREE_ARRAY_1 = (TwoBytes[])ValueClass.newNullRestrictedNonAtomicArray(TwoBytes.class, 2, TwoBytes.DEFAULT); + static final TwoBytes[] NULL_FREE_ATOMIC_ARRAY_1 = (TwoBytes[])ValueClass.newNullRestrictedAtomicArray(TwoBytes.class, 2, TwoBytes.DEFAULT); + static final TwoBytes[] NULLABLE_ARRAY_1 = new TwoBytes[2]; + static final TwoBytes[] NULLABLE_ATOMIC_ARRAY_1 = (TwoBytes[])ValueClass.newNullableAtomicArray(TwoBytes.class, 2); + + static final TwoShorts[] NULL_FREE_ARRAY_2 = (TwoShorts[])ValueClass.newNullRestrictedNonAtomicArray(TwoShorts.class, 2, TwoShorts.DEFAULT); + static final TwoShorts[] NULL_FREE_ATOMIC_ARRAY_2 = (TwoShorts[])ValueClass.newNullRestrictedAtomicArray(TwoShorts.class, 2, TwoShorts.DEFAULT); + static final TwoShorts[] NULLABLE_ARRAY_2 = new TwoShorts[2]; + static final TwoShorts[] NULLABLE_ATOMIC_ARRAY_2 = (TwoShorts[])ValueClass.newNullableAtomicArray(TwoShorts.class, 2); + + static final TwoInts[] NULL_FREE_ARRAY_3 = (TwoInts[])ValueClass.newNullRestrictedNonAtomicArray(TwoInts.class, 1, TwoInts.DEFAULT); + static final TwoInts[] NULL_FREE_ATOMIC_ARRAY_3 = (TwoInts[])ValueClass.newNullRestrictedAtomicArray(TwoInts.class, 1, TwoInts.DEFAULT); + static final TwoInts[] NULLABLE_ARRAY_3 = new TwoInts[1]; + static final TwoInts[] NULLABLE_ATOMIC_ARRAY_3 = (TwoInts[])ValueClass.newNullableAtomicArray(TwoInts.class, 1); + + static final TwoLongs[] NULL_FREE_ARRAY_4 = (TwoLongs[])ValueClass.newNullRestrictedNonAtomicArray(TwoLongs.class, 1, TwoLongs.DEFAULT); + static final TwoLongs[] NULL_FREE_ATOMIC_ARRAY_4 = (TwoLongs[])ValueClass.newNullRestrictedAtomicArray(TwoLongs.class, 1, TwoLongs.DEFAULT); + static final TwoLongs[] NULLABLE_ARRAY_4 = new TwoLongs[1]; + static final TwoLongs[] NULLABLE_ATOMIC_ARRAY_4 = (TwoLongs[])ValueClass.newNullableAtomicArray(TwoLongs.class, 1); + + static final ByteAndOop[] NULL_FREE_ARRAY_5 = (ByteAndOop[])ValueClass.newNullRestrictedNonAtomicArray(ByteAndOop.class, 1, ByteAndOop.DEFAULT); + static final ByteAndOop[] NULL_FREE_ATOMIC_ARRAY_5 = (ByteAndOop[])ValueClass.newNullRestrictedAtomicArray(ByteAndOop.class, 1, ByteAndOop.DEFAULT); + static final ByteAndOop[] NULLABLE_ARRAY_5 = new ByteAndOop[1]; + static final ByteAndOop[] NULLABLE_ATOMIC_ARRAY_5 = (ByteAndOop[])ValueClass.newNullableAtomicArray(ByteAndOop.class, 1); + + // Test access to constant arrays + public static void testConstantArrays(int i) { + OneByte val0 = new OneByte((byte)i); + Asserts.assertEQ(NULL_FREE_ARRAY_0[0], OneByte.DEFAULT); + Asserts.assertEQ(NULL_FREE_ATOMIC_ARRAY_0[0], OneByte.DEFAULT); + Asserts.assertEQ(NULLABLE_ARRAY_0[0], null); + Asserts.assertEQ(NULLABLE_ATOMIC_ARRAY_0[0], null); + + try { + NULL_FREE_ARRAY_0[0] = null; + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + try { + NULL_FREE_ATOMIC_ARRAY_0[0] = null; + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + + NULL_FREE_ARRAY_0[0] = val0; + NULL_FREE_ATOMIC_ARRAY_0[0] = val0; + NULLABLE_ARRAY_0[0] = val0; + NULLABLE_ATOMIC_ARRAY_0[0] = val0; + + Asserts.assertEQ(NULL_FREE_ARRAY_0[0], val0); + Asserts.assertEQ(NULL_FREE_ATOMIC_ARRAY_0[0], val0); + Asserts.assertEQ(NULLABLE_ARRAY_0[0], val0); + Asserts.assertEQ(NULLABLE_ATOMIC_ARRAY_0[0], val0); + + NULL_FREE_ARRAY_0[0] = OneByte.DEFAULT; + NULL_FREE_ATOMIC_ARRAY_0[0] = OneByte.DEFAULT; + NULLABLE_ARRAY_0[0] = null; + NULLABLE_ATOMIC_ARRAY_0[0] = null; + + TwoBytes val1 = new TwoBytes((byte)i, (byte)i); + Asserts.assertEQ(NULL_FREE_ARRAY_1[0], TwoBytes.DEFAULT); + Asserts.assertEQ(NULL_FREE_ATOMIC_ARRAY_1[0], TwoBytes.DEFAULT); + Asserts.assertEQ(NULLABLE_ARRAY_1[0], null); + Asserts.assertEQ(NULLABLE_ATOMIC_ARRAY_1[0], null); + + try { + NULL_FREE_ARRAY_1[0] = null; + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + try { + NULL_FREE_ATOMIC_ARRAY_1[0] = null; + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + + NULL_FREE_ARRAY_1[0] = val1; + NULL_FREE_ATOMIC_ARRAY_1[0] = val1; + NULLABLE_ARRAY_1[0] = val1; + NULLABLE_ATOMIC_ARRAY_1[0] = val1; + + Asserts.assertEQ(NULL_FREE_ARRAY_1[0], val1); + Asserts.assertEQ(NULL_FREE_ATOMIC_ARRAY_1[0], val1); + Asserts.assertEQ(NULLABLE_ARRAY_1[0], val1); + Asserts.assertEQ(NULLABLE_ATOMIC_ARRAY_1[0], val1); + + NULL_FREE_ARRAY_1[0] = TwoBytes.DEFAULT; + NULL_FREE_ATOMIC_ARRAY_1[0] = TwoBytes.DEFAULT; + NULLABLE_ARRAY_1[0] = null; + NULLABLE_ATOMIC_ARRAY_1[0] = null; + + TwoShorts val2 = new TwoShorts((short)i, (short)i); + Asserts.assertEQ(NULL_FREE_ARRAY_2[0], TwoShorts.DEFAULT); + Asserts.assertEQ(NULL_FREE_ATOMIC_ARRAY_2[0], TwoShorts.DEFAULT); + Asserts.assertEQ(NULLABLE_ARRAY_2[0], null); + Asserts.assertEQ(NULLABLE_ATOMIC_ARRAY_2[0], null); + + try { + NULL_FREE_ARRAY_2[0] = null; + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + try { + NULL_FREE_ATOMIC_ARRAY_2[0] = null; + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + + NULL_FREE_ARRAY_2[0] = val2; + NULL_FREE_ATOMIC_ARRAY_2[0] = val2; + NULLABLE_ARRAY_2[0] = val2; + NULLABLE_ATOMIC_ARRAY_2[0] = val2; + + Asserts.assertEQ(NULL_FREE_ARRAY_2[0], val2); + Asserts.assertEQ(NULL_FREE_ATOMIC_ARRAY_2[0], val2); + Asserts.assertEQ(NULLABLE_ARRAY_2[0], val2); + Asserts.assertEQ(NULLABLE_ATOMIC_ARRAY_2[0], val2); + + NULL_FREE_ARRAY_2[0] = TwoShorts.DEFAULT; + NULL_FREE_ATOMIC_ARRAY_2[0] = TwoShorts.DEFAULT; + NULLABLE_ARRAY_2[0] = null; + NULLABLE_ATOMIC_ARRAY_2[0] = null; + + TwoInts val3 = new TwoInts(i, i); + Asserts.assertEQ(NULL_FREE_ARRAY_3[0], TwoInts.DEFAULT); + Asserts.assertEQ(NULL_FREE_ATOMIC_ARRAY_3[0], TwoInts.DEFAULT); + Asserts.assertEQ(NULLABLE_ARRAY_3[0], null); + Asserts.assertEQ(NULLABLE_ATOMIC_ARRAY_3[0], null); + + try { + NULL_FREE_ARRAY_3[0] = null; + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + try { + NULL_FREE_ATOMIC_ARRAY_3[0] = null; + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + + NULL_FREE_ARRAY_3[0] = val3; + NULL_FREE_ATOMIC_ARRAY_3[0] = val3; + NULLABLE_ARRAY_3[0] = val3; + NULLABLE_ATOMIC_ARRAY_3[0] = val3; + + Asserts.assertEQ(NULL_FREE_ARRAY_3[0], val3); + Asserts.assertEQ(NULL_FREE_ATOMIC_ARRAY_3[0], val3); + Asserts.assertEQ(NULLABLE_ARRAY_3[0], val3); + Asserts.assertEQ(NULLABLE_ATOMIC_ARRAY_3[0], val3); + + NULL_FREE_ARRAY_3[0] = TwoInts.DEFAULT; + NULL_FREE_ATOMIC_ARRAY_3[0] = TwoInts.DEFAULT; + NULLABLE_ARRAY_3[0] = null; + NULLABLE_ATOMIC_ARRAY_3[0] = null; + + TwoLongs val4 = new TwoLongs(i, i); + Asserts.assertEQ(NULL_FREE_ARRAY_4[0], TwoLongs.DEFAULT); + Asserts.assertEQ(NULL_FREE_ATOMIC_ARRAY_4[0], TwoLongs.DEFAULT); + Asserts.assertEQ(NULLABLE_ARRAY_4[0], null); + Asserts.assertEQ(NULLABLE_ATOMIC_ARRAY_4[0], null); + + try { + NULL_FREE_ARRAY_4[0] = null; + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + try { + NULL_FREE_ATOMIC_ARRAY_4[0] = null; + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + + NULL_FREE_ARRAY_4[0] = val4; + NULL_FREE_ATOMIC_ARRAY_4[0] = val4; + NULLABLE_ARRAY_4[0] = val4; + NULLABLE_ATOMIC_ARRAY_4[0] = val4; + + Asserts.assertEQ(NULL_FREE_ARRAY_4[0], val4); + Asserts.assertEQ(NULL_FREE_ATOMIC_ARRAY_4[0], val4); + Asserts.assertEQ(NULLABLE_ARRAY_4[0], val4); + Asserts.assertEQ(NULLABLE_ATOMIC_ARRAY_4[0], val4); + + NULL_FREE_ARRAY_4[0] = TwoLongs.DEFAULT; + NULL_FREE_ATOMIC_ARRAY_4[0] = TwoLongs.DEFAULT; + NULLABLE_ARRAY_4[0] = null; + NULLABLE_ATOMIC_ARRAY_4[0] = null; + + ByteAndOop val5 = new ByteAndOop((byte)i, new MyClass(i)); + Asserts.assertEQ(NULL_FREE_ARRAY_5[0], ByteAndOop.DEFAULT); + Asserts.assertEQ(NULL_FREE_ATOMIC_ARRAY_5[0], ByteAndOop.DEFAULT); + Asserts.assertEQ(NULLABLE_ARRAY_5[0], null); + Asserts.assertEQ(NULLABLE_ATOMIC_ARRAY_5[0], null); + + try { + NULL_FREE_ARRAY_5[0] = null; + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + try { + NULL_FREE_ATOMIC_ARRAY_5[0] = null; + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + + NULL_FREE_ARRAY_5[0] = val5; + NULL_FREE_ATOMIC_ARRAY_5[0] = val5; + NULLABLE_ARRAY_5[0] = val5; + NULLABLE_ATOMIC_ARRAY_5[0] = val5; + + Asserts.assertEQ(NULL_FREE_ARRAY_5[0], val5); + Asserts.assertEQ(NULL_FREE_ATOMIC_ARRAY_5[0], val5); + Asserts.assertEQ(NULLABLE_ARRAY_5[0], val5); + Asserts.assertEQ(NULLABLE_ATOMIC_ARRAY_5[0], val5); + + NULL_FREE_ARRAY_5[0] = ByteAndOop.DEFAULT; + NULL_FREE_ATOMIC_ARRAY_5[0] = ByteAndOop.DEFAULT; + NULLABLE_ARRAY_5[0] = null; + NULLABLE_ATOMIC_ARRAY_5[0] = null; + } + + // Test correct wiring of memory for flat accesses + public static OneByte testMemoryEffects0() { + NULLABLE_ARRAY_0[1] = CANARY0; + NULLABLE_ATOMIC_ARRAY_0[1] = CANARY0; + return NULLABLE_ARRAY_0[1]; + } + + public static TwoBytes testMemoryEffects1() { + NULLABLE_ARRAY_1[1] = CANARY1; + NULLABLE_ATOMIC_ARRAY_1[1] = CANARY1; + return NULLABLE_ARRAY_1[1]; + } + + public static TwoShorts testMemoryEffects2() { + NULLABLE_ARRAY_2[1] = CANARY2; + NULLABLE_ATOMIC_ARRAY_2[1] = CANARY2; + return NULLABLE_ARRAY_2[1]; + } + + public static void main(String[] args) { + OneByte[] nullFreeArray0 = (OneByte[])ValueClass.newNullRestrictedNonAtomicArray(OneByte.class, 3, OneByte.DEFAULT); + OneByte[] nullFreeAtomicArray0 = (OneByte[])ValueClass.newNullRestrictedAtomicArray(OneByte.class, 3, OneByte.DEFAULT); + OneByte[] nullableArray0 = new OneByte[3]; + OneByte[] nullableAtomicArray0 = (OneByte[])ValueClass.newNullableAtomicArray(OneByte.class, 3); + + TwoBytes[] nullFreeArray1 = (TwoBytes[])ValueClass.newNullRestrictedNonAtomicArray(TwoBytes.class, 3, TwoBytes.DEFAULT); + TwoBytes[] nullFreeAtomicArray1 = (TwoBytes[])ValueClass.newNullRestrictedAtomicArray(TwoBytes.class, 3, TwoBytes.DEFAULT); + TwoBytes[] nullableArray1 = new TwoBytes[3]; + TwoBytes[] nullableAtomicArray1 = (TwoBytes[])ValueClass.newNullableAtomicArray(TwoBytes.class, 3); + + TwoShorts[] nullFreeArray2 = (TwoShorts[])ValueClass.newNullRestrictedNonAtomicArray(TwoShorts.class, 3, TwoShorts.DEFAULT); + TwoShorts[] nullFreeAtomicArray2 = (TwoShorts[])ValueClass.newNullRestrictedAtomicArray(TwoShorts.class, 3, TwoShorts.DEFAULT); + TwoShorts[] nullableArray2 = new TwoShorts[3]; + TwoShorts[] nullableAtomicArray2 = (TwoShorts[])ValueClass.newNullableAtomicArray(TwoShorts.class, 3); + + TwoInts[] nullFreeArray3 = (TwoInts[])ValueClass.newNullRestrictedNonAtomicArray(TwoInts.class, 3, TwoInts.DEFAULT); + TwoInts[] nullFreeAtomicArray3 = (TwoInts[])ValueClass.newNullRestrictedAtomicArray(TwoInts.class, 3, TwoInts.DEFAULT); + TwoInts[] nullableArray3 = new TwoInts[3]; + TwoInts[] nullableAtomicArray3 = (TwoInts[])ValueClass.newNullableAtomicArray(TwoInts.class, 3); + + TwoLongs[] nullFreeArray4 = (TwoLongs[])ValueClass.newNullRestrictedNonAtomicArray(TwoLongs.class, 3, TwoLongs.DEFAULT); + TwoLongs[] nullFreeAtomicArray4 = (TwoLongs[])ValueClass.newNullRestrictedAtomicArray(TwoLongs.class, 3, TwoLongs.DEFAULT); + TwoLongs[] nullableArray4 = new TwoLongs[3]; + TwoLongs[] nullableAtomicArray4 = (TwoLongs[])ValueClass.newNullableAtomicArray(TwoLongs.class, 3); + + ByteAndOop[] nullFreeArray5 = (ByteAndOop[])ValueClass.newNullRestrictedNonAtomicArray(ByteAndOop.class, 3, ByteAndOop.DEFAULT); + ByteAndOop[] nullFreeAtomicArray5 = (ByteAndOop[])ValueClass.newNullRestrictedAtomicArray(ByteAndOop.class, 3, ByteAndOop.DEFAULT); + ByteAndOop[] nullableArray5 = new ByteAndOop[3]; + ByteAndOop[] nullableAtomicArray5 = (ByteAndOop[])ValueClass.newNullableAtomicArray(ByteAndOop.class, 3); + + IntAndArrayOop[] nullFreeArray6 = (IntAndArrayOop[])ValueClass.newNullRestrictedNonAtomicArray(IntAndArrayOop.class, 3, IntAndArrayOop.DEFAULT); + IntAndArrayOop[] nullFreeAtomicArray6 = (IntAndArrayOop[])ValueClass.newNullRestrictedAtomicArray(IntAndArrayOop.class, 3, IntAndArrayOop.DEFAULT); + IntAndArrayOop[] nullableArray6 = new IntAndArrayOop[3]; + IntAndArrayOop[] nullableAtomicArray6 = (IntAndArrayOop[])ValueClass.newNullableAtomicArray(IntAndArrayOop.class, 3); + + MyValueEmpty[] nullFreeArrayEmpty = (MyValueEmpty[])ValueClass.newNullRestrictedNonAtomicArray(MyValueEmpty.class, 3, MyValueEmpty.DEFAULT); + MyValueEmpty[] nullFreeAtomicArrayEmpty = (MyValueEmpty[])ValueClass.newNullRestrictedAtomicArray(MyValueEmpty.class, 3, MyValueEmpty.DEFAULT); + MyValueEmpty[] nullableArrayEmpty = new MyValueEmpty[3]; + MyValueEmpty[] nullableAtomicArrayEmpty = (MyValueEmpty[])ValueClass.newNullableAtomicArray(MyValueEmpty.class, 3); + + // Write canary values to detect out of bound writes + nullFreeArray0[0] = CANARY0; + nullFreeArray0[2] = CANARY0; + nullFreeAtomicArray0[0] = CANARY0; + nullFreeAtomicArray0[2] = CANARY0; + nullableArray0[0] = CANARY0; + nullableArray0[2] = CANARY0; + nullableAtomicArray0[0] = CANARY0; + nullableAtomicArray0[2] = CANARY0; + + nullFreeArray1[0] = CANARY1; + nullFreeArray1[2] = CANARY1; + nullFreeAtomicArray1[0] = CANARY1; + nullFreeAtomicArray1[2] = CANARY1; + nullableArray1[0] = CANARY1; + nullableArray1[2] = CANARY1; + nullableAtomicArray1[0] = CANARY1; + nullableAtomicArray1[2] = CANARY1; + + nullFreeArray2[0] = CANARY2; + nullFreeArray2[2] = CANARY2; + nullFreeAtomicArray2[0] = CANARY2; + nullFreeAtomicArray2[2] = CANARY2; + nullableArray2[0] = CANARY2; + nullableArray2[2] = CANARY2; + nullableAtomicArray2[0] = CANARY2; + nullableAtomicArray2[2] = CANARY2; + + nullFreeArray3[0] = CANARY3; + nullFreeArray3[2] = CANARY3; + nullFreeAtomicArray3[0] = CANARY3; + nullFreeAtomicArray3[2] = CANARY3; + nullableArray3[0] = CANARY3; + nullableArray3[2] = CANARY3; + nullableAtomicArray3[0] = CANARY3; + nullableAtomicArray3[2] = CANARY3; + + nullFreeArray4[0] = CANARY4; + nullFreeArray4[2] = CANARY4; + nullFreeAtomicArray4[0] = CANARY4; + nullFreeAtomicArray4[2] = CANARY4; + nullableArray4[0] = CANARY4; + nullableArray4[2] = CANARY4; + nullableAtomicArray4[0] = CANARY4; + nullableAtomicArray4[2] = CANARY4; + + nullFreeArray5[0] = CANARY5; + nullFreeArray5[2] = CANARY5; + nullFreeAtomicArray5[0] = CANARY5; + nullFreeAtomicArray5[2] = CANARY5; + nullableArray5[0] = CANARY5; + nullableArray5[2] = CANARY5; + nullableAtomicArray5[0] = CANARY5; + nullableAtomicArray5[2] = CANARY5; + + final int LIMIT = 50_000; + for (int i = -50_000; i < LIMIT; ++i) { + OneByte val0 = new OneByte((byte)i); + TwoBytes val1 = new TwoBytes((byte)i, (byte)(i + 1)); + TwoShorts val2 = new TwoShorts((short)i, (short)(i + 1)); + TwoInts val3 = new TwoInts(i, i + 1); + TwoLongs val4 = new TwoLongs(i, i + 1); + + testWrite0(nullFreeArray0, 1, val0); + Asserts.assertEQ(testRead0(nullFreeArray0, 1), val0); + checkCanary0(nullFreeArray0); + + testWrite0(nullFreeAtomicArray0, 1, val0); + Asserts.assertEQ(testRead0(nullFreeAtomicArray0, 1), val0); + checkCanary0(nullFreeAtomicArray0); + + testWrite0(nullableArray0, 1, val0); + Asserts.assertEQ(testRead0(nullableArray0, 1), val0); + checkCanary0(nullableArray0); + testWrite0(nullableArray0, 1, null); + Asserts.assertEQ(testRead0(nullableArray0, 1), null); + checkCanary0(nullableArray0); + + testWrite0(nullableAtomicArray0, 1, val0); + Asserts.assertEQ(testRead0(nullableAtomicArray0, 1), val0); + checkCanary0(nullableAtomicArray0); + testWrite0(nullableAtomicArray0, 1, null); + Asserts.assertEQ(testRead0(nullableAtomicArray0, 1), null); + checkCanary0(nullableAtomicArray0); + + testWrite1(nullFreeArray1, 1, val1); + Asserts.assertEQ(testRead1(nullFreeArray1, 1), val1); + checkCanary1(nullFreeArray1); + + testWrite1(nullFreeAtomicArray1, 1, val1); + Asserts.assertEQ(testRead1(nullFreeAtomicArray1, 1), val1); + checkCanary1(nullFreeAtomicArray1); + + testWrite1(nullableArray1, 1, val1); + Asserts.assertEQ(testRead1(nullableArray1, 1), val1); + checkCanary1(nullableArray1); + testWrite1(nullableArray1, 1, null); + Asserts.assertEQ(testRead1(nullableArray1, 1), null); + checkCanary1(nullableArray1); + + testWrite1(nullableAtomicArray1, 1, val1); + Asserts.assertEQ(testRead1(nullableAtomicArray1, 1), val1); + checkCanary1(nullableAtomicArray1); + testWrite1(nullableAtomicArray1, 1, null); + Asserts.assertEQ(testRead1(nullableAtomicArray1, 1), null); + checkCanary1(nullableAtomicArray1); + + testWrite2(nullFreeArray2, 1, val2); + Asserts.assertEQ(testRead2(nullFreeArray2, 1), val2); + checkCanary2(nullFreeArray2); + + testWrite2(nullFreeAtomicArray2, 1, val2); + Asserts.assertEQ(testRead2(nullFreeAtomicArray2, 1), val2); + checkCanary2(nullFreeAtomicArray2); + + testWrite2(nullableArray2, 1, val2); + Asserts.assertEQ(testRead2(nullableArray2, 1), val2); + checkCanary2(nullableArray2); + testWrite2(nullableArray2, 1, null); + Asserts.assertEQ(testRead2(nullableArray2, 1), null); + checkCanary2(nullableArray2); + + testWrite2(nullableAtomicArray2, 1, val2); + Asserts.assertEQ(testRead2(nullableAtomicArray2, 1), val2); + checkCanary2(nullableAtomicArray2); + testWrite2(nullableAtomicArray2, 1, null); + Asserts.assertEQ(testRead2(nullableAtomicArray2, 1), null); + checkCanary2(nullableAtomicArray2); + + testWrite3(nullFreeArray3, 1, val3); + Asserts.assertEQ(testRead3(nullFreeArray3, 1), val3); + checkCanary3(nullFreeArray3); + + testWrite3(nullFreeAtomicArray3, 1, val3); + Asserts.assertEQ(testRead3(nullFreeAtomicArray3, 1), val3); + checkCanary3(nullFreeAtomicArray3); + + testWrite3(nullableArray3, 1, val3); + Asserts.assertEQ(testRead3(nullableArray3, 1), val3); + checkCanary3(nullableArray3); + testWrite3(nullableArray3, 1, null); + Asserts.assertEQ(testRead3(nullableArray3, 1), null); + checkCanary3(nullableArray3); + + testWrite3(nullableAtomicArray3, 1, val3); + Asserts.assertEQ(testRead3(nullableAtomicArray3, 1), val3); + checkCanary3(nullableAtomicArray3); + testWrite3(nullableAtomicArray3, 1, null); + Asserts.assertEQ(testRead3(nullableAtomicArray3, 1), null); + checkCanary3(nullableAtomicArray3); + + testWrite4(nullFreeArray4, 1, val4); + Asserts.assertEQ(testRead4(nullFreeArray4, 1), val4); + checkCanary4(nullFreeArray4); + + testWrite4(nullFreeAtomicArray4, 1, val4); + Asserts.assertEQ(testRead4(nullFreeAtomicArray4, 1), val4); + checkCanary4(nullFreeAtomicArray4); + + testWrite4(nullableArray4, 1, val4); + Asserts.assertEQ(testRead4(nullableArray4, 1), val4); + checkCanary4(nullableArray4); + testWrite4(nullableArray4, 1, null); + Asserts.assertEQ(testRead4(nullableArray4, 1), null); + checkCanary4(nullableArray4); + + testWrite4(nullableAtomicArray4, 1, val4); + Asserts.assertEQ(testRead4(nullableAtomicArray4, 1), val4); + checkCanary4(nullableAtomicArray4); + testWrite4(nullableAtomicArray4, 1, null); + Asserts.assertEQ(testRead4(nullableAtomicArray4, 1), null); + checkCanary4(nullableAtomicArray4); + + ByteAndOop val5 = new ByteAndOop((byte)i, new MyClass(i)); + testWrite5(nullFreeArray5, 1, val5); + testWrite5(nullFreeAtomicArray5, 1, val5); + testWrite5(nullableArray5, 1, val5); + testWrite5(nullableAtomicArray5, 1, val5); + + IntAndArrayOop val6 = new IntAndArrayOop(i, new MyClass[1]); + nullFreeArray6[1] = val6; + nullFreeArray6[2] = nullFreeArray6[1]; + nullFreeAtomicArray6[1] = val6; + nullFreeAtomicArray6[2] = nullFreeAtomicArray6[1]; + nullableArray6[1] = val6; + nullableArray6[2] = nullableArray6[1]; + nullableAtomicArray6[1] = val6; + nullableAtomicArray6[2] = nullableAtomicArray6[1]; + Asserts.assertEQ(nullFreeArray6[0], new IntAndArrayOop(0, null)); + Asserts.assertEQ(nullFreeAtomicArray6[0], new IntAndArrayOop(0, null)); + Asserts.assertEQ(nullableArray6[0], null); + Asserts.assertEQ(nullableAtomicArray6[0], null); + Asserts.assertEQ(nullFreeArray6[1], val6); + Asserts.assertEQ(nullFreeAtomicArray6[1], val6); + Asserts.assertEQ(nullableArray6[1], val6); + Asserts.assertEQ(nullableAtomicArray6[1], val6); + Asserts.assertEQ(nullFreeArray6[2], val6); + Asserts.assertEQ(nullFreeAtomicArray6[2], val6); + Asserts.assertEQ(nullableArray6[2], val6); + Asserts.assertEQ(nullableAtomicArray6[2], val6); + + // Test empty arrays + MyValueEmpty valEmpty = new MyValueEmpty(); + nullFreeArrayEmpty[1] = valEmpty; + nullFreeArrayEmpty[2] = nullFreeArrayEmpty[1]; + nullFreeAtomicArrayEmpty[1] = valEmpty; + nullFreeAtomicArrayEmpty[2] = nullFreeAtomicArrayEmpty[1]; + nullableArrayEmpty[1] = valEmpty; + nullableArrayEmpty[2] = nullableArrayEmpty[1]; + nullableAtomicArrayEmpty[1] = valEmpty; + nullableAtomicArrayEmpty[2] = nullableAtomicArrayEmpty[1]; + Asserts.assertEQ(nullFreeArrayEmpty[0], new MyValueEmpty()); + Asserts.assertEQ(nullFreeAtomicArrayEmpty[0], new MyValueEmpty()); + Asserts.assertEQ(nullableArrayEmpty[0], null); + Asserts.assertEQ(nullableAtomicArrayEmpty[0], null); + Asserts.assertEQ(nullFreeArrayEmpty[1], valEmpty); + Asserts.assertEQ(nullFreeAtomicArrayEmpty[1], valEmpty); + Asserts.assertEQ(nullableArrayEmpty[1], valEmpty); + Asserts.assertEQ(nullableAtomicArrayEmpty[1], valEmpty); + Asserts.assertEQ(nullFreeArrayEmpty[2], valEmpty); + Asserts.assertEQ(nullFreeAtomicArrayEmpty[2], valEmpty); + Asserts.assertEQ(nullableArrayEmpty[2], valEmpty); + Asserts.assertEQ(nullableAtomicArrayEmpty[2], valEmpty); + + if (i > (LIMIT - 50)) { + // After warmup, produce some garbage to trigger GC + produceGarbage(); + } + + Asserts.assertEQ(testRead5(nullFreeArray5, 1), val5); + checkCanary5(nullFreeArray5); + + Asserts.assertEQ(testRead5(nullFreeAtomicArray5, 1), val5); + checkCanary5(nullFreeAtomicArray5); + + Asserts.assertEQ(testRead5(nullableArray5, 1), val5); + checkCanary5(nullableArray5); + + testWrite5(nullableArray5, 1, null); + Asserts.assertEQ(testRead5(nullableArray5, 1), null); + checkCanary5(nullableArray5); + + Asserts.assertEQ(testRead5(nullableAtomicArray5, 1), val5); + checkCanary5(nullableAtomicArray5); + + testWrite5(nullableAtomicArray5, 1, null); + Asserts.assertEQ(testRead5(nullableAtomicArray5, 1), null); + checkCanary5(nullableAtomicArray5); + + // Test intrinsics + TwoBytes[] res = testNullRestrictedArrayIntrinsic(3, 1, val1); + Asserts.assertEQ(testRead1(res, 1), val1); + res = testNullRestrictedArrayIntrinsicDynamic1(3, 1, val1); + Asserts.assertEQ(testRead1(res, 1), val1); + res = testNullRestrictedArrayIntrinsicDynamic2(3, 1, val1); + Asserts.assertEQ(testRead1(res, 1), val1); + res = testNullRestrictedArrayIntrinsicDynamic3(3, 1, val1); + Asserts.assertEQ(testRead1(res, 1), val1); + res = testNullRestrictedAtomicArrayIntrinsic(3, 1, val1); + Asserts.assertEQ(testRead1(res, 1), val1); + res = testNullRestrictedAtomicArrayIntrinsicDynamic1(3, 1, val1); + Asserts.assertEQ(testRead1(res, 1), val1); + res = testNullRestrictedAtomicArrayIntrinsicDynamic2(3, 1, val1); + Asserts.assertEQ(testRead1(res, 1), val1); + res = testNullRestrictedAtomicArrayIntrinsicDynamic3(3, 1, val1); + Asserts.assertEQ(testRead1(res, 1), val1); + res = testNullableAtomicArrayIntrinsic(3, 1, val1); + Asserts.assertEQ(testRead1(res, 1), val1); + res = testNullableAtomicArrayIntrinsic(3, 2, null); + Asserts.assertEQ(testRead1(res, 2), null); + + testProfiling(); + + // Pollute profile + Object[] objArray = new Object[1]; + testWrite6(objArray, 0, objArray); + Asserts.assertEQ(testRead6(objArray, 0), objArray); + + testWrite6(nullFreeArray1, 1, val1); + Asserts.assertEQ(testRead6(nullFreeArray1, 1), val1); + checkCanary1(nullFreeArray1); + + testWrite6(nullFreeAtomicArray1, 1, val1); + Asserts.assertEQ(testRead6(nullFreeAtomicArray1, 1), val1); + checkCanary1(nullFreeAtomicArray1); + + testWrite6(nullableArray1, 1, val1); + Asserts.assertEQ(testRead6(nullableArray1, 1), val1); + checkCanary1(nullableArray1); + testWrite6(nullableArray1, 1, null); + Asserts.assertEQ(testRead6(nullableArray1, 1), null); + checkCanary1(nullableArray1); + + testWrite6(nullableAtomicArray1, 1, val1); + Asserts.assertEQ(testRead6(nullableAtomicArray1, 1), val1); + checkCanary1(nullableAtomicArray1); + testWrite6(nullableAtomicArray1, 1, null); + Asserts.assertEQ(testRead6(nullableAtomicArray1, 1), null); + checkCanary1(nullableAtomicArray1); + + // Test scalar replacement of array allocations + testScalarReplacement1(val0, null, false); + testScalarReplacement2(val1, false); + + // Test access to constant arrays + testConstantArrays(i); + + // Test correct wiring of memory for flat accesses + Asserts.assertEQ(testMemoryEffects0(), CANARY0); + Asserts.assertEQ(testMemoryEffects1(), CANARY1); + Asserts.assertEQ(testMemoryEffects2(), CANARY2); + } + + try { + testWrite0(nullFreeArray0, 0, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + checkCanary0(nullFreeArray0); + try { + testWrite0(nullFreeAtomicArray0, 0, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + checkCanary0(nullFreeAtomicArray0); + + try { + testWrite1(nullFreeArray1, 1, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + checkCanary1(nullFreeArray1); + try { + testWrite1(nullFreeAtomicArray1, 1, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + checkCanary1(nullFreeAtomicArray1); + + try { + testWrite2(nullFreeArray2, 1, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + checkCanary2(nullFreeArray2); + try { + testWrite2(nullFreeAtomicArray2, 1, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + checkCanary2(nullFreeAtomicArray2); + + try { + testWrite3(nullFreeArray3, 1, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + checkCanary3(nullFreeArray3); + try { + testWrite3(nullFreeAtomicArray3, 1, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + checkCanary3(nullFreeAtomicArray3); + + try { + testWrite4(nullFreeArray4, 1, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + checkCanary4(nullFreeArray4); + try { + testWrite4(nullFreeAtomicArray4, 1, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + checkCanary4(nullFreeAtomicArray4); + + try { + testWrite5(nullFreeArray5, 1, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + checkCanary5(nullFreeArray5); + try { + testWrite5(nullFreeAtomicArray5, 1, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + checkCanary5(nullFreeAtomicArray5); + + // Test intrinsics + try { + testNullRestrictedArrayIntrinsic(3, 1, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + try { + testNullRestrictedArrayIntrinsicDynamic1(3, 1, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + try { + testNullRestrictedArrayIntrinsicDynamic2(3, 1, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + try { + testNullRestrictedArrayIntrinsicDynamic3(3, 1, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + try { + testNullRestrictedAtomicArrayIntrinsic(3, 1, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + try { + testNullRestrictedAtomicArrayIntrinsicDynamic1(3, 1, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + try { + testNullRestrictedAtomicArrayIntrinsicDynamic2(3, 1, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + try { + testNullRestrictedAtomicArrayIntrinsicDynamic3(3, 1, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + + testScalarReplacement1(CANARY0, null, true); + testScalarReplacement2(CANARY1, true); + } +} + diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrayRematerializationWithProperties.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrayRematerializationWithProperties.java new file mode 100644 index 00000000000..1b70d7b09b5 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrayRematerializationWithProperties.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Test that rematerialized arrays keep their properties. + * @library /test/lib / + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm compiler.valhalla.inlinetypes.TestArrayRematerializationWithProperties + * @run main/othervm -XX:-TieredCompilation -Xbatch + * -XX:CompileCommand=compileonly,*TestArrayRematerializationWithProperties::test + * compiler.valhalla.inlinetypes.TestArrayRematerializationWithProperties + */ + +package compiler.valhalla.inlinetypes; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; + +import jdk.test.lib.Asserts; + +@LooselyConsistentValue +value class MyValue { + byte x = 42; + byte y = 43; + + static final MyValue DEFAULT = new MyValue(); +} + +public class TestArrayRematerializationWithProperties { + + static final boolean FLAT0 = ValueClass.isFlatArray(new MyValue[1]); + static final boolean FLAT1 = ValueClass.isFlatArray(ValueClass.newNullRestrictedAtomicArray(MyValue.class, 1, MyValue.DEFAULT)); + static final boolean FLAT2 = ValueClass.isFlatArray(ValueClass.newNullableAtomicArray(MyValue.class, 1)); + static final boolean FLAT3 = ValueClass.isFlatArray(ValueClass.newNullRestrictedNonAtomicArray(MyValue.class, 1, MyValue.DEFAULT)); + + static final boolean ATOMIC0 = ValueClass.isAtomicArray(new MyValue[1]); + static final boolean ATOMIC1 = ValueClass.isAtomicArray(ValueClass.newNullRestrictedAtomicArray(MyValue.class, 1, MyValue.DEFAULT)); + static final boolean ATOMIC2 = ValueClass.isAtomicArray(ValueClass.newNullableAtomicArray(MyValue.class, 1)); + static final boolean ATOMIC3 = ValueClass.isAtomicArray(ValueClass.newNullRestrictedNonAtomicArray(MyValue.class, 1, MyValue.DEFAULT)); + + static void test(boolean b) { + // C2 will scalar replace these arrays + MyValue[] array0 = { MyValue.DEFAULT }; + MyValue[] array1 = (MyValue[])ValueClass.newNullRestrictedAtomicArray(MyValue.class, 1, MyValue.DEFAULT); + MyValue[] array2 = (MyValue[])ValueClass.newNullableAtomicArray(MyValue.class, 1); + array2[0] = MyValue.DEFAULT; + MyValue[] array3 = (MyValue[])ValueClass.newNullRestrictedNonAtomicArray(MyValue.class, 1, MyValue.DEFAULT); + + if (b) { + // Uncommon trap, check content and properties of rematerialized arrays + Asserts.assertEquals(array0[0], MyValue.DEFAULT); + Asserts.assertEquals(array1[0], MyValue.DEFAULT); + Asserts.assertEquals(array2[0], MyValue.DEFAULT); + Asserts.assertEquals(array3[0], MyValue.DEFAULT); + + Asserts.assertEquals(ValueClass.isAtomicArray(array0), ATOMIC0); + Asserts.assertEquals(ValueClass.isAtomicArray(array1), ATOMIC1); + Asserts.assertEquals(ValueClass.isAtomicArray(array2), ATOMIC2); + Asserts.assertEquals(ValueClass.isAtomicArray(array3), ATOMIC3); + + Asserts.assertFalse(ValueClass.isNullRestrictedArray(array0)); + Asserts.assertTrue(ValueClass.isNullRestrictedArray(array1)); + Asserts.assertFalse(ValueClass.isNullRestrictedArray(array2)); + Asserts.assertTrue(ValueClass.isNullRestrictedArray(array3)); + + Asserts.assertEquals(ValueClass.isFlatArray(array0), FLAT0); + Asserts.assertEquals(ValueClass.isFlatArray(array1), FLAT1); + Asserts.assertEquals(ValueClass.isFlatArray(array2), FLAT2); + Asserts.assertEquals(ValueClass.isFlatArray(array3), FLAT3); + } + } + + public static void main(String[] args) { + // Warmup + for (int i = 0; i < 100_000; ++i) { + test(false); + } + // Trigger deopt + test(true); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrays.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrays.java new file mode 100644 index 00000000000..75d9b0da47a --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestArrays.java @@ -0,0 +1,3776 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.*; +import jdk.test.lib.Asserts; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; +import java.util.Arrays; + +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.ALLOC_ARRAY_OF_MYVALUE_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.ALLOC_OF_MYVALUE_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.CHECKCAST_ARRAYCOPY; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.CLONE_INTRINSIC_SLOW_PATH; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.INLINE_ARRAY_NULL_GUARD; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.JLONG_ARRAYCOPY; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.LOAD_OF_ANY_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.LOAD_UNKNOWN_INLINE; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.STORE_OF_ANY_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.STORE_UNKNOWN_INLINE; +import static compiler.valhalla.inlinetypes.InlineTypes.*; + +import static compiler.lib.ir_framework.IRNode.ALLOC; +import static compiler.lib.ir_framework.IRNode.ALLOC_ARRAY; +import static compiler.lib.ir_framework.IRNode.CLASS_CHECK_TRAP; +import static compiler.lib.ir_framework.IRNode.INTRINSIC_OR_TYPE_CHECKED_INLINING_TRAP; +import static compiler.lib.ir_framework.IRNode.LOOP; +import static compiler.lib.ir_framework.IRNode.PREDICATE_TRAP; +import static compiler.lib.ir_framework.IRNode.UNSTABLE_IF_TRAP; + +/* + * @test + * @key randomness + * @summary Test value class arrays. + * @library /test/lib / + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @requires (os.simpleArch == "x64" | os.simpleArch == "aarch64") + * @enablePreview + * @run main/othervm/timeout=300 compiler.valhalla.inlinetypes.TestArrays + */ + +@ForceCompileClassInitializer +public class TestArrays { + + public static void main(String[] args) { + Scenario[] scenarios = InlineTypes.DEFAULT_SCENARIOS; + scenarios[2].addFlags("--enable-preview", "-XX:-MonomorphicArrayCheck", "-XX:-UncommonNullCast", "-XX:+StressArrayCopyMacroNode"); + scenarios[3].addFlags("--enable-preview", "-XX:-MonomorphicArrayCheck", "-XX:+UseArrayFlattening", "-XX:-UncommonNullCast"); + scenarios[4].addFlags("--enable-preview", "-XX:-MonomorphicArrayCheck", "-XX:-UncommonNullCast"); + scenarios[5].addFlags("--enable-preview", "-XX:-MonomorphicArrayCheck", "-XX:-UncommonNullCast", "-XX:+StressArrayCopyMacroNode"); + + InlineTypes.getFramework() + .addScenarios(scenarios) + .addHelperClasses(MyValue1.class, MyValue2.class, MyValue2Inline.class) + .start(); + } + + static { + // Make sure RuntimeException is loaded to prevent uncommon traps in IR verified tests + RuntimeException tmp = new RuntimeException("42"); + } + + // Helper methods and classes + + protected long hash() { + return hash(rI, rL); + } + + protected long hash(int x, long y) { + return MyValue1.createWithFieldsInline(x, y).hash(); + } + + static void verify(Object[] src, Object[] dst) { + for (int i = 0; i < src.length; ++i) { + Asserts.assertEQ(src[i], dst[i]); + } + } + + static boolean compile_and_run_again_if_deoptimized(RunInfo info) { + if (!info.isWarmUp()) { + Method m = info.getTest(); + if (TestFramework.isCompiled(m)) { + TestFramework.compile(m, CompLevel.C2); + } + } + return false; + } + + @LooselyConsistentValue + static value class NotFlattenable { + private final Object o1 = null; + private final Object o2 = null; + private final Object o3 = null; + private final Object o4 = null; + private final Object o5 = null; + private final Object o6 = null; + + static final NotFlattenable DEFAULT = new NotFlattenable(); + } + + // Test value class array creation and initialization + @Test + @IR(applyIf = {"UseArrayFlattening", "true"}, + counts = {ALLOC_ARRAY_OF_MYVALUE_KLASS, "= 1"}) + @IR(applyIf = {"UseArrayFlattening", "false"}, + counts = {ALLOC_ARRAY_OF_MYVALUE_KLASS, "= 1"}, + failOn = {LOAD_OF_ANY_KLASS}) + public MyValue1[] test1(int len) { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + for (int i = 0; i < len; ++i) { + va[i] = MyValue1.createWithFieldsDontInline(rI, rL); + } + return va; + } + + @Run(test = "test1") + public void test1_verifier() { + int len = Math.abs(rI % 10); + MyValue1[] va = test1(len); + for (int i = 0; i < len; ++i) { + Asserts.assertEQ(va[i].hash(), hash()); + } + } + + // Test creation of a value class array and element access + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, ALLOC_ARRAY_OF_MYVALUE_KLASS, LOOP, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test2() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + va[0] = MyValue1.createWithFieldsInline(rI, rL); + return va[0].hash(); + } + + @Run(test = "test2") + public void test2_verifier() { + long result = test2(); + Asserts.assertEQ(result, hash()); + } + + // Test receiving a value class array from the interpreter, + // updating its elements in a loop and computing a hash. + @Test + @IR(failOn = {ALLOC_ARRAY_OF_MYVALUE_KLASS}) + public long test3(MyValue1[] va) { + long result = 0; + for (int i = 0; i < 10; ++i) { + result += va[i].hash(); + va[i] = MyValue1.createWithFieldsInline(rI + 1, rL + 1); + } + return result; + } + + @Run(test = "test3") + public void test3_verifier() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 10, MyValue1.DEFAULT); + long expected = 0; + for (int i = 0; i < 10; ++i) { + va[i] = MyValue1.createWithFieldsDontInline(rI + i, rL + i); + expected += va[i].hash(); + } + long result = test3(va); + Asserts.assertEQ(expected, result); + for (int i = 0; i < 10; ++i) { + if (va[i].hash() != hash(rI + 1, rL + 1)) { + Asserts.assertEQ(va[i].hash(), hash(rI + 1, rL + 1)); + } + } + } + + // Test returning a value class array received from the interpreter + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, ALLOC_ARRAY_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, LOOP, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public MyValue1[] test4(MyValue1[] va) { + return va; + } + + @Run(test = "test4") + public void test4_verifier() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 10, MyValue1.DEFAULT); + for (int i = 0; i < 10; ++i) { + va[i] = MyValue1.createWithFieldsDontInline(rI + i, rL + i); + } + va = test4(va); + for (int i = 0; i < 10; ++i) { + Asserts.assertEQ(va[i].hash(), hash(rI + i, rL + i)); + } + } + + // Merge value class arrays created from two branches + @Test + public MyValue1[] test5(boolean b) { + MyValue1[] va; + if (b) { + va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 5, MyValue1.DEFAULT); + for (int i = 0; i < 5; ++i) { + va[i] = MyValue1.createWithFieldsInline(rI, rL); + } + } else { + va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 10, MyValue1.DEFAULT); + for (int i = 0; i < 10; ++i) { + va[i] = MyValue1.createWithFieldsInline(rI + i, rL + i); + } + } + long sum = va[0].hashInterpreted(); + if (b) { + va[0] = MyValue1.createWithFieldsDontInline(rI, sum); + } else { + va[0] = MyValue1.createWithFieldsDontInline(rI + 1, sum + 1); + } + return va; + } + + @Run(test = "test5") + public void test5_verifier() { + MyValue1[] va = test5(true); + Asserts.assertEQ(va.length, 5); + Asserts.assertEQ(va[0].hash(), hash(rI, hash())); + for (int i = 1; i < 5; ++i) { + Asserts.assertEQ(va[i].hash(), hash()); + } + va = test5(false); + Asserts.assertEQ(va.length, 10); + Asserts.assertEQ(va[0].hash(), hash(rI + 1, hash(rI, rL) + 1)); + for (int i = 1; i < 10; ++i) { + Asserts.assertEQ(va[i].hash(), hash(rI + i, rL + i)); + } + } + + // Test creation of value class array with single element + @Test + @IR(failOn = {ALLOC_ARRAY_OF_MYVALUE_KLASS, LOOP, LOAD_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public MyValue1 test6() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + return va[0]; + } + + @Run(test = "test6") + public void test6_verifier() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + MyValue1 v = test6(); + Asserts.assertEQ(v.hashPrimitive(), va[0].hashPrimitive()); + } + + // Test initialization of value class arrays + @Test + @IR(failOn = {LOAD_OF_ANY_KLASS}) + public MyValue1[] test7(int len) { + return (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + } + + @Run(test = "test7") + public void test7_verifier() { + int len = Math.abs(rI % 10); + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + MyValue1[] var = test7(len); + for (int i = 0; i < len; ++i) { + Asserts.assertEQ(va[i].hashPrimitive(), var[i].hashPrimitive()); + } + } + + // Test creation of value class array with zero length + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, LOOP, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public MyValue1[] test8() { + return (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 0, MyValue1.DEFAULT); + } + + @Run(test = "test8") + public void test8_verifier() { + MyValue1[] va = test8(); + Asserts.assertEQ(va.length, 0); + } + + static MyValue1[] test9_va; + + // Test that value class array loaded from field has correct type + @Test + @IR(failOn = LOOP) + public long test9() { + return test9_va[0].hash(); + } + + @Run(test = "test9") + public void test9_verifier() { + test9_va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + test9_va[0] = MyValue1.createWithFieldsInline(rI, rL); + long result = test9(); + Asserts.assertEQ(result, hash()); + } + + // Multi-dimensional arrays + @Test + public MyValue1[][][] test10(int len1, int len2, int len3) { + MyValue1[][][] arr = new MyValue1[len1][len2][len3]; + for (int i = 0; i < len1; i++) { + for (int j = 0; j < len2; j++) { + for (int k = 0; k < len3; k++) { + arr[i][j][k] = MyValue1.createWithFieldsDontInline(rI + i , rL + j + k); + } + } + } + return arr; + } + + @Run(test = "test10") + public void test10_verifier() { + MyValue1[][][] arr = test10(2, 3, 4); + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 3; j++) { + for (int k = 0; k < 4; k++) { + Asserts.assertEQ(arr[i][j][k].hash(), MyValue1.createWithFieldsDontInline(rI + i , rL + j + k).hash()); + } + } + } + } + + @Test + public void test11(MyValue1[][][] arr, long[] res) { + int l = 0; + for (int i = 0; i < arr.length; i++) { + for (int j = 0; j < arr[i].length; j++) { + for (int k = 0; k < arr[i][j].length; k++) { + res[l] = arr[i][j][k].hash(); + l++; + } + } + } + } + + @Run(test = "test11") + public void test11_verifier() { + MyValue1[][][] arr = new MyValue1[2][3][4]; + long[] res = new long[2*3*4]; + long[] verif = new long[2*3*4]; + int l = 0; + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 3; j++) { + for (int k = 0; k < 4; k++) { + arr[i][j][k] = MyValue1.createWithFieldsDontInline(rI + i, rL + j + k); + verif[l] = arr[i][j][k].hash(); + l++; + } + } + } + test11(arr, res); + for (int i = 0; i < verif.length; i++) { + Asserts.assertEQ(res[i], verif[i]); + } + } + + // Array load out of bounds (upper bound) at compile time + @Test + public int test12() { + int arraySize = Math.abs(rI) % 10; + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, arraySize, MyValue1.DEFAULT); + + for (int i = 0; i < arraySize; i++) { + va[i] = MyValue1.createWithFieldsDontInline(rI + 1, rL); + } + + try { + return va[arraySize + 1].x; + } catch (ArrayIndexOutOfBoundsException e) { + return rI; + } + } + + @Run(test = "test12") + public void test12_verifier() { + Asserts.assertEQ(test12(), rI); + } + + // Array load out of bounds (lower bound) at compile time + @Test + public int test13() { + int arraySize = Math.abs(rI) % 10; + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, arraySize, MyValue1.DEFAULT); + + for (int i = 0; i < arraySize; i++) { + va[i] = MyValue1.createWithFieldsDontInline(rI + i, rL); + } + + try { + return va[-arraySize].x; + } catch (ArrayIndexOutOfBoundsException e) { + return rI; + } + } + + @Run(test = "test13") + public void test13_verifier() { + Asserts.assertEQ(test13(), rI); + } + + // Array load out of bound not known to compiler (both lower and upper bound) + @Test + public int test14(MyValue1[] va, int index) { + return va[index].x; + } + + @Run(test = "test14") + public void test14_verifier() { + int arraySize = Math.abs(rI) % 10; + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, arraySize, MyValue1.DEFAULT); + + for (int i = 0; i < arraySize; i++) { + va[i] = MyValue1.createWithFieldsDontInline(rI, rL); + } + + int result; + for (int i = -20; i < 20; i++) { + try { + result = test14(va, i); + } catch (ArrayIndexOutOfBoundsException e) { + result = rI; + } + Asserts.assertEQ(result, rI); + } + } + + // Array store out of bounds (upper bound) at compile time + @Test + public int test15() { + int arraySize = Math.abs(rI) % 10; + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, arraySize, MyValue1.DEFAULT); + + try { + for (int i = 0; i <= arraySize; i++) { + va[i] = MyValue1.createWithFieldsDontInline(rI + 1, rL); + } + return rI - 1; + } catch (ArrayIndexOutOfBoundsException e) { + return rI; + } + } + + @Run(test = "test15") + public void test15_verifier() { + Asserts.assertEQ(test15(), rI); + } + + // Array store out of bounds (lower bound) at compile time + @Test + public int test16() { + int arraySize = Math.abs(rI) % 10; + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, arraySize, MyValue1.DEFAULT); + + try { + for (int i = -1; i <= arraySize; i++) { + va[i] = MyValue1.createWithFieldsDontInline(rI + 1, rL); + } + return rI - 1; + } catch (ArrayIndexOutOfBoundsException e) { + return rI; + } + } + + @Run(test = "test16") + public void test16_verifier() { + Asserts.assertEQ(test16(), rI); + } + + // Array store out of bound not known to compiler (both lower and upper bound) + @Test + public int test17(MyValue1[] va, int index, MyValue1 vt) { + va[index] = vt; + return va[index].x; + } + + @Run(test = "test17") + public void test17_verifier() { + int arraySize = Math.abs(rI) % 10; + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, arraySize, MyValue1.DEFAULT); + + for (int i = 0; i < arraySize; i++) { + va[i] = MyValue1.createWithFieldsDontInline(rI, rL); + } + + MyValue1 vt = MyValue1.createWithFieldsDontInline(rI + 1, rL); + int result; + for (int i = -20; i < 20; i++) { + try { + result = test17(va, i, vt); + } catch (ArrayIndexOutOfBoundsException e) { + result = rI + 1; + } + Asserts.assertEQ(result, rI + 1); + } + + for (int i = 0; i < arraySize; i++) { + Asserts.assertEQ(va[i].x, rI + 1); + } + } + + // clone() as stub call + @Test + public MyValue1[] test18(MyValue1[] va) { + return va.clone(); + } + + @Run(test = "test18") + public void test18_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + for (int i = 0; i < len; ++i) { + va[i] = MyValue1.createWithFieldsInline(rI, rL); + } + MyValue1[] result = test18(va); + for (int i = 0; i < len; ++i) { + Asserts.assertEQ(result[i].hash(), va[i].hash()); + } + } + + // clone() as series of loads/stores + static MyValue1[] test19_orig = null; + + @Test + public MyValue1[] test19() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 8, MyValue1.DEFAULT); + for (int i = 0; i < va.length; ++i) { + va[i] = MyValue1.createWithFieldsInline(rI, rL); + } + test19_orig = va; + + return va.clone(); + } + + @Run(test = "test19") + public void test19_verifier() { + MyValue1[] result = test19(); + for (int i = 0; i < test19_orig.length; ++i) { + Asserts.assertEQ(result[i].hash(), test19_orig[i].hash()); + } + } + + // arraycopy() of value class array with oop fields + @Test + public void test20(MyValue1[] src, MyValue1[] dst) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Run(test = "test20") + public void test20_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] src = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + MyValue1[] dst = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + for (int i = 0; i < len; ++i) { + src[i] = MyValue1.createWithFieldsInline(rI, rL); + } + test20(src, dst); + for (int i = 0; i < len; ++i) { + Asserts.assertEQ(src[i].hash(), dst[i].hash()); + } + } + + // arraycopy() of value class array with no oop field + @Test + public void test21(MyValue2[] src, MyValue2[] dst) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Run(test = "test21") + public void test21_verifier() { + int len = Math.abs(rI) % 10; + MyValue2[] src = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, len, MyValue2.DEFAULT); + MyValue2[] dst = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, len, MyValue2.DEFAULT); + for (int i = 0; i < len; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test21(src, dst); + for (int i = 0; i < len; ++i) { + Asserts.assertEQ(src[i].hash(), dst[i].hash()); + } + } + + // arraycopy() of value class array with oop field and tightly + // coupled allocation as dest + @Test + public MyValue1[] test22(MyValue1[] src) { + MyValue1[] dst = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, src.length, MyValue1.DEFAULT); + System.arraycopy(src, 0, dst, 0, src.length); + return dst; + } + + @Run(test = "test22") + public void test22_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] src = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + for (int i = 0; i < len; ++i) { + src[i] = MyValue1.createWithFieldsInline(rI, rL); + } + MyValue1[] dst = test22(src); + for (int i = 0; i < len; ++i) { + Asserts.assertEQ(src[i].hash(), dst[i].hash()); + } + } + + // arraycopy() of value class array with oop fields and tightly + // coupled allocation as dest + @Test + public MyValue1[] test23(MyValue1[] src) { + MyValue1[] dst = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, src.length + 10, MyValue1.DEFAULT); + System.arraycopy(src, 0, dst, 5, src.length); + return dst; + } + + @Run(test = "test23") + public void test23_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] src = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + for (int i = 0; i < len; ++i) { + src[i] = MyValue1.createWithFieldsInline(rI, rL); + } + MyValue1[] dst = test23(src); + for (int i = 5; i < len; ++i) { + Asserts.assertEQ(src[i].hash(), dst[i].hash()); + } + } + + // arraycopy() of value class array passed as Object + @Test + public void test24(MyValue1[] src, Object dst) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Run(test = "test24") + public void test24_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] src = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + MyValue1[] dst1 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + Object[] dst2 = new Object[len]; + for (int i = 0; i < len; ++i) { + src[i] = MyValue1.createWithFieldsInline(rI, rL); + } + test24(src, dst1); + for (int i = 0; i < len; ++i) { + Asserts.assertEQ(src[i].hash(), dst1[i].hash()); + } + test24(src, dst2); + for (int i = 0; i < len; ++i) { + Asserts.assertEQ(src[i].hash(), ((MyValue1)dst2[i]).hash()); + } + } + + // short arraycopy() with no oop field + @Test + public void test25(MyValue2[] src, MyValue2[] dst) { + System.arraycopy(src, 0, dst, 0, 8); + } + + @Run(test = "test25") + public void test25_verifier() { + MyValue2[] src = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 8, MyValue2.DEFAULT); + MyValue2[] dst = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 8, MyValue2.DEFAULT); + for (int i = 0; i < 8; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test25(src, dst); + for (int i = 0; i < 8; ++i) { + Asserts.assertEQ(src[i].hash(), dst[i].hash()); + } + } + + // short arraycopy() with oop fields + @Test + public void test26(MyValue1[] src, MyValue1[] dst) { + System.arraycopy(src, 0, dst, 0, 8); + } + + @Run(test = "test26") + public void test26_verifier() { + MyValue1[] src = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 8, MyValue1.DEFAULT); + MyValue1[] dst = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 8, MyValue1.DEFAULT); + for (int i = 0; i < 8; ++i) { + src[i] = MyValue1.createWithFieldsInline(rI, rL); + } + test26(src, dst); + for (int i = 0; i < 8; ++i) { + Asserts.assertEQ(src[i].hash(), dst[i].hash()); + } + } + + // short arraycopy() with oop fields and offsets + @Test + public void test27(MyValue1[] src, MyValue1[] dst) { + System.arraycopy(src, 1, dst, 2, 6); + } + + @Run(test = "test27") + public void test27_verifier() { + MyValue1[] src = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 8, MyValue1.DEFAULT); + MyValue1[] dst = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 8, MyValue1.DEFAULT); + for (int i = 0; i < 8; ++i) { + src[i] = MyValue1.createWithFieldsInline(rI, rL); + } + test27(src, dst); + for (int i = 2; i < 8; ++i) { + Asserts.assertEQ(src[i-1].hash(), dst[i].hash()); + } + } + + // non escaping allocations + // TODO 8252027: Make sure this is optimized with ZGC + @Test + @IR(applyIf = {"UseZGC", "false"}, + failOn = {ALLOC_ARRAY_OF_MYVALUE_KLASS, LOOP, LOAD_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public MyValue2 test28() { + MyValue2[] src = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 10, MyValue2.DEFAULT); + src[0] = MyValue2.createWithFieldsInline(rI, rD); + MyValue2[] dst = (MyValue2[])src.clone(); + return dst[0]; + } + + @Run(test = "test28") + public void test28_verifier() { + MyValue2 v = MyValue2.createWithFieldsInline(rI, rD); + MyValue2 result = test28(); + Asserts.assertEQ(result.hash(), v.hash()); + } + + // non escaping allocations + // TODO 8227588: shouldn't this have the same IR matching rules as test6? + // @Test(failOn = ALLOC_OF_MYVALUE_KLASS, + ALLOC_ARRAY_OF_MYVALUE_KLASS + LOOP + LOAD_OF_ANY_KLASS + STORE_OF_ANY_KLASS + UNSTABLE_IF_TRAP, PREDICATE_TRAP) + @Test + @IR(applyIf = {"UseArrayFlattening", "true"}, + failOn = {ALLOC_ARRAY_OF_MYVALUE_KLASS, LOOP, LOAD_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + @IR(applyIf = {"UseArrayFlattening", "false"}, + failOn = {ALLOC_ARRAY_OF_MYVALUE_KLASS, LOOP, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public MyValue2 test29(MyValue2[] src) { + MyValue2[] dst = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 10, MyValue2.DEFAULT); + System.arraycopy(src, 0, dst, 0, 10); + return dst[0]; + } + + @Run(test = "test29") + public void test29_verifier() { + MyValue2[] src = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 10, MyValue2.DEFAULT); + for (int i = 0; i < 10; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + MyValue2 v = test29(src); + Asserts.assertEQ(src[0].hash(), v.hash()); + } + + // non escaping allocation with uncommon trap that needs + // eliminated value class array element as debug info + @Test + public MyValue2 test30(MyValue2[] src, boolean flag) { + MyValue2[] dst = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 10, MyValue2.DEFAULT); + System.arraycopy(src, 0, dst, 0, 10); + if (flag) { } + return dst[0]; + } + + @Run(test = "test30") + @Warmup(10000) + public void test30_verifier(RunInfo info) { + MyValue2[] src = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 10, MyValue2.DEFAULT); + for (int i = 0; i < 10; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + MyValue2 v = test30(src, !info.isWarmUp()); + Asserts.assertEQ(src[0].hash(), v.hash()); + } + + + // non escaping allocation with memory phi + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, ALLOC_ARRAY_OF_MYVALUE_KLASS, LOOP, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test31(boolean b, boolean deopt, Method m) { + MyValue2[] src = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 1, MyValue2.DEFAULT); + if (b) { + src[0] = MyValue2.createWithFieldsInline(rI, rD); + } else { + src[0] = MyValue2.createWithFieldsInline(rI+1, rD+1); + } + if (deopt) { + // uncommon trap + TestFramework.deoptimize(m); + } + return src[0].hash(); + } + + @Run(test = "test31") + public void test31_verifier(RunInfo info) { + MyValue2 v1 = MyValue2.createWithFieldsInline(rI, rD); + long result1 = test31(true, !info.isWarmUp(), info.getTest()); + Asserts.assertEQ(result1, v1.hash()); + MyValue2 v2 = MyValue2.createWithFieldsInline(rI + 1, rD + 1); + long result2 = test31(false, !info.isWarmUp(), info.getTest()); + Asserts.assertEQ(result2, v2.hash()); + } + + // Tests with Object arrays and clone/arraycopy + // clone() as stub call + @Test + public Object[] test32(Object[] va) { + return va.clone(); + } + + @Run(test = "test32") + public void test32_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + for (int i = 0; i < len; ++i) { + va[i] = MyValue1.createWithFieldsInline(rI, rL); + } + MyValue1[] result = (MyValue1[])test32(va); + for (int i = 0; i < len; ++i) { + Asserts.assertEQ(((MyValue1)result[i]).hash(), ((MyValue1)va[i]).hash()); + } + } + + @Test + public Object[] test33(Object[] va) { + return va.clone(); + } + + @Run(test = "test33") + public void test33_verifier() { + int len = Math.abs(rI) % 10; + Object[] va = new Object[len]; + for (int i = 0; i < len; ++i) { + va[i] = MyValue1.createWithFieldsInline(rI, rL); + } + Object[] result = test33(va); + for (int i = 0; i < len; ++i) { + Asserts.assertEQ(((MyValue1)result[i]).hash(), ((MyValue1)va[i]).hash()); + // Check that array has correct properties (null-ok) + result[i] = null; + } + } + + // clone() as series of loads/stores + static Object[] test34_orig = null; + + @ForceInline + public Object[] test34_helper(boolean flag) { + Object[] va = null; + if (flag) { + va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 8, MyValue1.DEFAULT); + for (int i = 0; i < va.length; ++i) { + va[i] = MyValue1.createWithFieldsDontInline(rI, rL); + } + } else { + va = new Object[8]; + } + return va; + } + + @Test + public Object[] test34(boolean flag) { + Object[] va = test34_helper(flag); + test34_orig = va; + return va.clone(); + } + + @Run(test = "test34") + public void test34_verifier(RunInfo info) { + test34(false); + for (int i = 0; i < 10; i++) { // make sure we do deopt + Object[] result = test34(true); + verify(test34_orig, result); + // Check that array has correct properties (null-free) + try { + result[0] = null; + throw new RuntimeException("Should throw NullPointerException"); + } catch (NullPointerException e) { + // Expected + } + } + if (compile_and_run_again_if_deoptimized(info)) { + Object[] result = test34(true); + verify(test34_orig, result); + // Check that array has correct properties (null-free) + try { + result[0] = null; + throw new RuntimeException("Should throw NullPointerException"); + } catch (NullPointerException e) { + // Expected + } + } + } + + // arraycopy() of value class array of unknown size + @Test + public void test35(Object src, Object dst, int len) { + System.arraycopy(src, 0, dst, 0, len); + } + + @Run(test = "test35") + public void test35_verifier(RunInfo info) { + int len = Math.abs(rI) % 10; + MyValue1[] src = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + MyValue1[] dst1 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + Object[] dst2 = new Object[len]; + for (int i = 0; i < len; ++i) { + src[i] = MyValue1.createWithFieldsInline(rI, rL); + } + test35(src, dst1, src.length); + verify(src, dst1); + test35(src, dst2, src.length); + verify(src, dst2); + if (compile_and_run_again_if_deoptimized(info)) { + test35(src, dst1, src.length); + verify(src, dst1); + } + } + + @Test + public void test36(Object src, MyValue2[] dst) { + System.arraycopy(src, 0, dst, 0, dst.length); + } + + @Run(test = "test36") + public void test36_verifier(RunInfo info) { + int len = Math.abs(rI) % 10; + MyValue2[] src = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, len, MyValue2.DEFAULT); + MyValue2[] dst = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, len, MyValue2.DEFAULT); + for (int i = 0; i < len; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test36(src, dst); + verify(src, dst); + if (compile_and_run_again_if_deoptimized(info)) { + test36(src, dst); + verify(src, dst); + } + } + + @Test + public void test37(MyValue2[] src, Object dst) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Run(test = "test37") + public void test37_verifier(RunInfo info) { + int len = Math.abs(rI) % 10; + MyValue2[] src = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, len, MyValue2.DEFAULT); + MyValue2[] dst = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, len, MyValue2.DEFAULT); + for (int i = 0; i < len; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test37(src, dst); + verify(src, dst); + if (compile_and_run_again_if_deoptimized(info)) { + test37(src, dst); + verify(src, dst); + } + } + + @Test + public void test38(Object src, MyValue2[] dst) { + System.arraycopy(src, 0, dst, 0, dst.length); + } + + @Run(test = "test38") + @Warmup(1) // Avoid early compilation + public void test38_verifier(RunInfo info) { + int len = Math.abs(rI) % 10; + Object[] src = new Object[len]; + MyValue2[] dst = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, len, MyValue2.DEFAULT); + for (int i = 0; i < len; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test38(src, dst); + verify(dst, src); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertDeoptimizedByC2(m); + TestFramework.compile(m, CompLevel.C2); + test38(src, dst); + verify(dst, src); + TestFramework.assertCompiledByC2(m); + } + } + + @Test + public void test39(MyValue2[] src, Object dst) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Run(test = "test39") + public void test39_verifier(RunInfo info) { + int len = Math.abs(rI) % 10; + MyValue2[] src = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, len, MyValue2.DEFAULT); + Object[] dst = new Object[len]; + for (int i = 0; i < len; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test39(src, dst); + verify(src, dst); + if (compile_and_run_again_if_deoptimized(info)) { + test39(src, dst); + verify(src, dst); + } + } + + @Test + public void test40(Object[] src, Object dst) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Run(test = "test40") + @Warmup(1) // Avoid early compilation + public void test40_verifier(RunInfo info) { + int len = Math.abs(rI) % 10; + Object[] src = new Object[len]; + MyValue2[] dst = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, len, MyValue2.DEFAULT); + for (int i = 0; i < len; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test40(src, dst); + verify(dst, src); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertDeoptimizedByC2(m); + TestFramework.compile(m, CompLevel.C2); + test40(src, dst); + verify(dst, src); + TestFramework.assertCompiledByC2(m); + } + } + + @Test + public void test41(Object src, Object[] dst) { + System.arraycopy(src, 0, dst, 0, dst.length); + } + + @Run(test = "test41") + public void test41_verifier(RunInfo info) { + int len = Math.abs(rI) % 10; + MyValue2[] src = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, len, MyValue2.DEFAULT); + Object[] dst = new Object[len]; + for (int i = 0; i < len; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test41(src, dst); + verify(src, dst); + if (compile_and_run_again_if_deoptimized(info)) { + test41(src, dst); + verify(src, dst); + } + } + + @Test + public void test42(Object[] src, Object[] dst) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Run(test = "test42") + public void test42_verifier(RunInfo info) { + int len = Math.abs(rI) % 10; + Object[] src = new Object[len]; + Object[] dst = new Object[len]; + for (int i = 0; i < len; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test42(src, dst); + verify(src, dst); + if (!info.isWarmUp()) { + TestFramework.assertCompiledByC2(info.getTest()); + } + } + + // short arraycopy()'s + @Test + public void test43(Object src, Object dst) { + System.arraycopy(src, 0, dst, 0, 8); + } + + @Run(test = "test43") + public void test43_verifier(RunInfo info) { + MyValue1[] src = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 8, MyValue1.DEFAULT); + MyValue1[] dst = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 8, MyValue1.DEFAULT); + for (int i = 0; i < 8; ++i) { + src[i] = MyValue1.createWithFieldsInline(rI, rL); + } + test43(src, dst); + verify(src, dst); + if (compile_and_run_again_if_deoptimized(info)) { + test43(src, dst); + verify(src, dst); + } + } + + @Test + public void test44(Object src, MyValue2[] dst) { + System.arraycopy(src, 0, dst, 0, 8); + } + + @Run(test = "test44") + public void test44_verifier(RunInfo info) { + MyValue2[] src = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 8, MyValue2.DEFAULT); + MyValue2[] dst = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 8, MyValue2.DEFAULT); + for (int i = 0; i < 8; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test44(src, dst); + verify(src, dst); + if (compile_and_run_again_if_deoptimized(info)) { + test44(src, dst); + verify(src, dst); + } + } + + @Test + public void test45(MyValue2[] src, Object dst) { + System.arraycopy(src, 0, dst, 0, 8); + } + + @Run(test = "test45") + public void test45_verifier(RunInfo info) { + MyValue2[] src = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 8, MyValue2.DEFAULT); + MyValue2[] dst = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 8, MyValue2.DEFAULT); + for (int i = 0; i < 8; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test45(src, dst); + verify(src, dst); + if (compile_and_run_again_if_deoptimized(info)) { + test45(src, dst); + verify(src, dst); + } + } + + @Test + public void test46(Object[] src, MyValue2[] dst) { + System.arraycopy(src, 0, dst, 0, 8); + } + + @Run(test = "test46") + @Warmup(1) // Avoid early compilation + public void test46_verifier(RunInfo info) { + Object[] src = new Object[8]; + MyValue2[] dst = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 8, MyValue2.DEFAULT); + for (int i = 0; i < 8; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test46(src, dst); + verify(dst, src); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertDeoptimizedByC2(m); + TestFramework.compile(m, CompLevel.C2); + test46(src, dst); + verify(dst, src); + TestFramework.assertCompiledByC2(m); + } + } + + @Test + public void test47(MyValue2[] src, Object[] dst) { + System.arraycopy(src, 0, dst, 0, 8); + } + + @Run(test = "test47") + public void test47_verifier(RunInfo info) { + MyValue2[] src = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 8, MyValue2.DEFAULT); + Object[] dst = new Object[8]; + for (int i = 0; i < 8; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test47(src, dst); + verify(src, dst); + if (compile_and_run_again_if_deoptimized(info)) { + test47(src, dst); + verify(src, dst); + } + } + + @Test + public void test48(Object[] src, Object dst) { + System.arraycopy(src, 0, dst, 0, 8); + } + + @Run(test = "test48") + @Warmup(1) // Avoid early compilation + public void test48_verifier(RunInfo info) { + Object[] src = new Object[8]; + MyValue2[] dst = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 8, MyValue2.DEFAULT); + for (int i = 0; i < 8; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test48(src, dst); + verify(dst, src); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertDeoptimizedByC2(m); + TestFramework.compile(m, CompLevel.C2); + test48(src, dst); + verify(dst, src); + TestFramework.assertCompiledByC2(m); + } + } + + @Test + public void test49(Object src, Object[] dst) { + System.arraycopy(src, 0, dst, 0, 8); + } + + @Run(test = "test49") + public void test49_verifier(RunInfo info) { + MyValue2[] src = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 8, MyValue2.DEFAULT); + Object[] dst = new Object[8]; + for (int i = 0; i < 8; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test49(src, dst); + verify(src, dst); + if (compile_and_run_again_if_deoptimized(info)) { + test49(src, dst); + verify(src, dst); + } + } + + @Test + public void test50(Object[] src, Object[] dst) { + System.arraycopy(src, 0, dst, 0, 8); + } + + @Run(test = "test50") + public void test50_verifier(RunInfo info) { + Object[] src = new Object[8]; + Object[] dst = new Object[8]; + for (int i = 0; i < 8; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test50(src, dst); + verify(src, dst); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertCompiledByC2(m); + } + } + + @Test + public MyValue1[] test51(MyValue1[] va) { + Object[] res = Arrays.copyOf(va, va.length, MyValue1[].class); + return (MyValue1[]) res; + } + + @Run(test = "test51") + public void test51_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + for (int i = 0; i < len; ++i) { + va[i] = MyValue1.createWithFieldsInline(rI, rL); + } + MyValue1[] result = test51(va); + verify(va, result); + } + + static final MyValue1[] test52_va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 8, MyValue1.DEFAULT); + + @Test + public MyValue1[] test52() { + Object[] res = Arrays.copyOf(test52_va, 8, MyValue1[].class); + return (MyValue1[]) res; + } + + @Run(test = "test52") + public void test52_verifier() { + for (int i = 0; i < 8; ++i) { + test52_va[i] = MyValue1.createWithFieldsInline(rI, rL); + } + MyValue1[] result = test52(); + verify(test52_va, result); + } + + @Test + public MyValue1[] test53(Object[] va) { + Object[] res = Arrays.copyOf(va, va.length, MyValue1[].class); + return (MyValue1[]) res; + } + + @Run(test = "test53") + public void test53_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + for (int i = 0; i < len; ++i) { + va[i] = MyValue1.createWithFieldsInline(rI, rL); + } + MyValue1[] result = test53(va); + verify(result, va); + } + + @Test + public Object[] test54(MyValue1[] va) { + return Arrays.copyOf(va, va.length, Object[].class); + } + + @Run(test = "test54") + public void test54_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + for (int i = 0; i < len; ++i) { + va[i] = MyValue1.createWithFieldsInline(rI, rL); + } + Object[] result = test54(va); + verify(va, result); + } + + @Test + public Object[] test55(Object[] va) { + return Arrays.copyOf(va, va.length, Object[].class); + } + + @Run(test = "test55") + public void test55_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + for (int i = 0; i < len; ++i) { + va[i] = MyValue1.createWithFieldsInline(rI, rL); + } + Object[] result = test55(va); + verify(va, result); + } + + @Test + public MyValue1[] test56(Object[] va) { + Object[] res = Arrays.copyOf(va, va.length, MyValue1[].class); + return (MyValue1[]) res; + } + + @Run(test = "test56") + public void test56_verifier() { + int len = Math.abs(rI) % 10; + Object[] va = new Object[len]; + for (int i = 0; i < len; ++i) { + va[i] = MyValue1.createWithFieldsInline(rI, rL); + } + MyValue1[] result = test56(va); + verify(result, va); + } + + @Test + public Object[] test57(Object[] va, Class klass) { + return Arrays.copyOf(va, va.length, klass); + } + + @Run(test = "test57") + public void test57_verifier() { + int len = Math.abs(rI) % 10; + Object[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + for (int i = 0; i < len; ++i) { + va[i] = MyValue1.createWithFieldsInline(rI, rL); + } + Object[] result = test57(va, MyValue1[].class); + verify(va, result); + } + + @Test + public Object[] test58(MyValue1[] va, Class klass) { + return Arrays.copyOf(va, va.length, klass); + } + + @Run(test = "test58") + public void test58_verifier(RunInfo info) { + int len = Math.abs(rI) % 10; + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + for (int i = 0; i < len; ++i) { + va[i] = MyValue1.createWithFieldsInline(rI, rL); + } + for (int i = 0; i < 10; i++) { + Object[] result = test58(va, MyValue1[].class); + verify(va, result); + } + if (compile_and_run_again_if_deoptimized(info)) { + Object[] result = test58(va, MyValue1[].class); + verify(va, result); + } + } + + @Test + public Object[] test59(MyValue1[] va) { + return Arrays.copyOf(va, va.length+1, va.getClass()); + } + + @Run(test = "test59") + public void test59_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] va = (MyValue1[])ValueClass.newNullableAtomicArray(MyValue1.class, len); + MyValue1[] verif = (MyValue1[])ValueClass.newNullableAtomicArray(MyValue1.class, len + 1); + for (int i = 0; i < len; ++i) { + va[i] = MyValue1.createWithFieldsInline(rI, rL); + verif[i] = va[i]; + } + Object[] result = test59(va); + verify(verif, result); + } + + @Test + public Object[] test60(Object[] va, Class klass) { + return Arrays.copyOf(va, va.length+1, klass); + } + + @Run(test = "test60") + public void test60_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] va = (MyValue1[])ValueClass.newNullableAtomicArray(MyValue1.class, len); + MyValue1[] verif = (MyValue1[])ValueClass.newNullableAtomicArray(MyValue1.class, len + 1); + for (int i = 0; i < len; ++i) { + va[i] = MyValue1.createWithFieldsInline(rI, rL); + verif[i] = (MyValue1)va[i]; + } + Object[] result = test60(va, va.getClass()); + verify(verif, result); + } + + @Test + public Object[] test61(Object[] va, Class klass) { + return Arrays.copyOf(va, va.length+1, klass); + } + + @Run(test = "test61") + public void test61_verifier() { + int len = Math.abs(rI) % 10; + Object[] va = new NonValueClass[len]; + for (int i = 0; i < len; ++i) { + va[i] = new NonValueClass(rI); + } + Object[] result = test61(va, NonValueClass[].class); + for (int i = 0; i < va.length; ++i) { + Asserts.assertEQ(va[i], result[i]); + } + } + + @ForceInline + public Object[] test62_helper(int i, MyValue1[] va, NonValueClass[] oa) { + Object[] arr = null; + if (i == 10) { + arr = oa; + } else { + arr = va; + } + return arr; + } + + @Test + public Object[] test62(MyValue1[] va, NonValueClass[] oa) { + int i = 0; + for (; i < 10; i++); + + Object[] arr = test62_helper(i, va, oa); + + return Arrays.copyOf(arr, arr.length+1, arr.getClass()); + } + + @Run(test = "test62") + public void test62_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + NonValueClass[] oa = new NonValueClass[len]; + for (int i = 0; i < len; ++i) { + oa[i] = new NonValueClass(rI); + } + test62_helper(42, va, oa); + Object[] result = test62(va, oa); + for (int i = 0; i < va.length; ++i) { + Asserts.assertEQ(oa[i], result[i]); + } + } + + @ForceInline + public Object[] test63_helper(int i, MyValue1[] va, NonValueClass[] oa) { + Object[] arr = null; + if (i == 10) { + arr = va; + } else { + arr = oa; + } + return arr; + } + + @Test + public Object[] test63(MyValue1[] va, NonValueClass[] oa) { + int i = 0; + for (; i < 10; i++); + + Object[] arr = test63_helper(i, va, oa); + + return Arrays.copyOf(arr, arr.length+1, arr.getClass()); + } + + @Run(test = "test63") + public void test63_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] va = (MyValue1[])ValueClass.newNullableAtomicArray(MyValue1.class, len); + MyValue1[] verif = (MyValue1[])ValueClass.newNullableAtomicArray(MyValue1.class, len + 1); + for (int i = 0; i < len; ++i) { + va[i] = MyValue1.createWithFieldsInline(rI, rL); + verif[i] = va[i]; + } + NonValueClass[] oa = new NonValueClass[len]; + test63_helper(42, va, oa); + Object[] result = test63(va, oa); + verify(verif, result); + } + + // Test initialization of value class arrays: small array + @Test + public MyValue1[] test64() { + return (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 8, MyValue1.DEFAULT); + } + + @Run(test = "test64") + public void test64_verifier() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 8, MyValue1.DEFAULT); + MyValue1[] var = test64(); + for (int i = 0; i < 8; ++i) { + Asserts.assertEQ(va[i].hashPrimitive(), var[i].hashPrimitive()); + } + } + + // Test initialization of value class arrays: large array + @Test + public MyValue1[] test65() { + return (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 32, MyValue1.DEFAULT); + } + + @Run(test = "test65") + public void test65_verifier() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 32, MyValue1.DEFAULT); + MyValue1[] var = test65(); + for (int i = 0; i < 32; ++i) { + Asserts.assertEQ(va[i].hashPrimitive(), var[i].hashPrimitive()); + } + } + + // Check init store elimination + @Test + @IR(counts = {ALLOC_ARRAY_OF_MYVALUE_KLASS, "= 1"}) + public MyValue1[] test66(MyValue1 vt) { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + va[0] = vt; + return va; + } + + @Run(test = "test66") + public void test66_verifier() { + MyValue1 vt = MyValue1.createWithFieldsDontInline(rI, rL); + MyValue1[] va = test66(vt); + Asserts.assertEQ(va[0].hashPrimitive(), vt.hashPrimitive()); + } + + // Zeroing elimination and arraycopy + @Test + public MyValue1[] test67(MyValue1[] src) { + MyValue1[] dst = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 16, MyValue1.DEFAULT); + System.arraycopy(src, 0, dst, 0, 13); + return dst; + } + + @Run(test = "test67") + public void test67_verifier() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 16, MyValue1.DEFAULT); + MyValue1[] var = test67(va); + for (int i = 0; i < 16; ++i) { + Asserts.assertEQ(va[i].hashPrimitive(), var[i].hashPrimitive()); + } + } + + // A store with an all-zero value can be eliminated + @Test + public MyValue1[] test68() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 2, MyValue1.DEFAULT); + va[0] = va[1]; + return va; + } + + @Run(test = "test68") + public void test68_verifier() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 2, MyValue1.DEFAULT); + MyValue1[] var = test68(); + for (int i = 0; i < 2; ++i) { + Asserts.assertEQ(va[i].hashPrimitive(), var[i].hashPrimitive()); + } + } + + // Requires individual stores to init array + @Test + public MyValue1[] test69(MyValue1 vt) { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 4, MyValue1.DEFAULT); + va[0] = vt; + va[3] = vt; + return va; + } + + @Run(test = "test69") + public void test69_verifier() { + MyValue1 vt = MyValue1.createWithFieldsDontInline(rI, rL); + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 4, MyValue1.DEFAULT); + va[0] = vt; + va[3] = vt; + MyValue1[] var = test69(vt); + for (int i = 0; i < va.length; ++i) { + Asserts.assertEQ(va[i].hashPrimitive(), var[i].hashPrimitive()); + } + } + + // Same as test68 but store is further away from allocation + @Test + public MyValue1[] test70(MyValue1[] other) { + other[1] = other[0]; + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 2, MyValue1.DEFAULT); + other[0] = va[1]; + va[0] = va[1]; + return va; + } + + @Run(test = "test70") + public void test70_verifier() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 2, MyValue1.DEFAULT); + MyValue1[] var = test70(va); + for (int i = 0; i < 2; ++i) { + Asserts.assertEQ(va[i].hashPrimitive(), var[i].hashPrimitive()); + } + } + + // EA needs to consider oop fields in flattened arrays + @Test + public void test71() { + int len = 10; + MyValue2[] src = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, len, MyValue2.DEFAULT); + MyValue2[] dst = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, len, MyValue2.DEFAULT); + for (int i = 0; i < len; ++i) { + src[i] = MyValue2.createWithFieldsDontInline(rI+i, rD+i); + } + System.arraycopy(src, 0, dst, 0, src.length); + for (int i = 0; i < len; ++i) { + Asserts.assertEQ(src[i].hash(), dst[i].hash()); + } + } + + @Run(test = "test71") + public void test71_verifier() { + test71(); + } + + // Test EA with leaf call to 'store_unknown_inline' + @Test + public void test72(Object[] o, boolean b, Object element) { + Object[] arr1 = new Object[10]; + Object[] arr2 = new Object[10]; + if (b) { + arr1 = o; + } + arr1[0] = element; + arr2[0] = element; + } + + @Run(test = "test72") + public void test72_verifier() { + Object[] arr = new Object[1]; + Object elem = new Object(); + test72(arr, true, elem); + test72(arr, false, elem); + } + + @Test + public void test73(Object[] oa, MyValue1 v, Object o) { + // TestLWorld.test38 use a C1 Phi node for the array. This test + // adds the case where the stored value is a C1 Phi node. + Object o2 = (o == null) ? v : o; + oa[0] = v; // The stored value is known to be flattenable + oa[1] = o; // The stored value may be flattenable + oa[2] = o2; // The stored value may be flattenable (a C1 Phi node) + oa[0] = oa; // The stored value is known to be not flattenable (an Object[]) + } + + @Run(test = "test73") + public void test73_verifier() { + MyValue1 v0 = MyValue1.createWithFieldsDontInline(rI, rL); + MyValue1 v1 = MyValue1.createWithFieldsDontInline(rI+1, rL+1); + MyValue1[] arr = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 3, MyValue1.DEFAULT); + try { + test73(arr, v0, v1); + throw new RuntimeException("ArrayStoreException expected"); + } catch (ArrayStoreException e) { + // expected + } + Asserts.assertEQ(arr[0].hash(), v0.hash()); + Asserts.assertEQ(arr[1].hash(), v1.hash()); + Asserts.assertEQ(arr[2].hash(), v1.hash()); + } + + public static void test74Callee(MyValue1[] va) { } + + // Tests invoking unloaded method with value class array in signature + @Test + public void test74(MethodHandle m, MyValue1[] va) throws Throwable { + m.invoke(va); + } + + @Run(test = "test74") + @Warmup(0) + public void test74_verifier() throws Throwable { + MethodHandle m = MethodHandles.lookup().findStatic(TestArrays.class, "test74Callee", MethodType.methodType(void.class, MyValue1[].class)); + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 0, MyValue1.DEFAULT); + test74(m, va); + } + + // Some more array clone tests + @ForceInline + public Object[] test75_helper(int i, MyValue1[] va, NonValueClass[] oa) { + Object[] arr = null; + if (i == 10) { + arr = oa; + } else { + arr = va; + } + return arr; + } + + @Test + public Object[] test75(MyValue1[] va, NonValueClass[] oa) { + int i = 0; + for (; i < 10; i++); + + Object[] arr = test75_helper(i, va, oa); + return arr.clone(); + } + + @Run(test = "test75") + public void test75_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + NonValueClass[] oa = new NonValueClass[len]; + for (int i = 0; i < len; ++i) { + oa[i] = new NonValueClass(rI); + } + test75_helper(42, va, oa); + Object[] result = test75(va, oa); + + for (int i = 0; i < va.length; ++i) { + Asserts.assertEQ(oa[i], result[i]); + // Check that array has correct properties (null-ok) + result[i] = null; + } + } + + @ForceInline + public Object[] test76_helper(int i, MyValue1[] va, NonValueClass[] oa) { + Object[] arr = null; + if (i == 10) { + arr = va; + } else { + arr = oa; + } + return arr; + } + + @Test + public Object[] test76(MyValue1[] va, NonValueClass[] oa) { + int i = 0; + for (; i < 10; i++); + + Object[] arr = test76_helper(i, va, oa); + return arr.clone(); + } + + @Run(test = "test76") + public void test76_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + MyValue1[] verif = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + for (int i = 0; i < len; ++i) { + va[i] = MyValue1.createWithFieldsInline(rI, rL); + verif[i] = va[i]; + } + NonValueClass[] oa = new NonValueClass[len]; + test76_helper(42, va, oa); + Object[] result = test76(va, oa); + verify(verif, result); + // Check that array has correct properties (null-free) + if (len > 0) { + try { + result[0] = null; + throw new RuntimeException("Should throw NullPointerException"); + } catch (NullPointerException e) { + // Expected + } + } + } + + @Test + public void test77() { + MyValue1 v0 = MyValue1.createWithFieldsDontInline(rI, rL); + MyValue1 v1 = MyValue1.createWithFieldsDontInline(rI+1, rL+1); + MyValue1[] arr = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + + Object[] oa = arr; + Object o1 = v1; + Object o = (o1 == null) ? v0 : o1; + + oa[0] = o; // For C1, due to IfOp optimization, the declared_type of o becomes NULL. + + Asserts.assertEQ(arr[0].hash(), v1.hash()); + } + + + @Run(test = "test77") + public void test77_verifier() { + test77(); + } + + @Test + public long test78(MyValue1 v, int n) { + long x = 0; + for (int i = 0; i < n; i++) { + } + + MyValue1[] a = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, n, MyValue1.DEFAULT); + a[0] = v; + for (int i = 0; i 0) { + return; + } + } + } + + @Run(test = "test143") + @Warmup(0) + public void test143_verifier() { + test143(); + } + + // Same as test143 but with two flat array checks that are unswitched + @Test + public void test144() { + MyInterface143[] arr1 = array143; + MyInterface143[] arr2 = array143; + int tmp1 = arr1.length; + int tmp2 = arr2.length; + for (int i = 0; i < len143; i++) { + if (arr1[i].hash() > 0 && arr2[i].hash() > 0) { + return; + } + } + } + + @Run(test = "test144") + @Warmup(0) + public void test144_verifier() { + test144(); + } + + // Test that array load slow path correctly initializes non-flattened field of empty value class + @Test + public Object test145(Object[] array) { + return array[0]; + } + + @Run(test = "test145") + public void test145_verifier() { + Object[] array = (EmptyContainer[])ValueClass.newNullRestrictedNonAtomicArray(EmptyContainer.class, 1, new EmptyContainer()); + EmptyContainer empty = (EmptyContainer)test145(array); + Asserts.assertEquals(empty, new EmptyContainer()); + } + + // Test that non-flattened array does not block scalarization + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, ALLOC_ARRAY_OF_MYVALUE_KLASS, LOOP, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS}) + public void test146(boolean b) { + MyValue2 vt = MyValue2.createWithFieldsInline(rI, rD); + MyValue2[] array = { vt }; + if (b) { + for (int i = 0; i < 10; ++i) { + if (array != array) { + array = null; + } + } + } + } + + @Run(test = "test146") + @Warmup(50000) + public void test146_verifier() { + test146(true); + } + + // Test that non-flattened array does not block scalarization + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, ALLOC_ARRAY_OF_MYVALUE_KLASS, LOOP, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS}) + public int test147(boolean deopt) { + // Both vt and array should be scalarized + MyValue2 vt = MyValue2.createWithFieldsInline(rI, rD); + MyValue2[] array = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 1, MyValue2.DEFAULT); + + // Delay scalarization to after loop opts + boolean store = false; + for (int i = 0; i < 5; ++i) { + if (i == 1) { + store = true; + } + } + if (store) { + array[0] = vt; + } + + if (deopt) { + // Uncommon trap referencing array + return array[0].x + 42; + } + return array[0].x; + } + + @Run(test = "test147") + @Warmup(50000) + public void test147_verifier(RunInfo info) { + int res = test147(!info.isWarmUp()); + Asserts.assertEquals(res, MyValue2.createWithFieldsInline(rI, rD).x + (info.isWarmUp() ? 0 : 42)); + } + + // Test that correct basic types are used when folding field + // loads from a scalar replaced array through an arraycopy. + @Test + public void test148(MyValue1 vt) { + MyValue1[] src = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + MyValue1[] dst = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + src[0] = vt; + System.arraycopy(src, 0, dst, 0, 1); + if (src[0].hash() != dst[0].hash()) { + throw new RuntimeException("Unexpected hash"); + } + } + + @Run(test = "test148") + public void test148_verifier() { + test148(MyValue1.createWithFieldsInline(rI, rL)); + } + + // Abstract class without any value class implementers + static abstract class MyAbstract149 { + public abstract int get(); + } + + static class TestClass149 extends MyAbstract149 { + final int x; + + public int get() { return x; }; + + public TestClass149(int x) { + this.x = x; + } + } + + // Test OSR compilation with array known to be not null-free/flat + @Test + public int test149(MyAbstract149[] array) { + int res = 0; + // Trigger OSR compilation + for (int i = 0; i < 10_000; ++i) { + res += array[i % 10].get(); + } + return res; + } + + @Run(test = "test149") + public void test149_verifier() { + TestClass149[] array = new TestClass149[10]; + for (int i = 0; i < 10; ++i) { + array[i] = new TestClass149(i); + } + Asserts.assertEquals(test149(array), 45000); + } + + @LooselyConsistentValue + static value class Test150Value { + Object s; + + public Test150Value(String s) { + this.s = s; + } + } + + // Test that optimizing a checkcast of a load from a flat array works as expected + @Test + static String test150(String s) { + Test150Value[] array = (Test150Value[])ValueClass.newNullRestrictedNonAtomicArray(Test150Value.class, 1, new Test150Value("test")); + array[0] = new Test150Value(s); + return (String)array[0].s; + } + + @Run(test = "test150") + public void test150_verifier() { + Asserts.assertEquals(test150("bla"), "bla"); + } + + static value class Test151Value { + byte b; + String s; + + Test151Value(byte b, String s) { + this.b = b; + this.s = s; + } + + static final Test151Value DEFAULT = new Test151Value((byte) 1, "hello"); + + static final Test151Value[] ARRAY = (Test151Value[]) ValueClass.newNullRestrictedAtomicArray(Test151Value.class, 100, DEFAULT); + } + + @Test + @IR(applyIf = {"InlineTypeReturnedAsFields", "true"}, + failOn = {ALLOC_OF_MYVALUE_KLASS,}) + static Test151Value test151(int i) { + return Test151Value.ARRAY[i]; + } + + @Run(test = "test151") + public void test151_verifier() { + Asserts.assertEquals(Test151Value.DEFAULT, test151(rI & 15)); + } + + // Make sure this can't be flattened + static value class MyValue152Inline { + long l1 = rL; + long l2 = rL; + } + + @LooselyConsistentValue + static value class MyValue152 { + double d = rD; + + @Strict + @NullRestricted + MyValue152Inline val = new MyValue152Inline(); // Not flat + } + + // Test that EA works for null-free arrays + @Test + // TODO 8350865 Scalar replacement does not work well for flat arrays + //@IR(applyIf = {"InlineTypeReturnedAsFields", "true"}, + // failOn = {ALLOC, ALLOCA}) + public MyValue152 test152() { + MyValue152[] array = (MyValue152[])ValueClass.newNullRestrictedNonAtomicArray(MyValue152.class, 1, new MyValue152()); + return array[0]; + } + + @Run(test = "test152") + public void test152_verifier() { + Asserts.assertEquals(test152(), new MyValue152()); + } + + @LooselyConsistentValue + static value class MyValue153 { + @Strict + @NullRestricted + MyValue152Inline val = new MyValue152Inline(); // Not flat + } + + // Same as test152 but triggers a slightly different asserts + @Test + // TODO 8350865 Scalar replacement does not work well for flat arrays + //@IR(applyIf = {"InlineTypeReturnedAsFields", "true"}, + // failOn = {ALLOC, ALLOCA}) + public MyValue153 test153() { + MyValue153[] array = (MyValue153[])ValueClass.newNullRestrictedNonAtomicArray(MyValue153.class, 1, new MyValue153()); + return array[0]; + } + + @Run(test = "test153") + public void test153_verifier() { + Asserts.assertEquals(test153(), new MyValue153()); + } + + // Same as test152 but triggers an incorrect result + @Test + // TODO 8350865 Scalar replacement does not work well for flat arrays + //@IR(failOn = {ALLOC, ALLOCA_G, LOAD, STORE, TRAP}) + public double test154() { + MyValue152[] array = (MyValue152[])ValueClass.newNullRestrictedNonAtomicArray(MyValue152.class, 1, new MyValue152()); + return array[0].d; + } + + @Run(test = "test154") + public void test154_verifier() { + Asserts.assertEquals(test154(), rD); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestBasicFunctionality.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestBasicFunctionality.java new file mode 100644 index 00000000000..656c4fd4bfd --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestBasicFunctionality.java @@ -0,0 +1,1251 @@ +/* + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.*; +import jdk.test.lib.Asserts; + +import java.lang.reflect.Method; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.ALLOC_ARRAY_OF_MYVALUE_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.ALLOC_OF_MYVALUE_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.LOAD_OF_ANY_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.STORE_OF_ANY_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypes.*; + +import static compiler.lib.ir_framework.IRNode.LOOP; +import static compiler.lib.ir_framework.IRNode.PREDICATE_TRAP; +import static compiler.lib.ir_framework.IRNode.SCOPE_OBJECT; +import static compiler.lib.ir_framework.IRNode.UNSTABLE_IF_TRAP; + +/* + * @test + * @key randomness + * @bug 8327695 + * @summary Test the basic value class implementation in C2. + * @requires (os.simpleArch == "x64" | os.simpleArch == "aarch64") + * @library /test/lib / + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm/timeout=300 compiler.valhalla.inlinetypes.TestBasicFunctionality + */ + +@ForceCompileClassInitializer +public class TestBasicFunctionality { + + public static void main(String[] args) { + InlineTypes.getFramework() + .addScenarios(InlineTypes.DEFAULT_SCENARIOS) + .addHelperClasses(MyValue1.class, + MyValue2.class, + MyValue2Inline.class, + MyValue3.class, + MyValue3Inline.class) + .start(); + } + + protected long hash() { + return hash(rI, rL); + } + + protected long hash(int x, long y) { + return MyValue1.createWithFieldsInline(x, y).hash(); + } + + @DontInline + static void call() {} + + // Receive value class through call to interpreter + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test1() { + MyValue1 v = MyValue1.createWithFieldsDontInline(rI, rL); + return v.hash(); + } + + @Run(test = "test1") + public void test1_verifier() { + long result = test1(); + Asserts.assertEQ(result, hash()); + } + + // Receive value object from interpreter via parameter + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test2(MyValue1 v) { + return v.hash(); + } + + @Run(test = "test2") + public void test2_verifier() { + MyValue1 v = MyValue1.createWithFieldsDontInline(rI, rL); + long result = test2(v); + Asserts.assertEQ(result, hash()); + } + + // Return incoming value object without accessing fields + @Test + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "true"}, + counts = {ALLOC_OF_MYVALUE_KLASS, "= 1", STORE_OF_ANY_KLASS, "= 19"}, + failOn = {LOAD_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "false"}, + failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public MyValue1 test3(MyValue1 v) { + return v; + } + + @Run(test = "test3") + public void test3_verifier() { + MyValue1 v1 = MyValue1.createWithFieldsDontInline(rI, rL); + MyValue1 v2 = test3(v1); + Asserts.assertEQ(v1.x, v2.x); + Asserts.assertEQ(v1.y, v2.y); + } + + // Create a value object in compiled code and only use fields. + // Allocation should go away because value object does not escape. + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test4() { + MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); + return v.hash(); + } + + @Run(test = "test4") + public void test4_verifier() { + long result = test4(); + Asserts.assertEQ(result, hash()); + } + + // Create a value object in compiled code and pass it to + // an inlined compiled method via a call. + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test5() { + MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); + return test5Inline(v); + } + + @ForceInline + public long test5Inline(MyValue1 v) { + return v.hash(); + } + + @Run(test = "test5") + public void test5_verifier() { + long result = test5(); + Asserts.assertEQ(result, hash()); + } + + // Create a value object in compiled code and pass it to + // the interpreter via a call. + @Test + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "true"}, + counts = {ALLOC_OF_MYVALUE_KLASS, "<= 1"}, // 1 MyValue2 allocation (if not the all-zero value) + failOn = {LOAD_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "false"}, + counts = {ALLOC_OF_MYVALUE_KLASS, "<= 2"}, // 1 MyValue1 and 1 MyValue2 allocation (if not the all-zero value) + failOn = {LOAD_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test6() { + MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); + // Pass to interpreter + return v.hashInterpreted(); + } + + @Run(test = "test6") + public void test6_verifier() { + long result = test6(); + Asserts.assertEQ(result, hash()); + } + + // Create a value object in compiled code and pass it to + // the interpreter by returning. + @Test + @IR(counts = {ALLOC_OF_MYVALUE_KLASS, "<= 2"}, + failOn = {LOAD_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public MyValue1 test7(int x, long y) { + return MyValue1.createWithFieldsInline(x, y); + } + + @Run(test = "test7") + public void test7_verifier() { + MyValue1 v = test7(rI, rL); + Asserts.assertEQ(v.hash(), hash()); + } + + // Merge value objects created from two branches + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test8(boolean b) { + MyValue1 v; + if (b) { + v = MyValue1.createWithFieldsInline(rI, rL); + } else { + v = MyValue1.createWithFieldsDontInline(rI + 1, rL + 1); + } + return v.hash(); + } + + @Run(test = "test8") + public void test8_verifier() { + Asserts.assertEQ(test8(true), hash()); + Asserts.assertEQ(test8(false), hash(rI + 1, rL + 1)); + } + +static MyValue1 tmp = null; + // Merge value objects created from two branches + @Test + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "true"}, + counts = {ALLOC_OF_MYVALUE_KLASS, "= 1", LOAD_OF_ANY_KLASS, "= 19", + STORE_OF_ANY_KLASS, "= 3"}, // InitializeNode::coalesce_subword_stores merges stores + failOn = {UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "false"}, + counts = {ALLOC_OF_MYVALUE_KLASS, "= 2", STORE_OF_ANY_KLASS, "= 19"}, + failOn = {LOAD_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public MyValue1 test9(boolean b, int localrI, long localrL) { + MyValue1 v; + if (b) { + // Value object is not allocated + // Do not use rI/rL directly here as null values may cause + // some redundant null initializations to be optimized out + // and matching to fail. + v = MyValue1.createWithFieldsInline(localrI, localrL); + v.hashInterpreted(); + } else { + // Value object is allocated by the callee + v = MyValue1.createWithFieldsDontInline(rI + 1, rL + 1); + } + // Need to allocate value object if 'b' is true + long sum = v.hashInterpreted(); + if (b) { + v = MyValue1.createWithFieldsDontInline(rI, sum); + } else { + v = MyValue1.createWithFieldsDontInline(rI, sum + 1); + } + // Don't need to allocate value object because both branches allocate + return v; + } + + @Run(test = "test9") + public void test9_verifier() { + MyValue1 v = test9(true, rI, rL); + Asserts.assertEQ(v.x, rI); + Asserts.assertEQ(v.y, hash()); + v = test9(false, rI, rL); + Asserts.assertEQ(v.x, rI); + Asserts.assertEQ(v.y, hash(rI + 1, rL + 1) + 1); + } + + // Merge value objects created in a loop (not inlined) + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test10(int x, long y) { + MyValue1 v = MyValue1.createWithFieldsDontInline(x, y); + for (int i = 0; i < 10; ++i) { + v = MyValue1.createWithFieldsDontInline(v.x + 1, v.y + 1); + } + return v.hash(); + } + + @Run(test = "test10") + public void test10_verifier() { + long result = test10(rI, rL); + Asserts.assertEQ(result, hash(rI + 10, rL + 10)); + } + + // Merge value objects created in a loop (inlined) + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test11(int x, long y) { + MyValue1 v = MyValue1.createWithFieldsInline(x, y); + for (int i = 0; i < 10; ++i) { + v = MyValue1.createWithFieldsInline(v.x + 1, v.y + 1); + } + return v.hash(); + } + + @Run(test = "test11") + public void test11_verifier() { + long result = test11(rI, rL); + Asserts.assertEQ(result, hash(rI + 10, rL + 10)); + } + + // Test loop with uncommon trap referencing a value object + @Test + @IR(applyIf = {"UseArrayFlattening", "true"}, + counts = {SCOPE_OBJECT, ">= 1", LOAD_OF_ANY_KLASS, "<= 12"}) // TODO 8227588 (loads should be removed) + public long test12(boolean b) { + MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, Math.abs(rI) % 10, MyValue1.DEFAULT); + for (int i = 0; i < va.length; ++i) { + va[i] = MyValue1.createWithFieldsInline(rI, rL); + } + long result = rL; + for (int i = 0; i < 1000; ++i) { + if (b) { + result += v.x; + } else { + // Uncommon trap referencing v. We delegate allocation to the + // interpreter by adding a SafePointScalarObjectNode. + result = v.hashInterpreted(); + for (int j = 0; j < va.length; ++j) { + result += va[j].hash(); + } + } + } + return result; + } + + @Run(test = "test12") + public void test12_verifier(RunInfo info) { + long result = test12(info.isWarmUp()); + Asserts.assertEQ(result, info.isWarmUp() ? rL + (1000 * rI) : ((Math.abs(rI) % 10) + 1) * hash()); + } + + // Test loop with uncommon trap referencing a value object + @Test + public long test13(boolean b) { + MyValue1 v = MyValue1.createWithFieldsDontInline(rI, rL); + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, Math.abs(rI) % 10, MyValue1.DEFAULT); + for (int i = 0; i < va.length; ++i) { + va[i] = MyValue1.createWithFieldsDontInline(rI, rL); + } + long result = rL; + for (int i = 0; i < 1000; ++i) { + if (b) { + result += v.x; + } else { + // Uncommon trap referencing v. Should not allocate + // but just pass the existing oop to the uncommon trap. + result = v.hashInterpreted(); + for (int j = 0; j < va.length; ++j) { + result += va[j].hashInterpreted(); + } + } + } + return result; + } + + @Run(test = "test13") + public void test13_verifier(RunInfo info) { + long result = test13(info.isWarmUp()); + Asserts.assertEQ(result, info.isWarmUp() ? rL + (1000 * rI) : ((Math.abs(rI) % 10) + 1) * hash()); + } + + // Create a value object in a non-inlined method and then call a + // non-inlined method on that value object. + @Test + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "true"}, + failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}, + counts = {LOAD_OF_ANY_KLASS, "= 19"}) + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "false"}, + failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test14() { + MyValue1 v = MyValue1.createWithFieldsDontInline(rI, rL); + return v.hashInterpreted(); + } + + @Run(test = "test14") + public void test14_verifier() { + long result = test14(); + Asserts.assertEQ(result, hash()); + } + + // Create a value object in an inlined method and then call a + // non-inlined method on that value object. + @Test + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "true"}, + failOn = {LOAD_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}, + counts = {ALLOC_OF_MYVALUE_KLASS, "<= 1"}) // 1 MyValue2 allocation (if not the all-zero value) + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "false"}, + failOn = {LOAD_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}, + counts = {ALLOC_OF_MYVALUE_KLASS, "<= 2"}) // 1 MyValue1 and 1 MyValue2 allocation (if not the all-zero value) + public long test15() { + MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); + return v.hashInterpreted(); + } + + @Run(test = "test15") + public void test15_verifier() { + long result = test15(); + Asserts.assertEQ(result, hash()); + } + + // Create a value object in a non-inlined method and then call an + // inlined method on that value object. + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test16() { + MyValue1 v = MyValue1.createWithFieldsDontInline(rI, rL); + return v.hash(); + } + + @Run(test = "test16") + public void test16_verifier() { + long result = test16(); + Asserts.assertEQ(result, hash()); + } + + // Create a value object in an inlined method and then call an + // inlined method on that value object. + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test17() { + MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); + return v.hash(); + } + + @Run(test = "test17") + public void test17_verifier() { + long result = test17(); + Asserts.assertEQ(result, hash()); + } + + // Create a value object in compiled code and pass it to the + // interpreter via a call. The value object is live at the first call so + // debug info should include a reference to all its fields. + @Test + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "true"}, + counts = {ALLOC_OF_MYVALUE_KLASS, "<= 1"}, // 1 MyValue2 allocation (if not the all-zero value) + failOn = {LOAD_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "false"}, + counts = {ALLOC_OF_MYVALUE_KLASS, "<= 2"}, // 1 MyValue1 and 1 MyValue2 allocation (if not the all-zero value) + failOn = {LOAD_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test18() { + MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); + v.hashInterpreted(); + return v.hashInterpreted(); + } + + @Run(test = "test18") + public void test18_verifier() { + long result = test18(); + Asserts.assertEQ(result, hash()); + } + + // Create a value object in compiled code and pass it to the + // interpreter via a call. The value object is passed twice but + // should only be allocated once. + @Test + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "true"}, + counts = {ALLOC_OF_MYVALUE_KLASS, "<= 1"}, // 1 MyValue2 allocation (if not the all-zero value) + failOn = {LOAD_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "false"}, + counts = {ALLOC_OF_MYVALUE_KLASS, "<= 2"}, // 1 MyValue1 and 1 MyValue2 allocation (if not the all-zero value) + failOn = {LOAD_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test19() { + MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); + return sumValue(v, v); + } + + @DontCompile + public long sumValue(MyValue1 v, MyValue1 dummy) { + return v.hash(); + } + + @Run(test = "test19") + public void test19_verifier() { + long result = test19(); + Asserts.assertEQ(result, hash()); + } + + // Create a value type (array) in compiled code and pass it to the + // interpreter via a call. The value object is live at the uncommon + // trap: verify that deoptimization causes the value object to be + // correctly allocated. + @Test + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "true"}, + counts = {ALLOC_OF_MYVALUE_KLASS, "<= 1"}, // 1 MyValue2 allocation (if not the all-zero value) + failOn = {LOAD_OF_ANY_KLASS}) + // TODO 8350865 + //@IR(applyIf = {"InlineTypePassFieldsAsArgs", "false"}, + // counts = {ALLOC_OF_MYVALUE_KLASS, "<= 2"}, // 1 MyValue1 and 1 MyValue2 allocation (if not the all-zero value) + // failOn = LOAD_OF_ANY_KLASS) + public long test20(boolean deopt, Method m) { + MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); + MyValue2[] va = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 3, MyValue2.DEFAULT); + if (deopt) { + // uncommon trap + TestFramework.deoptimize(m); + } + + return v.hashInterpreted() + va[0].hashInterpreted() + + va[1].hashInterpreted() + va[2].hashInterpreted(); + } + + @Run(test = "test20") + public void test20_verifier(RunInfo info) { + MyValue2[] va = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 42, MyValue2.DEFAULT); + long result = test20(!info.isWarmUp(), info.getTest()); + Asserts.assertEQ(result, hash() + va[0].hash() + va[1].hash() + va[2].hash()); + } + + // Value class fields in regular object + MyValue1 val1; + MyValue2 val2; + @Strict + @NullRestricted + final MyValue1 val3 = MyValue1.createWithFieldsInline(rI, rL); + @Strict + @NullRestricted + static MyValue1 val4 = MyValue1.DEFAULT; + @Strict + @NullRestricted + static final MyValue1 val5 = MyValue1.createWithFieldsInline(rI, rL); + + // Test value class fields in objects + @Test + @IR(counts = {ALLOC_OF_MYVALUE_KLASS, "= 4"}, failOn = {UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test21(int x, long y) { + // Compute hash of value class fields + long result = val1.hash() + val2.hash() + val3.hash() + val4.hash() + val5.hash(); + // Update fields + val1 = MyValue1.createWithFieldsInline(x, y); + val2 = MyValue2.createWithFieldsInline(x, rD); + val4 = MyValue1.createWithFieldsInline(x, y); + return result; + } + + @Run(test = "test21") + public void test21_verifier() { + // Check if hash computed by test18 is correct + val1 = MyValue1.createWithFieldsInline(rI, rL); + val2 = val1.v2; + // val3 is initialized in the constructor + val4 = val1; + // val5 is initialized in the static initializer + long hash = val1.hash() + val2.hash() + val3.hash() + val4.hash() + val5.hash(); + long result = test21(rI + 1, rL + 1); + Asserts.assertEQ(result, hash); + // Check if value class fields were updated + Asserts.assertEQ(val1.hash(), hash(rI + 1, rL + 1)); + Asserts.assertEQ(val2.hash(), MyValue2.createWithFieldsInline(rI + 1, rD).hash()); + Asserts.assertEQ(val4.hash(), hash(rI + 1, rL + 1)); + } + + // Test folding of constant value class fields + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, LOOP, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test22() { + // This should be constant folded + return val5.hash() + val5.v3.hash(); + } + + @Run(test = "test22") + public void test22_verifier() { + long result = test22(); + Asserts.assertEQ(result, val5.hash() + val5.v3.hash()); + } + + // Test aconst_init + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, LOOP, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test23() { + MyValue2 v = MyValue2.createDefaultInline(); + return v.hash(); + } + + @Run(test = "test23") + public void test23_verifier() { + long result = test23(); + Asserts.assertEQ(result, MyValue2.createDefaultInline().hash()); + } + + // Test aconst_init + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, LOOP, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test24() { + MyValue1 v1 = MyValue1.createDefaultInline(); + MyValue1 v2 = MyValue1.createDefaultDontInline(); + return v1.hashPrimitive() + v2.hashPrimitive(); + } + + @Run(test = "test24") + public void test24_verifier() { + long result = test24(); + Asserts.assertEQ(result, 2 * MyValue1.createDefaultInline().hashPrimitive()); + } + + // Test field initialization + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, LOOP, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test25() { + MyValue2 v = MyValue2.createWithFieldsInline(rI, rD); + return v.hash(); + } + + @Run(test = "test25") + public void test25_verifier() { + long result = test25(); + Asserts.assertEQ(result, MyValue2.createWithFieldsInline(rI, rD).hash()); + } + + // Test field initialization + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, LOOP, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test26() { + MyValue1 v1 = MyValue1.createWithFieldsInline(rI, rL); + MyValue1 v2 = MyValue1.createWithFieldsDontInline(rI, rL); + return v1.hash() + v2.hash(); + } + + @Run(test = "test26") + public void test26_verifier() { + long result = test26(); + Asserts.assertEQ(result, 2 * hash()); + } + + class TestClass27 { + @Strict + @NullRestricted + public MyValue1 v = MyValue1.DEFAULT; + } + + // Test allocation elimination of unused object with initialized value class field + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, LOOP}) + public void test27(boolean deopt, Method m) { + TestClass27 unused = new TestClass27(); + MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); + unused.v = v; + if (deopt) { + // uncommon trap + TestFramework.deoptimize(m); + } + } + + @Run(test = "test27") + public void test27_verifier(RunInfo info) { + test27(!info.isWarmUp(), info.getTest()); + } + + @Strict + @NullRestricted + static MyValue3 staticVal3 = MyValue3.DEFAULT; + @Strict + @NullRestricted + static MyValue3 staticVal3_copy = MyValue3.DEFAULT; + + // Check elimination of redundant value class allocations + @Test + @IR(counts = {ALLOC_OF_MYVALUE_KLASS, "= 1"}) + public MyValue3 test28(MyValue3[] va) { + // Create value object and force allocation + MyValue3 vt = MyValue3.create(); + va[0] = vt; + staticVal3 = vt; + vt.verify(staticVal3); + + // Value object is now allocated, make a copy and force allocation. + // Because copy is equal to vt, C2 should remove this redundant allocation. + MyValue3 copy = MyValue3.setC(vt, vt.c); + va[0] = copy; + staticVal3_copy = copy; + copy.verify(staticVal3_copy); + return copy; + } + + @Run(test = "test28") + public void test28_verifier() { + MyValue3[] va = (MyValue3[])ValueClass.newNullRestrictedNonAtomicArray(MyValue3.class, 1, MyValue3.DEFAULT); + MyValue3 vt = test28(va); + staticVal3.verify(vt); + staticVal3.verify(va[0]); + staticVal3_copy.verify(vt); + staticVal3_copy.verify(va[0]); + } + + // Verify that only dominating allocations are re-used + @Test + public MyValue3 test29(boolean warmup) { + MyValue3 vt = MyValue3.create(); + if (warmup) { + staticVal3 = vt; // Force allocation + } + // Force allocation to verify that above + // non-dominating allocation is not re-used + MyValue3 copy = MyValue3.setC(vt, vt.c); + staticVal3_copy = copy; + copy.verify(vt); + return copy; + } + + @Run(test = "test29") + public void test29_verifier(RunInfo info) { + MyValue3 vt = test29(info.isWarmUp()); + if (info.isWarmUp()) { + staticVal3.verify(vt); + } + } + + // Verify that C2 recognizes value class loads and re-uses the oop to avoid allocations + @Test + @IR(applyIf = {"UseArrayFlattening", "true"}, + failOn = {ALLOC_OF_MYVALUE_KLASS, ALLOC_ARRAY_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public MyValue3 test30() { + // C2 can re-use the oop of staticVal3 because staticVal3 is equal to copy + MyValue3[] va = (MyValue3[])ValueClass.newNullRestrictedNonAtomicArray(MyValue3.class, 1, MyValue3.DEFAULT); + MyValue3 copy = MyValue3.copy(staticVal3); + va[0] = copy; + copy.verify(va[0]); + staticVal3 = copy; + copy.verify(staticVal3); + return copy; + } + + @Run(test = "test30") + public void test30_verifier() { + staticVal3 = MyValue3.create(); + MyValue3 vt = test30(); + staticVal3.verify(vt); + } + + // Verify that C2 recognizes value class loads and re-uses the oop to avoid allocations + @Test + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "false"}, + failOn = {ALLOC_OF_MYVALUE_KLASS, ALLOC_ARRAY_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public MyValue3 test31() { + // C2 can re-use the oop returned by createDontInline() + // because the corresponding value object is equal to 'copy'. + MyValue3[] va = (MyValue3[])ValueClass.newNullRestrictedNonAtomicArray(MyValue3.class, 1, MyValue3.DEFAULT); + MyValue3 copy = MyValue3.copy(MyValue3.createDontInline()); + va[0] = copy; + copy.verify(va[0]); + staticVal3 = copy; + copy.verify(staticVal3); + return copy; + } + + @Run(test = "test31") + public void test31_verifier() { + MyValue3 vt = test31(); + staticVal3.verify(vt); + } + + // Verify that C2 recognizes value class loads and re-uses the oop to avoid allocations + @Test + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "false"}, + failOn = {ALLOC_OF_MYVALUE_KLASS, ALLOC_ARRAY_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public MyValue3 test32(MyValue3 vt) { + // C2 can re-use the oop of vt because vt is equal to 'copy'. + MyValue3[] va = (MyValue3[])ValueClass.newNullRestrictedNonAtomicArray(MyValue3.class, 1, MyValue3.DEFAULT); + MyValue3 copy = MyValue3.copy(vt); + va[0] = copy; + copy.verify(vt); + staticVal3 = copy; + copy.verify(staticVal3); + return copy; + } + + @Run(test = "test32") + public void test32_verifier() { + MyValue3 vt = MyValue3.create(); + MyValue3 result = test32(vt); + staticVal3.verify(vt); + result.verify(vt); + } + + // Test correct identification of value object copies + @Test + public MyValue3 test33() { + MyValue3[] va = (MyValue3[])ValueClass.newNullRestrictedNonAtomicArray(MyValue3.class, 1, MyValue3.DEFAULT); + MyValue3 vt = MyValue3.copy(staticVal3); + vt = MyValue3.setI(vt, vt.c); + // vt is not equal to staticVal3, so C2 should not re-use the oop + va[0] = vt; + Asserts.assertEQ(va[0].i, (int)vt.c); + staticVal3 = vt; + vt.verify(staticVal3); + return vt; + } + + @Run(test = "test33") + public void test33_verifier() { + staticVal3 = MyValue3.create(); + MyValue3 vt = test33(); + Asserts.assertEQ(staticVal3.i, (int)staticVal3.c); + Asserts.assertEQ(vt.i, (int)staticVal3.c); + } + + static final MyValue3[] test34Array = (MyValue3[])ValueClass.newNullRestrictedNonAtomicArray(MyValue3.class, 2, MyValue3.DEFAULT); + + // Verify that the all-zero value class is never allocated. + // C2 code should load and use the all-zero oop from the java mirror. + @Test + // The concept of a pre-allocated "all-zero value" was removed. + // @IR(applyIf = {"UseArrayFlattening", "true"}, + // failOn = {ALLOC_OF_MYVALUE_KLASS, ALLOC_ARRAY_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, LOOP, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public MyValue3 test34() { + // Explicitly create all-zero value + MyValue3 vt = MyValue3.createDefault(); + test34Array[0] = vt; + staticVal3 = vt; + vt.verify(vt); + + // Load all-zero value from uninitialized value class array + MyValue3[] dva = (MyValue3[])ValueClass.newNullRestrictedNonAtomicArray(MyValue3.class, 1, MyValue3.DEFAULT); + staticVal3_copy = dva[0]; + test34Array[1] = dva[0]; + dva[0].verify(dva[0]); + return vt; + } + + @Run(test = "test34") + public void test34_verifier() { + MyValue3 vt = MyValue3.createDefault(); + test34Array[0] = MyValue3.create(); + test34Array[1] = MyValue3.create(); + MyValue3 res = test34(); + res.verify(vt); + staticVal3.verify(vt); + staticVal3_copy.verify(vt); + test34Array[0].verify(vt); + test34Array[1].verify(vt); + } + + static final MyValue3[] test35Array = (MyValue3[])ValueClass.newNullRestrictedNonAtomicArray(MyValue3.class, 1, MyValue3.DEFAULT); + + // Same as above but manually initialize value class fields to all-zero. + @Test + // The concept of a pre-allocated "all-zero value" was removed. + // @IR(applyIf = {"UseArrayFlattening", "true"}, + // failOn = {ALLOC_OF_MYVALUE_KLASS, ALLOC_ARRAY_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, LOOP, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public MyValue3 test35(MyValue3 vt) { + vt = MyValue3.setC(vt, (char)0); + vt = MyValue3.setBB(vt, (byte)0); + vt = MyValue3.setS(vt, (short)0); + vt = MyValue3.setI(vt, 0); + vt = MyValue3.setL(vt, 0); + vt = MyValue3.setO(vt, null); + vt = MyValue3.setF1(vt, 0); + vt = MyValue3.setF2(vt, 0); + vt = MyValue3.setF3(vt, 0); + vt = MyValue3.setF4(vt, 0); + vt = MyValue3.setF5(vt, 0); + vt = MyValue3.setF6(vt, 0); + vt = MyValue3.setV1(vt, MyValue3Inline.createDefault()); + test35Array[0] = vt; + staticVal3 = vt; + vt.verify(vt); + return vt; + } + + @Run(test = "test35") + public void test35_verifier() { + MyValue3 vt = MyValue3.createDefault(); + test35Array[0] = MyValue3.create(); + MyValue3 res = test35(test35Array[0]); + res.verify(vt); + staticVal3.verify(vt); + test35Array[0].verify(vt); + } + + // Merge value objects created from two branches + + private Object test36_helper(Object v) { + return v; + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test36(boolean b) { + Object o; + if (b) { + o = test36_helper(MyValue1.createWithFieldsInline(rI, rL)); + } else { + o = test36_helper(MyValue1.createWithFieldsDontInline(rI + 1, rL + 1)); + } + MyValue1 v = (MyValue1)o; + return v.hash(); + } + + @Run(test = "test36") + public void test36_verifier() { + Asserts.assertEQ(test36(true), hash()); + Asserts.assertEQ(test36(false), hash(rI + 1, rL + 1)); + } + + // Test correct loading of flattened fields + @LooselyConsistentValue + value class Test37Value2 { + int x = 0; + int y = 0; + } + + @LooselyConsistentValue + value class Test37Value1 { + double d = 0; + float f = 0; + @Strict + @NullRestricted + Test37Value2 v = new Test37Value2(); + } + + @Test + public Test37Value1 test37(Test37Value1 vt) { + return vt; + } + + @Run(test = "test37") + public void test37_verifier() { + Test37Value1 vt = new Test37Value1(); + Asserts.assertEQ(test37(vt), vt); + } + + // Test elimination of value class allocations without a unique CheckCastPP + @LooselyConsistentValue + static value class Test38Value { + public int i; + public Test38Value(int i) { this.i = i; } + } + + @Strict + @NullRestricted + static Test38Value test38Field = new Test38Value(0); + + @Test + public void test38() { + for (int i = 3; i < 100; ++i) { + int j = 1; + while (++j < 11) { + try { + test38Field = new Test38Value(i); + } catch (ArithmeticException ae) { } + } + } + } + + @Run(test = "test38") + public void test38_verifier() { + test38Field = new Test38Value(0); + test38(); + Asserts.assertEQ(test38Field, new Test38Value(99)); + } + + // Tests split if with value class Phi users + @LooselyConsistentValue + static value class Test39Value { + public int iFld1; + public int iFld2; + + public Test39Value(int i1, int i2) { iFld1 = i1; iFld2 = i2; } + } + + static int test39A1[][] = new int[400][400]; + static double test39A2[] = new double[400]; + @Strict + @NullRestricted + static Test39Value test39Val = new Test39Value(0, 0); + + @DontInline + public int[] getArray() { + return new int[400]; + } + + @Test + public int test39() { + int result = 0; + for (int i = 0; i < 100; ++i) { + switch ((i >>> 1) % 3) { + case 0: + test39A1[i][i] = i; + break; + case 1: + for (int j = 0; j < 100; ++j) { + test39A1[i] = getArray(); + test39Val = new Test39Value(j, test39Val.iFld2); + } + break; + case 2: + for (float f = 142; f > i; f--) { + test39A2[i + 1] += 3; + } + result += test39Val.iFld1; + break; + } + double d1 = 1; + while (++d1 < 142) { + test39A1[(i >>> 1) % 400][i + 1] = result; + test39Val = new Test39Value(i, test39Val.iFld2); + } + } + return result; + } + + @Run(test = "test39") + @Warmup(10) + public void test39_verifier() { + int result = test39(); + Asserts.assertEQ(result, 1552); + } + + // Test scalar replacement of value class array containing value class with oop fields + @Test + public long test40(boolean b) { + MyValue1[] va = {MyValue1.createWithFieldsInline(rI, rL)}; + long result = 0; + for (int i = 0; i < 1000; ++i) { + if (!b) { + result = va[0].hash(); + } + } + return result; + } + + @Run(test = "test40") + public void test40_verifier(RunInfo info) { + long result = test40(info.isWarmUp()); + Asserts.assertEQ(result, info.isWarmUp() ? 0 : hash()); + } + + static value class MyValue41 { + int x; + + public MyValue41(int x) { + this.x = x; + } + + static MyValue41 make() { + return new MyValue41(0); + } + } + + static MyValue41 field41; + + // Test detection of value object copies and removal of the MemBarRelease following the value buffer initialization + @Test + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "false"}, + failOn = {ALLOC_OF_MYVALUE_KLASS, ALLOC_ARRAY_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public void test41(MyValue41 val) { + field41 = new MyValue41(val.x); + } + + @Run(test = "test41") + public void test41_verifier() { + MyValue41 val = new MyValue41(rI); + test41(val); + Asserts.assertEQ(field41, val); + } + + @DontInline + public void test42_helper(MyValue41 val) { + Asserts.assertEQ(val, new MyValue41(rI)); + } + + // Same as test41 but with call argument requiring buffering + @Test + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "false"}, + failOn = {ALLOC_OF_MYVALUE_KLASS, ALLOC_ARRAY_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public void test42(MyValue41 val) { + test42_helper(new MyValue41(val.x)); + } + + @Run(test = "test42") + public void test42_verifier() { + MyValue41 val = new MyValue41(rI); + test42(val); + } + + static value class MyValue42 { + int x; + + @ForceInline + MyValue42(int x) { + this.x = x; + call(); + super(); + } + + @ForceInline + static Object make(int x) { + return new MyValue42(x); + } + } + + @Test + @IR(failOn = {LOAD_OF_ANY_KLASS}) + public MyValue42 test43(int x) { + return (MyValue42) MyValue42.make(x); + } + + @Run(test = "test43") + public void test43_verifier() { + MyValue42 v = test43(rI); + Asserts.assertEQ(rI, v.x); + } + + static value class MyValue43 { + int x; + + @ForceInline + MyValue43(int x) { + this.x = x; + super(); + call(); + } + + @ForceInline + static Object make(int x) { + return new MyValue43(x); + } + } + + @Test + @IR(failOn = {LOAD_OF_ANY_KLASS}) + public MyValue43 test44(int x) { + return (MyValue43) MyValue43.make(x); + } + + @Run(test = "test44") + public void test44_verifier() { + MyValue43 v = test44(rI); + Asserts.assertEQ(rI, v.x); + } + + @LooselyConsistentValue + static value class MyValue45 { + Integer v; + + MyValue45(Integer v) { + this.v = v; + } + } + + static value class MyValue45ValueHolder { + @NullRestricted + @Strict + MyValue45 v; + + MyValue45ValueHolder(Integer v) { + this.v = new MyValue45(v); + } + } + + static class MyValue45Holder { + @NullRestricted + @Strict + MyValue45 v; + + MyValue45Holder(Integer v) { + this.v = new MyValue45(v); + } + } + + @Test + // TODO 8357580 more aggressive flattening + // @IR(applyIfAnd = {"UseFieldFlattening", "true", "UseNullableValueFlattening", "true"}, counts = {IRNode.LOAD_I, "1", IRNode.LOAD_B, "1"}) + public Integer test45(Object arg) { + return ((MyValue45ValueHolder) arg).v.v; + } + + @Run(test = "test45") + public void test45_verifier() { + Integer v = null; + Asserts.assertEQ(test45(new MyValue45ValueHolder(v)), v); + v = rI; + Asserts.assertEQ(test45(new MyValue45ValueHolder(v)), v); + } + + @Test + // TODO 8357580 more aggressive flattening + // @IR(applyIfAnd = {"UseFieldFlattening", "true", "UseNullableValueFlattening", "true"}, counts = {IRNode.LOAD_L, "1"}) + // @IR(applyIfAnd = {"UseFieldFlattening", "true", "UseNullableValueFlattening", "true"}, failOn = {IRNode.LOAD_I, IRNode.LOAD_B}) + public Integer test46(Object arg) { + return ((MyValue45Holder) arg).v.v; + } + + @Run(test = "test46") + public void test46_verifier() { + Integer v = null; + Asserts.assertEQ(test46(new MyValue45Holder(v)), v); + v = rI; + Asserts.assertEQ(test46(new MyValue45Holder(v)), v); + } + + static value class MyValue47 { + byte b1; + byte b2; + + MyValue47(byte b1, byte b2) { + this.b1 = b1; + this.b2 = b2; + } + } + + static value class MyValue47Holder { + @NullRestricted + @Strict + MyValue47 v; + + MyValue47Holder(int v) { + byte b1 = (byte) v; + byte b2 = (byte) (v >>> 8); + this.v = new MyValue47(b1, b2); + } + } + + static class MyValue47HolderHolder { + @NullRestricted + @Strict + MyValue47Holder v; + + MyValue47HolderHolder(MyValue47Holder v) { + this.v = v; + } + } + + @Test + @IR(applyIfAnd = {"UseFieldFlattening", "true", "UseAtomicValueFlattening", "true"}, counts = {IRNode.LOAD_S, "1"}) + @IR(applyIfAnd = {"UseFieldFlattening", "true", "UseAtomicValueFlattening", "true"}, failOn = {IRNode.LOAD_B}) + public MyValue47Holder test47(MyValue47HolderHolder arg) { + return arg.v; + } + + @Run(test = "test47") + public void test47_verifier() { + MyValue47Holder v = new MyValue47Holder(rI); + Asserts.assertEQ(test47(new MyValue47HolderHolder(v)), v); + } + + static final MyValue47Holder[] MY_VALUE_47_HOLDERS = (MyValue47Holder[]) ValueClass.newNullRestrictedAtomicArray(MyValue47Holder.class, 2, new MyValue47Holder(rI)); + + @Test + @IR(applyIfAnd = {"UseFieldFlattening", "true", "UseArrayFlattening", "true", "UseAtomicValueFlattening", "true"}, counts = {IRNode.LOAD_S, "1"}) + @IR(applyIfAnd = {"UseFieldFlattening", "true", "UseArrayFlattening", "true", "UseAtomicValueFlattening", "true"}, failOn = {IRNode.LOAD_B}) + public MyValue47Holder test48() { + return MY_VALUE_47_HOLDERS[0]; + } + + @Run(test = "test48") + public void test48_verifier() { + MyValue47Holder v = new MyValue47Holder(rI); + Asserts.assertEQ(test48(), v); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestBimorphicInlining.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestBimorphicInlining.java new file mode 100644 index 00000000000..6cb2d929f11 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestBimorphicInlining.java @@ -0,0 +1,141 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import java.util.Random; +import jdk.test.lib.Asserts; + +/** + * @test + * @key randomness + * @bug 8209009 + * @summary Test bimorphic inlining with value object receivers. + * @library /testlibrary /test/lib + * @enablePreview + * @run main/othervm -Xbatch -XX:TypeProfileLevel=222 + * -XX:CompileCommand=compileonly,compiler.valhalla.inlinetypes.TestBimorphicInlining::test* + * -XX:CompileCommand=quiet -XX:CompileCommand=print,compiler.valhalla.inlinetypes.TestBimorphicInlining::test* + * compiler.valhalla.inlinetypes.TestBimorphicInlining + * @run main/othervm -Xbatch -XX:TypeProfileLevel=222 + * -XX:+UnlockExperimentalVMOptions -XX:PerMethodTrapLimit=0 -XX:PerMethodSpecTrapLimit=0 + * -XX:CompileCommand=compileonly,compiler.valhalla.inlinetypes.TestBimorphicInlining::test* + * -XX:CompileCommand=quiet -XX:CompileCommand=print,compiler.valhalla.inlinetypes.TestBimorphicInlining::test* + * compiler.valhalla.inlinetypes.TestBimorphicInlining + */ + +interface MyInterface { + public MyInterface hash(MyInterface arg); +} + +value class TestValue1 implements MyInterface { + int x; + + public TestValue1(int x) { + this.x = x; + } + + public TestValue1 hash(MyInterface arg) { + return new TestValue1(x + ((TestValue1)arg).x); + } +} + +value class TestValue2 implements MyInterface { + int x; + + public TestValue2(int x) { + this.x = x; + } + + public TestValue2 hash(MyInterface arg) { + return new TestValue2(x + ((TestValue2)arg).x); + } +} + +class TestClass implements MyInterface { + int x; + + public TestClass(int x) { + this.x = x; + } + + public MyInterface hash(MyInterface arg) { + return new TestClass(x + ((TestClass)arg).x); + } +} + +public class TestBimorphicInlining { + + public static MyInterface test1(MyInterface i1, MyInterface i2) { + MyInterface result = i1.hash(i2); + i1.hash(i2); + return result; + } + + public static MyInterface test2(MyInterface i1, MyInterface i2) { + MyInterface result = i1.hash(i2); + i1.hash(i2); + return result; + } + + public static MyInterface test3(MyInterface i1, MyInterface i2) { + MyInterface result = i1.hash(i2); + i1.hash(i2); + return result; + } + + public static MyInterface test4(MyInterface i1, MyInterface i2) { + MyInterface result = i1.hash(i2); + i1.hash(i2); + return result; + } + + static public void main(String[] args) { + Random rand = new Random(); + TestClass testObject = new TestClass(rand.nextInt()); + TestValue1 testValue1 = new TestValue1(rand.nextInt()); + TestValue2 testValue2 = new TestValue2(rand.nextInt()); + + for (int i = 0; i < 10_000; ++i) { + // Trigger bimorphic inlining by calling test methods with different arguments + MyInterface arg, res; + boolean rare = (i % 10 == 0); + + arg = rare ? testValue1 : testObject; + res = test1(arg, arg); + Asserts.assertEQ(rare ? ((TestValue1)res).x : ((TestClass)res).x, 2 * (rare ? testValue1.x : testObject.x), "test1 failed"); + + arg = rare ? testObject : testValue1; + res = test2(arg, arg); + Asserts.assertEQ(rare ? ((TestClass)res).x : ((TestValue1)res).x, 2 * (rare ? testObject.x : testValue1.x), "test2 failed"); + + arg = rare ? testValue1 : testValue2; + res = test3(arg, arg); + Asserts.assertEQ(rare ? ((TestValue1)res).x : ((TestValue2)res).x, 2 * (rare ? testValue1.x : testValue2.x), "test3 failed"); + + arg = rare ? testValue2 : testValue1; + res = test4(arg, arg); + Asserts.assertEQ(rare ? ((TestValue2)res).x : ((TestValue1)res).x, 2 * (rare ? testValue2.x : testValue1.x), "test4 failed"); + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestBufferTearingC1.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestBufferTearingC1.java new file mode 100644 index 00000000000..6e874b5a277 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestBufferTearingC1.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, Arm Limited. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +/** + * @test TestBufferTearingC1 + * @key randomness + * @summary Tests for C1 missing barriers when buffering value classes. + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm -XX:+UseFieldFlattening -XX:+UseArrayFlattening + * compiler.valhalla.inlinetypes.TestBufferTearingC1 + * @run main/othervm -XX:+UseFieldFlattening -XX:+UseArrayFlattening + * -XX:TieredStopAtLevel=1 + * compiler.valhalla.inlinetypes.TestBufferTearingC1 + */ + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; + +@LooselyConsistentValue +value class Point { + public int x, y; + + public Point(int x, int y) { + this.x = x; + this.y = y; + } + + public static final Point DEFAULT = new Point(0, 0); +} + +@LooselyConsistentValue +value class Rect { + public Point a, b; + + public Rect(Point a, Point b) { + this.a = a; + this.b = b; + } + + public static final Rect DEFAULT = new Rect(Point.DEFAULT, Point.DEFAULT); +} + +public class TestBufferTearingC1 { + + public static Point[] points = (Point[])ValueClass.newNullRestrictedNonAtomicArray(Point.class, 1, Point.DEFAULT); + public static Rect rect = new Rect(new Point(1, 1), new Point(2, 2)); + public static Rect[] rects = (Rect[])ValueClass.newNullRestrictedNonAtomicArray(Rect.class, 1, Rect.DEFAULT); + + static { + points[0] = new Point(1, 1); + rects[0] = rect; + } + + public static Object ref1 = points[0]; + public static Object ref2 = rect.a; + public static Object ref3 = rects[0].a; + + static volatile boolean running = true; + + public static void writeRefs(int iter) { + ref1 = points[0]; // Indexed load of flattened array + ref2 = rect.a; // Load from flattened field + ref3 = rects[0].a; // Indexed load (delayed) followed by flattened field access + + points[0] = new Point(iter, iter); + rect = new Rect(new Point(iter, iter), new Point(iter + 1, iter + 1)); + rects[0] = rect; + } + + private static void checkMissingBarrier() { + while (running) { + // Each refN holds a "buffered" reference created when reading a + // flattened field or array element. It should not be possible to + // read through this reference and see the intermediate + // zero-initialised state of the object (i.e. there should be a + // store-store barrier after copying the flattened field contents + // before the store that publishes it). + + if (((Point)ref1).x == 0 || ((Point)ref1).y == 0) { + throw new IllegalStateException(); + } + + if (((Point)ref2).x == 0 || ((Point)ref2).y == 0) { + throw new IllegalStateException(); + } + + if (((Point)ref3).x == 0 || ((Point)ref3).y == 0) { + throw new IllegalStateException(); + } + } + } + + public static void main(String[] args) throws InterruptedException { + Thread[] threads = new Thread[10]; + for (int i = 0; i < 10; i++) { + threads[i] = new Thread(TestBufferTearingC1::checkMissingBarrier); + threads[i].start(); + } + + for (int i = 1; i < 1_000_000; i++) { + writeRefs(i); + } + + running = false; + + for (int i = 0; i < 10; i++) { + threads[i].join(); + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestC1.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestC1.java new file mode 100644 index 00000000000..3be040e31d4 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestC1.java @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.CompLevel; +import compiler.lib.ir_framework.Run; +import compiler.lib.ir_framework.Scenario; +import compiler.lib.ir_framework.Test; +import jdk.test.lib.Asserts; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import static compiler.valhalla.inlinetypes.InlineTypes.rI; +import static compiler.valhalla.inlinetypes.InlineTypes.rL; + +/* + * @test + * @key randomness + * @summary Various tests that are specific to C1. + * @library /test/lib / + * @requires (os.simpleArch == "x64" | os.simpleArch == "aarch64") + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm/timeout=300 compiler.valhalla.inlinetypes.TestC1 + */ + +public class TestC1 { + public static void main(String[] args) { + final Scenario[] scenarios = { + // C1 only + new Scenario(0, "-XX:TieredStopAtLevel=1", "-XX:+TieredCompilation"), + // C2 only. (Make sure the tests are correctly written) + new Scenario(1, "-XX:TieredStopAtLevel=4", "-XX:-TieredCompilation"), + // interpreter only + new Scenario(2, "-Xint"), + // Xcomp Only C1. + new Scenario(3, "-XX:TieredStopAtLevel=1", "-XX:+TieredCompilation", "-Xcomp"), + // Xcomp Only C2. + new Scenario(4, "-XX:TieredStopAtLevel=4", "-XX:-TieredCompilation", "-Xcomp") + }; + + InlineTypes.getFramework() + .addScenarios(scenarios) + .addFlags("--enable-preview", + "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED") + .addHelperClasses(MyValue1.class, + MyValue2.class, + MyValue2Inline.class, + MyValue3.class, + MyValue3Inline.class) + .start(); + } + + // JDK-8229799 + @Test(compLevel = CompLevel.C1_SIMPLE) + public long test1(Object a, Object b, long n) { + long r; + n += (a == b) ? 0x5678123456781234L : 0x1234567812345678L; + n -= 1; + return n; + } + + @Run(test = "test1") + public void test1_verifier() { + MyValue1 v1 = MyValue1.createWithFieldsInline(rI, rL); + MyValue1 v2 = MyValue1.createWithFieldsInline(rI, rL+1); + long r1 = test1(v1, v1, 1); + long r2 = test1(v1, v2, 1); + Asserts.assertEQ(r1, 0x5678123456781234L); + Asserts.assertEQ(r2, 0x1234567812345678L); + } + + @LooselyConsistentValue + static value class SimpleValue2 { + int value; + + SimpleValue2(int value) { + this.value = value; + } + } + + // JDK-8231961 + // Test that the value numbering optimization does not remove + // the second load from the buffered array element. + @Test(compLevel = CompLevel.C1_SIMPLE) + public int test2(SimpleValue2[] array) { + return array[0].value + array[0].value; + } + + @Run(test = "test2") + public void test2_verifier() { + SimpleValue2[] array = (SimpleValue2[])ValueClass.newNullRestrictedNonAtomicArray(SimpleValue2.class, 1, new SimpleValue2(0)); + array[0] = new SimpleValue2(rI); + int result = test2(array); + Asserts.assertEQ(result, 2*rI); + } + + + // Tests below (3 to 8) check the behavior of the C1 optimization to access + // sub-elements of a flattened array without copying the element first + + // Test access to a null array + @Test(compLevel = CompLevel.C1_SIMPLE) + public int test3(MyValue2[] array, int index) { + return array[index].x; + } + + @Run(test = "test3") + public void test3_verifier() { + NullPointerException npe = null; + try { + test3(null, 0); + } catch(NullPointerException e) { + npe = e; + } + Asserts.assertNE(npe, null); + } + + // Test out of bound accesses + @Test(compLevel = CompLevel.C1_SIMPLE) + public int test4(MyValue2[] array, int index) { + return array[index].x; + } + + @Run(test = "test4") + public void test4_verifier() { + MyValue2[] array = new MyValue2[2]; + ArrayIndexOutOfBoundsException aioob = null; + try { + test3(array, -1); + } catch(ArrayIndexOutOfBoundsException e) { + aioob = e; + } + Asserts.assertNE(aioob, null); + aioob = null; + try { + test3(array, 2); + } catch(ArrayIndexOutOfBoundsException e) { + aioob = e; + } + Asserts.assertNE(aioob, null); + } + + // Test 1st level sub-element access to primitive field + @Test(compLevel = CompLevel.C1_SIMPLE) + public int test5(MyValue2[] array, int index) { + return array[index].x; + } + + @Run(test = "test5") + public void test5_verifier() { + MyValue2[] array = new MyValue2[2]; + MyValue2 v = new MyValue2(1,(byte)2, new MyValue2Inline(5.0d, 345L)); + array[1] = v; + int x = test5(array, 1); + Asserts.assertEQ(x, 1); + } + + // Test 1st level sub-element access to flattened field + @Test(compLevel = CompLevel.C1_SIMPLE) + public MyValue2Inline test6(MyValue2[] array, int index) { + return array[index].v; + } + + @Run(test = "test6") + public void test6_verifier() { + MyValue2[] array = new MyValue2[2]; + MyValue2Inline vi = new MyValue2Inline(3.5d, 678L); + MyValue2 v = new MyValue2(1,(byte)2, vi); + array[0] = v; + MyValue2Inline vi2 = test6(array, 0); + Asserts.assertEQ(vi, vi2); + } + + // Test 1st level sub-element access to non-flattened field + @LooselyConsistentValue + static value class Big { + long l0,l1,l2,l3,l4,l5,l6,l7,l8,l9,l10,l11,l12,l13,l14,l15,l16,l17,l18,l19; + + Big(long n) { + l0 = n++; l1 = n++; l2 = n++; l3 = n++; l4 = n++; l5 = n++; l6 = n++; l7 = n++; l8 = n++; + l9 = n++; l10 = n++; l11 = n++; l12 = n++; l13 = n++; l14 = n++; l15 = n++; l16= n++; + l17 = n++; l18 = n++; l19 = n++; + } + + Big() { + l0 = l1 = l2 = l3 = l4 = l5 = l6 = l7 = l8 = l9 = l10 = + l11 = l12 = l13 = l14 = l15 = l16 = l17 = l18 = l19 = 0; + } + + void check(long n, int i) { + Asserts.assertEQ(l0, n); n += i; + Asserts.assertEQ(l1, n); n += i; + Asserts.assertEQ(l2, n); n += i; + Asserts.assertEQ(l3, n); n += i; + Asserts.assertEQ(l4, n); n += i; + Asserts.assertEQ(l5, n); n += i; + Asserts.assertEQ(l6, n); n += i; + Asserts.assertEQ(l7, n); n += i; + Asserts.assertEQ(l8, n); n += i; + Asserts.assertEQ(l9, n); n += i; + Asserts.assertEQ(l10, n); n += i; + Asserts.assertEQ(l11, n); n += i; + Asserts.assertEQ(l12, n); n += i; + Asserts.assertEQ(l13, n); n += i; + Asserts.assertEQ(l14, n); n += i; + Asserts.assertEQ(l15, n); n += i; + Asserts.assertEQ(l16, n); n += i; + Asserts.assertEQ(l17, n); n += i; + Asserts.assertEQ(l18, n); n += i; + Asserts.assertEQ(l19, n); + } + } + + @LooselyConsistentValue + static value class TestValue { + int i; + @Strict + @NullRestricted + Big big; + + TestValue(int i, Big big) { + this.i = i; + this.big = big; + } + } + + @Test(compLevel = CompLevel.C1_SIMPLE) + public Big test7(TestValue[] array, int index) { + return array[index].big; + } + + @Run(test = "test7") + public void test7_verifier() { + TestValue[] array = (TestValue[])ValueClass.newNullRestrictedNonAtomicArray(TestValue.class, 7, new TestValue(0, new Big())); + Big b0 = test7(array, 3); + b0.check(0, 0); + TestValue tv = new TestValue(9, new Big(9)); + array[5] = tv; + Big b1 = test7(array, 5); + b1.check(9, 1); + } + + // Test 2nd level sub-element access to primitive field + @Test(compLevel = CompLevel.C1_SIMPLE) + public byte test8(MyValue1[] array, int index) { + return array[index].v2.y; + } + + @Run(test = "test8") + public void test8_verifier() { + MyValue1[] array = new MyValue1[23]; + MyValue2 mv2a = MyValue2.createWithFieldsInline(7, 63L, 8.9d); + MyValue2 mv2b = MyValue2.createWithFieldsInline(11, 69L, 17.3d); + MyValue1 mv1 = new MyValue1(1, 2L, (short)3, 4, null, mv2a, mv2b, mv2a, mv2b, 'z'); + array[19] = mv1; + byte b = test8(array, 19); + Asserts.assertEQ(b, (byte)11); + } + + + // Test optimizations for arrays of empty types + // (read/write are not performed, pre-allocated instance is used for reads) + // Most tests check that error conditions are still correctly handled + // (OOB, null pointer) + @LooselyConsistentValue + static value class EmptyType {} + + @Test(compLevel = CompLevel.C1_SIMPLE) + public EmptyType test9() { + EmptyType[] array = (EmptyType[])ValueClass.newNullRestrictedNonAtomicArray(EmptyType.class, 10, new EmptyType()); + return array[4]; + } + + @Run(test = "test9") + public void test9_verifier() { + EmptyType et = test9(); + Asserts.assertEQ(et, new EmptyType()); + } + + @Test(compLevel = CompLevel.C1_SIMPLE) + public EmptyType test10(EmptyType[] array) { + return array[0]; + } + + @Run(test = "test10") + public void test10_verifier() { + EmptyType[] array = (EmptyType[])ValueClass.newNullRestrictedNonAtomicArray(EmptyType.class, 16, new EmptyType()); + EmptyType et = test10(array); + Asserts.assertEQ(et, new EmptyType()); + } + + @Test(compLevel = CompLevel.C1_SIMPLE) + public EmptyType test11(EmptyType[] array, int index) { + return array[index]; + } + + @Run(test = "test11") + public void test11_verifier() { + Exception e = null; + EmptyType[] array = (EmptyType[])ValueClass.newNullRestrictedNonAtomicArray(EmptyType.class, 10, new EmptyType()); + try { + EmptyType et = test11(array, 11); + } catch (ArrayIndexOutOfBoundsException ex) { + e = ex; + } + Asserts.assertNotNull(e); + e = null; + try { + EmptyType et = test11(array, -1); + } catch (ArrayIndexOutOfBoundsException ex) { + e = ex; + } + Asserts.assertNotNull(e); + e = null; + try { + EmptyType et = test11(null, 1); + } catch (NullPointerException ex) { + e = ex; + } + Asserts.assertNotNull(e); + } + + @Test(compLevel = CompLevel.C1_SIMPLE) + public void test12(EmptyType[] array, int index, EmptyType value) { + array[index] = value; + } + + @Run(test = "test12") + public void test12_verifier() { + EmptyType empty = new EmptyType(); + EmptyType[] array = (EmptyType[])ValueClass.newNullRestrictedNonAtomicArray(EmptyType.class, 16, new EmptyType()); + test12(array, 2, empty); + Exception e = null; + try { + test12(null, 2, empty); + } catch(NullPointerException ex) { + e = ex; + } + Asserts.assertNotNull(e); + e = null; + try { + test12(array, 17, empty); + } catch(ArrayIndexOutOfBoundsException ex) { + e = ex; + } + Asserts.assertNotNull(e); + e = null; + try { + test12(array, -8, empty); + } catch(ArrayIndexOutOfBoundsException ex) { + e = ex; + } + Asserts.assertNotNull(e); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestC1ValueNumbering.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestC1ValueNumbering.java new file mode 100644 index 00000000000..a2fa74cdf7a --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestC1ValueNumbering.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +package compiler.valhalla.inlinetypes; + +import jdk.test.lib.Asserts; + +/* + * @test + * @summary Test value numbering behaves correctly with flat fields. + * @library /testlibrary /test/lib + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm -Xcomp -XX:TieredStopAtLevel=1 -ea + * -XX:CompileCommand=compileonly,compiler.valhalla.inlinetypes.TestC1ValueNumbering::* + * compiler.valhalla.inlinetypes.TestC1ValueNumbering + */ + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +public class TestC1ValueNumbering { + @LooselyConsistentValue + static value class Point { + int x; + int y; + + public Point() { + x = 0; + y = 0; + } + + public Point(int x, int y) { + this.x = x; + this.y = y; + } + } + + @Strict + @NullRestricted + Point p = new Point(0, 0); + + // Notes on test 1: + // 1 - asserts are important create several basic blocks (asserts create branches) + // 2 - local variables x, y must be read in the same block as the putfield + static void test1() { + Point p = new Point(4,5); + TestC1ValueNumbering test = new TestC1ValueNumbering(); + assert test.p.x == 0; + assert test.p.y == 0; + test.p = p; + int x = test.p.x; + int y = test.p.y; + Asserts.assertEQ(x, 4, "Bad field value"); + Asserts.assertEQ(y, 5, "Bad field value"); + } + + public static void main(String[] args) { + for (int i = 0; i < 10; i++) { + test1(); + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestC2CCalls.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestC2CCalls.java new file mode 100644 index 00000000000..d8327d90c26 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestC2CCalls.java @@ -0,0 +1,631 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @key randomness + * @summary Test value class calling convention with compiled to compiled calls. + * @library /test/lib /compiler/whitebox / + * @enablePreview + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * TestC2CCalls + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:-UseBimorphicInlining -Xbatch + * -XX:CompileCommand=compileonly,TestC2CCalls*::test* + * -XX:CompileCommand=dontinline,TestC2CCalls*::test* + * TestC2CCalls + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:-UseBimorphicInlining -Xbatch -XX:-ProfileInterpreter + * -XX:CompileCommand=compileonly,TestC2CCalls*::test* + * -XX:CompileCommand=dontinline,TestC2CCalls*::test* + * TestC2CCalls + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:-UseBimorphicInlining -Xbatch + * -XX:CompileCommand=compileonly,TestC2CCalls::test* + * -XX:CompileCommand=dontinline,TestC2CCalls*::test* + * TestC2CCalls + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:-UseBimorphicInlining -Xbatch -XX:-ProfileInterpreter + * -XX:CompileCommand=compileonly,TestC2CCalls::test* + * -XX:CompileCommand=dontinline,TestC2CCalls*::test* + * TestC2CCalls + */ + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; + +import jdk.test.lib.Asserts; +import jdk.test.lib.Utils; + +import jdk.test.whitebox.WhiteBox; + +public class TestC2CCalls { + public static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + public static final int COMP_LEVEL_FULL_OPTIMIZATION = 4; // C2 or JVMCI + public static final int rI = Utils.getRandomInstance().nextInt() % 1000; + + static value class OtherVal { + public int x; + + private OtherVal(int x) { + this.x = x; + } + } + + static interface MyInterface1 { + public MyInterface1 test1(OtherVal other, int y); + public MyInterface1 test2(OtherVal other1, OtherVal other2, int y); + public MyInterface1 test3(OtherVal other1, OtherVal other2, int y, boolean deopt); + public MyInterface1 test4(OtherVal other1, OtherVal other2, int y); + public MyInterface1 test5(OtherVal other1, OtherVal other2, int y); + public MyInterface1 test6(); + public MyInterface1 test7(int i1, int i2, int i3, int i4, int i5, int i6); + public MyInterface1 test8(int i1, int i2, int i3, int i4, int i5, int i6, int i7); + public MyInterface1 test9(MyValue3 other, int i1, int i2, int i3, int i4, int i5, int i6); + public MyInterface1 test10(MyValue4 other, int i1, int i2, int i3, int i4, int i5, int i6); + + public int getValue(); + } + + static value class MyValue1 implements MyInterface1 { + public int x; + + private MyValue1(int x) { + this.x = x; + } + + @Override + public int getValue() { + return x; + } + + @Override + public MyValue1 test1(OtherVal other, int y) { + return new MyValue1(x + other.x + y); + } + + @Override + public MyValue1 test2(OtherVal other1, OtherVal other2, int y) { + return new MyValue1(x + other1.x + other2.x + y); + } + + @Override + public MyValue1 test3(OtherVal other1, OtherVal other2, int y, boolean deopt) { + if (!deopt) { + return new MyValue1(x + other1.x + other2.x + y); + } else { + // Uncommon trap + return test1(other1, y); + } + } + + @Override + public MyValue1 test4(OtherVal other1, OtherVal other2, int y) { + return new MyValue1(x + other1.x + other2.x + y); + } + + @Override + public MyValue1 test5(OtherVal other1, OtherVal other2, int y) { + return new MyValue1(x + other1.x + other2.x + y); + } + + @Override + public MyValue1 test6() { + return this; + } + + @Override + public MyValue1 test7(int i1, int i2, int i3, int i4, int i5, int i6) { + return new MyValue1(x + i1 + i2 + i3 + i4 + i5 + i6); + } + + @Override + public MyValue1 test8(int i1, int i2, int i3, int i4, int i5, int i6, int i7) { + return new MyValue1(x + i1 + i2 + i3 + i4 + i5 + i6 + i7); + } + + public MyValue1 test9(MyValue3 other, int i1, int i2, int i3, int i4, int i5, int i6) { + return new MyValue1(x + (int)(other.d1 + other.d2 + other.d3 + other.d4) + i1 + i2 + i3 + i4 + i5 + i6); + } + + public MyValue1 test10(MyValue4 other, int i1, int i2, int i3, int i4, int i5, int i6) { + return new MyValue1(x + other.x1 + other.x2 + other.x3 + other.x4 + i1 + i2 + i3 + i4 + i5 + i6); + } + } + + static value class MyValue2 implements MyInterface1 { + public int x; + + private MyValue2(int x) { + this.x = x; + } + + @Override + public int getValue() { + return x; + } + + @Override + public MyValue2 test1(OtherVal other, int y) { + return new MyValue2(x + other.x + y); + } + + @Override + public MyValue2 test2(OtherVal other1, OtherVal other2, int y) { + return new MyValue2(x + other1.x + other2.x + y); + } + + @Override + public MyValue2 test3(OtherVal other1, OtherVal other2, int y, boolean deopt) { + if (!deopt) { + return new MyValue2(x + other1.x + other2.x + y); + } else { + // Uncommon trap + return test1(other1, y); + } + } + + @Override + public MyValue2 test4(OtherVal other1, OtherVal other2, int y) { + return new MyValue2(x + other1.x + other2.x + y); + } + + @Override + public MyValue2 test5(OtherVal other1, OtherVal other2, int y) { + return new MyValue2(x + other1.x + other2.x + y); + } + + @Override + public MyValue2 test6() { + return this; + } + + @Override + public MyValue2 test7(int i1, int i2, int i3, int i4, int i5, int i6) { + return new MyValue2(x + i1 + i2 + i3 + i4 + i5 + i6); + } + + @Override + public MyValue2 test8(int i1, int i2, int i3, int i4, int i5, int i6, int i7) { + return new MyValue2(x + i1 + i2 + i3 + i4 + i5 + i6 + i7); + } + + public MyValue2 test9(MyValue3 other, int i1, int i2, int i3, int i4, int i5, int i6) { + return new MyValue2(x + (int)(other.d1 + other.d2 + other.d3 + other.d4) + i1 + i2 + i3 + i4 + i5 + i6); + } + + public MyValue2 test10(MyValue4 other, int i1, int i2, int i3, int i4, int i5, int i6) { + return new MyValue2(x + other.x1 + other.x2 + other.x3 + other.x4 + i1 + i2 + i3 + i4 + i5 + i6); + } + } + + static value class MyValue3 implements MyInterface1 { + public double d1; + public double d2; + public double d3; + public double d4; + + private MyValue3(double d) { + this.d1 = d; + this.d2 = d; + this.d3 = d; + this.d4 = d; + } + + @Override + public int getValue() { + return (int)d4; + } + + @Override + public MyValue3 test1(OtherVal other, int y) { return new MyValue3(0); } + @Override + public MyValue3 test2(OtherVal other1, OtherVal other2, int y) { return new MyValue3(0); } + @Override + public MyValue3 test3(OtherVal other1, OtherVal other2, int y, boolean deopt) { return new MyValue3(0); } + @Override + public MyValue3 test4(OtherVal other1, OtherVal other2, int y) { return new MyValue3(0); } + @Override + public MyValue3 test5(OtherVal other1, OtherVal other2, int y) { return new MyValue3(0); } + @Override + public MyValue3 test6() { return new MyValue3(0); } + + @Override + public MyValue3 test7(int i1, int i2, int i3, int i4, int i5, int i6) { + return new MyValue3(d1 + d2 + d3 + d4 + i1 + i2 + i3 + i4 + i5 + i6); + } + + @Override + public MyValue3 test8(int i1, int i2, int i3, int i4, int i5, int i6, int i7) { + return new MyValue3(d1 + d2 + d3 + d4 + i1 + i2 + i3 + i4 + i5 + i6 + i7); + } + + public MyValue3 test9(MyValue3 other, int i1, int i2, int i3, int i4, int i5, int i6) { + return new MyValue3(d1 + d2 + d3 + d4 + other.d1 + other.d2 + other.d3 + other.d4 + i1 + i2 + i3 + i4 + i5 + i6); + } + + public MyValue3 test10(MyValue4 other, int i1, int i2, int i3, int i4, int i5, int i6) { + return new MyValue3(d1 + d2 + d3 + d4 + other.x1 + other.x2 + other.x3 + other.x4 + i1 + i2 + i3 + i4 + i5 + i6); + } + } + + static value class MyValue4 implements MyInterface1 { + public int x1; + public int x2; + public int x3; + public int x4; + + private MyValue4(int i) { + this.x1 = i; + this.x2 = i; + this.x3 = i; + this.x4 = i; + } + + @Override + public int getValue() { + return x4; + } + + @Override + public MyValue4 test1(OtherVal other, int y) { return new MyValue4(0); } + @Override + public MyValue4 test2(OtherVal other1, OtherVal other2, int y) { return new MyValue4(0); } + @Override + public MyValue4 test3(OtherVal other1, OtherVal other2, int y, boolean deopt) { return new MyValue4(0); } + @Override + public MyValue4 test4(OtherVal other1, OtherVal other2, int y) { return new MyValue4(0); } + @Override + public MyValue4 test5(OtherVal other1, OtherVal other2, int y) { return new MyValue4(0); } + @Override + public MyValue4 test6() { return new MyValue4(0); } + + @Override + public MyValue4 test7(int i1, int i2, int i3, int i4, int i5, int i6) { + return new MyValue4(x1 + x2 + x3 + x4 + i1 + i2 + i3 + i4 + i5 + i6); + } + + @Override + public MyValue4 test8(int i1, int i2, int i3, int i4, int i5, int i6, int i7) { + return new MyValue4(x1 + x2 + x3 + x4 + i1 + i2 + i3 + i4 + i5 + i6 + i7); + } + + public MyValue4 test9(MyValue3 other, int i1, int i2, int i3, int i4, int i5, int i6) { + return new MyValue4(x1 + x2 + x3 + x4 + (int)(other.d1 + other.d2 + other.d3 + other.d4) + i1 + i2 + i3 + i4 + i5 + i6); + } + + public MyValue4 test10(MyValue4 other, int i1, int i2, int i3, int i4, int i5, int i6) { + return new MyValue4(x1 + x2 + x3 + x4 + other.x1 + other.x2 + other.x3 + other.x4 + i1 + i2 + i3 + i4 + i5 + i6); + } + } + + static class MyObject implements MyInterface1 { + private final int x; + + private MyObject(int x) { + this.x = x; + } + + @Override + public int getValue() { + return x; + } + + @Override + public MyObject test1(OtherVal other, int y) { + return new MyObject(x + other.x + y); + } + + @Override + public MyObject test2(OtherVal other1, OtherVal other2, int y) { + return new MyObject(x + other1.x + other2.x + y); + } + + @Override + public MyObject test3(OtherVal other1, OtherVal other2, int y, boolean deopt) { + if (!deopt) { + return new MyObject(x + other1.x + other2.x + y); + } else { + // Uncommon trap + return test1(other1, y); + } + } + + @Override + public MyObject test4(OtherVal other1, OtherVal other2, int y) { + return new MyObject(x + other1.x + other2.x + y); + } + + @Override + public MyObject test5(OtherVal other1, OtherVal other2, int y) { + return new MyObject(x + other1.x + other2.x + y); + } + + @Override + public MyObject test6() { + return this; + } + + @Override + public MyObject test7(int i1, int i2, int i3, int i4, int i5, int i6) { + return new MyObject(x + i1 + i2 + i3 + i4 + i5 + i6); + } + + @Override + public MyObject test8(int i1, int i2, int i3, int i4, int i5, int i6, int i7) { + return new MyObject(x + i1 + i2 + i3 + i4 + i5 + i6 + i7); + } + + public MyObject test9(MyValue3 other, int i1, int i2, int i3, int i4, int i5, int i6) { + return new MyObject(x + (int)(other.d1 + other.d2 + other.d3 + other.d4) + i1 + i2 + i3 + i4 + i5 + i6); + } + + public MyObject test10(MyValue4 other, int i1, int i2, int i3, int i4, int i5, int i6) { + return new MyObject(x + other.x1 + other.x2 + other.x3 + other.x4 + i1 + i2 + i3 + i4 + i5 + i6); + } + } + + // Test calling methods with value class arguments through an interface + public static int test1(MyInterface1 intf, OtherVal other, int y) { + return intf.test1(other, y).getValue(); + } + + public static int test2(MyInterface1 intf, OtherVal other, int y) { + return intf.test2(other, other, y).getValue(); + } + + // Test mixing null-tolerant and null-free value class arguments + public static int test3(MyValue1 vt, OtherVal other, int y) { + return vt.test2(other, other, y).getValue(); + } + + public static int test4(MyObject obj, OtherVal other, int y) { + return obj.test2(other, other, y).getValue(); + } + + // Optimized interface call with value class receiver + public static int test5(MyInterface1 intf, OtherVal other, int y) { + return intf.test1(other, y).getValue(); + } + + public static int test6(MyInterface1 intf, OtherVal other, int y) { + return intf.test2(other, other, y).getValue(); + } + + // Optimized interface call with object receiver + public static int test7(MyInterface1 intf, OtherVal other, int y) { + return intf.test1(other, y).getValue(); + } + + public static int test8(MyInterface1 intf, OtherVal other, int y) { + return intf.test2(other, other, y).getValue(); + } + + // Interface calls with deoptimized callee + public static int test9(MyInterface1 intf, OtherVal other, int y, boolean deopt) { + return intf.test3(other, other, y, deopt).getValue(); + } + + public static int test10(MyInterface1 intf, OtherVal other, int y, boolean deopt) { + return intf.test3(other, other, y, deopt).getValue(); + } + + // Optimized interface calls with deoptimized callee + public static int test11(MyInterface1 intf, OtherVal other, int y, boolean deopt) { + return intf.test3(other, other, y, deopt).getValue(); + } + + public static int test12(MyInterface1 intf, OtherVal other, int y, boolean deopt) { + return intf.test3(other, other, y, deopt).getValue(); + } + + public static int test13(MyInterface1 intf, OtherVal other, int y, boolean deopt) { + return intf.test3(other, other, y, deopt).getValue(); + } + + public static int test14(MyInterface1 intf, OtherVal other, int y, boolean deopt) { + return intf.test3(other, other, y, deopt).getValue(); + } + + // Interface calls without warmed up / compiled callees + public static int test15(MyInterface1 intf, OtherVal other, int y) { + return intf.test4(other, other, y).getValue(); + } + + public static int test16(MyInterface1 intf, OtherVal other, int y) { + return intf.test5(other, other, y).getValue(); + } + + // Interface call with no arguments + public static int test17(MyInterface1 intf) { + return intf.test6().getValue(); + } + + // Calls that require stack extension + public static int test18(MyInterface1 intf, int y) { + return intf.test7(y, y, y, y, y, y).getValue(); + } + + public static int test19(MyInterface1 intf, int y) { + return intf.test8(y, y, y, y, y, y, y).getValue(); + } + + public static int test20(MyInterface1 intf, MyValue3 v, int y) { + return intf.test9(v, y, y, y, y, y, y).getValue(); + } + + public static int test21(MyInterface1 intf, MyValue4 v, int y) { + return intf.test10(v, y, y, y, y, y, y).getValue(); + } + + public static void main(String[] args) { + // Sometimes, exclude some methods from compilation with C2 to stress test the calling convention + if (Utils.getRandomInstance().nextBoolean()) { + ArrayList methods = new ArrayList(); + Collections.addAll(methods, MyValue1.class.getDeclaredMethods()); + Collections.addAll(methods, MyValue2.class.getDeclaredMethods()); + Collections.addAll(methods, MyValue3.class.getDeclaredMethods()); + Collections.addAll(methods, MyValue4.class.getDeclaredMethods()); + Collections.addAll(methods, MyObject.class.getDeclaredMethods()); + Collections.addAll(methods, TestC2CCalls.class.getDeclaredMethods()); + System.out.println("Excluding methods from C2 compilation:"); + for (Method m : methods) { + if (Utils.getRandomInstance().nextBoolean()) { + System.out.println(m); + WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_FULL_OPTIMIZATION, false); + } + } + } + + MyValue1 val1 = new MyValue1(rI); + MyValue2 val2 = new MyValue2(rI+1); + MyValue3 val3 = new MyValue3(rI+2); + MyValue4 val4 = new MyValue4(rI+3); + OtherVal other = new OtherVal(rI+4); + MyObject obj = new MyObject(rI+5); + + // Make sure callee methods are compiled + for (int i = 0; i < 10_000; ++i) { + Asserts.assertEQ(val1.test1(other, rI).getValue(), val1.x + other.x + rI); + Asserts.assertEQ(val2.test1(other, rI).getValue(), val2.x + other.x + rI); + Asserts.assertEQ(obj.test1(other, rI).getValue(), obj.x + other.x + rI); + Asserts.assertEQ(val1.test2(other, other, rI).getValue(), val1.x + 2*other.x + rI); + Asserts.assertEQ(val2.test2(other, other, rI).getValue(), val2.x + 2*other.x + rI); + Asserts.assertEQ(obj.test2(other, other, rI).getValue(), obj.x + 2*other.x + rI); + Asserts.assertEQ(val1.test3(other, other, rI, false).getValue(), val1.x + 2*other.x + rI); + Asserts.assertEQ(val2.test3(other, other, rI, false).getValue(), val2.x + 2*other.x + rI); + Asserts.assertEQ(obj.test3(other, other, rI, false).getValue(), obj.x + 2*other.x + rI); + Asserts.assertEQ(val1.test7(rI, rI, rI, rI, rI, rI).getValue(), val1.x + 6*rI); + Asserts.assertEQ(val2.test7(rI, rI, rI, rI, rI, rI).getValue(), val2.x + 6*rI); + Asserts.assertEQ(val3.test7(rI, rI, rI, rI, rI, rI).getValue(), (int)(4*val3.d1 + 6*rI)); + Asserts.assertEQ(val4.test7(rI, rI, rI, rI, rI, rI).getValue(), (int)(4*val4.x1 + 6*rI)); + Asserts.assertEQ(obj.test7(rI, rI, rI, rI, rI, rI).getValue(), obj.x + 6*rI); + Asserts.assertEQ(val1.test8(rI, rI, rI, rI, rI, rI, rI).getValue(), val1.x + 7*rI); + Asserts.assertEQ(val2.test8(rI, rI, rI, rI, rI, rI, rI).getValue(), val2.x + 7*rI); + Asserts.assertEQ(val3.test8(rI, rI, rI, rI, rI, rI, rI).getValue(), (int)(4*val3.d1 + 7*rI)); + Asserts.assertEQ(val4.test8(rI, rI, rI, rI, rI, rI, rI).getValue(), (int)(4*val4.x1 + 7*rI)); + Asserts.assertEQ(obj.test8(rI, rI, rI, rI, rI, rI, rI).getValue(), obj.x + 7*rI); + Asserts.assertEQ(val1.test9(val3, rI, rI, rI, rI, rI, rI).getValue(), (int)(val1.x + 4*val3.d1 + 6*rI)); + Asserts.assertEQ(val2.test9(val3, rI, rI, rI, rI, rI, rI).getValue(), (int)(val2.x + 4*val3.d1 + 6*rI)); + Asserts.assertEQ(val3.test9(val3, rI, rI, rI, rI, rI, rI).getValue(), (int)(4*val3.d1 + 4*val3.d1 + 6*rI)); + Asserts.assertEQ(val4.test9(val3, rI, rI, rI, rI, rI, rI).getValue(), (int)(4*val4.x1 + 4*val3.d1 + 6*rI)); + Asserts.assertEQ(obj.test9(val3, rI, rI, rI, rI, rI, rI).getValue(), (int)(obj.x + 4*val3.d1 + 6*rI)); + Asserts.assertEQ(val1.test10(val4, rI, rI, rI, rI, rI, rI).getValue(), (int)(val1.x + 4*val4.x1 + 6*rI)); + Asserts.assertEQ(val2.test10(val4, rI, rI, rI, rI, rI, rI).getValue(), (int)(val2.x + 4*val4.x1 + 6*rI)); + Asserts.assertEQ(val3.test10(val4, rI, rI, rI, rI, rI, rI).getValue(), (int)(4*val3.d1 + 4*val4.x1 + 6*rI)); + Asserts.assertEQ(val4.test10(val4, rI, rI, rI, rI, rI, rI).getValue(), (int)(4*val4.x1 + 4*val4.x1 + 6*rI)); + Asserts.assertEQ(obj.test10(val4, rI, rI, rI, rI, rI, rI).getValue(), (int)(obj.x + 4*val4.x1 + 6*rI)); + } + + // Polute call profile + for (int i = 0; i < 100; ++i) { + Asserts.assertEQ(test15(val1, other, rI), val1.x + 2*other.x + rI); + Asserts.assertEQ(test16(obj, other, rI), obj.x + 2*other.x + rI); + Asserts.assertEQ(test17(obj), obj.x); + } + + // Trigger compilation of caller methods + for (int i = 0; i < 100_000; ++i) { + val1 = new MyValue1(rI+i); + val2 = new MyValue2(rI+i+1); + val3 = new MyValue3(rI+i+2); + val4 = new MyValue4(rI+i+3); + other = new OtherVal(rI+i+4); + obj = new MyObject(rI+i+5); + + Asserts.assertEQ(test1(val1, other, rI), val1.x + other.x + rI); + Asserts.assertEQ(test1(obj, other, rI), obj.x + other.x + rI); + Asserts.assertEQ(test2(obj, other, rI), obj.x + 2*other.x + rI); + Asserts.assertEQ(test2(val1, other, rI), val1.x + 2*other.x + rI); + Asserts.assertEQ(test3(val1, other, rI), val1.x + 2*other.x + rI); + Asserts.assertEQ(test4(obj, other, rI), obj.x + 2*other.x + rI); + Asserts.assertEQ(test5(val1, other, rI), val1.x + other.x + rI); + Asserts.assertEQ(test6(val1, other, rI), val1.x + 2*other.x + rI); + Asserts.assertEQ(test7(obj, other, rI), obj.x + other.x + rI); + Asserts.assertEQ(test8(obj, other, rI), obj.x + 2*other.x + rI); + Asserts.assertEQ(test9(val1, other, rI, false), val1.x + 2*other.x + rI); + Asserts.assertEQ(test9(obj, other, rI, false), obj.x + 2*other.x + rI); + Asserts.assertEQ(test10(val1, other, rI, false), val1.x + 2*other.x + rI); + Asserts.assertEQ(test10(obj, other, rI, false), obj.x + 2*other.x + rI); + Asserts.assertEQ(test11(val1, other, rI, false), val1.x + 2*other.x + rI); + Asserts.assertEQ(test12(val1, other, rI, false), val1.x + 2*other.x + rI); + Asserts.assertEQ(test13(obj, other, rI, false), obj.x + 2*other.x + rI); + Asserts.assertEQ(test14(obj, other, rI, false), obj.x + 2*other.x + rI); + Asserts.assertEQ(test15(obj, other, rI), obj.x + 2*other.x + rI); + Asserts.assertEQ(test16(val1, other, rI), val1.x + 2*other.x + rI); + Asserts.assertEQ(test17(val1), val1.x); + Asserts.assertEQ(test18(val1, rI), val1.x + 6*rI); + Asserts.assertEQ(test18(val2, rI), val2.x + 6*rI); + Asserts.assertEQ(test18(val3, rI), (int)(4*val3.d1 + 6*rI)); + Asserts.assertEQ(test18(val4, rI), 4*val4.x1 + 6*rI); + Asserts.assertEQ(test18(obj, rI), obj.x + 6*rI); + Asserts.assertEQ(test19(val1, rI), val1.x + 7*rI); + Asserts.assertEQ(test19(val2, rI), val2.x + 7*rI); + Asserts.assertEQ(test19(val3, rI), (int)(4*val3.d1 + 7*rI)); + Asserts.assertEQ(test19(val4, rI), 4*val4.x1 + 7*rI); + Asserts.assertEQ(test19(obj, rI), obj.x + 7*rI); + Asserts.assertEQ(test20(val1, val3, rI), (int)(val1.x + 4*val3.d1 + 6*rI)); + Asserts.assertEQ(test20(val2, val3, rI), (int)(val2.x + 4*val3.d1 + 6*rI)); + Asserts.assertEQ(test20(val3, val3, rI), (int)(4*val3.d1 + 4*val3.d1 + 6*rI)); + Asserts.assertEQ(test20(val4, val3, rI), (int)(4*val4.x1 + 4*val3.d1 + 6*rI)); + Asserts.assertEQ(test20(obj, val3, rI), (int)(obj.x + 4*val3.d1 + 6*rI)); + Asserts.assertEQ(test21(val1, val4, rI), val1.x + 4*val4.x1 + 6*rI); + Asserts.assertEQ(test21(val2, val4, rI), val2.x + 4*val4.x1 + 6*rI); + Asserts.assertEQ(test21(val3, val4, rI), (int)(4*val3.d1 + 4*val4.x1 + 6*rI)); + Asserts.assertEQ(test21(val4, val4, rI), 4*val4.x1 + 4*val4.x1 + 6*rI); + Asserts.assertEQ(test21(obj, val4, rI), obj.x + 4*val4.x1 + 6*rI); + } + + // Trigger deoptimization + Asserts.assertEQ(val1.test3(other, other, rI, true).getValue(), val1.x + other.x + rI); + Asserts.assertEQ(obj.test3(other, other, rI, true).getValue(), obj.x + other.x + rI); + + // Check results of methods still calling the deoptimized methods + Asserts.assertEQ(test9(val1, other, rI, false), val1.x + 2*other.x + rI); + Asserts.assertEQ(test9(obj, other, rI, false), obj.x + 2*other.x + rI); + Asserts.assertEQ(test10(obj, other, rI, false), obj.x + 2*other.x + rI); + Asserts.assertEQ(test10(val1, other, rI, false), val1.x + 2*other.x + rI); + Asserts.assertEQ(test11(val1, other, rI, false), val1.x + 2*other.x + rI); + Asserts.assertEQ(test11(obj, other, rI, false), obj.x + 2*other.x + rI); + Asserts.assertEQ(test12(obj, other, rI, false), obj.x + 2*other.x + rI); + Asserts.assertEQ(test12(val1, other, rI, false), val1.x + 2*other.x + rI); + Asserts.assertEQ(test13(val1, other, rI, false), val1.x + 2*other.x + rI); + Asserts.assertEQ(test13(obj, other, rI, false), obj.x + 2*other.x + rI); + Asserts.assertEQ(test14(obj, other, rI, false), obj.x + 2*other.x + rI); + Asserts.assertEQ(test14(val1, other, rI, false), val1.x + 2*other.x + rI); + + // Check with unexpected arguments + Asserts.assertEQ(test1(val2, other, rI), val2.x + other.x + rI); + Asserts.assertEQ(test2(val2, other, rI), val2.x + 2*other.x + rI); + Asserts.assertEQ(test5(val2, other, rI), val2.x + other.x + rI); + Asserts.assertEQ(test6(val2, other, rI), val2.x + 2*other.x + rI); + Asserts.assertEQ(test7(val1, other, rI), val1.x + other.x + rI); + Asserts.assertEQ(test8(val1, other, rI), val1.x + 2*other.x + rI); + Asserts.assertEQ(test15(val1, other, rI), val1.x + 2*other.x + rI); + Asserts.assertEQ(test16(obj, other, rI), obj.x + 2*other.x + rI); + Asserts.assertEQ(test17(obj), obj.x); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestCallingConvention.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestCallingConvention.java new file mode 100644 index 00000000000..e185e6d2d36 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestCallingConvention.java @@ -0,0 +1,1396 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.*; +import jdk.test.lib.Asserts; +import jdk.test.whitebox.WhiteBox; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; + +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.ALLOC_OF_MYVALUE_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.LOAD_OF_ANY_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.STORE_OF_ANY_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypes.*; + +import static compiler.lib.ir_framework.IRNode.ALLOC; +import static compiler.lib.ir_framework.IRNode.PREDICATE_TRAP; +import static compiler.lib.ir_framework.IRNode.UNSTABLE_IF_TRAP; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +/* + * @test + * @key randomness + * @summary Test value class calling convention optimizations. + * @library /test/lib / + * @requires (os.simpleArch == "x64" | os.simpleArch == "aarch64") + * @build jdk.test.whitebox.WhiteBox + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=450 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI compiler.valhalla.inlinetypes.TestCallingConvention + */ + +@ForceCompileClassInitializer +public class TestCallingConvention { + + private final static WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + + static { + try { + Class clazz = TestCallingConvention.class; + MethodHandles.Lookup lookup = MethodHandles.lookup(); + + MethodType mt = MethodType.methodType(MyValue2.class, boolean.class); + test32_mh = lookup.findVirtual(clazz, "test32_interp", mt); + + mt = MethodType.methodType(Object.class, boolean.class); + test33_mh = lookup.findVirtual(clazz, "test33_interp", mt); + + mt = MethodType.methodType(int.class); + test37_mh = lookup.findVirtual(Test37Value.class, "test", mt); + + mt = MethodType.methodType(MyValue2.class); + test54_mh = lookup.findVirtual(clazz, "test54_callee", mt); + + mt = MethodType.methodType(MyValue2.class, boolean.class); + test56_mh = lookup.findVirtual(clazz, "test56_callee", mt); + } catch (NoSuchMethodException | IllegalAccessException e) { + e.printStackTrace(); + throw new RuntimeException("Method handle lookup failed"); + } + } + + public static void main(String[] args) { + + Scenario[] scenarios = InlineTypes.DEFAULT_SCENARIOS; + // Don't generate bytecodes but call through runtime for reflective calls + scenarios[0].addFlags("-Dsun.reflect.inflationThreshold=10000"); + scenarios[1].addFlags("-Dsun.reflect.inflationThreshold=10000"); + scenarios[3].addFlags("-XX:-UseArrayFlattening"); + scenarios[4].addFlags("-XX:-UseTLAB"); + + InlineTypes.getFramework() + .addScenarios(scenarios) + .addHelperClasses(MyValue1.class, + MyValue2.class, + MyValue2Inline.class, + MyValue3.class, + MyValue3Inline.class, + MyValue4.class) + .start(); + } + + // Helper methods and classes + + private void deoptimize(String name, Class... params) { + try { + TestFramework.deoptimize(getClass().getDeclaredMethod(name, params)); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + // Test interpreter to compiled code with various signatures + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test1(MyValue2 v) { + return v.hash(); + } + + @Run(test = "test1") + public void test1_verifier() { + MyValue2 v = MyValue2.createWithFieldsInline(rI, rD); + long result = test1(v); + Asserts.assertEQ(result, v.hashInterpreted()); + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test2(int i1, MyValue2 v, int i2) { + return v.hash() + i1 - i2; + } + + @Run(test = "test2") + public void test2_verifier() { + MyValue2 v = MyValue2.createWithFieldsInline(rI, rD); + long result = test2(rI, v, 2*rI); + Asserts.assertEQ(result, v.hashInterpreted() - rI); + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test3(long l1, MyValue2 v, long l2) { + return v.hash() + l1 - l2; + } + + @Run(test = "test3") + public void test3_verifier() { + MyValue2 v = MyValue2.createWithFieldsInline(rI, rD); + long result = test3(rL, v, 2*rL); + Asserts.assertEQ(result, v.hashInterpreted() - rL); + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test4(int i, MyValue2 v, long l) { + return v.hash() + i + l; + } + + @Run(test = "test4") + public void test4_verifier() { + MyValue2 v = MyValue2.createWithFieldsInline(rI, rD); + long result = test4(rI, v, rL); + Asserts.assertEQ(result, v.hashInterpreted() + rL + rI); + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test5(long l, MyValue2 v, int i) { + return v.hash() + i + l; + } + + @Run(test = "test5") + public void test5_verifier() { + MyValue2 v = MyValue2.createWithFieldsInline(rI, rD); + long result = test5(rL, v, rI); + Asserts.assertEQ(result, v.hashInterpreted() + rL + rI); + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test6(long l, MyValue1 v1, int i, MyValue2 v2) { + return v1.hash() + i + l + v2.hash(); + } + + @Run(test = "test6") + public void test6_verifier() { + MyValue1 v1 = MyValue1.createWithFieldsDontInline(rI, rL); + MyValue2 v2 = MyValue2.createWithFieldsInline(rI, rD); + long result = test6(rL, v1, rI, v2); + Asserts.assertEQ(result, v1.hashInterpreted() + rL + rI + v2.hashInterpreted()); + } + + // Test compiled code to interpreter with various signatures + @DontCompile + public long test7_interp(MyValue2 v) { + return v.hash(); + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test7(MyValue2 v) { + return test7_interp(v); + } + + @Run(test = "test7") + public void test7_verifier() { + MyValue2 v = MyValue2.createWithFieldsInline(rI, rD); + long result = test7(v); + Asserts.assertEQ(result, v.hashInterpreted()); + } + + @DontCompile + public long test8_interp(int i1, MyValue2 v, int i2) { + return v.hash() + i1 - i2; + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test8(int i1, MyValue2 v, int i2) { + return test8_interp(i1, v, i2); + } + + @Run(test = "test8") + public void test8_verifier() { + MyValue2 v = MyValue2.createWithFieldsInline(rI, rD); + long result = test8(rI, v, 2*rI); + Asserts.assertEQ(result, v.hashInterpreted() - rI); + } + + @DontCompile + public long test9_interp(long l1, MyValue2 v, long l2) { + return v.hash() + l1 - l2; + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test9(long l1, MyValue2 v, long l2) { + return test9_interp(l1, v, l2); + } + + @Run(test = "test9") + public void test9_verifier() { + MyValue2 v = MyValue2.createWithFieldsInline(rI, rD); + long result = test9(rL, v, 2*rL); + Asserts.assertEQ(result, v.hashInterpreted() - rL); + } + + @DontCompile + public long test10_interp(int i, MyValue2 v, long l) { + return v.hash() + i + l; + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test10(int i, MyValue2 v, long l) { + return test10_interp(i, v, l); + } + + @Run(test = "test10") + public void test10_verifier() { + MyValue2 v = MyValue2.createWithFieldsInline(rI, rD); + long result = test10(rI, v, rL); + Asserts.assertEQ(result, v.hashInterpreted() + rL + rI); + } + + @DontCompile + public long test11_interp(long l, MyValue2 v, int i) { + return v.hash() + i + l; + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test11(long l, MyValue2 v, int i) { + return test11_interp(l, v, i); + } + + @Run(test = "test11") + public void test11_verifier() { + MyValue2 v = MyValue2.createWithFieldsInline(rI, rD); + long result = test11(rL, v, rI); + Asserts.assertEQ(result, v.hashInterpreted() + rL + rI); + } + + @DontCompile + public long test12_interp(long l, MyValue1 v1, int i, MyValue2 v2) { + return v1.hash() + i + l + v2.hash(); + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test12(long l, MyValue1 v1, int i, MyValue2 v2) { + return test12_interp(l, v1, i, v2); + } + + @Run(test = "test12") + public void test12_verifier() { + MyValue1 v1 = MyValue1.createWithFieldsDontInline(rI, rL); + MyValue2 v2 = MyValue2.createWithFieldsInline(rI, rD); + long result = test12(rL, v1, rI, v2); + Asserts.assertEQ(result, v1.hashInterpreted() + rL + rI + v2.hashInterpreted()); + } + + // Test that debug info at a call is correct + @DontCompile + public long test13_interp(MyValue2 v, MyValue1[] va, boolean deopt) { + if (deopt) { + // uncommon trap + deoptimize("test13", MyValue2.class, MyValue1[].class, boolean.class, long.class); + } + return v.hash() + va[0].hash() + va[1].hash(); + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test13(MyValue2 v, MyValue1[] va, boolean flag, long l) { + return test13_interp(v, va, flag) + l; + } + + @Run(test = "test13") + public void test13_verifier(RunInfo info) { + MyValue2 v = MyValue2.createWithFieldsInline(rI, rD); + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 2, MyValue1.DEFAULT); + va[0] = MyValue1.createWithFieldsDontInline(rI, rL); + va[1] = MyValue1.createWithFieldsDontInline(rI, rL); + long result = test13(v, va, !info.isWarmUp(), rL); + Asserts.assertEQ(result, v.hashInterpreted() + va[0].hash() + va[1].hash() + rL); + } + + // Test deoptimization at call return with value object returned in registers + @DontCompile + public MyValue2 test14_interp(boolean deopt) { + if (deopt) { + // uncommon trap + deoptimize("test14", boolean.class); + } + return MyValue2.createWithFieldsInline(rI, rD); + } + + @Test + public MyValue2 test14(boolean flag) { + return test14_interp(flag); + } + + @Run(test = "test14") + public void test14_verifier(RunInfo info) { + MyValue2 result = test14(!info.isWarmUp()); + MyValue2 v = MyValue2.createWithFieldsInline(rI, rD); + Asserts.assertEQ(result.hash(), v.hash()); + } + + // Return value objects in registers from interpreter -> compiled + @Strict + @NullRestricted + final MyValue3 test15_vt = MyValue3.create(); + + @DontCompile + public MyValue3 test15_interp() { + return test15_vt; + } + + @Test + @IR(applyIf = {"InlineTypeReturnedAsFields", "true"}, + failOn = {ALLOC_OF_MYVALUE_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public MyValue3 test15() { + return test15_interp(); + } + + @Run(test = "test15") + public void test15_verifier() { + test15_vt.verify(test15()); + } + + // Return value objects in registers from compiled -> interpreter + @Strict + @NullRestricted + final MyValue3 test16_vt = MyValue3.create(); + + @Test + @IR(applyIf = {"InlineTypeReturnedAsFields", "true"}, + failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public MyValue3 test16() { + return test16_vt; + } + + @Run(test = "test16") + public void test16_verifier() { + MyValue3 vt = test16(); + test16_vt.verify(vt); + } + + // Return value objects in registers from compiled -> compiled + @Strict + @NullRestricted + final MyValue3 test17_vt = MyValue3.create(); + + @DontInline + public MyValue3 test17_comp() { + return test17_vt; + } + + @Test + @IR(applyIf = {"InlineTypeReturnedAsFields", "true"}, + failOn = {ALLOC_OF_MYVALUE_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public MyValue3 test17() { + return test17_comp(); + } + + @Run(test = "test17") + public void test17_verifier(RunInfo info) throws Exception { + Method helper_m = getClass().getDeclaredMethod("test17_comp"); + if (!info.isWarmUp() && TestFramework.isCompiled(helper_m)) { + TestFramework.compile(helper_m, CompLevel.C2); + TestFramework.assertCompiledByC2(helper_m); + } + + test17_vt.verify(test17()); + } + + // Same tests as above but with a value class that cannot be returned in registers + + // Return value objects in registers from interpreter -> compiled + @Strict + @NullRestricted + final MyValue4 test18_vt = MyValue4.create(); + + @DontCompile + public MyValue4 test18_interp() { + return test18_vt; + } + + MyValue4 test18_vt2; + + @Test + public void test18() { + test18_vt2 = test18_interp(); + } + + @Run(test = "test18") + public void test18_verifier() { + test18(); + test18_vt.verify(test18_vt2); + } + + // Return value objects in registers from compiled -> interpreter + @Strict + @NullRestricted + final MyValue4 test19_vt = MyValue4.create(); + + @Test + public MyValue4 test19() { + return test19_vt; + } + + @Run(test = "test19") + public void test19_verifier() { + MyValue4 vt = test19(); + test19_vt.verify(vt); + } + + // Return value objects in registers from compiled -> compiled + @Strict + @NullRestricted + final MyValue4 test20_vt = MyValue4.create(); + + @DontInline + public MyValue4 test20_comp() { + return test20_vt; + } + + MyValue4 test20_vt2; + + @Test + public void test20() { + test20_vt2 = test20_comp(); + } + + @Run(test = "test20") + public void test20_verifier(RunInfo info) throws Exception { + Method helper_m = getClass().getDeclaredMethod("test20_comp"); + if (!info.isWarmUp() && TestFramework.isCompiled(helper_m)) { + TestFramework.compile(helper_m, CompLevel.C2); + TestFramework.assertCompiledByC2(helper_m); + } + test20(); + test20_vt.verify(test20_vt2); + } + + // Test no result from inlined method for incremental inlining + @Strict + @NullRestricted + final MyValue3 test21_vt = MyValue3.create(); + + public MyValue3 test21_inlined() { + throw new RuntimeException(); + } + + @Test + public MyValue3 test21() { + try { + return test21_inlined(); + } catch (RuntimeException ex) { + return test21_vt; + } + } + + @Run(test = "test21") + public void test21_verifier() { + MyValue3 vt = test21(); + test21_vt.verify(vt); + } + + // Test returning a non-flattened value object as fields + MyValue3 test22_vt = MyValue3.create(); + + @Test + public MyValue3 test22() { + return (MyValue3) test22_vt; + } + + @Run(test = "test22") + public void test22_verifier() { + MyValue3 vt = test22(); + test22_vt.verify(vt); + } + + // Test calling a method that has circular register/stack dependencies when unpacking value class arguments + @LooselyConsistentValue + static value class TestValue23 { + double f1; + + TestValue23(double val) { + f1 = val; + } + } + + static double test23Callee(int i1, int i2, int i3, int i4, int i5, int i6, + TestValue23 v1, TestValue23 v2, TestValue23 v3, TestValue23 v4, TestValue23 v5, TestValue23 v6, TestValue23 v7, TestValue23 v8, + double d1, double d2, double d3, double d4, double d5, double d6, double d7, double d8) { + return i1 + i2 + i3 + i4 + i5 + i6 + v1.f1 + v2.f1 + v3.f1 + v4.f1 + v5.f1 + v6.f1 + v7.f1 + v8.f1 + d1 + d2 + d3 + d4 + d5 + d6 + d7 + d8; + } + + @Test + public double test23(int i1, int i2, int i3, int i4, int i5, int i6, + TestValue23 v1, TestValue23 v2, TestValue23 v3, TestValue23 v4, TestValue23 v5, TestValue23 v6, TestValue23 v7, TestValue23 v8, + double d1, double d2, double d3, double d4, double d5, double d6, double d7, double d8) { + return test23Callee(i1, i2, i3, i4, i5, i6, + v1, v2, v3, v4, v5, v6, v7, v8, + d1, d2, d3, d4, d5, d6, d7, d8); + } + + @Run(test = "test23") + public void test23_verifier() { + TestValue23 vt = new TestValue23(rI); + double res1 = test23(rI, rI, rI, rI, rI, rI, + vt, vt, vt, vt, vt, vt, vt, vt, + rI, rI, rI, rI, rI, rI, rI, rI); + double res2 = test23Callee(rI, rI, rI, rI, rI, rI, + vt, vt, vt, vt, vt, vt, vt, vt, + rI, rI, rI, rI, rI, rI, rI, rI); + double res3 = 6*rI + 8*rI + 8*rI; + Asserts.assertEQ(res1, res2); + Asserts.assertEQ(res2, res3); + } + + // Should not return a nullable value object as fields + @Test + public MyValue2 test24() { + return null; + } + + @Run(test = "test24") + public void test24_verifier() { + MyValue2 vt = test24(); + Asserts.assertEQ(vt, null); + } + + // Same as test24 but with control flow and inlining + @ForceInline + public MyValue2 test26_callee(boolean b) { + if (b) { + return null; + } else { + return MyValue2.createWithFieldsInline(rI, rD); + } + } + + @Test + public MyValue2 test26(boolean b) { + return test26_callee(b); + } + + @Run(test = "test26") + public void test26_verifier() { + MyValue2 vt = test26(true); + Asserts.assertEQ(vt, null); + vt = test26(false); + Asserts.assertEQ(vt.hash(), MyValue2.createWithFieldsInline(rI, rD).hash()); + } + + // Test calling convention with deep hierarchy of flattened fields + @LooselyConsistentValue + static value class Test27Value1 { + Test27Value2 valueField; + + private Test27Value1(Test27Value2 val2) { + valueField = val2; + } + + @DontInline + public int test(Test27Value1 val1) { + return valueField.test(valueField) + val1.valueField.test(valueField); + } + } + + @LooselyConsistentValue + static value class Test27Value2 { + Test27Value3 valueField; + + private Test27Value2(Test27Value3 val3) { + valueField = val3; + } + + @DontInline + public int test(Test27Value2 val2) { + return valueField.test(valueField) + val2.valueField.test(valueField); + } + } + + @LooselyConsistentValue + static value class Test27Value3 { + int x; + + private Test27Value3(int x) { + this.x = x; + } + + @DontInline + public int test(Test27Value3 val3) { + return x + val3.x; + } + } + + @Test + public int test27(Test27Value1 val) { + return val.test(val); + } + + @Run(test = "test27") + public void test27_verifier() { + Test27Value3 val3 = new Test27Value3(rI); + Test27Value2 val2 = new Test27Value2(val3); + Test27Value1 val1 = new Test27Value1(val2); + int result = test27(val1); + Asserts.assertEQ(result, 8*rI); + } + + static final MyValue1 test28Val = MyValue1.createWithFieldsDontInline(rI, rL); + + @Test + public String test28() { + return test28Val.toString(); + } + + @Run(test = "test28") + @Warmup(0) + public void test28_verifier() { + String result = test28(); + } + + // Test calling a method returning a value object as fields via reflection + @Strict + @NullRestricted + MyValue3 test29_vt = MyValue3.create(); + + @Test + public MyValue3 test29() { + return test29_vt; + } + + @Run(test = "test29") + public void test29_verifier() throws Exception { + MyValue3 vt = (MyValue3)TestCallingConvention.class.getDeclaredMethod("test29").invoke(this); + test29_vt.verify(vt); + } + + @Test + public MyValue3 test30(MyValue3[] array) { + MyValue3 result = MyValue3.create(); + array[0] = result; + return result; + } + + @Run(test = "test30") + public void test30_verifier() throws Exception { + MyValue3[] array = (MyValue3[])ValueClass.newNullRestrictedNonAtomicArray(MyValue3.class, 1, MyValue3.DEFAULT); + MyValue3 vt = (MyValue3)TestCallingConvention.class.getDeclaredMethod("test30", MyValue3[].class).invoke(this, (Object)array); + array[0].verify(vt); + } + + MyValue3 test31_vt; + + @Test + public MyValue3 test31() { + MyValue3 result = MyValue3.create(); + test31_vt = result; + return result; + } + + @Run(test = "test31") + public void test31_verifier() throws Exception { + MyValue3 vt = (MyValue3)TestCallingConvention.class.getDeclaredMethod("test31").invoke(this); + test31_vt.verify(vt); + } + + // Test deoptimization at call return with value object returned in registers. + // Same as test14, except the interpreted method is called via a MethodHandle. + static MethodHandle test32_mh; + + @DontCompile + public MyValue2 test32_interp(boolean deopt) { + if (deopt) { + // uncommon trap + deoptimize("test32", boolean.class); + } + return MyValue2.createWithFieldsInline(rI+32, rD); + } + + @Test + public MyValue2 test32(boolean flag) throws Throwable { + return (MyValue2)test32_mh.invokeExact(this, flag); + } + + @Run(test = "test32") + public void test32_verifier(RunInfo info) throws Throwable { + MyValue2 result = test32(!info.isWarmUp()); + MyValue2 v = MyValue2.createWithFieldsInline(rI+32, rD); + Asserts.assertEQ(result.hash(), v.hash()); + } + + // Same as test32, except the return type is not flattenable. + static MethodHandle test33_mh; + + @DontCompile + public Object test33_interp(boolean deopt) { + if (deopt) { + // uncommon trap + deoptimize("test33", boolean.class); + } + return MyValue2.createWithFieldsInline(rI+33, rD); + } + + @Test + public MyValue2 test33(boolean flag) throws Throwable { + Object o = test33_mh.invokeExact(this, flag); + return (MyValue2)o; + } + + @Run(test = "test33") + public void test33_verifier(RunInfo info) throws Throwable { + MyValue2 result = test33(!info.isWarmUp()); + MyValue2 v = MyValue2.createWithFieldsInline(rI+33, rD); + Asserts.assertEQ(result.hash(), v.hash()); + } + + // Test selection of correct entry point in SharedRuntime::handle_wrong_method + static boolean test34_deopt = false; + + @DontInline + public static long test34_callee(MyValue2 vt, int i1, int i2, int i3, int i4) { + Asserts.assertEQ(i1, rI); + Asserts.assertEQ(i2, rI); + Asserts.assertEQ(i3, rI); + Asserts.assertEQ(i4, rI); + + if (test34_deopt) { + // uncommon trap + int result = 0; + for (int i = 0; i < 10; ++i) { + result += rL; + } + return vt.hash() + i1 + i2 + i3 + i4 + result; + } + return vt.hash() + i1 + i2 + i3 + i4; + } + + @Test + public static long test34(MyValue2 vt, int i1, int i2, int i3, int i4) { + return test34_callee(vt, i1, i2, i3, i4); + } + + @Run(test = "test34") + @Warmup(10000) // Make sure test34_callee is compiled + public void test34_verifier(RunInfo info) { + MyValue2 vt = MyValue2.createWithFieldsInline(rI, rD); + long result = test34(vt, rI, rI, rI, rI); + Asserts.assertEQ(result, vt.hash()+4*rI); + if (!info.isWarmUp()) { + test34_deopt = true; + for (int i = 0; i < 100; ++i) { + result = test34(vt, rI, rI, rI, rI); + Asserts.assertEQ(result, vt.hash()+4*rI+10*rL); + } + } + } + + // Test OSR compilation of method with scalarized argument + @Test + public static long test35(MyValue2 vt, int i1, int i2, int i3, int i4) { + int result = 0; + // Trigger OSR compilation + for (int i = 0; i < 10_000; ++i) { + result += i1; + } + return vt.hash() + i1 + i2 + i3 + i4 + result; + } + + @Run(test = "test35") + public void test35_verifier() { + MyValue2 vt = MyValue2.createWithFieldsInline(rI, rD); + long result = test35(vt, rI, rI, rI, rI); + Asserts.assertEQ(result, vt.hash()+10004*rI); + } + + // Same as test31 but with GC in callee to verify that the + // pre-allocated buffer for the returned value object remains valid. + MyValue3 test36_vt; + + @Test + public MyValue3 test36() { + MyValue3 result = MyValue3.create(); + test36_vt = result; + System.gc(); + return result; + } + + @Run(test = "test36") + public void test36_verifier() throws Exception { + MyValue3 vt = (MyValue3)TestCallingConvention.class.getDeclaredMethod("test36").invoke(this); + test36_vt.verify(vt); + } + + // Test method resolution with scalarized value object receiver at invokespecial + static final MethodHandle test37_mh; + + @LooselyConsistentValue + static value class Test37Value { + int x = rI; + + @DontInline + public int test() { + return x; + } + } + + @Test + public int test37(Test37Value vt) throws Throwable { + // Generates invokespecial call of Test37Value::test + return (int)test37_mh.invokeExact(vt); + } + + @Run(test = "test37") + public void test37_verifier() throws Throwable { + Test37Value vt = new Test37Value(); + int res = test37(vt); + Asserts.assertEQ(res, rI); + } + + // Test passing/returning an empty value object + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public MyValueEmpty test38(MyValueEmpty vt) { + return vt.copy(vt); + } + + @Run(test = "test38") + public void test38_verifier() { + MyValueEmpty vt = new MyValueEmpty(); + MyValueEmpty res = test38(vt); + Asserts.assertEQ(res, vt); + } + + @LooselyConsistentValue + static value class LargeValueWithOops { + // Use all 6 int registers + 50/2 on stack = 29 + Object o1 = null; + Object o2 = null; + Object o3 = null; + Object o4 = null; + Object o5 = null; + Object o6 = null; + Object o7 = null; + Object o8 = null; + Object o9 = null; + Object o10 = null; + Object o11 = null; + Object o12 = null; + Object o13 = null; + Object o14 = null; + Object o15 = null; + Object o16 = null; + Object o17 = null; + Object o18 = null; + Object o19 = null; + Object o20 = null; + Object o21 = null; + Object o22 = null; + Object o23 = null; + Object o24 = null; + Object o25 = null; + Object o26 = null; + Object o27 = null; + Object o28 = null; + Object o29 = null; + } + + @LooselyConsistentValue + static value class LargeValueWithoutOops { + // Use all 6 int registers + 50/2 on stack = 29 + int i1 = 0; + int i2 = 0; + int i3 = 0; + int i4 = 0; + int i5 = 0; + int i6 = 0; + int i7 = 0; + int i8 = 0; + int i9 = 0; + int i10 = 0; + int i11 = 0; + int i12 = 0; + int i13 = 0; + int i14 = 0; + int i15 = 0; + int i16 = 0; + int i17 = 0; + int i18 = 0; + int i19 = 0; + int i20 = 0; + int i21 = 0; + int i22 = 0; + int i23 = 0; + int i24 = 0; + int i25 = 0; + int i26 = 0; + int i27 = 0; + int i28 = 0; + int i29 = 0; + // Use all 7 float registers + double d1 = 0; + double d2 = 0; + double d3 = 0; + double d4 = 0; + double d5 = 0; + double d6 = 0; + double d7 = 0; + double d8 = 0; + } + + // Test passing/returning a large value object with oop fields + @Test + public static LargeValueWithOops test39(LargeValueWithOops vt) { + return vt; + } + + @Run(test = "test39") + public void test39_verifier() { + LargeValueWithOops vt = new LargeValueWithOops(); + LargeValueWithOops res = test39(vt); + Asserts.assertEQ(res, vt); + } + + // Test passing/returning a large value object with only int/float fields + @Test + public static LargeValueWithoutOops test40(LargeValueWithoutOops vt) { + return vt; + } + + @Run(test = "test40") + public void test40_verifier() { + LargeValueWithoutOops vt = new LargeValueWithoutOops(); + LargeValueWithoutOops res = test40(vt); + Asserts.assertEQ(res, vt); + } + + // Test passing/returning an empty value object together with non-empty + // value objects such that only some value class arguments are scalarized. + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public MyValueEmpty test41(MyValue1 vt1, MyValueEmpty vt2, MyValue1 vt3) { + return vt2.copy(vt2); + } + + @Run(test = "test41") + public void test41_verifier() { + MyValueEmpty res = test41(MyValue1.createDefaultInline(), new MyValueEmpty(), MyValue1.createDefaultInline()); + Asserts.assertEQ(res, new MyValueEmpty()); + } + + // More empty value class tests with containers + + @LooselyConsistentValue + static value class EmptyContainer { + @Strict + @NullRestricted + private MyValueEmpty empty; + + @ForceInline + EmptyContainer(MyValueEmpty empty) { + this.empty = empty; + } + + @ForceInline + MyValueEmpty getInline() { return empty; } + + @DontInline + MyValueEmpty getNoInline() { return empty; } + } + + @LooselyConsistentValue + static value class MixedContainer { + public int val; + @Strict + @NullRestricted + private EmptyContainer empty; + + @ForceInline + MixedContainer(int val, EmptyContainer empty) { + this.val = val; + this.empty = empty; + } + + @ForceInline + EmptyContainer getInline() { return empty; } + + @DontInline + EmptyContainer getNoInline() { return empty; } + } + + // Empty value object return + @Test + @IR(failOn = {LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + @IR(applyIf = {"InlineTypeReturnedAsFields", "true"}, + failOn = {ALLOC_OF_MYVALUE_KLASS}) + public MyValueEmpty test42() { + EmptyContainer c = new EmptyContainer(new MyValueEmpty()); + return c.getInline(); + } + + @Run(test = "test42") + public void test42_verifier() { + MyValueEmpty empty = test42(); + Asserts.assertEquals(empty, new MyValueEmpty()); + } + + // Empty value class container return + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public EmptyContainer test43(EmptyContainer c) { + return c; + } + + @Run(test = "test43") + public void test43_verifier() { + EmptyContainer empty = new EmptyContainer(new MyValueEmpty()); + EmptyContainer c = test43(empty); + Asserts.assertEquals(c, empty); + } + + // Empty value class container (mixed) return + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public MixedContainer test44() { + MixedContainer c = new MixedContainer(rI, new EmptyContainer(new MyValueEmpty())); + c = new MixedContainer(rI, c.getInline()); + return c; + } + + @Run(test = "test44") + public void test44_verifier() { + MixedContainer c = test44(); + Asserts.assertEquals(c, new MixedContainer(rI, new EmptyContainer(new MyValueEmpty()))); + } + + // Empty value class container argument + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public EmptyContainer test45(EmptyContainer c) { + return new EmptyContainer(c.getInline()); + } + + @Run(test = "test45") + public void test45_verifier() { + EmptyContainer empty = new EmptyContainer(new MyValueEmpty()); + EmptyContainer c = test45(empty); + Asserts.assertEquals(c, empty); + } + + // Empty value class container and mixed container arguments + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public MyValueEmpty test46(EmptyContainer c1, MixedContainer c2, MyValueEmpty empty) { + c2 = new MixedContainer(c2.val, c1); + return c2.getNoInline().getNoInline(); + } + + @Run(test = "test46") + public void test46_verifier() { + MyValueEmpty empty = test46(new EmptyContainer(new MyValueEmpty()), new MixedContainer(0, new EmptyContainer(new MyValueEmpty())), new MyValueEmpty()); + Asserts.assertEquals(empty, new MyValueEmpty()); + } + + // No receiver and only empty argument + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public static MyValueEmpty test47(MyValueEmpty empty) { + return empty; + } + + @Run(test = "test47") + public void test47_verifier() { + MyValueEmpty empty = test47(new MyValueEmpty()); + Asserts.assertEquals(empty,new MyValueEmpty()); + } + + // No receiver and only empty container argument + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public static MyValueEmpty test48(EmptyContainer empty) { + return empty.getNoInline(); + } + + @Run(test = "test48") + public void test48_verifier() { + MyValueEmpty empty = test48(new EmptyContainer(new MyValueEmpty())); + Asserts.assertEquals(empty, new MyValueEmpty()); + } + + // Test conditional value class return with incremental inlining + public MyValue3 test49_inlined1(boolean b) { + if (b) { + return MyValue3.create(); + } else { + return MyValue3.create(); + } + } + + public MyValue3 test49_inlined2(boolean b) { + return test49_inlined1(b); + } + + @Test + public void test49(boolean b) { + test49_inlined2(b); + } + + @Run(test = "test49") + public void test49_verifier() { + test49(true); + test49(false); + } + + // Variant of test49 with result verification (triggered different failure mode) + @Strict + @NullRestricted + final MyValue3 test50_vt = MyValue3.create(); + @Strict + @NullRestricted + final MyValue3 test50_vt2 = MyValue3.create(); + + public MyValue3 test50_inlined1(boolean b) { + if (b) { + return test50_vt; + } else { + return test50_vt2; + } + } + + public MyValue3 test50_inlined2(boolean b) { + return test50_inlined1(b); + } + + @Test + public void test50(boolean b) { + MyValue3 vt = test50_inlined2(b); + vt.verify(b ? test50_vt : test50_vt2); + } + + @Run(test = "test50") + public void test50_verifier() { + test50(true); + test50(false); + } + + // Test stack repair with stack slots reserved for monitors + private static final Object lock1 = new Object(); + private static final Object lock2 = new Object(); + private static final Object lock3 = new Object(); + + @DontInline + static void test51_callee() { } + + @Test + public void test51(MyValue1 val) { + synchronized (lock1) { + test51_callee(); + } + } + + @Run(test = "test51") + public void test51_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + test51(vt); + } + + @DontInline + static void test52_callee() { } + + @Test + public void test52(MyValue1 val) { + synchronized (lock1) { + synchronized (lock2) { + test52_callee(); + } + } + } + + @Run(test = "test52") + public void test52_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + test52(vt); + } + + @DontInline + static void test53_callee() { } + + @Test + public void test53(MyValue1 val) { + synchronized (lock1) { + synchronized (lock2) { + synchronized (lock3) { + test53_callee(); + } + } + } + } + + @Run(test = "test53") + public void test53_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + test53(vt); + } + + static MethodHandle test54_mh; + + @DontInline + public MyValue2 test54_callee() { + return MyValue2.createWithFieldsInline(rI, rD); + } + + // Test that method handle invocation does not block scalarization of return value + @Test + @IR(failOn = {ALLOC, STORE_OF_ANY_KLASS}) + public long test54(Method m, boolean b1, boolean b2) throws Throwable { + MyInterface obj = MyValue2.createWithFieldsInline(rI, rD); + if (b1) { + obj = (MyValue2)test54_mh.invokeExact(this); + } + if (b2) { + // Uncommon trap + TestFramework.deoptimize(m); + return obj.hash(); + } + return -1; + } + + @Run(test = "test54") + @Warmup(10000) + public void test54_verifier(RunInfo info) throws Throwable { + Asserts.assertEQ(test54(info.getTest(), true, false), -1L); + Asserts.assertEQ(test54(info.getTest(), false, false), -1L); + if (!info.isWarmUp()) { + MyValue2 v = MyValue2.createWithFieldsInline(rI, rD); + Asserts.assertEQ(test54(info.getTest(), true, true), v.hash()); + } + } + + @DontInline + public MyValue2 test55_callee() { + return MyValue2.createWithFieldsInline(rI, rD); + } + + // Test scalarization of nullable return value that is unused + @Test + public void test55() { + test55_callee(); + } + + @Run(test = "test55") + public void test55_verifier() { + test55(); + } + + static MethodHandle test56_mh; + + @DontInline + public MyValue2 test56_callee(boolean b) { + return b ? MyValue2.createWithFieldsInline(rI, rD) : null; + } + + // Test that scalarization of nullable return works properly for method handle calls + @Test + public MyValue2 test56(boolean b) throws Throwable { + return (MyValue2)test56_mh.invokeExact(this, b); + } + + @Run(test = "test56") + @Warmup(10000) + public void test56_verifier(RunInfo info) throws Throwable { + MyValue2 vt = MyValue2.createWithFieldsInline(rI, rD); + Asserts.assertEQ(test56(true).hash(), vt.hash()); + if (!info.isWarmUp()) { + Asserts.assertEQ(test56(false), null); + } + } + + static boolean expectedUseArrayFlattening = WHITE_BOX.getBooleanVMFlag("UseArrayFlattening"); + + // Test value class return from native method + @Test + public boolean test57() { + return WHITE_BOX.getBooleanVMFlag("UseArrayFlattening"); + } + + @Run(test = "test57") + public void test57_verifier() { + Asserts.assertEQ(test57(), expectedUseArrayFlattening); + } + + // Test abstract value class with flat fields + @LooselyConsistentValue + abstract value class MyAbstract58 { + @Strict + @NullRestricted + MyValue58Inline nullfree = new MyValue58Inline(); + + MyValue58Inline nullable = new MyValue58Inline(); + } + + @LooselyConsistentValue + value class MyValue58Inline { + int x = rI; + } + + @LooselyConsistentValue + value class MyValue58_1 extends MyAbstract58 { + } + + @LooselyConsistentValue + value class MyValue58_2 extends MyAbstract58 { + int x = rI; + } + + @LooselyConsistentValue + value class MyValue58_3 extends MyAbstract58 { + int x = rI; + + @Strict + @NullRestricted + MyValue1 nullfree = MyValue1.DEFAULT; + + MyValue1 nullable = null; + } + + @Test + public MyValue58_3 test58(MyValue58_1 arg1, MyValue58_2 arg2, MyValue58_3 arg3) { + Asserts.assertEQ(arg1, new MyValue58_1()); + Asserts.assertEQ(arg2, new MyValue58_2()); + Asserts.assertEQ(arg3, new MyValue58_3()); + return arg3; + } + + @Run(test = "test58") + public void test58_verifier() { + Asserts.assertEQ(test58(new MyValue58_1(), new MyValue58_2(), new MyValue58_3()), new MyValue58_3()); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestCallingConventionC1.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestCallingConventionC1.java new file mode 100644 index 00000000000..bf6a06b1c69 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestCallingConventionC1.java @@ -0,0 +1,2415 @@ +/* + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.*; +import jdk.test.lib.Asserts; +import jdk.test.whitebox.WhiteBox; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import static compiler.valhalla.inlinetypes.InlineTypes.rI; +import static compiler.valhalla.inlinetypes.InlineTypes.rL; + +/* + * @test + * @key randomness + * @summary Test calls from {C1} to {C2, Interpreter}, and vice versa. + * @library /test/lib / + * @requires (os.simpleArch == "x64" | os.simpleArch == "aarch64") + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm/timeout=300 compiler.valhalla.inlinetypes.TestCallingConventionC1 + */ + +@ForceCompileClassInitializer +public class TestCallingConventionC1 { + + public static void main(String[] args) { + final Scenario[] scenarios = { + // Default: both C1 and C2 are enabled, tiered compilation enabled + new Scenario(0, + "--enable-preview", + "-XX:CICompilerCount=2", + "-XX:TieredStopAtLevel=4", + "-XX:+TieredCompilation"), + // Default: both C1 and C2 are enabled, tiered compilation enabled + new Scenario(1, + "--enable-preview", + "-XX:CICompilerCount=2", + "-XX:TieredStopAtLevel=4", + "-XX:+TieredCompilation", + "-XX:+IgnoreUnrecognizedVMOptions", + "-XX:+StressCallingConvention"), + // Same as above, but flip all the compLevel=CompLevel.C1_SIMPLE and compLevel=CompLevel.C2, so we test + // the compliment of the above scenario. + new Scenario(2, + "--enable-preview", + "-XX:CICompilerCount=2", + "-XX:TieredStopAtLevel=4", + "-XX:+TieredCompilation", + "-DFlipC1C2=true"), + // Only C1. Tiered compilation disabled. + new Scenario(3, + "--enable-preview", + "-XX:TieredStopAtLevel=1", + "-XX:+TieredCompilation", + "-XX:+IgnoreUnrecognizedVMOptions", + "-XX:-PatchALot"), + // Only C2. + new Scenario(4, + "--enable-preview", + "-XX:TieredStopAtLevel=4", + "-XX:-TieredCompilation") + }; + + System.gc(); // Resolve this call, to avoid C1 code patching in the test cases. + + InlineTypes.getFramework() + .addScenarios(scenarios) + .start(); + } + + // Helper methods and classes + @LooselyConsistentValue + static value class Point { + int x; + int y; + public Point(int x, int y) { + this.x = x; + this.y = y; + } + + @DontCompile + public int func() { + return x + y; + } + + @ForceCompile(CompLevel.C1_SIMPLE) + @DontInline + public int func_c1(Point p) { + return x + y + p.x + p.y; + } + } + + static interface FunctorInterface { + public int apply_interp(Point p); + } + + static class Functor implements FunctorInterface { + @DontCompile + public int apply_interp(Point p) { + return p.func() + 0; + } + } + + static class Functor1 extends Functor { + @DontCompile + public int apply_interp(Point p) { + return p.func() + 10000; + } + } + + static class Functor2 extends Functor { + @DontCompile + public int apply_interp(Point p) { + return p.func() + 20000; + } + } + + static class Functor3 extends Functor { + @DontCompile + public int apply_interp(Point p) { + return p.func() + 30000; + } + } + + static class Functor4 extends Functor { + @DontCompile + public int apply_interp(Point p) { + return p.func() + 40000; + } + } + + static Functor functors[] = { + new Functor(), + new Functor1(), + new Functor2(), + new Functor3(), + new Functor4() + }; + static int functorCounter = 0; + static Functor getFunctor() { + int n = (++ functorCounter) % functors.length; + return functors[n]; + } + + @Strict + @NullRestricted + static Point pointField = new Point(123, 456); + @Strict + @NullRestricted + static Point pointField1 = new Point(1123, 1456); + @Strict + @NullRestricted + static Point pointField2 = new Point(2123, 2456); + + static interface Intf { + public int func1(int a, int b); + public int func2(int a, int b, Point p); + } + + static class MyImplPojo0 implements Intf { + int field = 0; + @DontCompile + public int func1(int a, int b) { return field + a + b + 1; } + @DontCompile + public int func2(int a, int b, Point p) { return field + a + b + p.x + p.y + 1; } + } + + static class MyImplPojo1 implements Intf { + int field = 1000; + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + public int func1(int a, int b) { return field + a + b + 20; } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + public int func2(int a, int b, Point p) { return field + a + b + p.x + p.y + 20; } + } + + static class MyImplPojo2 implements Intf { + int field = 2000; + + @DontInline + @ForceCompile(CompLevel.C2) + public int func1(int a, int b) { return field + a + b + 20; } + + @DontInline + @ForceCompile(CompLevel.C2) + public int func2(int a, int b, Point p) { return field + a + b + p.x + p.y + 20; } + } + + static class MyImplPojo3 implements Intf { + int field = 0; + @DontInline // will be compiled with counters + public int func1(int a, int b) { return field + a + b + 1; } + @DontInline // will be compiled with counters + public int func2(int a, int b, Point p) { return field + a + b + p.x + p.y + 1; } + } + + @LooselyConsistentValue + static value class MyImplVal1 implements Intf { + int field; + MyImplVal1() { + field = 11000; + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + public int func1(int a, int b) { return field + a + b + 300; } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + public int func2(int a, int b, Point p) { return field + a + b + p.x + p.y + 300; } + } + + @LooselyConsistentValue + static value class MyImplVal2 implements Intf { + int field; + MyImplVal2() { + field = 12000; + } + + @DontInline + @ForceCompile(CompLevel.C2) + public int func1(int a, int b) { return field + a + b + 300; } + + @DontInline + @ForceCompile(CompLevel.C2) + public int func2(int a, int b, Point p) { return field + a + b + p.x + p.y + 300; } + } + + @LooselyConsistentValue + static value class MyImplVal1X implements Intf { + int field; + MyImplVal1X() { + field = 11000; + } + + @DontCompile + public int func1(int a, int b) { return field + a + b + 300; } + + @DontCompile + public int func2(int a, int b, Point p) { return field + a + b + p.x + p.y + 300; } + } + + @LooselyConsistentValue + static value class MyImplVal2X implements Intf { + int field; + MyImplVal2X() { + field = 12000; + } + + @DontInline // will be compiled with counters + public int func1(int a, int b) { return field + a + b + 300; } + + @DontInline // will be compiled with counters + public int func2(int a, int b, Point p) { return field + a + b + p.x + p.y + 300; } + } + + static Intf intfs[] = { + new MyImplPojo0(), // methods not compiled + new MyImplPojo1(), // methods compiled by C1 + new MyImplPojo2(), // methods compiled by C2 + new MyImplVal1(), // methods compiled by C1 + new MyImplVal2() // methods compiled by C2 + }; + static Intf getIntf(int i) { + int n = i % intfs.length; + return intfs[n]; + } + + @LooselyConsistentValue + static value class FixedPoints { + boolean Z0 = false; + boolean Z1 = true; + byte B = (byte)2; + char C = (char)34; + short S = (short)456; + int I = 5678; + long J = 0x1234567800abcdefL; + } + @Strict + @NullRestricted + static FixedPoints fixedPointsField = new FixedPoints(); + + @LooselyConsistentValue + static value class FloatPoint { + float x; + float y; + public FloatPoint(float x, float y) { + this.x = x; + this.y = y; + } + } + + @LooselyConsistentValue + static value class DoublePoint { + double x; + double y; + public DoublePoint(double x, double y) { + this.x = x; + this.y = y; + } + } + @Strict + @NullRestricted + static FloatPoint floatPointField = new FloatPoint(123.456f, 789.012f); + @Strict + @NullRestricted + static DoublePoint doublePointField = new DoublePoint(123.456, 789.012); + + @LooselyConsistentValue + static value class EightFloats { + float f1, f2, f3, f4, f5, f6, f7, f8; + public EightFloats() { + f1 = 1.1f; + f2 = 2.2f; + f3 = 3.3f; + f4 = 4.4f; + f5 = 5.5f; + f6 = 6.6f; + f7 = 7.7f; + f8 = 8.8f; + } + } + + static EightFloats eightFloatsField = new EightFloats(); + + static class Number { + int n; + Number(int v) { + n = v; + } + void set(int v) { + n = v; + } + } + + static interface RefPoint_Access { + public int func1(RefPoint rp2); + public int func2(RefPoint rp1, RefPoint rp2, Number n1, RefPoint rp3, RefPoint rp4, Number n2); + } + + @LooselyConsistentValue + static value class RefPoint implements RefPoint_Access { + Number x; + Number y; + public RefPoint(int x, int y) { + this.x = new Number(x); + this.y = new Number(y); + } + public RefPoint(Number x, Number y) { + this.x = x; + this.y = y; + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + public final int final_func(RefPoint rp2) { // opt_virtual_call + return this.x.n + this.y.n + rp2.x.n + rp2.y.n; + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + public int func1(RefPoint rp2) { + return this.x.n + this.y.n + rp2.x.n + rp2.y.n; + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + public int func2(RefPoint rp1, RefPoint rp2, Number n1, RefPoint rp3, RefPoint rp4, Number n2) { + return x.n + y.n + + rp1.x.n + rp1.y.n + + rp2.x.n + rp2.y.n + + n1.n + + rp3.x.n + rp3.y.n + + rp4.x.n + rp4.y.n + + n2.n; + } + } + + static class RefPoint_Access_Impl1 implements RefPoint_Access { + @DontCompile + public int func1(RefPoint rp2) { + return rp2.x.n + rp2.y.n + 1111111; + } + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + public int func2(RefPoint rp1, RefPoint rp2, Number n1, RefPoint rp3, RefPoint rp4, Number n2) { + return 111111 + + rp1.x.n + rp1.y.n + + rp2.x.n + rp2.y.n + + n1.n + + rp3.x.n + rp3.y.n + + rp4.x.n + rp4.y.n + + n2.n; + } + } + + static class RefPoint_Access_Impl2 implements RefPoint_Access { + @DontCompile + public int func1(RefPoint rp2) { + return rp2.x.n + rp2.y.n + 2222222; + } + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + public int func2(RefPoint rp1, RefPoint rp2, Number n1, RefPoint rp3, RefPoint rp4, Number n2) { + return 222222 + + rp1.x.n + rp1.y.n + + rp2.x.n + rp2.y.n + + n1.n + + rp3.x.n + rp3.y.n + + rp4.x.n + rp4.y.n + + n2.n; + } + } + + static RefPoint_Access refPoint_Access_impls[] = { + new RefPoint_Access_Impl1(), + new RefPoint_Access_Impl2(), + new RefPoint(0x12345, 0x6789a) + }; + + static int next_RefPoint_Access = 0; + static RefPoint_Access get_RefPoint_Access() { + int i = next_RefPoint_Access ++; + return refPoint_Access_impls[i % refPoint_Access_impls.length]; + } + + @Strict + @NullRestricted + static RefPoint refPointField1 = new RefPoint(12, 34); + @Strict + @NullRestricted + static RefPoint refPointField2 = new RefPoint(56789, 0x12345678); + + // This value class has too many fields to fit in registers on x64 for + // InlineTypeReturnedAsFields. + @LooselyConsistentValue + static value class TooBigToReturnAsFields { + int a0 = 0; + int a1 = 1; + int a2 = 2; + int a3 = 3; + int a4 = 4; + int a5 = 5; + int a6 = 6; + int a7 = 7; + int a8 = 8; + int a9 = 9; + } + + @Strict + @NullRestricted + static TooBigToReturnAsFields tooBig = new TooBigToReturnAsFields(); + + //********************************************************************** + // PART 1 - C1 calls interpreted code + //********************************************************************** + + //** C1 passes value object to interpreter (static) + @Test(compLevel = CompLevel.C1_SIMPLE) + public int test1() { + return test1_helper(pointField); + } + + @DontCompile + private static int test1_helper(Point p) { + return p.func(); + } + + @Run(test = "test1") + public void test1_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 10; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + int result = test1() + i; + Asserts.assertEQ(result, pointField.func() + i); + } + } + + //** C1 passes value object to interpreter (monomorphic) + @Test(compLevel = CompLevel.C1_SIMPLE) + public int test2() { + return test2_helper(pointField); + } + + @DontCompile + private int test2_helper(Point p) { + return p.func(); + } + + @Run(test = "test2") + public void test2_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 10; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + int result = test2() + i; + Asserts.assertEQ(result, pointField.func() + i); + } + } + + // C1 passes value object to interpreter (megamorphic: vtable) + @Test(compLevel = CompLevel.C1_SIMPLE) + public int test3(Functor functor) { + return functor.apply_interp(pointField); + } + + @Run(test = "test3") + public void test3_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 100; + for (int i = 0; i < count; i++) { // need a loop to test inline cache and vtable indexing + Functor functor = info.isWarmUp() ? functors[0] : getFunctor(); + int result = test3(functor) + i; + Asserts.assertEQ(result, functor.apply_interp(pointField) + i); + } + } + + // Same as test3, but compiled with C2. Test the hastable of VtableStubs + @Test(compLevel = CompLevel.C2) + public int test3b(Functor functor) { + return functor.apply_interp(pointField); + } + + @Run(test = "test3b") + public void test3b_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 100; + for (int i = 0; i < count; i++) { // need a loop to test inline cache and vtable indexing + Functor functor = info.isWarmUp() ? functors[0] : getFunctor(); + int result = test3b(functor) + i; + Asserts.assertEQ(result, functor.apply_interp(pointField) + i); + } + } + + // C1 passes value object to interpreter (megamorphic: itable) + @Test(compLevel = CompLevel.C1_SIMPLE) + public int test4(FunctorInterface fi) { + return fi.apply_interp(pointField); + } + + @Run(test = "test4") + public void test4_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 100; + for (int i = 0; i < count; i++) { // need a loop to test inline cache and itable indexing + Functor functor = info.isWarmUp() ? functors[0] : getFunctor(); + int result = test4(functor) + i; + Asserts.assertEQ(result, functor.apply_interp(pointField) + i); + } + } + + //********************************************************************** + // PART 2 - interpreter calls C1 + //********************************************************************** + + // Interpreter passes value object to C1 (static) + @Test(compLevel = CompLevel.C1_SIMPLE) + static public int test20(Point p1, long l, Point p2) { + return p1.x + p2.y; + } + + @Run(test = "test20") + public void test20_verifier() { + int result = test20(pointField1, 0, pointField2); + int n = pointField1.x + pointField2.y; + Asserts.assertEQ(result, n); + } + + // Interpreter passes value object to C1 (instance method in value class) + @Test + public int test21(Point p) { + return test21_helper(p); + } + + @DontCompile + int test21_helper(Point p) { + return p.func_c1(p); + } + + @Run(test = "test21") + public void test21_verifier() { + int result = test21(pointField); + int n = 2 * (pointField.x + pointField.y); + Asserts.assertEQ(result, n); + } + + + //********************************************************************** + // PART 3 - C2 calls C1 + //********************************************************************** + + // C2->C1 invokestatic, single inline arg + @Test(compLevel = CompLevel.C2) + public int test30() { + return test30_helper(pointField); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static int test30_helper(Point p) { + return p.x + p.y; + } + + @Run(test = "test30") + public void test30_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 2; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + int result = test30(); + int n = pointField.x + pointField.y; + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokestatic, two single inline args + @Test(compLevel = CompLevel.C2) + public int test31() { + return test31_helper(pointField1, pointField2); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static int test31_helper(Point p1, Point p2) { + return p1.x + p2.y; + } + + @Run(test = "test31") + public void test31_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 2; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + int result = test31(); + int n = pointField1.x + pointField2.y; + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokestatic, two single inline args and interleaving ints (all passed in registers on x64) + @Test(compLevel = CompLevel.C2) + public int test32() { + return test32_helper(0, pointField1, 1, pointField2); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static int test32_helper(int x, Point p1, int y, Point p2) { + return p1.x + p2.y + x + y; + } + + @Run(test = "test32") + public void test32_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 2; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + int result = test32(); + int n = pointField1.x + pointField2.y + 0 + 1; + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokeinterface -- no verified_ro_entry (no inline args except for receiver) + @Test(compLevel = CompLevel.C2) + public int test33(Intf intf, int a, int b) { + return intf.func1(a, b); + } + + @Run(test = "test33") + public void test33_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 20; + for (int i = 0; i < count; i++) { + Intf intf = info.isWarmUp() ? intfs[0] : getIntf(i+1); + int result = test33(intf, 123, 456) + i; + Asserts.assertEQ(result, intf.func1(123, 456) + i); + } + } + + // C2->C1 invokeinterface -- use verified_ro_entry (has inline args other than receiver) + @Test(compLevel = CompLevel.C2) + public int test34(Intf intf, int a, int b) { + return intf.func2(a, b, pointField); + } + + @Run(test = "test34") + public void test34_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 20; + for (int i = 0; i < count; i++) { + Intf intf = info.isWarmUp() ? intfs[0] : getIntf(i+1); + int result = test34(intf, 123, 456) + i; + Asserts.assertEQ(result, intf.func2(123, 456, pointField) + i); + } + } + + // C2->C1 invokestatic, Point.y is on stack (x64) + @Test(compLevel = CompLevel.C2) + public int test35() { + return test35_helper(1, 2, 3, 4, 5, pointField); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static int test35_helper(int a1, int a2, int a3, int a4, int a5, Point p) { + return a1 + a2 + a3 + a4 + a5 + p.x + p.y; + } + + @Run(test = "test35") + public void test35_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 2; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + int result = test35(); + int n = 1 + 2 + 3 + 4 + 5 + pointField.x + pointField.y; + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokestatic, shuffling arguments that are passed on stack + @Test(compLevel = CompLevel.C2) + public int test36() { + return test36_helper(pointField, 1, 2, 3, 4, 5, 6, 7, 8); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static int test36_helper(Point p, int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8) { + return a6 + a8; + } + + @Run(test = "test36") + public void test36_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 2; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + int result = test36(); + int n = 6 + 8; + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokestatic, shuffling long arguments + @Test(compLevel = CompLevel.C2) + public int test37() { + return test37_helper(pointField, 1, 2, 3, 4, 5, 6, 7, 8); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static int test37_helper(Point p, long a1, long a2, long a3, long a4, long a5, long a6, long a7, long a8) { + return (int)(a6 + a8); + } + + @Run(test = "test37") + public void test37_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 2; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + int result = test37(); + int n = 6 + 8; + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokestatic, shuffling boolean, byte, char, short, int, long arguments + @Test(compLevel = CompLevel.C2) + public int test38() { + return test38_helper(pointField, true, (byte)1, (char)2, (short)3, 4, 5, (byte)6, (short)7, 8); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static int test38_helper(Point p, boolean a0, byte a1, char a2, short a3, int a4, long a5, byte a6, short a7, int a8) { + if (a0) { + return (int)(a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8); + } else { + return -1; + } + } + + @Run(test = "test38") + public void test38_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 2; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + int result = test38(); + int n = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8; + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokestatic, packing a value object with all types of fixed point primitive fields. + @Test(compLevel = CompLevel.C2) + public long test39() { + return test39_helper(1, fixedPointsField, 2, fixedPointsField); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static long test39_helper(int a1, FixedPoints f1, int a2, FixedPoints f2) { + if (f1.Z0 == false && f1.Z1 == true && f2.Z0 == false && f2.Z1 == true) { + return f1.B + f2.C + f1.S + f2.I + f1.J; + } else { + return -1; + } + } + + @Run(test = "test39") + public void test39_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 2; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + long result = test39(); + long n = test39_helper(1, fixedPointsField, 2, fixedPointsField); + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokestatic, shuffling floating point args + @Test(compLevel = CompLevel.C2) + public double test40() { + return test40_helper(1.1f, 1.2, floatPointField, doublePointField, 1.3f, 1.4, 1.5f, 1.7, 1.7, 1.8, 1.9, 1.10, 1.11, 1.12); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static double test40_helper(float a1, double a2, FloatPoint fp, DoublePoint dp, float a3, double a4, float a5, double a6, double a7, double a8, double a9, double a10, double a11, double a12) { + return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 + fp.x + fp.y - dp.x - dp.y; + } + + @Run(test = "test40") + public void test40_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 2; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + double result = test40(); + double n = test40_helper(1.1f, 1.2, floatPointField, doublePointField, 1.3f, 1.4, 1.5f, 1.7, 1.7, 1.8, 1.9, 1.10, 1.11, 1.12); + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokestatic, mixing floats and ints + @Test(compLevel = CompLevel.C2) + public double test41() { + return test41_helper(1, 1.2, pointField, floatPointField, doublePointField, 1.3f, 4, 1.5f, 1.7, 1.7, 1.8, 9, 1.10, 1.11, 1.12); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static double test41_helper(int a1, double a2, Point p, FloatPoint fp, DoublePoint dp, float a3, int a4, float a5, double a6, double a7, double a8, long a9, double a10, double a11, double a12) { + return a1 + a2 + fp.x + fp.y - dp.x - dp.y + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12; + } + + @Run(test = "test41") + public void test41_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 2; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + double result = test41(); + double n = test41_helper(1, 1.2, pointField, floatPointField, doublePointField, 1.3f, 4, 1.5f, 1.7, 1.7, 1.8, 9, 1.10, 1.11, 1.12); + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokestatic, circular dependency (between rdi and first stack slot on x64) + @Test(compLevel = CompLevel.C2) + public float test42() { + return test42_helper(eightFloatsField, pointField, 3, 4, 5, floatPointField, 7); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static float test42_helper(EightFloats ep1, // (xmm0 ... xmm7) -> rsi + Point p2, // (rsi, rdx) -> rdx + int i3, // rcx -> rcx + int i4, // r8 -> r8 + int i5, // r9 -> r9 + FloatPoint fp6, // (stk[0], stk[1]) -> rdi ** circ depend + int i7) // rdi -> stk[0] ** circ depend + { + return ep1.f1 + ep1.f2 + ep1.f3 + ep1.f4 + ep1.f5 + ep1.f6 + ep1.f7 + ep1.f8 + + p2.x + p2.y + i3 + i4 + i5 + fp6.x + fp6.y + i7; + } + + @Run(test = "test42") + public void test42_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 2; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + float result = test42(); + float n = test42_helper(eightFloatsField, pointField, 3, 4, 5, floatPointField, 7); + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokestatic, packing causes stack growth (1 extra stack word) + @Test(compLevel = CompLevel.C2) + public float test43() { + return test43_helper(floatPointField, 1, 2, 3, 4, 5, 6); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static float test43_helper(FloatPoint fp, int a1, int a2, int a3, int a4, int a5, int a6) { + // On x64: + // Scalarized entry -- all parameters are passed in registers + // Non-scalarized entry -- a6 is passed on stack[0] + return fp.x + fp.y + a1 + a2 + a3 + a4 + a5 + a6; + } + + @Run(test = "test43") + public void test43_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 2; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + float result = test43(); + float n = test43_helper(floatPointField, 1, 2, 3, 4, 5, 6); + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokestatic, packing causes stack growth (2 extra stack words) + @Test(compLevel = CompLevel.C2) + public float test44() { + return test44_helper(floatPointField, floatPointField, 1, 2, 3, 4, 5, 6); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static float test44_helper(FloatPoint fp1, FloatPoint fp2, int a1, int a2, int a3, int a4, int a5, int a6) { + // On x64: + // Scalarized entry -- all parameters are passed in registers + // Non-scalarized entry -- a5 is passed on stack[0] + // Non-scalarized entry -- a6 is passed on stack[1] + return fp1.x + fp1.y + + fp2.x + fp2.y + + a1 + a2 + a3 + a4 + a5 + a6; + } + + @Run(test = "test44") + public void test44_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 2; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + float result = test44(); + float n = test44_helper(floatPointField, floatPointField, 1, 2, 3, 4, 5, 6); + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokestatic, packing causes stack growth (5 extra stack words) + @Test(compLevel = CompLevel.C2) + public float test45() { + return test45_helper(floatPointField, floatPointField, floatPointField, floatPointField, floatPointField, 1, 2, 3, 4, 5, 6, 7); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static float test45_helper(FloatPoint fp1, FloatPoint fp2, FloatPoint fp3, FloatPoint fp4, FloatPoint fp5, int a1, int a2, int a3, int a4, int a5, int a6, int a7) { + return fp1.x + fp1.y + + fp2.x + fp2.y + + fp3.x + fp3.y + + fp4.x + fp4.y + + fp5.x + fp5.y + + a1 + a2 + a3 + a4 + a5 + a6 + a7; + } + + @Run(test = "test45") + public void test45_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 2; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + float result = test45(); + float n = test45_helper(floatPointField, floatPointField, floatPointField, floatPointField, floatPointField, 1, 2, 3, 4, 5, 6, 7); + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokestatic, packing causes stack growth (1 extra stack word -- mixing Point and FloatPoint) + @Test(compLevel = CompLevel.C2) + public float test46() { + return test46_helper(floatPointField, floatPointField, pointField, floatPointField, floatPointField, pointField, floatPointField, 1, 2, 3, 4, 5, 6, 7); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static float test46_helper(FloatPoint fp1, FloatPoint fp2, Point p1, FloatPoint fp3, FloatPoint fp4, Point p2, FloatPoint fp5, int a1, int a2, int a3, int a4, int a5, int a6, int a7) { + return p1.x + p1.y + + p2.x + p2.y + + fp1.x + fp1.y + + fp2.x + fp2.y + + fp3.x + fp3.y + + fp4.x + fp4.y + + fp5.x + fp5.y + + a1 + a2 + a3 + a4 + a5 + a6 + a7; + } + + @Run(test = "test46") + public void test46_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 2; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + float result = test46(); + float n = test46_helper(floatPointField, floatPointField, pointField, floatPointField, floatPointField, pointField, floatPointField, 1, 2, 3, 4, 5, 6, 7); + Asserts.assertEQ(result, n); + } + } + + static class MyRuntimeException extends RuntimeException { + MyRuntimeException(String s) { + super(s); + } + } + + static void checkStackTrace(Throwable t, String... methodNames) { + StackTraceElement[] trace = t.getStackTrace(); + for (int i = 0; i < methodNames.length; i++) { + if (!methodNames[i].equals(trace[i].getMethodName())) { + String error = "Unexpected stack trace: level " + i + " should be " + methodNames[i]; + System.out.println(error); + t.printStackTrace(System.out); + throw new RuntimeException(error, t); + } + } + } + //* + + // C2->C1 invokestatic, make sure stack walking works (with static variable) + @Test(compLevel = CompLevel.C2) + public void test47(int n) { + try { + test47_helper(floatPointField, 1, 2, 3, 4, 5); + test47_value = 666; + } catch (MyRuntimeException e) { + // expected; + } + test47_value = n; + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static float test47_helper(FloatPoint fp, int a1, int a2, int a3, int a4, int a5) { + test47_thrower(); + return 0.0f; + } + + @DontCompile + private static void test47_thrower() { + MyRuntimeException e = new MyRuntimeException("This exception should have been caught!"); + checkStackTrace(e, "test47_thrower", "test47_helper", "test47", "test47_verifier"); + throw e; + } + + static int test47_value = 999; + + @Run(test = "test47") + public void test47_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 5; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + test47_value = 777 + i; + test47(i); + Asserts.assertEQ(test47_value, i); + } + } + + // C2->C1 invokestatic, make sure stack walking works (with returned value object) + @Test(compLevel = CompLevel.C2) + public int test48(int n) { + try { + test48_helper(floatPointField, 1, 2, 3, 4, 5); + return 666; + } catch (MyRuntimeException e) { + // expected; + } + return n; + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static float test48_helper(FloatPoint fp, int a1, int a2, int a3, int a4, int a5) { + test48_thrower(); + return 0.0f; + } + + @DontCompile + private static void test48_thrower() { + MyRuntimeException e = new MyRuntimeException("This exception should have been caught!"); + checkStackTrace(e, "test48_thrower", "test48_helper", "test48", "test48_verifier"); + throw e; + } + + @Run(test = "test48") + public void test48_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 5; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + int n = test48(i); + Asserts.assertEQ(n, i); + } + } + + // C2->interpreter invokestatic, make sure stack walking works (same as test 48, but with stack extension/repair) + // (this is the baseline for test50 -- + // the only difference is: test49_helper is interpreted but test50_helper is compiled by C1). + @Test(compLevel = CompLevel.C2) + public int test49(int n) { + try { + test49_helper(floatPointField, 1, 2, 3, 4, 5, 6); + return 666; + } catch (MyRuntimeException e) { + // expected; + } + return n; + } + + @DontCompile + private static float test49_helper(FloatPoint fp, int a1, int a2, int a3, int a4, int a5, int a6) { + test49_thrower(); + return 0.0f; + } + + @DontCompile + private static void test49_thrower() { + MyRuntimeException e = new MyRuntimeException("This exception should have been caught!"); + checkStackTrace(e, "test49_thrower", "test49_helper", "test49", "test49_verifier"); + throw e; + } + + @Run(test = "test49") + public void test49_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 5; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + int n = test49(i); + Asserts.assertEQ(n, i); + } + } + + // C2->C1 invokestatic, make sure stack walking works (same as test 48, but with stack extension/repair) + @Test(compLevel = CompLevel.C2) + public int test50(int n) { + try { + test50_helper(floatPointField, 1, 2, 3, 4, 5, 6); + return 666; + } catch (MyRuntimeException e) { + // expected; + } + return n; + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static float test50_helper(FloatPoint fp, int a1, int a2, int a3, int a4, int a5, int a6) { + test50_thrower(); + return 0.0f; + } + + @DontCompile + private static void test50_thrower() { + MyRuntimeException e = new MyRuntimeException("This exception should have been caught!"); + checkStackTrace(e, "test50_thrower", "test50_helper", "test50", "test50_verifier"); + throw e; + } + + @Run(test = "test50") + public void test50_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 5; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + int n = test50(i); + Asserts.assertEQ(n, i); + } + } + + + // C2->C1 invokestatic, value class with ref fields (RefPoint) + @Test(compLevel = CompLevel.C2) + public int test51() { + return test51_helper(refPointField1); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static int test51_helper(RefPoint rp1) { + return rp1.x.n + rp1.y.n; + } + + @Run(test = "test51") + public void test51_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 5; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + int result = test51(); + int n = test51_helper(refPointField1); + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokestatic, value class with ref fields (Point, RefPoint) + @Test(compLevel = CompLevel.C2) + public int test52() { + return test52_helper(pointField, refPointField1); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static int test52_helper(Point p1, RefPoint rp1) { + return p1.x + p1.y + rp1.x.n + rp1.y.n; + } + + @Run(test = "test52") + public void test52_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 5; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + int result = test52(); + int n = test52_helper(pointField, refPointField1); + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokestatic, value class with ref fields (RefPoint, RefPoint, RefPoint, RefPoint) + @Test(compLevel = CompLevel.C2) + public int test53() { + return test53_helper(refPointField1, refPointField2, refPointField1, refPointField2); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static int test53_helper(RefPoint rp1, RefPoint rp2, RefPoint rp3, RefPoint rp4) { + return rp1.x.n + rp1.y.n + + rp2.x.n + rp2.y.n + + rp3.x.n + rp3.y.n + + rp4.x.n + rp4.y.n; + } + + @Run(test = "test53") + public void test53_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 5; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + int result = test53(); + int n = test53_helper(refPointField1, refPointField2, refPointField1, refPointField2); + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokestatic, value class with ref fields (RefPoint, RefPoint, float, int, RefPoint, RefPoint) + @Test(compLevel = CompLevel.C2) + public int test54() { + return test54_helper(refPointField1, refPointField2, 1.0f, 2, refPointField1, refPointField2); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static int test54_helper(RefPoint rp1, RefPoint rp2, float f, int i, RefPoint rp3, RefPoint rp4) { + return rp1.x.n + rp1.y.n + + rp2.x.n + rp2.y.n + + (int)(f) + i + + rp3.x.n + rp3.y.n + + rp4.x.n + rp4.y.n; + } + + @Run(test = "test54") + public void test54_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 5; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + int result = test54(); + int n = test54_helper(refPointField1, refPointField2, 1.0f, 2, refPointField1, refPointField2); + Asserts.assertEQ(result, n); + } + } + + /** + * Each allocation with a "try" block like this will cause a GC + * + * try (ForceGCMarker m = ForceGCMarker.mark(warmup)) { + * result = test55(p1); + * } + */ + + static final String ScavengeALot = "ScavengeALot"; + + static class ForceGCMarker implements java.io.Closeable { + ForceGCMarker() { + WhiteBox.getWhiteBox().setBooleanVMFlag(ScavengeALot, true); + } + public void close() { + WhiteBox.getWhiteBox().setBooleanVMFlag(ScavengeALot, false); + } + + static ForceGCMarker mark(boolean warmup) { + return warmup ? null : new ForceGCMarker(); + } + } + + // C2->C1 invokestatic, force GC for every allocation when entering a C1 VEP (Point) + @Test(compLevel = CompLevel.C2) + public int test55(Point p1) { + return test55_helper(p1); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static int test55_helper(Point p1) { + return p1.x + p1.y; + } + + @Run(test = "test55") + public void test55_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 5; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + Point p1 = new Point(1, 2); + int result; + try (ForceGCMarker m = ForceGCMarker.mark(info.isWarmUp())) { + result = test55(p1); + } + int n = test55_helper(p1); + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokestatic, force GC for every allocation when entering a C1 VEP (RefPoint) + @Test(compLevel = CompLevel.C2) + public int test56(RefPoint rp1) { + return test56_helper(rp1); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static int test56_helper(RefPoint rp1) { + return rp1.x.n + rp1.y.n; + } + + @Run(test = "test56") + public void test56_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 5; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + RefPoint rp1 = new RefPoint(1, 2); + int result; + try (ForceGCMarker m = ForceGCMarker.mark(info.isWarmUp())) { + result = test56(rp1); + } + int n = test56_helper(rp1); + Asserts.assertEQ(result, n); + } + } + + // C2->Interpreter (same as test56, but test C2i entry instead of C1) + @Test(compLevel = CompLevel.C2) + public int test57(RefPoint rp1) { + return test57_helper(rp1); + } + + @DontCompile + private static int test57_helper(RefPoint rp1) { + return rp1.x.n + rp1.y.n; + } + + @Run(test = "test57") + public void test57_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 5; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + RefPoint rp1 = new RefPoint(1, 2); + int result; + try (ForceGCMarker m = ForceGCMarker.mark(info.isWarmUp())) { + result = test57(rp1); + } + int n = test57_helper(rp1); + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokestatic, force GC for every allocation when entering a C1 VEP (a bunch of RefPoints and Numbers); + @Test(compLevel = CompLevel.C2) + public int test58(RefPoint rp1, RefPoint rp2, Number n1, RefPoint rp3, RefPoint rp4, Number n2) { + return test58_helper(rp1, rp2, n1, rp3, rp4, n2); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static int test58_helper(RefPoint rp1, RefPoint rp2, Number n1, RefPoint rp3, RefPoint rp4, Number n2) { + return rp1.x.n + rp1.y.n + + rp2.x.n + rp2.y.n + + n1.n + + rp3.x.n + rp3.y.n + + rp4.x.n + rp4.y.n + + n2.n; + } + + @Run(test = "test58") + public void test58_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 5; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + RefPoint rp1 = new RefPoint(1, 2); + RefPoint rp2 = refPointField1; + RefPoint rp3 = new RefPoint(222, 777); + RefPoint rp4 = refPointField2; + Number n1 = new Number(5878); + Number n2 = new Number(1234); + int result; + try (ForceGCMarker m = ForceGCMarker.mark(info.isWarmUp())) { + result = test58(rp1, rp2, n1, rp3, rp4, n2); + } + int n = test58_helper(rp1, rp2, n1, rp3, rp4, n2); + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokestatic, GC inside main body of C1-compiled method (caller's args should not be GC'ed). + @Test(compLevel = CompLevel.C2) + public int test59(RefPoint rp1, boolean doGC) { + return test59_helper(rp1, 11, 222, 3333, 4444, doGC); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static int test59_helper(RefPoint rp1, int a1, int a2, int a3, int a4, boolean doGC) { + if (doGC) { + System.gc(); + } + return rp1.x.n + rp1.y.n + a1 + a2 + a3 + a4; + } + + @Run(test = "test59") + public void test59_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 5; + boolean doGC = !info.isWarmUp(); + for (int i = 0; i < count; i++) { // need a loop to test inline cache + RefPoint rp1 = new RefPoint(1, 2); + int result = test59(rp1, doGC); + int n = test59_helper(rp1, 11, 222, 3333, 4444, doGC); + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokestatic, GC inside main body of C1-compiled method (caller's args should not be GC'ed). + // same as test59, but the incoming (scalarized) oops are passed in both registers and stack. + @Test(compLevel = CompLevel.C2) + public int test60(RefPoint rp1, RefPoint rp2, boolean doGC) { + return test60_helper(555, 6666, 77777, rp1, rp2, 11, 222, 3333, 4444, doGC); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static int test60_helper(int x0, int x1, int x2, RefPoint rp1, RefPoint rp2,int a1, int a2, int a3, int a4, boolean doGC) { + // On x64, C2 passes: reg0=x1, reg1=x1, reg2=x2, reg3=rp1.x, reg4=rp1.y, reg5=rp2.x stack0=rp2.y .... + // C1 expects: reg0=x1, reg1=x1, reg2=x2, reg3=rp1, reg4=rp2, reg5=a1 stack0=a2 ... + // When GC happens, make sure it does not treat reg5 and stack0 as oops! + if (doGC) { + System.gc(); + } + return x0 + x1 + x2 + rp1.x.n + rp1.y.n + rp2.x.n + rp2.y.n + a1 + a2 + a3 + a4; + } + + @Run(test = "test60") + public void test60_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 5; + boolean doGC = !info.isWarmUp(); + for (int i = 0; i < count; i++) { // need a loop to test inline cache + RefPoint rp1 = new RefPoint(1, 2); + RefPoint rp2 = new RefPoint(33, 44); + int result = test60(rp1, rp2, doGC); + int n = test60_helper(555, 6666, 77777, rp1, rp2, 11, 222, 3333, 4444, doGC); + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokeinterface via VVEP(RO) + @Test(compLevel = CompLevel.C2) + public int test61(RefPoint_Access rpa, RefPoint rp2) { + return rpa.func1(rp2); + } + + @Run(test = "test61") + public void test61_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 20; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + RefPoint_Access rpa = get_RefPoint_Access(); + RefPoint rp2 = refPointField2; + int result = test61(rpa, rp2); + int n = rpa.func1(rp2); + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokeinterface via VVEP(RO) -- force GC for every allocation when entering a C1 VVEP(RO) (RefPoint) + @Test(compLevel = CompLevel.C2) + public int test62(RefPoint_Access rpa, RefPoint rp2) { + return rpa.func1(rp2); + } + + @Run(test = "test62") + public void test62_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 20; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + RefPoint_Access rpa = get_RefPoint_Access(); + RefPoint rp2 = new RefPoint(111, 2222); + int result; + try (ForceGCMarker m = ForceGCMarker.mark(info.isWarmUp())) { + result = test62(rpa, rp2); + } + int n = rpa.func1(rp2); + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokeinterface via VVEP(RO) -- force GC for every allocation when entering a C1 VVEP(RO) (a bunch of RefPoints and Numbers) + @Test(compLevel = CompLevel.C2) + public int test63(RefPoint_Access rpa, RefPoint rp1, RefPoint rp2, Number n1, RefPoint rp3, RefPoint rp4, Number n2) { + return rpa.func2(rp1, rp2, n1, rp3, rp4, n2); + } + + @Run(test = "test63") + public void test63_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 20; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + RefPoint_Access rpa = get_RefPoint_Access(); + RefPoint rp1 = new RefPoint(1, 2); + RefPoint rp2 = refPointField1; + RefPoint rp3 = new RefPoint(222, 777); + RefPoint rp4 = refPointField2; + Number n1 = new Number(5878); + Number n2 = new Number(1234); + int result; + try (ForceGCMarker m = ForceGCMarker.mark(info.isWarmUp())) { + result = test63(rpa, rp1, rp2, n1, rp3, rp4, n2); + } + int n = rpa.func2(rp1, rp2, n1, rp3, rp4, n2); + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokestatic (same as test63, but use invokestatic instead) + @Test(compLevel = CompLevel.C2) + public int test64(RefPoint_Access rpa, RefPoint rp1, RefPoint rp2, Number n1, RefPoint rp3, RefPoint rp4, Number n2) { + return test64_helper(rpa, rp1, rp2, n1, rp3, rp4, n2); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + public static int test64_helper(RefPoint_Access rpa, RefPoint rp1, RefPoint rp2, Number n1, RefPoint rp3, RefPoint rp4, Number n2) { + return rp3.y.n; + } + + @Run(test = "test64") + public void test64_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 20; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + RefPoint_Access rpa = get_RefPoint_Access(); + RefPoint rp1 = new RefPoint(1, 2); + RefPoint rp2 = refPointField1; + RefPoint rp3 = new RefPoint(222, 777); + RefPoint rp4 = refPointField2; + Number n1 = new Number(5878); + Number n2 = new Number(1234); + int result; + try (ForceGCMarker m = ForceGCMarker.mark(info.isWarmUp())) { + result = test64(rpa, rp1, rp2, n1, rp3, rp4, n2); + } + int n = test64_helper(rpa, rp1, rp2, n1, rp3, rp4, n2); + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokevirtual via VVEP(RO) (opt_virtual_call) + @Test(compLevel = CompLevel.C2) + public int test76(RefPoint rp1, RefPoint rp2) { + return rp1.final_func(rp2); + } + + @Run(test = "test76") + public void test76_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 5; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + RefPoint rp1 = refPointField1; + RefPoint rp2 = refPointField2; + int result = test76(rp1, rp2); + int n = rp1.final_func(rp2); + Asserts.assertEQ(result, n); + } + } + + // C2->C1 invokevirtual, force GC for every allocation when entering a C1 VEP (RefPoint) + // Same as test56, except we call the VVEP(RO) instead of VEP. + @Test(compLevel = CompLevel.C2) + public int test77(RefPoint rp1, RefPoint rp2) { + return rp1.final_func(rp2); + } + + @Run(test = "test77") + public void test77_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 5; + for (int i = 0; i < count; i++) { // need a loop to test inline cache + RefPoint rp1 = new RefPoint(1, 2); + RefPoint rp2 = new RefPoint(22, 33); + int result; + try (ForceGCMarker m = ForceGCMarker.mark(info.isWarmUp())) { + result = test77(rp1, rp2); + } + int n = rp1.final_func(rp2); + Asserts.assertEQ(result, n); + } + } + + //------------------------------------------------------------------------------- + // Tests for how C1 handles InlineTypeReturnedAsFields in both calls and returns + //------------------------------------------------------------------------------- + // C2->C1 invokestatic with InlineTypeReturnedAsFields (Point) + @Test(compLevel = CompLevel.C2) + public int test78(Point p) { + Point np = test78_helper(p); + return np.x + np.y; + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static Point test78_helper(Point p) { + return p; + } + + @Run(test = "test78") + public void test78_verifier() { + int result = test78(pointField1); + int n = pointField1.x + pointField1.y; + Asserts.assertEQ(result, n); + } + + // C2->C1 invokestatic with InlineTypeReturnedAsFields (RefPoint) + @Test(compLevel = CompLevel.C2) + public int test79(RefPoint p) { + RefPoint np = test79_helper(p); + return np.x.n + np.y.n; + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static RefPoint test79_helper(RefPoint p) { + return p; + } + + @Run(test = "test79") + public void test79_verifier() { + int result = test79(refPointField1); + int n = refPointField1.x.n + refPointField1.y.n; + Asserts.assertEQ(result, n); + } + + // C1->C2 invokestatic with InlineTypeReturnedAsFields (RefPoint) + @Test(compLevel = CompLevel.C1_SIMPLE) + public int test80(RefPoint p) { + RefPoint np = test80_helper(p); + return np.x.n + np.y.n; + } + + @DontInline + @ForceCompile(CompLevel.C2) + private static RefPoint test80_helper(RefPoint p) { + return p; + } + + @Run(test = "test80") + public void test80_verifier() { + int result = test80(refPointField1); + int n = refPointField1.x.n + refPointField1.y.n; + Asserts.assertEQ(result, n); + } + + // Interpreter->C1 invokestatic with InlineTypeReturnedAsFields (Point) + @Test(compLevel = CompLevel.C1_SIMPLE) + public Point test81(Point p) { + return p; + } + + @Run(test = "test81") + public void test81_verifier() { + Point p = test81(pointField1); + Asserts.assertEQ(p.x, pointField1.x); + Asserts.assertEQ(p.y, pointField1.y); + p = test81(pointField2); + Asserts.assertEQ(p.x, pointField2.x); + Asserts.assertEQ(p.y, pointField2.y); + } + + // C1->Interpreter invokestatic with InlineTypeReturnedAsFields (RefPoint) + @Test(compLevel = CompLevel.C1_SIMPLE) + public int test82(RefPoint p) { + RefPoint np = test82_helper(p); + return np.x.n + np.y.n; + } + + @DontCompile + private static RefPoint test82_helper(RefPoint p) { + return p; + } + + @Run(test = "test82") + public void test82_verifier() { + int result = test82(refPointField1); + int n = refPointField1.x.n + refPointField1.y.n; + Asserts.assertEQ(result, n); + } + + //------------------------------------------------------------------------------- + // Tests for InlineTypeReturnedAsFields vs the value class TooBigToReturnAsFields + //------------------------------------------------------------------------------- + + // C2->C1 invokestatic with InlineTypeReturnedAsFields (TooBigToReturnAsFields) + @Test(compLevel = CompLevel.C2) + public int test83(TooBigToReturnAsFields p) { + TooBigToReturnAsFields np = test83_helper(p); + return p.a0 + p.a5; + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static TooBigToReturnAsFields test83_helper(TooBigToReturnAsFields p) { + return p; + } + + @Run(test = "test83") + public void test83_verifier() { + int result = test83(tooBig); + int n = tooBig.a0 + tooBig.a5; + Asserts.assertEQ(result, n); + } + + // C1->C2 invokestatic with InlineTypeReturnedAsFields (TooBigToReturnAsFields) + @Test(compLevel = CompLevel.C1_SIMPLE) + public int test84(TooBigToReturnAsFields p) { + TooBigToReturnAsFields np = test84_helper(p); + return p.a0 + p.a5; + } + + @DontInline + @ForceCompile(CompLevel.C2) + private static TooBigToReturnAsFields test84_helper(TooBigToReturnAsFields p) { + return p; + } + + @Run(test = "test84") + public void test84_verifier() { + int result = test84(tooBig); + int n = tooBig.a0 + tooBig.a5; + Asserts.assertEQ(result, n); + } + + // Interpreter->C1 invokestatic with InlineTypeReturnedAsFields (TooBigToReturnAsFields) + @Test(compLevel = CompLevel.C1_SIMPLE) + public TooBigToReturnAsFields test85(TooBigToReturnAsFields p) { + return p; + } + + @Run(test = "test85") + public void test85_verifier() { + TooBigToReturnAsFields p = test85(tooBig); + Asserts.assertEQ(p.a0, tooBig.a0); + Asserts.assertEQ(p.a2, tooBig.a2); + } + + // C1->Interpreter invokestatic with InlineTypeReturnedAsFields (TooBigToReturnAsFields) + @Test(compLevel = CompLevel.C1_SIMPLE) + public int test86(TooBigToReturnAsFields p) { + TooBigToReturnAsFields np = test86_helper(p); + return p.a0 + p.a5; + } + + @DontCompile + private static TooBigToReturnAsFields test86_helper(TooBigToReturnAsFields p) { + return p; + } + + @Run(test = "test86") + public void test86_verifier() { + int result = test86(tooBig); + int n = tooBig.a0 + tooBig.a5; + Asserts.assertEQ(result, n); + } + + //------------------------------------------------------------------------------- + // Tests for how C1 handles InlineTypeReturnedAsFields in both calls and returns + //------------------------------------------------------------------------------- + + // C2->C1 invokestatic with InlineTypeReturnedAsFields (RefPoint) + @Test(compLevel = CompLevel.C2) + public RefPoint test87(RefPoint p) { + return test87_helper(p); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static RefPoint test87_helper(RefPoint p) { + return p; + } + + @Run(test = "test87") + public void test87_verifier() { + Asserts.assertEQ(test87(null), null); + Asserts.assertEQ(test87(refPointField1), refPointField1); + } + + // C2->C1 invokestatic with InlineTypeReturnedAsFields (RefPoint with constant null) + @Test(compLevel = CompLevel.C2) + public RefPoint test88() { + return test88_helper(); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static RefPoint test88_helper() { + return null; + } + + @Run(test = "test88") + public void test88_verifier() { + Asserts.assertEQ(test88(), null); + } + + // C1->C2 invokestatic with InlineTypeReturnedAsFields (RefPoint) + @Test(compLevel = CompLevel.C1_SIMPLE) + public RefPoint test89(RefPoint p) { + return test89_helper(p); + } + + @DontInline + @ForceCompile(CompLevel.C2) + private static RefPoint test89_helper(RefPoint p) { + return p; + } + + @Run(test = "test89") + public void test89_verifier() { + Asserts.assertEQ(test89(null), null); + Asserts.assertEQ(test89(refPointField1), refPointField1); + } + + //---------------------------------------------------------------------------------- + // Tests for unverified entries: there are 6 cases: + // C1 -> Unverified Value Entry compiled by C1 + // C1 -> Unverified Value Entry compiled by C2 + // C2 -> Unverified Entry compiled by C1 (target is NOT a value class) + // C2 -> Unverified Entry compiled by C2 (target is NOT a value class) + // C2 -> Unverified Entry compiled by C1 (target IS a value class, i.e., has VVEP_RO) + // C2 -> Unverified Entry compiled by C2 (target IS a value class, i.e., has VVEP_RO) + //---------------------------------------------------------------------------------- + + // C1->C1 invokeinterface -- call Unverified Value Entry of MyImplPojo1.func2 (compiled by C1) + @Test(compLevel = CompLevel.C1_SIMPLE) + public int test90(Intf intf, int a, int b) { + return intf.func2(a, b, pointField); + } + + static Intf test90_intfs[] = { + new MyImplPojo1(), + new MyImplPojo2(), + }; + + @Run(test = "test90") + public void test90_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 20; + for (int i = 0; i < count; i++) { + Intf intf = test90_intfs[i % test90_intfs.length]; + int result = test90(intf, 123, 456) + i; + Asserts.assertEQ(result, intf.func2(123, 456, pointField) + i); + } + } + + // C1->C2 invokeinterface -- call Unverified Value Entry of MyImplPojo2.func2 (compiled by C2) + @Test(compLevel = CompLevel.C1_SIMPLE) + public int test91(Intf intf, int a, int b) { + return intf.func2(a, b, pointField); + } + + static Intf test91_intfs[] = { + new MyImplPojo2(), + new MyImplPojo1(), + }; + + @Run(test = "test91") + public void test91_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 20; + for (int i = 0; i < count; i++) { + Intf intf = test91_intfs[i % test91_intfs.length]; + int result = test91(intf, 123, 456) + i; + Asserts.assertEQ(result, intf.func2(123, 456, pointField) + i); + } + } + + // C2->C1 invokeinterface -- call Unverified Entry of MyImplPojo1.func2 (compiled by C1) + @Test(compLevel = CompLevel.C2) + public int test92(Intf intf, int a, int b) { + return intf.func2(a, b, pointField); + } + + static Intf test92_intfs[] = { + new MyImplPojo1(), + new MyImplPojo2(), + }; + + @Run(test = "test92") + public void test92_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 20; + for (int i = 0; i < count; i++) { + Intf intf = test92_intfs[i % test92_intfs.length]; + int result = test92(intf, 123, 456) + i; + Asserts.assertEQ(result, intf.func2(123, 456, pointField) + i); + } + } + + // C2->C2 invokeinterface -- call Unverified Entry of MyImplPojo2.func2 (compiled by C2) + @Test(compLevel = CompLevel.C2) + public int test93(Intf intf, int a, int b) { + return intf.func2(a, b, pointField); + } + + static Intf test93_intfs[] = { + new MyImplPojo2(), + new MyImplPojo1(), + }; + + @Run(test = "test93") + public void test93_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 20; + for (int i = 0; i < count; i++) { + Intf intf = test93_intfs[i % test93_intfs.length]; + int result = test93(intf, 123, 456) + i; + Asserts.assertEQ(result, intf.func2(123, 456, pointField) + i); + } + } + + // C2->C1 invokeinterface -- call Unverified Entry of MyImplVal1.func2 (compiled by C1 - has VVEP_RO) + @Test(compLevel = CompLevel.C2) + public int test94(Intf intf, int a, int b) { + return intf.func2(a, b, pointField); + } + + static Intf test94_intfs[] = { + new MyImplVal1(), + new MyImplVal2(), + }; + + @Run(test = "test94") + public void test94_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 20; + for (int i = 0; i < count; i++) { + Intf intf = test94_intfs[i % test94_intfs.length]; + int result = test94(intf, 123, 456) + i; + Asserts.assertEQ(result, intf.func2(123, 456, pointField) + i); + } + } + + // C2->C2 invokeinterface -- call Unverified Entry of MyImplVal2.func2 (compiled by C2 - has VVEP_RO) + @Test(compLevel = CompLevel.C2) + public int test95(Intf intf, int a, int b) { + return intf.func2(a, b, pointField); + } + + static Intf test95_intfs[] = { + new MyImplVal2(), + new MyImplVal1(), + }; + + @Run(test = "test95") + public void test95_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 20; + for (int i = 0; i < count; i++) { + Intf intf = test95_intfs[i % test95_intfs.length]; + int result = test95(intf, 123, 456) + i; + Asserts.assertEQ(result, intf.func2(123, 456, pointField) + i); + } + } + + // C1->C2 GC handling in StubRoutines::store_inline_type_fields_to_buf() + @Test(compLevel = CompLevel.C1_SIMPLE) + public RefPoint test96(RefPoint rp, boolean b) { + RefPoint p = test96_helper(rp); + if (b) { + return rp; + } + return p; + } + + @DontInline + @ForceCompile(CompLevel.C2) + public RefPoint test96_helper(RefPoint rp) { + return rp; + } + + @Run(test = "test96") + public void test96_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 20000; // Do enough iteration to cause GC inside StubRoutines::store_inline_type_fields_to_buf + Number x = new Number(10); // old object + for (int i = 0; i < count; i++) { + Number y = new Number(i); // new object for each iteraton + RefPoint rp1 = new RefPoint(x, y); + RefPoint rp2 = test96(rp1, info.isWarmUp()); + + Asserts.assertEQ(rp1.x, x); + Asserts.assertEQ(rp1.y, y); + Asserts.assertEQ(rp1.y.n, i); + } + } + + // C1->C1 - caller is compiled first. It invokes callee(test97) a few times while the + // callee is executed by the interpreter. Then, callee is compiled + // and SharedRuntime::fixup_callers_callsite is called to fix up the + // callsite from test97_verifier->test97. + @Test(compLevel = CompLevel.C1_SIMPLE) + public int test97(Point p1, Point p2) { + return test97_helper(p1, p2); + } + + @DontCompile + public int test97_helper(Point p1, Point p2) { + return p1.x + p1.y + p2.x + p2.y; + } + + @ForceCompile(CompLevel.C1_SIMPLE) + public void test97_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 20; + for (int i = 0; i < count; i++) { + int result = test97(pointField1, pointField2); + int n = test97_helper(pointField1, pointField2); + Asserts.assertEQ(result, n); + } + } + + @Run(test = "test97") + public void run_test97_verifier(RunInfo info) { + test97_verifier(info); + } + + // C1->C2 - same as test97, except the callee is compiled by C2. + @Test(compLevel = CompLevel.C2) + public int test98(Point p1, Point p2) { + return test98_helper(p1, p2); + } + + @DontCompile + public int test98_helper(Point p1, Point p2) { + return p1.x + p1.y + p2.x + p2.y; + } + + @ForceCompile(CompLevel.C1_SIMPLE) + public void test98_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 20; + for (int i = 0; i < count; i++) { + int result = test98(pointField1, pointField2); + int n = test98_helper(pointField1, pointField2); + Asserts.assertEQ(result, n); + } + } + + @Run(test = "test98") + public void run_test98_verifier(RunInfo info) { + test98_verifier(info); + } + + // C1->C2 - same as test97, except the callee is a static method. + @Test(compLevel = CompLevel.C1_SIMPLE) + public static int test99(Point p1, Point p2) { + return test99_helper(p1, p2); + } + + @DontCompile + public static int test99_helper(Point p1, Point p2) { + return p1.x + p1.y + p2.x + p2.y; + } + + @ForceCompile(CompLevel.C1_SIMPLE) + public void test99_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 20; + for (int i = 0; i < count; i++) { + int result = test99(pointField1, pointField2); + int n = test99_helper(pointField1, pointField2); + Asserts.assertEQ(result, n); + } + } + + @Run(test = "test99") + public void run_test99_verifier(RunInfo info) { + test99_verifier(info); + } + + // C2->C1 invokestatic, packing causes stack growth (1 extra stack word). + // Make sure stack frame is set up properly for GC. + @Test(compLevel = CompLevel.C2) + public float test100(FloatPoint fp1, FloatPoint fp2, RefPoint rp, int a1, int a2, int a3, int a4) { + return test100_helper(fp1, fp2, rp, a1, a2, a3, a4); + } + + @DontInline + @ForceCompile(CompLevel.C1_SIMPLE) + private static float test100_helper(FloatPoint fp1, FloatPoint fp2, RefPoint rp, int a1, int a2, int a3, int a4) { + // On x64: + // Scalarized entry -- all parameters are passed in registers + // xmm0 = fp1.x + // xmm1 = fp1.y + // xmm2 = fp2.x + // xmm3 = fp2.y + // rsi = rp.x (oop) + // rdx = rp.y (oop) + // cx = a1 + // r8 = a2 + // r9 = a3 + // di = a4 + // Non-scalarized entry -- a6 is passed on stack[0] + // rsi = fp1 + // rdx = fp2 + // rcx = rp + // r8 = a1 + // r9 = a2 + // di = a3 + // [sp + ??] = a4 + return fp1.x + fp1.y + fp2.x + fp2.y + rp.x.n + rp.y.n + a1 + a2 + a3 + a4; + } + + @Run(test = "test100") + public void test100_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 4; + for (int i = 0; i < count; i++) { + FloatPoint fp1 = new FloatPoint(i+0, i+11); + FloatPoint fp2 = new FloatPoint(i+222, i+3333); + RefPoint rp = new RefPoint(i+44444, i+555555); + float result; + try (ForceGCMarker m = ForceGCMarker.mark(info.isWarmUp())) { + result = test100(fp1, fp2, rp, 1, 2, 3, 4); + } + float n = test100_helper(fp1, fp2, rp, 1, 2, 3, 4); + Asserts.assertEQ(result, n); + } + } + + // C1->C2 force GC for every allocation when storing the returned + // fields back into a buffered object. + @Test(compLevel = CompLevel.C1_SIMPLE) + public RefPoint test101(RefPoint rp) { + return test101_helper(rp); + } + + @DontInline + @ForceCompile(CompLevel.C2) + public RefPoint test101_helper(RefPoint rp) { + return rp; + } + + @Run(test = "test101") + public void test101_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 5; + for (int i = 0; i < count; i++) { + RefPoint rp = new RefPoint(1, 2); + Object x = rp.x; + Object y = rp.y; + RefPoint result = new RefPoint(3, 4); + try (ForceGCMarker m = ForceGCMarker.mark(info.isWarmUp())) { + result = test101(rp); + } + Asserts.assertEQ(rp.x, result.x); + Asserts.assertEQ(rp.y, result.y); + Asserts.assertEQ(x, result.x); + Asserts.assertEQ(y, result.y); + } + } + + // Same as test101, except we have Interpreter->C2 instead. + @Test(compLevel = CompLevel.C1_SIMPLE) + public RefPoint test102(RefPoint rp) { + return test102_interp(rp); + } + + @DontInline + public RefPoint test102_interp(RefPoint rp) { + return test102_helper(rp); + } + + @DontInline + @ForceCompile(CompLevel.C2) + public RefPoint test102_helper(RefPoint rp) { + return rp; + } + + @Run(test = "test102") + public void test102_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 5; + for (int i = 0; i < count; i++) { + RefPoint rp = new RefPoint(11, 22); + Object x = rp.x; + Object y = rp.y; + RefPoint result = new RefPoint(333, 444); + try (ForceGCMarker m = ForceGCMarker.mark(info.isWarmUp())) { + result = test102(rp); + } + Asserts.assertEQ(rp.x, result.x); + Asserts.assertEQ(rp.y, result.y); + Asserts.assertEQ(x, result.x); + Asserts.assertEQ(y, result.y); + } + } + + @Test(compLevel = CompLevel.C1_SIMPLE) + public void test103() { + // when this method is compiled by C1, the Test103Value class is not yet loaded. + test103_v = new Test103Value(); // invokestatic "Test103Value.()QTest103Value;" + } + + @LooselyConsistentValue + static value class Test103Value { + int x = rI; + } + + static Object test103_v; + + @Run(test = "test103") + public void test103_verifier(RunInfo info) { + if (info.isWarmUp()) { + // Make sure test103() is compiled before Test103Value is loaded + return; + } + test103(); + Test103Value v = (Test103Value)test103_v; + Asserts.assertEQ(v.x, rI); + } + + + // Same as test103, but with a value class that's too big to return as fields. + @Test(compLevel = CompLevel.C1_SIMPLE) + public void test104() { + // when this method is compiled by C1, the Test104Value class is not yet loaded. + test104_v = new Test104Value(); // invokestatic "Test104Value.()QTest104Value;" + } + + @LooselyConsistentValue + static value class Test104Value { + long x0 = rL; + long x1 = rL; + long x2 = rL; + long x3 = rL; + long x4 = rL; + long x5 = rL; + long x6 = rL; + long x7 = rL; + long x8 = rL; + long x9 = rL; + long xa = rL; + long xb = rL; + long xc = rL; + long xd = rL; + long xe = rL; + long xf = rL; + } + + static Object test104_v; + + @Run(test = "test104") + public void test104_verifier(RunInfo info) { + if (info.isWarmUp()) { + // Make sure test104() is compiled before Test104Value is loaded + return; + } + test104(); + Test104Value v = (Test104Value)test104_v; + Asserts.assertEQ(v.x0, rL); + } + + // C2->C1 invokeinterface -- call Unverified Entry of MyImplVal1.func1 (compiled by C1 - has VVEP_RO) + /// (same as test94, except we are calling func1, which shares VVEP and VVEP_RO + @Test(compLevel = CompLevel.C2) + public int test105(Intf intf, int a, int b) { + return intf.func1(a, b); + } + + static Intf test105_intfs[] = { + new MyImplVal1(), + new MyImplVal2(), + }; + + @Run(test = "test105") + public void test105_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 20; + for (int i = 0; i < count; i++) { + Intf intf = test105_intfs[i % test105_intfs.length]; + int result = test105(intf, 123, 456) + i; + Asserts.assertEQ(result, intf.func1(123, 456) + i); + } + } + + // C2->C2 invokeinterface -- call Unverified Entry of MyImplVal2.func1 (compiled by C2 - has VVEP_RO) + /// (same as test95, except we are calling func1, which shares VVEP and VVEP_RO + @Test(compLevel = CompLevel.C2) + public int test106(Intf intf, int a, int b) { + return intf.func1(a, b); + } + + static Intf test106_intfs[] = { + new MyImplVal2(), + new MyImplVal1(), + }; + + @Run(test = "test106") + public void test106_verifier(RunInfo info) { + int count = info.isWarmUp() ? 1 : 20; + for (int i = 0; i < count; i++) { + Intf intf = test106_intfs[i % test106_intfs.length]; + int result = test106(intf, 123, 456) + i; + Asserts.assertEQ(result, intf.func1(123, 456) + i); + } + } + + // C2->C1 invokeinterface -- C2 calls call Unverified Entry of MyImplVal2X.func1 (compiled by + // C1, with VVEP_RO==VVEP) + // This test is developed to validate JDK-8230325. + @Test(compLevel = CompLevel.WAIT_FOR_COMPILATION) + public int test107(Intf intf, int a, int b) { + return intf.func1(a, b); + } + + @ForceCompile + public void test107_verifier() { + Intf intf1 = new MyImplVal1X(); + Intf intf2 = new MyImplVal2X(); + + for (int i = 0; i < 1000; i++) { + test107(intf1, 123, 456); + } + for (int i = 0; i < 500_000; i++) { + // Run enough loops so that test107 will be compiled by C2. + if (i % 30 == 0) { + // This will indirectly call MyImplVal2X.func1, but the call frequency is low, so + // test107 will be compiled by C2, but MyImplVal2X.func1 will compiled by C1 only. + int result = test107(intf2, 123, 456) + i; + Asserts.assertEQ(result, intf2.func1(123, 456) + i); + } else { + // Call test107 with a mix of intf1 and intf2, so C2 will use a virtual call (not an optimized call) + // for the invokeinterface bytecode in test107. + test107(intf1, 123, 456); + } + } + } + + @Run(test = "test107") + @Warmup(0) + public void run_test107_verifier() { + test107_verifier(); + } + + // Same as test107, except we call MyImplVal2X.func2 (compiled by C1, VVEP_RO != VVEP) + @Test(compLevel = CompLevel.WAIT_FOR_COMPILATION) + public int test108(Intf intf, int a, int b) { + return intf.func2(a, b, pointField); + } + + @ForceCompile + public void test108_verifier() { + Intf intf1 = new MyImplVal1X(); + Intf intf2 = new MyImplVal2X(); + + for (int i = 0; i < 1000; i++) { + test108(intf1, 123, 456); + } + for (int i = 0; i < 500_000; i++) { + // Run enough loops so that test108 will be compiled by C2. + if (i % 30 == 0) { + // This will indirectly call MyImplVal2X.func2, but the call frequency is low, so + // test108 will be compiled by C2, but MyImplVal2X.func2 will compiled by C1 only. + int result = test108(intf2, 123, 456) + i; + Asserts.assertEQ(result, intf2.func2(123, 456, pointField) + i); + } else { + // Call test108 with a mix of intf1 and intf2, so C2 will use a virtual call (not an optimized call) + // for the invokeinterface bytecode in test108. + test108(intf1, 123, 456); + } + } + } + + @Run(test = "test108") + @Warmup(0) + public void run_test108_verifier() { + test108_verifier(); + } + + // Same as test107, except we call MyImplPojo3.func2 (compiled by C1, VVEP_RO == VEP) + @Test(compLevel = CompLevel.WAIT_FOR_COMPILATION) + public int test109(Intf intf, int a, int b) { + return intf.func2(a, b, pointField); + } + + @ForceCompile + public void test109_verifier() { + Intf intf1 = new MyImplPojo0(); + Intf intf2 = new MyImplPojo3(); + + for (int i = 0; i < 1000; i++) { + test109(intf1, 123, 456); + } + for (int i = 0; i < 500_000; i++) { + // Run enough loops so that test109 will be compiled by C2. + if (i % 30 == 0) { + // This will indirectly call MyImplPojo3.func2, but the call frequency is low, so + // test109 will be compiled by C2, but MyImplPojo3.func2 will compiled by C1 only. + int result = test109(intf2, 123, 456) + i; + Asserts.assertEQ(result, intf2.func2(123, 456, pointField) + i); + } else { + // Call test109 with a mix of intf1 and intf2, so C2 will use a virtual call (not an optimized call) + // for the invokeinterface bytecode in test109. + test109(intf1, 123, 456); + } + } + } + + @Run(test = "test109") + @Warmup(0) + public void run_test109_verifier() { + test109_verifier(); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestCastMismatch.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestCastMismatch.java new file mode 100644 index 00000000000..ca6a914282e --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestCastMismatch.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2023, 2024, Arm Limited. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8309650 + * @summary Fix mismatch inline type issue during method calls + * @library /test/lib + * @enablePreview + * @run main/othervm -XX:-TieredCompilation -Xcomp + * compiler.valhalla.inlinetypes.TestCastMismatch + */ + +package compiler.valhalla.inlinetypes; + +import java.util.Random; +import jdk.test.lib.Utils; + +public class TestCastMismatch { + private static int LOOP_COUNT = 50000; + + private static final Random RD = Utils.getRandomInstance(); + + public static MultiValues add(MultiValues v1, MultiValues v2) { + return v1.factory(v1.value1() + v2.value1(), v1.value2() + v2.value2()); + } + + public static void main(String[] args) { + Point p1 = new Point(RD.nextInt(), RD.nextInt()); + Point p2 = new Point(RD.nextInt(), RD.nextInt()); + for (int i = 0; i < LOOP_COUNT; i++) { + p1 = (Point) add(p1, p2); + } + + System.out.println("PASS"); + } + + static abstract value class MultiValues { + public abstract int value1(); + public abstract int value2(); + public abstract MultiValues factory(int value1, int value2); + } + + static value class Point extends MultiValues { + private int x; + private int y; + + private Point(int x, int y) { + this.x = x; + this.y = y; + } + + @Override + public int value1() { + return x; + } + + @Override + public int value2() { + return y; + } + + @Override + public Point factory(int value1, int value2) { + return new Point(value1, value2); + } + } +} + diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestDeadAllocationRemoval.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestDeadAllocationRemoval.java new file mode 100644 index 00000000000..900953e3a9a --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestDeadAllocationRemoval.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8230397 + * @summary Test removal of an already dead AllocateNode with not-yet removed proj outputs. + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm -Xbatch TestDeadAllocationRemoval + */ + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +public class TestDeadAllocationRemoval { + + public static void main(String[] args) { + Test test = new Test(); + for (int i = 0; i < 10; ++i) { + test.test(); + } + } +} + +@LooselyConsistentValue +value class MyValue { + public static long instanceCount = 0; + public float fFld = 0; + public boolean bFld = true; + public float fFld1 = 0; +} + +class Test { + public static final int N = 400; + + public static long instanceCount=2149450457L; + public static double dFld=63.1805; + public static boolean bFld1=false; + public static int iFld=-4; + public static double dArrFld[]=new double[N]; + public static int iArrFld[]=new int[N]; + @Strict + @NullRestricted + public static MyValue OFld=new MyValue(); + + public static long vMeth_check_sum = 0; + public static long lMeth_check_sum = 0; + + public void vMeth(int i) { + for (double d = 8; d < 307; d++) { + for (int i3 = 1; i3 < 6; i3 += 2) { + i <<= -23877; + } + } + } + + public void test() { + int i21=-35918, i22=11, i23=31413, i24=-7, i25=0, i26=70; + double d3=0.122541; + + vMeth(Test.iFld); + for (i21 = 20; i21 < 396; ++i21) { + d3 = 1; + while (++d3 < 67) { + byte by=38; + Test.dArrFld[(int)(d3)] = -7; + switch ((i21 % 9) + 1) { + case 1: + for (i23 = 1; i23 < 1; i23 += 3) { + Test.instanceCount = i22; + Test.iFld -= (int)Test.OFld.fFld1; + Test.instanceCount >>= MyValue.instanceCount; + i22 = (int)Test.OFld.fFld1; + Test.bFld1 = false; + Test.iArrFld[(int)(d3 - 1)] &= i23; + i22 += (i23 + i24); + i22 -= (int)d3; + Test.iFld |= (int)MyValue.instanceCount; + } + Test.iFld -= (int)Test.instanceCount; + break; + case 2: + for (i25 = 1; i25 < 1; i25++) { + i26 += i22; + i26 += i25; + Test.iArrFld[i25 + 1] += (int)MyValue.instanceCount; + i22 += (i25 - Test.instanceCount); + i26 += (i25 + i21); + } + Test.instanceCount -= 2; + Test.dFld = i22; + Test.iFld += (int)(((d3 * by) + by) - i24); + break; + case 3: + i24 = (int)1.84829; + Test.OFld = new MyValue(); + break; + case 4: + Test.OFld = new MyValue(); + MyValue.instanceCount += (long)d3; + break; + case 5: + MyValue.instanceCount += (long)(d3 * d3); + break; + case 6: + Test.dFld -= i25; + case 7: + try { + i24 = (78 / Test.iFld); + Test.iFld = (-5836 / Test.iArrFld[(int)(d3 + 1)]); + i24 = (i23 / -205); + } catch (ArithmeticException a_e) {} + break; + case 8: + if (Test.bFld1) continue; + case 9: + default: + try { + i26 = (i24 / -929688879); + i24 = (Test.iArrFld[(int)(d3)] % -1067487586); + Test.iArrFld[(int)(d3)] = (-208 % i24); + } catch (ArithmeticException a_e) {} + } + } + } + + System.out.println("i21 i22 d3 = " + i21 + "," + i22 + "," + Double.doubleToLongBits(d3)); + System.out.println("i23 i24 Test.OFld.fFld1 = " + i23 + "," + i24 + "," + Float.floatToIntBits(Test.OFld.fFld1)); + System.out.println("MyValue = " + MyValue.instanceCount); + System.out.println("Test.instanceCount Test.dFld Test.bFld1 = " + Test.instanceCount + "," + Double.doubleToLongBits(Test.dFld) + "," + (Test.bFld1 ? 1 : 0)); + System.out.println("MyValue = " + MyValue.instanceCount); + System.out.println("lMeth_check_sum: " + lMeth_check_sum); + System.out.println("vMeth_check_sum: " + vMeth_check_sum); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestDeoptimizationWhenBuffering.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestDeoptimizationWhenBuffering.java new file mode 100644 index 00000000000..c962b983385 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestDeoptimizationWhenBuffering.java @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import java.lang.invoke.*; +import java.lang.reflect.Method; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import jdk.test.lib.Asserts; + +import jdk.test.whitebox.WhiteBox; + +/** + * @test TestDeoptimizationWhenBuffering + * @summary Test correct execution after deoptimizing from inline type specific runtime calls. + * @library /testlibrary /test/lib /compiler/whitebox / + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @build org.openjdk.asmtools.* org.openjdk.asmtools.jasm.* + * @build jdk.test.whitebox.WhiteBox + * @enablePreview + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:+DeoptimizeALot -XX:CompileCommand=dontinline,compiler.valhalla.inlinetypes.*::test* + * compiler.valhalla.inlinetypes.TestDeoptimizationWhenBuffering C1 + * @run main/othervm -Xbootclasspath/a:. -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:+DeoptimizeALot -XX:-UseTLAB -Xbatch + * compiler.valhalla.inlinetypes.TestDeoptimizationWhenBuffering + * @run main/othervm -Xbootclasspath/a:. -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:+DeoptimizeALot -XX:-UseTLAB -Xbatch -XX:-MonomorphicArrayCheck -XX:-AlwaysIncrementalInline + * -XX:-InlineTypePassFieldsAsArgs -XX:-InlineTypeReturnedAsFields -XX:+UseArrayFlattening + * -XX:CompileCommand=dontinline,compiler.valhalla.inlinetypes.*::test* + * compiler.valhalla.inlinetypes.TestDeoptimizationWhenBuffering + * @run main/othervm -Xbootclasspath/a:. -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:+DeoptimizeALot -XX:-UseTLAB -Xbatch -XX:-MonomorphicArrayCheck -XX:+AlwaysIncrementalInline + * -XX:-InlineTypePassFieldsAsArgs -XX:-InlineTypeReturnedAsFields -XX:+UseArrayFlattening + * -XX:CompileCommand=dontinline,compiler.valhalla.inlinetypes.*::test* + * compiler.valhalla.inlinetypes.TestDeoptimizationWhenBuffering + * @run main/othervm -Xbootclasspath/a:. -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:+DeoptimizeALot -XX:-UseTLAB -Xbatch -XX:-MonomorphicArrayCheck -XX:-AlwaysIncrementalInline + * -XX:+InlineTypePassFieldsAsArgs -XX:+InlineTypeReturnedAsFields -XX:+UseArrayFlattening + * -XX:CompileCommand=dontinline,compiler.valhalla.inlinetypes.*::test* + * compiler.valhalla.inlinetypes.TestDeoptimizationWhenBuffering + * @run main/othervm -Xbootclasspath/a:. -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:+DeoptimizeALot -XX:-UseTLAB -Xbatch -XX:-MonomorphicArrayCheck -XX:+AlwaysIncrementalInline + * -XX:+InlineTypePassFieldsAsArgs -XX:+InlineTypeReturnedAsFields -XX:+UseArrayFlattening + * -XX:CompileCommand=dontinline,compiler.valhalla.inlinetypes.*::test* + * compiler.valhalla.inlinetypes.TestDeoptimizationWhenBuffering + * @run main/othervm -Xbootclasspath/a:. -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:+DeoptimizeALot -XX:-UseTLAB -Xbatch -XX:-MonomorphicArrayCheck -XX:-AlwaysIncrementalInline + * -XX:+InlineTypePassFieldsAsArgs -XX:+InlineTypeReturnedAsFields -XX:+UseArrayFlattening -XX:-UseFieldFlattening + * -XX:CompileCommand=dontinline,compiler.valhalla.inlinetypes.*::test* + * compiler.valhalla.inlinetypes.TestDeoptimizationWhenBuffering + * @run main/othervm -Xbootclasspath/a:. -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:+DeoptimizeALot -XX:-UseTLAB -Xbatch -XX:-MonomorphicArrayCheck -XX:+AlwaysIncrementalInline + * -XX:+InlineTypePassFieldsAsArgs -XX:+InlineTypeReturnedAsFields -XX:+UseArrayFlattening -XX:-UseFieldFlattening + * -XX:CompileCommand=dontinline,compiler.valhalla.inlinetypes.*::test* + * compiler.valhalla.inlinetypes.TestDeoptimizationWhenBuffering + */ + +public class TestDeoptimizationWhenBuffering { + static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + static final int COMP_LEVEL_FULL_OPTIMIZATION = 4; // C2 or JVMCI + + @LooselyConsistentValue + static value class MyValue1 { + static int cnt = 0; + int x; + @Strict + @NullRestricted + MyValue2 vtField1; + MyValue2 vtField2; + + public MyValue1() { + cnt++; + x = cnt; + vtField1 = new MyValue2(); + vtField2 = new MyValue2(); + } + + public MyValue1(int x, MyValue2 vtField1, MyValue2 vtField2) { + this.x = x; + this.vtField1 = vtField1; + this.vtField2 = vtField2; + } + + public int hash() { + return x + vtField1.x + vtField2.x; + } + + public MyValue1 testWithField(int x) { + return new MyValue1(x, vtField1, vtField2); + } + + public static MyValue1 makeDefault() { + return new MyValue1(0, MyValue2.makeDefault(), null); + } + + public static final MyValue1 DEFAULT = new MyValue1(0, new MyValue2(0), new MyValue2(0)); + } + + @LooselyConsistentValue + static value class MyValue2 { + static int cnt = 0; + int x; + + public MyValue2() { + cnt++; + x = cnt; + } + + public MyValue2(int x) { + this.x = x; + } + + public static MyValue2 makeDefault() { + return new MyValue2(0); + } + } + + static { + try { + Class clazz = TestDeoptimizationWhenBuffering.class; + MethodHandles.Lookup lookup = MethodHandles.lookup(); + + MethodType mt = MethodType.methodType(MyValue1.class); + test9_mh = lookup.findStatic(clazz, "test9Callee", mt); + test10_mh = lookup.findStatic(clazz, "test10Callee", mt); + } catch (NoSuchMethodException | IllegalAccessException e) { + e.printStackTrace(); + throw new RuntimeException("Method handle lookup failed"); + } + } + + MyValue1 test1() { + return new MyValue1(); + } + + @Strict + @NullRestricted + static MyValue1 vtField1 = MyValue1.DEFAULT; + + MyValue1 test2() { + vtField1 = new MyValue1(); + return vtField1; + } + + public int test3Callee(MyValue1 vt) { + return vt.hash(); + } + + int test3() { + MyValue1 vt = new MyValue1(); + return test3Callee(vt); + } + + static MyValue1[] vtArray = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + + MyValue1 test4() { + vtArray[0] = new MyValue1(); + return vtArray[0]; + } + + Object test5(Object[] array) { + array[0] = new MyValue1(); + return array[0]; + } + + boolean test6(Object obj) { + MyValue1 vt = new MyValue1(); + return vt == obj; + } + + Object test7(Object[] obj) { + return obj[0]; + } + + MyValue1 test8(MyValue1[] obj) { + return obj[0]; + } + + static final MethodHandle test9_mh; + + public static MyValue1 test9Callee() { + return new MyValue1(); + } + + MyValue1 test9() throws Throwable { + return (MyValue1)test9_mh.invokeExact(); + } + + static final MethodHandle test10_mh; + @Strict + @NullRestricted + static final MyValue1 test10Field = new MyValue1(); + static int test10Counter = 0; + + public static MyValue1 test10Callee() { + test10Counter++; + return test10Field; + } + + Object test10() throws Throwable { + return test10_mh.invoke(); + } + + MyValue1 test11(MyValue1 vt) { + return vt.testWithField(42); + } + + MyValue1 vtField2; + + MyValue1 test12() { + vtField2 = new MyValue1(); + return vtField2; + } + + public static void main(String[] args) throws Throwable { + if (args.length > 0) { + // Compile callees with C1 only, to exercise deoptimization while buffering at method entry + Asserts.assertEQ(args[0], "C1", "unsupported mode"); + Method m = MyValue1.class.getMethod("testWithField", int.class); + WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_FULL_OPTIMIZATION, false); + m = TestDeoptimizationWhenBuffering.class.getMethod("test3Callee", MyValue1.class); + WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_FULL_OPTIMIZATION, false); + m = TestDeoptimizationWhenBuffering.class.getMethod("test9Callee"); + WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_FULL_OPTIMIZATION, false); + m = TestDeoptimizationWhenBuffering.class.getMethod("test10Callee"); + WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_FULL_OPTIMIZATION, false); + } + + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 3, MyValue1.DEFAULT); + va[0] = new MyValue1(); + Object[] oa = new Object[3]; + oa[0] = va[0]; + TestDeoptimizationWhenBuffering t = new TestDeoptimizationWhenBuffering(); + for (int i = 0; i < 100_000; ++i) { + // Check counters to make sure that we don't accidentially reexecute calls when deoptimizing + int expected = MyValue1.cnt + MyValue2.cnt + MyValue2.cnt; + Asserts.assertEQ(t.test1().hash(), expected + 4); + vtField1 = MyValue1.makeDefault(); + Asserts.assertEQ(t.test2().hash(), expected + 9); + Asserts.assertEQ(vtField1.hash(), expected + 9); + Asserts.assertEQ(t.test3(), expected + 14); + Asserts.assertEQ(t.test4().hash(), expected + 19); + Asserts.assertEQ(((MyValue1)t.test5(vtArray)).hash(), expected + 24); + Asserts.assertEQ(t.test6(vtField1), false); + Asserts.assertEQ(t.test7(((i % 2) == 0) ? va : oa), va[0]); + Asserts.assertEQ(t.test8(va), va[0]); + Asserts.assertEQ(t.test8(va), va[0]); + Asserts.assertEQ(t.test9().hash(), expected + 34); + int count = test10Counter; + Asserts.assertEQ(((MyValue1)t.test10()).hash(), test10Field.hash()); + Asserts.assertEQ(t.test10Counter, count + 1); + Asserts.assertEQ(t.test11(va[0]).hash(), va[0].testWithField(42).hash()); + t.vtField2 = MyValue1.makeDefault(); + Asserts.assertEQ(t.test12().hash(), expected + 39); + Asserts.assertEQ(t.vtField2.hash(), expected + 39); + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestFieldNullMarkers.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestFieldNullMarkers.java new file mode 100644 index 00000000000..4ca10f1ba97 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestFieldNullMarkers.java @@ -0,0 +1,1221 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import jdk.test.lib.Asserts; + +/* + * @test + * @key randomness + * @summary Test support for null markers in flat fields. + * @library /test/lib / + * @requires (os.simpleArch == "x64" | os.simpleArch == "aarch64") + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm/timeout=300 compiler.valhalla.inlinetypes.TestFieldNullMarkers + * @run main/othervm/timeout=300 -Xbatch -XX:-UseNullableValueFlattening -XX:-UseAtomicValueFlattening -XX:-UseNonAtomicValueFlattening + * compiler.valhalla.inlinetypes.TestFieldNullMarkers + * @run main/othervm/timeout=300 -Xbatch -XX:-UseNullableValueFlattening -XX:-UseAtomicValueFlattening -XX:+UseNonAtomicValueFlattening + * compiler.valhalla.inlinetypes.TestFieldNullMarkers + * @run main/othervm/timeout=300 -Xbatch -XX:-UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:-UseNonAtomicValueFlattening + * compiler.valhalla.inlinetypes.TestFieldNullMarkers + * @run main/othervm/timeout=300 -Xbatch -XX:-UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseNonAtomicValueFlattening + * compiler.valhalla.inlinetypes.TestFieldNullMarkers + * @run main/othervm/timeout=300 -Xbatch -XX:+UseNullableValueFlattening -XX:-UseAtomicValueFlattening -XX:-UseNonAtomicValueFlattening + * compiler.valhalla.inlinetypes.TestFieldNullMarkers + * @run main/othervm/timeout=300 -Xbatch -XX:+UseNullableValueFlattening -XX:-UseAtomicValueFlattening -XX:+UseNonAtomicValueFlattening + * compiler.valhalla.inlinetypes.TestFieldNullMarkers + * @run main/othervm/timeout=300 -Xbatch -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:-UseNonAtomicValueFlattening + * compiler.valhalla.inlinetypes.TestFieldNullMarkers + * @run main/othervm/timeout=300 -Xbatch -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseNonAtomicValueFlattening + * compiler.valhalla.inlinetypes.TestFieldNullMarkers + * + * @run main/othervm/timeout=300 -Xbatch -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseNonAtomicValueFlattening + * -XX:CompileCommand=dontinline,*::testHelper* + * compiler.valhalla.inlinetypes.TestFieldNullMarkers + * @run main/othervm/timeout=300 -Xbatch -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseNonAtomicValueFlattening + * -XX:+InlineTypeReturnedAsFields -XX:+InlineTypePassFieldsAsArgs + * compiler.valhalla.inlinetypes.TestFieldNullMarkers + * @run main/othervm/timeout=300 -Xbatch -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseNonAtomicValueFlattening + * -XX:-InlineTypeReturnedAsFields -XX:-InlineTypePassFieldsAsArgs + * compiler.valhalla.inlinetypes.TestFieldNullMarkers + * @run main/othervm/timeout=300 -Xbatch -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseNonAtomicValueFlattening + * -XX:+InlineTypeReturnedAsFields -XX:-InlineTypePassFieldsAsArgs + * compiler.valhalla.inlinetypes.TestFieldNullMarkers + * @run main/othervm/timeout=300 -Xbatch -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseNonAtomicValueFlattening + * -XX:-InlineTypeReturnedAsFields -XX:+InlineTypePassFieldsAsArgs + * compiler.valhalla.inlinetypes.TestFieldNullMarkers + */ + +public class TestFieldNullMarkers { + + // Value class with two nullable flat fields + @LooselyConsistentValue + static value class MyValue1 { + byte x; + MyValue2 val1; + MyValue2 val2; + + public MyValue1(byte x, MyValue2 val1, MyValue2 val2) { + this.x = x; + this.val1 = val1; + this.val2 = val2; + } + + public String toString() { + return "x = " + x + ", val1 = [" + val1 + "], val2 = [" + val2 + "]"; + } + } + + @LooselyConsistentValue + static abstract value class MyAbstract1 { + byte x; + + public MyAbstract1(byte x) { + this.x = x; + } + } + + // Empty value class inheriting single field from abstract super class + @LooselyConsistentValue + static value class MyValue2 extends MyAbstract1 { + public MyValue2(byte x) { + super(x); + } + + public String toString() { + return "x = " + x; + } + } + + // Value class with a hole in the payload that will be used for the null marker + @LooselyConsistentValue + static value class MyValue3 { + byte x; + // Hole that will be used by the null marker + int i; + + public MyValue3(byte x) { + this.x = x; + this.i = x; + } + + public String toString() { + return "x = " + x + ", i = " + i; + } + } + + // Value class with two nullable flat fields that have their null markers *not* at the end of the payload + @LooselyConsistentValue + static value class MyValue4 { + MyValue3 val1; + MyValue3 val2; + + public MyValue4(MyValue3 val1, MyValue3 val2) { + this.val1 = val1; + this.val2 = val2; + } + + public String toString() { + return "val1 = [" + val1 + "], val2 = [" + val2 + "]"; + } + } + + @LooselyConsistentValue + static value class MyValue5_3 { + byte x; + + public MyValue5_3(byte x) { + this.x = x; + } + } + + @LooselyConsistentValue + static value class MyValue5_2 { + byte x; + MyValue5_3 val; + + public MyValue5_2(byte x, MyValue5_3 val) { + this.x = x; + this.val = val; + } + } + + @LooselyConsistentValue + static value class MyValue5_1 { + byte x; + MyValue5_2 val; + + public MyValue5_1(byte x, MyValue5_2 val) { + this.x = x; + this.val = val; + } + } + + // Value class with deep nesting of nullable flat fields + @LooselyConsistentValue + static value class MyValue5 { + byte x; + MyValue5_1 val; + + public MyValue5(byte x, MyValue5_1 val) { + this.x = x; + this.val = val; + } + } + + @LooselyConsistentValue + static value class MyValueEmpty { + + } + + // Value class with flat field of empty value class + @LooselyConsistentValue + static value class MyValue6 { + MyValueEmpty val; + + public MyValue6(MyValueEmpty val) { + this.val = val; + } + } + + // Same as MyValue6 but one more level of nested flat fields + @LooselyConsistentValue + static value class MyValue7 { + MyValue6 val; + + public MyValue7(MyValue6 val) { + this.val = val; + } + } + + // Some more field types + + @LooselyConsistentValue + static value class MyValue8 { + byte b; + + public MyValue8(byte b) { + this.b = b; + } + } + + @LooselyConsistentValue + static value class MyValue9 { + short s; + + public MyValue9(short s) { + this.s = s; + } + } + + @LooselyConsistentValue + static value class MyValue10 { + int i; + + public MyValue10(int i) { + this.i = i; + } + } + + @LooselyConsistentValue + static value class MyValue11 { + float f; + + public MyValue11(float f) { + this.f = f; + } + } + + @LooselyConsistentValue + static value class MyValue12 { + char c; + + public MyValue12(char c) { + this.c = c; + } + } + + @LooselyConsistentValue + static value class MyValue13 { + boolean b; + + public MyValue13(boolean b) { + this.b = b; + } + } + + // Test value class with nullable and null-free fields + @LooselyConsistentValue + static value class MyValue14 { + @Strict + @NullRestricted + MyValue8 nullfree; + MyValue8 nullable; + + public MyValue14(MyValue8 nullfree, MyValue8 nullable) { + this.nullfree = nullfree; + this.nullable = nullable; + } + + public static final MyValue14 DEFAULT = new MyValue14(new MyValue8((byte)0), null); + } + + static class MyClass { + int x; + + public MyClass(int x) { + this.x = x; + } + } + + // Value class with oop field + @LooselyConsistentValue + static value class MyValue15 { + MyClass obj; + + public MyValue15(MyClass obj) { + this.obj = obj; + } + } + + // Value class with two oop fields + @LooselyConsistentValue + static value class MyValue16 { + MyClass obj1; + MyClass obj2; + + public MyValue16(MyClass obj1, MyClass obj2) { + this.obj1 = obj1; + this.obj2 = obj2; + } + } + + // Value class with oop field and primitive fields + @LooselyConsistentValue + static value class MyValue17 { + byte b1; + MyClass obj; + byte b2; + + public MyValue17(MyClass obj, byte b1, byte b2) { + this.obj = obj; + this.b1 = b1; + this.b2 = b2; + } + } + + MyValue1 field1; // Not flat + MyValue4 field2; // Not flat + MyValue5 field3; // Flat + MyValue6 field4; // Flat + MyValue7 field5; // Flat + MyValue8 field6; // Flat + MyValue9 field7; // Flat + MyValue10 field8; // Flat + MyValue11 field9; // Flat + MyValue12 field10; // Flat + MyValue13 field11; // Flat + + @Strict + @NullRestricted + volatile MyValue8 field12 = new MyValue8((byte)0); + + @Strict + @NullRestricted + MyValue14 field13 = MyValue14.DEFAULT; // Null-free, flat + volatile MyValue14 field14; // Nullable, atomic, flat + MyValue14 field15; // Nullable, (atomic), flat + @Strict + @NullRestricted + volatile MyValue14 field16 = MyValue14.DEFAULT; // Null-free, atomic, flat + + @Strict + @NullRestricted + volatile MyValue15 field17 = new MyValue15(null); + MyValue15 field18; + @Strict + @NullRestricted + volatile MyValue16 field19 = new MyValue16(null, null); + @Strict + @NullRestricted + volatile MyValue17 field20 = new MyValue17(null, (byte)0, (byte)0); + MyValue17 field21; + + // Combinations of strict fields + static class StrictFieldHolder { + @Strict + MyValue8 strictField1; + @Strict + final MyValue8 strictField2; + @Strict + @NullRestricted + MyValue8 strictField3; + @Strict + @NullRestricted + final MyValue8 strictField4; + @Strict + volatile MyValue8 strictField5; + @Strict + @NullRestricted + volatile MyValue8 strictField6; + + @Strict + TwoBytes strictField7; + @Strict + final TwoBytes strictField8; + @Strict + @NullRestricted + TwoBytes strictField9; + @Strict + @NullRestricted + final TwoBytes strictField10; + @Strict + volatile TwoBytes strictField11; + @Strict + @NullRestricted + volatile TwoBytes strictField12; + + public StrictFieldHolder(MyValue8 val8, MyValue8 val8NullFree, TwoBytes twoBytes, TwoBytes twoBytesNullFree) { + strictField1 = val8; + strictField2 = val8; + strictField3 = val8NullFree; + strictField4 = val8NullFree; + strictField5 = val8NullFree; + strictField6 = val8NullFree; + + strictField7 = twoBytes; + strictField8 = twoBytes; + strictField9 = twoBytesNullFree; + strictField10 = twoBytesNullFree; + strictField11 = twoBytesNullFree; + strictField12 = twoBytesNullFree; + } + } + + @Strict + @NullRestricted + MyValueEmpty emptyField1 = new MyValueEmpty(); + @Strict + @NullRestricted + volatile MyValueEmpty emptyField2 = new MyValueEmpty(); + MyValueEmpty emptyField3; + volatile MyValueEmpty emptyField4; + + static final MyValue1 VAL1 = new MyValue1((byte)42, new MyValue2((byte)43), null); + static final MyValue4 VAL4 = new MyValue4(new MyValue3((byte)42), null); + static final MyValue5 VAL5 = new MyValue5((byte)42, new MyValue5_1((byte)43, new MyValue5_2((byte)44, new MyValue5_3((byte)45)))); + static final MyValue6 VAL6 = new MyValue6(new MyValueEmpty()); + static final MyValue7 VAL7 = new MyValue7(new MyValue6(new MyValueEmpty())); + + // Using two bytes such that null-free fields will not be naturally atomic + @LooselyConsistentValue + static value class TwoBytes { + byte b1; + byte b2; + + public TwoBytes(byte b1, byte b2) { + this.b1 = b1; + this.b2 = b2; + } + + public static final TwoBytes DEFAULT = new TwoBytes((byte)0, (byte)0); + } + + static private final MyValue8 CANARY_VALUE = new MyValue8((byte)42); + + public static class Cage1 { + MyValue8 canary1 = CANARY_VALUE; + + @Strict + @NullRestricted + volatile TwoBytes field = TwoBytes.DEFAULT; + + MyValue8 canary2 = CANARY_VALUE; + + public void verify(TwoBytes val) { + Asserts.assertEQ(canary1, CANARY_VALUE); + Asserts.assertEQ(field, val); + Asserts.assertEQ(canary2, CANARY_VALUE); + } + } + + public static class Cage2 { + @Strict + @NullRestricted + MyValue8 canary1 = CANARY_VALUE; + + @Strict + @NullRestricted + volatile TwoBytes field = TwoBytes.DEFAULT; + + @Strict + @NullRestricted + MyValue8 canary2 = CANARY_VALUE; + + public void verify(TwoBytes val) { + Asserts.assertEQ(canary1, CANARY_VALUE); + Asserts.assertEQ(field, val); + Asserts.assertEQ(canary2, CANARY_VALUE); + } + } + + public static class Cage3 { + @Strict + @NullRestricted + MyValue8 canary1 = CANARY_VALUE; + + volatile TwoBytes field; + + @Strict + @NullRestricted + MyValue8 canary2 = CANARY_VALUE; + + public void verify(TwoBytes val) { + Asserts.assertEQ(canary1, CANARY_VALUE); + Asserts.assertEQ(field, val); + Asserts.assertEQ(canary2, CANARY_VALUE); + } + } + + public static class Cage4 { + MyValue8 canary1 = CANARY_VALUE; + + volatile TwoBytes field; + + MyValue8 canary2 = CANARY_VALUE; + + public void verify(TwoBytes val) { + Asserts.assertEQ(canary1, CANARY_VALUE); + Asserts.assertEQ(field, val); + Asserts.assertEQ(canary2, CANARY_VALUE); + } + } + + static final Cage1 canaryCage1 = new Cage1(); + static final Cage2 canaryCage2 = new Cage2(); + static final Cage3 canaryCage3 = new Cage3(); + static final Cage4 canaryCage4 = new Cage4(); + + // Check that the canary values are not accidentally overwritten + public void testOutOfBoundsAccess(int i) { + TwoBytes val = new TwoBytes((byte)i, (byte)(i+1)); + canaryCage1.field = val; + canaryCage1.verify(val); + + canaryCage2.field = val; + canaryCage2.verify(val); + + canaryCage3.field = val; + canaryCage3.verify(val); + + canaryCage3.field = null; + canaryCage3.verify(null); + + canaryCage4.field = val; + canaryCage4.verify(val); + + canaryCage4.field = null; + canaryCage4.verify(null); + } + + // Test that the calling convention is keeping track of the null marker + public MyValue1 testHelper1(MyValue1 val) { + return val; + } + + public void testSet1(MyValue1 val) { + field1 = testHelper1(val); + } + + public MyValue1 testGet1() { + return field1; + } + + public void testDeopt1(byte x, MyValue1 neverNull, MyValue1 alwaysNull, boolean deopt) { + MyValue2 val2 = new MyValue2(x); + MyValue1 val1 = new MyValue1(x, val2, val2); + if (deopt) { + Asserts.assertEQ(val1.x, x); + Asserts.assertEQ(val1.val1, val2); + Asserts.assertEQ(val1.val2, val2); + Asserts.assertEQ(neverNull.x, x); + Asserts.assertEQ(neverNull.val1, val2); + Asserts.assertEQ(neverNull.val2, val2); + Asserts.assertEQ(alwaysNull.x, x); + Asserts.assertEQ(alwaysNull.val1, null); + Asserts.assertEQ(alwaysNull.val2, null); + } + } + + public void testOSR() { + // Trigger OSR + for (int i = 0; i < 100_000; ++i) { + field1 = null; + Asserts.assertEQ(field1, null); + MyValue2 val2 = new MyValue2((byte)i); + MyValue1 val = new MyValue1((byte)i, val2, null); + field1 = val; + Asserts.assertEQ(field1.x, (byte)i); + Asserts.assertEQ(field1.val1, val2); + Asserts.assertEQ(field1.val2, null); + } + } + + public boolean testACmp(MyValue2 val2) { + return field1.val1 == val2; + } + + // Test that the calling convention is keeping track of the null marker + public MyValue4 testHelper2(MyValue4 val) { + return val; + } + + public void testSet2(MyValue4 val) { + field2 = testHelper2(val); + } + + public MyValue4 testGet2() { + return field2; + } + + public void testDeopt2(byte x, MyValue4 neverNull, MyValue4 alwaysNull, boolean deopt) { + MyValue3 val3 = new MyValue3(x); + MyValue4 val4 = new MyValue4(val3, null); + if (deopt) { + Asserts.assertEQ(val4.val1, val3); + Asserts.assertEQ(val4.val2, null); + Asserts.assertEQ(neverNull.val1, val3); + Asserts.assertEQ(neverNull.val2, val3); + Asserts.assertEQ(alwaysNull.val1, null); + Asserts.assertEQ(alwaysNull.val2, null); + } + } + + // Test that the calling convention is keeping track of the null marker + public MyValue5 testHelper3(MyValue5 val) { + return val; + } + + public void testSet3(MyValue5 val) { + field3 = testHelper3(val); + } + + public MyValue5 testGet3() { + return field3; + } + + public void testDeopt3(byte x, MyValue5 val6, MyValue5 val7, MyValue5 val8, MyValue5 val9, boolean deopt) { + MyValue5 val1 = new MyValue5(x, new MyValue5_1(x, new MyValue5_2(x, new MyValue5_3(x)))); + MyValue5 val2 = new MyValue5(x, new MyValue5_1(x, new MyValue5_2(x, null))); + MyValue5 val3 = new MyValue5(x, new MyValue5_1(x, null)); + MyValue5 val4 = new MyValue5(x, null); + MyValue5 val5 = null; + if (deopt) { + Asserts.assertEQ(val1.x, x); + Asserts.assertEQ(val1.val.x, x); + Asserts.assertEQ(val1.val.val.x, x); + Asserts.assertEQ(val1.val.val.val.x, x); + Asserts.assertEQ(val2.x, x); + Asserts.assertEQ(val2.val.x, x); + Asserts.assertEQ(val2.val.val.x, x); + Asserts.assertEQ(val2.val.val.val, null); + Asserts.assertEQ(val3.x, x); + Asserts.assertEQ(val3.val.x, x); + Asserts.assertEQ(val3.val.val, null); + Asserts.assertEQ(val4.x, x); + Asserts.assertEQ(val4.val, null); + Asserts.assertEQ(val5, null); + + Asserts.assertEQ(val6.x, x); + Asserts.assertEQ(val6.val.x, x); + Asserts.assertEQ(val6.val.val.x, x); + Asserts.assertEQ(val6.val.val.val.x, x); + Asserts.assertEQ(val7.x, x); + Asserts.assertEQ(val7.val.x, x); + Asserts.assertEQ(val7.val.val.x, x); + Asserts.assertEQ(val7.val.val.val, null); + Asserts.assertEQ(val8.x, x); + Asserts.assertEQ(val8.val.x, x); + Asserts.assertEQ(val8.val.val, null); + Asserts.assertEQ(val9.x, x); + Asserts.assertEQ(val9.val, null); + } + } + + // Test that the calling convention is keeping track of the null marker + public MyValue6 testHelper4(MyValue6 val) { + return val; + } + + public void testSet4(MyValue6 val) { + field4 = testHelper4(val); + } + + public MyValue6 testGet4() { + return field4; + } + + public void testDeopt4(MyValue6 val4, MyValue6 val5, MyValue6 val6, boolean deopt) { + MyValue6 val1 = new MyValue6(new MyValueEmpty()); + MyValue6 val2 = new MyValue6(null); + MyValue6 val3 = null; + if (deopt) { + Asserts.assertEQ(val1.val, new MyValueEmpty()); + Asserts.assertEQ(val2.val, null); + Asserts.assertEQ(val3, null); + + Asserts.assertEQ(val4.val, new MyValueEmpty()); + Asserts.assertEQ(val5.val, null); + Asserts.assertEQ(val6, null); + } + } + + // Test that the calling convention is keeping track of the null marker + public MyValue7 testHelper5(MyValue7 val) { + return val; + } + + public void testSet5(MyValue7 val) { + field5 = testHelper5(val); + } + + public MyValue7 testGet5() { + return field5; + } + + public void testDeopt5(MyValue7 val5, MyValue7 val6, MyValue7 val7, MyValue7 val8, boolean deopt) { + MyValue7 val1 = new MyValue7(new MyValue6(new MyValueEmpty())); + MyValue7 val2 = new MyValue7(new MyValue6(null)); + MyValue7 val3 = new MyValue7(null); + MyValue7 val4 = null; + if (deopt) { + Asserts.assertEQ(val1.val, new MyValue6(new MyValueEmpty())); + Asserts.assertEQ(val2.val, new MyValue6(null)); + Asserts.assertEQ(val3.val, null); + Asserts.assertEQ(val4, null); + + Asserts.assertEQ(val5.val, new MyValue6(new MyValueEmpty())); + Asserts.assertEQ(val6.val, new MyValue6(null)); + Asserts.assertEQ(val7.val, null); + Asserts.assertEQ(val8, null); + } + } + + // Make sure that flat field accesses contain a (implicit) null check + public static void testNPE1() { + TestFieldNullMarkers t = null; + try { + MyValue8 v = t.field6; + throw new RuntimeException("No NPE thrown!"); + } catch (NullPointerException e) { + // Expected + } + } + + public static void testNPE2() { + TestFieldNullMarkers t = null; + try { + t.field6 = null; + throw new RuntimeException("No NPE thrown!"); + } catch (NullPointerException e) { + // Expected + } + } + + public void checkFields(int i) { + Asserts.assertEQ(field6.b, (byte)i); + Asserts.assertEQ(field7.s, (short)i); + Asserts.assertEQ(field8.i, i); + Asserts.assertEQ(field9.f, (float)i); + Asserts.assertEQ(field10.c, (char)i); + Asserts.assertEQ(field11.b, (i % 2) == 0); + } + + // Test that writing and reading a (signed) byte stays in bounds + public void testBounds(int i) { + MyValue8 val = new MyValue8((byte)i); + field6 = val; + int b = field6.b; + if (b < -128 || b > 127) { + throw new RuntimeException("Byte value out of bounds: " + b); + } + } + + static void produceGarbage() { + for (int i = 0; i < 100; ++i) { + Object[] arrays = new Object[1024]; + for (int j = 0; j < arrays.length; j++) { + arrays[j] = new int[1024]; + } + } + System.gc(); + } + + // Test that barriers are emitted when writing flat, atomic fields with oops + public void testWriteOopFields1(MyValue15 val) { + field17 = val; + field18 = val; + } + + public void testWriteOopFields2(MyValue16 val) { + field19 = val; + } + + public void testWriteOopFields3(MyValue17 val) { + field20 = val; + field21 = val; + } + + public static class MyHolderClass9 { + @Strict + @NullRestricted + TwoBytes field1 = TwoBytes.DEFAULT; + + TwoBytes field2; + + @Strict + @NullRestricted + volatile TwoBytes field3 = TwoBytes.DEFAULT; + + volatile TwoBytes field4; + } + + static final MyHolderClass9 constantHolder = new MyHolderClass9(); + + // Test loading a flat field from a constant container (should not be constant folded because fields are immutable) + public void testLoadingFromConstantHolder(int i) { + TwoBytes val = new TwoBytes((byte)i, (byte)(i + 1)); + constantHolder.field1 = val; + Asserts.assertEQ(constantHolder.field1, val); + + constantHolder.field2 = val; + Asserts.assertEQ(constantHolder.field2, val); + + constantHolder.field2 = null; + Asserts.assertEQ(constantHolder.field2, null); + + constantHolder.field3 = val; + Asserts.assertEQ(constantHolder.field3, val); + + constantHolder.field4 = val; + Asserts.assertEQ(constantHolder.field4, val); + + constantHolder.field4 = null; + Asserts.assertEQ(constantHolder.field4, null); + } + + public void testStrictFields(StrictFieldHolder holder, MyValue8 val8, MyValue8 val8NullFree, TwoBytes twoBytes, TwoBytes twoBytesNullFree) { + Asserts.assertEQ(holder.strictField1, val8); + Asserts.assertEQ(holder.strictField2, val8); + Asserts.assertEQ(holder.strictField3, val8NullFree); + Asserts.assertEQ(holder.strictField4, val8NullFree); + Asserts.assertEQ(holder.strictField5, val8NullFree); + Asserts.assertEQ(holder.strictField6, val8NullFree); + + Asserts.assertEQ(holder.strictField7, twoBytes); + Asserts.assertEQ(holder.strictField8, twoBytes); + Asserts.assertEQ(holder.strictField9, twoBytesNullFree); + Asserts.assertEQ(holder.strictField10, twoBytesNullFree); + Asserts.assertEQ(holder.strictField11, twoBytesNullFree); + Asserts.assertEQ(holder.strictField12, twoBytesNullFree); + } + + public static void main(String[] args) { + TestFieldNullMarkers t = new TestFieldNullMarkers(); + t.testOSR(); + + final int LIMIT = 50_000; + for (int i = -50_000; i < LIMIT; ++i) { + t.field1 = null; + Asserts.assertEQ(t.testGet1(), null); + + boolean useNull = (i % 2) == 0; + MyValue2 val2 = useNull ? null : new MyValue2((byte)i); + MyValue1 val = new MyValue1((byte)i, val2, val2); + t.field1 = val; + Asserts.assertEQ(t.testGet1().x, val.x); + Asserts.assertEQ(t.testGet1().val1, val2); + Asserts.assertEQ(t.testGet1().val2, val2); + + Asserts.assertTrue(t.testACmp(val2)); + + t.testSet1(null); + Asserts.assertEQ(t.field1, null); + + t.testSet1(val); + Asserts.assertEQ(t.field1.x, val.x); + Asserts.assertEQ(t.field1.val1, val2); + Asserts.assertEQ(t.field1.val2, val2); + + t.testDeopt1((byte)i, null, null, false); + + t.field2 = null; + Asserts.assertEQ(t.testGet2(), null); + + MyValue3 val3 = useNull ? null : new MyValue3((byte)i); + MyValue4 val4 = new MyValue4(val3, val3); + t.field2 = val4; + Asserts.assertEQ(t.testGet2().val1, val3); + Asserts.assertEQ(t.testGet2().val2, val3); + + t.testSet2(null); + Asserts.assertEQ(t.testGet2(), null); + + t.testSet2(val4); + Asserts.assertEQ(t.testGet2().val1, val3); + Asserts.assertEQ(t.testGet2().val2, val3); + + t.testDeopt2((byte)i, null, null, false); + + t.field3 = null; + Asserts.assertEQ(t.testGet3(), null); + + boolean useNull_1 = (i % 4) == 0; + boolean useNull_2 = (i % 4) == 1; + boolean useNull_3 = (i % 4) == 2; + MyValue5_3 val5_3 = useNull_3 ? null : new MyValue5_3((byte)i); + MyValue5_2 val5_2 = useNull_2 ? null : new MyValue5_2((byte)i, val5_3); + MyValue5_1 val5_1 = useNull_1 ? null : new MyValue5_1((byte)i, val5_2); + MyValue5 val5 = new MyValue5((byte)i, val5_1); + t.field3 = val5; + Asserts.assertEQ(t.testGet3().x, val5.x); + if (useNull_1) { + Asserts.assertEQ(t.testGet3().val, null); + } else { + Asserts.assertEQ(t.testGet3().val.x, val5_1.x); + if (useNull_2) { + Asserts.assertEQ(t.testGet3().val.val, null); + } else { + Asserts.assertEQ(t.testGet3().val.val.x, val5_2.x); + if (useNull_3) { + Asserts.assertEQ(t.testGet3().val.val.val, null); + } else { + Asserts.assertEQ(t.testGet3().val.val.val.x, val5_3.x); + } + } + } + + t.testSet3(null); + Asserts.assertEQ(t.field3, null); + + t.testSet3(val5); + Asserts.assertEQ(t.testGet3().x, val5.x); + if (useNull_1) { + Asserts.assertEQ(t.testGet3().val, null); + } else { + Asserts.assertEQ(t.testGet3().val.x, val5_1.x); + if (useNull_2) { + Asserts.assertEQ(t.testGet3().val.val, null); + } else { + Asserts.assertEQ(t.testGet3().val.val.x, val5_2.x); + if (useNull_3) { + Asserts.assertEQ(t.testGet3().val.val.val, null); + } else { + Asserts.assertEQ(t.testGet3().val.val.val.x, val5_3.x); + } + } + } + t.testDeopt3((byte)i, null, null, null, null, false); + + t.field4 = null; + Asserts.assertEQ(t.testGet4(), null); + + MyValueEmpty empty = useNull ? null : new MyValueEmpty(); + MyValue6 val6 = new MyValue6(empty); + t.field4 = val6; + Asserts.assertEQ(t.testGet4().val, empty); + + t.testSet4(null); + Asserts.assertEQ(t.testGet4(), null); + + t.testSet4(val6); + Asserts.assertEQ(t.testGet4().val, empty); + + t.testDeopt4(null, null, null, false); + + t.field5 = null; + Asserts.assertEQ(t.testGet5(), null); + + empty = ((i % 3) == 0) ? null : new MyValueEmpty(); + val6 = ((i % 3) == 1) ? null : new MyValue6(empty); + MyValue7 val7 = new MyValue7(val6); + t.field5 = val7; + Asserts.assertEQ(t.testGet5().val, val6); + + t.testSet5(null); + Asserts.assertEQ(t.testGet5(), null); + + t.testSet5(val7); + Asserts.assertEQ(t.testGet5().val, val6); + + t.testDeopt5(null, null, null, null, false); + + // Check accesses with constant value + t.field1 = VAL1; + Asserts.assertEQ(t.field1.x, VAL1.x); + Asserts.assertEQ(t.field1.val1, VAL1.val1); + Asserts.assertEQ(t.field1.val2, VAL1.val2); + + t.field2 = VAL4; + Asserts.assertEQ(t.field2.val1, VAL4.val1); + Asserts.assertEQ(t.field2.val2, VAL4.val2); + + t.field3 = VAL5; + Asserts.assertEQ(t.field3.x, VAL5.x); + Asserts.assertEQ(t.field3.val.x, VAL5.val.x); + Asserts.assertEQ(t.field3.val.val.x, VAL5.val.val.x); + Asserts.assertEQ(t.field3.val.val.val.x, VAL5.val.val.val.x); + + t.field4 = VAL6; + Asserts.assertEQ(t.field4.val, VAL6.val); + + t.field5 = VAL7; + Asserts.assertEQ(t.field5.val, VAL7.val); + + // Some more values classes with different flavors of primitive fields + t.field6 = null; + Asserts.assertEQ(t.field6, null); + t.field6 = new MyValue8((byte)i); + Asserts.assertEQ(t.field6.b, (byte)i); + t.field7 = null; + Asserts.assertEQ(t.field7, null); + t.field7 = new MyValue9((short)i); + Asserts.assertEQ(t.field7.s, (short)i); + t.field8 = null; + Asserts.assertEQ(t.field8, null); + t.field8 = new MyValue10(i); + Asserts.assertEQ(t.field8.i, i); + t.field9 = null; + Asserts.assertEQ(t.field9, null); + t.field9 = new MyValue11((float)i); + Asserts.assertEQ(t.field9.f, (float)i); + t.field10 = null; + Asserts.assertEQ(t.field10, null); + t.field10 = new MyValue12((char)i); + Asserts.assertEQ(t.field10.c, (char)i); + t.field11 = null; + Asserts.assertEQ(t.field11, null); + t.field11 = new MyValue13((i % 2) == 0); + Asserts.assertEQ(t.field11.b, (i % 2) == 0); + + // Write the fields again and check that we don't overwrite other fields + t.checkFields(i); + t.field6 = new MyValue8((byte)i); + t.checkFields(i); + t.field7 = new MyValue9((short)i); + t.checkFields(i); + t.field8 = new MyValue10(i); + t.checkFields(i); + t.field9 = new MyValue11((float)i); + t.checkFields(i); + t.field10 = new MyValue12((char)i); + t.checkFields(i); + t.field11 = new MyValue13((i % 2) == 0); + t.checkFields(i); + + testNPE1(); + testNPE2(); + + t.testBounds(i); + + // Null-free, flat, atomic + MyValue8 val8 = new MyValue8((byte)i); + t.field12 = val8; + Asserts.assertEQ(t.field12.b, (byte)i); + + try { + t.field12 = null; + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException npe) { + // Expected + } + + // Null-free, flat with both nullable and null-free fields + t.field13 = new MyValue14(val8, val8); + Asserts.assertEQ(t.field13.nullfree, val8); + Asserts.assertEQ(t.field13.nullable, val8); + + t.field13 = new MyValue14(val8, null); + Asserts.assertEQ(t.field13.nullfree, val8); + Asserts.assertEQ(t.field13.nullable, null); + + try { + t.field13 = new MyValue14(null, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException npe) { + // Expected + } + try { + t.field13 = null; + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException npe) { + // Expected + } + + // Nullable, atomic, flat with both nullable and null-free fields + t.field14 = null; + Asserts.assertEQ(t.field14, null); + + t.field14 = new MyValue14(val8, val8); + Asserts.assertEQ(t.field14.nullfree, val8); + Asserts.assertEQ(t.field14.nullable, val8); + + t.field14 = new MyValue14(val8, null); + Asserts.assertEQ(t.field14.nullfree, val8); + Asserts.assertEQ(t.field14.nullable, null); + + try { + t.field14 = new MyValue14(null, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException npe) { + // Expected + } + + // Nullable, (atomic), flat with both nullable and null-free fields + t.field15 = null; + Asserts.assertEQ(t.field15, null); + + t.field15 = new MyValue14(val8, val8); + Asserts.assertEQ(t.field15.nullfree, val8); + Asserts.assertEQ(t.field15.nullable, val8); + + t.field15 = new MyValue14(val8, null); + Asserts.assertEQ(t.field15.nullfree, val8); + Asserts.assertEQ(t.field15.nullable, null); + + try { + t.field15 = new MyValue14(null, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException npe) { + // Expected + } + + // Null-free, atomic, flat with both nullable and null-free fields + t.field16 = new MyValue14(val8, val8); + Asserts.assertEQ(t.field16.nullfree, val8); + Asserts.assertEQ(t.field16.nullable, val8); + + t.field16 = new MyValue14(val8, null); + Asserts.assertEQ(t.field16.nullfree, val8); + Asserts.assertEQ(t.field16.nullable, null); + + try { + t.field16 = new MyValue14(null, null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException npe) { + // Expected + } + try { + t.field16 = null; + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException npe) { + // Expected + } + + MyValue15 val15 = new MyValue15(new MyClass(i)); + t.testWriteOopFields1(val15); + if (i > (LIMIT - 50)) { + // After warmup, produce some garbage to trigger GC + produceGarbage(); + } + Asserts.assertEQ(t.field17.obj.x, i); + Asserts.assertEQ(t.field18.obj.x, i); + + MyValue16 val16 = new MyValue16(new MyClass(i), new MyClass(i)); + t.testWriteOopFields2(val16); + if (i > (LIMIT - 50)) { + // After warmup, produce some garbage to trigger GC + produceGarbage(); + } + Asserts.assertEQ(t.field19.obj1.x, i); + Asserts.assertEQ(t.field19.obj2.x, i); + + MyValue17 val17 = new MyValue17(new MyClass(i), (byte)i, (byte)i); + t.testWriteOopFields3(val17); + if (i > (LIMIT - 50)) { + // After warmup, produce some garbage to trigger GC + produceGarbage(); + } + Asserts.assertEQ(t.field20.obj.x, i); + Asserts.assertEQ(t.field20.b1, (byte)i); + Asserts.assertEQ(t.field20.b2, (byte)i); + Asserts.assertEQ(t.field21.obj.x, i); + Asserts.assertEQ(t.field21.b1, (byte)i); + Asserts.assertEQ(t.field21.b2, (byte)i); + + Asserts.assertEQ(t.emptyField1, new MyValueEmpty()); + Asserts.assertEQ(t.emptyField2, new MyValueEmpty()); + + // Test empty fields + t.emptyField3 = new MyValueEmpty(); + t.emptyField4 = new MyValueEmpty(); + Asserts.assertEQ(t.emptyField3, new MyValueEmpty()); + Asserts.assertEQ(t.emptyField4, new MyValueEmpty()); + t.emptyField3 = null; + t.emptyField4 = null; + Asserts.assertEQ(t.emptyField3, null); + Asserts.assertEQ(t.emptyField4, null); + + t.testLoadingFromConstantHolder(i); + + // Verify that no out of bounds accesses happen + t.testOutOfBoundsAccess(i); + + // Test strict fields + TwoBytes twoBytes = new TwoBytes((byte)i, (byte)(i + 1)); + t.testStrictFields(new StrictFieldHolder(val8, val8, twoBytes, twoBytes), val8, val8, twoBytes, twoBytes); + t.testStrictFields(new StrictFieldHolder(null, val8, null, twoBytes), null, val8, null, twoBytes); + } + + // Trigger deoptimization to check that re-materialization takes the null marker into account + byte x = (byte)42; + t.testDeopt1(x, new MyValue1(x, new MyValue2(x), new MyValue2(x)), new MyValue1(x, null, null), true); + t.testDeopt2(x, new MyValue4(new MyValue3(x), new MyValue3(x)), new MyValue4(null, null), true); + + MyValue5 val1 = new MyValue5(x, new MyValue5_1(x, new MyValue5_2(x, new MyValue5_3(x)))); + MyValue5 val2 = new MyValue5(x, new MyValue5_1(x, new MyValue5_2(x, null))); + MyValue5 val3 = new MyValue5(x, new MyValue5_1(x, null)); + MyValue5 val4 = new MyValue5(x, null); + t.testDeopt3(x, val1, val2, val3, val4, true); + + MyValue6 val5 = new MyValue6(new MyValueEmpty()); + MyValue6 val6 = new MyValue6(null); + MyValue6 val7 = null; + t.testDeopt4(val5, val6, val7, true); + + MyValue7 val8 = new MyValue7(new MyValue6(new MyValueEmpty())); + MyValue7 val9 = new MyValue7(new MyValue6(null)); + MyValue7 val10 = new MyValue7(null); + MyValue7 val11 = null; + t.testDeopt5(val8, val9, val10, val11, false); + } +} + diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestFlatArrayAliasesCardMark.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestFlatArrayAliasesCardMark.java new file mode 100644 index 00000000000..398a37553b7 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestFlatArrayAliasesCardMark.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2021, Red Hat, Inc. All rights reserved. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8260363 + * @summary C2 compilation fails with assert(n->Opcode() != Op_Phi) failed: cannot match + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm -XX:-BackgroundCompilation TestFlatArrayAliasesCardMark + */ + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; + +@LooselyConsistentValue +value class Test0 { + int x = 42; + short[] array = new short[7]; +} + +public class TestFlatArrayAliasesCardMark { + int f = 1; + + public void method1(Test0[] array) { + for (int i = 0; i < 100; ++i) { + array[0] = array[0]; + for (int j = 0; j < 10; ++j) { + for (int k = 0; k < 10; ++k) { + f = 42; + } + } + } + } + + public static void main(String[] args) { + TestFlatArrayAliasesCardMark t = new TestFlatArrayAliasesCardMark(); + Test0[] array = (Test0[])ValueClass.newNullRestrictedNonAtomicArray(Test0.class, 1, new Test0()); + array[0] = new Test0(); + + for (int l1 = 0; l1 < 10_000; ++l1) { + t.method1(array); + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestFlatArrayThreshold.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestFlatArrayThreshold.java new file mode 100644 index 00000000000..6c4b0211908 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestFlatArrayThreshold.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @summary Test accessing value class arrays that exceed the flattening threshold. + * @library /test/lib + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm -Xbatch + * TestFlatArrayThreshold + * @run main/othervm -XX:FlatArrayElementMaxOops=1 -Xbatch + * TestFlatArrayThreshold + * @run main/othervm -XX:+UseArrayFlattening -Xbatch + * TestFlatArrayThreshold + + */ + +import jdk.test.lib.Asserts; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; + +@LooselyConsistentValue +value class MyValue1 { + Object o1; + Object o2; + + public MyValue1(Object o1, Object o2) { + this.o1 = o1; + this.o2 = o2; + } +} + +public class TestFlatArrayThreshold { + + public static MyValue1 test1(MyValue1[] va, MyValue1 vt) { + va[0] = vt; + return va[1]; + } + + public static MyValue1 test2(MyValue1[] va, MyValue1 vt) { + va[0] = vt; + return va[1]; + } + + public static Object test3(Object[] va, MyValue1 vt) { + va[0] = vt; + return va[1]; + } + + public static Object test4(Object[] va, MyValue1 vt) { + va[0] = vt; + return va[1]; + } + + public static MyValue1 test5(MyValue1[] va, Object vt) { + va[0] = (MyValue1)vt; + return va[1]; + } + + public static MyValue1 test6(MyValue1[] va, Object vt) { + va[0] = (MyValue1)vt; + return va[1]; + } + + public static Object test7(Object[] va, Object vt) { + va[0] = vt; + return va[1]; + } + + static public void main(String[] args) { + MyValue1 vt = new MyValue1(new Integer(42), new Integer(43)); + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 2, new MyValue1(null, null)); + MyValue1[] vaB = new MyValue1[2]; + va[1] = vt; + for (int i = 0; i < 10_000; ++i) { + MyValue1 result1 = test1(va, vt); + Asserts.assertEQ(result1.o1, 42); + Asserts.assertEQ(result1.o2, 43); + + MyValue1 result2 = test2(va, vt); + Asserts.assertEQ(result2.o1, 42); + Asserts.assertEQ(result2.o2, 43); + result2 = test2(vaB, null); + Asserts.assertEQ(result2, null); + + MyValue1 result3 = (MyValue1)test3(va, vt); + Asserts.assertEQ(result3.o1, 42); + Asserts.assertEQ(result3.o2, 43); + result3 = (MyValue1)test3(vaB, vt); + Asserts.assertEQ(result3, null); + + MyValue1 result4 = (MyValue1)test4(va, vt); + Asserts.assertEQ(result4.o1, 42); + Asserts.assertEQ(result4.o2, 43); + result4 = (MyValue1)test4(vaB, null); + Asserts.assertEQ(result4, null); + + MyValue1 result5 = test5(va, vt); + Asserts.assertEQ(result5.o1, 42); + Asserts.assertEQ(result5.o2, 43); + + MyValue1 result6 = test6(va, vt); + Asserts.assertEQ(result6.o1, 42); + Asserts.assertEQ(result6.o2, 43); + result6 = test6(vaB, null); + Asserts.assertEQ(result6, null); + + MyValue1 result7 = (MyValue1)test7(va, vt); + Asserts.assertEQ(result7.o1, 42); + Asserts.assertEQ(result7.o2, 43); + result7 = (MyValue1)test7(vaB, null); + Asserts.assertEQ(result7, null); + } + try { + test2(va, null); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException npe) { + // Expected + } + try { + test4(va, null); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException npe) { + // Expected + } + try { + test5(va, null); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException npe) { + // Expected + } + try { + test6(va, null); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException npe) { + // Expected + } + try { + test7(va, null); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException npe) { + // Expected + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestFlatInArraysFolding.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestFlatInArraysFolding.java new file mode 100644 index 00000000000..e8d08a6437b --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestFlatInArraysFolding.java @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test id=serialgc + * @bug 8321734 8348961 + * @requires vm.gc.Serial + * @summary Test that CmpPNode::sub and SubTypeCheckNode::sub correctly identify unrelated classes based on the flat + * in array property of the types. Additionally check that the type system properly handles the case of a + * super class being flat in array while the sub klass could be flat in array. + * @library /test/lib / + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main compiler.valhalla.inlinetypes.TestFlatInArraysFolding serial + */ + +/* + * @test + * @bug 8321734 8348961 + * @summary Test that CmpPNode::sub and SubTypeCheckNode::sub correctly identify unrelated classes based on the flat + * in array property of the types. Additionally check that the type system properly handles the case of a + * super class being flat in array while the sub klass could be flat in array. + * @library /test/lib / + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main compiler.valhalla.inlinetypes.TestFlatInArraysFolding + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.*; + +import jdk.internal.vm.annotation.LooselyConsistentValue; + +public class TestFlatInArraysFolding { + static Object[] oArrArr = new Object[100][100]; + static Object[] oArr = new Object[100]; + static Object o = new Object(); + static Object[] oArrSmall = new Object[2]; + static Object[] vArr = new V[2]; + static { + oArrSmall[0] = new Object(); + oArrSmall[1] = new Object(); + vArr[0] = new V(); + vArr[1] = new V(); + } + static int limit = 2; + + // Make sure these are loaded such that A has a flat in array and a not flat in array sub class. + static FlatInArray flat = new FlatInArray(34); + static NotFlatInArray notFlat = new NotFlatInArray(34); + + // Make sure PUnique is the unique concrete sub class loaded from AUnique. + static PUnique pUnique = new PUnique(34); + + static int iFld; + + public static void main(String[] args) { + // TODO 8350865 Scenarios are equivalent, FlatArrayElementMaxSize does not exist anymore + Scenario flatArrayElementMaxSize1Scenario = new Scenario(1, "-XX:-UseArrayFlattening"); + Scenario flatArrayElementMaxSize4Scenario = new Scenario(2, "-XX:-UseArrayFlattening"); + Scenario noFlagsScenario = new Scenario(3); + TestFramework testFramework = new TestFramework(); + testFramework.setDefaultWarmup(0) + .addFlags("--enable-preview", + "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED") + .addScenarios(flatArrayElementMaxSize1Scenario, + flatArrayElementMaxSize4Scenario, noFlagsScenario); + + if (args.length > 0) { + // Disable Loop Unrolling for IR matching in testCmpP(). + // Use IgnoreUnrecognizedVMOptions since LoopMaxUnroll is a C2 flag. + // testSubTypeCheck() only triggers with SerialGC. + Scenario serialGCScenario = new Scenario(4, "-XX:+UseSerialGC", "-XX:+IgnoreUnrecognizedVMOptions", + "-XX:LoopMaxUnroll=0", "-XX:+UseArrayFlattening"); + testFramework.addScenarios(serialGCScenario); + } + Scenario noMethodTraps = new Scenario(5, "-XX:PerMethodTrapLimit=0", "-Xbatch"); + testFramework.addScenarios(noMethodTraps); + testFramework.start(); + } + + // SubTypeCheck is not folded while CheckCastPPNode is replaced by top which results in a bad graph (data dies while + // control does not). + @Test + static void testSubTypeCheck() { + for (int i = 0; i < 100; i++) { + Object arrayElement = oArrArr[i]; + oArr = (Object[])arrayElement; + } + } + + @Test + @IR(counts = {IRNode.COUNTED_LOOP, "2", // Loop Unswitching done? + IRNode.STORE_I, "1"}, // CmpP folded in unswitched loop version with flat in array? + applyIf = {"LoopMaxUnroll", "0"}) + static void testCmpP() { + Object[] arr = oArr; + if (arr == null) { + // Needed for the 'arrayElement == arr' to fold in + // the flat array case because both could be null. + throw new NullPointerException("arr is null"); + } + for (int i = 0; i < 100; i++) { + Object arrayElement = oArrArr[i]; + if (arrayElement == arr) { + iFld = 34; + } + } + } + + // Type system does not correctly handle the case that a super klass is flat in array while the sub klass is + // maybe flat in array. This leads to a bad graph. + @Test + static void testUnswitchingAbstractClass() { + Object[] arr = oArr; + for (int i = 0; i < 100; i++) { + Object arrayElement = arr[i]; + if (arrayElement instanceof A) { + A a = (A)arrayElement; + if (a == o) { + a.foo(); + } + } + } + } + + // Same as testUnswitchingAbstractClass() but with interfaces. This worked before because I has type Object(I) + // from which Object is a sub type of. + @Test + static void testUnswitchingInterface() { + Object[] arr = oArr; + for (int i = 0; i < 100; i++) { + Object arrayElement = arr[i]; + if (arrayElement instanceof I) { + I iVar = (I)arrayElement; + if (iVar == o) { + iVar.bar(); + } + } + } + } + + // TODO 8350865 FlatArrayElementMaxSize does not exist anymore + // PUnique is the unique concrete sub class of AUnique and is not flat in array (with FlatArrayElementMaxSize=4). + // The CheckCastPP output of the sub type check uses PUnique while the sub type check itself uses AUnique. This leads + // to a bad graph because the type system determines that the flat in array super klass cannot be met with the + // not flat in array sub klass. But the sub type check does not fold away because AUnique *could* be flat in array. + // Fixed with in JDK-8328480 in mainline but not yet merged in. Applied manually to make this work. + @Test + static void testSubTypeCheckNotFoldedParsingAbstractClass() { + Object[] arr = oArr; + for (int i = 0; i < 100; i++) { + Object arrayElement = arr[i]; + if (arrayElement instanceof AUnique) { + AUnique aUnique = (AUnique)arrayElement; + if (aUnique == o) { + aUnique.foo(); + } + } + } + } + + // Same as testSubTypeCheckNotFoldedParsingAbstractClass() but with interfaces. This worked before because IUnique + // has type Object(IUnique) from which Object is a sub type of. + @Test + static void testSubTypeCheckNotFoldedParsingInterface() { + Object[] arr = oArr; + for (int i = 0; i < 100; i++) { + Object arrayElement = arr[i]; + if (arrayElement instanceof IUnique) { + IUnique iUnique = (IUnique)arrayElement; + if (iUnique == o) { + iUnique.bar(); + } + } + } + } + + @Test + @Warmup(10000) + static void testJoinSameKlassDifferentFlatInArray() { + // Accessing the array: It could be a flat array. We therefore add a Phi to select from the normal vs. + // flat in array access: + // Phi(Object:flat_in_array, Object) -> CheckCastPP[Object:NotNull:exact] + for (Object o : oArrSmall) { + // We speculate that we always call Object::hashCode() and thus add a CheckCastPP[Object:NotNull:exact] + // together with a speculate_class_check trap on the failing path. + // We decide to unswitch the loop to get a loop version where we only have flat in array accesses. This + // means we can get rid of the Phi. During IGVN and folding some nodes we eventually end up with: + // CheckCastPP[Object:NotNull (flat_in_array)] -> CheckCastPP[Object:NotNull:exact] + // + // We know that we have some kind of Value class that needs to be joined with an exact Object that is not + // a value class. Thus, the result in an empty set. But this is missing in the type system. We fail with + // an assertion. This is fixed with 8348961. + // + // To make this type system change work, we require that the TypeInstKlassPtr::not_flat_in_array() takes + // exactness information into account to also fold the corresponding control path. This requires another + // follow up fix: The super class of a sub type check is always an exact class, i.e. "o instanceof Super". + // We need a version of TypeInstKlassPtr::not_flat_in_array() that treats "Super" as inexact. Failing to do + // so will erroneously fold a sub type check away (covered by testSubTypeCheckForObjectReceiver()). + o.hashCode(); + } + } + + @Test + @Warmup(10000) + static void testSubTypeCheckForObjectReceiver() { + for (int i = 0; i < limit; i++) { + // We perform a sub type check that V is a sub type of Object. This is obviously true + vArr[i].hashCode(); + } + } + + interface IUnique { + abstract void bar(); + } + + static abstract value class AUnique implements IUnique { + abstract void foo(); + } + + @LooselyConsistentValue + static value class PUnique extends AUnique { + int x; + int y; + PUnique(int x) { + this.x = x; + this.y = 34; + } + + public void foo() {} + public void bar() {} + } + + interface I { + void bar(); + } + + static abstract value class A implements I { + abstract void foo(); + } + + @LooselyConsistentValue + static value class FlatInArray extends A implements I { + int x; + FlatInArray(int x) { + this.x = x; + } + + public void foo() {} + public void bar() {} + } + + // TODO 8350865 FlatArrayElementMaxSize does not exist anymore + // Not flat in array with -XX:FlatArrayElementMaxSize=4 + static value class NotFlatInArray extends A implements I { + int x; + int y; + NotFlatInArray(int x) { + this.x = x; + this.y = 34; + } + + public void foo() {} + public void bar() {} + } + + static value class V {} +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestGenerated.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestGenerated.java new file mode 100644 index 00000000000..d5a25072d42 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestGenerated.java @@ -0,0 +1,357 @@ +/* + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8260034 8260225 8260283 8261037 8261874 8262128 8262831 8306986 8355299 + * @summary A selection of generated tests that triggered bugs not covered by other tests. + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm -Xbatch + * compiler.valhalla.inlinetypes.TestGenerated + * @run main/othervm -Xbatch -XX:-UseArrayFlattening + * compiler.valhalla.inlinetypes.TestGenerated + * @run main/othervm -Xbatch -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseNonAtomicValueFlattening + * compiler.valhalla.inlinetypes.TestGenerated + */ + +package compiler.valhalla.inlinetypes; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +@LooselyConsistentValue +value class EmptyPrimitive { + +} + +value class EmptyValue { + +} + +@LooselyConsistentValue +value class MyValue1 { + int x = 42; + int[] array = new int[1]; +} + +@LooselyConsistentValue +value class MyValue2 { + int[] a = new int[1]; + int[] b = new int[6]; + int[] c = new int[5]; +} + +@LooselyConsistentValue +value class MyValue3 { + int[] intArray = new int[1]; + float[] floatArray = new float[1]; +} + +@LooselyConsistentValue +value class MyValue4 { + short b = 2; + int c = 8; +} + +class MyValue4Wrapper { + public MyValue4 val; + + public MyValue4Wrapper(MyValue4 val) { + this.val = val; + } +} + +@LooselyConsistentValue +value class MyValue5 { + int b = 2; +} + +value class MyValue6 { + int x = 42; +} + +public class TestGenerated { + EmptyPrimitive f1 = new EmptyPrimitive(); + EmptyPrimitive f2 = new EmptyPrimitive(); + + void test1(EmptyPrimitive[] array) { + for (int i = 0; i < 10; ++i) { + f1 = array[0]; + f2 = array[0]; + } + } + + MyValue1 test2(MyValue1[] array) { + MyValue1 res = new MyValue1(); + for (int i = 0; i < array.length; ++i) { + res = array[i]; + } + for (int i = 0; i < 1000; ++i) { + + } + return res; + } + + void test3(MyValue1[] array) { + for (int i = 0; i < array.length; ++i) { + array[i] = new MyValue1(); + } + for (int i = 0; i < 1000; ++i) { + + } + } + + void test4(MyValue1[] array) { + array[0].array[0] = 0; + } + + int test5(MyValue1[] array) { + return array[0].array[0]; + } + + long f3; + @Strict + @NullRestricted + MyValue1 f4 = new MyValue1(); + + void test6() { + f3 = 123L; + int res = f4.x; + if (res != 42) { + throw new RuntimeException("test6 failed"); + } + } + + MyValue2 f5; + + void test7(boolean b) { + MyValue2[] array1 = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 6, new MyValue2()); + array1[0] = new MyValue2(); + array1[1] = new MyValue2(); + array1[2] = new MyValue2(); + array1[3] = new MyValue2(); + array1[4] = new MyValue2(); + array1[5] = new MyValue2(); + + MyValue2 h = new MyValue2(); + MyValue2 n = new MyValue2(); + int[] array2 = new int[1]; + + for (int i = 0; i < 10; ++i) { + for (int j = 0; j < 10; ++j) { + array1[0] = array1[0]; + if (i == 1) { + h = h; + array2[0] *= 42; + } + } + } + if (b) { + f5 = n; + } + } + + boolean test8(MyValue1[] array) { + return array[0].array == array[0].array; + } + + void test9(boolean b) { + MyValue1[] array = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, new MyValue1()); + if (b) { + for (int i = 0; i < 10; ++i) { + if (array != array) { + array = null; + } + } + } + } + + int[] f6 = new int[1]; + + void test10(MyValue3[] array) { + float[] floatArray = array[0].floatArray; + if (f6 == f6) { + f6 = array[0].intArray; + } + } + + void test11(MyValue3[] array) { + float[] floatArray = array[0].floatArray; + if (array[0].intArray[0] != 42) { + throw new RuntimeException("test11 failed"); + } + } + + MyValue4[] d = (MyValue4[])ValueClass.newNullRestrictedNonAtomicArray(MyValue4.class, 1, new MyValue4()); + @Strict + @NullRestricted + MyValue4 e = new MyValue4(); + byte f; + + byte test12() { + MyValue4 i = new MyValue4(); + for (int j = 0; j < 6; ++j) { + MyValue4[] k = (MyValue4[])ValueClass.newNullRestrictedNonAtomicArray(MyValue4.class, 0, new MyValue4()); + if (i.b < 101) { + i = e; + } + for (int l = 0; l < 9; ++l) { + MyValue4 m = new MyValue4(); + i = m; + } + } + if (d[0].c > 1) { + for (int n = 0; n < 7; ++n) { + } + } + return f; + } + + int test13_iField; + MyValue5 test13_c; + @Strict + @NullRestricted + MyValue5 test13_t = new MyValue5(); + + void test13(MyValue5[] array) { + for (int i = 0; i < 10; ++i) { + for (int j = 0; j < 10; ++j) { + test13_iField = 6; + } + for (int j = 0; j < 2; ++j) { + test13_iField += array[0].b; + } + MyValue5[] array2 = (MyValue5[])ValueClass.newNullRestrictedNonAtomicArray(MyValue5.class, 1, new MyValue5()); + test13_c = array[0]; + array2[0] = test13_t; + } + } + + void test14(boolean b, MyValue4 val) { + for (int i = 0; i < 10; ++i) { + if (b) { + val = new MyValue4(); + } + MyValue4[] array = (MyValue4[])ValueClass.newNullRestrictedNonAtomicArray(MyValue4.class, 1, new MyValue4()); + array[0] = val; + + for (int j = 0; j < 5; ++j) { + for (int k = 0; k < 5; ++k) { + } + } + } + } + + void test15() { + MyValue4 val = new MyValue4(); + for (int i = 0; i < 10; ++i) { + for (int j = 0; j < 10; ++j) { + MyValue4[] array = (MyValue4[])ValueClass.newNullRestrictedNonAtomicArray(MyValue4.class, 1, new MyValue4()); + for (int k = 0; k < 10; ++k) { + array[0] = val; + val = array[0]; + } + } + } + } + + void test16() { + MyValue4 val = new MyValue4(); + for (int i = 0; i < 10; ++i) { + for (int j = 0; j < 10; ++j) { + val = (new MyValue4Wrapper(val)).val; + for (int k = 0; k < 10; ++k) { + } + } + } + } + + static MyValue6 test17Field = new MyValue6(); + + void test17() { + for (int i = 0; i < 10; ++i) { + MyValue6 val = new MyValue6(); + for (int j = 0; j < 10; ++j) { + test17Field = val; + } + } + } + + EmptyValue test18Field; + + EmptyValue test18() { + EmptyValue val = new EmptyValue(); + test18Field = val; + return test18Field; + } + + MyValue1 test19Field = new MyValue1(); + + public void test19() { + for (int i = 0; i < 10; ++i) { + MyValue1 val = new MyValue1(); + for (int j = 0; j < 10; ++j) { + test19Field = val; + } + } + } + + public static void main(String[] args) { + TestGenerated t = new TestGenerated(); + EmptyPrimitive[] array1 = (EmptyPrimitive[])ValueClass.newNullRestrictedNonAtomicArray(EmptyPrimitive.class, 1, new EmptyPrimitive()); + MyValue1[] array2 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 10, new MyValue1()); + MyValue1[] array3 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, new MyValue1()); + array3[0] = new MyValue1(); + MyValue3[] array4 = (MyValue3[])ValueClass.newNullRestrictedNonAtomicArray(MyValue3.class, 1, new MyValue3()); + array4[0] = new MyValue3(); + MyValue5[] array5 = (MyValue5[])ValueClass.newNullRestrictedNonAtomicArray(MyValue5.class, 1, new MyValue5()); + array5[0] = new MyValue5(); + array4[0].intArray[0] = 42; + + for (int i = 0; i < 50_000; ++i) { + t.test1(array1); + t.test2(array2); + t.test3(array2); + t.test4(array3); + t.test5(array3); + t.test6(); + t.test7(false); + t.test8(array3); + t.test9(true); + t.test10(array4); + t.test11(array4); + t.test12(); + t.test13(array5); + t.test14(false, new MyValue4()); + t.test15(); + t.test16(); + t.test17(); + t.test18(); + t.test19(); + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestGetfieldChains.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestGetfieldChains.java new file mode 100644 index 00000000000..db7b067431b --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestGetfieldChains.java @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.CompLevel; +import compiler.lib.ir_framework.Run; +import compiler.lib.ir_framework.Scenario; +import compiler.lib.ir_framework.Test; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import jdk.test.lib.Asserts; + +/* + * @test + * @key randomness + * @summary Verify that chains of getfields on flat fields are correctly optimized. + * @library /test/lib / + * @requires (os.simpleArch == "x64" | os.simpleArch == "aarch64") + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @compile GetfieldChains.jcod + * @run main/othervm/timeout=300 compiler.valhalla.inlinetypes.TestGetfieldChains + */ + +@LooselyConsistentValue +value class Point { + int x = 4; + int y = 7; +} + +@LooselyConsistentValue +value class Rectangle { + @Strict + @NullRestricted + Point p0 = new Point(); + @Strict + @NullRestricted + Point p1 = new Point(); +} + +class NamedRectangle { + @Strict + @NullRestricted + Rectangle rect = new Rectangle(); + String name = ""; + + static int getP1X(NamedRectangle nr) { + return nr.rect + .p1 + .x; + } + + static Point getP1(NamedRectangle nr) { + return nr.rect + .p1; + } +} + +public class TestGetfieldChains { + + public static void main(String[] args) { + + final Scenario[] scenarios = { + new Scenario(0, + // C1 only + "-XX:TieredStopAtLevel=1", + "-XX:+TieredCompilation"), + new Scenario(1, + // C2 only. (Make sure the tests are correctly written) + "-XX:TieredStopAtLevel=4", + "-XX:-TieredCompilation", + "-XX:-OmitStackTraceInFastThrow"), + new Scenario(2, + // interpreter only + "-Xint"), + new Scenario(3, + // Xcomp Only C1 + "-XX:TieredStopAtLevel=1", + "-XX:+TieredCompilation", + "-Xcomp"), + new Scenario(4, + // Xcomp Only C2 + "-XX:TieredStopAtLevel=4", + "-XX:-TieredCompilation", + "-XX:-OmitStackTraceInFastThrow", + "-Xcomp") + }; + + InlineTypes.getFramework() + .addScenarios(scenarios) + .addFlags("--enable-preview", + "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED") + .start(); + } + + + // Simple chain of getfields ending with value type field + @Test(compLevel = CompLevel.C1_SIMPLE) + public int test1() { + return NamedRectangle.getP1X(new NamedRectangle()); + } + + @Run(test = "test1") + public void test1_verifier() { + int res = test1(); + Asserts.assertEQ(res, 4); + } + + // Simple chain of getfields ending with a flat field + @Test(compLevel = CompLevel.C1_SIMPLE) + public Point test2() { + return NamedRectangle.getP1(new NamedRectangle()); + } + + @Run(test = "test2") + public void test2_verifier() { + Point p = test2(); + Asserts.assertEQ(p.x, 4); + Asserts.assertEQ(p.y, 7); + } + + // Chain of getfields but the initial receiver is null + @Test(compLevel = CompLevel.C1_SIMPLE) + public NullPointerException test3() { + NullPointerException npe = null; + try { + NamedRectangle.getP1X(null); + throw new RuntimeException("No NullPointerException thrown"); + } catch (NullPointerException e) { + npe = e; + } + return npe; + } + + @Run(test = "test3") + public void test3_verifier() { + NullPointerException npe = test3(); + Asserts.assertNE(npe, null); + StackTraceElement st = npe.getStackTrace()[0]; + Asserts.assertEQ(st.getMethodName(), "getP1X"); + } + + // Chain of getfields but one getfield in the middle of the chain triggers an illegal access + @Test(compLevel = CompLevel.C1_SIMPLE) + public IllegalAccessError test4() { + IllegalAccessError iae = null; + try { + int i = NamedRectangleP.getP1Y(new NamedRectangleP()); + throw new RuntimeException("No IllegalAccessError thrown"); + } catch (IllegalAccessError e) { + iae = e; + } + return iae; + } + + @Run(test = "test4") + public void test4_verifier() { + IllegalAccessError iae = test4(); + Asserts.assertNE(iae, null); + StackTraceElement st = iae.getStackTrace()[0]; + Asserts.assertEQ(st.getMethodName(), "getP1Y"); + Asserts.assertTrue(iae.getMessage().contains("class compiler.valhalla.inlinetypes.NamedRectangleP tried to access private field compiler.valhalla.inlinetypes.RectangleP.p1")); + } + + // Chain of getfields but the last getfield triggers a NoSuchFieldError + @Test(compLevel = CompLevel.C1_SIMPLE) + public NoSuchFieldError test5() { + NoSuchFieldError nsfe = null; + try { + int i = NamedRectangleN.getP1X(new NamedRectangleN()); + throw new RuntimeException("No NoSuchFieldError thrown"); + } catch (NoSuchFieldError e) { + nsfe = e; + } + return nsfe; + } + + @Run(test = "test5") + public void test5_verifier() { + NoSuchFieldError nsfe = test5(); + Asserts.assertNE(nsfe, null); + StackTraceElement st = nsfe.getStackTrace()[0]; + Asserts.assertEQ(st.getMethodName(), "getP1X"); + Asserts.assertEQ(nsfe.getMessage(), "Class compiler.valhalla.inlinetypes.PointN does not have member field 'int x'"); + } + + @LooselyConsistentValue + static value class EmptyType1 { } + + @LooselyConsistentValue + static value class EmptyContainer1 { + int i = 0; + @Strict + @NullRestricted + EmptyType1 et = new EmptyType1(); + } + + @LooselyConsistentValue + static value class Container1 { + @Strict + @NullRestricted + EmptyContainer1 container0 = new EmptyContainer1(); + @Strict + @NullRestricted + EmptyContainer1 container1 = new EmptyContainer1(); + } + + @Test(compLevel = CompLevel.C1_SIMPLE) + public EmptyType1 test6() { + Container1 c = new Container1(); + return c.container1.et; + } + + @Run(test = "test6") + public void test6_verifier() { + EmptyType1 et = test6(); + Asserts.assertEQ(et, new EmptyType1()); + } + + @Test(compLevel = CompLevel.C1_SIMPLE) + public EmptyType1 test7() { + Container1[] ca = (Container1[])ValueClass.newNullRestrictedNonAtomicArray(Container1.class, 10, new Container1()); + return ca[3].container0.et; + } + + @Run(test = "test7") + public void test7_verifier() { + EmptyType1 et = test7(); + Asserts.assertEQ(et, new EmptyType1()); + } + + // Same as test6/test7 but not null-free and EmptyContainer2 with only one field + + static value class EmptyType2 { } + + static value class EmptyContainer2 { + EmptyType2 et = null; + } + + static value class Container2 { + EmptyContainer2 container0 = new EmptyContainer2(); + EmptyContainer2 container1 = new EmptyContainer2(); + } + + @Test(compLevel = CompLevel.C1_SIMPLE) + public EmptyType2 test8() { + Container2 c = new Container2(); + return c.container1.et; + } + + @Run(test = "test8") + public void test8_verifier() { + EmptyType2 et = test8(); + Asserts.assertEQ(et, null); + } + + @Test(compLevel = CompLevel.C1_SIMPLE) + public EmptyType2 test9() { + Container2[] ca = new Container2[10]; + ca[3] = new Container2(); + return ca[3].container0.et; + } + + @Run(test = "test9") + public void test9_verifier() { + EmptyType2 et = test9(); + Asserts.assertEQ(et, null); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestInlineFieldNonFlattened.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestInlineFieldNonFlattened.java new file mode 100644 index 00000000000..da8225543f4 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestInlineFieldNonFlattened.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2023, Arm Limited. All rights reserved. + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.*; +import java.util.Random; + +import jdk.test.lib.Utils; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +/* + * @test + * @bug 8311219 + * @summary VM option "UseFieldFlattening" does not work well. + * @library /test/lib / + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm -XX:-TieredCompilation + * -XX:-UseFieldFlattening + * compiler.valhalla.inlinetypes.TestInlineFieldNonFlattened + */ + +public class TestInlineFieldNonFlattened { + static class MyClass { + @Strict + @NullRestricted + public final MyValue v1 = new MyValue(5); + + public MyValue v2; + + public MyClass(MyValue v) { + v2 = v; + } + } + + @LooselyConsistentValue + static value class MyValue { + public int field; + + public MyValue(int f) { + field = f; + } + } + + private static final Random RD = Utils.getRandomInstance(); + + static MyClass c; + + static { + c = new MyClass(new MyValue(RD.nextInt(100))); + } + + static int f; + + @Test + @IR(counts = {IRNode.LOAD_N, "2"}) + public static void testNonFlattenedField() { + f = c.v2.field; + } + + @Test + @IR(counts = {IRNode.LOAD_N, "2"}) + public static void testNonFlattenedFinalField() { + f = c.v1.field; + } + + public static void main(String[] args) { + TestFramework testFramework = new TestFramework(); + testFramework.setDefaultWarmup(10000) + .addFlags("--enable-preview", + "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED", + "-XX:-TieredCompilation", + "-XX:-UseFieldFlattening") + .start(); + } +} + diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestIntrinsics.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestIntrinsics.java new file mode 100644 index 00000000000..80ee8f4fe22 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestIntrinsics.java @@ -0,0 +1,1800 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import jdk.internal.misc.Unsafe; +import jdk.test.lib.Asserts; +import compiler.lib.ir_framework.*; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.Arrays; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import jdk.test.whitebox.WhiteBox; + +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.CALL_UNSAFE; +import static compiler.valhalla.inlinetypes.InlineTypes.rI; +import static compiler.valhalla.inlinetypes.InlineTypes.rL; + +import static compiler.lib.ir_framework.IRNode.LOAD_KLASS; + +/* + * @test + * @key randomness + * @summary Test intrinsic support for value classes. + * @library /test/lib / + * @requires (os.simpleArch == "x64" | os.simpleArch == "aarch64") + * @enablePreview + * @modules java.base/jdk.internal.misc + * java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI compiler.valhalla.inlinetypes.TestIntrinsics + */ + +@ForceCompileClassInitializer +public class TestIntrinsics { + + private static final WhiteBox WHITEBOX = WhiteBox.getWhiteBox(); + private static final boolean UseArrayFlattening = WHITEBOX.getBooleanVMFlag("UseArrayFlattening"); + private static final boolean UseFieldFlattening = WHITEBOX.getBooleanVMFlag("UseFieldFlattening"); + private static final boolean PreloadClasses = WHITEBOX.getBooleanVMFlag("PreloadClasses"); + + public static void main(String[] args) { + + Scenario[] scenarios = InlineTypes.DEFAULT_SCENARIOS; + scenarios[3].addFlags("-XX:-MonomorphicArrayCheck", "-XX:+UseArrayFlattening"); + scenarios[4].addFlags("-XX:-MonomorphicArrayCheck", "-XX:+UnlockExperimentalVMOptions", "-XX:PerMethodSpecTrapLimit=0", "-XX:PerMethodTrapLimit=0"); + + InlineTypes.getFramework() + .addScenarios(scenarios) + .addFlags("-Xbootclasspath/a:.", "-XX:+UnlockDiagnosticVMOptions", "-XX:+WhiteBoxAPI", + "--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED") + .addHelperClasses(MyValue1.class, + MyValue2.class, + MyValue2Inline.class) + .start(); + } + + static { + // Make sure RuntimeException is loaded to prevent uncommon traps in IR verified tests + RuntimeException tmp = new RuntimeException("42"); + } + + // Test correctness of the Class::isAssignableFrom intrinsic + @Test + public boolean test1(Class supercls, Class subcls) { + return supercls.isAssignableFrom(subcls); + } + + @Run(test = "test1") + public void test1_verifier() { + Asserts.assertTrue(test1(java.util.AbstractList.class, java.util.ArrayList.class), "test1_1 failed"); + Asserts.assertTrue(test1(MyValue1.class, MyValue1.class), "test1_2 failed"); + Asserts.assertTrue(test1(Object.class, java.util.ArrayList.class), "test1_3 failed"); + Asserts.assertTrue(test1(Object.class, MyValue1.class), "test1_4 failed"); + Asserts.assertTrue(!test1(MyValue1.class, Object.class), "test1_5 failed"); + } + + // Verify that Class::isAssignableFrom checks with statically known classes are folded + @Test + @IR(failOn = {LOAD_KLASS}) + public boolean test2() { + boolean check1 = java.util.AbstractList.class.isAssignableFrom(java.util.ArrayList.class); + boolean check2 = MyValue1.class.isAssignableFrom(MyValue1.class); + boolean check3 = Object.class.isAssignableFrom(java.util.ArrayList.class); + boolean check4 = Object.class.isAssignableFrom(MyValue1.class); + boolean check5 = !MyValue1.class.isAssignableFrom(Object.class); + return check1 && check2 && check3 && check4 && check5; + } + + @Run(test = "test2") + public void test2_verifier() { + Asserts.assertTrue(test2(), "test2 failed"); + } + + // Test correctness of the Class::getSuperclass intrinsic + @Test + public Class test3(Class cls) { + return cls.getSuperclass(); + } + + @Run(test = "test3") + public void test3_verifier() { + Asserts.assertTrue(test3(Object.class) == null, "test3_1 failed"); + Asserts.assertTrue(test3(MyValue1.class) == MyAbstract.class, "test3_2 failed"); + Asserts.assertTrue(test3(MyValue1.class) == MyAbstract.class, "test3_3 failed"); + Asserts.assertTrue(test3(Class.class) == Object.class, "test3_4 failed"); + } + + // Verify that Class::getSuperclass checks with statically known classes are folded + @Test + @IR(failOn = {LOAD_KLASS}) + public boolean test4() { + boolean check1 = Object.class.getSuperclass() == null; + boolean check2 = MyValue1.class.getSuperclass() == MyAbstract.class; + boolean check3 = MyValue1.class.getSuperclass() == MyAbstract.class; + boolean check4 = Class.class.getSuperclass() == Object.class; + return check1 && check2 && check3 && check4; + } + + @Run(test = "test4") + public void test4_verifier() { + Asserts.assertTrue(test4(), "test4 failed"); + } + + // Test toString() method + @Test + public String test5(MyValue1 v) { + return v.toString(); + } + + @Run(test = "test5") + public void test5_verifier() { + MyValue1 v = MyValue1.createDefaultInline(); + test5(v); + } + + // Test hashCode() method + @Test + public int test6(MyValue1 v) { + return v.hashCode(); + } + + @Run(test = "test6") + public void test6_verifier() { + MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); + int res = test6(v); + Asserts.assertEQ(res, v.hashCode()); + } + + // Test value class array creation via reflection + @Test + public Object[] test7(Class componentType, int len, Object initValue) { + Object[] va = ValueClass.newNullRestrictedNonAtomicArray(componentType, len, initValue); + return va; + } + + @Run(test = "test7") + public void test7_verifier() { + int len = Math.abs(rI) % 42; + long hash = MyValue1.createDefaultDontInline().hashPrimitive(); + Object[] va = test7(MyValue1.class, len, MyValue1.DEFAULT); + for (int i = 0; i < len; ++i) { + Asserts.assertEQ(((MyValue1)va[i]).hashPrimitive(), hash); + } + } + + // Class.isInstance + @Test + public boolean test8(Class c, MyValue1 vt) { + return c.isInstance(vt); + } + + @Run(test = "test8") + public void test8_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + boolean result = test8(MyValue1.class, vt); + Asserts.assertTrue(result); + result = test8(MyValue1.class, vt); + Asserts.assertTrue(result); + } + + @Test + public boolean test9(Class c, MyValue1 vt) { + return c.isInstance(vt); + } + + @Run(test = "test9") + public void test9_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + boolean result = test9(MyValue2.class, vt); + Asserts.assertFalse(result); + result = test9(MyValue2.class, vt); + Asserts.assertFalse(result); + } + + // Class.cast + @Test + public Object test10(Class c, MyValue1 vt) { + return c.cast(vt); + } + + @Run(test = "test10") + public void test10_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + Object result = test10(MyValue1.class, vt); + Asserts.assertEQ(((MyValue1)result).hash(), vt.hash()); + } + + @Test + public Object test11(Class c, MyValue1 vt) { + return c.cast(vt); + } + + @Run(test = "test11") + public void test11_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + try { + test11(MyValue2.class, vt); + throw new RuntimeException("should have thrown"); + } catch (ClassCastException cce) { + } + } + + @Test + public Object test12(MyValue1 vt) { + return MyValue1.class.cast(vt); + } + + @Run(test = "test12") + public void test12_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + Object result = test12(vt); + Asserts.assertEQ(((MyValue1)result).hash(), vt.hash()); + } + + @Test + public Object test13(MyValue1 vt) { + return MyValue2.class.cast(vt); + } + + @Run(test = "test13") + public void test13_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + try { + test13(vt); + throw new RuntimeException("should have thrown"); + } catch (ClassCastException cce) { + } + } + + // Value class array creation via reflection + @Test + public void test14(int len, long hash) { + Object[] va = ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + for (int i = 0; i < len; ++i) { + Asserts.assertEQ(((MyValue1)va[i]).hashPrimitive(), hash); + } + } + + @Run(test = "test14") + public void test14_verifier() { + int len = Math.abs(rI) % 42; + long hash = MyValue1.createDefaultDontInline().hashPrimitive(); + test14(len, hash); + } + + // Test hashCode() method + @Test + public int test15(Object v) { + return v.hashCode(); + } + + @Run(test = "test15") + public void test15_verifier() { + MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); + int res = test15(v); + Asserts.assertEQ(res, v.hashCode()); + } + + @Test + public int test16(Object v) { + return System.identityHashCode(v); + } + + @Run(test = "test16") + public void test16_verifier() { + MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); + int res = test16(v); + Asserts.assertEQ(res, System.identityHashCode((Object)v)); + } + + @Test + public int test17(Object v) { + return System.identityHashCode(v); + } + + @Run(test = "test17") + public void test17_verifier() { + Integer v = Integer.valueOf(rI); + int res = test17(v); + Asserts.assertEQ(res, System.identityHashCode(v)); + } + + @Test + public int test18(Object v) { + return System.identityHashCode(v); + } + + @Run(test = "test18") + public void test18_verifier() { + Object v = null; + int res = test18(v); + Asserts.assertEQ(res, System.identityHashCode(v)); + } + + // hashCode() and toString() with different value objects + @Test + public int test19(MyValue1 vt1, MyValue1 vt2, boolean b) { + MyValue1 res = b ? vt1 : vt2; + return res.hashCode(); + } + + @Run(test = "test19") + public void test19_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + int res = test19(vt, vt, true); + Asserts.assertEQ(res, vt.hashCode()); + res = test19(vt, vt, false); + Asserts.assertEQ(res, vt.hashCode()); + } + + @Test + public String test20(MyValue1 vt1, MyValue1 vt2, boolean b) { + MyValue1 res = b ? vt1 : vt2; + return res.toString(); + } + + @Run(test = "test20") + public void test20_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + String res = test20(vt, vt, true); + Asserts.assertEQ(res, vt.toString()); + res = test20(vt, vt, false); + Asserts.assertEQ(res, vt.toString()); + } + + private static final Unsafe U = Unsafe.getUnsafe(); + private static final long X_OFFSET; + private static final long Y_OFFSET; + private static final long V1_OFFSET; + private static final boolean V1_FLATTENED; + private static final int V1_LAYOUT; + + static { + try { + Field xField = MyValue1.class.getDeclaredField("x"); + X_OFFSET = U.objectFieldOffset(xField); + Field yField = MyValue1.class.getDeclaredField("y"); + Y_OFFSET = U.objectFieldOffset(yField); + Field v1Field = MyValue1.class.getDeclaredField("v1"); + V1_OFFSET = U.objectFieldOffset(v1Field); + V1_FLATTENED = U.isFlatField(v1Field); + V1_LAYOUT = U.fieldLayout(v1Field); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + @IR(failOn = {CALL_UNSAFE}) + public int test21(MyValue1 v) { + return U.getInt(v, X_OFFSET); + } + + @Run(test = "test21") + public void test21_verifier() { + MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); + int res = test21(v); + Asserts.assertEQ(res, v.x); + } + + MyValue1 test22_vt; + + @Test + @IR(failOn = {CALL_UNSAFE}) + public void test22(MyValue1 v) { + v = U.makePrivateBuffer(v); + U.putInt(v, X_OFFSET, rI); + v = U.finishPrivateBuffer(v); + test22_vt = v; + } + + @Run(test = "test22") + public void test22_verifier() { + MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); + test22(v.setX(v, 0)); + Asserts.assertEQ(test22_vt.hash(), v.hash()); + } + + @Test + @IR(failOn = {CALL_UNSAFE}) + public int test23(MyValue1 v, long offset) { + return U.getInt(v, offset); + } + + @Run(test = "test23") + public void test23_verifier() { + MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); + int res = test23(v, X_OFFSET); + Asserts.assertEQ(res, v.x); + } + + @Strict + @NullRestricted + MyValue1 test24_vt = MyValue1.createWithFieldsInline(rI, rL); + + @Test + @IR(failOn = {CALL_UNSAFE}) + public int test24(long offset) { + return U.getInt(test24_vt, offset); + } + + @Run(test = "test24") + public void test24_verifier() { + int res = test24(X_OFFSET); + Asserts.assertEQ(res, test24_vt.x); + } + + // Test copyOf intrinsic with allocated value object in it's debug information + @LooselyConsistentValue + static value class Test25Value { + int x; + + public Test25Value(int x) { + this.x = x; + } + } + + final Test25Value[] test25Array = (Test25Value[])ValueClass.newNullRestrictedNonAtomicArray(Test25Value.class, 10, new Test25Value(0)); + + @Test + public Test25Value[] test25(Test25Value element) { + Object[] newArray = Arrays.copyOf(test25Array, test25Array.length); + newArray[test25Array.length - 1] = element; + return (Test25Value[]) newArray; + } + + @Run(test = "test25") + public void test25_verifier() { + Test25Value vt = new Test25Value(42); + test25(vt); + } + + @Test + @IR(failOn = IRNode.LOAD_I) // Load of the all-zero value should be folded + public Object test26() { + Class[] ca = new Class[1]; + for (int i = 0; i < 1; ++i) { + // Folds during loop opts + ca[i] = MyValue1.class; + } + return ValueClass.newNullRestrictedNonAtomicArray(ca[0], 1, MyValue1.DEFAULT); + } + + @Run(test = "test26") + public void test26_verifier() { + Object[] res = (Object[])test26(); + Asserts.assertEQ(((MyValue1)res[0]).hashPrimitive(), MyValue1.createDefaultInline().hashPrimitive()); + } + + // Load non-flattenable value class field with unsafe + MyValue1 test27_vt; + private static final long TEST27_OFFSET; + static { + try { + Field field = TestIntrinsics.class.getDeclaredField("test27_vt"); + TEST27_OFFSET = U.objectFieldOffset(field); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + @IR(failOn = {CALL_UNSAFE}) + public MyValue1 test27() { + return (MyValue1)U.getReference(this, TEST27_OFFSET); + } + + @Run(test = "test27") + public void test27_verifier() { + test27_vt = null; + MyValue1 res = test27(); + Asserts.assertEQ(res, null); + test27_vt = MyValue1.createWithFieldsInline(rI, rL); + res = test27(); + Asserts.assertEQ(res.hash(), test24_vt.hash()); + } + + // Mismatched type + @Test + @IR(failOn = {CALL_UNSAFE}) + public int test28(MyValue1 v) { + return U.getByte(v, X_OFFSET); + } + + @Run(test = "test28") + public void test28_verifier() { + MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); + int res = test28(v); + if (java.nio.ByteOrder.nativeOrder() == java.nio.ByteOrder.LITTLE_ENDIAN) { + Asserts.assertEQ(res, (int)((byte)v.x)); + } else { + Asserts.assertEQ(res, (int)((byte)Integer.reverseBytes(v.x))); + } + } + + // Wrong alignment + @Test + @IR(failOn = {CALL_UNSAFE}) + public long test29(MyValue1 v) { + // Read the field that's guaranteed to not be last in the + // value class so we don't read out of bounds. + if (X_OFFSET < Y_OFFSET) { + return U.getInt(v, X_OFFSET+1); + } + return U.getLong(v, Y_OFFSET+1); + } + + @Run(test = "test29") + public void test29_verifier() { + MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); + long res = test29(v); + if (java.nio.ByteOrder.nativeOrder() == java.nio.ByteOrder.LITTLE_ENDIAN) { + if (X_OFFSET < Y_OFFSET) { + Asserts.assertEQ(((int)res) << 8, (v.x >> 8) << 8); + } else { + Asserts.assertEQ(res << 8, (v.y >> 8) << 8); + } + } else { + if (X_OFFSET < Y_OFFSET) { + Asserts.assertEQ(((int)res), v.x >>> 8); + } else { + Asserts.assertEQ(res, v.y >>> 8); + } + } + } + + // getValue to retrieve flattened field from value object + @Test + @IR(failOn = {CALL_UNSAFE}) + public MyValue2 test30(MyValue1 v) { + if (V1_FLATTENED) { + return U.getFlatValue(v, V1_OFFSET, V1_LAYOUT, MyValue2.class); + } + return (MyValue2)U.getReference(v, V1_OFFSET); + } + + @Run(test = "test30") + public void test30_verifier(RunInfo info) { + MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); + MyValue2 res = test30(v); + Asserts.assertEQ(res.hash(), v.v1.hash()); + } + + MyValue1 test31_vt; + private static final long TEST31_VT_OFFSET; + private static final boolean TEST31_VT_FLATTENED; + private static final int TEST31_VT_LAYOUT; + static { + try { + Field test31_vt_Field = TestIntrinsics.class.getDeclaredField("test31_vt"); + TEST31_VT_OFFSET = U.objectFieldOffset(test31_vt_Field); + TEST31_VT_FLATTENED = U.isFlatField(test31_vt_Field); + TEST31_VT_LAYOUT = U.fieldLayout(test31_vt_Field); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + // getValue to retrieve flattened field from object + @Test + @IR(failOn = {CALL_UNSAFE}) + public MyValue1 test31() { + if (TEST31_VT_FLATTENED) { + return U.getFlatValue(this, TEST31_VT_OFFSET, TEST31_VT_LAYOUT, MyValue1.class); + } + return (MyValue1)U.getReference(this, TEST31_VT_OFFSET); + } + + @Run(test = "test31") + public void test31_verifier() { + test31_vt = MyValue1.createWithFieldsInline(rI, rL); + MyValue1 res = test31(); + Asserts.assertEQ(res.hash(), test31_vt.hash()); + } + + // putValue to set flattened field in object + @Test + @IR(failOn = {CALL_UNSAFE}) + public void test32(MyValue1 vt) { + if (TEST31_VT_FLATTENED) { + U.putFlatValue(this, TEST31_VT_OFFSET, TEST31_VT_LAYOUT, MyValue1.class, vt); + } else { + U.putReference(this, TEST31_VT_OFFSET, vt); + } + } + + @Run(test = "test32") + public void test32_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + test31_vt = MyValue1.createDefaultInline(); + test32(vt); + Asserts.assertEQ(vt.hash(), test31_vt.hash()); + } + + private static final long TEST33_BASE_OFFSET; + private static final int TEST33_INDEX_SCALE; + private static final MyValue1[] TEST33_ARRAY; + private static final boolean TEST33_FLATTENED_ARRAY; + private static final int TEST33_LAYOUT; + static { + try { + TEST33_ARRAY = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 2, MyValue1.DEFAULT); + TEST33_BASE_OFFSET = U.arrayBaseOffset(TEST33_ARRAY); + TEST33_INDEX_SCALE = U.arrayIndexScale(TEST33_ARRAY); + TEST33_FLATTENED_ARRAY = ValueClass.isFlatArray(TEST33_ARRAY); + TEST33_LAYOUT = U.arrayLayout(TEST33_ARRAY); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + // getValue to retrieve flattened field from array + @Test + @IR(failOn = {CALL_UNSAFE}) + public MyValue1 test33() { + if (TEST33_FLATTENED_ARRAY) { + return U.getFlatValue(TEST33_ARRAY, TEST33_BASE_OFFSET + TEST33_INDEX_SCALE, TEST33_LAYOUT, MyValue1.class); + } + return (MyValue1)U.getReference(TEST33_ARRAY, TEST33_BASE_OFFSET + TEST33_INDEX_SCALE); + } + + @Run(test = "test33") + public void test33_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + TEST33_ARRAY[1] = vt; + MyValue1 res = test33(); + Asserts.assertEQ(res.hash(), vt.hash()); + } + + // putValue to set flattened field in array + @Test + @IR(failOn = {CALL_UNSAFE}) + public void test34(MyValue1 vt) { + if (TEST33_FLATTENED_ARRAY) { + U.putFlatValue(TEST33_ARRAY, TEST33_BASE_OFFSET + TEST33_INDEX_SCALE, TEST33_LAYOUT, MyValue1.class, vt); + } else { + U.putReference(TEST33_ARRAY, TEST33_BASE_OFFSET + TEST33_INDEX_SCALE, vt); + } + } + + @Run(test = "test34") + public void test34_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + test34(vt); + Asserts.assertEQ(TEST33_ARRAY[1].hash(), vt.hash()); + } + + // getValue to retrieve flattened field from object with unknown + // container type + @Test + @IR(failOn = {CALL_UNSAFE}) + public MyValue1 test35(Object o) { + if (TEST31_VT_FLATTENED) { + return U.getFlatValue(o, TEST31_VT_OFFSET, TEST31_VT_LAYOUT, MyValue1.class); + } + return (MyValue1)U.getReference(o, TEST31_VT_OFFSET); + } + + @Run(test = "test35") + public void test35_verifier() { + test31_vt = MyValue1.createWithFieldsInline(rI, rL); + MyValue1 res = test35(this); + Asserts.assertEQ(res.hash(), test31_vt.hash()); + } + + // getValue to retrieve flattened field from object at unknown + // offset + @Test + @IR(failOn = {CALL_UNSAFE}) + public MyValue1 test36(long offset) { + if (TEST31_VT_FLATTENED) { + return U.getFlatValue(this, offset, TEST31_VT_LAYOUT, MyValue1.class); + } + return (MyValue1)U.getReference(this, offset); + } + + @Run(test = "test36") + public void test36_verifier() { + test31_vt = MyValue1.createWithFieldsInline(rI, rL); + MyValue1 res = test36(TEST31_VT_OFFSET); + Asserts.assertEQ(res.hash(), test31_vt.hash()); + } + + // putValue to set flattened field in object with unknown + // container + @Test + @IR(failOn = {CALL_UNSAFE}) + public void test37(Object o, MyValue1 vt) { + if (TEST31_VT_FLATTENED) { + U.putFlatValue(o, TEST31_VT_OFFSET, TEST31_VT_LAYOUT, MyValue1.class, vt); + } else { + U.putReference(o, TEST31_VT_OFFSET, vt); + } + } + + @Run(test = "test37") + public void test37_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + test31_vt = MyValue1.createDefaultInline(); + test37(this, vt); + Asserts.assertEQ(vt.hash(), test31_vt.hash()); + } + + // putValue to set flattened field in object, non inline argument + // to store + @Test + @IR(failOn = {CALL_UNSAFE}) + public void test38(Object o) { + if (TEST31_VT_FLATTENED) { + U.putFlatValue(this, TEST31_VT_OFFSET, TEST31_VT_LAYOUT, MyValue1.class, o); + } else { + U.putReference(this, TEST31_VT_OFFSET, o); + } + } + + @Run(test = "test38") + public void test38_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + test31_vt = MyValue1.createDefaultInline(); + test38(vt); + Asserts.assertEQ(vt.hash(), test31_vt.hash()); + } + + @Test + @IR(failOn = {CALL_UNSAFE}) + public MyValue1 test39(MyValue1 v) { + v = U.makePrivateBuffer(v); + U.putInt(v, X_OFFSET, rI); + v = U.finishPrivateBuffer(v); + return v; + } + + @Run(test = "test39") + public void test39_verifier() { + MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); + MyValue1 res = test39(v.setX(v, 0)); + Asserts.assertEQ(res.hash(), v.hash()); + } + + // Test value class array creation via reflection + @Test + public Object[] test40(Class componentType, int len) { + Object[] va = (Object[])Array.newInstance(componentType, len); + return va; + } + + @Run(test = "test40") + public void test40_verifier() { + int len = Math.abs(rI) % 42; + Object[] va = test40(MyValue1.class, len); + for (int i = 0; i < len; ++i) { + Asserts.assertEQ(va[i], null); + } + } + + // Class.isInstance + @Test + public boolean test41(Class c, MyValue1 vt) { + return c.isInstance(vt); + } + + @Run(test = "test41") + public void test41_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + boolean result = test41(MyValue1.class, vt); + Asserts.assertTrue(result); + result = test41(MyValue1.class, null); + Asserts.assertFalse(result); + result = test41(MyValue1.class, vt); + Asserts.assertTrue(result); + result = test41(MyValue1.class, null); + Asserts.assertFalse(result); + } + + @Test + public boolean test42(Class c, MyValue1 vt) { + return c.isInstance(vt); + } + + @Run(test = "test42") + public void test42_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + boolean result = test42(MyValue2.class, vt); + Asserts.assertFalse(result); + result = test42(MyValue2.class, null); + Asserts.assertFalse(result); + result = test42(MyValue2.class, vt); + Asserts.assertFalse(result); + result = test42(MyValue2.class, null); + Asserts.assertFalse(result); + } + + // Class.cast + @Test + public Object test43(Class c, MyValue1 vt) { + return c.cast(vt); + } + + @Run(test = "test43") + public void test43_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + Object result = test43(MyValue1.class, vt); + Asserts.assertEQ(result, vt); + result = test43(MyValue1.class, null); + Asserts.assertEQ(result, null); + result = test43(MyValue1.class, vt); + Asserts.assertEQ(result, vt); + result = test43(NonValueClass.class, null); + Asserts.assertEQ(result, null); + } + + @Test + public Object test44(Class c, MyValue1 vt) { + return c.cast(vt); + } + + @Run(test = "test44") + public void test44_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + try { + test44(MyValue2.class, vt); + throw new RuntimeException("should have thrown"); + } catch (ClassCastException cce) { + } + Object res = test44(MyValue2.class, null); + Asserts.assertEQ(res, null); + try { + test44(MyValue2.class, vt); + throw new RuntimeException("should have thrown"); + } catch (ClassCastException cce) { + } + } + + @Test + public Object test45(MyValue1 vt) { + return MyValue1.class.cast(vt); + } + + @Run(test = "test45") + public void test45_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + Object result = test45(vt); + Asserts.assertEQ(((MyValue1)result).hash(), vt.hash()); + result = test45(null); + Asserts.assertEQ(result, null); + } + + @Test + public Object test46(MyValue1 vt) { + return MyValue2.class.cast(vt); + } + + @Run(test = "test46") + public void test46_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + Object result = test46(null); + Asserts.assertEQ(result, null); + try { + test46(vt); + throw new RuntimeException("should have thrown"); + } catch (ClassCastException cce) { + } + } + + @Test + public Object test47(MyValue1 vt) { + return MyValue1.class.cast(vt); + } + + @Run(test = "test47") + public void test47_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + Object result = test47(vt); + Asserts.assertEQ(((MyValue1)result).hash(), vt.hash()); + result = test47(null); + Asserts.assertEQ(result, null); + } + + @Test + public Object test48(Class c, MyValue1 vt) { + return c.cast(vt); + } + + @Run(test = "test48") + public void test48_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + Object result = test48(MyValue1.class, vt); + Asserts.assertEQ(((MyValue1)result).hash(), vt.hash()); + result = test48(MyValue1.class, null); + Asserts.assertEQ(result, null); + } + + @Test + public Object test49(MyValue1 vt) { + return MyValue1.class.cast(vt); + } + + @Run(test = "test49") + public void test49_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + Object result = test49(vt); + Asserts.assertEQ(((MyValue1)result).hash(), vt.hash()); + } + + @Test + public Object test50(Class c, Object obj) { + return c.cast(obj); + } + + @Run(test = "test50") + public void test50_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 42, MyValue1.DEFAULT); + MyValue1[] vba = new MyValue1[42]; + Object result = test50(MyValue1.class, vt); + Asserts.assertEQ(((MyValue1)result).hash(), vt.hash()); + result = test50(MyValue1.class, vt); + Asserts.assertEQ(((MyValue1)result).hash(), vt.hash()); + result = test50(MyValue1[].class, va); + Asserts.assertEQ(result, va); + result = test50(MyValue1[].class, vba); + Asserts.assertEQ(result, vba); + result = test50(MyValue1[].class, va); + Asserts.assertEQ(result, va); + result = test50(MyValue1.class, null); + Asserts.assertEQ(result, null); + result = test50(va.getClass(), vba); + Asserts.assertEQ(result, vba); + } + + // Value class array creation via reflection + @Test + public void test51(int len) { + Object[] va = (Object[])Array.newInstance(MyValue1.class, len); + for (int i = 0; i < len; ++i) { + Asserts.assertEQ(va[i], null); + } + } + + @Run(test = "test51") + public void test51_verifier() { + int len = Math.abs(rI) % 42; + test51(len); + } + + // multidimensional value class array creation via reflection + @Test + public Object[][] test52(int len, int val) { + MyValue1[][] va1 = (MyValue1[][])Array.newInstance(MyValue1[].class, len); + MyValue1[][] va2 = (MyValue1[][])Array.newInstance(MyValue1[].class, len); + Object[][] result; + if (val == 1) { + va1[0] = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + result = va1; + } else { + va2[0] = new MyValue1[1]; + result = va2; + } + if (val == 1) { + Asserts.assertEQ(va1[0][0].hash(), ((MyValue1)result[0][0]).hash()); + } else { + Asserts.assertEQ(result[0][0], null); + result[0][0] = null; + } + return result; + } + + @Run(test = "test52") + public void test52_verifier() { + test52(1, 1); + test52(1, 2); + } + + @Test + public Object[][] test53(Class c1, Class c2, int len, int val) { + MyValue1[][] va1 = (MyValue1[][])Array.newInstance(MyValue1[].class, len); + MyValue1[][] va2 = (MyValue1[][])Array.newInstance(MyValue1[].class, len); + Object[][] va3 = (Object[][])Array.newInstance(c1, len); + Object[][] va4 = (Object[][])Array.newInstance(c2, len); + for (int i = 0; i < len; ++i) { + Asserts.assertEQ(va1[i], null); + Asserts.assertEQ(va2[i], null); + Asserts.assertEQ(va3[i], null); + Asserts.assertEQ(va4[i], null); + va1[i] = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + va2[i] = new MyValue1[1]; + va3[i] = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + va4[i] = new MyValue1[1]; + Asserts.assertEQ(va1[i][0].hash(), ((MyValue1)va3[i][0]).hash()); + Asserts.assertEQ(va2[i][0], null); + Asserts.assertEQ(va4[i][0], null); + } + Object[][] result; + if (val == 1) { + result = va1; + } else if (val == 2) { + result = va2; + } else if (val == 3) { + result = va3; + } else { + result = va4; + } + if ((val == 1 || val == 3) && len > 0) { + Asserts.assertEQ(va1[0][0].hash(), ((MyValue1)result[0][0]).hash()); + } else if (len > 0) { + Asserts.assertEQ(result[0][0], null); + result[0][0] = null; + } + return result; + } + + @Run(test = "test53") + public void test53_verifier() { + int len = Math.abs(rI) % 42; + test53(MyValue1[].class, MyValue1[].class, len, 1); + test53(MyValue1[].class, MyValue1[].class, len, 2); + test53(MyValue1[].class, MyValue1[].class, len, 3); + test53(MyValue1[].class, MyValue1[].class, len, 4); + } + + @Strict + @NullRestricted + static final MyValue1 test55_vt = MyValue1.createWithFieldsInline(rI, rL); + + // Same as test30 but with constant field holder + @Test + @IR(failOn = {CALL_UNSAFE}) + public MyValue2 test55() { + if (V1_FLATTENED) { + return U.getFlatValue(test55_vt, V1_OFFSET, V1_LAYOUT, MyValue2.class); + } + return (MyValue2)U.getReference(test55_vt, V1_OFFSET); + } + + @Run(test = "test55") + public void test55_verifier() { + MyValue2 res = test55(); + Asserts.assertEQ(res.hash(), test55_vt.v1.hash()); + } + + // Test OptimizePtrCompare part of Escape Analysis + @Test + public void test56(int idx) { + Object[] va = ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + if (va[idx] == null) { + throw new RuntimeException("Unexpected null"); + } + } + + @Run(test = "test56") + public void test56_verifier() { + test56(0); + } + + // Same as test56 but with load from known array index + @Test + public void test57() { + Object[] va = ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + if (va[0] == null) { + throw new RuntimeException("Unexpected null"); + } + } + + @Run(test = "test57") + public void test57_verifier() { + test57(); + } + + // Use a value class without strict fields for Unsafe allocation in below tests + // because without the constructor, these fields won't be initialized with is illegal. + static value class ValueWithNoStrictFields1 { + int x = rI; + } + + static value class ValueWithNoStrictFields2 { + long x = rL; + } + + // Test unsafe allocation + @Test + public boolean test58(Class c1, Class c2) throws Exception { + Object obj1 = U.allocateInstance(c1); + Object obj2 = U.allocateInstance(c2); + return obj1 == obj2; + } + + @Run(test = "test58") + public void test58_verifier() throws Exception { + boolean res = test58(ValueWithNoStrictFields1.class, ValueWithNoStrictFields1.class); + Asserts.assertTrue(res); + res = test58(Object.class, ValueWithNoStrictFields1.class); + Asserts.assertFalse(res); + res = test58(ValueWithNoStrictFields1.class, Object.class); + Asserts.assertFalse(res); + } + + // Test synchronization on unsafe value object allocation + @Test + public void test59(Class c) throws Exception { + Object obj = U.allocateInstance(c); + synchronized (obj) { + + } + } + + @Run(test = "test59") + public void test59_verifier() throws Exception { + test59(Object.class); + try { + test59(ValueWithNoStrictFields1.class); + throw new RuntimeException("test59 failed: synchronization on value object should not succeed"); + } catch (IdentityException e) { + + } + } + + // Test mark word load optimization on unsafe value object allocation + @Test + public boolean test60(Class c1, Class c2, boolean b1, boolean b2) throws Exception { + Object obj1 = b1 ? new Object() : U.allocateInstance(c1); + Object obj2 = b2 ? new Object() : U.allocateInstance(c2); + return obj1 == obj2; + } + + @Run(test = "test60") + public void test60_verifier() throws Exception { + Asserts.assertTrue(test60(ValueWithNoStrictFields1.class, ValueWithNoStrictFields1.class, false, false)); + Asserts.assertFalse(test60(ValueWithNoStrictFields1.class, ValueWithNoStrictFields2.class, false, false)); + Asserts.assertFalse(test60(ValueWithNoStrictFields1.class, ValueWithNoStrictFields1.class, false, true)); + Asserts.assertFalse(test60(ValueWithNoStrictFields1.class, ValueWithNoStrictFields1.class, true, false)); + Asserts.assertFalse(test60(ValueWithNoStrictFields1.class, ValueWithNoStrictFields1.class, true, true)); + } + + static public value class SmallValue { + byte a; + byte b; + static final SmallValue DEFAULT = createDefaultInline(); + SmallValue(byte a, byte b) { + this.a = a; + this.b = b; + } + + @ForceInline + static SmallValue createDefaultInline() { + return new SmallValue((byte)0, (byte)0); + } + + @ForceInline + static SmallValue createWithFieldsInline(int x, long y) { + return new SmallValue((byte)x, (byte)y); + } + } + + SmallValue test63_vt; + private static final long TEST63_VT_OFFSET; + private static final boolean TEST63_VT_FLATTENED; + private static final int TEST63_VT_LAYOUT; + static { + try { + Field test63_vt_Field = TestIntrinsics.class.getDeclaredField("test63_vt"); + TEST63_VT_OFFSET = U.objectFieldOffset(test63_vt_Field); + TEST63_VT_FLATTENED = U.isFlatField(test63_vt_Field); + TEST63_VT_LAYOUT = U.fieldLayout(test63_vt_Field); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + // compareAndSet to flattened field in object + @Test + public boolean test63(SmallValue oldVal, SmallValue newVal) { + if (TEST63_VT_FLATTENED) { + Asserts.assertTrue(UseFieldFlattening && PreloadClasses); + return U.compareAndSetFlatValue(this, TEST63_VT_OFFSET, TEST63_VT_LAYOUT, SmallValue.class, oldVal, newVal); + } else { + Asserts.assertFalse(UseFieldFlattening && PreloadClasses); + return U.compareAndSetReference(this, TEST63_VT_OFFSET, oldVal, newVal); + } + } + + @Run(test = "test63") + public void test63_verifier() { + // Unsafe::compareAndSetFlatValue needs UseArrayFlattening. + if (UseFieldFlattening && !UseArrayFlattening) return; + SmallValue vt = SmallValue.createWithFieldsInline(rI, rL); + test63_vt = SmallValue.createDefaultInline(); + + boolean res = test63(test63_vt, vt); + // Checks are disabled for non-flattened field because reference comparison + // fails if C2 scalarizes and re-allocates the value class arguments. + if (TEST63_VT_FLATTENED) { + Asserts.assertTrue(res); + Asserts.assertEQ(test63_vt, vt); + } + + res = test63(SmallValue.createDefaultInline(), SmallValue.createDefaultInline()); + if (TEST63_VT_FLATTENED) { + Asserts.assertFalse(res); + Asserts.assertEQ(test63_vt, vt); + } + } + + private static final long TEST64_BASE_OFFSET; + private static final int TEST64_INDEX_SCALE; + private static final SmallValue[] TEST64_ARRAY; + private static final boolean TEST64_FLATTENED_ARRAY; + private static final boolean TEST64_ATOMIC_ARRAY; + private static final int TEST64_LAYOUT; + static { + try { + TEST64_ARRAY = (SmallValue[])ValueClass.newNullRestrictedAtomicArray(SmallValue.class, 2, SmallValue.DEFAULT); + TEST64_BASE_OFFSET = U.arrayBaseOffset(TEST64_ARRAY); + TEST64_INDEX_SCALE = U.arrayIndexScale(TEST64_ARRAY); + TEST64_FLATTENED_ARRAY = ValueClass.isFlatArray(TEST64_ARRAY); + TEST64_ATOMIC_ARRAY = ValueClass.isAtomicArray(TEST64_ARRAY); + TEST64_LAYOUT = U.arrayLayout(TEST64_ARRAY); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + // compareAndSet to flattened field in array + @Test + public boolean test64(SmallValue[] arr, SmallValue oldVal, SmallValue newVal) { + if (TEST64_FLATTENED_ARRAY) { + Asserts.assertTrue(UseArrayFlattening); + return U.compareAndSetFlatValue(arr, TEST64_BASE_OFFSET + TEST64_INDEX_SCALE, TEST64_LAYOUT, SmallValue.class, oldVal, newVal); + } else { + Asserts.assertFalse(UseArrayFlattening); + return U.compareAndSetReference(arr, TEST64_BASE_OFFSET + TEST64_INDEX_SCALE, oldVal, newVal); + } + } + + @Run(test = "test64") + public void test64_verifier() { + Asserts.assertTrue(TEST64_ATOMIC_ARRAY); + SmallValue[] arr = (SmallValue[])ValueClass.newNullRestrictedAtomicArray(SmallValue.class, 2, SmallValue.DEFAULT); + SmallValue vt = SmallValue.createWithFieldsInline(rI, rL); + + boolean res = test64(arr, arr[1], vt); + // Checks are disabled for non-flattened array because reference comparison + // fails if C2 scalarizes and re-allocates the value class arguments. + if (TEST64_FLATTENED_ARRAY) { + Asserts.assertTrue(res); + Asserts.assertEQ(arr[1], vt); + } + + res = test64(arr, SmallValue.createDefaultInline(), SmallValue.createDefaultInline()); + if (TEST64_FLATTENED_ARRAY) { + Asserts.assertFalse(res); + Asserts.assertEQ(arr[1], vt); + } + } + + // compareAndSet to flattened field in object with unknown container + @Test + public boolean test65(Object o, Object oldVal, SmallValue newVal) { + if (TEST63_VT_FLATTENED) { + Asserts.assertTrue(UseFieldFlattening && PreloadClasses); + return U.compareAndSetFlatValue(o, TEST63_VT_OFFSET, TEST63_VT_LAYOUT, SmallValue.class, oldVal, newVal); + } else { + Asserts.assertFalse(UseFieldFlattening && PreloadClasses); + return U.compareAndSetReference(o, TEST63_VT_OFFSET, oldVal, newVal); + } + } + + @Run(test = "test65") + public void test65_verifier() { + // Unsafe::compareAndSetFlatValue needs UseArrayFlattening. + if (UseFieldFlattening && !UseArrayFlattening) return; + SmallValue vt = SmallValue.createWithFieldsInline(rI, rL); + test63_vt = SmallValue.createDefaultInline(); + + boolean res = test65(this, test63_vt, vt); + Asserts.assertTrue(res); + Asserts.assertEQ(test63_vt, vt); + + res = test65(this, SmallValue.createDefaultInline(), SmallValue.createDefaultInline()); + Asserts.assertFalse(res); + Asserts.assertEQ(test63_vt, vt); + } + + // compareAndSet to flattened field in object, non-inline arguments to compare and set + @Test + public boolean test66(Object oldVal, Object newVal) { + if (TEST63_VT_FLATTENED) { + Asserts.assertTrue(UseFieldFlattening && PreloadClasses); + return U.compareAndSetFlatValue(this, TEST63_VT_OFFSET, TEST63_VT_LAYOUT, SmallValue.class, oldVal, newVal); + } else { + Asserts.assertFalse(UseFieldFlattening && PreloadClasses); + return U.compareAndSetReference(this, TEST63_VT_OFFSET, oldVal, newVal); + } + } + + @Run(test = "test66") + public void test66_verifier() { + // Unsafe::compareAndSetFlatValue needs UseArrayFlattening. + if (UseFieldFlattening && !UseArrayFlattening) return; + SmallValue vt = SmallValue.createWithFieldsInline(rI, rL); + test63_vt = SmallValue.createDefaultInline(); + + boolean res = test66(test63_vt, vt); + Asserts.assertTrue(res); + Asserts.assertEQ(test63_vt, vt); + + res = test66(SmallValue.createDefaultInline(), SmallValue.createDefaultInline()); + Asserts.assertFalse(res); + Asserts.assertEQ(test63_vt, vt); + } + + // compareAndExchange to flattened field in object + @Test + public Object test67(SmallValue oldVal, SmallValue newVal) { + if (TEST63_VT_FLATTENED) { + Asserts.assertTrue(UseFieldFlattening && PreloadClasses); + return U.compareAndExchangeFlatValue(this, TEST63_VT_OFFSET, TEST63_VT_LAYOUT, SmallValue.class, oldVal, newVal); + } else { + Asserts.assertFalse(UseFieldFlattening && PreloadClasses); + return U.compareAndExchangeReference(this, TEST63_VT_OFFSET, oldVal, newVal); + } + } + + @Run(test = "test67") + public void test67_verifier() { + // Unsafe::compareAndExchangeFlatValue needs UseArrayFlattening. + if (UseFieldFlattening && !UseArrayFlattening) return; + SmallValue vt = SmallValue.createWithFieldsInline(rI, rL); + SmallValue oldVal = SmallValue.createDefaultInline(); + test63_vt = oldVal; + + Object res = test67(test63_vt, vt); + // Checks are disabled for non-flattened field because reference comparison + // fails if C2 scalarizes and re-allocates the value class arguments. + if (TEST63_VT_FLATTENED) { + Asserts.assertEQ(res, oldVal); + Asserts.assertEQ(test63_vt, vt); + } + + res = test67(SmallValue.createDefaultInline(), SmallValue.createDefaultInline()); + if (TEST63_VT_FLATTENED) { + Asserts.assertEQ(res, vt); + Asserts.assertEQ(test63_vt, vt); + } + } + + // compareAndExchange to flattened field in array + @Test + public Object test68(SmallValue[] arr, SmallValue oldVal, Object newVal) { + if (TEST64_FLATTENED_ARRAY) { + Asserts.assertTrue(UseArrayFlattening); + return U.compareAndExchangeFlatValue(arr, TEST64_BASE_OFFSET + TEST64_INDEX_SCALE, TEST64_LAYOUT, SmallValue.class, oldVal, newVal); + } else { + Asserts.assertFalse(UseArrayFlattening); + return U.compareAndExchangeReference(arr, TEST64_BASE_OFFSET + TEST64_INDEX_SCALE, oldVal, newVal); + } + } + + @Run(test = "test68") + public void test68_verifier() { + Asserts.assertTrue(TEST64_ATOMIC_ARRAY); + SmallValue[] arr = (SmallValue[])ValueClass.newNullRestrictedAtomicArray(SmallValue.class, 2, SmallValue.DEFAULT); + SmallValue vt = SmallValue.createWithFieldsInline(rI, rL); + + Object res = test68(arr, arr[1], vt); + // Checks are disabled for non-flattened array because reference comparison + // fails if C2 scalarizes and re-allocates the value class arguments. + if (TEST64_FLATTENED_ARRAY) { + Asserts.assertEQ(res, SmallValue.createDefaultInline()); + Asserts.assertEQ(arr[1], vt); + } + + res = test68(arr, SmallValue.createDefaultInline(), SmallValue.createDefaultInline()); + if (TEST64_FLATTENED_ARRAY) { + Asserts.assertEQ(res, vt); + Asserts.assertEQ(arr[1], vt); + } + } + + // compareAndExchange to flattened field in object with unknown container + @Test + public Object test69(Object o, Object oldVal, SmallValue newVal) { + if (TEST63_VT_FLATTENED) { + Asserts.assertTrue(UseFieldFlattening && PreloadClasses); + return U.compareAndExchangeFlatValue(o, TEST63_VT_OFFSET, TEST63_VT_LAYOUT, SmallValue.class, oldVal, newVal); + } else { + Asserts.assertFalse(UseFieldFlattening && PreloadClasses); + return U.compareAndExchangeReference(o, TEST63_VT_OFFSET, oldVal, newVal); + } + } + + @Run(test = "test69") + public void test69_verifier() { + // Unsafe::compareAndExchangeFlatValue needs UseArrayFlattening. + if (UseFieldFlattening && !UseArrayFlattening) return; + SmallValue vt = SmallValue.createWithFieldsInline(rI, rL); + SmallValue oldVal = SmallValue.createDefaultInline(); + test63_vt = oldVal; + + Object res = test69(this, test63_vt, vt); + Asserts.assertEQ(res, oldVal); + Asserts.assertEQ(test63_vt, vt); + + res = test69(this, SmallValue.createDefaultInline(), SmallValue.createDefaultInline()); + Asserts.assertEQ(res, vt); + Asserts.assertEQ(test63_vt, vt); + } + + // compareAndExchange to flattened field in object, non-inline arguments to compare and set + @Test + public Object test70(Object oldVal, Object newVal) { + if (TEST63_VT_FLATTENED) { + Asserts.assertTrue(UseFieldFlattening && PreloadClasses); + return U.compareAndExchangeFlatValue(this, TEST63_VT_OFFSET, TEST63_VT_LAYOUT, SmallValue.class, oldVal, newVal); + } else { + Asserts.assertFalse(UseFieldFlattening && PreloadClasses); + return U.compareAndExchangeReference(this, TEST63_VT_OFFSET, oldVal, newVal); + } + } + + @Run(test = "test70") + public void test70_verifier() { + // Unsafe::compareAndExchangeFlatValue needs UseArrayFlattening. + if (UseFieldFlattening && !UseArrayFlattening) return; + SmallValue vt = SmallValue.createWithFieldsInline(rI, rL); + SmallValue oldVal = SmallValue.createDefaultInline(); + test63_vt = oldVal; + + Object res = test70(test63_vt, vt); + Asserts.assertEQ(res, oldVal); + Asserts.assertEQ(test63_vt, vt); + + res = test70(SmallValue.createDefaultInline(), SmallValue.createDefaultInline()); + Asserts.assertEQ(res, vt); + Asserts.assertEQ(test63_vt, vt); + } + + // getValue to retrieve flattened field from (nullable) value class + @Test + @IR(failOn = {CALL_UNSAFE}) + public MyValue2 test71(boolean b, MyValue1 v1, MyValue1 v2) { + if (b) { + if (V1_FLATTENED) { + return U.getFlatValue(v1, V1_OFFSET, V1_LAYOUT, MyValue2.class); + } + return (MyValue2)U.getReference(v1, V1_OFFSET); + } else { + if (V1_FLATTENED) { + return U.getFlatValue(v2, V1_OFFSET, V1_LAYOUT, MyValue2.class); + } + return (MyValue2)U.getReference(v2, V1_OFFSET); + } + } + + @Run(test = "test71") + public void test71_verifier() { + MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); + Asserts.assertEQ(test71(true, v, v), v.v1); + Asserts.assertEQ(test71(false, v, v), v.v1); + } + + // Same as test71 but with non-constant offset + @Test + @IR(failOn = {CALL_UNSAFE}) + public MyValue2 test72(boolean b, MyValue1 v1, MyValue1 v2, long offset) { + if (b) { + if (V1_FLATTENED) { + return U.getFlatValue(v1, offset, V1_LAYOUT, MyValue2.class); + } + return (MyValue2)U.getReference(v1, offset); + } else { + if (V1_FLATTENED) { + return U.getFlatValue(v2, offset, V1_LAYOUT, MyValue2.class); + } + return (MyValue2)U.getReference(v2, offset); + } + } + + @Run(test = "test72") + public void test72_verifier() { + MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); + Asserts.assertEQ(test72(true, v, v, V1_OFFSET), v.v1); + Asserts.assertEQ(test72(false, v, v, V1_OFFSET), v.v1); + } + + @Strict + @NullRestricted + static final MyValue1 test73_value1 = MyValue1.createWithFieldsInline(rI, rL); + static final MyValue1 test73_value2 = MyValue1.createWithFieldsInline(rI+1, rL+1); + + // Same as test72 but with constant base + @Test + @IR(failOn = {CALL_UNSAFE}) + public MyValue2 test73(boolean b, long offset) { + if (b) { + if (V1_FLATTENED) { + return U.getFlatValue(test73_value1, offset, V1_LAYOUT, MyValue2.class); + } + return (MyValue2)U.getReference(test73_value1, offset); + } else { + if (V1_FLATTENED) { + return U.getFlatValue(test73_value2, offset, V1_LAYOUT, MyValue2.class); + } + return (MyValue2)U.getReference(test73_value2, offset); + } + } + + @Run(test = "test73") + public void test73_verifier() { + Asserts.assertEQ(test73(true, V1_OFFSET), test73_value1.v1); + Asserts.assertEQ(test73(false, V1_OFFSET), test73_value2.v1); + } + + @LooselyConsistentValue + static value class EmptyInline { + + } + + @LooselyConsistentValue + static value class ByteInline { + byte x = 0; + } + + @Test + public void test74(EmptyInline[] emptyArray) { + System.arraycopy(emptyArray, 0, emptyArray, 10, 10); + System.arraycopy(emptyArray, 0, emptyArray, 20, 10); + } + + @Run(test = "test74") + public void test74_verifier() { + EmptyInline[] emptyArray = (EmptyInline[])ValueClass.newNullRestrictedNonAtomicArray(EmptyInline.class, 100, new EmptyInline()); + test74(emptyArray); + for (EmptyInline empty : emptyArray) { + Asserts.assertEQ(empty, new EmptyInline()); + } + } + + @Test + public void test75(EmptyInline[] emptyArray) { + System.arraycopy(emptyArray, 0, emptyArray, 10, 10); + } + + @Run(test = "test75") + public void test75_verifier() { + EmptyInline[] emptyArray = (EmptyInline[])ValueClass.newNullRestrictedNonAtomicArray(EmptyInline.class, 100, new EmptyInline()); + test75(emptyArray); + for (EmptyInline empty : emptyArray) { + Asserts.assertEQ(empty, new EmptyInline()); + } + } + + @Test + public void test76(ByteInline[] byteArray) { + System.arraycopy(byteArray, 0, byteArray, 10, 10); + System.arraycopy(byteArray, 0, byteArray, 20, 10); + } + + @Run(test = "test76") + public void test76_verifier() { + ByteInline[] byteArray = (ByteInline[])ValueClass.newNullRestrictedNonAtomicArray(ByteInline.class, 100, new ByteInline()); + test76(byteArray); + for (ByteInline b : byteArray) { + Asserts.assertEQ(b, new ByteInline()); + } + } + + @Test + public void test77(ByteInline[] byteArray) { + System.arraycopy(byteArray, 0, byteArray, 10, 10); + } + + @Run(test = "test77") + public void test77_verifier() { + ByteInline[] byteArray = (ByteInline[])ValueClass.newNullRestrictedNonAtomicArray(ByteInline.class, 100, new ByteInline()); + test77(byteArray); + for (ByteInline b : byteArray) { + Asserts.assertEQ(b, new ByteInline()); + } + } + + @Test + public Object test78(MyValue1 vt) { + return NonValueClass.class.cast(vt); + } + + @Run(test = "test78") + public void test78_verifier() { + Object result = test78(null); + Asserts.assertEQ(result, null); + try { + test78(MyValue1.createWithFieldsInline(rI, rL)); + throw new RuntimeException("should have thrown"); + } catch (ClassCastException cce) { + } + } + + @Test + public Object test79(MyValue1 vt) { + Object tmp = vt; + return (NonValueClass)tmp; + } + + @Run(test = "test79") + public void test79_verifier() { + Object result = test79(null); + Asserts.assertEQ(result, null); + try { + test79(MyValue1.createWithFieldsInline(rI, rL)); + throw new RuntimeException("should have thrown"); + } catch (ClassCastException cce) { + } + } + + @LooselyConsistentValue + public static value class Test80Value1 { + @Strict + @NullRestricted + Test80Value2 v = new Test80Value2(); + } + + @LooselyConsistentValue + public static value class Test80Value2 { + long l = rL; + NonValueClass obj = new NonValueClass(rI); + } + + // layout is not a constant + @Test + @IR(counts = {CALL_UNSAFE, "1"}) + public Test80Value2 test80(Test80Value1 v, boolean flat, int layout, long offset) { + if (flat) { + return U.getFlatValue(v, offset, layout, Test80Value2.class); + } else { + return (Test80Value2)U.getReference(v, offset); + } + } + + @Run(test = "test80") + public void test80_verifier() throws Exception { + Test80Value1 v = new Test80Value1(); + Field field = Test80Value1.class.getDeclaredField("v"); + Asserts.assertEQ(test80(v, U.isFlatField(field), U.fieldLayout(field), U.objectFieldOffset(field)), v.v); + } + + // Test correctness of the ValueClass::isFlatArray intrinsic + @Test + public boolean test81(Object array) { + return ValueClass.isFlatArray(array); + } + + @Run(test = "test81") + public void test81_verifier() { + Asserts.assertEQ(test81(TEST33_ARRAY), TEST33_FLATTENED_ARRAY, "test81_1 failed"); + Asserts.assertFalse(test81(new String[0]), "test81_2 failed"); + Asserts.assertFalse(test81("test"), "test81_3 failed"); + Asserts.assertFalse(test81(new int[0]), "test81_4 failed"); + } + + // Verify that ValueClass::isFlatArray checks with statically known classes + // are folded + @Test + @IR(failOn = {LOAD_KLASS}) + public boolean test82() { + boolean check1 = ValueClass.isFlatArray(TEST33_ARRAY); + if (!TEST33_FLATTENED_ARRAY) { + check1 = !check1; + } + boolean check2 = !ValueClass.isFlatArray(new String[0]); + boolean check3 = !ValueClass.isFlatArray("test"); + boolean check4 = !ValueClass.isFlatArray(new int[0]); + return check1 && check2 && check3 && check4; + } + + @Run(test = "test82") + public void test82_verifier() { + Asserts.assertTrue(test82(), "test82 failed"); + } + + // Test that LibraryCallKit::arraycopy_move_allocation_here works as expected + @Test + public MyValue1 test83(Object[] src) { + MyValue1[] dst = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 10, MyValue1.DEFAULT); + System.arraycopy(src, 0, dst, 0, 10); + return dst[0]; + } + + @Run(test = "test83") + public void test83_verifier(RunInfo info) { + if (info.isWarmUp()) { + MyValue1[] src = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 10, MyValue1.DEFAULT); + Asserts.assertEQ(test83(src), src[0]); + } else { + // Trigger deoptimization to verify that re-execution works + try { + test83(new NonValueClass[10]); + throw new RuntimeException("No NullPointerException thrown"); + } catch (NullPointerException npe) { + // Expected + } + } + } + + /* TODO: 8322547: Unsafe::putInt checks the larval bit which leads to a VM crash + @Test + @IR(failOn = {CALL_UNSAFE}) + public MyValue1 test84(MyValue1 v) { + v = U.makePrivateBuffer(v); + for (int i = 0; i < 10; i++) { + U.putInt(v, X_OFFSET, i); + } + U.putInt(v, X_OFFSET, rI); + v = U.finishPrivateBuffer(v); + return v; + } + + @Run(test = "test84") + public void test84_verifier() { + MyValue1 v1 = MyValue1.createWithFieldsInline(rI, rL); + MyValue1 v2 = test84(MyValue1.setX(v1, 0)); + Asserts.assertEQ(v1.hash(), v2.hash()); + } + */ + + static value class MyValueClonable implements Cloneable { + int x; + + MyValueClonable(int x) { + this.x = x; + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + } + + @Test + @IR(counts = {IRNode.ALLOC, "1"}) + public Object testClone() throws CloneNotSupportedException { + MyValueClonable obj = new MyValueClonable(3); + return obj.clone(); + } + + @Run(test = "testClone") + public void testClone_verifier() { + try { + testClone(); + } catch (Exception e) { + Asserts.fail("testClone() failed", e); + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestIsSubstitutableReresolution.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestIsSubstitutableReresolution.java new file mode 100644 index 00000000000..61c19eba2f0 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestIsSubstitutableReresolution.java @@ -0,0 +1,61 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import jdk.test.lib.Asserts; + +/** + * @test + * @bug 8234108 + * @library /testlibrary /test/lib + * @summary Verify that call reresolution works for C2 compiled calls to java.lang.runtime.ValueObjectMethods::isSubstitutable0. + * @enablePreview + * @run main/othervm -XX:CompileCommand=dontinline,compiler.valhalla.inlinetypes.TestIsSubstitutableReresolution::test + * compiler.valhalla.inlinetypes.TestIsSubstitutableReresolution + */ + +value class MyValue { + int x; + + public MyValue(int x) { + this.x = x; + } +} + +public class TestIsSubstitutableReresolution { + + static boolean test(Object obj) { + MyValue vt = new MyValue(42); + return vt == obj; + } + + public static void main(String[] args) throws Exception { + MyValue vt1 = new MyValue(42); + MyValue vt2 = new MyValue(43); + for (int i = 0; i < 1_000_000; ++i) { + Asserts.assertEQ(test(vt1), true); + Asserts.assertEQ(test(vt2), false); + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestJNICalls.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestJNICalls.java new file mode 100644 index 00000000000..e780d765fca --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestJNICalls.java @@ -0,0 +1,120 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import jdk.test.lib.Asserts; +import compiler.lib.ir_framework.*; + +import static compiler.valhalla.inlinetypes.InlineTypes.rI; +import static compiler.valhalla.inlinetypes.InlineTypes.rL; + +/* + * @test + * @key randomness + * @summary Test calling native methods with value class arguments from compiled code. + * @library /test/lib / + * @requires (os.simpleArch == "x64" | os.simpleArch == "aarch64") + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm/timeout=300 compiler.valhalla.inlinetypes.TestJNICalls + */ + +@ForceCompileClassInitializer +public class TestJNICalls { + + public static void main(String[] args) { + Scenario[] scenarios = InlineTypes.DEFAULT_SCENARIOS; + + InlineTypes.getFramework() + .addScenarios(scenarios) + .addHelperClasses(MyValue1.class) + .start(); + } + + static { + System.loadLibrary("TestJNICalls"); + } + + public native Object testMethod1(MyValue1 o); + public native long testMethod2(MyValue1 o); + + // Pass a value object to a native method that calls back into Java code and returns a value object + @Test + public MyValue1 test1(MyValue1 vt, boolean callback) { + if (!callback) { + return (MyValue1)testMethod1(vt); + } else { + return vt; + } + } + + @Run(test = "test1") + @Warmup(10000) // Make sure native method is compiled + public void test1_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + MyValue1 result = test1(vt, false); + Asserts.assertEQ(result.hash(), vt.hash()); + result = test1(vt, true); + Asserts.assertEQ(result.hash(), vt.hash()); + } + + // Pass a value object to a native method that calls the hash method and returns the result + @Test + public long test2(MyValue1 vt) { + return testMethod2(vt); + } + + @Run(test = "test2") + @Warmup(10000) // Make sure native method is compiled + public void test2_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + long result = test2(vt); + Asserts.assertEQ(result, vt.hash()); + } + + static value class MyValueWithNative { + public int x; + + private MyValueWithNative(int x) { + this.x = x; + } + + public native int testMethod3(); + } + + // Call a native method with a value class receiver + @Test + public int test3(MyValueWithNative vt) { + return vt.testMethod3(); + } + + @Run(test = "test3") + @Warmup(10000) // Make sure native method is compiled + public void test3_verifier() { + MyValueWithNative vt = new MyValueWithNative(rI); + int result = test3(vt); + Asserts.assertEQ(result, rI); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestLWorld.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestLWorld.java new file mode 100644 index 00000000000..c2318c8179e --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestLWorld.java @@ -0,0 +1,4685 @@ +/* + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.*; +import jdk.test.lib.Asserts; +import test.java.lang.invoke.lib.InstructionHelper; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Objects; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.ALLOC_OF_MYVALUE_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.INLINE_ARRAY_NULL_GUARD; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.LOAD_OF_ANY_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.LOAD_UNKNOWN_INLINE; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.STORE_OF_ANY_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.STORE_UNKNOWN_INLINE; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.SUBSTITUTABILITY_TEST; +import static compiler.valhalla.inlinetypes.InlineTypes.*; + +import static compiler.lib.ir_framework.IRNode.ALLOC; +import static compiler.lib.ir_framework.IRNode.CLASS_CHECK_TRAP; +import static compiler.lib.ir_framework.IRNode.COUNTED_LOOP; +import static compiler.lib.ir_framework.IRNode.COUNTED_LOOP_MAIN; +import static compiler.lib.ir_framework.IRNode.FIELD_ACCESS; +import static compiler.lib.ir_framework.IRNode.LOAD_OF_CLASS; +import static compiler.lib.ir_framework.IRNode.LOOP; +import static compiler.lib.ir_framework.IRNode.MEMBAR; +import static compiler.lib.ir_framework.IRNode.NULL_CHECK_TRAP; +import static compiler.lib.ir_framework.IRNode.PREDICATE_TRAP; +import static compiler.lib.ir_framework.IRNode.STORE_OF_CLASS; +import static compiler.lib.ir_framework.IRNode.UNSTABLE_IF_TRAP; + +/* + * @test + * @key randomness + * @summary Test inline types in LWorld. + * @library /test/lib /test/jdk/java/lang/invoke/common / + * @requires (os.simpleArch == "x64" | os.simpleArch == "aarch64") + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @build test.java.lang.invoke.lib.InstructionHelper + * @run main/othervm/timeout=600 compiler.valhalla.inlinetypes.TestLWorld + */ + +@ForceCompileClassInitializer +public class TestLWorld { + + public static void main(String[] args) { + // Make sure Test140Value is loaded but not linked + Class class1 = Test140Value.class; + // Make sure Test141Value is linked but not initialized + Class class2 = Test141Value.class; + class2.getDeclaredFields(); + + Scenario[] scenarios = InlineTypes.DEFAULT_SCENARIOS; + scenarios[3].addFlags("-XX:-MonomorphicArrayCheck", "-XX:+UseArrayFlattening"); + scenarios[4].addFlags("-XX:-MonomorphicArrayCheck"); + + InlineTypes.getFramework() + .addScenarios(scenarios) + .addHelperClasses(MyValue1.class, + MyValue2.class, + MyValue2Inline.class, + MyValue3.class, + MyValue3Inline.class) + .start(); + } + + static { + // Make sure RuntimeException is loaded to prevent uncommon traps in IR verified tests + RuntimeException tmp = new RuntimeException("42"); + } + + // Helper methods + + @Strict + @NullRestricted + private static final MyValue1 testValue1 = MyValue1.createWithFieldsInline(rI, rL); + @Strict + @NullRestricted + private static final MyValue2 testValue2 = MyValue2.createWithFieldsInline(rI, rD); + + protected long hash() { + return testValue1.hash(); + } + + // Test passing an inline type as an Object + @DontInline + public Object test1_dontinline1(Object o) { + return o; + } + + @DontInline + public MyValue1 test1_dontinline2(Object o) { + return (MyValue1)o; + } + + @ForceInline + public Object test1_inline1(Object o) { + return o; + } + + @ForceInline + public MyValue1 test1_inline2(Object o) { + return (MyValue1)o; + } + + @Test + @IR(failOn = {ALLOC}) + public MyValue1 test1() { + MyValue1 vt = testValue1; + vt = (MyValue1)test1_dontinline1(vt); + vt = test1_dontinline2(vt); + vt = (MyValue1)test1_inline1(vt); + vt = test1_inline2(vt); + return vt; + } + + @Run(test = "test1") + public void test1_verifier() { + Asserts.assertEQ(test1().hash(), hash()); + } + + // Test storing/loading inline types to/from Object and inline type fields + Object objectField1 = null; + Object objectField2 = null; + Object objectField3 = null; + Object objectField4 = null; + Object objectField5 = null; + Object objectField6 = null; + + @Strict + @NullRestricted + MyValue1 valueField1 = testValue1; + @Strict + @NullRestricted + MyValue1 valueField2 = testValue1; + MyValue1 valueField3 = testValue1; + @Strict + @NullRestricted + MyValue1 valueField4 = MyValue1.DEFAULT; + MyValue1 valueField5; + + static MyValue1 staticValueField1 = testValue1; + @Strict + @NullRestricted + static MyValue1 staticValueField2 = testValue1; + @Strict + @NullRestricted + static MyValue1 staticValueField3 = MyValue1.DEFAULT; + static MyValue1 staticValueField4; + + @DontInline + public Object readValueField5() { + return (Object)valueField5; + } + + @DontInline + public Object readStaticValueField4() { + return (Object)staticValueField4; + } + + @Test + public long test2(MyValue1 vt1, Object vt2) { + objectField1 = vt1; + objectField2 = (MyValue1)vt2; + objectField3 = testValue1; + objectField4 = MyValue1.createWithFieldsDontInline(rI, rL); + objectField5 = valueField1; + objectField6 = valueField3; + valueField1 = (MyValue1)objectField1; + valueField2 = (MyValue1)vt2; + valueField3 = (MyValue1)vt2; + staticValueField1 = (MyValue1)objectField1; + staticValueField2 = (MyValue1)vt1; + // Don't inline these methods because reading NULL will trigger a deoptimization + if (readValueField5() != null || readStaticValueField4() != null) { + throw new RuntimeException("Should be null"); + } + return ((MyValue1)objectField1).hash() + ((MyValue1)objectField2).hash() + + ((MyValue1)objectField3).hash() + ((MyValue1)objectField4).hash() + + ((MyValue1)objectField5).hash() + ((MyValue1)objectField6).hash() + + valueField1.hash() + valueField2.hash() + valueField3.hash() + valueField4.hashPrimitive() + + staticValueField1.hash() + staticValueField2.hash() + staticValueField3.hashPrimitive(); + } + + @Run(test = "test2") + public void test2_verifier() { + MyValue1 vt = testValue1; + MyValue1 def = MyValue1.createDefaultDontInline(); + long result = test2(vt, vt); + Asserts.assertEQ(result, 11*vt.hash() + 2*def.hashPrimitive()); + } + + // Test merging inline types and objects + @Test + public Object test3(int state) { + Object res = null; + if (state == 0) { + res = new NonValueClass(rI); + } else if (state == 1) { + res = MyValue1.createWithFieldsInline(rI, rL); + } else if (state == 2) { + res = MyValue1.createWithFieldsDontInline(rI, rL); + } else if (state == 3) { + res = (MyValue1)objectField1; + } else if (state == 4) { + res = valueField1; + } else if (state == 5) { + res = null; + } else if (state == 6) { + res = MyValue2.createWithFieldsInline(rI, rD); + } else if (state == 7) { + res = testValue2; + } + return res; + } + + @Run(test = "test3") + public void test3_verifier() { + objectField1 = valueField1; + Object result = null; + result = test3(0); + Asserts.assertEQ(((NonValueClass)result).x, rI); + result = test3(1); + Asserts.assertEQ(((MyValue1)result).hash(), hash()); + result = test3(2); + Asserts.assertEQ(((MyValue1)result).hash(), hash()); + result = test3(3); + Asserts.assertEQ(((MyValue1)result).hash(), hash()); + result = test3(4); + Asserts.assertEQ(((MyValue1)result).hash(), hash()); + result = test3(5); + Asserts.assertEQ(result, null); + result = test3(6); + Asserts.assertEQ(((MyValue2)result).hash(), testValue2.hash()); + result = test3(7); + Asserts.assertEQ(((MyValue2)result).hash(), testValue2.hash()); + } + + // Test merging inline types and objects in loops + @Test + public Object test4(int iters) { + Object res = new NonValueClass(rI); + for (int i = 0; i < iters; ++i) { + if (res instanceof NonValueClass) { + res = MyValue1.createWithFieldsInline(rI, rL); + } else { + res = MyValue1.createWithFieldsInline(((MyValue1)res).x + 1, rL); + } + } + return res; + } + + @Run(test = "test4") + public void test4_verifier() { + NonValueClass result1 = (NonValueClass)test4(0); + Asserts.assertEQ(result1.x, rI); + int iters = (Math.abs(rI) % 10) + 1; + MyValue1 result2 = (MyValue1)test4(iters); + MyValue1 vt = MyValue1.createWithFieldsInline(rI + iters - 1, rL); + Asserts.assertEQ(result2.hash(), vt.hash()); + } + + // Test inline types in object variables that are live at safepoint + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, LOOP}) + public long test5(MyValue1 arg, boolean deopt, Method m) { + Object vt1 = MyValue1.createWithFieldsInline(rI, rL); + Object vt2 = MyValue1.createWithFieldsDontInline(rI, rL); + Object vt3 = arg; + Object vt4 = valueField1; + if (deopt) { + // uncommon trap + TestFramework.deoptimize(m); + } + return ((MyValue1)vt1).hash() + ((MyValue1)vt2).hash() + + ((MyValue1)vt3).hash() + ((MyValue1)vt4).hash(); + } + + @Run(test = "test5") + public void test5_verifier(RunInfo info) { + long result = test5(valueField1, !info.isWarmUp(), info.getTest()); + Asserts.assertEQ(result, 4*hash()); + } + + // Test comparing inline types with objects + @Test + public boolean test6(Object arg) { + Object vt = MyValue1.createWithFieldsInline(rI, rL); + if (vt == arg || vt == (Object)valueField1 || vt == objectField1 || vt == null || + arg == vt || (Object)valueField1 == vt || objectField1 == vt || null == vt) { + return true; + } + return false; + } + + @Run(test = "test6") + public void test6_verifier() { + boolean result = test6(null); + Asserts.assertFalse(result); + } + + // merge of inline type and non-inline type + @Test + public Object test7(boolean flag) { + Object res = null; + if (flag) { + res = valueField1; + } else { + res = objectField1; + } + return res; + } + + @Run(test = "test7") + public void test7_verifier() { + Asserts.assertEQ(test7(true), valueField1); + Asserts.assertEQ(test7(false), objectField1); + } + + @Test + public Object test8(boolean flag) { + Object res = null; + if (flag) { + res = objectField1; + } else { + res = valueField1; + } + return res; + } + + @Run(test = "test8") + public void test8_verifier() { + Asserts.assertEQ(test8(true), objectField1); + Asserts.assertEQ(test8(false), valueField1); + } + + // merge of inline types in a loop, stored in an object local + @Test + public Object test9() { + Object o = valueField1; + for (int i = 1; i < 100; i *= 2) { + MyValue1 v = (MyValue1)o; + o = MyValue1.setX(v, v.x + 1); + } + return o; + } + + @Run(test = "test9") + public void test9_verifier() { + Asserts.assertEQ(test9(), MyValue1.setX(valueField1, valueField1.x + 7)); + } + + // merge of inline types in an object local + @ForceInline + public Object test10_helper() { + return valueField1; + } + + @Test + @IR(failOn = {ALLOC}) + public void test10(boolean flag) { + Object o = null; + if (flag) { + o = valueField1; + } else { + o = test10_helper(); + } + valueField1 = (MyValue1)o; + } + + @Run(test = "test10") + public void test10_verifier() { + test10(true); + test10(false); + } + + // Interface tests + + @DontInline + public MyInterface test11_dontinline1(MyInterface o) { + return o; + } + + @DontInline + public MyValue1 test11_dontinline2(MyInterface o) { + return (MyValue1)o; + } + + @ForceInline + public MyInterface test11_inline1(MyInterface o) { + return o; + } + + @ForceInline + public MyValue1 test11_inline2(MyInterface o) { + return (MyValue1)o; + } + + @Test + public MyValue1 test11() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + vt = (MyValue1)test11_dontinline1(vt); + vt = test11_dontinline2(vt); + vt = (MyValue1)test11_inline1(vt); + vt = test11_inline2(vt); + return vt; + } + + @Run(test = "test11") + public void test11_verifier() { + Asserts.assertEQ(test11().hash(), hash()); + } + + // Test storing/loading inline types to/from interface and inline type fields + MyInterface interfaceField1 = null; + MyInterface interfaceField2 = null; + MyInterface interfaceField3 = null; + MyInterface interfaceField4 = null; + MyInterface interfaceField5 = null; + MyInterface interfaceField6 = null; + + @DontInline + public MyInterface readValueField5AsInterface() { + return (MyInterface)valueField5; + } + + @DontInline + public MyInterface readStaticValueField4AsInterface() { + return (MyInterface)staticValueField4; + } + + @Test + public long test12(MyValue1 vt1, MyInterface vt2) { + interfaceField1 = vt1; + interfaceField2 = (MyValue1)vt2; + interfaceField3 = MyValue1.createWithFieldsInline(rI, rL); + interfaceField4 = MyValue1.createWithFieldsDontInline(rI, rL); + interfaceField5 = valueField1; + interfaceField6 = valueField3; + valueField1 = (MyValue1)interfaceField1; + valueField2 = (MyValue1)vt2; + valueField3 = (MyValue1)vt2; + staticValueField1 = (MyValue1)interfaceField1; + staticValueField2 = (MyValue1)vt1; + // Don't inline these methods because reading NULL will trigger a deoptimization + if (readValueField5AsInterface() != null || readStaticValueField4AsInterface() != null) { + throw new RuntimeException("Should be null"); + } + return ((MyValue1)interfaceField1).hash() + ((MyValue1)interfaceField2).hash() + + ((MyValue1)interfaceField3).hash() + ((MyValue1)interfaceField4).hash() + + ((MyValue1)interfaceField5).hash() + ((MyValue1)interfaceField6).hash() + + valueField1.hash() + valueField2.hash() + valueField3.hash() + valueField4.hashPrimitive() + + staticValueField1.hash() + staticValueField2.hash() + staticValueField3.hashPrimitive(); + } + + @Run(test = "test12") + public void test12_verifier() { + MyValue1 vt = testValue1; + MyValue1 def = MyValue1.createDefaultDontInline(); + long result = test12(vt, vt); + Asserts.assertEQ(result, 11*vt.hash() + 2*def.hashPrimitive()); + } + + class MyObject1 implements MyInterface { + public int x; + + public MyObject1(int x) { + this.x = x; + } + + @ForceInline + public long hash() { + return x; + } + } + + // Test merging inline types and interfaces + @Test + public MyInterface test13(int state) { + MyInterface res = null; + if (state == 0) { + res = new MyObject1(rI); + } else if (state == 1) { + res = MyValue1.createWithFieldsInline(rI, rL); + } else if (state == 2) { + res = MyValue1.createWithFieldsDontInline(rI, rL); + } else if (state == 3) { + res = (MyValue1)objectField1; + } else if (state == 4) { + res = valueField1; + } else if (state == 5) { + res = null; + } + return res; + } + + @Run(test = "test13") + public void test13_verifier() { + objectField1 = valueField1; + MyInterface result = null; + result = test13(0); + Asserts.assertEQ(((MyObject1)result).x, rI); + result = test13(1); + Asserts.assertEQ(((MyValue1)result).hash(), hash()); + result = test13(2); + Asserts.assertEQ(((MyValue1)result).hash(), hash()); + result = test13(3); + Asserts.assertEQ(((MyValue1)result).hash(), hash()); + result = test13(4); + Asserts.assertEQ(((MyValue1)result).hash(), hash()); + result = test13(5); + Asserts.assertEQ(result, null); + } + + // Test merging inline types and interfaces in loops + @Test + public MyInterface test14(int iters) { + MyInterface res = new MyObject1(rI); + for (int i = 0; i < iters; ++i) { + if (res instanceof MyObject1) { + res = MyValue1.createWithFieldsInline(rI, rL); + } else { + res = MyValue1.createWithFieldsInline(((MyValue1)res).x + 1, rL); + } + } + return res; + } + + @Run(test = "test14") + public void test14_verifier() { + MyObject1 result1 = (MyObject1)test14(0); + Asserts.assertEQ(result1.x, rI); + int iters = (Math.abs(rI) % 10) + 1; + MyValue1 result2 = (MyValue1)test14(iters); + MyValue1 vt = MyValue1.createWithFieldsInline(rI + iters - 1, rL); + Asserts.assertEQ(result2.hash(), vt.hash()); + } + + // Test inline types in interface variables that are live at safepoint + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, LOOP}) + public long test15(MyValue1 arg, boolean deopt, Method m) { + MyInterface vt1 = MyValue1.createWithFieldsInline(rI, rL); + MyInterface vt2 = MyValue1.createWithFieldsDontInline(rI, rL); + MyInterface vt3 = arg; + MyInterface vt4 = valueField1; + return ((MyValue1)vt1).hash() + ((MyValue1)vt2).hash() + + ((MyValue1)vt3).hash() + ((MyValue1)vt4).hash(); + } + + @Run(test = "test15") + public void test15_verifier(RunInfo info) { + long result = test15(valueField1, !info.isWarmUp(), info.getTest()); + Asserts.assertEQ(result, 4*hash()); + } + + // Test comparing inline types with interfaces + @Test + public boolean test16(Object arg) { + MyInterface vt = MyValue1.createWithFieldsInline(rI, rL); + if (vt == arg || vt == (MyInterface)valueField1 || vt == interfaceField1 || vt == null || + arg == vt || (MyInterface)valueField1 == vt || interfaceField1 == vt || null == vt) { + return true; + } + return false; + } + + @Run(test = "test16") + public void test16_verifier() { + boolean result = test16(null); + Asserts.assertFalse(result); + } + + // Test subtype check when casting to inline type + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public MyValue1 test17(MyValue1 vt, Object obj) { + try { + vt = (MyValue1)obj; + throw new RuntimeException("ClassCastException expected"); + } catch (ClassCastException e) { + // Expected + } + return vt; + } + + @Run(test = "test17") + public void test17_verifier() { + MyValue1 vt = testValue1; + MyValue1 result = test17(vt, new NonValueClass(rI)); + Asserts.assertEquals(result.hash(), vt.hash()); + } + + @Test + public MyValue1 test18(MyValue1 vt) { + Object obj = vt; + vt = (MyValue1)obj; + return vt; + } + + @Run(test = "test18") + public void test18_verifier() { + MyValue1 vt = testValue1; + MyValue1 result = test18(vt); + Asserts.assertEquals(result.hash(), vt.hash()); + } + + @Test + @IR(failOn = {ALLOC}) + public void test19(MyValue1 vt) { + if (vt == null) { + return; + } + Object obj = vt; + try { + MyValue2 vt2 = (MyValue2)obj; + throw new RuntimeException("ClassCastException expected"); + } catch (ClassCastException e) { + // Expected + } + } + + @Run(test = "test19") + public void test19_verifier() { + test19(valueField1); + } + + @Test + @IR(failOn = {ALLOC}) + public void test20(MyValue1 vt) { + if (vt == null) { + return; + } + Object obj = vt; + try { + NonValueClass i = (NonValueClass)obj; + throw new RuntimeException("ClassCastException expected"); + } catch (ClassCastException e) { + // Expected + } + } + + @Run(test = "test20") + public void test20_verifier() { + test20(valueField1); + } + + // Array tests + + private static final MyValue1[] testValue1Array = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 3, MyValue1.DEFAULT); + static { + for (int i = 0; i < 3; ++i) { + testValue1Array[i] = testValue1; + } + } + + private static final MyValue1[][] testValue1Array2 = new MyValue1[][] {testValue1Array, + testValue1Array, + testValue1Array}; + + private static final MyValue2[] testValue2Array = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 3, MyValue2.DEFAULT); + static { + for (int i = 0; i < 3; ++i) { + testValue2Array[i] = testValue2; + } + } + + private static final NonValueClass[] testNonValueArray = new NonValueClass[42]; + + // Test load from (flattened) inline type array disguised as object array + @Test + public Object test21(Object[] oa, int index) { + return oa[index]; + } + + @Run(test = "test21") + public void test21_verifier() { + MyValue1 result = (MyValue1)test21(testValue1Array, Math.abs(rI) % 3); + Asserts.assertEQ(result.hash(), hash()); + } + + // Test load from (flattened) inline type array disguised as interface array + @Test + public Object test22Interface(MyInterface[] ia, int index) { + return ia[index]; + } + + @Run(test = "test22Interface") + public void test22Interface_verifier() { + MyValue1 result = (MyValue1)test22Interface(testValue1Array, Math.abs(rI) % 3); + Asserts.assertEQ(result.hash(), hash()); + } + + // Test load from (flattened) inline type array disguised as abstract array + @Test + public Object test22Abstract(MyAbstract[] ia, int index) { + return ia[index]; + } + + @Run(test = "test22Abstract") + public void test22Abstract_verifier() { + MyValue1 result = (MyValue1)test22Abstract(testValue1Array, Math.abs(rI) % 3); + Asserts.assertEQ(result.hash(), hash()); + } + + // Test inline store to (flattened) inline type array disguised as object array + @ForceInline + public void test23_inline(Object[] oa, Object o, int index) { + oa[index] = o; + } + + @Test + public void test23(Object[] oa, MyValue1 vt, int index) { + test23_inline(oa, vt, index); + } + + @Run(test = "test23") + public void test23_verifier() { + int index = Math.abs(rI) % 3; + MyValue1 vt = MyValue1.createWithFieldsInline(rI + 1, rL + 1); + test23(testValue1Array, vt, index); + Asserts.assertEQ(testValue1Array[index].hash(), vt.hash()); + testValue1Array[index] = testValue1; + try { + test23(testValue2Array, vt, index); + throw new RuntimeException("No ArrayStoreException thrown"); + } catch (ArrayStoreException e) { + // Expected + } + Asserts.assertEQ(testValue2Array[index].hash(), testValue2.hash()); + } + + @ForceInline + public void test24_inline(Object[] oa, Object o, int index) { + oa[index] = o; + } + + @Test + public void test24(Object[] oa, MyValue1 vt, int index) { + test24_inline(oa, vt, index); + } + + @Run(test = "test24") + public void test24_verifier() { + int index = Math.abs(rI) % 3; + try { + test24(testNonValueArray, testValue1, index); + throw new RuntimeException("No ArrayStoreException thrown"); + } catch (ArrayStoreException e) { + // Expected + } + } + + @ForceInline + public void test25_inline(Object[] oa, Object o, int index) { + oa[index] = o; + } + + @Test + public void test25(Object[] oa, MyValue1 vt, int index) { + test25_inline(oa, vt, index); + } + + @Run(test = "test25") + public void test25_verifier() { + int index = Math.abs(rI) % 3; + try { + test25(null, testValue1, index); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + // Test inline store to (flattened) inline type array disguised as interface array + @ForceInline + public void test26Interface_inline(MyInterface[] ia, MyInterface i, int index) { + ia[index] = i; + } + + @Test + public void test26Interface(MyInterface[] ia, MyValue1 vt, int index) { + test26Interface_inline(ia, vt, index); + } + + @Run(test = "test26Interface") + public void test26Interface_verifier() { + int index = Math.abs(rI) % 3; + MyValue1 vt = MyValue1.createWithFieldsInline(rI + 1, rL + 1); + test26Interface(testValue1Array, vt, index); + Asserts.assertEQ(testValue1Array[index].hash(), vt.hash()); + testValue1Array[index] = testValue1; + try { + test26Interface(testValue2Array, vt, index); + throw new RuntimeException("No ArrayStoreException thrown"); + } catch (ArrayStoreException e) { + // Expected + } + Asserts.assertEQ(testValue2Array[index].hash(), testValue2.hash()); + } + + @ForceInline + public void test27Interface_inline(MyInterface[] ia, MyInterface i, int index) { + ia[index] = i; + } + + @Test + public void test27Interface(MyInterface[] ia, MyValue1 vt, int index) { + test27Interface_inline(ia, vt, index); + } + + @Run(test = "test27Interface") + public void test27Interface_verifier() { + int index = Math.abs(rI) % 3; + try { + test27Interface(null, testValue1, index); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + // Test inline store to (flattened) inline type array disguised as abstract array + @ForceInline + public void test26Abstract_inline(MyAbstract[] ia, MyAbstract i, int index) { + ia[index] = i; + } + + @Test + public void test26Abstract(MyAbstract[] ia, MyValue1 vt, int index) { + test26Abstract_inline(ia, vt, index); + } + + @Run(test = "test26Abstract") + public void test26Abstract_verifier() { + int index = Math.abs(rI) % 3; + MyValue1 vt = MyValue1.createWithFieldsInline(rI + 1, rL + 1); + test26Abstract(testValue1Array, vt, index); + Asserts.assertEQ(testValue1Array[index].hash(), vt.hash()); + testValue1Array[index] = testValue1; + try { + test26Abstract(testValue2Array, vt, index); + throw new RuntimeException("No ArrayStoreException thrown"); + } catch (ArrayStoreException e) { + // Expected + } + Asserts.assertEQ(testValue2Array[index].hash(), testValue2.hash()); + } + + @ForceInline + public void test27Abstract_inline(MyAbstract[] ia, MyAbstract i, int index) { + ia[index] = i; + } + + @Test + public void test27Abstract(MyAbstract[] ia, MyValue1 vt, int index) { + test27Abstract_inline(ia, vt, index); + } + + @Run(test = "test27Abstract") + public void test27Abstract_verifier() { + int index = Math.abs(rI) % 3; + try { + test27Abstract(null, testValue1, index); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + // Test object store to (flattened) inline type array disguised as object array + @ForceInline + public void test28_inline(Object[] oa, Object o, int index) { + oa[index] = o; + } + + @Test + public void test28(Object[] oa, Object o, int index) { + test28_inline(oa, o, index); + } + + @Run(test = "test28") + public void test28_verifier() { + int index = Math.abs(rI) % 3; + MyValue1 vt1 = MyValue1.createWithFieldsInline(rI + 1, rL + 1); + test28(testValue1Array, vt1, index); + Asserts.assertEQ(testValue1Array[index].hash(), vt1.hash()); + try { + test28(testValue1Array, testValue2, index); + throw new RuntimeException("No ArrayStoreException thrown"); + } catch (ArrayStoreException e) { + // Expected + } + Asserts.assertEQ(testValue1Array[index].hash(), vt1.hash()); + testValue1Array[index] = testValue1; + } + + @ForceInline + public void test29_inline(Object[] oa, Object o, int index) { + oa[index] = o; + } + + @Test + public void test29(Object[] oa, Object o, int index) { + test29_inline(oa, o, index); + } + + @Run(test = "test29") + public void test29_verifier() { + int index = Math.abs(rI) % 3; + try { + test29(testValue2Array, testValue1, index); + throw new RuntimeException("No ArrayStoreException thrown"); + } catch (ArrayStoreException e) { + // Expected + } + Asserts.assertEQ(testValue2Array[index].hash(), testValue2.hash()); + } + + @ForceInline + public void test30_inline(Object[] oa, Object o, int index) { + oa[index] = o; + } + + @Test + public void test30(Object[] oa, Object o, int index) { + test30_inline(oa, o, index); + } + + @Run(test = "test30") + public void test30_verifier() { + int index = Math.abs(rI) % 3; + try { + test30(testNonValueArray, testValue1, index); + throw new RuntimeException("No ArrayStoreException thrown"); + } catch (ArrayStoreException e) { + // Expected + } + } + + // Test inline store to (flattened) inline type array disguised as interface array + @ForceInline + public void test31Interface_inline(MyInterface[] ia, MyInterface i, int index) { + ia[index] = i; + } + + @Test + public void test31Interface(MyInterface[] ia, MyInterface i, int index) { + test31Interface_inline(ia, i, index); + } + + @Run(test = "test31Interface") + public void test31Interface_verifier() { + int index = Math.abs(rI) % 3; + MyValue1 vt1 = MyValue1.createWithFieldsInline(rI + 1, rL + 1); + test31Interface(testValue1Array, vt1, index); + Asserts.assertEQ(testValue1Array[index].hash(), vt1.hash()); + try { + test31Interface(testValue1Array, testValue2, index); + throw new RuntimeException("No ArrayStoreException thrown"); + } catch (ArrayStoreException e) { + // Expected + } + Asserts.assertEQ(testValue1Array[index].hash(), vt1.hash()); + testValue1Array[index] = testValue1; + } + + @ForceInline + public void test32Interface_inline(MyInterface[] ia, MyInterface i, int index) { + ia[index] = i; + } + + @Test + public void test32Interface(MyInterface[] ia, MyInterface i, int index) { + test32Interface_inline(ia, i, index); + } + + @Run(test = "test32Interface") + public void test32Interface_verifier() { + int index = Math.abs(rI) % 3; + try { + test32Interface(testValue2Array, testValue1, index); + throw new RuntimeException("No ArrayStoreException thrown"); + } catch (ArrayStoreException e) { + // Expected + } + } + + // Test inline store to (flattened) inline type array disguised as abstract array + @ForceInline + public void test31Abstract_inline(MyAbstract[] ia, MyAbstract i, int index) { + ia[index] = i; + } + + @Test + public void test31Abstract(MyAbstract[] ia, MyAbstract i, int index) { + test31Abstract_inline(ia, i, index); + } + + @Run(test = "test31Abstract") + public void test31Abstract_verifier() { + int index = Math.abs(rI) % 3; + MyValue1 vt1 = MyValue1.createWithFieldsInline(rI + 1, rL + 1); + test31Abstract(testValue1Array, vt1, index); + Asserts.assertEQ(testValue1Array[index].hash(), vt1.hash()); + try { + test31Abstract(testValue1Array, testValue2, index); + throw new RuntimeException("No ArrayStoreException thrown"); + } catch (ArrayStoreException e) { + // Expected + } + Asserts.assertEQ(testValue1Array[index].hash(), vt1.hash()); + testValue1Array[index] = testValue1; + } + + @ForceInline + public void test32Abstract_inline(MyAbstract[] ia, MyAbstract i, int index) { + ia[index] = i; + } + + @Test + public void test32Abstract(MyAbstract[] ia, MyAbstract i, int index) { + test32Abstract_inline(ia, i, index); + } + + @Run(test = "test32Abstract") + public void test32Abstract_verifier() { + int index = Math.abs(rI) % 3; + try { + test32Abstract(testValue2Array, testValue1, index); + throw new RuntimeException("No ArrayStoreException thrown"); + } catch (ArrayStoreException e) { + // Expected + } + } + + // Test writing null to a (flattened) inline type array disguised as object array + @ForceInline + public void test33_inline(Object[] oa, Object o, int index) { + oa[index] = o; + } + + @Test + public void test33(Object[] oa, Object o, int index) { + test33_inline(oa, o, index); + } + + @Run(test = "test33") + public void test33_verifier() { + int index = Math.abs(rI) % 3; + try { + test33(testValue1Array, null, index); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + Asserts.assertEQ(testValue1Array[index].hash(), hash()); + } + + // Test writing constant null to a (flattened) inline type array disguised as object array + + @ForceInline + public void test34_inline(Object[] oa, Object o, int index) { + oa[index] = o; + } + + @Test + public void test34(Object[] oa, int index) { + test34_inline(oa, null, index); + } + + @Run(test = "test34") + public void test34_verifier() { + int index = Math.abs(rI) % 3; + try { + test34(testValue1Array, index); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + Asserts.assertEQ(testValue1Array[index].hash(), hash()); + } + + // Test writing constant null to a (flattened) inline type array + + private static final MethodHandle setArrayElementNull = InstructionHelper.buildMethodHandle(MethodHandles.lookup(), + "setArrayElementNull", + MethodType.methodType(void.class, TestLWorld.class, MyValue1[].class, int.class), + CODE -> { + CODE. + aload(1). + iload(2). + aconst_null(). + aastore(). + return_(); + }); + + @Test + public void test35(MyValue1[] va, int index) throws Throwable { + setArrayElementNull.invoke(this, va, index); + } + + @Run(test = "test35") + @Warmup(10000) + public void test35_verifier() throws Throwable { + int index = Math.abs(rI) % 3; + try { + test35(testValue1Array, index); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + Asserts.assertEQ(testValue1Array[index].hash(), hash()); + } + + // Test writing an inline type to a null inline type array + @Test + public void test36(MyValue1[] va, MyValue1 vt, int index) { + va[index] = vt; + } + + @Run(test = "test36") + public void test36_verifier() { + int index = Math.abs(rI) % 3; + try { + test36(null, testValue1Array[index], index); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + // Test incremental inlining + @ForceInline + public void test37_inline(Object[] oa, Object o, int index) { + oa[index] = o; + } + + @Test + @IR(failOn = {ALLOC}) + public void test37(MyValue1[] va, Object o, int index) { + test37_inline(va, o, index); + } + + @Run(test = "test37") + public void test37_verifier() { + int index = Math.abs(rI) % 3; + MyValue1 vt1 = MyValue1.createWithFieldsInline(rI + 1, rL + 1); + test37(testValue1Array, vt1, index); + Asserts.assertEQ(testValue1Array[index].hash(), vt1.hash()); + try { + test37(testValue1Array, testValue2, index); + throw new RuntimeException("No ArrayStoreException thrown"); + } catch (ArrayStoreException e) { + // Expected + } + Asserts.assertEQ(testValue1Array[index].hash(), vt1.hash()); + testValue1Array[index] = testValue1; + } + + // Test merging of inline type arrays + + @ForceInline + public Object[] test38_inline() { + return (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 42, MyValue1.DEFAULT); + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public Object[] test38(Object[] oa, Object o, int i1, int i2, int num) { + Object[] result = null; + switch (num) { + case 0: + result = test38_inline(); + break; + case 1: + result = oa; + break; + case 2: + result = testValue1Array; + break; + case 3: + result = testValue2Array; + break; + case 4: + result = testNonValueArray; + break; + case 5: + result = null; + break; + case 6: + result = testValue1Array2; + break; + } + result[i1] = result[i2]; + result[i2] = o; + return result; + } + + @Run(test = "test38") + public void test38_verifier() { + int index = Math.abs(rI) % 3; + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 42, MyValue1.DEFAULT); + Object[] result = test38(null, testValue1, index, index, 0); + Asserts.assertEQ(((MyValue1)result[index]).hash(), testValue1.hash()); + result = test38(testValue1Array, testValue1, index, index, 1); + Asserts.assertEQ(((MyValue1)result[index]).hash(), testValue1.hash()); + result = test38(null, testValue1, index, index, 2); + Asserts.assertEQ(((MyValue1)result[index]).hash(), testValue1.hash()); + result = test38(null, testValue2, index, index, 3); + Asserts.assertEQ(((MyValue2)result[index]).hash(), testValue2.hash()); + try { + result = test38(null, null, index, index, 3); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + result = test38(null, null, index, index, 4); + try { + result = test38(null, testValue1, index, index, 4); + throw new RuntimeException("No ArrayStoreException thrown"); + } catch (ArrayStoreException e) { + // Expected + } + try { + result = test38(null, testValue1, index, index, 5); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + result = test38(null, testValue1Array, index, index, 6); + Asserts.assertEQ(((MyValue1[][])result)[index][index].hash(), testValue1.hash()); + } + + @ForceInline + public Object test39_inline() { + return (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 42, MyValue1.DEFAULT); + } + + // Same as above but merging into Object instead of Object[] + @Test + public Object test39(Object oa, Object o, int i1, int i2, int num) { + Object result = null; + switch (num) { + case 0: + result = test39_inline(); + break; + case 1: + result = oa; + break; + case 2: + result = testValue1Array; + break; + case 3: + result = testValue2Array; + break; + case 4: + result = testNonValueArray; + break; + case 5: + result = null; + break; + case 6: + result = testValue1; + break; + case 7: + result = testValue2; + break; + case 8: + result = MyValue1.createWithFieldsInline(rI, rL); + break; + case 9: + result = new NonValueClass(42); + break; + case 10: + result = testValue1Array2; + break; + } + if (result instanceof Object[]) { + ((Object[])result)[i1] = ((Object[])result)[i2]; + ((Object[])result)[i2] = o; + } + return result; + } + + @Run(test = "test39") + public void test39_verifier() { + int index = Math.abs(rI) % 3; + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 42, MyValue1.DEFAULT); + Object result = test39(null, testValue1, index, index, 0); + Asserts.assertEQ(((MyValue1[])result)[index].hash(), testValue1.hash()); + result = test39(testValue1Array, testValue1, index, index, 1); + Asserts.assertEQ(((MyValue1[])result)[index].hash(), testValue1.hash()); + result = test39(null, testValue1, index, index, 2); + Asserts.assertEQ(((MyValue1[])result)[index].hash(), testValue1.hash()); + result = test39(null, testValue2, index, index, 3); + Asserts.assertEQ(((MyValue2[])result)[index].hash(), testValue2.hash()); + try { + result = test39(null, null, index, index, 3); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + result = test39(null, null, index, index, 4); + try { + result = test39(null, testValue1, index, index, 4); + throw new RuntimeException("No ArrayStoreException thrown"); + } catch (ArrayStoreException e) { + // Expected + } + result = test39(null, testValue1, index, index, 5); + Asserts.assertEQ(result, null); + result = test39(null, testValue1, index, index, 6); + Asserts.assertEQ(((MyValue1)result).hash(), testValue1.hash()); + result = test39(null, testValue1, index, index, 7); + Asserts.assertEQ(((MyValue2)result).hash(), testValue2.hash()); + result = test39(null, testValue1, index, index, 8); + Asserts.assertEQ(((MyValue1)result).hash(), testValue1.hash()); + result = test39(null, testValue1, index, index, 9); + Asserts.assertEQ(((NonValueClass)result).x, 42); + result = test39(null, testValue1Array, index, index, 10); + Asserts.assertEQ(((MyValue1[][])result)[index][index].hash(), testValue1.hash()); + } + + // Test instanceof with inline types and arrays + @Test + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "true"}, + failOn = {ALLOC}) + public long test40(Object o, int index) { + if (o instanceof MyValue1) { + return ((MyValue1)o).hashInterpreted(); + } else if (o instanceof MyValue1[]) { + return ((MyValue1[])o)[index].hashInterpreted(); + } else if (o instanceof MyValue2) { + return ((MyValue2)o).hash(); + } else if (o instanceof MyValue2[]) { + return ((MyValue2[])o)[index].hash(); + } else if (o instanceof MyValue1[][]) { + return ((MyValue1[][])o)[index][index].hash(); + } else if (o instanceof Long) { + return (long)o; + } + return 0; + } + + @Run(test = "test40") + public void test40_verifier() { + int index = Math.abs(rI) % 3; + long result = test40(testValue1, 0); + Asserts.assertEQ(result, testValue1.hashInterpreted()); + result = test40(testValue1Array, index); + Asserts.assertEQ(result, testValue1.hashInterpreted()); + result = test40(testValue2, index); + Asserts.assertEQ(result, testValue2.hash()); + result = test40(testValue2Array, index); + Asserts.assertEQ(result, testValue2.hash()); + result = test40(testValue1Array2, index); + Asserts.assertEQ(result, testValue1.hash()); + result = test40(Long.valueOf(42), index); + Asserts.assertEQ(result, 42L); + } + + // Test for bug in Escape Analysis + @DontInline + public void test41_dontinline(Object o) { + Asserts.assertEQ(o, rI); + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public void test41() { + MyValue1[] vals = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + vals[0] = testValue1; + test41_dontinline(vals[0].oa[0]); + test41_dontinline(vals[0].oa[0]); + } + + @Run(test = "test41") + public void test41_verifier() { + test41(); + } + + // Test for bug in Escape Analysis + private static final MyValue1 test42VT1 = MyValue1.createWithFieldsInline(rI, rL); + private static final MyValue1 test42VT2 = MyValue1.createWithFieldsInline(rI + 1, rL + 1); + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public void test42() { + MyValue1[] vals = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 2, MyValue1.DEFAULT); + vals[0] = test42VT1; + vals[1] = test42VT2; + Asserts.assertEQ(vals[0].hash(), test42VT1.hash()); + Asserts.assertEQ(vals[1].hash(), test42VT2.hash()); + } + + @Run(test = "test42") + public void test42_verifier(RunInfo info) { + if (!info.isWarmUp()) test42(); // We need -Xcomp behavior + } + + // Test for bug in Escape Analysis + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public long test43(boolean deopt, Method m) { + MyValue1[] vals = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 2, MyValue1.DEFAULT); + vals[0] = test42VT1; + vals[1] = test42VT2; + + if (deopt) { + // uncommon trap + TestFramework.deoptimize(m); + Asserts.assertEQ(vals[0].hash(), test42VT1.hash()); + Asserts.assertEQ(vals[1].hash(), test42VT2.hash()); + } + + return vals[0].hash(); + } + + @Run(test = "test43") + public void test43_verifier(RunInfo info) { + test43(!info.isWarmUp(), info.getTest()); + } + + // Tests writing an array element with a (statically known) incompatible type + private static final MethodHandle setArrayElementIncompatible = InstructionHelper.buildMethodHandle(MethodHandles.lookup(), + "setArrayElementIncompatible", + MethodType.methodType(void.class, TestLWorld.class, MyValue1[].class, int.class, MyValue2.class), + CODE -> { + CODE. + aload(1). + iload(2). + aload(3). + aastore(). + return_(); + }); + + @Test + public void test44(MyValue1[] va, int index, MyValue2 v) throws Throwable { + setArrayElementIncompatible.invoke(this, va, index, v); + } + + @Run(test = "test44") + @Warmup(10000) + public void test44_verifier() throws Throwable { + int index = Math.abs(rI) % 3; + try { + test44(testValue1Array, index, testValue2); + throw new RuntimeException("No ArrayStoreException thrown"); + } catch (ArrayStoreException e) { + // Expected + } + Asserts.assertEQ(testValue1Array[index].hash(), hash()); + } + + // Tests writing an array element with a (statically known) incompatible type + @ForceInline + public void test45_inline(Object[] oa, Object o, int index) { + oa[index] = o; + } + + @Test + public void test45(MyValue1[] va, int index, MyValue2 v) throws Throwable { + test45_inline(va, v, index); + } + + @Run(test = "test45") + public void test45_verifier() throws Throwable { + int index = Math.abs(rI) % 3; + try { + test45(testValue1Array, index, testValue2); + throw new RuntimeException("No ArrayStoreException thrown"); + } catch (ArrayStoreException e) { + // Expected + } + Asserts.assertEQ(testValue1Array[index].hash(), hash()); + } + + // instanceof tests with inline types + @Test + @IR(failOn = {ALLOC}) + public boolean test46(MyValue1 vt) { + Object obj = vt; + return obj instanceof MyValue1; + } + + @Run(test = "test46") + public void test46_verifier() { + MyValue1 vt = testValue1; + boolean result = test46(vt); + Asserts.assertTrue(result); + } + + @Test + @IR(failOn = {ALLOC}) + public boolean test47(MyValue1 vt) { + Object obj = vt; + return obj instanceof MyValue2; + } + + @Run(test = "test47") + public void test47_verifier() { + MyValue1 vt = testValue1; + boolean result = test47(vt); + Asserts.assertFalse(result); + } + + @Test + @IR(failOn = {ALLOC}) + public boolean test48(Object obj) { + return obj instanceof MyValue1; + } + + @Run(test = "test48") + public void test48_verifier() { + MyValue1 vt = testValue1; + boolean result = test48(vt); + Asserts.assertTrue(result); + } + + @Test + @IR(failOn = {ALLOC}) + public boolean test49(Object obj) { + return obj instanceof MyValue2; + } + + @Run(test = "test49") + public void test49_verifier() { + MyValue1 vt = testValue1; + boolean result = test49(vt); + Asserts.assertFalse(result); + } + + @Test + @IR(failOn = {ALLOC}) + public boolean test50(Object obj) { + return obj instanceof MyValue1; + } + + @Run(test = "test50") + public void test50_verifier() { + Asserts.assertFalse(test49(new NonValueClass(42))); + } + + // Inline type with some non-flattened fields + @LooselyConsistentValue + static value class Test51Value { + Object objectField1; + Object objectField2; + Object objectField3; + Object objectField4; + Object objectField5; + Object objectField6; + + @Strict + @NullRestricted + MyValue1 valueField1; + @Strict + @NullRestricted + MyValue1 valueField2; + MyValue1 valueField3; + @Strict + @NullRestricted + MyValue1 valueField4; + MyValue1 valueField5; + + public Test51Value() { + objectField1 = null; + objectField2 = null; + objectField3 = null; + objectField4 = null; + objectField5 = null; + objectField6 = null; + valueField1 = testValue1; + valueField2 = testValue1; + valueField3 = testValue1; + valueField4 = MyValue1.createDefaultDontInline(); + valueField5 = MyValue1.createDefaultDontInline(); + } + + public Test51Value(Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, + MyValue1 vt1, MyValue1 vt2, MyValue1 vt3, MyValue1 vt4, MyValue1 vt5) { + objectField1 = o1; + objectField2 = o2; + objectField3 = o3; + objectField4 = o4; + objectField5 = o5; + objectField6 = o6; + valueField1 = vt1; + valueField2 = vt2; + valueField3 = vt3; + valueField4 = vt4; + valueField5 = vt5; + } + + @ForceInline + public long test(Test51Value holder, MyValue1 vt1, Object vt2) { + holder = new Test51Value(vt1, holder.objectField2, holder.objectField3, holder.objectField4, holder.objectField5, holder.objectField6, + holder.valueField1, holder.valueField2, holder.valueField3, holder.valueField4, holder.valueField5); + holder = new Test51Value(holder.objectField1, (MyValue1)vt2, holder.objectField3, holder.objectField4, holder.objectField5, holder.objectField6, + holder.valueField1, holder.valueField2, holder.valueField3, holder.valueField4, holder.valueField5); + holder = new Test51Value(holder.objectField1, holder.objectField2, testValue1, holder.objectField4, holder.objectField5, holder.objectField6, + holder.valueField1, holder.valueField2, holder.valueField3, holder.valueField4, holder.valueField5); + holder = new Test51Value(holder.objectField1, holder.objectField2, holder.objectField3, MyValue1.createWithFieldsDontInline(rI, rL), holder.objectField5, holder.objectField6, + holder.valueField1, holder.valueField2, holder.valueField3, holder.valueField4, holder.valueField5); + holder = new Test51Value(holder.objectField1, holder.objectField2, holder.objectField3, holder.objectField4, holder.valueField1, holder.objectField6, + holder.valueField1, holder.valueField2, holder.valueField3, holder.valueField4, holder.valueField5); + holder = new Test51Value(holder.objectField1, holder.objectField2, holder.objectField3, holder.objectField4, holder.objectField5, holder.valueField3, + holder.valueField1, holder.valueField2, holder.valueField3, holder.valueField4, holder.valueField5); + holder = new Test51Value(holder.objectField1, holder.objectField2, holder.objectField3, holder.objectField4, holder.objectField5, holder.objectField6, + (MyValue1)holder.objectField1, holder.valueField2, holder.valueField3, holder.valueField4, holder.valueField5); + holder = new Test51Value(holder.objectField1, holder.objectField2, holder.objectField3, holder.objectField4, holder.objectField5, holder.objectField6, + holder.valueField1, (MyValue1)vt2, holder.valueField3, holder.valueField4, holder.valueField5); + holder = new Test51Value(holder.objectField1, holder.objectField2, holder.objectField3, holder.objectField4, holder.objectField5, holder.objectField6, + holder.valueField1, holder.valueField2, (MyValue1)vt2, holder.valueField4, holder.valueField5); + + return ((MyValue1)holder.objectField1).hash() + + ((MyValue1)holder.objectField2).hash() + + ((MyValue1)holder.objectField3).hash() + + ((MyValue1)holder.objectField4).hash() + + ((MyValue1)holder.objectField5).hash() + + ((MyValue1)holder.objectField6).hash() + + holder.valueField1.hash() + + holder.valueField2.hash() + + holder.valueField3.hash() + + holder.valueField4.hashPrimitive(); + } + } + + // Pass arguments via fields to avoid exzessive spilling leading to compilation bailouts + @Strict + @NullRestricted + static Test51Value test51_arg1 = new Test51Value(); + @Strict + @NullRestricted + static MyValue1 test51_arg2 = MyValue1.DEFAULT; + static Object test51_arg3; + + // Same as test2 but with field holder being an inline type + @Test + public long test51() { + return test51_arg1.test(test51_arg1, test51_arg2, test51_arg3); + } + + @Run(test = "test51") + public void test51_verifier() { + MyValue1 vt = testValue1; + MyValue1 def = MyValue1.createDefaultDontInline(); + Test51Value holder = new Test51Value(); + Asserts.assertEQ(testValue1.hash(), vt.hash()); + Asserts.assertEQ(holder.valueField1.hash(), vt.hash()); + test51_arg1 = holder; + test51_arg2 = vt; + test51_arg3 = vt; + long result = test51(); + Asserts.assertEQ(result, 9*vt.hash() + def.hashPrimitive()); + } + + // Access non-flattened, uninitialized inline type field with inline type holder + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public void test52(Test51Value holder) { + if ((Object)holder.valueField5 != null) { + throw new RuntimeException("Should be null"); + } + } + + @Run(test = "test52") + public void test52_verifier() { + Test51Value vt = new Test51Value(null, null, null, null, null, null, + MyValue1.createDefaultInline(), MyValue1.createDefaultInline(), null, MyValue1.createDefaultInline(), null); + test52(vt); + } + + // Merging inline types of different types + @Test + public Object test53(Object o, boolean b) { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + return b ? vt : o; + } + + @Run(test = "test53") + public void test53_verifier() { + test53(new Object(), false); + MyValue1 result = (MyValue1)test53(new Object(), true); + Asserts.assertEQ(result.hash(), hash()); + } + + @Test + public Object test54(boolean b) { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + return b ? vt : testValue2; + } + + @Run(test = "test54") + public void test54_verifier() { + MyValue1 result1 = (MyValue1)test54(true); + Asserts.assertEQ(result1.hash(), hash()); + MyValue2 result2 = (MyValue2)test54(false); + Asserts.assertEQ(result2.hash(), testValue2.hash()); + } + + @Test + public Object test55(boolean b) { + MyValue1 vt1 = MyValue1.createWithFieldsInline(rI, rL); + MyValue2 vt2 = MyValue2.createWithFieldsInline(rI, rD); + return b ? vt1 : vt2; + } + + @Run(test = "test55") + public void test55_verifier() { + MyValue1 result1 = (MyValue1)test55(true); + Asserts.assertEQ(result1.hash(), hash()); + MyValue2 result2 = (MyValue2)test55(false); + Asserts.assertEQ(result2.hash(), testValue2.hash()); + } + + // Test synchronization on inline types + @Test + public void test56(Object vt) { + synchronized (vt) { + throw new RuntimeException("test56 failed: synchronization on inline type should not succeed"); + } + } + + @Run(test = "test56") + public void test56_verifier() { + try { + test56(testValue1); + throw new RuntimeException("test56 failed: no exception thrown"); + } catch (IdentityException ex) { + // Expected + } + } + + @ForceInline + public void test57_inline(Object vt) { + synchronized (vt) { + throw new RuntimeException("test57 failed: synchronization on inline type should not succeed"); + } + } + + @Test + @IR(failOn = {ALLOC}) + public void test57(MyValue1 vt) { + test57_inline(vt); + } + + @Run(test = "test57") + public void test57_verifier() { + try { + test57(testValue1); + throw new RuntimeException("test57 failed: no exception thrown"); + } catch (IdentityException ex) { + // Expected + } + } + + @ForceInline + public void test58_inline(Object vt) { + synchronized (vt) { + throw new RuntimeException("test58 failed: synchronization on inline type should not succeed"); + } + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public void test58() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + test58_inline(vt); + } + + @Run(test = "test58") + public void test58_verifier() { + try { + test58(); + throw new RuntimeException("test58 failed: no exception thrown"); + } catch (IdentityException ex) { + // Expected + } + } + + @Test + public void test59(Object o, boolean b) { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + Object sync = b ? vt : o; + synchronized (sync) { + if (b) { + throw new RuntimeException("test59 failed: synchronization on inline type should not succeed"); + } + } + } + + @Run(test = "test59") + public void test59_verifier() { + test59(new Object(), false); + try { + test59(new Object(), true); + throw new RuntimeException("test59 failed: no exception thrown"); + } catch (IdentityException ex) { + // Expected + } + } + + @Test + public void test60(boolean b) { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + Object sync = b ? vt : testValue2; + synchronized (sync) { + throw new RuntimeException("test60 failed: synchronization on inline type should not succeed"); + } + } + + @Run(test = "test60") + public void test60_verifier() { + try { + test60(false); + throw new RuntimeException("test60 failed: no exception thrown"); + } catch (IdentityException ex) { + // Expected + } + try { + test60(true); + throw new RuntimeException("test60 failed: no exception thrown"); + } catch (IdentityException ex) { + // Expected + } + } + + // Test catching the IdentityException in compiled code + @Test + public void test61(Object vt) { + boolean thrown = false; + try { + synchronized (vt) { + throw new RuntimeException("test61 failed: no exception thrown"); + } + } catch (IdentityException ex) { + thrown = true; + } + if (!thrown) { + throw new RuntimeException("test61 failed: no exception thrown"); + } + } + + @Run(test = "test61") + public void test61_verifier() { + test61(testValue1); + } + + @Test + public void test62(Object o) { + try { + synchronized (o) { } + } catch (IdentityException ex) { + // Expected + return; + } + throw new RuntimeException("test62 failed: no exception thrown"); + } + + @Run(test = "test62") + public void test62_verifier() { + test62(testValue1); + } + + // Test synchronization without any instructions in the synchronized block + @Test + public void test63(Object o) { + synchronized (o) { } + } + + @Run(test = "test63") + public void test63_verifier() { + try { + test63(testValue1); + } catch (IdentityException ex) { + // Expected + return; + } + throw new RuntimeException("test63 failed: no exception thrown"); + } + + // type system test with interface and inline type + @ForceInline + public MyInterface test64Interface_helper(MyValue1 vt) { + return vt; + } + + @Test + public MyInterface test64Interface(MyValue1 vt) { + return test64Interface_helper(vt); + } + + @Run(test = "test64Interface") + public void test64Interface_verifier() { + test64Interface(testValue1); + } + + // type system test with abstract and inline type + @ForceInline + public MyAbstract test64Abstract_helper(MyValue1 vt) { + return vt; + } + + @Test + public MyAbstract test64Abstract(MyValue1 vt) { + return test64Abstract_helper(vt); + } + + @Run(test = "test64Abstract") + public void test64Abstract_verifier() { + test64Abstract(testValue1); + } + + // Array store tests + @Test + public void test65(Object[] array, MyValue1 vt) { + array[0] = vt; + } + + @Run(test = "test65") + public void test65_verifier() { + Object[] array = new Object[1]; + test65(array, testValue1); + Asserts.assertEQ(((MyValue1)array[0]).hash(), testValue1.hash()); + } + + @Test + public void test66(Object[] array, MyValue1 vt) { + array[0] = vt; + } + + @Run(test = "test66") + public void test66_verifier() { + MyValue1[] array = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + test66(array, testValue1); + Asserts.assertEQ(array[0].hash(), testValue1.hash()); + } + + @Test + public void test67(Object[] array, Object vt) { + array[0] = vt; + } + + @Run(test = "test67") + public void test67_verifier() { + MyValue1[] array = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + test67(array, testValue1); + Asserts.assertEQ(array[0].hash(), testValue1.hash()); + } + + @Test + public void test68(Object[] array, NonValueClass o) { + array[0] = o; + } + + @Run(test = "test68") + public void test68_verifier() { + NonValueClass[] array = new NonValueClass[1]; + NonValueClass obj = new NonValueClass(1); + test68(array, obj); + Asserts.assertEQ(array[0], obj); + } + + // Test convertion between an inline type and java.lang.Object without an allocation + @ForceInline + public Object test69_sum(Object a, Object b) { + int sum = ((MyValue1)a).x + ((MyValue1)b).x; + return MyValue1.setX(((MyValue1)a), sum); + } + + @Test + @IR(failOn = {ALLOC, STORE_OF_ANY_KLASS}) + public int test69(MyValue1[] array) { + MyValue1 result = MyValue1.createDefaultInline(); + for (int i = 0; i < array.length; ++i) { + result = (MyValue1)test69_sum(result, array[i]); + } + return result.x; + } + + @Run(test = "test69") + public void test69_verifier() { + int result = test69(testValue1Array); + Asserts.assertEQ(result, rI * testValue1Array.length); + } + + // Same as test69 but with an Interface + @ForceInline + public MyInterface test70Interface_sum(MyInterface a, MyInterface b) { + int sum = ((MyValue1)a).x + ((MyValue1)b).x; + return MyValue1.setX(((MyValue1)a), sum); + } + + @Test + @IR(failOn = {ALLOC, STORE_OF_ANY_KLASS}) + public int test70Interface(MyValue1[] array) { + MyValue1 result = MyValue1.createDefaultInline(); + for (int i = 0; i < array.length; ++i) { + result = (MyValue1)test70Interface_sum(result, array[i]); + } + return result.x; + } + + @Run(test = "test70Interface") + public void test70Interface_verifier() { + int result = test70Interface(testValue1Array); + Asserts.assertEQ(result, rI * testValue1Array.length); + } + + // Same as test69 but with an Abstract + @ForceInline + public MyAbstract test70Abstract_sum(MyAbstract a, MyAbstract b) { + int sum = ((MyValue1)a).x + ((MyValue1)b).x; + return MyValue1.setX(((MyValue1)a), sum); + } + + @Test + @IR(failOn = {ALLOC, STORE_OF_ANY_KLASS}) + public int test70Abstract(MyValue1[] array) { + MyValue1 result = MyValue1.createDefaultInline(); + for (int i = 0; i < array.length; ++i) { + result = (MyValue1)test70Abstract_sum(result, array[i]); + } + return result.x; + } + + @Run(test = "test70Abstract") + public void test70Abstract_verifier() { + int result = test70Abstract(testValue1Array); + Asserts.assertEQ(result, rI * testValue1Array.length); + } + + // Test that allocated inline type is not used in non-dominated path + public MyValue1 test71_inline(Object obj) { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + try { + vt = (MyValue1)Objects.requireNonNull(obj); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + return vt; + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public MyValue1 test71() { + return test71_inline(null); + } + + @Run(test = "test71") + public void test71_verifier() { + MyValue1 vt = test71(); + Asserts.assertEquals(vt.hash(), hash()); + } + + // Test calling a method on an uninitialized inline type + @LooselyConsistentValue + value class Test72Value { + int x = 0; + + public int get() { + return x; + } + } + + // Make sure Test72Value is loaded but not initialized + public void unused(Test72Value vt) { } + + @Test + @IR(failOn = {ALLOC}) + public int test72() { + Test72Value vt = new Test72Value(); + return vt.get(); + } + + @Run(test = "test72") + @Warmup(0) + public void test72_verifier() { + int result = test72(); + Asserts.assertEquals(result, 0); + } + + // Tests for loading/storing unkown values + @Test + public Object test73(Object[] va) { + return va[0]; + } + + @Run(test = "test73") + public void test73_verifier() { + MyValue1 vt = (MyValue1)test73(testValue1Array); + Asserts.assertEquals(testValue1Array[0].hash(), vt.hash()); + } + + @Test + public void test74(Object[] va, Object vt) { + va[0] = vt; + } + + @Run(test = "test74") + public void test74_verifier() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + test74(va, testValue1); + Asserts.assertEquals(va[0].hash(), testValue1.hash()); + } + + // Verify that mixing instances and arrays with the clone api + // doesn't break anything + @Test + @IR(failOn = {ALLOC}) + public Object test75(Object o) { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + Object[] next = va; + Object[] arr = va; + for (int i = 0; i < 10; i++) { + arr = next; + next = new NonValueClass[1]; + } + return arr[0]; + } + + @Run(test = "test75") + public void test75_verifier() { + test75(42); + } + + // Casting an NonValueClass to a inline type should throw a ClassCastException + @ForceInline + public MyValue1 test77_helper(Object o) { + return (MyValue1)o; + } + + @Test + @IR(failOn = {ALLOC}) + public MyValue1 test77(NonValueClass obj) throws Throwable { + return test77_helper(obj); + } + + @Run(test = "test77") + public void test77_verifier() throws Throwable { + try { + test77(new NonValueClass(42)); + throw new RuntimeException("ClassCastException expected"); + } catch (ClassCastException e) { + // Expected + } catch (Exception e) { + throw new RuntimeException("test77 failed: unexpected exception", e); + } + } + + // Casting a null NonValueClass to a nullable inline type should not throw + @ForceInline + public MyValue1 test78_helper(Object o) { + return (MyValue1)o; + } + + @Test + @IR(failOn = {ALLOC}) + public MyValue1 test78(NonValueClass obj) throws Throwable { + return test78_helper(obj); + } + + @Run(test = "test78") + public void test78_verifier() throws Throwable { + try { + test78(null); // Should not throw + } catch (Exception e) { + throw new RuntimeException("test78 failed: unexpected exception", e); + } + } + + // Casting an NonValueClass to a nullable inline type should throw a ClassCastException + @ForceInline + public MyValue1 test79_helper(Object o) { + return (MyValue1)o; + } + + @Test + @IR(failOn = {ALLOC}) + public MyValue1 test79(NonValueClass obj) throws Throwable { + return test79_helper(obj); + } + + @Run(test = "test79") + public void test79_verifier() throws Throwable { + try { + test79(new NonValueClass(42)); + throw new RuntimeException("ClassCastException expected"); + } catch (ClassCastException e) { + // Expected + } catch (Exception e) { + throw new RuntimeException("test79 failed: unexpected exception", e); + } + } + + // Test flattened field with non-flattenend (but flattenable) inline type field + @LooselyConsistentValue + static value class Small { + int i; + @Strict + @NullRestricted + Big big; // Too big to be flattened + + private Small() { + i = rI; + big = new Big(); + } + + private Small(int i, Big big) { + this.i = i; + this.big = big; + } + } + + @LooselyConsistentValue + static value class Big { + long l0,l1,l2,l3,l4,l5,l6,l7,l8,l9; + long l10,l11,l12,l13,l14,l15,l16,l17,l18,l19; + long l20,l21,l22,l23,l24,l25,l26,l27,l28,l29; + + private Big() { + l0 = l1 = l2 = l3 = l4 = l5 = l6 = l7 = l8 = l9 = rL; + l10 = l11 = l12 = l13 = l14 = l15 = l16 = l17 = l18 = l19 = rL+1; + l20 = l21 = l22 = l23 = l24 = l25 = l26 = l27 = l28 = l29 = rL+2; + } + + private Big(long l) { + l0 = l1 = l2 = l3 = l4 = l5 = l6 = l7 = l8 = l9 = l10 = + l11 = l12 = l13 = l14 = l15 = l16 = l17 = l18 = l19 = l20 = + l21 = l22 = l23 = l24 = l25 = l26 = l27 = l28 = l29 = 0; + } + } + + @Strict + @NullRestricted + Small small = new Small(); + @Strict + @NullRestricted + Small smallDefault = new Small(0, new Big(0)); + @Strict + @NullRestricted + Big big = new Big(); + @Strict + @NullRestricted + Big bigDefault = new Big(0); + + @Test + public long test80() { + return small.i + small.big.l0 + smallDefault.i + smallDefault.big.l29 + big.l0 + bigDefault.l29; + } + + @Run(test = "test80") + public void test80_verifier() throws Throwable { + long result = test80(); + Asserts.assertEQ(result, rI + 2*rL); + } + + // Test scalarization with exceptional control flow + public int test81Callee(MyValue1 vt) { + return vt.x; + } + + @Test + @IR(failOn = {ALLOC, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS}) + public int test81() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + int result = 0; + for (int i = 0; i < 10; i++) { + try { + result += test81Callee(vt); + } catch (NullPointerException npe) { + result += rI; + } + } + return result; + } + + @Run(test = "test81") + public void test81_verifier() { + int result = test81(); + Asserts.assertEQ(result, 10*rI); + } + + // Test check for null free array when storing to inline tpye array + @Test + public void test82(Object[] dst, Object v) { + dst[0] = v; + } + + @Run(test = "test82") + public void test82_verifier(RunInfo info) { + MyValue2[] dst = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 1, MyValue2.DEFAULT); + test82(dst, testValue2); + if (!info.isWarmUp()) { + try { + test82(dst, null); + throw new RuntimeException("No ArrayStoreException thrown"); + } catch (NullPointerException e) { + // Expected + } + } + } + + @Test + @IR(failOn = {ALLOC}) + public void test83(Object[] dst, Object v, boolean flag) { + if (dst == null) { // null check + } + if (flag) { + if (dst.getClass() == MyValue1[].class) { // trigger split if + } + } else { + dst = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 1, MyValue2.DEFAULT); // constant null free property + } + dst[0] = v; + } + + @Run(test = "test83") + @Warmup(10000) + public void test83_verifier(RunInfo info) { + MyValue2[] dst = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 1, MyValue2.DEFAULT); + test83(dst, testValue2, false); + test83(dst, testValue2, true); + if (!info.isWarmUp()) { + try { + test83(dst, null, true); + throw new RuntimeException("No ArrayStoreException thrown"); + } catch (NullPointerException e) { + // Expected + } + } + } + + private void rerun_and_recompile_for(Method m, int num, Runnable test) { + for (int i = 1; i < num; i++) { + test.run(); + + if (!TestFramework.isCompiled(m)) { + TestFramework.compile(m, CompLevel.C2); + } + } + } + + // Tests for the Loop Unswitching optimization + // Should make 2 copies of the loop, one for non flattened arrays, one for other cases. + @Test + @IR(applyIf = {"UseArrayFlattening", "true"}, + counts = {COUNTED_LOOP_MAIN, "= 2"}) + @IR(applyIf = {"UseArrayFlattening", "false"}, + counts = {COUNTED_LOOP_MAIN, "= 1"}) + public void test84(Object[] src, Object[] dst) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @Run(test = "test84") + @Warmup(0) + public void test84_verifier(RunInfo info) { + MyValue2[] src = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 100, MyValue2.DEFAULT); + Arrays.fill(src, testValue2); + MyValue2[] dst = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 100, MyValue2.DEFAULT); + rerun_and_recompile_for(info.getTest(), 10, + () -> { test84(src, dst); + Asserts.assertTrue(Arrays.equals(src, dst)); }); + } + + @Test + @IR(applyIf = {"UseArrayFlattening", "true"}, + counts = {COUNTED_LOOP, "= 2", LOAD_UNKNOWN_INLINE, "= 1"}) + public void test85(Object[] src, Object[] dst) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @Run(test = "test85") + @Warmup(0) + public void test85_verifier(RunInfo info) { + Object[] src = new Object[100]; + Arrays.fill(src, new Object()); + src[0] = null; + Object[] dst = new Object[100]; + rerun_and_recompile_for(info.getTest(), 10, + () -> { test85(src, dst); + Asserts.assertTrue(Arrays.equals(src, dst)); }); + } + + @Test + @IR(applyIf = {"UseArrayFlattening", "true"}, + counts = {COUNTED_LOOP, "= 2"}) + public void test86(Object[] src, Object[] dst) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @Run(test = "test86") + @Warmup(0) + public void test86_verifier(RunInfo info) { + MyValue2[] src = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 100, MyValue2.DEFAULT); + Arrays.fill(src, testValue2); + Object[] dst = new Object[100]; + rerun_and_recompile_for(info.getTest(), 10, + () -> { test86(src, dst); + Asserts.assertTrue(Arrays.equals(src, dst)); }); + } + + @Test + @IR(applyIf = {"UseArrayFlattening", "true"}, + counts = {COUNTED_LOOP_MAIN, "= 2"}) + @IR(applyIf = {"UseArrayFlattening", "false"}, + counts = {COUNTED_LOOP_MAIN, "= 1"}) + public void test87(Object[] src, Object[] dst) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @Run(test = "test87") + @Warmup(0) + public void test87_verifier(RunInfo info) { + Object[] src = new Object[100]; + Arrays.fill(src, testValue2); + MyValue2[] dst = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 100, MyValue2.DEFAULT); + + rerun_and_recompile_for(info.getTest(), 10, + () -> { test87(src, dst); + Asserts.assertTrue(Arrays.equals(src, dst)); }); + } + + @Test + @IR(applyIf = {"UseArrayFlattening", "true"}, + counts = {COUNTED_LOOP_MAIN, "= 2"}) + @IR(applyIf = {"UseArrayFlattening", "false"}, + counts = {COUNTED_LOOP_MAIN, "= 0"}) + public void test88(Object[] src1, Object[] dst1, Object[] src2, Object[] dst2) { + for (int i = 0; i < src1.length; i++) { + dst1[i] = src1[i]; + dst2[i] = src2[i]; + } + } + + @Run(test = "test88") + @Warmup(0) + public void test88_verifier(RunInfo info) { + MyValue2[] src1 = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 100, MyValue2.DEFAULT); + Arrays.fill(src1, testValue2); + MyValue2[] dst1 = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 100, MyValue2.DEFAULT); + Object[] src2 = new Object[100]; + Arrays.fill(src2, new Object()); + Object[] dst2 = new Object[100]; + + rerun_and_recompile_for(info.getTest(), 10, + () -> { test88(src1, dst1, src2, dst2); + Asserts.assertTrue(Arrays.equals(src1, dst1)); + Asserts.assertTrue(Arrays.equals(src2, dst2)); }); + } + + @Test + public boolean test89(Object obj) { + return obj.getClass() == NonValueClass.class; + } + + @Run(test = "test89") + public void test89_verifier() { + Asserts.assertTrue(test89(new NonValueClass(42))); + Asserts.assertFalse(test89(new Object())); + } + + @Test + public NonValueClass test90(Object obj) { + return (NonValueClass)obj; + } + + @Run(test = "test90") + public void test90_verifier() { + test90(new NonValueClass(42)); + try { + test90(new Object()); + throw new RuntimeException("ClassCastException expected"); + } catch (ClassCastException e) { + // Expected + } + } + + @Test + public boolean test91(Object obj) { + return obj.getClass() == MyValue2[].class; + } + + @Run(test = "test91") + public void test91_verifier() { + Asserts.assertTrue(test91((MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 1, MyValue2.DEFAULT))); + Asserts.assertTrue(test91(new MyValue2[1])); + Asserts.assertFalse(test91(new Object())); + } + + @LooselyConsistentValue + static value class Test92Value { + int field; + + public Test92Value() { + field = 0x42; + } + } + + @Test + // TODO 8355382 The optimization only applies to null-free, flat arrays + @IR(applyIfAnd = {"UseArrayFlattening", "true", "UseNullableValueFlattening", "false"}, + counts = {CLASS_CHECK_TRAP, "= 2"}, + failOn = {LOAD_UNKNOWN_INLINE, ALLOC, MEMBAR}) + public Object test92(Object[] array) { + // Dummy loops to ensure we run enough passes of split if + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + for (int k = 0; k < 2; k++) { + } + } + } + return (NonValueClass)array[0]; + } + + @Run(test = "test92") + @Warmup(10000) + public void test92_verifier() { + Object[] array = new Object[1]; + Object obj = new NonValueClass(rI); + array[0] = obj; + Object result = test92(array); + Asserts.assertEquals(result, obj); + } + + // If the class check succeeds, the flattened array check that + // precedes will never succeed and the flat array branch should + // trigger an uncommon trap. + @Test + public Object test93(Object[] array) { + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + } + } + + return (NonValueClass)array[0]; + } + + @Run(test = "test93") + @Warmup(10000) + public void test93_verifier(RunInfo info) { + if (info.isWarmUp()) { + Object[] array = new Object[1]; + array[0] = new NonValueClass(42); + Object result = test93(array); + Asserts.assertEquals(((NonValueClass)result).x, 42); + } else { + Object[] array = (Test92Value[])ValueClass.newNullRestrictedNonAtomicArray(Test92Value.class, 1, new Test92Value()); + Method m = info.getTest(); + int extra = 3; + for (int j = 0; j < extra; j++) { + for (int i = 0; i < 10; i++) { + try { + test93(array); + } catch (ClassCastException cce) { + } + } + boolean compiled = TestFramework.isCompiled(m); + boolean compilationSkipped = info.isCompilationSkipped(); + Asserts.assertTrue(compilationSkipped || compiled || (j != extra-1)); + if (!compilationSkipped && !compiled) { + TestFramework.compile(m, CompLevel.ANY); + } + } + } + } + + @Test + // TODO 8355382 The optimization only applies to null-free, flat arrays + @IR(applyIfAnd = {"UseArrayFlattening", "true", "UseNullableValueFlattening", "false"}, + counts = {CLASS_CHECK_TRAP, "= 2", LOOP, "= 1"}, + failOn = {LOAD_UNKNOWN_INLINE, ALLOC, MEMBAR}) + public int test94(Object[] array) { + int res = 0; + for (int i = 1; i < 4; i *= 2) { + Object v = array[i]; + res += ((NonValueClass)v).x; + } + return res; + } + + @Run(test = "test94") + @Warmup(10000) + public void test94_verifier() { + Object[] array = new Object[4]; + Object obj = new NonValueClass(rI); + array[0] = obj; + array[1] = obj; + array[2] = obj; + array[3] = obj; + int result = test94(array); + Asserts.assertEquals(result, rI * 2); + } + + @Test + public boolean test95(Object o1, Object o2) { + return o1 == o2; + } + + @Run(test = "test95") + @Warmup(10000) + public void test95_verifier() { + Object o1 = new Object(); + Object o2 = new Object(); + Asserts.assertTrue(test95(o1, o1)); + Asserts.assertTrue(test95(null, null)); + Asserts.assertFalse(test95(o1, null)); + Asserts.assertFalse(test95(o1, o2)); + } + + @Test + public boolean test96(Object o1, Object o2) { + return o1 == o2; + } + + @Run(test = "test96") + @Warmup(10000) + public void test96_verifier(RunInfo info) { + Object o1 = new Object(); + Object o2 = new Object(); + Asserts.assertTrue(test96(o1, o1)); + Asserts.assertFalse(test96(o1, o2)); + if (!info.isWarmUp()) { + Asserts.assertTrue(test96(null, null)); + Asserts.assertFalse(test96(o1, null)); + } + } + + // Abstract class tests + + @DontInline + public MyAbstract test97_dontinline1(MyAbstract o) { + return o; + } + + @DontInline + public MyValue1 test97_dontinline2(MyAbstract o) { + return (MyValue1)o; + } + + @ForceInline + public MyAbstract test97_inline1(MyAbstract o) { + return o; + } + + @ForceInline + public MyValue1 test97_inline2(MyAbstract o) { + return (MyValue1)o; + } + + @Test + public MyValue1 test97() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + vt = (MyValue1)test97_dontinline1(vt); + vt = test97_dontinline2(vt); + vt = (MyValue1)test97_inline1(vt); + vt = test97_inline2(vt); + return vt; + } + + @Run(test = "test97") + public void test97_verifier() { + Asserts.assertEQ(test97().hash(), hash()); + } + + // Test storing/loading inline types to/from abstract and inline type fields + MyAbstract abstractField1 = null; + MyAbstract abstractField2 = null; + MyAbstract abstractField3 = null; + MyAbstract abstractField4 = null; + MyAbstract abstractField5 = null; + MyAbstract abstractField6 = null; + + @DontInline + public MyAbstract readValueField5AsAbstract() { + return (MyAbstract)valueField5; + } + + @DontInline + public MyAbstract readStaticValueField4AsAbstract() { + return (MyAbstract)staticValueField4; + } + + @Test + public long test98(MyValue1 vt1, MyAbstract vt2) { + abstractField1 = vt1; + abstractField2 = (MyValue1)vt2; + abstractField3 = MyValue1.createWithFieldsInline(rI, rL); + abstractField4 = MyValue1.createWithFieldsDontInline(rI, rL); + abstractField5 = valueField1; + abstractField6 = valueField3; + valueField1 = (MyValue1)abstractField1; + valueField2 = (MyValue1)vt2; + valueField3 = (MyValue1)vt2; + staticValueField1 = (MyValue1)abstractField1; + staticValueField2 = (MyValue1)vt1; + // Don't inline these methods because reading NULL will trigger a deoptimization + if (readValueField5AsAbstract() != null || readStaticValueField4AsAbstract() != null) { + throw new RuntimeException("Should be null"); + } + return ((MyValue1)abstractField1).hash() + ((MyValue1)abstractField2).hash() + + ((MyValue1)abstractField3).hash() + ((MyValue1)abstractField4).hash() + + ((MyValue1)abstractField5).hash() + ((MyValue1)abstractField6).hash() + + valueField1.hash() + valueField2.hash() + valueField3.hash() + valueField4.hashPrimitive() + + staticValueField1.hash() + staticValueField2.hash() + staticValueField3.hashPrimitive(); + } + + @Run(test = "test98") + public void test98_verifier() { + MyValue1 vt = testValue1; + MyValue1 def = MyValue1.createDefaultDontInline(); + long result = test98(vt, vt); + Asserts.assertEQ(result, 11*vt.hash() + 2*def.hashPrimitive()); + } + + value class MyObject2 extends MyAbstract { + public int x; + + public MyObject2(int x) { + this.x = x; + } + + @ForceInline + public long hash() { + return x; + } + } + + // Test merging inline types and abstract classes + @Test + public MyAbstract test99(int state) { + MyAbstract res = null; + if (state == 0) { + res = new MyObject2(rI); + } else if (state == 1) { + res = MyValue1.createWithFieldsInline(rI, rL); + } else if (state == 2) { + res = MyValue1.createWithFieldsDontInline(rI, rL); + } else if (state == 3) { + res = (MyValue1)objectField1; + } else if (state == 4) { + res = valueField1; + } else if (state == 5) { + res = null; + } + return res; + } + + @Run(test = "test99") + public void test99_verifier() { + objectField1 = valueField1; + MyAbstract result = null; + result = test99(0); + Asserts.assertEQ(((MyObject2)result).x, rI); + result = test99(1); + Asserts.assertEQ(((MyValue1)result).hash(), hash()); + result = test99(2); + Asserts.assertEQ(((MyValue1)result).hash(), hash()); + result = test99(3); + Asserts.assertEQ(((MyValue1)result).hash(), hash()); + result = test99(4); + Asserts.assertEQ(((MyValue1)result).hash(), hash()); + result = test99(5); + Asserts.assertEQ(result, null); + } + + // Test merging inline types and abstract classes in loops + @Test + public MyAbstract test100(int iters) { + MyAbstract res = new MyObject2(rI); + for (int i = 0; i < iters; ++i) { + if (res instanceof MyObject2) { + res = MyValue1.createWithFieldsInline(rI, rL); + } else { + res = MyValue1.createWithFieldsInline(((MyValue1)res).x + 1, rL); + } + } + return res; + } + + @Run(test = "test100") + public void test100_verifier() { + MyObject2 result1 = (MyObject2)test100(0); + Asserts.assertEQ(result1.x, rI); + int iters = (Math.abs(rI) % 10) + 1; + MyValue1 result2 = (MyValue1)test100(iters); + MyValue1 vt = MyValue1.createWithFieldsInline(rI + iters - 1, rL); + Asserts.assertEQ(result2.hash(), vt.hash()); + } + + // Test inline types in abstract class variables that are live at safepoint + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, LOOP}) + public long test101(MyValue1 arg, boolean deopt, Method m) { + MyAbstract vt1 = MyValue1.createWithFieldsInline(rI, rL); + MyAbstract vt2 = MyValue1.createWithFieldsDontInline(rI, rL); + MyAbstract vt3 = arg; + MyAbstract vt4 = valueField1; + if (deopt) { + // uncommon trap + TestFramework.deoptimize(m); + } + return ((MyValue1)vt1).hash() + ((MyValue1)vt2).hash() + + ((MyValue1)vt3).hash() + ((MyValue1)vt4).hash(); + } + + @Run(test = "test101") + public void test101_verifier(RunInfo info) { + long result = test101(valueField1, !info.isWarmUp(), info.getTest()); + Asserts.assertEQ(result, 4*hash()); + } + + // Test comparing inline types with abstract classes + @Test + public boolean test102(Object arg) { + MyAbstract vt = MyValue1.createWithFieldsInline(rI, rL); + if (vt == arg || vt == (MyAbstract)valueField1 || vt == abstractField1 || vt == null || + arg == vt || (MyAbstract)valueField1 == vt || abstractField1 == vt || null == vt) { + return true; + } + return false; + } + + @Run(test = "test102") + public void test102_verifier() { + boolean result = test102(null); + Asserts.assertFalse(result); + } + + // An abstract class with a non-static field can never be implemented by an inline type + abstract class NoValueImplementors1 { + int field = 42; + } + + class MyObject3 extends NoValueImplementors1 { + + } + + class MyObject4 extends NoValueImplementors1 { + + } + + // Loading from an abstract class array does not require a flatness check if the abstract class has a non-static field + @Test + @IR(failOn = {ALLOC, MEMBAR, LOAD_UNKNOWN_INLINE, STORE_UNKNOWN_INLINE, INLINE_ARRAY_NULL_GUARD}) + public NoValueImplementors1 test103(NoValueImplementors1[] array, int i) { + return array[i]; + } + + @Run(test = "test103") + public void test103_verifier() { + NoValueImplementors1[] array1 = new NoValueImplementors1[3]; + MyObject3[] array2 = new MyObject3[3]; + MyObject4[] array3 = new MyObject4[3]; + NoValueImplementors1 result = test103(array1, 0); + Asserts.assertEquals(result, array1[0]); + + result = test103(array2, 1); + Asserts.assertEquals(result, array1[1]); + + result = test103(array3, 2); + Asserts.assertEquals(result, array1[2]); + } + + // Storing to an abstract class array does not require a flatness/null check if the abstract class has a non-static field + @Test + @IR(failOn = {ALLOC, LOAD_UNKNOWN_INLINE, STORE_UNKNOWN_INLINE, INLINE_ARRAY_NULL_GUARD}) + public NoValueImplementors1 test104(NoValueImplementors1[] array, NoValueImplementors1 v, MyObject3 o, int i) { + array[0] = v; + array[1] = array[0]; + array[2] = o; + return array[i]; + } + + @Run(test = "test104") + public void test104_verifier() { + MyObject4 v = new MyObject4(); + MyObject3 o = new MyObject3(); + NoValueImplementors1[] array1 = new NoValueImplementors1[3]; + MyObject3[] array2 = new MyObject3[3]; + MyObject4[] array3 = new MyObject4[3]; + NoValueImplementors1 result = test104(array1, v, o, 0); + Asserts.assertEquals(array1[0], v); + Asserts.assertEquals(array1[1], v); + Asserts.assertEquals(array1[2], o); + Asserts.assertEquals(result, v); + + result = test104(array2, o, o, 1); + Asserts.assertEquals(array2[0], o); + Asserts.assertEquals(array2[1], o); + Asserts.assertEquals(array2[2], o); + Asserts.assertEquals(result, o); + + result = test104(array3, v, null, 1); + Asserts.assertEquals(array3[0], v); + Asserts.assertEquals(array3[1], v); + Asserts.assertEquals(array3[2], null); + Asserts.assertEquals(result, v); + } + + // An abstract class with a single, non-inline implementor + abstract class NoValueImplementors2 { + + } + + class MyObject5 extends NoValueImplementors2 { + + } + + // Loading from an abstract class array does not require a flatness check if the abstract class has no inline implementor + @Test + @IR(failOn = {ALLOC, MEMBAR, LOAD_UNKNOWN_INLINE, STORE_UNKNOWN_INLINE, INLINE_ARRAY_NULL_GUARD}) + public NoValueImplementors2 test105(NoValueImplementors2[] array, int i) { + return array[i]; + } + + @Run(test = "test105") + public void test105_verifier() { + NoValueImplementors2[] array1 = new NoValueImplementors2[3]; + MyObject5[] array2 = new MyObject5[3]; + NoValueImplementors2 result = test105(array1, 0); + Asserts.assertEquals(result, array1[0]); + + result = test105(array2, 1); + Asserts.assertEquals(result, array1[1]); + } + + // Storing to an abstract class array does not require a flatness/null check if the abstract class has no inline implementor + @Test + @IR(failOn = {ALLOC, LOAD_UNKNOWN_INLINE, STORE_UNKNOWN_INLINE, INLINE_ARRAY_NULL_GUARD}) + public NoValueImplementors2 test106(NoValueImplementors2[] array, NoValueImplementors2 v, MyObject5 o, int i) { + array[0] = v; + array[1] = array[0]; + array[2] = o; + return array[i]; + } + + @Run(test = "test106") + public void test106_verifier() { + MyObject5 v = new MyObject5(); + NoValueImplementors2[] array1 = new NoValueImplementors2[3]; + MyObject5[] array2 = new MyObject5[3]; + NoValueImplementors2 result = test106(array1, v, null, 0); + Asserts.assertEquals(array1[0], v); + Asserts.assertEquals(array1[1], v); + Asserts.assertEquals(array1[2], null); + Asserts.assertEquals(result, v); + + result = test106(array2, v, v, 1); + Asserts.assertEquals(array2[0], v); + Asserts.assertEquals(array2[1], v); + Asserts.assertEquals(array2[2], v); + Asserts.assertEquals(result, v); + } + + // More tests for the Loop Unswitching optimization (similar to test84 and following) + Object oFld1, oFld2; + + @Test + @IR(applyIf = {"UseArrayFlattening", "true"}, + failOn = {STORE_UNKNOWN_INLINE, INLINE_ARRAY_NULL_GUARD}, + counts = {COUNTED_LOOP, "= 2", LOAD_UNKNOWN_INLINE, "= 2"}, + // Match on CCP since we are removing one of the unswitched loop versions later due to being empty + phase = {CompilePhase.CCP1}) + public void test107(Object[] src1, Object[] src2) { + for (int i = 0; i < src1.length; i++) { + oFld1 = src1[i]; + oFld2 = src2[i]; + } + } + + @Run(test = "test107") + @Warmup(0) + public void test107_verifier(RunInfo info) { + MyValue2[] src1 = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 100, MyValue2.DEFAULT); + Arrays.fill(src1, testValue2); + Object[] src2 = new Object[100]; + Object obj = new Object(); + Arrays.fill(src2, obj); + rerun_and_recompile_for(info.getTest(), 10, + () -> { test107(src1, src2); + Asserts.assertEquals(oFld1, testValue2); + Asserts.assertEquals(oFld2, obj); + test107(src2, src1); + Asserts.assertEquals(oFld1, obj); + Asserts.assertEquals(oFld2, testValue2); }); + } + + @Test + @IR(applyIf = {"UseArrayFlattening", "true"}, + failOn = {LOAD_UNKNOWN_INLINE, INLINE_ARRAY_NULL_GUARD}, + counts = {COUNTED_LOOP, "= 4", STORE_UNKNOWN_INLINE, "= 9"}) + public void test108(Object[] dst1, Object[] dst2, Object o1, Object o2) { + for (int i = 0; i < dst1.length; i++) { + dst1[i] = o1; + dst2[i] = o2; + } + } + + @Run(test = "test108") + @Warmup(0) + public void test108_verifier(RunInfo info) { + MyValue2[] dst1 = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 100, MyValue2.DEFAULT); + Object[] dst2 = new Object[100]; + Object o1 = new Object(); + rerun_and_recompile_for(info.getTest(), 10, + () -> { test108(dst1, dst2, testValue2, o1); + for (int i = 0; i < dst1.length; i++) { + Asserts.assertEquals(dst1[i], testValue2); + Asserts.assertEquals(dst2[i], o1); + } + test108(dst2, dst1, o1, testValue2); + for (int i = 0; i < dst1.length; i++) { + Asserts.assertEquals(dst1[i], testValue2); + Asserts.assertEquals(dst2[i], o1); + } }); + } + + // Escape analysis tests + + static interface WrapperInterface { + long value(); + + final static WrapperInterface ZERO = new LongWrapper(0); + + @ForceInline + static WrapperInterface wrap(long val) { + return (val == 0L) ? ZERO : new LongWrapper(val); + } + } + + @ForceCompileClassInitializer + @LooselyConsistentValue + static value class LongWrapper implements WrapperInterface { + @Strict + @NullRestricted + final static LongWrapper ZERO = new LongWrapper(0); + private long val; + + @ForceInline + LongWrapper(long val) { + this.val = val; + } + + @ForceInline + static LongWrapper wrap(long val) { + return (val == 0L) ? ZERO : new LongWrapper(val); + } + + @ForceInline + public long value() { + return val; + } + } + + static class InterfaceBox { + WrapperInterface content; + + @ForceInline + InterfaceBox(WrapperInterface content) { + this.content = content; + } + + @ForceInline + static InterfaceBox box_sharp(long val) { + return new InterfaceBox(LongWrapper.wrap(val)); + } + + @ForceInline + static InterfaceBox box(long val) { + return new InterfaceBox(WrapperInterface.wrap(val)); + } + } + + static class ObjectBox { + Object content; + + @ForceInline + ObjectBox(Object content) { + this.content = content; + } + + @ForceInline + static ObjectBox box_sharp(long val) { + return new ObjectBox(LongWrapper.wrap(val)); + } + + @ForceInline + static ObjectBox box(long val) { + return new ObjectBox(WrapperInterface.wrap(val)); + } + } + + static class RefBox { + LongWrapper content; + + @ForceInline + RefBox(LongWrapper content) { + this.content = content; + } + + @ForceInline + static RefBox box_sharp(long val) { + return new RefBox(LongWrapper.wrap(val)); + } + + @ForceInline + static RefBox box(long val) { + return new RefBox((LongWrapper)WrapperInterface.wrap(val)); + } + } + + static class InlineBox { + @Strict + @NullRestricted + LongWrapper content; + + @ForceInline + InlineBox(long val) { + this.content = LongWrapper.wrap(val); + } + + @ForceInline + static InlineBox box(long val) { + return new InlineBox(val); + } + } + + static class GenericBox { + T content; + + @ForceInline + static GenericBox box_sharp(long val) { + GenericBox res = new GenericBox<>(); + res.content = LongWrapper.wrap(val); + return res; + } + + @ForceInline + static GenericBox box(long val) { + GenericBox res = new GenericBox<>(); + res.content = WrapperInterface.wrap(val); + return res; + } + } + + long[] lArr = {0L, rL, 0L, rL, 0L, rL, 0L, rL, 0L, rL}; + + // Test removal of allocations when inline type instance is wrapped into box object + @Test + @IR(failOn = {ALLOC, MEMBAR}, + counts = {PREDICATE_TRAP, "= 1"}) + public long test109() { + long res = 0; + for (int i = 0; i < lArr.length; i++) { + res += InterfaceBox.box(lArr[i]).content.value(); + } + return res; + } + + @Run(test = "test109") + @Warmup(10000) // Make sure interface calls are inlined + public void test109_verifier() { + long res = test109(); + Asserts.assertEquals(res, 5*rL); + } + + @Test + @IR(failOn = {ALLOC, MEMBAR}, + counts = {PREDICATE_TRAP, "= 1"}) + @IR(failOn = {ALLOC, MEMBAR}) + public long test109_sharp() { + long res = 0; + for (int i = 0; i < lArr.length; i++) { + res += InterfaceBox.box_sharp(lArr[i]).content.value(); + } + return res; + } + + @Run(test = "test109_sharp") + @Warmup(10000) // Make sure interface calls are inlined + public void test109_sharp_verifier() { + long res = test109_sharp(); + Asserts.assertEquals(res, 5*rL); + } + + // Same as test109 but with ObjectBox + @Test + @IR(failOn = {ALLOC, MEMBAR}, + counts = {PREDICATE_TRAP, "= 1"}) + public long test110() { + long res = 0; + for (int i = 0; i < lArr.length; i++) { + res += ((WrapperInterface)ObjectBox.box(lArr[i]).content).value(); + } + return res; + } + + @Run(test = "test110") + @Warmup(10000) // Make sure interface calls are inlined + public void test110_verifier() { + long res = test110(); + Asserts.assertEquals(res, 5*rL); + } + + @Test + @IR(failOn = {ALLOC, MEMBAR}, + counts = {PREDICATE_TRAP, "= 1"}) + @IR(failOn = {ALLOC, MEMBAR}) + public long test110_sharp() { + long res = 0; + for (int i = 0; i < lArr.length; i++) { + res += ((WrapperInterface)ObjectBox.box_sharp(lArr[i]).content).value(); + } + return res; + } + + @Run(test = "test110_sharp") + @Warmup(10000) // Make sure interface calls are inlined + public void test110_sharp_verifier() { + long res = test110_sharp(); + Asserts.assertEquals(res, 5*rL); + } + + // Same as test109 but with RefBox + @Test + @IR(failOn = {ALLOC, MEMBAR}, + counts = {PREDICATE_TRAP, "= 1"}) + public long test111() { + long res = 0; + for (int i = 0; i < lArr.length; i++) { + res += RefBox.box(lArr[i]).content.value(); + } + return res; + } + + @Run(test = "test111") + public void test111_verifier() { + long res = test111(); + Asserts.assertEquals(res, 5*rL); + } + + @Test + @IR(failOn = {ALLOC, MEMBAR}, + counts = {PREDICATE_TRAP, "= 1"}) + public long test111_sharp() { + long res = 0; + for (int i = 0; i < lArr.length; i++) { + res += RefBox.box_sharp(lArr[i]).content.value(); + } + return res; + } + + @Run(test = "test111_sharp") + public void test111_sharp_verifier() { + long res = test111_sharp(); + Asserts.assertEquals(res, 5*rL); + } + + // Same as test109 but with InlineBox + @Test + @IR(failOn = {ALLOC, MEMBAR}, + counts = {PREDICATE_TRAP, "= 1"}) + public long test112() { + long res = 0; + for (int i = 0; i < lArr.length; i++) { + res += InlineBox.box(lArr[i]).content.value(); + } + return res; + } + + @Run(test = "test112") + public void test112_verifier() { + long res = test112(); + Asserts.assertEquals(res, 5*rL); + } + + // Same as test109 but with GenericBox + @Test + @IR(failOn = {ALLOC, MEMBAR}, + counts = {PREDICATE_TRAP, "= 1"}) + public long test113() { + long res = 0; + for (int i = 0; i < lArr.length; i++) { + res += GenericBox.box(lArr[i]).content.value(); + } + return res; + } + + @Run(test = "test113") + @Warmup(10000) // Make sure interface calls are inlined + public void test113_verifier() { + long res = test113(); + Asserts.assertEquals(res, 5*rL); + } + + @Test + @IR(failOn = {ALLOC, MEMBAR}, + counts = {PREDICATE_TRAP, "= 1"}) + public long test113_sharp() { + long res = 0; + for (int i = 0; i < lArr.length; i++) { + res += GenericBox.box_sharp(lArr[i]).content.value(); + } + return res; + } + + @Run(test = "test113_sharp") + @Warmup(10000) // Make sure interface calls are inlined + public void test113_sharp_verifier() { + long res = test113_sharp(); + Asserts.assertEquals(res, 5*rL); + } + + static interface WrapperInterface2 { + public long value(); + + static final InlineWrapper ZERO = new InlineWrapper(0); + + @ForceInline + public static WrapperInterface2 wrap(long val) { + return (val == 0) ? ZERO.content : new LongWrapper2(val); + } + + @ForceInline + public static WrapperInterface2 wrap_dynamic(long val) { + return (val == 0) ? new LongWrapper2(0) : new LongWrapper2(val); + } + } + + @LooselyConsistentValue + static value class LongWrapper2 implements WrapperInterface2 { + private long val; + + @ForceInline + public LongWrapper2(long val) { + this.val = val; + } + + @ForceInline + public long value() { + return val; + } + } + + @LooselyConsistentValue + static value class InlineWrapper { + WrapperInterface2 content; + + @ForceInline + public InlineWrapper(long val) { + content = new LongWrapper2(val); + } + } + + static class InterfaceBox2 { + WrapperInterface2 content; + + @ForceInline + public InterfaceBox2(long val, boolean def) { + this.content = def ? WrapperInterface2.wrap_dynamic(val) : WrapperInterface2.wrap(val); + } + + @ForceInline + static InterfaceBox2 box(long val) { + return new InterfaceBox2(val, false); + } + + @ForceInline + static InterfaceBox2 box_dynamic(long val) { + return new InterfaceBox2(val, true); + } + } + + // Same as tests above but with ZERO hidden in field of another inline type + @Test + @IR(failOn = {ALLOC, MEMBAR}, + counts = {PREDICATE_TRAP, "= 1"}) + public long test114() { + long res = 0; + for (int i = 0; i < lArr.length; i++) { + res += InterfaceBox2.box(lArr[i]).content.value(); + } + return res; + } + + @Run(test = "test114") + @Warmup(10000) + public void test114_verifier() { + long res = test114(); + Asserts.assertEquals(res, 5*rL); + } + + // Same as test114 but with dynamic instead of constant ZERO field + @Test + @IR(failOn = {ALLOC, MEMBAR}, + counts = {PREDICATE_TRAP, "= 1"}) + public long test115() { + long res = 0; + for (int i = 0; i < lArr.length; i++) { + res += InterfaceBox2.box_dynamic(lArr[i]).content.value(); + } + return res; + } + + @Run(test = "test115") + @Warmup(10000) + public void test115_verifier() { + long res = test115(); + Asserts.assertEquals(res, 5*rL); + } + + @Strict + @NullRestricted + static MyValueEmpty fEmpty1 = new MyValueEmpty(); + static MyValueEmpty fEmpty2 = new MyValueEmpty(); + @Strict + @NullRestricted + MyValueEmpty fEmpty3 = new MyValueEmpty(); + MyValueEmpty fEmpty4 = new MyValueEmpty(); + + // Test fields loads/stores with empty inline types + @Test + public void test116() { + fEmpty1 = fEmpty4; + fEmpty2 = fEmpty1; + fEmpty3 = fEmpty2; + fEmpty4 = fEmpty3; + } + + @Run(test = "test116") + public void test116_verifier() { + test116(); + Asserts.assertEquals(fEmpty1, fEmpty2); + Asserts.assertEquals(fEmpty2, fEmpty3); + Asserts.assertEquals(fEmpty3, fEmpty4); + } + + // Test array loads/stores with empty inline types + @Test + public MyValueEmpty test117(MyValueEmpty[] arr1, MyValueEmpty[] arr2) { + arr1[0] = arr2[0]; + arr2[0] = new MyValueEmpty(); + return arr1[0]; + } + + @Run(test = "test117") + public void test117_verifier() { + MyValueEmpty[] arr1 = new MyValueEmpty[] { new MyValueEmpty() }; + MyValueEmpty res = test117(arr1, arr1); + Asserts.assertEquals(res, new MyValueEmpty()); + // TODO 8366668 Re-enable + // Asserts.assertEquals(arr1[0], new MyValueEmpty()); + } + + // Test acmp with empty inline types + @Test + public boolean test118(MyValueEmpty v1, MyValueEmpty v2, Object o1) { + return (v1 == v2) && (v2 == o1); + } + + @Run(test = "test118") + public void test118_verifier() { + boolean res = test118(new MyValueEmpty(), new MyValueEmpty(), new MyValueEmpty()); + Asserts.assertTrue(res); + } + + @LooselyConsistentValue + static value class EmptyContainer { + @Strict + @NullRestricted + private MyValueEmpty empty = new MyValueEmpty(); + } + + @LooselyConsistentValue + static value class MixedContainer { + public int val = 0; + @Strict + @NullRestricted + private EmptyContainer empty = new EmptyContainer(); + } + + @Strict + @NullRestricted + static final MyValueEmpty empty = new MyValueEmpty(); + + @Strict + @NullRestricted + static final EmptyContainer emptyC = new EmptyContainer(); + + @Strict + @NullRestricted + static final MixedContainer mixedContainer = new MixedContainer(); + + // Test re-allocation of empty inline type array during deoptimization + @Test + public void test119(boolean deopt, Method m) { + MyValueEmpty[] array1 = new MyValueEmpty[] { empty }; + EmptyContainer[] array2 = (EmptyContainer[])ValueClass.newNullRestrictedNonAtomicArray(EmptyContainer.class, 1, emptyC); + array2[0] = emptyC; + MixedContainer[] array3 = (MixedContainer[])ValueClass.newNullRestrictedNonAtomicArray(MixedContainer.class, 1, mixedContainer); + array3[0] = mixedContainer; + if (deopt) { + // uncommon trap + TestFramework.deoptimize(m); + } + // TODO 8366668 Re-enable + // Asserts.assertEquals(array1[0], empty); + Asserts.assertEquals(array2[0], emptyC); + Asserts.assertEquals(array3[0], mixedContainer); + } + + @Run(test = "test119") + public void test119_verifier(RunInfo info) { + test119(!info.isWarmUp(), info.getTest()); + } + + // Test optimization of empty inline type field stores + @Test + @IR(failOn = {ALLOC, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, NULL_CHECK_TRAP, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public void test120() { + fEmpty1 = empty; + fEmpty3 = empty; + // fEmpty2 and fEmpty4 could be null, store can't be removed + } + + @Run(test = "test120") + public void test120_verifier() { + test120(); + Asserts.assertEquals(fEmpty1, empty); + } + + // Test removal of empty inline type field loads + @Test + @IR(failOn = {LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, FIELD_ACCESS, NULL_CHECK_TRAP, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public boolean test121() { + return fEmpty1.equals(fEmpty3); + // fEmpty2 and fEmpty4 could be null, load can't be removed + } + + @Run(test = "test121") + public void test121_verifier() { + boolean res = test121(); + Asserts.assertTrue(res); + } + + // Verify that empty inline type field loads check for null holder + @Test + @IR(applyIf = {"InlineTypeReturnedAsFields", "true"}, + failOn = {ALLOC}) + public MyValueEmpty test122(TestLWorld t) { + return t.fEmpty3; + } + + @Run(test = "test122") + public void test122_verifier() { + MyValueEmpty res = test122(this); + Asserts.assertEquals(res, new MyValueEmpty()); + try { + test122(null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + // Verify that empty inline type field stores check for null holder + @Test + @IR(failOn = {ALLOC}) + public void test123(TestLWorld t) { + t.fEmpty3 = new MyValueEmpty(); + } + + @Run(test = "test123") + public void test123_verifier() { + test123(this); + Asserts.assertEquals(fEmpty3, new MyValueEmpty()); + try { + test123(null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + // acmp doesn't need substitutability test when one input is known + // not to be a value type + @Test + @IR(failOn = SUBSTITUTABILITY_TEST) + public boolean test124(NonValueClass o1, Object o2) { + return o1 == o2; + } + + @Run(test = "test124") + public void test124_verifier() { + NonValueClass obj = new NonValueClass(rI); + test124(obj, obj); + test124(obj, testValue1); + } + + // acmp doesn't need substitutability test when one input is null + @Test + @IR(failOn = {SUBSTITUTABILITY_TEST}) + public boolean test125(Object o1) { + Object o2 = null; + return o1 == o2; + } + + @Run(test = "test125") + public void test125_verifier() { + test125(testValue1); + test125(null); + } + + // Test inline type that can only be scalarized after loop opts + @Test + @IR(failOn = {ALLOC, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS}) + public long test126(boolean trap) { + MyValue2 nonNull = MyValue2.createWithFieldsInline(rI, rD); + MyValue2 val = null; + + for (int i = 0; i < 4; i++) { + if ((i % 2) == 0) { + val = nonNull; + } + } + // 'val' is always non-null here but that's only known after loop opts + if (trap) { + // Uncommon trap with an inline input that can only be scalarized after loop opts + return val.hash(); + } + return 0; + } + + @Run(test = "test126") + @Warmup(10000) + public void test126_verifier(RunInfo info) { + long res = test126(false); + Asserts.assertEquals(res, 0L); + if (!info.isWarmUp()) { + res = test126(true); + Asserts.assertEquals(res, testValue2.hash()); + } + } + + // Same as test126 but with interface type + @Test + @IR(failOn = {ALLOC, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS}) + public long test127(boolean trap) { + MyValue2 nonNull = MyValue2.createWithFieldsInline(rI, rD); + MyInterface val = null; + + for (int i = 0; i < 4; i++) { + if ((i % 2) == 0) { + val = nonNull; + } + } + // 'val' is always non-null here but that's only known after loop opts + if (trap) { + // Uncommon trap with an inline input that can only be scalarized after loop opts + return val.hash(); + } + return 0; + } + + @Run(test = "test127") + @Warmup(10000) + public void test127_verifier(RunInfo info) { + long res = test127(false); + Asserts.assertEquals(res, 0L); + if (!info.isWarmUp()) { + res = test127(true); + Asserts.assertEquals(res, testValue2.hash()); + } + } + + // Test inline type that can only be scalarized after CCP + @Test + @IR(failOn = {ALLOC, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS}) + public long test128(boolean trap) { + MyValue2 nonNull = MyValue2.createWithFieldsInline(rI, rD); + MyValue2 val = null; + + int limit = 2; + for (; limit < 4; limit *= 2); + for (int i = 2; i < limit; i++) { + val = nonNull; + } + // 'val' is always non-null here but that's only known after CCP + if (trap) { + // Uncommon trap with an inline input that can only be scalarized after CCP + return val.hash(); + } + return 0; + } + + @Run(test = "test128") + @Warmup(10000) + public void test128_verifier(RunInfo info) { + long res = test128(false); + Asserts.assertEquals(res, 0L); + if (!info.isWarmUp()) { + res = test128(true); + Asserts.assertEquals(res, testValue2.hash()); + } + } + + // Same as test128 but with interface type + @Test + @IR(failOn = {ALLOC, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS}) + public long test129(boolean trap) { + MyValue2 nonNull = MyValue2.createWithFieldsInline(rI, rD); + MyInterface val = null; + + int limit = 2; + for (; limit < 4; limit *= 2); + for (int i = 0; i < limit; i++) { + val = nonNull; + } + // 'val' is always non-null here but that's only known after CCP + if (trap) { + // Uncommon trap with an inline input that can only be scalarized after CCP + return val.hash(); + } + return 0; + } + + @Run(test = "test129") + @Warmup(10000) + public void test129_verifier(RunInfo info) { + long res = test129(false); + Asserts.assertEquals(res, 0L); + if (!info.isWarmUp()) { + res = test129(true); + Asserts.assertEquals(res, testValue2.hash()); + } + } + + // Lock on inline type (known after inlining) + @ForceInline + public Object test130_inlinee() { + return MyValue1.createWithFieldsInline(rI, rL); + } + + @Test + @IR(failOn = {LOAD_OF_ANY_KLASS}, + // LockNode keeps MyValue1 allocation alive up until macro expansion which in turn keeps MyValue2 + // alloc alive. Although the MyValue1 allocation is removed (unused), MyValue2 is expanded first + // and therefore stays. + counts = {ALLOC_OF_MYVALUE_KLASS, "<= 1", STORE_OF_ANY_KLASS, "<= 1"}) + public void test130() { + Object obj = test130_inlinee(); + synchronized (obj) { + throw new RuntimeException("test130 failed: synchronization on inline type should not succeed"); + } + } + + @Run(test = "test130") + public void test130_verifier() { + try { + test130(); + throw new RuntimeException("test130 failed: no exception thrown"); + } catch (IdentityException ex) { + // Expected + } + } + + // Same as test130 but with field load instead of allocation + @ForceInline + public Object test131_inlinee() { + return testValue1; + } + + @Test + @IR(failOn = {ALLOC}) + public void test131() { + Object obj = test131_inlinee(); + synchronized (obj) { + throw new RuntimeException("test131 failed: synchronization on inline type should not succeed"); + } + } + + @Run(test = "test131") + public void test131_verifier() { + try { + test131(); + throw new RuntimeException("test131 failed: no exception thrown"); + } catch (IdentityException ex) { + // Expected + } + } + + // Test locking on object that is known to be an inline type only after CCP + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS}) + public void test132() { + MyValue2 vt = MyValue2.createWithFieldsInline(rI, rD); + Object obj = new NonValueClass(42); + + int limit = 2; + for (; limit < 4; limit *= 2); + for (int i = 2; i < limit; i++) { + obj = vt; + } + synchronized (obj) { + throw new RuntimeException("test132 failed: synchronization on inline type should not succeed"); + } + } + + @Run(test = "test132") + @Warmup(10000) + public void test132_verifier() { + try { + test132(); + throw new RuntimeException("test132 failed: no exception thrown"); + } catch (IdentityException ex) { + // Expected + } + } + + // Test conditional locking on inline type and non-escaping object + @Test + public void test133(boolean b) { + Object obj = b ? new NonValueClass(rI) : MyValue2.createWithFieldsInline(rI, rD); + synchronized (obj) { + if (!b) { + throw new RuntimeException("test133 failed: synchronization on inline type should not succeed"); + } + } + } + + @Run(test = "test133") + public void test133_verifier() { + test133(true); + try { + test133(false); + throw new RuntimeException("test133 failed: no exception thrown"); + } catch (IdentityException ex) { + // Expected + } + } + + // Variant with non-scalarized inline type + @Test + @IR(failOn = {ALLOC}) + public void test134(boolean b) { + Object obj = null; + if (b) { + obj = MyValue2.createWithFieldsInline(rI, rD); + } + synchronized (obj) { + + } + } + + @Run(test = "test134") + public void test134_verifier() { + try { + test134(true); + throw new RuntimeException("test134 failed: no exception thrown"); + } catch (IdentityException ex) { + // Expected + } + } + + // Test that acmp of the same inline object is removed + @Test + @IR(failOn = {ALLOC, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, NULL_CHECK_TRAP, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public boolean test135() { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + return val == val; + } + + @Run(test = "test135") + public void test135_verifier() { + Asserts.assertTrue(test135()); + } + + // Same as test135 but with null + @Test + @IR(failOn = {ALLOC, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, NULL_CHECK_TRAP, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public boolean test136(boolean b) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + if (b) { + val = null; + } + return val == val; + } + + @Run(test = "test136") + public void test136_verifier() { + Asserts.assertTrue(test136(false)); + Asserts.assertTrue(test136(true)); + } + + // Test that acmp of different inline objects with same content is removed + @Test + // TODO 8228361 + // @IR(failOn = {ALLOC, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, NULL_CHECK_TRAP, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public boolean test137(int i) { + MyValue2 val1 = MyValue2.createWithFieldsInline(i, rD); + MyValue2 val2 = MyValue2.createWithFieldsInline(i, rD); + return val1 == val2; + } + + @Run(test = "test137") + public void test137_verifier() { + Asserts.assertTrue(test137(rI)); + } + + // Same as test137 but with null + @Test + // TODO 8228361 + // @IR(failOn = {ALLOC, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, NULL_CHECK_TRAP, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public boolean test138(int i, boolean b) { + MyValue2 val1 = MyValue2.createWithFieldsInline(i, rD); + MyValue2 val2 = MyValue2.createWithFieldsInline(i, rD); + if (b) { + val1 = null; + val2 = null; + } + return val1 == val2; + } + + @Run(test = "test138") + public void test138_verifier() { + Asserts.assertTrue(test138(rI, false)); + Asserts.assertTrue(test138(rI, true)); + } + + @LooselyConsistentValue + static value class Test139Value { + Object obj = null; + @Strict + @NullRestricted + MyValueEmpty empty = new MyValueEmpty(); + } + + @LooselyConsistentValue + static value class Test139Wrapper { + @Strict + @NullRestricted + Test139Value value = new Test139Value(); + } + + @Test + @IR(applyIf = {"InlineTypeReturnedAsFields", "true"}, + failOn = {ALLOC}) + @IR(failOn = {LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public MyValueEmpty test139() { + Test139Wrapper w = new Test139Wrapper(); + return w.value.empty; + } + + @Run(test = "test139") + public void test139_verifier() { + MyValueEmpty empty = test139(); + Asserts.assertEquals(empty, new MyValueEmpty()); + } + + // Test calling a method on a loaded but not linked inline type + @LooselyConsistentValue + value class Test140Value { + int x = 0; + + public int get() { + return x; + } + } + + @Test + @IR(failOn = {ALLOC}) + public int test140() { + Test140Value vt = new Test140Value(); + return vt.get(); + } + + @Run(test = "test140") + @Warmup(0) + public void test140_verifier() { + int result = test140(); + Asserts.assertEquals(result, 0); + } + + // Test calling a method on a linked but not initialized inline type + @LooselyConsistentValue + value class Test141Value { + int x = 0; + + public int get() { + return x; + } + } + + @Test + @IR(failOn = {ALLOC}) + public int test141() { + Test141Value vt = new Test141Value(); + return vt.get(); + } + + @Run(test = "test141") + @Warmup(0) + public void test141_verifier() { + int result = test141(); + Asserts.assertEquals(result, 0); + } + + // Test that virtual calls on inline type receivers are properly inlined + @Test + @IR(failOn = {ALLOC, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS}) + public long test142() { + MyValue2 nonNull = MyValue2.createWithFieldsInline(rI, rD); + MyInterface val = null; + + for (int i = 0; i < 4; i++) { + if ((i % 2) == 0) { + val = nonNull; + } + } + return val.hash(); + } + + @Run(test = "test142") + public void test142_verifier() { + long res = test142(); + Asserts.assertEquals(res, testValue2.hash()); + } + + // Test merging of buffered inline types + @Test + public Object test144(int i) { + if (i == 0) { + return MyValue1.createDefaultInline(); + } else if (i == 1) { + return testValue1; + } else { + return MyValue1.createDefaultInline(); + } + } + + @Run(test = "test144") + public void test144_verifier() { + Asserts.assertEquals(test144(0), MyValue1.createDefaultInline()); + Asserts.assertEquals(test144(1), testValue1); + Asserts.assertEquals(test144(2), MyValue1.createDefaultInline()); + } + + // Tests writing an array element with a (statically known) incompatible type + private static final MethodHandle setArrayElementIncompatibleRef = InstructionHelper.buildMethodHandle(MethodHandles.lookup(), + "setArrayElementIncompatibleRef", + MethodType.methodType(void.class, TestLWorld.class, MyValue1[].class, int.class, MyValue2.class), + CODE -> { + CODE. + aload(1). + iload(2). + aload(3). + aastore(). + return_(); + }); + + // Test inline type connected to result node + @Test + @IR(failOn = {ALLOC}) + public MyValue1 test146(Object obj) { + return (MyValue1)obj; + } + + @Run(test = "test146") + @Warmup(10000) + public void test146_verifier() { + Asserts.assertEQ(test146(testValue1), testValue1); + } + + @ForceInline + public Object test148_helper(Object obj) { + return (MyValue1)obj; + } + + // Same as test146 but with helper method + @Test + public Object test148(Object obj) { + return test148_helper(obj); + } + + @Run(test = "test148") + @Warmup(10000) + public void test148_verifier() { + Asserts.assertEQ(test148(testValue1), testValue1); + } + + @ForceInline + public Object test149_helper(Object obj) { + return (MyValue1)obj; + } + + // Same as test147 but with helper method + @Test + public Object test149(Object obj) { + return test149_helper(obj); + } + + @Run(test = "test149") + @Warmup(10000) + public void test149_verifier() { + Asserts.assertEQ(test149(testValue1), testValue1); + Asserts.assertEQ(test149(null), null); + } + + // Test post-parse call devirtualization with inline type receiver + @Test + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "true"}, + failOn = {ALLOC_OF_MYVALUE_KLASS}) + @IR(failOn = {compiler.lib.ir_framework.IRNode.DYNAMIC_CALL_OF_METHOD, "MyValue2::hash"}, + counts = {compiler.lib.ir_framework.IRNode.STATIC_CALL_OF_METHOD, "MyValue2::hash", "= 1"}) + public long test150() { + MyValue2 val = MyValue2.createWithFieldsInline(rI, rD); + MyInterface receiver = MyValue1.createWithFieldsInline(rI, rL); + + for (int i = 0; i < 4; i++) { + if ((i % 2) == 0) { + receiver = val; + } + } + // Trigger post parse call devirtualization (strength-reducing + // virtual calls to direct calls). + return receiver.hash(); + } + + @Run(test = "test150") + public void test150_verifier() { + Asserts.assertEquals(test150(), testValue2.hash()); + } + + // Same as test150 but with a real loop and val not being allocated in the scope of the method + @Test + // Dynamic call does not null check the receiver, so it cannot be strength reduced to a static + // call without an explicit null check + @IR(failOn = {compiler.lib.ir_framework.IRNode.DYNAMIC_CALL_OF_METHOD, "MyValue2::hash"}, + counts = {compiler.lib.ir_framework.IRNode.STATIC_CALL_OF_METHOD, "MyValue2::hash", "= 1"}) + public long test151(MyValue2 val) { + val = Objects.requireNonNull(val); + MyAbstract receiver = MyValue1.createWithFieldsInline(rI, rL); + + for (int i = 0; i < 100; i++) { + if ((i % 2) == 0) { + receiver = val; + } + } + // Trigger post parse call devirtualization (strength-reducing + // virtual calls to direct calls). + return receiver.hash(); + } + + @Run(test = "test151") + @Warmup(0) // Make sure there is no receiver type profile + public void test151_verifier() { + Asserts.assertEquals(test151(testValue2), testValue2.hash()); + } + + static interface MyInterface2 { + public int val(); + } + + static abstract value class MyAbstract2 implements MyInterface2 { + + } + + static class MyClass152 extends MyAbstract2 { + private int val; + + @ForceInline + public MyClass152(int val) { + this.val = val; + } + + @Override + public int val() { + return val; + } + } + + @LooselyConsistentValue + static value class MyValue152 extends MyAbstract2 { + private int unused = 0; // Make sure sub-offset of val is field non-zero + private int val; + + @ForceInline + public MyValue152(int val) { + this.val = val; + } + + @Override + public int val() { + return val; + } + } + + @LooselyConsistentValue + static value class MyWrapper152 { + private int unused = 0; // Make sure sub-offset of val field is non-zero + @Strict + @NullRestricted + MyValue152 val; + + @ForceInline + public MyWrapper152(MyInterface2 val) { + this.val = (MyValue152)val; + } + } + + // Test that checkcast with speculative type does not break scalarization in return + @Test + public MyWrapper152 test152(MyInterface2 val) { + return new MyWrapper152(val); + } + + @Run(test = "test152") + @Warmup(10000) // Make sure profile information is available at cast + public void test152_verifier() { + MyClass152 unused = new MyClass152(rI); + MyValue152 val = new MyValue152(rI); + Asserts.assertEquals(test152(val).val, val); + } + + @DontInline + static void test153_helper(MyWrapper152 arg) { + + } + + // Test that checkcast with speculative type does not prevent scalarization in args + @Test + public void test153(MyInterface2 val) { + test153_helper(new MyWrapper152(val)); + } + + @Run(test = "test153") + @Warmup(10000) // Make sure profile information is available at cast + public void test153_verifier() { + MyClass152 unused = new MyClass152(rI); + MyValue152 val = new MyValue152(rI); + test153(val); + } + + // Test that checkcast with speculative type enables scalarization + @Test + @IR(failOn = {ALLOC, STORE_OF_ANY_KLASS}) + public int test154(Method m, MyInterface2 val, boolean b1, boolean b2) { + MyInterface2 obj = new MyValue152(rI); + if (b1) { + // Speculative cast to MyValue152 enables scalarization + obj = (MyAbstract2)val; + } + if (b2) { + // Uncommon trap + TestFramework.deoptimize(m); + return obj.val(); + } + return -1; + } + + @Run(test = "test154") + @Warmup(10000) // Make sure profile information is available at cast + public void test154_verifier(RunInfo info) { + MyClass152 unused = new MyClass152(rI); + MyValue152 val = new MyValue152(rI); + Asserts.assertEquals(test154(info.getTest(), val, false, false), -1); + Asserts.assertEquals(test154(info.getTest(), val, true, false), -1); + if (!info.isWarmUp()) { + Asserts.assertEquals(test154(info.getTest(), val, false, true), rI); + } + } + + // Same as test154 but with null val + @Test + @IR(failOn = {ALLOC, STORE_OF_ANY_KLASS}) + public int test155(Method m, MyInterface2 val, boolean b1, boolean b2) { + MyInterface2 obj = new MyValue152(rI); + if (b1) { + // Speculative cast to MyValue152 enables scalarization + obj = (MyAbstract2)val; + } + if (b2) { + // Uncommon trap + TestFramework.deoptimize(m); + return obj.val(); + } + return -1; + } + + @Run(test = "test155") + @Warmup(10000) // Make sure profile information is available at cast + public void test155_verifier(RunInfo info) { + MyClass152 unused = new MyClass152(rI); + MyValue152 val = new MyValue152(rI); + Asserts.assertEquals(test155(info.getTest(), val, false, false), -1); + Asserts.assertEquals(test155(info.getTest(), val, true, false), -1); + Asserts.assertEquals(test155(info.getTest(), null, true, false), -1); + if (!info.isWarmUp()) { + Asserts.assertEquals(test155(info.getTest(), val, false, true), rI); + } + } + + @Strict + @NullRestricted + final static MyValue1 test157Cache = MyValue1.createWithFieldsInline(rI, 0); + + // Test merging buffered inline type from field load with non-buffered inline type + @Test + public MyValue1 test157(long val) { + return (val == 0L) ? test157Cache : MyValue1.createWithFieldsInline(rI, val); + } + + @Run(test = "test157") + public void test157_verifier() { + Asserts.assertEquals(test157(0), test157Cache); + Asserts.assertEquals(test157(rL).hash(), testValue1.hash()); + } + + @Strict + @NullRestricted + static MyValue1 test158Cache = MyValue1.createWithFieldsInline(rI, 0); + + // Same as test157 but with non-final field load + @Test + public MyValue1 test158(long val) { + return (val == 0L) ? test158Cache : MyValue1.createWithFieldsInline(rI, val); + } + + @Run(test = "test158") + public void test158_verifier() { + Asserts.assertEquals(test158(0), test158Cache); + Asserts.assertEquals(test158(rL).hash(), testValue1.hash()); + } + + // Verify that cast that with incompatible types is properly handled + @Test + public void test160(NonValueClass arg) { + Object tmp = arg; + MyValue1 res = (MyValue1)tmp; + } + + @Run(test = "test160") + @Warmup(10000) + public void test160_verifier(RunInfo info) { + try { + test160(new NonValueClass(42)); + throw new RuntimeException("No CCE thrown"); + } catch (ClassCastException e) { + // Expected + } + test160(null); + } + + abstract value static class AbstractValueClassSingleSubclass { + } + + value static class UniqueValueSubClass extends AbstractValueClassSingleSubclass { + int x = 34; + } + + static AbstractValueClassSingleSubclass abstractValueClassSingleSubclass = new UniqueValueSubClass(); + + @Test + public void testUniqueConcreteValueSubKlass(boolean flag) { + // C2 should recognize that even though we do not know the exact layout of the underlying inline type of the + // abstract field abstractValueClassSingleSubclass (i.e. cannot scalarize), we only have a unique concrete sub + // class from which we know at compile time whether it can be scalarized or not. This unique sub class + // optimization was missing, resulting in a missing InlineTypeNode assertion failure. + doNothing(abstractValueClassSingleSubclass, flag ? 23 : 34); + } + + void doNothing(Object a, int i) {} + + @Run(test = "testUniqueConcreteValueSubKlass") + public void testUniqueConcreteValueSubKlass_verifier() { + testUniqueConcreteValueSubKlass(true); + } + + static value class MyValueContainer { + private final Object value; + + private MyValueContainer(Object value) { + this.value = value; + } + } + + static value class MyValue161 { + int x = 0; + } + + // Test merging value classes with Object fields + @Test + public MyValueContainer test161(boolean b) { + MyValueContainer res = b ? new MyValueContainer(new MyValue161()) : null; + // Cast to verify that merged values are of correct type + Object obj = b ? (MyValue161)res.value : null; + return res; + } + + @Run(test = "test161") + public void test161_verifier() { + Asserts.assertEquals(test161(true), new MyValueContainer(new MyValue161())); + Asserts.assertEquals(test161(false), null); + } + + @Test + public MyValueContainer test162(boolean b) { + MyValueContainer res = b ? null : new MyValueContainer(new MyValue161()); + // Cast to verify that merged values are of correct type + Object obj = b ? null : (MyValue161)res.value; + return res; + } + + @Run(test = "test162") + public void test162_verifier() { + Asserts.assertEquals(test162(true), null); + Asserts.assertEquals(test162(false), new MyValueContainer(new MyValue161())); + } + + @Test + public MyValueContainer test163(boolean b) { + MyValueContainer res = b ? new MyValueContainer(new MyValue161()) : new MyValueContainer(null); + // Cast to verify that merged values are of correct type + Object obj = b ? (MyValue161)res.value : (MyValue161)res.value; + return res; + } + + @Run(test = "test163") + public void test163_verifier() { + Asserts.assertEquals(test163(true), new MyValueContainer(new MyValue161())); + Asserts.assertEquals(test163(false), new MyValueContainer(null)); + } + + @Test + public MyValueContainer test164(boolean b) { + MyValueContainer res = b ? new MyValueContainer(null) : new MyValueContainer(new MyValue161()); + // Cast to verify that merged values are of correct type + Object obj = b ? (MyValue161)res.value : (MyValue161)res.value; + return res; + } + + @Run(test = "test164") + public void test164_verifier() { + Asserts.assertEquals(test164(true), new MyValueContainer(null)); + Asserts.assertEquals(test164(false), new MyValueContainer(new MyValue161())); + } + + @Test + public MyValueContainer test165(boolean b) { + MyValueContainer res = b ? new MyValueContainer(new MyValue161()) : new MyValueContainer(42); + // Cast to verify that merged values are of correct type + Object obj = b ? (MyValue161)res.value : (Integer)res.value; + return res; + } + + @Run(test = "test165") + public void test165_verifier() { + Asserts.assertEquals(test165(true), new MyValueContainer(new MyValue161())); + Asserts.assertEquals(test165(false), new MyValueContainer(42)); + } + + @Test + public MyValueContainer test166(boolean b) { + MyValueContainer res = b ? new MyValueContainer(42) : new MyValueContainer(new MyValue161()); + // Cast to verify that merged values are of correct type + Object obj = b ? (Integer)res.value : (MyValue161)res.value; + return res; + } + + @Run(test = "test166") + public void test166_verifier() { + Asserts.assertEquals(test166(true), new MyValueContainer(42)); + Asserts.assertEquals(test166(false), new MyValueContainer(new MyValue161())); + } + + // Verify that monitor information in JVMState is correct at method exit + @Test + public synchronized Object test167() { + return MyValue1.createWithFieldsInline(rI, rL); // Might trigger buffering which requires JVMState + } + + @Run(test = "test167") + public void test167_verifier() { + Asserts.assertEquals(((MyValue1)test167()).hash(), hash()); + } + + @LooselyConsistentValue + static value class ValueClassWithInt { + int i; + + ValueClassWithInt(int i) { + this.i = i; + } + } + + @LooselyConsistentValue + static value class ValueClassWithDouble { + double d; + + ValueClassWithDouble(double d) { + this.d = d; + } + } + + @LooselyConsistentValue + static abstract value class AbstractValueClassWithByte { + byte b; + + AbstractValueClassWithByte(byte b) { + this.b = b; + } + } + + @LooselyConsistentValue + static value class SubValueClassWithInt extends AbstractValueClassWithByte { + int i; + + SubValueClassWithInt(int i) { + this.i = i; + super((byte)(i + 1)); + } + } + + @LooselyConsistentValue + static value class SubValueClassWithDouble extends AbstractValueClassWithByte { + double d; + + SubValueClassWithDouble(double d) { + this.d = d; + super((byte)(d + 1)); + } + } + + // TODO 8350865 We need more copies of these tests for all ValueClass array factories + static final ValueClassWithInt[] VALUE_CLASS_WITH_INT_ARRAY = (ValueClassWithInt[]) ValueClass.newNullRestrictedNonAtomicArray(ValueClassWithInt.class, 2, new ValueClassWithInt(0)); + static final ValueClassWithDouble[] VALUE_CLASS_WITH_DOUBLE_ARRAY = (ValueClassWithDouble[]) ValueClass.newNullRestrictedNonAtomicArray(ValueClassWithDouble.class, 2, new ValueClassWithDouble(0)); + static final SubValueClassWithInt[] SUB_VALUE_CLASS_WITH_INT_ARRAY = (SubValueClassWithInt[]) ValueClass.newNullRestrictedNonAtomicArray(SubValueClassWithInt.class, 2, new SubValueClassWithInt(0)); + static final SubValueClassWithDouble[] SUB_VALUE_CLASS_WITH_DOUBLE_ARRAY = (SubValueClassWithDouble[]) ValueClass.newNullRestrictedNonAtomicArray(SubValueClassWithDouble.class, 2, new SubValueClassWithDouble(0)); + +// TODO: Can only be enabled once JDK-8343835 is fixed. Otherwise, we hit the mismatched stores assert. +// static { +// VALUE_CLASS_WITH_INT_ARRAY[0] = new ValueClassWithInt(5); +// VALUE_CLASS_WITH_DOUBLE_ARRAY[0] = new ValueClassWithDouble(6); +// SUB_VALUE_CLASS_WITH_INT_ARRAY[0] = new SubValueClassWithInt(7); +// SUB_VALUE_CLASS_WITH_DOUBLE_ARRAY[0] = new SubValueClassWithDouble(8); +// } + + @Test + static void testFlatArrayInexactObjectStore(Object o, boolean flag) { + Object[] oArr; + if (flag) { + oArr = VALUE_CLASS_WITH_INT_ARRAY; // VALUE_CLASS_WITH_INT_ARRAY is statically known to be flat. + } else { + oArr = VALUE_CLASS_WITH_DOUBLE_ARRAY; // VALUE_CLASS_WITH_DOUBLE_ARRAY is statically known to be flat. + } + // The type of 'oArr' is inexact here because we merge two arrays. Since both arrays are flat, 'oArr' is also flat: + // Type: flat:narrowoop: java/lang/Object:NotNull * (flat in array)[int:2] + // Since the type is inexact, we do not know the exact flat array layout statically and thus need to fall back + // to call "store_unknown_inline_Type()" at runtime where we know the flat array layout + oArr[0] = o; + } + + @Test + static Object testFlatArrayInexactObjectLoad(boolean flag) { + Object[] oArr; + if (flag) { + oArr = VALUE_CLASS_WITH_INT_ARRAY; // VALUE_CLASS_WITH_INT_ARRAY is statically known to be flat. + } else { + oArr = VALUE_CLASS_WITH_DOUBLE_ARRAY; // VALUE_CLASS_WITH_DOUBLE_ARRAY is statically known to be flat. + } + // The type of 'oArr' is inexact here because we merge two arrays. Since both arrays are flat, 'oArr' is also flat: + // Type: flat:narrowoop: java/lang/Object:NotNull * (flat in array)[int:2] + // Since the type is inexact, we do not know the exact flat array layout statically and thus need to fall back + // to call "load_unknown_inline_Type()" at runtime where we know the flat array layout + return oArr[0]; + } + + @Test + static void testFlatArrayInexactAbstractValueClassStore(AbstractValueClassWithByte abstractValueClassWithByte, + boolean flag) { + AbstractValueClassWithByte[] avArr; + if (flag) { + avArr = SUB_VALUE_CLASS_WITH_INT_ARRAY; + } else { + avArr = SUB_VALUE_CLASS_WITH_DOUBLE_ARRAY; + } + // Same as testFlatArrayInexactObjectStore() but the inexact type is with an abstract value class: + // flat:narrowoop: compiler/valhalla/inlinetypes/TestLWorld$AbstractValueClassWithByte:NotNull * (flat in array)[int:2] + avArr[0] = abstractValueClassWithByte; + } + + @Test + static AbstractValueClassWithByte testFlatArrayInexactAbstractValueClassLoad(boolean flag) { + AbstractValueClassWithByte[] avArr; + if (flag) { + avArr = SUB_VALUE_CLASS_WITH_INT_ARRAY; + } else { + avArr = SUB_VALUE_CLASS_WITH_DOUBLE_ARRAY; + } + // Same as testFlatArrayInexactObjectLoad() but the inexact type is with an abstract value class: + // flat:narrowoop: compiler/valhalla/inlinetypes/TestLWorld$AbstractValueClassWithByte:NotNull * (flat in array)[int:2] + return avArr[0]; + } + + @Run(test = {"testFlatArrayInexactObjectStore", + "testFlatArrayInexactObjectLoad", + "testFlatArrayInexactAbstractValueClassStore", + "testFlatArrayInexactAbstractValueClassLoad"}) + static void runFlatArrayInexactLoadAndStore() { + // TODO: Remove these again once JDK-8343835 is fixed and uncomment static initializer above + VALUE_CLASS_WITH_INT_ARRAY[0] = new ValueClassWithInt(5); + VALUE_CLASS_WITH_DOUBLE_ARRAY[0] = new ValueClassWithDouble(6); + SUB_VALUE_CLASS_WITH_INT_ARRAY[0] = new SubValueClassWithInt(7); + SUB_VALUE_CLASS_WITH_DOUBLE_ARRAY[0] = new SubValueClassWithDouble(8); + + boolean flag = true; + ValueClassWithInt valueClassWithInt = new ValueClassWithInt(15); + ValueClassWithDouble valueClassWithDouble = new ValueClassWithDouble(16); + + testFlatArrayInexactObjectStore(valueClassWithInt, true); + Asserts.assertEQ(valueClassWithInt, VALUE_CLASS_WITH_INT_ARRAY[0]); + testFlatArrayInexactObjectStore(valueClassWithDouble, false); + Asserts.assertEQ(valueClassWithDouble, VALUE_CLASS_WITH_DOUBLE_ARRAY[0]); + + Asserts.assertEQ(valueClassWithInt, testFlatArrayInexactObjectLoad(true)); + Asserts.assertEQ(valueClassWithDouble, testFlatArrayInexactObjectLoad(false)); + + SubValueClassWithInt subValueClassWithInt = new SubValueClassWithInt(17); + SubValueClassWithDouble subValueClassWithDouble = new SubValueClassWithDouble(18); + + testFlatArrayInexactAbstractValueClassStore(subValueClassWithInt, true); + Asserts.assertEQ(subValueClassWithInt, SUB_VALUE_CLASS_WITH_INT_ARRAY[0]); + testFlatArrayInexactAbstractValueClassStore(subValueClassWithDouble, false); + Asserts.assertEQ(subValueClassWithDouble, SUB_VALUE_CLASS_WITH_DOUBLE_ARRAY[0]); + + Asserts.assertEQ(subValueClassWithInt, testFlatArrayInexactAbstractValueClassLoad(true)); + Asserts.assertEQ(subValueClassWithDouble, testFlatArrayInexactAbstractValueClassLoad(false)); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestLWorldProfiling.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestLWorldProfiling.java new file mode 100644 index 00000000000..bb75f1eb51c --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestLWorldProfiling.java @@ -0,0 +1,1224 @@ +/* + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.*; +import jdk.test.lib.Asserts; +import jdk.test.whitebox.WhiteBox; + +import java.lang.reflect.Method; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.LOAD_UNKNOWN_INLINE; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.STORE_UNKNOWN_INLINE; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.SUBSTITUTABILITY_TEST; +import static compiler.valhalla.inlinetypes.InlineTypes.*; + +import static compiler.lib.ir_framework.IRNode.CLASS_CHECK_TRAP; +import static compiler.lib.ir_framework.IRNode.NULL_ASSERT_TRAP; +import static compiler.lib.ir_framework.IRNode.NULL_CHECK_TRAP; +import static compiler.lib.ir_framework.IRNode.RANGE_CHECK_TRAP; +import static compiler.lib.ir_framework.IRNode.STATIC_CALL; + +/* + * @test + * @key randomness + * @summary Test value class specific type profiling. + * @library /test/lib / + * @requires (os.simpleArch == "x64" | os.simpleArch == "aarch64") + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm/timeout=300 compiler.valhalla.inlinetypes.TestLWorldProfiling + */ + +@ForceCompileClassInitializer +public class TestLWorldProfiling { + + public static void main(String[] args) { + final Scenario[] scenarios = { + new Scenario(0, + "-XX:+UseArrayFlattening", + "-XX:-UseArrayLoadStoreProfile", + "-XX:-UseACmpProfile", + "-XX:TypeProfileLevel=0", + "-XX:-MonomorphicArrayCheck"), + new Scenario(1, + "-XX:+UseArrayFlattening", + "-XX:+UseArrayLoadStoreProfile", + "-XX:+UseACmpProfile", + "-XX:TypeProfileLevel=0", + "-XX:+UseFieldFlattening"), + new Scenario(2, + "-XX:+UseArrayFlattening", + "-XX:-UseArrayLoadStoreProfile", + "-XX:-UseACmpProfile", + "-XX:TypeProfileLevel=222", + "-XX:-MonomorphicArrayCheck"), + new Scenario(3, + "-XX:+UseArrayFlattening", + "-XX:-UseArrayLoadStoreProfile", + "-XX:-UseACmpProfile", + "-XX:TypeProfileLevel=0", + "-XX:-MonomorphicArrayCheck", + "-XX:TieredStopAtLevel=4", + "-XX:-TieredCompilation"), + new Scenario(4, + "-XX:+UseArrayFlattening", + "-XX:+UseArrayLoadStoreProfile", + "-XX:+UseACmpProfile", + "-XX:TypeProfileLevel=0", + "-XX:TieredStopAtLevel=4", + "-XX:-TieredCompilation", + "-XX:+UseFieldFlattening"), + new Scenario(5, + "-XX:+UseArrayFlattening", + "-XX:-UseArrayLoadStoreProfile", + "-XX:-UseACmpProfile", + "-XX:TypeProfileLevel=222", + "-XX:-MonomorphicArrayCheck", + "-XX:TieredStopAtLevel=4", + "-XX:-TieredCompilation") + }; + + InlineTypes.getFramework() + .addScenarios(scenarios) + .addFlags("-XX:+IgnoreUnrecognizedVMOptions", "--enable-preview", + "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED") + .addHelperClasses(MyValue1.class, + MyValue2.class) + .start(); + } + + @Strict + @NullRestricted + private static final MyValue1 testValue1 = MyValue1.createWithFieldsInline(rI, rL); + @Strict + @NullRestricted + private static final MyValue2 testValue2 = MyValue2.createWithFieldsInline(rI, rD); + private static final MyValue1[] testValue1Array = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + static { + testValue1Array[0] = testValue1; + } + private static final MyValue2[] testValue2Array = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 1, MyValue2.DEFAULT); + static { + testValue2Array[0] = testValue2; + } + + // Some non-value classes + static class MyInteger extends Number { + int val; + + public MyInteger(int val) { + this.val = val; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof MyInteger)) { + return false; + } + return this.val == ((MyInteger)o).val; + } + + public double doubleValue() { return val; } + public float floatValue() { return val; } + public int intValue() { return val; } + public long longValue() { return val; } + } + + static class MyLong extends Number { + long val; + + public MyLong(long val) { + this.val = val; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof MyLong)) { + return false; + } + return this.val == ((MyLong)o).val; + } + + public double doubleValue() { return val; } + public float floatValue() { return val; } + public int intValue() { return (int)val; } + public long longValue() { return val; } + } + + private static final MyInteger[] testMyIntegerArray = new MyInteger[] { new MyInteger(42) }; + private static final MyLong[] testMyLongArray = new MyLong[] { new MyLong(42L) }; + private static final MyValue1[] testValue1NotFlatArray = new MyValue1[] { testValue1 }; + private static final MyValue1[][] testValue1ArrayArray = new MyValue1[][] { testValue1Array }; + + // Wrap these variables into helper class because + // WhiteBox API needs to be initialized by TestFramework first. + static class WBFlags { + static final boolean UseACmpProfile = (Boolean) WhiteBox.getWhiteBox().getVMFlag("UseACmpProfile"); + static final boolean TieredCompilation = (Boolean) WhiteBox.getWhiteBox().getVMFlag("TieredCompilation"); + static final boolean ProfileInterpreter = (Boolean) WhiteBox.getWhiteBox().getVMFlag("ProfileInterpreter"); + static final boolean UseArrayLoadStoreProfile = (Boolean) WhiteBox.getWhiteBox().getVMFlag("UseArrayLoadStoreProfile"); + static final long TypeProfileLevel = (Long) WhiteBox.getWhiteBox().getVMFlag("TypeProfileLevel"); + } + + static abstract value class ValueAbstract { + + } + + static class NonValueClass1 extends ValueAbstract { + int x; + + public NonValueClass1(int x) { + this.x = x; + } + } + + static class NonValueClass2 extends ValueAbstract { + int x; + + public NonValueClass2(int x) { + this.x = x; + } + } + + static final NonValueClass1 obj = new NonValueClass1(rI); + static final NonValueClass2 otherObj = new NonValueClass2(rI); + + // aaload + + @Test + @IR(applyIfOr = {"UseArrayLoadStoreProfile", "true", "TypeProfileLevel", "= 222"}, + failOn = {LOAD_UNKNOWN_INLINE}) + @IR(applyIfAnd={"UseACmpProfile", "false", "TypeProfileLevel", "!= 222"}, + counts = {LOAD_UNKNOWN_INLINE, "= 1"}) + public Object test1(Object[] array) { + return array[0]; + } + + @Run(test = "test1") + @Warmup(10000) + public void test1_verifier(RunInfo info) { + if (info.isWarmUp()) { + Object o = test1(testValue1Array); + Asserts.assertEQ(((MyValue1)o).hash(), testValue1.hash()); + } else { + Object o = test1(testValue2Array); + Asserts.assertEQ(((MyValue2)o).hash(), testValue2.hash()); + } + } + + @Test + @IR(applyIfOr = {"UseArrayLoadStoreProfile", "true", "TypeProfileLevel", "= 222"}, + failOn = {LOAD_UNKNOWN_INLINE}) + @IR(applyIfAnd = {"UseArrayLoadStoreProfile", "false", "TypeProfileLevel", "!= 222"}, + counts = {LOAD_UNKNOWN_INLINE, "= 1"}) + public Object test2(Object[] array) { + return array[0]; + } + + @Run(test = "test2") + @Warmup(10000) + public void test2_verifier(RunInfo info) { + if (info.isWarmUp()) { + Object o = test2(testMyIntegerArray); + Asserts.assertEQ(o, new MyInteger(42)); + } else { + Object o = test2(testMyLongArray); + Asserts.assertEQ(o, new MyLong(42L)); + } + } + + @Test + @IR(counts = {LOAD_UNKNOWN_INLINE, "= 1"}) + public Object test3(Object[] array) { + return array[0]; + } + + @Run(test = "test3") + @Warmup(10000) + public void test3_verifier() { + Object o = test3(testValue1Array); + Asserts.assertEQ(((MyValue1)o).hash(), testValue1.hash()); + o = test3(testValue2Array); + Asserts.assertEQ(((MyValue2)o).hash(), testValue2.hash()); + } + + @Test + @IR(applyIf = {"UseArrayLoadStoreProfile", "true"}, + failOn = {LOAD_UNKNOWN_INLINE}) + @IR(applyIf = {"UseArrayLoadStoreProfile", "false"}, + counts = {LOAD_UNKNOWN_INLINE, "= 1"}) + public Object test4(Object[] array) { + return array[0]; + } + + @Run(test = "test4") + @Warmup(10000) + public void test4_verifier(RunInfo info) { + if (info.isWarmUp()) { + Object o = test4(testMyIntegerArray); + Asserts.assertEQ(o, new MyInteger(42)); + o = test4(testMyLongArray); + Asserts.assertEQ(o, new MyLong(42L)); + } else { + Object o = test4(testValue2Array); + Asserts.assertEQ(((MyValue2)o).hash(), testValue2.hash()); + } + } + + @Test + @IR(counts = {LOAD_UNKNOWN_INLINE, "= 1"}) + public Object test5(Object[] array) { + return array[0]; + } + + @Run(test = "test5") + @Warmup(10000) + public void test5_verifier() { + Object o = test5(testValue1Array); + Asserts.assertEQ(((MyValue1)o).hash(), testValue1.hash()); + o = test5(testValue1NotFlatArray); + Asserts.assertEQ(((MyValue1)o).hash(), testValue1.hash()); + } + + // Check that profile data that's useless at the aaload is + // leveraged at a later point + @DontInline + public void test6_no_inline() { + } + + @ForceInline + public void test6_helper(ValueAbstract[] arg) { + if (arg instanceof NonValueClass1[]) { + test6_no_inline(); + } + } + + @Test + @IR(applyIfOr = {"UseArrayLoadStoreProfile", "true", "TypeProfileLevel", "= 222"}, + counts = {STATIC_CALL, "= 4", CLASS_CHECK_TRAP, "= 1", NULL_CHECK_TRAP, "= 1", RANGE_CHECK_TRAP, "= 1"}) + @IR(applyIfAnd = {"UseArrayLoadStoreProfile", "false", "TypeProfileLevel", "!= 222"}, + counts = {STATIC_CALL, "= 4", RANGE_CHECK_TRAP, "= 1", NULL_CHECK_TRAP, "= 1"}) + public Object test6(ValueAbstract[] array) { + ValueAbstract v = array[0]; + test6_helper(array); + return v; + } + + @Run(test = "test6") + @Warmup(10000) + public void test6_verifier(RunInfo info) { + if (info.isWarmUp()) { + // pollute profile + test6_helper(new NonValueClass1[1]); + test6_helper(new NonValueClass2[1]); + } + test6(new NonValueClass1[1]); + } + + @DontInline + public void test7_no_inline() { + } + + @ForceInline + public void test7_helper(ValueAbstract arg) { + if (arg instanceof NonValueClass1) { + test7_no_inline(); + } + } + + @Test + @IR(applyIfOr = {"UseArrayLoadStoreProfile", "true", "TypeProfileLevel", "= 222"}, + counts = {STATIC_CALL, "= 4", CLASS_CHECK_TRAP, "= 1", NULL_CHECK_TRAP, "= 1", RANGE_CHECK_TRAP, "= 1"}) + @IR(applyIfAnd = {"UseArrayLoadStoreProfile", "false", "TypeProfileLevel", "!= 222"}, + counts = {STATIC_CALL, "= 4", RANGE_CHECK_TRAP, "= 1", NULL_CHECK_TRAP, "= 1"}) + public Object test7(ValueAbstract[] array) { + ValueAbstract v = array[0]; + test7_helper(v); + return v; + } + + @Run(test = "test7") + @Warmup(10000) + public void test7_verifier(RunInfo info) { + if (info.isWarmUp()) { + // pollute profile + test7_helper(new NonValueClass1(rI)); + test7_helper(new NonValueClass2(rI)); + } + test7(new NonValueClass1[1]); + } + + @DontInline + public void test8_no_inline() { + } + + public void test8_helper(Object arg) { + if (arg instanceof Long) { + test8_no_inline(); + } + } + + @Test + @IR(applyIf = {"UseArrayLoadStoreProfile", "true"}, + counts = {STATIC_CALL, "= 5", CLASS_CHECK_TRAP, "= 1", NULL_CHECK_TRAP, "= 2", + RANGE_CHECK_TRAP, "= 1"}) + @IR(applyIf = {"UseArrayLoadStoreProfile", "false"}, + counts = {STATIC_CALL, "= 5", RANGE_CHECK_TRAP, "= 1", NULL_CHECK_TRAP, "= 2"}) + public Object test8(Object[] array) { + Object v = array[0]; + test8_helper(v); + return v; + } + + @Run(test = "test8") + @Warmup(10000) + public void test8_verifier(RunInfo info) { + if (info.isWarmUp()) { + // pollute profile + test8_helper(42L); + test8_helper(42.0D); + } + test8(testValue1Array); + test8(testValue1NotFlatArray); + } + + // aastore + + @Test + @IR(applyIfOr = {"UseArrayLoadStoreProfile", "true", "TypeProfileLevel", "= 222"}, + failOn = {STORE_UNKNOWN_INLINE}) + @IR(applyIfAnd = {"UseArrayLoadStoreProfile", "false", "TypeProfileLevel", "!= 222"}, + counts = {STORE_UNKNOWN_INLINE, "= 1"}) + public void test9(Object[] array, Object v) { + array[0] = v; + } + + @Run(test = "test9") + @Warmup(10000) + public void test9_verifier() { + test9(testValue1Array, testValue1); + Asserts.assertEQ(testValue1Array[0].hash(), testValue1.hash()); + } + + @Test + @IR(applyIfOr = {"UseArrayLoadStoreProfile", "true", "TypeProfileLevel", "= 222"}, + failOn = {STORE_UNKNOWN_INLINE}) + @IR(applyIfAnd = {"UseArrayLoadStoreProfile", "false", "TypeProfileLevel", "!= 222"}, + counts = {STORE_UNKNOWN_INLINE, "= 1"}) + public void test10(Object[] array, Object v) { + array[0] = v; + } + + @Run(test = "test10") + @Warmup(10000) + public void test10_verifier() { + test10(testMyIntegerArray, new MyInteger(42)); + } + + @Test + @IR(counts = {STORE_UNKNOWN_INLINE, "= 1"}) + public void test11(Object[] array, Object v) { + array[0] = v; + } + + @Run(test = "test11") + @Warmup(10000) + public void test11_verifier() { + test11(testValue1Array, testValue1); + test11(testValue2Array, testValue2); + } + + @Test + @IR(applyIf = {"UseArrayLoadStoreProfile", "true"}, + failOn = {STORE_UNKNOWN_INLINE}) + @IR(applyIf = {"UseArrayLoadStoreProfile", "false"}, + counts = {STORE_UNKNOWN_INLINE, "= 1"}) + public void test12(Object[] array, Object v) { + array[0] = v; + } + + @Run(test = "test12") + @Warmup(10000) + public void test12_verifier() { + test12(testMyIntegerArray, new MyInteger(42)); + test12(testMyLongArray, new MyLong(42L)); + } + + @Test + @IR(counts = {STORE_UNKNOWN_INLINE, "= 1"}) + public void test13(Object[] array, Object v) { + array[0] = v; + } + + @Run(test = "test13") + @Warmup(10000) + public void test13_verifier() { + test13(testValue1Array, testValue1); + test13(testValue1NotFlatArray, testValue1); + } + + // MonomorphicArrayCheck + @Test + public void test14(Number[] array, Number v) { + array[0] = v; + } + + @Run(test = "test14") + @Warmup(10000) + public void test14_verifier(RunInfo info) { + if (info.isWarmUp()) { + test14(testMyIntegerArray, new MyInteger(42)); + } else { + Method m = info.getTest(); + boolean deopt = false; + for (int i = 0; i < 100; i++) { + test14(testMyIntegerArray, new MyInteger(42)); + if (!info.isCompilationSkipped() && !TestFramework.isCompiled(m)) { + deopt = true; + } + } + if (deopt && TestFramework.isStableDeopt(m, CompLevel.C2) && !WBFlags.TieredCompilation && WBFlags.ProfileInterpreter && + (WBFlags.UseArrayLoadStoreProfile || WBFlags.TypeProfileLevel == 222)) { + throw new RuntimeException("Monomorphic array check should rely on profiling and be accurate"); + } + } + } + + // null free array profiling + + @LooselyConsistentValue + static value class NotFlattenable { + private Object o1 = null; + private Object o2 = null; + private Object o3 = null; + private Object o4 = null; + private Object o5 = null; + private Object o6 = null; + } + + @Strict + @NullRestricted + private static final NotFlattenable notFlattenable = new NotFlattenable(); + private static final NotFlattenable[] testNotFlattenableArray = (NotFlattenable[])ValueClass.newNullRestrictedNonAtomicArray(NotFlattenable.class, 1, new NotFlattenable()); + + @Test + @IR(applyIfOr = {"UseArrayLoadStoreProfile", "true", "TypeProfileLevel", "= 222"}, + counts = {NULL_CHECK_TRAP, "= 2"}, + failOn = {STORE_UNKNOWN_INLINE}) + @IR(applyIfAnd = {"UseArrayLoadStoreProfile", "false", "TypeProfileLevel", "!= 222"}, + counts = {NULL_CHECK_TRAP, "= 2", STORE_UNKNOWN_INLINE, "= 1"}) + public void test15(Object[] array, Object v) { + array[0] = v; + } + + @Run(test = "test15") + @Warmup(10000) + public void test15_verifier() { + test15(testNotFlattenableArray, notFlattenable); + try { + test15(testNotFlattenableArray, null); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException npe) { + // Expected + } + } + + @Test + @IR(applyIf = {"UseArrayLoadStoreProfile", "true"}, + counts = {NULL_CHECK_TRAP, "= 2"}, + failOn = {STORE_UNKNOWN_INLINE}) + @IR(applyIf = {"UseArrayLoadStoreProfile", "false"}, + counts = {NULL_CHECK_TRAP, "= 2", STORE_UNKNOWN_INLINE, "= 1"}) + public void test16(Object[] array, Object v) { + array[0] = v; + } + + @Run(test = "test16") + @Warmup(10000) + public void test16_verifier() { + test16(testNotFlattenableArray, notFlattenable); + try { + test16(testNotFlattenableArray, null); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException npe) { + // Expected + } + test16(testMyIntegerArray, new MyInteger(42)); + } + + @Test + @IR(applyIf = {"UseArrayLoadStoreProfile", "true"}, + counts = {NULL_CHECK_TRAP, "= 1"}, + failOn = {STORE_UNKNOWN_INLINE}) + @IR(applyIf = {"UseArrayLoadStoreProfile", "false"}, + counts = {NULL_CHECK_TRAP, "= 2", STORE_UNKNOWN_INLINE, "= 1"}) + public void test17(Object[] array, Object v) { + array[0] = v; + } + + @Run(test = "test17") + @Warmup(10000) + public void test17_verifier() { + test17(testMyIntegerArray, new MyInteger(42)); + test17(testMyIntegerArray, null); + testMyIntegerArray[0] = new MyInteger(42); + test17(testMyLongArray, new MyLong(42L)); + } + + public void test18_helper(Object[] array, Object v) { + array[0] = v; + } + + @Test + @IR(applyIf = {"UseArrayLoadStoreProfile", "true"}, + counts = {NULL_CHECK_TRAP, "= 1"}, + failOn = {STORE_UNKNOWN_INLINE}) + @IR(applyIf = {"UseArrayLoadStoreProfile", "false"}, + counts = {NULL_CHECK_TRAP, "= 2", STORE_UNKNOWN_INLINE, "= 1"}) + public Object test18(Object[] array, Object v1) { + Object v2 = array[0]; + test18_helper(array, v1); + return v2; + } + + @Run(test = "test18") + @Warmup(10000) + public void test18_verifier() { + test18_helper(testValue1Array, testValue1); // pollute profile + test18(testMyIntegerArray, new MyInteger(42)); + test18(testMyIntegerArray, null); + testMyIntegerArray[0] = new MyInteger(42); + test18(testMyLongArray, new MyLong(42L)); + } + + // maybe null free, not flat + + @Test + @IR(applyIf = {"UseArrayLoadStoreProfile", "true"}, + failOn = {LOAD_UNKNOWN_INLINE}) + @IR(applyIf = {"UseArrayLoadStoreProfile", "false"}, + counts = {LOAD_UNKNOWN_INLINE, "= 1"}) + public Object test19(Object[] array) { + return array[0]; + } + + @Run(test = "test19") + @Warmup(10000) + public void test19_verifier() { + Object o = test19(testMyIntegerArray); + Asserts.assertEQ(o, new MyInteger(42)); + o = test19(testNotFlattenableArray); + Asserts.assertEQ(o, notFlattenable); + } + + @Test + @IR(applyIf = {"UseArrayLoadStoreProfile", "true"}, + failOn = {STORE_UNKNOWN_INLINE}) + @IR(applyIf = {"UseArrayLoadStoreProfile", "false"}, + counts = {STORE_UNKNOWN_INLINE, "= 1"}) + public void test20(Object[] array, Object o) { + array[0] = o; + } + + @Run(test = "test20") + @Warmup(10000) + public void test20_verifier() { + test20(testMyIntegerArray, new MyInteger(42)); + test20(testNotFlattenableArray, notFlattenable); + } + + // acmp tests + + // branch frequency profiling causes not equal branch to be optimized out + @Test + @IR(counts = {IRNode.UNSTABLE_IF_TRAP, " = 1"}) + public boolean test21(Object o1, Object o2) { + return o1 == o2; + } + + @Run(test = "test21") + @Warmup(10000) + public void test21_verifier() { + test21(obj, obj); + test21(testValue1, testValue1); + } + + // Input profiled non null + @Test + @IR(applyIf = {"UseACmpProfile", "true"}, + failOn = {SUBSTITUTABILITY_TEST}, + counts = {NULL_ASSERT_TRAP, "= 1"}) + @IR(applyIf = {"UseACmpProfile", "false"}, + counts = {SUBSTITUTABILITY_TEST, "= 1"}) + public boolean test22(Object o1, Object o2) { + return o1 == o2; + } + + @Run(test = "test22") + @Warmup(10000) + public void test22_verifier(RunInfo info) { + test22(obj, null); + test22(otherObj, null); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertCompiledByC2(m); + test22(obj, otherObj); + if (WBFlags.UseACmpProfile) { + TestFramework.assertDeoptimizedByC2(m); + } + } + } + + @Test + @IR(applyIfOr = {"UseACmpProfile", "true", "TypeProfileLevel", "= 222"}, + failOn = {SUBSTITUTABILITY_TEST}, + counts = {NULL_ASSERT_TRAP, "= 1"}) + @IR(applyIfAnd = {"UseACmpProfile", "false", "TypeProfileLevel", "!= 222"}, + counts = {SUBSTITUTABILITY_TEST, "= 1"}) + public boolean test23(Object o1, Object o2) { + return o1 == o2; + } + + @Run(test = "test23") + @Warmup(10000) + public void test23_verifier(RunInfo info) { + test23(null, obj); + test23(null, otherObj); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertCompiledByC2(m); + test23(obj, otherObj); + if (WBFlags.UseACmpProfile || WBFlags.TypeProfileLevel != 0) { + TestFramework.assertDeoptimizedByC2(m); + } + } + } + + @Test + @IR(applyIf = {"UseACmpProfile", "true"}, + failOn = {SUBSTITUTABILITY_TEST}, + counts = {NULL_ASSERT_TRAP, "= 1"}) + @IR(applyIf = {"UseACmpProfile", "false"}, + counts = {SUBSTITUTABILITY_TEST, "= 1"}) + public boolean test24(Object o1, Object o2) { + return o1 != o2; + } + + @Run(test = "test24") + @Warmup(10000) + public void test24_verifier(RunInfo info) { + test24(obj, null); + test24(otherObj, null); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertCompiledByC2(m); + test24(obj, otherObj); + if (WBFlags.UseACmpProfile) { + TestFramework.assertDeoptimizedByC2(m); + } + } + } + + @Test + @IR(applyIfOr = {"UseACmpProfile", "true", "TypeProfileLevel", "= 222"}, + failOn = {SUBSTITUTABILITY_TEST}, + counts = {NULL_ASSERT_TRAP, "= 1"}) + @IR(applyIfAnd = {"UseACmpProfile", "false", "TypeProfileLevel", "!= 222"}, + counts = {SUBSTITUTABILITY_TEST, "= 1"}) + public boolean test25(Object o1, Object o2) { + return o1 != o2; + } + + @Run(test = "test25") + @Warmup(10000) + public void test25_verifier(RunInfo info) { + test25(null, obj); + test25(null, otherObj); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertCompiledByC2(m); + test25(obj, otherObj); + if (WBFlags.UseACmpProfile || WBFlags.TypeProfileLevel != 0) { + TestFramework.assertDeoptimizedByC2(m); + } + } + } + + // Input profiled not value class with known type + @Test + @IR(applyIfOr = {"UseACmpProfile", "true", "TypeProfileLevel", "= 222"}, + failOn = {SUBSTITUTABILITY_TEST}, + counts = {NULL_CHECK_TRAP, "= 1", CLASS_CHECK_TRAP, "= 1"}) + @IR(applyIfAnd = {"UseACmpProfile", "false", "TypeProfileLevel", "!= 222"}, + counts = {SUBSTITUTABILITY_TEST, "= 1"}) + public boolean test26(Object o1, Object o2) { + return o1 == o2; + } + + @Run(test = "test26") + @Warmup(10000) + public void test26_verifier(RunInfo info) { + test26(obj, obj); + test26(obj, otherObj); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertCompiledByC2(m); + for (int i = 0; i < 10; i++) { + test26(otherObj, obj); + } + if (WBFlags.UseACmpProfile || WBFlags.TypeProfileLevel != 0) { + TestFramework.assertDeoptimizedByC2(m); + } + } + } + + @Test + @IR(applyIf = {"UseACmpProfile", "true"}, + failOn = {SUBSTITUTABILITY_TEST}, + counts = { NULL_CHECK_TRAP, "= 1", CLASS_CHECK_TRAP, "= 1"}) + @IR(applyIf = {"UseACmpProfile", "false"}, + counts = {SUBSTITUTABILITY_TEST, "= 1"}) + public boolean test27(Object o1, Object o2) { + return o1 == o2; + } + + @Run(test = "test27") + @Warmup(10000) + public void test27_verifier(RunInfo info) { + test27(obj, obj); + test27(otherObj, obj); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertCompiledByC2(m); + for (int i = 0; i < 10; i++) { + test27(obj, otherObj); + } + if (WBFlags.UseACmpProfile) { + TestFramework.assertDeoptimizedByC2(m); + } + } + } + + @Test + @IR(applyIfOr = {"UseACmpProfile", "true", "TypeProfileLevel", "= 222"}, + failOn = {SUBSTITUTABILITY_TEST}, + counts = {NULL_CHECK_TRAP, "= 1", CLASS_CHECK_TRAP, "= 1"}) + @IR(applyIfAnd = {"UseACmpProfile", "false", "TypeProfileLevel", "!= 222"}, + counts = {SUBSTITUTABILITY_TEST, "= 1"}) + public boolean test28(Object o1, Object o2) { + return o1 != o2; + } + + @Run(test = "test28") + @Warmup(10000) + public void test28_verifier(RunInfo info) { + test28(obj, obj); + test28(obj, otherObj); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertCompiledByC2(m); + for (int i = 0; i < 10; i++) { + test28(otherObj, obj); + } + if (WBFlags.UseACmpProfile || WBFlags.TypeProfileLevel != 0) { + TestFramework.assertDeoptimizedByC2(m); + } + } + } + + @Test + @IR(applyIf = {"UseACmpProfile", "true"}, + failOn = {SUBSTITUTABILITY_TEST}, + counts = {NULL_CHECK_TRAP, "= 1", CLASS_CHECK_TRAP, "= 1"}) + @IR(applyIf = {"UseACmpProfile", "false"}, + counts = {SUBSTITUTABILITY_TEST, "= 1"}) + public boolean test29(Object o1, Object o2) { + return o1 != o2; + } + + @Run(test = "test29") + @Warmup(10000) + public void test29_verifier(RunInfo info) { + test29(obj, obj); + test29(otherObj, obj); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertCompiledByC2(m); + for (int i = 0; i < 10; i++) { + test29(obj, otherObj); + } + if (WBFlags.UseACmpProfile) { + TestFramework.assertDeoptimizedByC2(m); + } + } + } + + @Test + @IR(applyIfOr = {"UseACmpProfile", "true", "TypeProfileLevel", "= 222"}, + failOn = {SUBSTITUTABILITY_TEST, NULL_CHECK_TRAP}, + counts = {CLASS_CHECK_TRAP, "= 1"}) + @IR(applyIfAnd = {"UseACmpProfile", "false", "TypeProfileLevel", "!= 222"}, + counts = {SUBSTITUTABILITY_TEST, "= 1"}) + public boolean test30(Object o1, Object o2) { + return o1 == o2; + } + + @Run(test = "test30") + @Warmup(10000) + public void test30_verifier(RunInfo info) { + test30(obj, obj); + test30(obj, otherObj); + test30(null, 42); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertCompiledByC2(m); + for (int i = 0; i < 10; i++) { + test30(otherObj, obj); + } + if (WBFlags.UseACmpProfile || WBFlags.TypeProfileLevel != 0) { + TestFramework.assertDeoptimizedByC2(m); + } + } + } + + @Test + @IR(applyIf = {"UseACmpProfile", "true"}, + failOn = {SUBSTITUTABILITY_TEST, NULL_CHECK_TRAP}) + @IR(applyIf = {"UseACmpProfile", "false"}, + counts = {SUBSTITUTABILITY_TEST, "= 1"}) + public boolean test31(Object o1, Object o2) { + return o1 == o2; + } + + @Run(test = "test31") + @Warmup(10000) + public void test31_verifier(RunInfo info) { + test31(obj, obj); + test31(otherObj, obj); + test31(obj, null); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertCompiledByC2(m); + for (int i = 0; i < 10; i++) { + test31(obj, otherObj); + } + if (WBFlags.UseACmpProfile) { + TestFramework.assertDeoptimizedByC2(m); + } + } + } + + // Input profiled not value class with unknown type + @Test + @IR(applyIf = {"UseACmpProfile", "true"}, + failOn = {SUBSTITUTABILITY_TEST}, + counts = {NULL_CHECK_TRAP, "= 1", CLASS_CHECK_TRAP, "= 1"}) + @IR(applyIf = {"UseACmpProfile", "false"}, + counts = {SUBSTITUTABILITY_TEST, "= 1"}) + public boolean test32(Object o1, Object o2) { + return o1 == o2; + } + + @Run(test = "test32") + @Warmup(10000) + public void test32_verifier(RunInfo info) { + test32(obj, obj); + test32(obj, testValue1); + test32(otherObj, obj); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertCompiledByC2(m); + for (int i = 0; i < 10; i++) { + test32(testValue1, 42); + } + if (WBFlags.UseACmpProfile) { + TestFramework.assertDeoptimizedByC2(m); + } + } + } + + @Test + @IR(applyIf = {"UseACmpProfile", "true"}, + failOn = {SUBSTITUTABILITY_TEST}, + counts = {NULL_CHECK_TRAP, "= 1", CLASS_CHECK_TRAP, "= 1"}) + @IR(applyIf = {"UseACmpProfile", "false"}, + counts = {SUBSTITUTABILITY_TEST, "= 1"}) + public boolean test33(Object o1, Object o2) { + return o1 == o2; + } + + @Run(test = "test33") + @Warmup(10000) + public void test33_verifier(RunInfo info) { + test33(obj, obj); + test33(testValue1, obj); + test33(obj, otherObj); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertCompiledByC2(m); + for (int i = 0; i < 10; i++) { + test33(obj, testValue1); + } + if (WBFlags.UseACmpProfile) { + TestFramework.assertDeoptimizedByC2(m); + } + } + } + + @Test + @IR(applyIf = {"UseACmpProfile", "true"}, + failOn = {SUBSTITUTABILITY_TEST}, + counts = {NULL_CHECK_TRAP, "= 1", CLASS_CHECK_TRAP, "= 1"}) + @IR(applyIf = {"UseACmpProfile", "false"}, + counts = {SUBSTITUTABILITY_TEST, "= 1"}) + public boolean test34(Object o1, Object o2) { + return o1 != o2; + } + + @Run(test = "test34") + @Warmup(10000) + public void test34_verifier(RunInfo info) { + test34(obj, obj); + test34(obj, testValue1); + test34(otherObj, obj); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertCompiledByC2(m); + for (int i = 0; i < 10; i++) { + test34(testValue1, 42); + } + if (WBFlags.UseACmpProfile) { + TestFramework.assertDeoptimizedByC2(m); + } + } + } + + @Test + @IR(applyIf = {"UseACmpProfile", "true"}, + failOn = {SUBSTITUTABILITY_TEST}, + counts = {NULL_CHECK_TRAP, "= 1", CLASS_CHECK_TRAP, "= 1"}) + @IR(applyIf = {"UseACmpProfile", "false"}, + counts = {SUBSTITUTABILITY_TEST, "= 1"}) + public boolean test35(Object o1, Object o2) { + return o1 != o2; + } + + @Run(test = "test35") + @Warmup(10000) + public void test35_verifier(RunInfo info) { + test35(obj, obj); + test35(testValue1, obj); + test35(obj, otherObj); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertCompiledByC2(m); + for (int i = 0; i < 10; i++) { + test35(obj, testValue1); + } + if (WBFlags.UseACmpProfile) { + TestFramework.assertDeoptimizedByC2(m); + } + } + } + + @Test + @IR(applyIf = {"UseACmpProfile", "true"}, + failOn = {SUBSTITUTABILITY_TEST, NULL_CHECK_TRAP}, + counts = {CLASS_CHECK_TRAP, "= 1"}) + @IR(applyIf = {"UseACmpProfile", "false"}, + counts = {SUBSTITUTABILITY_TEST, "= 1"}) + public boolean test36(Object o1, Object o2) { + return o1 == o2; + } + + @Run(test = "test36") + @Warmup(10000) + public void test36_verifier(RunInfo info) { + test36(obj, otherObj); + test36(otherObj, testValue1); + test36(null, obj); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertCompiledByC2(m); + for (int i = 0; i < 10; i++) { + test36(testValue1, obj); + } + if (WBFlags.UseACmpProfile) { + TestFramework.assertDeoptimizedByC2(m); + } + } + } + + @Test + @IR(applyIf = {"UseACmpProfile", "true"}, + failOn = {SUBSTITUTABILITY_TEST, NULL_CHECK_TRAP}) + @IR(applyIf = {"UseACmpProfile", "false"}, + counts = {SUBSTITUTABILITY_TEST, "= 1"}) + public boolean test37(Object o1, Object o2) { + return o1 == o2; + } + + @Run(test = "test37") + @Warmup(10000) + public void test37_verifier(RunInfo info) { + test37(otherObj, obj); + test37(testValue1, otherObj); + test37(obj, null); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertCompiledByC2(m); + for (int i = 0; i < 10; i++) { + test37(obj, testValue1); + } + if (WBFlags.UseACmpProfile) { + TestFramework.assertDeoptimizedByC2(m); + } + } + } + + // Test that acmp profile data that's unused at the acmp is fed to + // speculation and leverage later + @Test + @IR(applyIfOr = {"UseACmpProfile", "true", "TypeProfileLevel", "= 222"}, + failOn = {SUBSTITUTABILITY_TEST}, + counts = {CLASS_CHECK_TRAP, "= 2"}) + @IR(applyIfAnd = {"UseACmpProfile", "false", "TypeProfileLevel", "!= 222"}, + counts = {SUBSTITUTABILITY_TEST, "= 2"}) + public void test38(Object o1, Object o2, Object o3) { + if (o1 == o2) { + test38_helper2(); + } + test38_helper(o1, o3); + } + + public void test38_helper(Object o1, Object o2) { + if (o1 == o2) { + } + } + + public void test38_helper2() { + } + + @Run(test = "test38") + @Warmup(10000) + public void test38_verifier() { + test38(obj, obj, obj); + test38_helper(testValue1, testValue2); + } + + @Test + @IR(applyIfOr = {"UseACmpProfile", "true", "TypeProfileLevel", "= 222"}, + failOn = {SUBSTITUTABILITY_TEST}, + counts = {CLASS_CHECK_TRAP, "= 2"}) + @IR(applyIfAnd = {"UseACmpProfile", "false", "TypeProfileLevel", "!= 222"}, + counts = {SUBSTITUTABILITY_TEST, "= 2"}) + public void test39(Object o1, Object o2, Object o3) { + if (o1 == o2) { + test39_helper2(); + } + test39_helper(o2, o3); + } + + public void test39_helper(Object o1, Object o2) { + if (o1 == o2) { + } + } + + public void test39_helper2() { + } + + @Run(test = "test39") + @Warmup(10000) + public void test39_verifier() { + test39(obj, obj, obj); + test39_helper(testValue1, testValue2); + } + + // Test array access with polluted array type profile + static abstract value class Test40Abstract { } + static value class Test40Class extends Test40Abstract { } + + @LooselyConsistentValue + static value class Test40Inline extends Test40Abstract { } + + @ForceInline + public Object test40_access(Object[] array) { + return array[0]; + } + + @Test + public Object test40(Test40Abstract[] array) { + return test40_access(array); + } + + @Run(test = "test40") + @Warmup(10000) + public void test40_verifier(RunInfo info) { + // Make sure multiple implementors of Test40Abstract are loaded + Test40Inline tmp1 = new Test40Inline(); + Test40Class tmp2 = new Test40Class(); + if (info.isWarmUp()) { + // Pollute profile with Object[] (exact) + test40_access(new Object[1]); + } else { + // When inlining test40_access, profiling contradicts actual type of array + test40(new Test40Class[1]); + } + } + + // Same as test40 but with array store + @ForceInline + public void test41_access(Object[] array, Object val) { + array[0] = val; + } + + @Test + public void test41(Test40Inline[] array, Object val) { + test41_access(array, val); + } + + @Run(test = "test41") + @Warmup(10000) + public void test41_verifier(RunInfo info) { + // Make sure multiple implementors of Test40Abstract are loaded + Test40Inline tmp1 = new Test40Inline(); + Test40Class tmp2 = new Test40Class(); + if (info.isWarmUp()) { + // Pollute profile with exact Object[] + test41_access(new Object[1], new Object()); + } else { + // When inlining test41_access, profiling contradicts actual type of array + Test40Inline[] array = (Test40Inline[])ValueClass.newNullRestrictedNonAtomicArray(Test40Inline.class, 1, new Test40Inline()); + test41(array, new Test40Inline()); + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestLarvalState.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestLarvalState.java new file mode 100644 index 00000000000..770633557cf --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestLarvalState.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2023, Arm Limited. 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8303416 + * @summary Fix JVM crash at Unsafe_FinishPrivateBuffer + * @library /test/lib + * @enablePreview + * @modules java.base/jdk.internal.misc + * @run main/othervm compiler.valhalla.inlinetypes.TestLarvalState + */ + +package compiler.valhalla.inlinetypes; + +import java.lang.reflect.*; +import java.util.Random; + +import jdk.internal.misc.Unsafe; +import jdk.test.lib.Asserts; +import jdk.test.lib.Utils; + +public class TestLarvalState { + private static int LENGTH = 10000; + + private static final Random RD = Utils.getRandomInstance(); + + static byte[] arr = new byte[LENGTH]; + + static { + for (int i = 0; i < LENGTH; i++) { + arr[i] = (byte) RD.nextInt(127); + } + } + + public static byte test(byte b) { + Value obj = new Value(); + obj = Unsafe.getUnsafe().makePrivateBuffer(obj); + Unsafe.getUnsafe().putByte(obj, obj.offset, b); + obj = Unsafe.getUnsafe().finishPrivateBuffer(obj); + return Unsafe.getUnsafe().getByte(obj, obj.offset); + } + + public static void main(String[] args) { + byte actual = 0; + for (int i = 0; i < LENGTH; i++) { + actual += test(arr[i]); + } + + byte expected = 0; + for (int i = 0; i < LENGTH; i++) { + expected += arr[i]; + } + Asserts.assertEquals(expected, actual); + } + + static value class Value { + byte field = 0; + + static long offset = fieldOffset(); + + private static long fieldOffset() { + try { + var f = Value.class.getDeclaredField("field"); + return Unsafe.getUnsafe().objectFieldOffset(f); + } catch (Exception e) { + System.out.println(e); + } + return -1L; + } + } +} + diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestLoopUnswitchingWithFlatArrayCheck.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestLoopUnswitchingWithFlatArrayCheck.java new file mode 100644 index 00000000000..02e52c924c0 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestLoopUnswitchingWithFlatArrayCheck.java @@ -0,0 +1,66 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8275276 + * @summary Test loop unswitching with flat array checks. + * @enablePreview + * @run main/othervm -Xcomp -XX:CompileCommand=compileonly,TestLoopUnswitchingWithFlatArrayCheck::test TestLoopUnswitchingWithFlatArrayCheck + */ + +public class TestLoopUnswitchingWithFlatArrayCheck { + + Object[] objectArray = new Object[1000]; + + boolean test(Object o) { + return false; + } + + private Object test(int start, int end) { + Object res = null; + Object[] array = objectArray; + if (array == null) { + return null; + } + for (int i = start; ; i++) { + if (test(array[i])) { + continue; + } + if (i == end) { + break; + } + } + for (int i = 0; i < 100; i++) { + for (; i < 100; i++) { + res = array[i]; + } + } + return res; + } + + public static void main(String[] args) { + TestLoopUnswitchingWithFlatArrayCheck t = new TestLoopUnswitchingWithFlatArrayCheck(); + t.test(0, 10); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestMeetingAryPtr.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestMeetingAryPtr.java new file mode 100644 index 00000000000..727a17f379e --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestMeetingAryPtr.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8353180 + * @summary Test that meeting TypeAry*Ptr works with new layouts and does not trigger "not monotonic" assert. + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @enablePreview + * @run main/othervm -Xcomp -XX:CompileOnly=*TestMeetingAryPtr*::test* -XX:+UnlockDiagnosticVMOptions -XX:+StressCCP + * -XX:RepeatCompilation=100 compiler.valhalla.inlinetypes.TestMeetingAryPtr + * @run main compiler.valhalla.inlinetypes.TestMeetingAryPtr + */ + +package compiler.valhalla.inlinetypes; + +import jdk.internal.value.ValueClass; + +public class TestMeetingAryPtr { + static V vFld = new V(34); + + static V[] vArrFlat = new V[100]; + static final V[] vArrFinalNullable = (V[]) ValueClass.newNullableAtomicArray(V.class, 100); + static final V[] vArrFinalNullFree = (V[]) ValueClass.newNullRestrictedNonAtomicArray(V.class, 100, new V(0)); + static final V[] vArrFinalNullFree2 = (V[]) ValueClass.newNullRestrictedNonAtomicArray(V.class, 100, new V(0)); + + static boolean flag; + + public static void main(String[] args) { + for (int i = 0; i < 10000; i++) { + testNonConstant(); + testConstantDifferentNullNess(); + testConstantSameNullNess(); + flag = !flag; + } + } + + static void testNonConstant() { + for (int i = 0; i < 100; ++i) { + vFld = vArrFlat[i]; + } + } + + static void testConstantDifferentNullNess() { + // Meeting: ConP(flat+nullable) ConP(flat+null-free) + V[] arr = flag ? vArrFinalNullable : vArrFinalNullFree; + // Phi for arr after meet: flat+maybe-null-free+exact + // -> Wrongly set to exact even though it's maybe null free only. + // This causes an assertion failure during CCP. + vFld = arr[2]; + } + + static void testConstantSameNullNess() { + V[] arr = flag ? vArrFinalNullable : vArrFinalNullFree2; + vFld = arr[2]; + } +} + +value class V { + int x; + + V(int x) { + this.x = x; + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestMemBars.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestMemBars.java new file mode 100644 index 00000000000..3163a981da8 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestMemBars.java @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8354981 8359345 + * @summary Test that membars are emitted around flat, atomic loads and stores. + * @library /test/lib + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm -Xbatch TestMemBars + */ + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import jdk.test.lib.Asserts; + +public class TestMemBars { + static long VAL = 42; // Prevent constant folding + + static value class MyValue1 { + @Strict + @NullRestricted + MyValue3 val = new MyValue3(); // Too large to be flattened + + int unused = 42; // Make sure it's not naturally atomic + } + + static value class MyValue2 { + @Strict + MyValue3 val = new MyValue3(); // Too large to be flattened + + int unused = 42; // Make sure it's not naturally atomic + } + + static value class MyValue3 { + long l0 = VAL; + long l1 = VAL; + } + + @Strict + @NullRestricted + MyValue1 field1 = new MyValue1(); + + @Strict + @NullRestricted + MyValue2 field2 = new MyValue2(); + + static MyValue1[] array1 = new MyValue1[1]; + static MyValue1[] array2 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, new MyValue1()); + static MyValue1[] array3 = (MyValue1[])ValueClass.newNullRestrictedAtomicArray(MyValue1.class, 1, new MyValue1()); + static MyValue1[] array4 = (MyValue1[])ValueClass.newNullableAtomicArray(MyValue1.class, 1); + static { + array1[0] = new MyValue1(); + array4[0] = new MyValue1(); + } + + static MyValue2[] array5 = new MyValue2[1]; + static MyValue2[] array6 = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 1, new MyValue2()); + static MyValue2[] array7 = (MyValue2[])ValueClass.newNullRestrictedAtomicArray(MyValue2.class, 1, new MyValue2()); + static MyValue2[] array8 = (MyValue2[])ValueClass.newNullableAtomicArray(MyValue2.class, 1); + static { + array5[0] = new MyValue2(); + array8[0] = new MyValue2(); + } + + public long testFieldLoad1() { + return field1.val.l0; + } + + public void testFieldStore1(MyValue1 val) { + field1 = val; + } + + public long testFieldLoad2() { + return field2.val.l0; + } + + public void testFieldStore2(MyValue2 val) { + field2 = val; + } + + public long testArrayLoad1() { + return array1[0].val.l0; + } + + public void testArrayStore1(MyValue1 val) { + array1[0] = val; + } + + public long testArrayLoad2() { + return array2[0].val.l0; + } + + public void testArrayStore2(MyValue1 val) { + array2[0] = val; + } + + public long testArrayLoad3() { + return array3[0].val.l0; + } + + public void testArrayStore3(MyValue1 val) { + array3[0] = val; + } + + public long testArrayLoad4() { + return array4[0].val.l0; + } + + public void testArrayStore4(MyValue1 val) { + array4[0] = val; + } + + public long testArrayLoad5() { + return array5[0].val.l0; + } + + public void testArrayStore5(MyValue2 val) { + array5[0] = val; + } + + public long testArrayLoad6() { + return array6[0].val.l0; + } + + public void testArrayStore6(MyValue2 val) { + array6[0] = val; + } + + public long testArrayLoad7() { + return array7[0].val.l0; + } + + public void testArrayStore7(MyValue2 val) { + array7[0] = val; + } + + public long testArrayLoad8() { + return array8[0].val.l0; + } + + public void testArrayStore8(MyValue2 val) { + array8[0] = val; + } + + public long testFieldLoadStore1(MyValue1 val) { + long res = field1.val.l0; + field1 = val; + return res; + } + + public long testFieldLoadStore1Independent(MyValue1 val) { + long res = field2.val.l0; + field1 = val; + return res; + } + + public long testFieldStoreLoad1(MyValue1 val) { + field1 = val; + return field1.val.l0; + } + + public long testFieldStoreLoad1Independent(MyValue1 val) { + field1 = val; + return field2.val.l0; + } + + public long testArrayLoadStore1(MyValue1 val) { + long res = array3[0].val.l0; + array3[0] = val; + return res; + } + + public long testArrayLoadStore1Independent(MyValue1 val) { + long res = array7[0].val.l0; + array3[0] = val; + return res; + } + + public long testArrayStoreLoad1(MyValue1 val) { + array3[0] = val; + return array3[0].val.l0; + } + + public long testArrayStoreLoad1Independent(MyValue1 val) { + array3[0] = val; + return array7[0].val.l0; + } + + public static void main(String[] args) { + TestMemBars t = new TestMemBars(); + for (int i = 0; i < 50_000; ++i) { + VAL++; + MyValue1 val1 = new MyValue1(); + MyValue2 val2 = new MyValue2(); + + t.testFieldStore1(val1); + Asserts.assertEQ(t.testFieldLoad1(), VAL); + t.testFieldStore2(val2); + Asserts.assertEQ(t.testFieldLoad2(), VAL); + t.testArrayStore1(val1); + Asserts.assertEQ(t.testArrayLoad1(), VAL); + t.testArrayStore2(val1); + Asserts.assertEQ(t.testArrayLoad2(), VAL); + t.testArrayStore3(val1); + Asserts.assertEQ(t.testArrayLoad3(), VAL); + t.testArrayStore4(val1); + Asserts.assertEQ(t.testArrayLoad4(), VAL); + t.testArrayStore5(val2); + Asserts.assertEQ(t.testArrayLoad5(), VAL); + t.testArrayStore6(val2); + Asserts.assertEQ(t.testArrayLoad6(), VAL); + t.testArrayStore7(val2); + Asserts.assertEQ(t.testArrayLoad7(), VAL); + t.testArrayStore8(val2); + Asserts.assertEQ(t.testArrayLoad8(), VAL); + + VAL++; + val1 = new MyValue1(); + val2 = new MyValue2(); + + Asserts.assertEQ(t.testFieldLoadStore1(val1), VAL-1); + Asserts.assertEQ(t.field1, val1); + Asserts.assertEQ(t.testFieldLoadStore1Independent(val1), VAL-1); + Asserts.assertEQ(t.field1, val1); + + VAL++; + val1 = new MyValue1(); + val2 = new MyValue2(); + + Asserts.assertEQ(t.testFieldStoreLoad1(val1), VAL); + Asserts.assertEQ(t.field1, val1); + Asserts.assertEQ(t.testFieldStoreLoad1Independent(val1), VAL-2); + Asserts.assertEQ(t.field1, val1); + + VAL++; + val1 = new MyValue1(); + val2 = new MyValue2(); + + Asserts.assertEQ(t.testArrayLoadStore1(val1), VAL-3); + Asserts.assertEQ(t.array3[0], val1); + Asserts.assertEQ(t.testArrayLoadStore1Independent(val1), VAL-3); + Asserts.assertEQ(t.array3[0], val1); + + VAL++; + val1 = new MyValue1(); + val2 = new MyValue2(); + + Asserts.assertEQ(t.testArrayStoreLoad1(val1), VAL); + Asserts.assertEQ(t.array3[0], val1); + Asserts.assertEQ(t.testArrayStoreLoad1Independent(val1), VAL-4); + Asserts.assertEQ(t.array3[0], val1); + } + } +} + diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestMethodHandles.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestMethodHandles.java new file mode 100644 index 00000000000..af715ad60e0 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestMethodHandles.java @@ -0,0 +1,549 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.*; +import jdk.test.lib.Asserts; +import jdk.test.whitebox.WhiteBox; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; + +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.ALLOC_ARRAY_OF_MYVALUE_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.ALLOC_OF_MYVALUE_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.STORE_INLINE_FIELDS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.STORE_OF_ANY_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypes.*; + +import static compiler.lib.ir_framework.IRNode.STATIC_CALL; + +/* + * @test + * @key randomness + * @summary Test method handle support for inline types + * @library /test/lib / + * @requires (os.simpleArch == "x64" | os.simpleArch == "aarch64") + * @requires vm.opt.AbortVMOnCompilationFailure != true + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm/timeout=450 compiler.valhalla.inlinetypes.TestMethodHandles + */ + +@ForceCompileClassInitializer +public class TestMethodHandles { + + static { + try { + Class clazz = TestMethodHandles.class; + MethodHandles.Lookup lookup = MethodHandles.lookup(); + + MethodType mt = MethodType.methodType(MyValue3.class); + test1_mh = lookup.findVirtual(clazz, "test1_target", mt); + test2_mh = lookup.findVirtual(clazz, "test2_target", mt); + test3_mh = lookup.findVirtual(clazz, "test3_target", mt); + + MethodType test4_mt1 = MethodType.methodType(int.class, MyValue1.class); + MethodType test4_mt2 = MethodType.methodType(MyValue1.class); + MethodHandle test4_mh1 = lookup.findStatic(clazz, "test4_helper1", test4_mt1); + MethodHandle test4_mh2 = lookup.findStatic(clazz, "test4_helper2", test4_mt2); + test4_mh = MethodHandles.filterReturnValue(test4_mh2, test4_mh1); + + MethodType test5_mt = MethodType.methodType(int.class, MyValue1.class); + test5_mh = lookup.findVirtual(clazz, "test5_target", test5_mt); + + MethodType test6_mt = MethodType.methodType(MyValue3.class); + MethodHandle test6_mh1 = lookup.findVirtual(clazz, "test6_target1", test6_mt); + MethodHandle test6_mh2 = lookup.findVirtual(clazz, "test6_target2", test6_mt); + MethodType boolean_mt = MethodType.methodType(boolean.class); + MethodHandle test6_mh_test = lookup.findVirtual(clazz, "test6_test", boolean_mt); + test6_mh = MethodHandles.guardWithTest(test6_mh_test, test6_mh1, test6_mh2); + + MethodType myvalue2_mt = MethodType.methodType(MyValue2.class); + test7_mh1 = lookup.findStatic(clazz, "test7_target1", myvalue2_mt); + MethodHandle test7_mh2 = lookup.findStatic(clazz, "test7_target2", myvalue2_mt); + MethodHandle test7_mh_test = lookup.findStatic(clazz, "test7_test", boolean_mt); + test7_mh = MethodHandles.guardWithTest(test7_mh_test, + MethodHandles.invoker(myvalue2_mt), + MethodHandles.dropArguments(test7_mh2, 0, MethodHandle.class)); + + MethodHandle test8_mh1 = lookup.findStatic(clazz, "test8_target1", myvalue2_mt); + test8_mh2 = lookup.findStatic(clazz, "test8_target2", myvalue2_mt); + MethodHandle test8_mh_test = lookup.findStatic(clazz, "test8_test", boolean_mt); + test8_mh = MethodHandles.guardWithTest(test8_mh_test, + MethodHandles.dropArguments(test8_mh1, 0, MethodHandle.class), + MethodHandles.invoker(myvalue2_mt)); + + MethodType test9_mt = MethodType.methodType(MyValue3.class); + MethodHandle test9_mh1 = lookup.findVirtual(clazz, "test9_target1", test9_mt); + MethodHandle test9_mh2 = lookup.findVirtual(clazz, "test9_target2", test9_mt); + MethodHandle test9_mh3 = lookup.findVirtual(clazz, "test9_target3", test9_mt); + MethodType test9_mt2 = MethodType.methodType(boolean.class); + MethodHandle test9_mh_test1 = lookup.findVirtual(clazz, "test9_test1", test9_mt2); + MethodHandle test9_mh_test2 = lookup.findVirtual(clazz, "test9_test2", test9_mt2); + test9_mh = MethodHandles.guardWithTest(test9_mh_test1, + test9_mh1, + MethodHandles.guardWithTest(test9_mh_test2, test9_mh2, test9_mh3)); + + MethodType test10_mt = MethodType.methodType(MyValue2.class); + MethodHandle test10_mh1 = lookup.findStatic(clazz, "test10_target1", test10_mt); + test10_mh2 = lookup.findStatic(clazz, "test10_target2", test10_mt); + test10_mh3 = lookup.findStatic(clazz, "test10_target3", test10_mt); + MethodType test10_mt2 = MethodType.methodType(boolean.class); + MethodType test10_mt3 = MethodType.methodType(MyValue2.class); + MethodHandle test10_mh_test1 = lookup.findStatic(clazz, "test10_test1", test10_mt2); + MethodHandle test10_mh_test2 = lookup.findStatic(clazz, "test10_test2", test10_mt2); + test10_mh = MethodHandles.guardWithTest(test10_mh_test1, + MethodHandles.dropArguments(test10_mh1, 0, MethodHandle.class, MethodHandle.class), + MethodHandles.guardWithTest(test10_mh_test2, + MethodHandles.dropArguments(MethodHandles.invoker(test10_mt3), 1, MethodHandle.class), + MethodHandles.dropArguments(MethodHandles.invoker(test10_mt3), 0, MethodHandle.class)) + ); + + MethodHandle test11_mh1 = lookup.findStatic(clazz, "test11_target1", myvalue2_mt); + test11_mh2 = lookup.findStatic(clazz, "test11_target2", myvalue2_mt); + MethodHandle test11_mh_test = lookup.findStatic(clazz, "test11_test", boolean_mt); + test11_mh = MethodHandles.guardWithTest(test11_mh_test, + MethodHandles.dropArguments(test11_mh1, 0, MethodHandle.class), + MethodHandles.invoker(myvalue2_mt)); + } catch (NoSuchMethodException | IllegalAccessException e) { + e.printStackTrace(); + throw new RuntimeException("Method handle lookup failed"); + } + } + + public static void main(String[] args) { + Scenario[] scenarios = InlineTypes.DEFAULT_SCENARIOS; + + // Prevent inlining through MethodHandle linkTo adapters to stress the calling convention + scenarios[2].addFlags("-DVerifyIR=false", + "-XX:CompileCommand=dontinline,java.lang.invoke.DirectMethodHandle::internalMemberName"); + scenarios[4].addFlags("-XX:CompileCommand=dontinline,java.lang.invoke.DirectMethodHandle::internalMemberName"); + + InlineTypes.getFramework() + .addScenarios(scenarios) + .addFlags("-XX:CompileCommand=inline,java.lang.invoke.MethodHandleImpl::*") + .addFlags("-XX:CompileCommand=inline,java.lang.invoke.LambdaForm*::*") + .addHelperClasses(MyValue1.class, + MyValue2.class, + MyValue2Inline.class, + MyValue3.class, + MyValue3Inline.class) + .start(); + } + + // Everything inlined + @Strict + @NullRestricted + final MyValue3 test1_vt = MyValue3.create(); + + @ForceInline + MyValue3 test1_target() { + return test1_vt; + } + + static final MethodHandle test1_mh; + + @Test + @IR(applyIf = {"InlineTypeReturnedAsFields", "true"}, + failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, STATIC_CALL}) + @IR(applyIf = {"InlineTypeReturnedAsFields", "false"}, + counts = {ALLOC_OF_MYVALUE_KLASS, "= 1", STORE_OF_ANY_KLASS, "= 14"}) + public MyValue3 test1() throws Throwable { + return (MyValue3)test1_mh.invokeExact(this); + } + + @Run(test = "test1") + @Warmup(10000) + public void test1_verifier() throws Throwable { + MyValue3 vt = test1(); + test1_vt.verify(vt); + } + + // Leaf method not inlined but returned type is known + @Strict + @NullRestricted + final MyValue3 test2_vt = MyValue3.create(); + + @DontInline + MyValue3 test2_target() { + return test2_vt; + } + + static final MethodHandle test2_mh; + + @Test + public MyValue3 test2() throws Throwable { + return (MyValue3)test2_mh.invokeExact(this); + } + + @Run(test = "test2") + public void test2_verifier(RunInfo info) throws Throwable { + if (!info.isWarmUp()) { + Method helper_m = getClass().getDeclaredMethod("test2_target"); + if (!TestFramework.isCompiled(helper_m)) { + TestFramework.compile(helper_m, CompLevel.C2); + } + } + MyValue3 vt = test2(); + test2_vt.verify(vt); + } + + // Leaf method not inlined and returned type not known + @Strict + @NullRestricted + final MyValue3 test3_vt = MyValue3.create(); + + @DontInline + MyValue3 test3_target() { + return test3_vt; + } + + static final MethodHandle test3_mh; + + @Test + public MyValue3 test3() throws Throwable { + return (MyValue3)test3_mh.invokeExact(this); + } + + @Run(test = "test3") + public void test3_verifier(RunInfo info) throws Throwable { + // hack so C2 doesn't know the target of the invoke call + Class c = Class.forName("java.lang.invoke.DirectMethodHandle"); + Method m = c.getDeclaredMethod("internalMemberName", Object.class); + WhiteBox.getWhiteBox().testSetDontInlineMethod(m, info.isWarmUp()); + MyValue3 vt = test3(); + test3_vt.verify(vt); + } + + // When test75_helper1 is inlined in test75, the method handle + // linker that called it is passed a pointer to a copy of vt + // stored in memory. The method handle linker needs to load the + // fields from memory before it inlines test75_helper1. + static public int test4_helper1(MyValue1 vt) { + return vt.x; + } + + @Strict + @NullRestricted + static MyValue1 test4_vt = MyValue1.createWithFieldsInline(rI, rL); + + static public MyValue1 test4_helper2() { + return test4_vt; + } + + static final MethodHandle test4_mh; + + @Test + public int test4() throws Throwable { + return (int)test4_mh.invokeExact(); + } + + @Run(test = "test4") + public void test4_verifier() throws Throwable { + int i = test4(); + Asserts.assertEQ(i, test4_vt.x); + } + + // Test method handle call with inline type argument + public int test5_target(MyValue1 vt) { + return vt.x; + } + + static final MethodHandle test5_mh; + + @Strict + @NullRestricted + MyValue1 test5_vt = MyValue1.createWithFieldsInline(rI, rL); + + @Test + public int test5() throws Throwable { + return (int)test5_mh.invokeExact(this, test5_vt); + } + + @Run(test = "test5") + public void test5_verifier() throws Throwable { + int i = test5(); + Asserts.assertEQ(i, test5_vt.x); + } + + // Return of target1 and target2 merged in a Lambda Form as an + // Object. Shouldn't cause any allocation + @Strict + @NullRestricted + final MyValue3 test6_vt1 = MyValue3.create(); + + @ForceInline + MyValue3 test6_target1() { + return test6_vt1; + } + + @Strict + @NullRestricted + final MyValue3 test6_vt2 = MyValue3.create(); + + @ForceInline + MyValue3 test6_target2() { + return test6_vt2; + } + + boolean test6_bool = true; + @ForceInline + boolean test6_test() { + return test6_bool; + } + + static final MethodHandle test6_mh; + + @Test + @IR(applyIf = {"InlineTypeReturnedAsFields", "true"}, + failOn = {ALLOC_OF_MYVALUE_KLASS, ALLOC_ARRAY_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, STORE_INLINE_FIELDS}) + public MyValue3 test6() throws Throwable { + return (MyValue3)test6_mh.invokeExact(this); + } + + @Run(test = "test6") + public void test6_verifier() throws Throwable { + test6_bool = !test6_bool; + MyValue3 vt = test6(); + vt.verify(test6_bool ? test6_vt1 : test6_vt2); + } + + // Similar as above but with the method handle for target1 not + // constant. Shouldn't cause any allocation. + @ForceInline + static MyValue2 test7_target1() { + return MyValue2.createWithFieldsInline(rI, rD); + } + + @ForceInline + static MyValue2 test7_target2() { + return MyValue2.createWithFieldsInline(rI+1, rD+1); + } + + static boolean test7_bool = true; + @ForceInline + static boolean test7_test() { + return test7_bool; + } + + static final MethodHandle test7_mh; + static MethodHandle test7_mh1; + + @Test + public long test7() throws Throwable { + return ((MyValue2)test7_mh.invokeExact(test7_mh1)).hash(); + } + + @Run(test = "test7") + public void test7_verifier() throws Throwable { + test7_bool = !test7_bool; + long hash = test7(); + Asserts.assertEQ(hash, MyValue2.createWithFieldsInline(rI+(test7_bool ? 0 : 1), rD+(test7_bool ? 0 : 1)).hash()); + } + + // Same as above but with the method handle for target2 not + // constant. Shouldn't cause any allocation. + @ForceInline + static MyValue2 test8_target1() { + return MyValue2.createWithFieldsInline(rI, rD); + } + + @ForceInline + static MyValue2 test8_target2() { + return MyValue2.createWithFieldsInline(rI+1, rD+1); + } + + static boolean test8_bool = true; + @ForceInline + static boolean test8_test() { + return test8_bool; + } + + static final MethodHandle test8_mh; + static MethodHandle test8_mh2; + + @Test + public long test8() throws Throwable { + return ((MyValue2)test8_mh.invokeExact(test8_mh2)).hash(); + } + + @Run(test = "test8") + public void test8_verifier() throws Throwable { + test8_bool = !test8_bool; + long hash = test8(); + Asserts.assertEQ(hash, MyValue2.createWithFieldsInline(rI+(test8_bool ? 0 : 1), rD+(test8_bool ? 0 : 1)).hash()); + } + + // Return of target1, target2 and target3 merged in Lambda Forms + // as an Object. Shouldn't cause any allocation + @Strict + @NullRestricted + final MyValue3 test9_vt1 = MyValue3.create(); + + @ForceInline + MyValue3 test9_target1() { + return test9_vt1; + } + + @Strict + @NullRestricted + final MyValue3 test9_vt2 = MyValue3.create(); + + @ForceInline + MyValue3 test9_target2() { + return test9_vt2; + } + + @Strict + @NullRestricted + final MyValue3 test9_vt3 = MyValue3.create(); + + @ForceInline + MyValue3 test9_target3() { + return test9_vt3; + } + + boolean test9_bool1 = true; + @ForceInline + boolean test9_test1() { + return test9_bool1; + } + + boolean test9_bool2 = true; + @ForceInline + boolean test9_test2() { + return test9_bool2; + } + + static final MethodHandle test9_mh; + + @Test + @IR(applyIf = {"InlineTypeReturnedAsFields", "true"}, + failOn = {ALLOC_OF_MYVALUE_KLASS, ALLOC_ARRAY_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, STORE_INLINE_FIELDS}) + public MyValue3 test9() throws Throwable { + return (MyValue3)test9_mh.invokeExact(this); + } + + static int test9_i = 0; + @Run(test = "test9") + public void test9_verifier() throws Throwable { + test9_i++; + test9_bool1 = (test9_i % 2) == 0; + test9_bool2 = (test9_i % 3) == 0; + MyValue3 vt = test9(); + vt.verify(test9_bool1 ? test9_vt1 : (test9_bool2 ? test9_vt2 : test9_vt3)); + } + + // Same as above but with non constant target2 and target3 + @ForceInline + static MyValue2 test10_target1() { + return MyValue2.createWithFieldsInline(rI, rD); + } + + @ForceInline + static MyValue2 test10_target2() { + return MyValue2.createWithFieldsInline(rI+1, rD+1); + } + + @ForceInline + static MyValue2 test10_target3() { + return MyValue2.createWithFieldsInline(rI+2, rD+2); + } + + static boolean test10_bool1 = true; + @ForceInline + static boolean test10_test1() { + return test10_bool1; + } + + static boolean test10_bool2 = true; + @ForceInline + static boolean test10_test2() { + return test10_bool2; + } + + static final MethodHandle test10_mh; + static MethodHandle test10_mh2; + static MethodHandle test10_mh3; + + @Test + public long test10() throws Throwable { + return ((MyValue2)test10_mh.invokeExact(test10_mh2, test10_mh3)).hash(); + } + + static int test10_i = 0; + + @Run(test = "test10") + public void test10_verifier() throws Throwable { + test10_i++; + test10_bool1 = (test10_i % 2) == 0; + test10_bool2 = (test10_i % 3) == 0; + long hash = test10(); + int i = rI + (test10_bool1 ? 0 : (test10_bool2 ? 1 : 2)); + double d = rD + (test10_bool1 ? 0 : (test10_bool2 ? 1 : 2)); + Asserts.assertEQ(hash, MyValue2.createWithFieldsInline(i, d).hash()); + } + + static int test11_i = 0; + + @ForceInline + static MyValue2 test11_target1() { + return MyValue2.createWithFieldsInline(rI+test11_i, rD+test11_i); + } + + @ForceInline + static MyValue2 test11_target2() { + return MyValue2.createWithFieldsInline(rI-test11_i, rD-test11_i); + } + + @ForceInline + static boolean test11_test() { + return (test11_i % 100) == 0; + } + + static final MethodHandle test11_mh; + static MethodHandle test11_mh2; + + // Check that a buffered inline type returned by a compiled lambda form + // is properly handled by the caller. + @Test + public long test11() throws Throwable { + return ((MyValue2)test11_mh.invokeExact(test11_mh2)).hash(); + } + + @Run(test = "test11") + @Warmup(11000) + public void test11_verifier() throws Throwable { + test11_i++; + long hash = test11(); + boolean b = (test11_i % 100) == 0; + Asserts.assertEQ(hash, MyValue2.createWithFieldsInline(rI+test11_i * (b ? 1 : -1), rD+test11_i * (b ? 1 : -1)).hash()); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestMismatchHandling.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestMismatchHandling.java new file mode 100644 index 00000000000..f2cabb1845d --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestMismatchHandling.java @@ -0,0 +1,210 @@ +/* + * 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. + */ + +/** + * @test + * @bug 8301007 + * @key randomness + * @summary Verify that mismatches of the preload attribute are properly handled in the calling convention. + * @library /test/lib /compiler/whitebox / + * @enablePreview + * @compile TestMismatchHandling.jcod TestMismatchHandling.java + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -Xbatch + * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:-Inline -XX:-InlineAccessors -XX:-UseBimorphicInlining -XX:-UseCHA -XX:-UseTypeProfile + * -XX:CompileCommand=compileonly,TestMismatchHandling::test* + * TestMismatchHandling + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -Xbatch + * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:-Inline -XX:-InlineAccessors -XX:-UseBimorphicInlining -XX:-UseCHA -XX:-UseTypeProfile + * -XX:CompileCommand=compileonly,*::method + * TestMismatchHandling + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -Xbatch + * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:-Inline -XX:-InlineAccessors -XX:-UseBimorphicInlining -XX:-UseCHA -XX:-UseTypeProfile + * TestMismatchHandling + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -Xbatch + * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:-Inline -XX:-InlineAccessors -XX:-UseBimorphicInlining -XX:-UseCHA -XX:-UseTypeProfile + * -XX:-InlineTypePassFieldsAsArgs + * TestMismatchHandling + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -Xbatch + * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:-Inline -XX:-InlineAccessors -XX:-UseBimorphicInlining -XX:-UseCHA -XX:-UseTypeProfile + * -XX:-InlineTypeReturnedAsFields + * TestMismatchHandling + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -Xbatch + * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:+DeoptimizeNMethodBarriersALot + * TestMismatchHandling + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -Xbatch + * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * TestMismatchHandling + */ + +// ##################################### WARNING ###################################### +// Use below script to re-generate TestMismatchHandling.jcod, don't modify it manually. +// Be careful when changing anything (even the order) in this test and related files. +// ##################################### WARNING ###################################### + +/* + #!/bin/bash + export PATH=/oracle/valhalla/build/fastdebug/jdk/bin/:$PATH + ASMTOOLS=/oracle/valhalla/open/test/lib + + # With preload attribute + javac --enable-preview --source 22 TestMismatchHandlingGenerator.java + java -cp $ASMTOOLS org.openjdk.asmtools.Main jdec MyValue1.class MyValue2.class MyValue3.class MyValue4.class MyValue5.class MyValue6.class MyValue7.class Verifiable.class B.class I3.class I4.class E.class G.class J.class K.class L.class P.class Q.class R.class S.class TestMismatchHandlingHelper.class > TestMismatchHandling.jcod + + # Without preload attribute + sed -i 's/value class MyValue/class MyValue/g' TestMismatchHandlingGenerator.java + javac TestMismatchHandlingGenerator.java + java -cp $ASMTOOLS org.openjdk.asmtools.Main jdec A.class C.class I1.class I2.class D.class F.class H.class I5.class M.class N.class O.class I6.class P.class >> TestMismatchHandling.jcod + + sed -i 's/class MyValue/value class MyValue/g' TestMismatchHandlingGenerator.java +*/ + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; + +import jdk.test.lib.Utils; +import jdk.test.whitebox.WhiteBox; + +public class TestMismatchHandling { + public static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + + public static void main(String[] args) throws Exception { + M m = new M(); + // Make sure M::method is C1 compiled once with unloaded MyValue4 and not re-compiled + for (int i = 0; i < 1000; ++i) { + TestMismatchHandlingHelper.test4(m, true); + } + Method disable = M.class.getDeclaredMethod("method", boolean.class); + WHITE_BOX.makeMethodNotCompilable(disable, 1, false); + WHITE_BOX.makeMethodNotCompilable(disable, 2, false); + WHITE_BOX.makeMethodNotCompilable(disable, 3, false); + WHITE_BOX.makeMethodNotCompilable(disable, 4, false); + + // Sometimes, exclude some methods from compilation with C2 to stress test the calling convention + // WARNING: This triggers class loading of argument/return types of all methods! + if (Utils.getRandomInstance().nextBoolean()) { + ArrayList methods = new ArrayList(); + Collections.addAll(methods, TestMismatchHandlingHelper.class.getDeclaredMethods()); + Collections.addAll(methods, A.class.getDeclaredMethods()); + Collections.addAll(methods, B.class.getDeclaredMethods()); + Collections.addAll(methods, C.class.getDeclaredMethods()); + Collections.addAll(methods, E.class.getDeclaredMethods()); + Collections.addAll(methods, F.class.getDeclaredMethods()); + Collections.addAll(methods, G.class.getDeclaredMethods()); + Collections.addAll(methods, H.class.getDeclaredMethods()); + Collections.addAll(methods, J.class.getDeclaredMethods()); + Collections.addAll(methods, K.class.getDeclaredMethods()); + Collections.addAll(methods, L.class.getDeclaredMethods()); + // Don't do this because it would load MyValue5 + // Collections.addAll(methods, N.class.getDeclaredMethods()); + System.out.println("Excluding methods from C2 compilation:"); + for (Method method : methods) { + if (Utils.getRandomInstance().nextBoolean()) { + System.out.println(method); + WHITE_BOX.makeMethodNotCompilable(method, 4, false); + } + } + } + + A a = new A(); + B b = new B(); + C c = new C(); + D d = new D(); + E e = new E(); + H h = new H(); + J j = new J(); + K k = new K(); + N n = new N(); + O o = new O(); + P p = new P(); + Q q = new Q(); + R r = new R(); + + // Warmup + for (int i = 0; i < 50_000; ++i) { + TestMismatchHandlingHelper.test1(a, a, a, b, c, b, b, c); + TestMismatchHandlingHelper.test1(b, a, a, b, c, b, b, c); + TestMismatchHandlingHelper.test1(c, b, a, b, c, c, b, c); + TestMismatchHandlingHelper.test2(d, d, d, d, d, d, d, d, d, d, d, d, e, e, e, e, e, e, e, e, e, e, e, e, d, e); + TestMismatchHandlingHelper.test2(d, d, d, d, d, d, d, d, d, d, d, d, e, e, e, e, e, e, e, e, e, e, e, e, d, e); + TestMismatchHandlingHelper.test3(h, h, h, j, k, j, k, j, h, k); + TestMismatchHandlingHelper.test3(h, h, h, j, k, j, k, k, h, k); + TestMismatchHandlingHelper.test4(m, true); + TestMismatchHandlingHelper.test5(n, true); + TestMismatchHandlingHelper.test7(o, true); + TestMismatchHandlingHelper.test8(p, p, p, q, r, q, r, q, p, r); + TestMismatchHandlingHelper.test8(p, p, p, q, r, q, r, r, p, r); + } + + // Only load these now + F f = new F(); + G g = new G(); + L l = new L(); + S s = new S(); + + for (int i = 0; i < 50_000; ++i) { + TestMismatchHandlingHelper.test1(a, a, a, b, c, b, b, c); + TestMismatchHandlingHelper.test1(b, a, a, b, c, b, b, c); + TestMismatchHandlingHelper.test1(c, b, a, b, c, c, b, c); + TestMismatchHandlingHelper.test2(d, f, g, d, f, d, d, f, g, d, f, d, e, f, g, e, f, g, e, f, g, e, f, g, d, e); + TestMismatchHandlingHelper.test2(d, f, g, d, f, f, d, f, g, d, f, f, e, f, g, e, f, f, e, f, g, e, f, f, d, e); + TestMismatchHandlingHelper.test2(d, f, g, f, g, g, d, f, g, f, g, g, e, f, g, f, g, g, e, f, g, f, g, g, d, e); + TestMismatchHandlingHelper.test3(h, l, h, j, k, j, k, j, h, k); + TestMismatchHandlingHelper.test3(h, l, h, j, k, k, k, k, h, k); + TestMismatchHandlingHelper.test3(h, l, l, j, k, k, l, l, h, l); + TestMismatchHandlingHelper.test4(m, false); + TestMismatchHandlingHelper.test5(n, false); + TestMismatchHandlingHelper.test6(f, g, l); + TestMismatchHandlingHelper.test7TriggerCalleeCompilation(o); + TestMismatchHandlingHelper.test8(p, s, p, q, r, q, r, q, p, r); + TestMismatchHandlingHelper.test8(p, s, p, q, r, r, r, r, p, r); + TestMismatchHandlingHelper.test8(p, s, s, q, r, r, s, s, p, s); + } + TestMismatchHandlingHelper.test7(o, false).verify(); + + switch (Utils.getRandomInstance().nextInt() % 3) { + case 0: + TestMismatchHandlingHelper.test2(d, d, d, d, d, d, d, d, d, d, d, d, e, e, e, e, e, e, e, e, e, e, e, e, d, e); + TestMismatchHandlingHelper.test3(l, h, l, k, l, l, j, j, h, l); + TestMismatchHandlingHelper.test8(s, p, s, r, s, s, q, q, p, s); + break; + case 1: + TestMismatchHandlingHelper.test2(f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, f, d, e); + TestMismatchHandlingHelper.test3(l, h, l, l, j, j, k, l, h, l); + TestMismatchHandlingHelper.test8(s, p, s, s, q, q, r, s, p, s); + break; + case 2: + TestMismatchHandlingHelper.test2(g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, d, e); + TestMismatchHandlingHelper.test3(l, h, l, j, k, k, l, j, h, l); + TestMismatchHandlingHelper.test8(s, p, s, q, r, r, s, q, p, s); + break; + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestMismatchHandling.jcod b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestMismatchHandling.jcod new file mode 100644 index 00000000000..0d03823875c --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestMismatchHandling.jcod @@ -0,0 +1,3743 @@ +/* + * 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. + */ + +// ##################################### WARNING ######################################## +// Generated file, don't modify manually. See TestMismatchHandling.java for instructions. +// ##################################### WARNING ######################################## + +class MyValue1 { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Field #8 #9; // #7 + class #10; // #8 + NameAndType #11 #12; // #9 + Utf8 "MyValue1"; // #10 + Utf8 "x"; // #11 + Utf8 "I"; // #12 + class #14; // #13 + Utf8 "java/lang/RuntimeException"; // #14 + String #16; // #15 + Utf8 "Verification failed"; // #16 + Method #13 #18; // #17 + NameAndType #5 #19; // #18 + Utf8 "(Ljava/lang/String;)V"; // #19 + Utf8 "Code"; // #20 + Utf8 "LineNumberTable"; // #21 + Utf8 "verify"; // #22 + Utf8 "StackMapTable"; // #23 + Utf8 "SourceFile"; // #24 + Utf8 "TestMismatchHandlingGenerator.java"; // #25 + } // Constant Pool + + 0x0020; // access + #8;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + { // field + 0x0000; // access + #11; // name_index + #12; // descriptor_index + [] { // Attributes + } // Attributes + } + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#20) { // Code + 2; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB700012A102AB5; + 0x0007B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#21) { // LineNumberTable + [] { // line_number_table + 0 24; + 4 25; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0000; // access + #22; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#20) { // Code + 3; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB40007102A9F00; + 0x0DBB000D59120FB7; + 0x0011BFB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#21) { // LineNumberTable + [] { // line_number_table + 0 28; + 9 29; + 19 31; + } + } // end LineNumberTable + ; + Attr(#23) { // StackMapTable + [] { // + 19b; // same_frame + } + } // end StackMapTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#24) { // SourceFile + #25; + } // end SourceFile + } // Attributes +} // end class MyValue1 +class MyValue2 { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Field #8 #9; // #7 + class #10; // #8 + NameAndType #11 #12; // #9 + Utf8 "MyValue2"; // #10 + Utf8 "x"; // #11 + Utf8 "I"; // #12 + class #14; // #13 + Utf8 "java/lang/RuntimeException"; // #14 + String #16; // #15 + Utf8 "Verification failed"; // #16 + Method #13 #18; // #17 + NameAndType #5 #19; // #18 + Utf8 "(Ljava/lang/String;)V"; // #19 + Utf8 "Code"; // #20 + Utf8 "LineNumberTable"; // #21 + Utf8 "verify"; // #22 + Utf8 "StackMapTable"; // #23 + Utf8 "SourceFile"; // #24 + Utf8 "TestMismatchHandlingGenerator.java"; // #25 + } // Constant Pool + + 0x0020; // access + #8;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + { // field + 0x0000; // access + #11; // name_index + #12; // descriptor_index + [] { // Attributes + } // Attributes + } + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#20) { // Code + 2; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB700012A102AB5; + 0x0007B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#21) { // LineNumberTable + [] { // line_number_table + 0 34; + 4 35; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0000; // access + #22; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#20) { // Code + 3; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB40007102A9F00; + 0x0DBB000D59120FB7; + 0x0011BFB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#21) { // LineNumberTable + [] { // line_number_table + 0 38; + 9 39; + 19 41; + } + } // end LineNumberTable + ; + Attr(#23) { // StackMapTable + [] { // + 19b; // same_frame + } + } // end StackMapTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#24) { // SourceFile + #25; + } // end SourceFile + } // Attributes +} // end class MyValue2 +class MyValue3 { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Field #8 #9; // #7 + class #10; // #8 + NameAndType #11 #12; // #9 + Utf8 "MyValue3"; // #10 + Utf8 "x"; // #11 + Utf8 "I"; // #12 + class #14; // #13 + Utf8 "java/lang/RuntimeException"; // #14 + String #16; // #15 + Utf8 "Verification failed"; // #16 + Method #13 #18; // #17 + NameAndType #5 #19; // #18 + Utf8 "(Ljava/lang/String;)V"; // #19 + Utf8 "Code"; // #20 + Utf8 "LineNumberTable"; // #21 + Utf8 "verify"; // #22 + Utf8 "StackMapTable"; // #23 + Utf8 "SourceFile"; // #24 + Utf8 "TestMismatchHandlingGenerator.java"; // #25 + } // Constant Pool + + 0x0020; // access + #8;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + { // field + 0x0000; // access + #11; // name_index + #12; // descriptor_index + [] { // Attributes + } // Attributes + } + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#20) { // Code + 2; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB700012A102AB5; + 0x0007B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#21) { // LineNumberTable + [] { // line_number_table + 0 44; + 4 45; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0000; // access + #22; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#20) { // Code + 3; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB40007102A9F00; + 0x0DBB000D59120FB7; + 0x0011BFB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#21) { // LineNumberTable + [] { // line_number_table + 0 48; + 9 49; + 19 51; + } + } // end LineNumberTable + ; + Attr(#23) { // StackMapTable + [] { // + 19b; // same_frame + } + } // end StackMapTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#24) { // SourceFile + #25; + } // end SourceFile + } // Attributes +} // end class MyValue3 +class MyValue4 { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Field #8 #9; // #7 + class #10; // #8 + NameAndType #11 #12; // #9 + Utf8 "MyValue4"; // #10 + Utf8 "x"; // #11 + Utf8 "I"; // #12 + class #14; // #13 + Utf8 "java/lang/RuntimeException"; // #14 + String #16; // #15 + Utf8 "Verification failed"; // #16 + Method #13 #18; // #17 + NameAndType #5 #19; // #18 + Utf8 "(Ljava/lang/String;)V"; // #19 + Method #8 #3; // #20 + Utf8 "Code"; // #21 + Utf8 "LineNumberTable"; // #22 + Utf8 "verify"; // #23 + Utf8 "StackMapTable"; // #24 + Utf8 "make"; // #25 + Utf8 "()LMyValue4;"; // #26 + Utf8 "SourceFile"; // #27 + Utf8 "TestMismatchHandlingGenerator.java"; // #28 + } // Constant Pool + + 0x0020; // access + #8;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + { // field + 0x0000; // access + #11; // name_index + #12; // descriptor_index + [] { // Attributes + } // Attributes + } + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#21) { // Code + 2; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB700012A102AB5; + 0x0007B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#22) { // LineNumberTable + [] { // line_number_table + 0 54; + 4 55; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0001; // access + #23; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#21) { // Code + 3; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB40007102A9F00; + 0x0DBB000D59120FB7; + 0x0011BFB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#22) { // LineNumberTable + [] { // line_number_table + 0 58; + 9 59; + 19 61; + } + } // end LineNumberTable + ; + Attr(#24) { // StackMapTable + [] { // + 19b; // same_frame + } + } // end StackMapTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0008; // access + #25; // name_index + #26; // descriptor_index + [] { // Attributes + Attr(#21) { // Code + 2; // max_stack + 0; // max_locals + Bytes[]{ + 0xBB000859B70014B0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#22) { // LineNumberTable + [] { // line_number_table + 0 64; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#27) { // SourceFile + #28; + } // end SourceFile + } // Attributes +} // end class MyValue4 +class MyValue5 { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Field #8 #9; // #7 + class #10; // #8 + NameAndType #11 #12; // #9 + Utf8 "MyValue5"; // #10 + Utf8 "x"; // #11 + Utf8 "I"; // #12 + class #14; // #13 + Utf8 "java/lang/RuntimeException"; // #14 + String #16; // #15 + Utf8 "Verification failed"; // #16 + Method #13 #18; // #17 + NameAndType #5 #19; // #18 + Utf8 "(Ljava/lang/String;)V"; // #19 + Method #8 #3; // #20 + class #22; // #21 + Utf8 "Verifiable"; // #22 + Utf8 "Code"; // #23 + Utf8 "LineNumberTable"; // #24 + Utf8 "verify"; // #25 + Utf8 "StackMapTable"; // #26 + Utf8 "make"; // #27 + Utf8 "()LMyValue5;"; // #28 + Utf8 "SourceFile"; // #29 + Utf8 "TestMismatchHandlingGenerator.java"; // #30 + } // Constant Pool + + 0x0020; // access + #8;// this_cpx + #2;// super_cpx + + [] { // Interfaces + #21; + } // Interfaces + + [] { // Fields + { // field + 0x0000; // access + #11; // name_index + #12; // descriptor_index + [] { // Attributes + } // Attributes + } + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#23) { // Code + 2; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB700012A102AB5; + 0x0007B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#24) { // LineNumberTable + [] { // line_number_table + 0 72; + 4 73; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0001; // access + #25; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#23) { // Code + 3; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB40007102A9F00; + 0x0DBB000D59120FB7; + 0x0011BFB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#24) { // LineNumberTable + [] { // line_number_table + 0 77; + 9 78; + 19 80; + } + } // end LineNumberTable + ; + Attr(#26) { // StackMapTable + [] { // + 19b; // same_frame + } + } // end StackMapTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0008; // access + #27; // name_index + #28; // descriptor_index + [] { // Attributes + Attr(#23) { // Code + 2; // max_stack + 0; // max_locals + Bytes[]{ + 0xBB000859B70014B0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#24) { // LineNumberTable + [] { // line_number_table + 0 83; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#29) { // SourceFile + #30; + } // end SourceFile + } // Attributes +} // end class MyValue5 +class MyValue6 { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Field #8 #9; // #7 + class #10; // #8 + NameAndType #11 #12; // #9 + Utf8 "MyValue6"; // #10 + Utf8 "x"; // #11 + Utf8 "I"; // #12 + class #14; // #13 + Utf8 "java/lang/RuntimeException"; // #14 + String #16; // #15 + Utf8 "Verification failed"; // #16 + Method #13 #18; // #17 + NameAndType #5 #19; // #18 + Utf8 "(Ljava/lang/String;)V"; // #19 + Method #8 #3; // #20 + class #22; // #21 + Utf8 "Verifiable"; // #22 + Utf8 "Code"; // #23 + Utf8 "LineNumberTable"; // #24 + Utf8 "verify"; // #25 + Utf8 "StackMapTable"; // #26 + Utf8 "make"; // #27 + Utf8 "()LMyValue6;"; // #28 + Utf8 "SourceFile"; // #29 + Utf8 "TestMismatchHandlingGenerator.java"; // #30 + } // Constant Pool + + 0x0020; // access + #8;// this_cpx + #2;// super_cpx + + [] { // Interfaces + #21; + } // Interfaces + + [] { // Fields + { // field + 0x0000; // access + #11; // name_index + #12; // descriptor_index + [] { // Attributes + } // Attributes + } + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#23) { // Code + 2; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB700012A102AB5; + 0x0007B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#24) { // LineNumberTable + [] { // line_number_table + 0 87; + 4 88; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0001; // access + #25; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#23) { // Code + 3; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB40007102A9F00; + 0x0DBB000D59120FB7; + 0x0011BFB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#24) { // LineNumberTable + [] { // line_number_table + 0 92; + 9 93; + 19 95; + } + } // end LineNumberTable + ; + Attr(#26) { // StackMapTable + [] { // + 19b; // same_frame + } + } // end StackMapTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0008; // access + #27; // name_index + #28; // descriptor_index + [] { // Attributes + Attr(#23) { // Code + 2; // max_stack + 0; // max_locals + Bytes[]{ + 0xBB000859B70014B0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#24) { // LineNumberTable + [] { // line_number_table + 0 98; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#29) { // SourceFile + #30; + } // end SourceFile + } // Attributes +} // end class MyValue6 +class MyValue7 { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Field #8 #9; // #7 + class #10; // #8 + NameAndType #11 #12; // #9 + Utf8 "MyValue7"; // #10 + Utf8 "x"; // #11 + Utf8 "I"; // #12 + class #14; // #13 + Utf8 "java/lang/RuntimeException"; // #14 + String #16; // #15 + Utf8 "Verification failed"; // #16 + Method #13 #18; // #17 + NameAndType #5 #19; // #18 + Utf8 "(Ljava/lang/String;)V"; // #19 + Utf8 "Code"; // #20 + Utf8 "LineNumberTable"; // #21 + Utf8 "verify"; // #22 + Utf8 "StackMapTable"; // #23 + Utf8 "SourceFile"; // #24 + Utf8 "TestMismatchHandlingGenerator.java"; // #25 + } // Constant Pool + + 0x0020; // access + #8;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + { // field + 0x0000; // access + #11; // name_index + #12; // descriptor_index + [] { // Attributes + } // Attributes + } + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#20) { // Code + 2; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB700012A102AB5; + 0x0007B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#21) { // LineNumberTable + [] { // line_number_table + 0 102; + 4 103; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0000; // access + #22; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#20) { // Code + 3; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB40007102A9F00; + 0x0DBB000D59120FB7; + 0x0011BFB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#21) { // LineNumberTable + [] { // line_number_table + 0 106; + 9 107; + 19 109; + } + } // end LineNumberTable + ; + Attr(#23) { // StackMapTable + [] { // + 19b; // same_frame + } + } // end StackMapTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#24) { // SourceFile + #25; + } // end SourceFile + } // Attributes +} // end class MyValue7 +class Verifiable { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + class #2; // #1 + Utf8 "Verifiable"; // #2 + class #4; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 "verify"; // #5 + Utf8 "()V"; // #6 + Utf8 "SourceFile"; // #7 + Utf8 "TestMismatchHandlingGenerator.java"; // #8 + } // Constant Pool + + 0x0600; // access + #1;// this_cpx + #3;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0401; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#7) { // SourceFile + #8; + } // end SourceFile + } // Attributes +} // end class Verifiable +file "B.class" { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "A"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #6; // #9 + Utf8 "MyValue1"; // #10 + Utf8 "verify"; // #11 + class #13; // #12 + Utf8 "B"; // #13 + Utf8 "Code"; // #14 + Utf8 "LineNumberTable"; // #15 + Utf8 "method"; // #16 + Utf8 "(LMyValue1;)LMyValue1;"; // #17 + Utf8 "SourceFile"; // #18 + Utf8 "TestMismatchHandlingGenerator.java"; // #19 + } // Constant Pool + + 0x0020; // access + #12;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#14) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#15) { // LineNumberTable + [] { // line_number_table + 0 119; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0001; // access + #16; // name_index + #17; // descriptor_index + [] { // Attributes + Attr(#14) { // Code + 1; // max_stack + 2; // max_locals + Bytes[]{ + 0x2BB600072BB0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#15) { // LineNumberTable + [] { // line_number_table + 0 122; + 4 123; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#18) { // SourceFile + #19; + } // end SourceFile + } // Attributes +} // end class B +class I3 { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + class #2; // #1 + Utf8 "I3"; // #2 + class #4; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 "method"; // #5 + Utf8 "(LMyValue2;)LMyValue2;"; // #6 + Utf8 "SourceFile"; // #7 + Utf8 "TestMismatchHandlingGenerator.java"; // #8 + } // Constant Pool + + 0x0600; // access + #1;// this_cpx + #3;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0401; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#7) { // SourceFile + #8; + } // end SourceFile + } // Attributes +} // end class I3 +class I4 { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + class #2; // #1 + Utf8 "I4"; // #2 + class #4; // #3 + Utf8 "java/lang/Object"; // #4 + class #6; // #5 + Utf8 "I3"; // #6 + Utf8 "method"; // #7 + Utf8 "(LMyValue2;)LMyValue2;"; // #8 + Utf8 "SourceFile"; // #9 + Utf8 "TestMismatchHandlingGenerator.java"; // #10 + } // Constant Pool + + 0x0600; // access + #1;// this_cpx + #3;// super_cpx + + [] { // Interfaces + #5; + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0401; // access + #7; // name_index + #8; // descriptor_index + [] { // Attributes + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#9) { // SourceFile + #10; + } // end SourceFile + } // Attributes +} // end class I4 +class E { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #6; // #9 + Utf8 "MyValue2"; // #10 + Utf8 "verify"; // #11 + class #13; // #12 + Utf8 "E"; // #13 + class #15; // #14 + Utf8 "I4"; // #15 + Utf8 "Code"; // #16 + Utf8 "LineNumberTable"; // #17 + Utf8 "method"; // #18 + Utf8 "(LMyValue2;)LMyValue2;"; // #19 + Utf8 "SourceFile"; // #20 + Utf8 "TestMismatchHandlingGenerator.java"; // #21 + } // Constant Pool + + 0x0020; // access + #12;// this_cpx + #2;// super_cpx + + [] { // Interfaces + #14; + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#16) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#17) { // LineNumberTable + [] { // line_number_table + 0 160; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0001; // access + #18; // name_index + #19; // descriptor_index + [] { // Attributes + Attr(#16) { // Code + 1; // max_stack + 2; // max_locals + Bytes[]{ + 0x2BB600072BB0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#17) { // LineNumberTable + [] { // line_number_table + 0 163; + 4 164; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#20) { // SourceFile + #21; + } // end SourceFile + } // Attributes +} // end class E +class G { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #6; // #9 + Utf8 "MyValue2"; // #10 + Utf8 "verify"; // #11 + class #13; // #12 + Utf8 "G"; // #13 + class #15; // #14 + Utf8 "I2"; // #15 + class #17; // #16 + Utf8 "I4"; // #17 + Utf8 "Code"; // #18 + Utf8 "LineNumberTable"; // #19 + Utf8 "method"; // #20 + Utf8 "(LMyValue2;)LMyValue2;"; // #21 + Utf8 "SourceFile"; // #22 + Utf8 "TestMismatchHandlingGenerator.java"; // #23 + } // Constant Pool + + 0x0020; // access + #12;// this_cpx + #2;// super_cpx + + [] { // Interfaces + #14; + #16; + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#18) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#19) { // LineNumberTable + [] { // line_number_table + 0 177; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0001; // access + #20; // name_index + #21; // descriptor_index + [] { // Attributes + Attr(#18) { // Code + 1; // max_stack + 2; // max_locals + Bytes[]{ + 0x2BB600072BB0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#19) { // LineNumberTable + [] { // line_number_table + 0 180; + 4 181; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#22) { // SourceFile + #23; + } // end SourceFile + } // Attributes +} // end class G +class J { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #6; // #9 + Utf8 "MyValue3"; // #10 + Utf8 "verify"; // #11 + class #13; // #12 + Utf8 "J"; // #13 + Utf8 "Code"; // #14 + Utf8 "LineNumberTable"; // #15 + Utf8 "method"; // #16 + Utf8 "(LMyValue3;)LMyValue3;"; // #17 + Utf8 "SourceFile"; // #18 + Utf8 "TestMismatchHandlingGenerator.java"; // #19 + } // Constant Pool + + 0x0020; // access + #12;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#14) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#15) { // LineNumberTable + [] { // line_number_table + 0 197; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0001; // access + #16; // name_index + #17; // descriptor_index + [] { // Attributes + Attr(#14) { // Code + 1; // max_stack + 2; // max_locals + Bytes[]{ + 0x2BB600072BB0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#15) { // LineNumberTable + [] { // line_number_table + 0 199; + 4 200; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#18) { // SourceFile + #19; + } // end SourceFile + } // Attributes +} // end class J +class K { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "J"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #6; // #9 + Utf8 "MyValue3"; // #10 + Utf8 "verify"; // #11 + class #13; // #12 + Utf8 "K"; // #13 + Utf8 "Code"; // #14 + Utf8 "LineNumberTable"; // #15 + Utf8 "method"; // #16 + Utf8 "(LMyValue3;)LMyValue3;"; // #17 + Utf8 "SourceFile"; // #18 + Utf8 "TestMismatchHandlingGenerator.java"; // #19 + } // Constant Pool + + 0x0020; // access + #12;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#14) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#15) { // LineNumberTable + [] { // line_number_table + 0 204; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0001; // access + #16; // name_index + #17; // descriptor_index + [] { // Attributes + Attr(#14) { // Code + 1; // max_stack + 2; // max_locals + Bytes[]{ + 0x2BB600072BB0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#15) { // LineNumberTable + [] { // line_number_table + 0 207; + 4 208; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#18) { // SourceFile + #19; + } // end SourceFile + } // Attributes +} // end class K +file "L.class" { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "K"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #6; // #9 + Utf8 "MyValue3"; // #10 + Utf8 "verify"; // #11 + class #13; // #12 + Utf8 "L"; // #13 + class #15; // #14 + Utf8 "I5"; // #15 + Utf8 "Code"; // #16 + Utf8 "LineNumberTable"; // #17 + Utf8 "method"; // #18 + Utf8 "(LMyValue3;)LMyValue3;"; // #19 + Utf8 "SourceFile"; // #20 + Utf8 "TestMismatchHandlingGenerator.java"; // #21 + } // Constant Pool + + 0x0020; // access + #12;// this_cpx + #2;// super_cpx + + [] { // Interfaces + #14; + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#16) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#17) { // LineNumberTable + [] { // line_number_table + 0 212; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0001; // access + #18; // name_index + #19; // descriptor_index + [] { // Attributes + Attr(#16) { // Code + 1; // max_stack + 2; // max_locals + Bytes[]{ + 0x2BB600072BB0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#17) { // LineNumberTable + [] { // line_number_table + 0 215; + 4 216; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#20) { // SourceFile + #21; + } // end SourceFile + } // Attributes +} // end class L +class P { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #6; // #9 + Utf8 "MyValue7"; // #10 + Utf8 "verify"; // #11 + class #13; // #12 + Utf8 "P"; // #13 + class #15; // #14 + Utf8 "I6"; // #15 + Utf8 "Code"; // #16 + Utf8 "LineNumberTable"; // #17 + Utf8 "method"; // #18 + Utf8 "(LMyValue7;)LMyValue7;"; // #19 + Utf8 "SourceFile"; // #20 + Utf8 "TestMismatchHandlingGenerator.java"; // #21 + } // Constant Pool + + 0x0020; // access + #12;// this_cpx + #2;// super_cpx + + [] { // Interfaces + #14; + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#16) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#17) { // LineNumberTable + [] { // line_number_table + 0 263; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0001; // access + #18; // name_index + #19; // descriptor_index + [] { // Attributes + Attr(#16) { // Code + 1; // max_stack + 2; // max_locals + Bytes[]{ + 0x2BB600072BB0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#17) { // LineNumberTable + [] { // line_number_table + 0 266; + 4 267; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#20) { // SourceFile + #21; + } // end SourceFile + } // Attributes +} // end class P +class Q { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #6; // #9 + Utf8 "MyValue7"; // #10 + Utf8 "verify"; // #11 + class #13; // #12 + Utf8 "Q"; // #13 + Utf8 "Code"; // #14 + Utf8 "LineNumberTable"; // #15 + Utf8 "method"; // #16 + Utf8 "(LMyValue7;)LMyValue7;"; // #17 + Utf8 "SourceFile"; // #18 + Utf8 "TestMismatchHandlingGenerator.java"; // #19 + } // Constant Pool + + 0x0020; // access + #12;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#14) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#15) { // LineNumberTable + [] { // line_number_table + 0 271; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0000; // access + #16; // name_index + #17; // descriptor_index + [] { // Attributes + Attr(#14) { // Code + 1; // max_stack + 2; // max_locals + Bytes[]{ + 0x2BB600072BB0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#15) { // LineNumberTable + [] { // line_number_table + 0 273; + 4 274; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#18) { // SourceFile + #19; + } // end SourceFile + } // Attributes +} // end class Q +class R { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "Q"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #6; // #9 + Utf8 "MyValue7"; // #10 + Utf8 "verify"; // #11 + class #13; // #12 + Utf8 "R"; // #13 + Utf8 "Code"; // #14 + Utf8 "LineNumberTable"; // #15 + Utf8 "method"; // #16 + Utf8 "(LMyValue7;)LMyValue7;"; // #17 + Utf8 "SourceFile"; // #18 + Utf8 "TestMismatchHandlingGenerator.java"; // #19 + } // Constant Pool + + 0x0020; // access + #12;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#14) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#15) { // LineNumberTable + [] { // line_number_table + 0 278; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0000; // access + #16; // name_index + #17; // descriptor_index + [] { // Attributes + Attr(#14) { // Code + 1; // max_stack + 2; // max_locals + Bytes[]{ + 0x2BB600072BB0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#15) { // LineNumberTable + [] { // line_number_table + 0 281; + 4 282; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#18) { // SourceFile + #19; + } // end SourceFile + } // Attributes +} // end class R +class S { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "R"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #6; // #9 + Utf8 "MyValue7"; // #10 + Utf8 "verify"; // #11 + class #13; // #12 + Utf8 "S"; // #13 + class #15; // #14 + Utf8 "I6"; // #15 + Utf8 "Code"; // #16 + Utf8 "LineNumberTable"; // #17 + Utf8 "method"; // #18 + Utf8 "(LMyValue7;)LMyValue7;"; // #19 + Utf8 "SourceFile"; // #20 + Utf8 "TestMismatchHandlingGenerator.java"; // #21 + } // Constant Pool + + 0x0020; // access + #12;// this_cpx + #2;// super_cpx + + [] { // Interfaces + #14; + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#16) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#17) { // LineNumberTable + [] { // line_number_table + 0 286; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0001; // access + #18; // name_index + #19; // descriptor_index + [] { // Attributes + Attr(#16) { // Code + 1; // max_stack + 2; // max_locals + Bytes[]{ + 0x2BB600072BB0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#17) { // LineNumberTable + [] { // line_number_table + 0 289; + 4 290; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#20) { // SourceFile + #21; + } // end SourceFile + } // Attributes +} // end class S +class TestMismatchHandlingHelper { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + class #8; // #7 + Utf8 "MyValue1"; // #8 + Method #7 #3; // #9 + Method #11 #12; // #10 + class #13; // #11 + NameAndType #14 #15; // #12 + Utf8 "A"; // #13 + Utf8 "method"; // #14 + Utf8 "(LMyValue1;)LMyValue1;"; // #15 + Method #7 #17; // #16 + NameAndType #18 #6; // #17 + Utf8 "verify"; // #18 + Method #20 #12; // #19 + class #21; // #20 + Utf8 "B"; // #21 + Method #23 #12; // #22 + class #24; // #23 + Utf8 "C"; // #24 + class #26; // #25 + Utf8 "MyValue2"; // #26 + Method #25 #3; // #27 + InterfaceMethod #29 #30; // #28 + class #31; // #29 + NameAndType #14 #32; // #30 + Utf8 "I1"; // #31 + Utf8 "(LMyValue2;)LMyValue2;"; // #32 + Method #25 #17; // #33 + InterfaceMethod #35 #30; // #34 + class #36; // #35 + Utf8 "I2"; // #36 + Method #38 #30; // #37 + class #39; // #38 + Utf8 "D"; // #39 + InterfaceMethod #41 #30; // #40 + class #42; // #41 + Utf8 "I3"; // #42 + InterfaceMethod #44 #30; // #43 + class #45; // #44 + Utf8 "I4"; // #45 + Method #47 #30; // #46 + class #48; // #47 + Utf8 "E"; // #48 + class #50; // #49 + Utf8 "MyValue3"; // #50 + Method #49 #3; // #51 + InterfaceMethod #53 #54; // #52 + class #55; // #53 + NameAndType #14 #56; // #54 + Utf8 "I5"; // #55 + Utf8 "(LMyValue3;)LMyValue3;"; // #56 + Method #49 #17; // #57 + Method #59 #54; // #58 + class #60; // #59 + Utf8 "H"; // #60 + Method #62 #54; // #61 + class #63; // #62 + Utf8 "J"; // #63 + Method #65 #54; // #64 + class #66; // #65 + Utf8 "K"; // #66 + Method #68 #69; // #67 + class #70; // #68 + NameAndType #14 #71; // #69 + Utf8 "M"; // #70 + Utf8 "(Z)LMyValue4;"; // #71 + Field #73 #74; // #72 + class #75; // #73 + NameAndType #76 #77; // #74 + Utf8 "MyValue4"; // #75 + Utf8 "x"; // #76 + Utf8 "I"; // #77 + class #79; // #78 + Utf8 "java/lang/RuntimeException"; // #79 + String #81; // #80 + Utf8 "Verification failed"; // #81 + Method #78 #83; // #82 + NameAndType #5 #84; // #83 + Utf8 "(Ljava/lang/String;)V"; // #84 + Method #86 #87; // #85 + class #88; // #86 + NameAndType #14 #89; // #87 + Utf8 "N"; // #88 + Utf8 "(Z)LMyValue5;"; // #89 + InterfaceMethod #91 #17; // #90 + class #92; // #91 + Utf8 "Verifiable"; // #92 + Method #94 #30; // #93 + class #95; // #94 + Utf8 "F"; // #95 + Method #97 #30; // #96 + class #98; // #97 + Utf8 "G"; // #98 + Method #100 #54; // #99 + class #101; // #100 + Utf8 "L"; // #101 + Method #103 #104; // #102 + class #105; // #103 + NameAndType #14 #106; // #104 + Utf8 "O"; // #105 + Utf8 "(Z)LMyValue6;"; // #106 + Method #108 #17; // #107 + class #109; // #108 + Utf8 "MyValue6"; // #109 + class #111; // #110 + Utf8 "MyValue7"; // #111 + Method #110 #3; // #112 + InterfaceMethod #114 #115; // #113 + class #116; // #114 + NameAndType #14 #117; // #115 + Utf8 "I6"; // #116 + Utf8 "(LMyValue7;)LMyValue7;"; // #117 + Method #110 #17; // #118 + Method #120 #115; // #119 + class #121; // #120 + Utf8 "P"; // #121 + Method #123 #115; // #122 + class #124; // #123 + Utf8 "Q"; // #124 + Method #126 #115; // #125 + class #127; // #126 + Utf8 "R"; // #127 + class #129; // #128 + Utf8 "TestMismatchHandlingHelper"; // #129 + Utf8 "Code"; // #130 + Utf8 "LineNumberTable"; // #131 + Utf8 "test1"; // #132 + Utf8 "(LA;LA;LA;LA;LA;LB;LB;LC;)V"; // #133 + Utf8 "test2"; // #134 + Utf8 "(LI1;LI1;LI1;LI1;LI1;LI1;LI2;LI2;LI2;LI2;LI2;LI2;LI3;LI3;LI3;LI3;LI3;LI3;LI4;LI4;LI4;LI4;LI4;LI4;LD;LE;)V"; // #135 + Utf8 "test3"; // #136 + Utf8 "(LI5;LI5;LI5;LJ;LJ;LJ;LJ;LJ;LH;LK;)V"; // #137 + Utf8 "test4"; // #138 + Utf8 "(LM;Z)V"; // #139 + Utf8 "StackMapTable"; // #140 + Utf8 "test5"; // #141 + Utf8 "(LN;Z)V"; // #142 + Utf8 "test6"; // #143 + Utf8 "(LF;LG;LL;)V"; // #144 + Utf8 "test7"; // #145 + Utf8 "(LO;Z)LVerifiable;"; // #146 + Utf8 "test7TriggerCalleeCompilation"; // #147 + Utf8 "(LO;)V"; // #148 + Utf8 "test8"; // #149 + Utf8 "(LI6;LI6;LI6;LQ;LQ;LQ;LQ;LQ;LP;LR;)V"; // #150 + Utf8 "SourceFile"; // #151 + Utf8 "TestMismatchHandlingGenerator.java"; // #152 + } // Constant Pool + + 0x0020; // access + #128;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#130) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#131) { // LineNumberTable + [] { // line_number_table + 0 294; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0009; // access + #132; // name_index + #133; // descriptor_index + [] { // Attributes + Attr(#130) { // Code + 3; // max_stack + 8; // max_locals + Bytes[]{ + 0x2ABB000759B70009; + 0xB6000AB600102BBB; + 0x000759B70009B600; + 0x0AB600102CBB0007; + 0x59B70009B6000AB6; + 0x00102DBB000759B7; + 0x0009B6000AB60010; + 0x1904BB000759B700; + 0x09B6000AB6001019; + 0x05BB000759B70009; + 0xB60013B600101906; + 0xBB000759B70009B6; + 0x0013B600101907BB; + 0x000759B70009B600; + 0x16B60010B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#131) { // LineNumberTable + [] { // line_number_table + 0 300; + 14 301; + 28 302; + 42 303; + 56 304; + 71 306; + 86 307; + 101 308; + 116 309; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0009; // access + #134; // name_index + #135; // descriptor_index + [] { // Attributes + Attr(#130) { // Code + 3; // max_stack + 26; // max_locals + Bytes[]{ + 0x2ABB001959B7001B; + 0xB9001C0200B60021; + 0x2BBB001959B7001B; + 0xB9001C0200B60021; + 0x2CBB001959B7001B; + 0xB9001C0200B60021; + 0x2DBB001959B7001B; + 0xB9001C0200B60021; + 0x1904BB001959B700; + 0x1BB9001C0200B600; + 0x211905BB001959B7; + 0x001BB9001C0200B6; + 0x00211906BB001959; + 0xB7001BB900220200; + 0xB600211907BB0019; + 0x59B7001BB9002202; + 0x00B600211908BB00; + 0x1959B7001BB90022; + 0x0200B600211909BB; + 0x001959B7001BB900; + 0x220200B60021190A; + 0xBB001959B7001BB9; + 0x00220200B6002119; + 0x0BBB001959B7001B; + 0xB900220200B60021; + 0x1918BB001959B700; + 0x1BB60025B6002119; + 0x0CBB001959B7001B; + 0xB900280200B60021; + 0x190DBB001959B700; + 0x1BB900280200B600; + 0x21190EBB001959B7; + 0x001BB900280200B6; + 0x0021190FBB001959; + 0xB7001BB900280200; + 0xB600211910BB0019; + 0x59B7001BB9002802; + 0x00B600211911BB00; + 0x1959B7001BB90028; + 0x0200B600211912BB; + 0x001959B7001BB900; + 0x2B0200B600211913; + 0xBB001959B7001BB9; + 0x002B0200B6002119; + 0x14BB001959B7001B; + 0xB9002B0200B60021; + 0x1915BB001959B700; + 0x1BB9002B0200B600; + 0x211916BB001959B7; + 0x001BB9002B0200B6; + 0x00211917BB001959; + 0xB7001BB9002B0200; + 0xB600211919BB0019; + 0x59B7001BB6002EB6; + 0x0021B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#131) { // LineNumberTable + [] { // line_number_table + 0 318; + 16 319; + 32 320; + 48 321; + 64 322; + 81 323; + 98 324; + 115 325; + 132 326; + 149 327; + 166 328; + 183 329; + 200 330; + 215 332; + 232 333; + 249 334; + 266 335; + 283 336; + 300 337; + 317 338; + 334 339; + 351 340; + 368 341; + 385 342; + 402 343; + 419 344; + 434 345; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0009; // access + #136; // name_index + #137; // descriptor_index + [] { // Attributes + Attr(#130) { // Code + 3; // max_stack + 10; // max_locals + Bytes[]{ + 0x2ABB003159B70033; + 0xB900340200B60039; + 0x2BBB003159B70033; + 0xB900340200B60039; + 0x2CBB003159B70033; + 0xB900340200B60039; + 0x1908BB003159B700; + 0x33B6003AB600392D; + 0xBB003159B70033B6; + 0x003DB600391904BB; + 0x003159B70033B600; + 0x3DB600391905BB00; + 0x3159B70033B6003D; + 0xB600391906BB0031; + 0x59B70033B6003DB6; + 0x00391907BB003159; + 0xB70033B6003DB600; + 0x391909BB003159B7; + 0x0033B60040B60039; + 0xB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#131) { // LineNumberTable + [] { // line_number_table + 0 353; + 16 354; + 32 355; + 48 356; + 63 358; + 77 359; + 92 360; + 107 361; + 122 362; + 137 363; + 152 364; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0009; // access + #138; // name_index + #139; // descriptor_index + [] { // Attributes + Attr(#130) { // Code + 3; // max_stack + 2; // max_locals + Bytes[]{ + 0x1B99000C2A1BB600; + 0x4357A7001A2A1BB6; + 0x0043B40048102A9F; + 0x000DBB004E591250; + 0xB70052BFB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#131) { // LineNumberTable + [] { // line_number_table + 0 369; + 4 370; + 13 372; + 26 373; + 36 376; + } + } // end LineNumberTable + ; + Attr(#140) { // StackMapTable + [] { // + 13b; // same_frame + 22b; // same_frame + } + } // end StackMapTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0009; // access + #141; // name_index + #142; // descriptor_index + [] { // Attributes + Attr(#130) { // Code + 2; // max_stack + 3; // max_locals + Bytes[]{ + 0x2A1BB600554D1B9A; + 0x00092CB9005A0100; + 0xB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#131) { // LineNumberTable + [] { // line_number_table + 0 381; + 6 382; + 10 383; + 16 385; + } + } // end LineNumberTable + ; + Attr(#140) { // StackMapTable + [] { // + 252b, 16, []z{O,91}; // append_frame 1 + } + } // end StackMapTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0009; // access + #143; // name_index + #144; // descriptor_index + [] { // Attributes + Attr(#130) { // Code + 3; // max_stack + 3; // max_locals + Bytes[]{ + 0x2ABB001959B7001B; + 0xB6005D572BBB0019; + 0x59B7001BB6006057; + 0x2CBB003159B70033; + 0xB6006357B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#131) { // LineNumberTable + [] { // line_number_table + 0 389; + 12 390; + 24 391; + 36 392; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0009; // access + #145; // name_index + #146; // descriptor_index + [] { // Attributes + Attr(#130) { // Code + 2; // max_stack + 2; // max_locals + Bytes[]{ + 0x2A1BB60066B0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#131) { // LineNumberTable + [] { // line_number_table + 0 397; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0009; // access + #147; // name_index + #148; // descriptor_index + [] { // Attributes + Attr(#130) { // Code + 2; // max_stack + 1; // max_locals + Bytes[]{ + 0x2A04B60066572A03; + 0xB60066B6006BB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#131) { // LineNumberTable + [] { // line_number_table + 0 401; + 6 402; + 14 403; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0009; // access + #149; // name_index + #150; // descriptor_index + [] { // Attributes + Attr(#130) { // Code + 3; // max_stack + 10; // max_locals + Bytes[]{ + 0x2ABB006E59B70070; + 0xB900710200B60076; + 0x2BBB006E59B70070; + 0xB900710200B60076; + 0x2CBB006E59B70070; + 0xB900710200B60076; + 0x1908BB006E59B700; + 0x70B60077B600762D; + 0xBB006E59B70070B6; + 0x007AB600761904BB; + 0x006E59B70070B600; + 0x7AB600761905BB00; + 0x6E59B70070B6007A; + 0xB600761906BB006E; + 0x59B70070B6007AB6; + 0x00761907BB006E59; + 0xB70070B6007AB600; + 0x761909BB006E59B7; + 0x0070B6007DB60076; + 0xB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#131) { // LineNumberTable + [] { // line_number_table + 0 412; + 16 413; + 32 414; + 48 415; + 63 417; + 77 418; + 92 419; + 107 420; + 122 421; + 137 422; + 152 423; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#151) { // SourceFile + #152; + } // end SourceFile + } // Attributes +} // end class TestMismatchHandlingHelper +class A { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #6; // #9 + Utf8 "MyValue1"; // #10 + Utf8 "verify"; // #11 + class #13; // #12 + Utf8 "A"; // #13 + Utf8 "Code"; // #14 + Utf8 "LineNumberTable"; // #15 + Utf8 "method"; // #16 + Utf8 "(LMyValue1;)LMyValue1;"; // #17 + Utf8 "SourceFile"; // #18 + Utf8 "TestMismatchHandlingGenerator.java"; // #19 + } // Constant Pool + + 0x0020; // access + #12;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#14) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#15) { // LineNumberTable + [] { // line_number_table + 0 112; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0001; // access + #16; // name_index + #17; // descriptor_index + [] { // Attributes + Attr(#14) { // Code + 1; // max_stack + 2; // max_locals + Bytes[]{ + 0x2BB600072BB0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#15) { // LineNumberTable + [] { // line_number_table + 0 114; + 4 115; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#18) { // SourceFile + #19; + } // end SourceFile + } // Attributes +} // end class A +class C { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "B"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #6; // #9 + Utf8 "MyValue1"; // #10 + Utf8 "verify"; // #11 + class #13; // #12 + Utf8 "C"; // #13 + Utf8 "Code"; // #14 + Utf8 "LineNumberTable"; // #15 + Utf8 "method"; // #16 + Utf8 "(LMyValue1;)LMyValue1;"; // #17 + Utf8 "SourceFile"; // #18 + Utf8 "TestMismatchHandlingGenerator.java"; // #19 + } // Constant Pool + + 0x0020; // access + #12;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#14) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#15) { // LineNumberTable + [] { // line_number_table + 0 127; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0001; // access + #16; // name_index + #17; // descriptor_index + [] { // Attributes + Attr(#14) { // Code + 1; // max_stack + 2; // max_locals + Bytes[]{ + 0x2BB600072BB0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#15) { // LineNumberTable + [] { // line_number_table + 0 130; + 4 131; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#18) { // SourceFile + #19; + } // end SourceFile + } // Attributes +} // end class C +class I1 { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + class #2; // #1 + Utf8 "I1"; // #2 + class #4; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 "method"; // #5 + Utf8 "(LMyValue2;)LMyValue2;"; // #6 + Utf8 "SourceFile"; // #7 + Utf8 "TestMismatchHandlingGenerator.java"; // #8 + } // Constant Pool + + 0x0600; // access + #1;// this_cpx + #3;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0401; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#7) { // SourceFile + #8; + } // end SourceFile + } // Attributes +} // end class I1 +class I2 { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + class #2; // #1 + Utf8 "I2"; // #2 + class #4; // #3 + Utf8 "java/lang/Object"; // #4 + class #6; // #5 + Utf8 "I1"; // #6 + Utf8 "method"; // #7 + Utf8 "(LMyValue2;)LMyValue2;"; // #8 + Utf8 "SourceFile"; // #9 + Utf8 "TestMismatchHandlingGenerator.java"; // #10 + } // Constant Pool + + 0x0600; // access + #1;// this_cpx + #3;// super_cpx + + [] { // Interfaces + #5; + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0401; // access + #7; // name_index + #8; // descriptor_index + [] { // Attributes + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#9) { // SourceFile + #10; + } // end SourceFile + } // Attributes +} // end class I2 +file "D.class" { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #6; // #9 + Utf8 "MyValue2"; // #10 + Utf8 "verify"; // #11 + class #13; // #12 + Utf8 "D"; // #13 + class #15; // #14 + Utf8 "I2"; // #15 + Utf8 "Code"; // #16 + Utf8 "LineNumberTable"; // #17 + Utf8 "method"; // #18 + Utf8 "(LMyValue2;)LMyValue2;"; // #19 + Utf8 "SourceFile"; // #20 + Utf8 "TestMismatchHandlingGenerator.java"; // #21 + } // Constant Pool + + 0x0020; // access + #12;// this_cpx + #2;// super_cpx + + [] { // Interfaces + #14; + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#16) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#17) { // LineNumberTable + [] { // line_number_table + 0 152; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0001; // access + #18; // name_index + #19; // descriptor_index + [] { // Attributes + Attr(#16) { // Code + 1; // max_stack + 2; // max_locals + Bytes[]{ + 0x2BB600072BB0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#17) { // LineNumberTable + [] { // line_number_table + 0 155; + 4 156; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#20) { // SourceFile + #21; + } // end SourceFile + } // Attributes +} // end class D +file "F.class" { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #6; // #9 + Utf8 "MyValue2"; // #10 + Utf8 "verify"; // #11 + class #13; // #12 + Utf8 "F"; // #13 + class #15; // #14 + Utf8 "I2"; // #15 + class #17; // #16 + Utf8 "I4"; // #17 + Utf8 "Code"; // #18 + Utf8 "LineNumberTable"; // #19 + Utf8 "method"; // #20 + Utf8 "(LMyValue2;)LMyValue2;"; // #21 + Utf8 "SourceFile"; // #22 + Utf8 "TestMismatchHandlingGenerator.java"; // #23 + } // Constant Pool + + 0x0020; // access + #12;// this_cpx + #2;// super_cpx + + [] { // Interfaces + #14; + #16; + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#18) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#19) { // LineNumberTable + [] { // line_number_table + 0 169; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0001; // access + #20; // name_index + #21; // descriptor_index + [] { // Attributes + Attr(#18) { // Code + 1; // max_stack + 2; // max_locals + Bytes[]{ + 0x2BB600072BB0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#19) { // LineNumberTable + [] { // line_number_table + 0 172; + 4 173; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#22) { // SourceFile + #23; + } // end SourceFile + } // Attributes +} // end class F +class H { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #6; // #9 + Utf8 "MyValue3"; // #10 + Utf8 "verify"; // #11 + class #13; // #12 + Utf8 "H"; // #13 + class #15; // #14 + Utf8 "I5"; // #15 + Utf8 "Code"; // #16 + Utf8 "LineNumberTable"; // #17 + Utf8 "method"; // #18 + Utf8 "(LMyValue3;)LMyValue3;"; // #19 + Utf8 "SourceFile"; // #20 + Utf8 "TestMismatchHandlingGenerator.java"; // #21 + } // Constant Pool + + 0x0020; // access + #12;// this_cpx + #2;// super_cpx + + [] { // Interfaces + #14; + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#16) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#17) { // LineNumberTable + [] { // line_number_table + 0 189; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0001; // access + #18; // name_index + #19; // descriptor_index + [] { // Attributes + Attr(#16) { // Code + 1; // max_stack + 2; // max_locals + Bytes[]{ + 0x2BB600072BB0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#17) { // LineNumberTable + [] { // line_number_table + 0 192; + 4 193; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#20) { // SourceFile + #21; + } // end SourceFile + } // Attributes +} // end class H +class I5 { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + class #2; // #1 + Utf8 "I5"; // #2 + class #4; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 "method"; // #5 + Utf8 "(LMyValue3;)LMyValue3;"; // #6 + Utf8 "SourceFile"; // #7 + Utf8 "TestMismatchHandlingGenerator.java"; // #8 + } // Constant Pool + + 0x0600; // access + #1;// this_cpx + #3;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0401; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#7) { // SourceFile + #8; + } // end SourceFile + } // Attributes +} // end class I5 +class M { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Field #8 #9; // #7 + class #10; // #8 + NameAndType #11 #12; // #9 + Utf8 "M"; // #10 + Utf8 "val"; // #11 + Utf8 "I"; // #12 + Method #14 #15; // #13 + class #16; // #14 + NameAndType #17 #18; // #15 + Utf8 "MyValue4"; // #16 + Utf8 "make"; // #17 + Utf8 "()LMyValue4;"; // #18 + Utf8 "Code"; // #19 + Utf8 "LineNumberTable"; // #20 + Utf8 "method"; // #21 + Utf8 "(Z)LMyValue4;"; // #22 + Utf8 "StackMapTable"; // #23 + Utf8 "SourceFile"; // #24 + Utf8 "TestMismatchHandlingGenerator.java"; // #25 + } // Constant Pool + + 0x0020; // access + #8;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + { // field + 0x0000; // access + #11; // name_index + #12; // descriptor_index + [] { // Attributes + } // Attributes + } + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#19) { // Code + 2; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB700012A03B500; + 0x07B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#20) { // LineNumberTable + [] { // line_number_table + 0 220; + 4 221; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0001; // access + #21; // name_index + #22; // descriptor_index + [] { // Attributes + Attr(#19) { // Code + 3; // max_stack + 4; // max_locals + Bytes[]{ + 0x1B99000501B0B800; + 0x0D4D033E1D100AA2; + 0x00132A59B4000704; + 0x60B50007840301A7; + 0xFFED2CB0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#20) { // LineNumberTable + [] { // line_number_table + 0 224; + 4 225; + 6 227; + 10 229; + 18 230; + 28 229; + 34 232; + } + } // end LineNumberTable + ; + Attr(#23) { // StackMapTable + [] { // + 6b; // same_frame + 253b, 5, []z{O,14; I}; // append_frame 2 + 250b, 21; // chop_frame 1 + } + } // end StackMapTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#24) { // SourceFile + #25; + } // end SourceFile + } // Attributes +} // end class M +file "N.class" { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #12; // #9 + Utf8 "MyValue5"; // #10 + Utf8 "make"; // #11 + Utf8 "()LMyValue5;"; // #12 + class #14; // #13 + Utf8 "N"; // #14 + Utf8 "Code"; // #15 + Utf8 "LineNumberTable"; // #16 + Utf8 "method"; // #17 + Utf8 "(Z)LMyValue5;"; // #18 + Utf8 "StackMapTable"; // #19 + Utf8 "SourceFile"; // #20 + Utf8 "TestMismatchHandlingGenerator.java"; // #21 + } // Constant Pool + + 0x0020; // access + #13;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#15) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#16) { // LineNumberTable + [] { // line_number_table + 0 237; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0001; // access + #17; // name_index + #18; // descriptor_index + [] { // Attributes + Attr(#15) { // Code + 1; // max_stack + 2; // max_locals + Bytes[]{ + 0x1B99000501B0B800; + 0x07B0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#16) { // LineNumberTable + [] { // line_number_table + 0 239; + 4 240; + 6 242; + } + } // end LineNumberTable + ; + Attr(#19) { // StackMapTable + [] { // + 6b; // same_frame + } + } // end StackMapTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#20) { // SourceFile + #21; + } // end SourceFile + } // Attributes +} // end class N +file "O.class" { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #12; // #9 + Utf8 "MyValue6"; // #10 + Utf8 "make"; // #11 + Utf8 "()LMyValue6;"; // #12 + class #14; // #13 + Utf8 "O"; // #14 + Utf8 "Code"; // #15 + Utf8 "LineNumberTable"; // #16 + Utf8 "method"; // #17 + Utf8 "(Z)LMyValue6;"; // #18 + Utf8 "StackMapTable"; // #19 + Utf8 "SourceFile"; // #20 + Utf8 "TestMismatchHandlingGenerator.java"; // #21 + } // Constant Pool + + 0x0020; // access + #13;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#15) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#16) { // LineNumberTable + [] { // line_number_table + 0 247; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0001; // access + #17; // name_index + #18; // descriptor_index + [] { // Attributes + Attr(#15) { // Code + 1; // max_stack + 2; // max_locals + Bytes[]{ + 0x1B99000501B0B800; + 0x07B0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#16) { // LineNumberTable + [] { // line_number_table + 0 249; + 4 250; + 6 252; + } + } // end LineNumberTable + ; + Attr(#19) { // StackMapTable + [] { // + 6b; // same_frame + } + } // end StackMapTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#20) { // SourceFile + #21; + } // end SourceFile + } // Attributes +} // end class O +class I6 { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + class #2; // #1 + Utf8 "I6"; // #2 + class #4; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 "method"; // #5 + Utf8 "(LMyValue7;)LMyValue7;"; // #6 + Utf8 "Code"; // #7 + Utf8 "LineNumberTable"; // #8 + Utf8 "SourceFile"; // #9 + Utf8 "TestMismatchHandlingGenerator.java"; // #10 + } // Constant Pool + + 0x0600; // access + #1;// this_cpx + #3;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#7) { // Code + 1; // max_stack + 2; // max_locals + Bytes[]{ + 0x01B0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#8) { // LineNumberTable + [] { // line_number_table + 0 259; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#9) { // SourceFile + #10; + } // end SourceFile + } // Attributes +} // end class I6 +class P { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #6; // #9 + Utf8 "MyValue7"; // #10 + Utf8 "verify"; // #11 + class #13; // #12 + Utf8 "P"; // #13 + class #15; // #14 + Utf8 "I6"; // #15 + Utf8 "Code"; // #16 + Utf8 "LineNumberTable"; // #17 + Utf8 "method"; // #18 + Utf8 "(LMyValue7;)LMyValue7;"; // #19 + Utf8 "SourceFile"; // #20 + Utf8 "TestMismatchHandlingGenerator.java"; // #21 + } // Constant Pool + + 0x0020; // access + #12;// this_cpx + #2;// super_cpx + + [] { // Interfaces + #14; + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0000; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#16) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#17) { // LineNumberTable + [] { // line_number_table + 0 263; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0001; // access + #18; // name_index + #19; // descriptor_index + [] { // Attributes + Attr(#16) { // Code + 1; // max_stack + 2; // max_locals + Bytes[]{ + 0x2BB600072BB0; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#17) { // LineNumberTable + [] { // line_number_table + 0 266; + 4 267; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#20) { // SourceFile + #21; + } // end SourceFile + } // Attributes +} // end class P diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestMismatchHandlingGenerator.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestMismatchHandlingGenerator.java new file mode 100644 index 00000000000..2d765b2c45d --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestMismatchHandlingGenerator.java @@ -0,0 +1,424 @@ +/* + * 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. + */ + +value class MyValue1 { + int x = 42; + + void verify() { + if (x != 42) { + throw new RuntimeException("Verification failed"); + } + } +} + +value class MyValue2 { + int x = 42; + + void verify() { + if (x != 42) { + throw new RuntimeException("Verification failed"); + } + } +} + +value class MyValue3 { + int x = 42; + + void verify() { + if (x != 42) { + throw new RuntimeException("Verification failed"); + } + } +} + +value class MyValue4 { + int x = 42; + + public void verify() { + if (x != 42) { + throw new RuntimeException("Verification failed"); + } + } + + static MyValue4 make() { + return new MyValue4(); + } +} + +interface Verifiable { + public void verify(); +} + +value class MyValue5 implements Verifiable { + int x = 42; + + @Override + public void verify() { + if (x != 42) { + throw new RuntimeException("Verification failed"); + } + } + + static MyValue5 make() { + return new MyValue5(); + } +} + +value class MyValue6 implements Verifiable { + int x = 42; + + @Override + public void verify() { + if (x != 42) { + throw new RuntimeException("Verification failed"); + } + } + + static MyValue6 make() { + return new MyValue6(); + } +} + +value class MyValue7 { + int x = 42; + + void verify() { + if (x != 42) { + throw new RuntimeException("Verification failed"); + } + } +} + +class A { + public MyValue1 method(MyValue1 arg) { + arg.verify(); + return arg; + } +} + +class B extends A { + @Override + public MyValue1 method(MyValue1 arg) { + arg.verify(); + return arg; + } +} + +class C extends B { + @Override + public MyValue1 method(MyValue1 arg) { + arg.verify(); + return arg; + } +} + +interface I1 { + public MyValue2 method(MyValue2 arg); +} + +interface I2 extends I1 { + public MyValue2 method(MyValue2 arg); +} + +interface I3 { + public MyValue2 method(MyValue2 arg); +} + +interface I4 extends I3 { + public MyValue2 method(MyValue2 arg); +} + + +class D implements I2 { + @Override + public MyValue2 method(MyValue2 arg) { + arg.verify(); + return arg; + } +} + +class E implements I4 { + @Override + public MyValue2 method(MyValue2 arg) { + arg.verify(); + return arg; + } +} + + +class F implements I2, I4 { + @Override + public MyValue2 method(MyValue2 arg) { + arg.verify(); + return arg; + } +} + +class G implements I2, I4 { + @Override + public MyValue2 method(MyValue2 arg) { + arg.verify(); + return arg; + } +} + +interface I5 { + public MyValue3 method(MyValue3 arg); +} + +class H implements I5 { + @Override + public MyValue3 method(MyValue3 arg) { + arg.verify(); + return arg; + } +} + +class J { + public MyValue3 method(MyValue3 arg) { + arg.verify(); + return arg; + } +} + +class K extends J { + @Override + public MyValue3 method(MyValue3 arg) { + arg.verify(); + return arg; + } +} + +class L extends K implements I5 { + @Override + public MyValue3 method(MyValue3 arg) { + arg.verify(); + return arg; + } +} + +class M { + int val = 0; + + public MyValue4 method(boolean warmup) { + if (warmup) { + return null; + } else { + MyValue4 res = MyValue4.make(); + // Do something here to "corrupt" registers + for (int i = 0; i < 10; ++i) { + val++; + } + return res; + } + } +} + +class N { + public MyValue5 method(boolean warmup) { + if (warmup) { + return null; + } else { + return MyValue5.make(); + } + } +} + +class O { + public MyValue6 method(boolean warmup) { + if (warmup) { + return null; + } else { + return MyValue6.make(); + } + } +} + +interface I6 { + default MyValue7 method(MyValue7 arg) { + return null; + } +} + +class P implements I6 { + @Override + public MyValue7 method(MyValue7 arg) { + arg.verify(); + return arg; + } +} + +class Q { + MyValue7 method(MyValue7 arg) { + arg.verify(); + return arg; + } +} + +class R extends Q { + @Override + MyValue7 method(MyValue7 arg) { + arg.verify(); + return arg; + } +} + +class S extends R implements I6 { + @Override + public MyValue7 method(MyValue7 arg) { + arg.verify(); + return arg; + } +} + +class TestMismatchHandlingHelper { + // * = has preload attribute for MyValue* + + // With C <: B* <: A + public static void test1(A a1, A a2, A a3, A a4, A a5, B b1, B b2, C c) { + // Non-scalarized virtual call site, mismatching on B + a1.method(new MyValue1()).verify(); + a2.method(new MyValue1()).verify(); + a3.method(new MyValue1()).verify(); + a4.method(new MyValue1()).verify(); + a5.method(new MyValue1()).verify(); + // Scalarized virtual call sites, mismatching on C + b1.method(new MyValue1()).verify(); + b2.method(new MyValue1()).verify(); + c.method(new MyValue1()).verify(); + } + + // D <: I2 <: I1 + // E* <: I4* <: I3* + // Loaded later, combine both hierachies and introduce a mismatch: + // F <: I2, I4* + // G* <: I2, I4* + public static void test2(I1 i11, I1 i12, I1 i13, I1 i14, I1 i15, I1 i16, I2 i21, I2 i22, I2 i23, I2 i24, I2 i25, I2 i26, I3 i31, I3 i32, I3 i33, I3 i34, I3 i35, I3 i36, I4 i41, I4 i42, I4 i43, I4 i44, I4 i45, I4 i46, D d, E e) { + // Non-scalarized virtual call sites, mismatching on E + i11.method(new MyValue2()).verify(); + i12.method(new MyValue2()).verify(); + i13.method(new MyValue2()).verify(); + i14.method(new MyValue2()).verify(); + i15.method(new MyValue2()).verify(); + i16.method(new MyValue2()).verify(); + i21.method(new MyValue2()).verify(); + i22.method(new MyValue2()).verify(); + i23.method(new MyValue2()).verify(); + i24.method(new MyValue2()).verify(); + i25.method(new MyValue2()).verify(); + i26.method(new MyValue2()).verify(); + d.method(new MyValue2()).verify(); + // Scalarized virtual call sites, mismatching on D + i31.method(new MyValue2()).verify(); + i32.method(new MyValue2()).verify(); + i33.method(new MyValue2()).verify(); + i34.method(new MyValue2()).verify(); + i35.method(new MyValue2()).verify(); + i36.method(new MyValue2()).verify(); + i41.method(new MyValue2()).verify(); + i42.method(new MyValue2()).verify(); + i43.method(new MyValue2()).verify(); + i44.method(new MyValue2()).verify(); + i45.method(new MyValue2()).verify(); + i46.method(new MyValue2()).verify(); + e.method(new MyValue2()).verify(); + } + + // H <: I5 + // K* <: J* + // Loaded later, combines both hierachies and introduces a mismatch: + // L* <: K*, I5 + public static void test3(I5 i51, I5 i52, I5 i53, J j1, J j2, J j3, J j4, J j5, H h, K k) { + // Non-scalarized virtual call sites, mismatching on L + i51.method(new MyValue3()).verify(); + i52.method(new MyValue3()).verify(); + i53.method(new MyValue3()).verify(); + h.method(new MyValue3()).verify(); + // Scalarized virtual call sites + j1.method(new MyValue3()).verify(); + j2.method(new MyValue3()).verify(); + j3.method(new MyValue3()).verify(); + j4.method(new MyValue3()).verify(); + j5.method(new MyValue3()).verify(); + k.method(new MyValue3()).verify(); + } + + // Test that a C1 compiled method returns in scalarized form if the method holder class M + // is loaded but the value class return type is not due to a missing preload attribute. + public static void test4(M m, boolean warmup) { + if (warmup) { + m.method(warmup); + } else { + if (m.method(warmup).x != 42) { + throw new RuntimeException("Verification failed"); + } + } + } + + // Test that C1 correctly handles scalarized returns at calls if the method holder class N + // is loaded but the value class return type is not due to a missing preload attribute. + public static void test5(N n, boolean warmup) { + Verifiable res = n.method(warmup); + if (!warmup) { + res.verify(); + } + } + + // Test direct calls + public static void test6(F f, G g, L l) { + f.method(new MyValue2()); + g.method(new MyValue2()); + l.method(new MyValue3()); + } + + // Test scalarized return from C2 compiled callee to C2 compiled caller with an unloaded + // return type at caller compile time due to a missing preload attribute. + public static Verifiable test7(O o, boolean warmup) { + return o.method(warmup); + } + + public static void test7TriggerCalleeCompilation(O o) { + o.method(true); + o.method(false).verify(); + } + + // Same as test3 but with default method in interface and package private methods + // P <: I6 + // R* <: Q* + // Loaded later, combines both hierachies and introduces a mismatch: + // S* <: R*, I6 + public static void test8(I6 i61, I6 i62, I6 i63, Q q1, Q q2, Q q3, Q q4, Q q5, P p, R r) { + // Non-scalarized virtual call sites, mismatching on S + i61.method(new MyValue7()).verify(); + i62.method(new MyValue7()).verify(); + i63.method(new MyValue7()).verify(); + p.method(new MyValue7()).verify(); + // Scalarized virtual call sites + q1.method(new MyValue7()).verify(); + q2.method(new MyValue7()).verify(); + q3.method(new MyValue7()).verify(); + q4.method(new MyValue7()).verify(); + q5.method(new MyValue7()).verify(); + r.method(new MyValue7()).verify(); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestMultidimArrays.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestMultidimArrays.java new file mode 100644 index 00000000000..4040300a860 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestMultidimArrays.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @library /test/lib + * @summary Test correct handling of multidimensional arrays. + * @enablePreview + * @run main TestMultidimArrays + * @run main/othervm -Xbatch -XX:-TieredCompilation + * -XX:CompileCommand=compileonly,TestMultidimArrays::test* + * TestMultidimArrays + * @run main/othervm -Xbatch -XX:-TieredCompilation -XX:MultiArrayExpandLimit=0 + * -XX:CompileCommand=compileonly,TestMultidimArrays::test* + * TestMultidimArrays + * @run main/othervm -Xbatch -XX:-TieredCompilation + * -XX:+IgnoreUnrecognizedVMOptions -XX:+StressReflectiveCode + * -XX:CompileCommand=compileonly,TestMultidimArrays::test* + * TestMultidimArrays + * @run main/othervm -Xbatch -XX:-TieredCompilation -XX:-DoEscapeAnalysis + * -XX:CompileCommand=compileonly,TestMultidimArrays::test* + * TestMultidimArrays + */ + +import jdk.test.lib.Asserts; + +public class TestMultidimArrays { + + static value class MyValue { + int val; + + public MyValue(int val) { + this.val = val; + } + } + + static MyValue[][] test1() { + MyValue[][] arr = new MyValue[2][2]; + arr[0][1] = new MyValue(42); + for (int i = 0; i < 50_000; i++) { + } + return arr; + } + + static MyValue[][] test2() { + MyValue[][] arr = new MyValue[2][2]; + arr[0][1] = new MyValue(42); + return arr; + } + + static int[][] test3() { + int[][] arr = new int[2][2]; + arr[0][1] = 42; + return arr; + } + + public static void main(String[] args) { + for (int i = 0; i < 50_000; ++i) { + MyValue[][] res1 = test1(); + Asserts.assertEQ(res1[0][0], null); + Asserts.assertEQ(res1[0][1], new MyValue(42)); + + MyValue[][] res2 = test2(); + Asserts.assertEQ(res2[0][0], null); + Asserts.assertEQ(res2[0][1], new MyValue(42)); + + int[][] res3 = test3(); + Asserts.assertEQ(res3[0][0], 0); + Asserts.assertEQ(res3[0][1], 42); + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestNativeClone.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestNativeClone.java new file mode 100644 index 00000000000..9a11b5c213b --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestNativeClone.java @@ -0,0 +1,77 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8209702 + * @summary Verify that the native clone intrinsic handles value objects. + * @library /test/lib + * @modules java.base/java.lang:+open + * @enablePreview + * @run main/othervm -Xbatch -XX:-UseTypeProfile + * -XX:CompileCommand=compileonly,compiler.valhalla.inlinetypes.MyValue::* + * -XX:CompileCommand=compileonly,compiler.valhalla.inlinetypes.TestNativeClone::test* + * -XX:CompileCommand=compileonly,jdk.internal.reflect.GeneratedMethodAccessor1::invoke + * -XX:CompileCommand=dontinline,jdk.internal.reflect.GeneratedMethodAccessor1::invoke + * compiler.valhalla.inlinetypes.TestNativeClone + */ + +package compiler.valhalla.inlinetypes; + +import java.lang.invoke.*; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import jdk.test.lib.Asserts; + +value class MyValue { + public int x; + + public MyValue(int x) { + this.x = x; + } +} + +public class TestNativeClone { + + public static void test1(Method clone, Object obj) { + try { + clone.invoke(obj); + } catch (InvocationTargetException e) { + // Expected + Asserts.assertTrue(e.getCause() instanceof CloneNotSupportedException, "Unexpected exception thrown"); + return; + } catch (Exception e) { + throw new RuntimeException("Unexpected exception thrown", e); + } + throw new RuntimeException("No exception thrown"); + } + + public static void main(String[] args) throws Throwable { + MyValue vt = new MyValue(42); + Method clone = Object.class.getDeclaredMethod("clone"); + clone.setAccessible(true); + for (int i = 0; i < 20_000; ++i) { + test1(clone, vt); + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestNestmateAccess.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestNestmateAccess.java new file mode 100644 index 00000000000..85b53310e16 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestNestmateAccess.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import jdk.test.lib.Asserts; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + + +/** + * @test + * @bug 8253416 + * @summary Test nestmate access to flattened field if nest-host is not loaded. + * @library /test/lib + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm -Xcomp + * -XX:CompileCommand=compileonly,compiler.valhalla.inlinetypes.Test*:: + * compiler.valhalla.inlinetypes.TestNestmateAccess + * @run main/othervm -Xcomp -XX:TieredStopAtLevel=1 + * -XX:CompileCommand=compileonly,compiler.valhalla.inlinetypes.Test*:: + * compiler.valhalla.inlinetypes.TestNestmateAccess + * @run main/othervm compiler.valhalla.inlinetypes.TestNestmateAccess + */ + +interface MyInterface { + int hash(); +} + +@LooselyConsistentValue +value class MyValue implements MyInterface { + int x = 42; + int y = 43; + + @Override + public int hash() { return x + y; } +} + +// Test load from flattened field in nestmate when nest-host is not loaded. +class Test1 { + @Strict + @NullRestricted + private MyValue vt; + + public Test1(final MyValue vt) { + this.vt = vt; + } + + public MyInterface test() { + return new MyInterface() { + // The vt field load does not link. + private int x = (Test1.this).vt.hash(); + + @Override + public int hash() { return x; } + }; + } +} + +// Same as Test1 but outer class is a value class +@LooselyConsistentValue +value class Test2 { + @Strict + @NullRestricted + private MyValue vt; + + public Test2(final MyValue vt) { + this.vt = vt; + } + + public MyInterface test() { + return new MyInterface() { + // Delayed flattened load of Test2.this. + // The vt field load does not link. + private int x = (Test2.this).vt.hash(); + + @Override + public int hash() { return x; } + }; + } +} + +// Test store to flattened field in nestmate when nest-host is not loaded. +class Test3 { + private MyValue vt; + + public MyInterface test(MyValue init) { + return new MyInterface() { + // Store to the vt field does not link. + private MyValue tmp = (vt = init); + + @Override + public int hash() { return tmp.hash() + vt.hash(); } + }; + } +} + +// Same as Test1 but with static field +class Test4 { + private static MyValue vt = null; + + public Test4(final MyValue vt) { + this.vt = vt; + } + + public MyInterface test() { + return new MyInterface() { + // The vt field load does not link. + private int x = (Test4.this).vt.hash(); + + @Override + public int hash() { return x; } + }; + } +} + +// Same as Test2 but with static field +@LooselyConsistentValue +value class Test5 { + private static MyValue vt; + + public Test5(final MyValue vt) { + this.vt = vt; + } + + public MyInterface test() { + return new MyInterface() { + // Delayed flattened load of Test5.this. + // The vt field load does not link. + private int x = (Test5.this).vt.hash(); + + @Override + public int hash() { return x; } + }; + } +} + +// Same as Test3 but with static field +class Test6 { + private static MyValue vt; + + public MyInterface test(MyValue init) { + return new MyInterface() { + // Store to the vt field does not link. + private MyValue tmp = (vt = init); + + @Override + public int hash() { return tmp.hash() + vt.hash(); } + }; + } +} + +// Same as Test6 but outer class is a value class +@LooselyConsistentValue +value class Test7 { + private static MyValue vt; + + public MyInterface test(MyValue init) { + return new MyInterface() { + // Store to the vt field does not link. + private MyValue tmp = (vt = init); + + @Override + public int hash() { return tmp.hash() + vt.hash(); } + }; + } +} + +public class TestNestmateAccess { + + public static void main(String[] args) { + Test1 t1 = new Test1(new MyValue()); + int res = t1.test().hash(); + Asserts.assertEQ(res, 85); + + Test2 t2 = new Test2(new MyValue()); + res = t2.test().hash(); + Asserts.assertEQ(res, 85); + + Test3 t3 = new Test3(); + res = t3.test(new MyValue()).hash(); + Asserts.assertEQ(res, 170); + + Test4 t4 = new Test4(new MyValue()); + res = t4.test().hash(); + Asserts.assertEQ(res, 85); + + Test5 t5 = new Test5(new MyValue()); + res = t5.test().hash(); + Asserts.assertEQ(res, 85); + + Test6 t6 = new Test6(); + res = t6.test(new MyValue()).hash(); + Asserts.assertEQ(res, 170); + + Test7 t7 = new Test7(); + res = t7.test(new MyValue()).hash(); + Asserts.assertEQ(res, 170); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestNewAcmp.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestNewAcmp.java new file mode 100644 index 00000000000..1c006291aba --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestNewAcmp.java @@ -0,0 +1,1911 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test TestNewAcmp + * @summary Verifies correctness of the acmp bytecode with value object operands. + * @library /testlibrary /test/lib /compiler/whitebox / + * @enablePreview + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * compiler.valhalla.inlinetypes.TestNewAcmp + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.CompLevel; +import compiler.lib.ir_framework.TestFramework; +import jdk.test.lib.Asserts; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.process.OutputAnalyzer; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.invoke.*; +import java.lang.reflect.Method; +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import jdk.test.whitebox.WhiteBox; + +interface MyInterface { + +} + +abstract value class MyAbstract implements MyInterface { + +} + +value class MyValue1 extends MyAbstract { + int x; + + MyValue1(int x) { + this.x = x; + } + + static MyValue1 createDefault() { + return new MyValue1(0); + } + + static MyValue1 setX(MyValue1 v, int x) { + return new MyValue1(x); + } +} + +value class MyValue2 extends MyAbstract { + int x; + + MyValue2(int x) { + this.x = x; + } + + static MyValue2 createDefault() { + return new MyValue2(0); + } + + static MyValue2 setX(MyValue2 v, int x) { + return new MyValue2(x); + } +} + +class MyObject extends MyAbstract { + int x; +} + +// Mark test methods that return false if the argument is null +@Retention(RetentionPolicy.RUNTIME) +@interface FalseIfNull { } + +// Mark test methods that return true if the argument is null +@Retention(RetentionPolicy.RUNTIME) +@interface TrueIfNull { } + +public class TestNewAcmp { + + public boolean testEq01_1(Object u1, Object u2) { + return get(u1) == u2; // new acmp + } + + public boolean testEq01_2(Object u1, Object u2) { + return u1 == get(u2); // new acmp + } + + public boolean testEq01_3(Object u1, Object u2) { + return get(u1) == get(u2); // new acmp + } + + @FalseIfNull + public boolean testEq01_4(Object u1, Object u2) { + return getNotNull(u1) == u2; // new acmp without null check + } + + @FalseIfNull + public boolean testEq01_5(Object u1, Object u2) { + return u1 == getNotNull(u2); // new acmp without null check + } + + @FalseIfNull + public boolean testEq01_6(Object u1, Object u2) { + return getNotNull(u1) == getNotNull(u2); // new acmp without null check + } + + public boolean testEq02_1(MyValue1 v1, MyValue1 v2) { + return get(v1) == (Object)v2; // only true if both null + } + + public boolean testEq02_2(MyValue1 v1, MyValue1 v2) { + return (Object)v1 == get(v2); // only true if both null + } + + public boolean testEq02_3(MyValue1 v1, MyValue1 v2) { + return get(v1) == get(v2); // only true if both null + } + + public boolean testEq03_1(MyValue1 v, Object u) { + return get(v) == u; // only true if both null + } + + public boolean testEq03_2(MyValue1 v, Object u) { + return (Object)v == get(u); // only true if both null + } + + public boolean testEq03_3(MyValue1 v, Object u) { + return get(v) == get(u); // only true if both null + } + + public boolean testEq04_1(Object u, MyValue1 v) { + return get(u) == (Object)v; // only true if both null + } + + public boolean testEq04_2(Object u, MyValue1 v) { + return u == get(v); // only true if both null + } + + public boolean testEq04_3(Object u, MyValue1 v) { + return get(u) == get(v); // only true if both null + } + + public boolean testEq05_1(MyObject o, MyValue1 v) { + return get(o) == (Object)v; // only true if both null + } + + public boolean testEq05_2(MyObject o, MyValue1 v) { + return o == get(v); // only true if both null + } + + public boolean testEq05_3(MyObject o, MyValue1 v) { + return get(o) == get(v); // only true if both null + } + + public boolean testEq06_1(MyValue1 v, MyObject o) { + return get(v) == o; // only true if both null + } + + public boolean testEq06_2(MyValue1 v, MyObject o) { + return (Object)v == get(o); // only true if both null + } + + public boolean testEq06_3(MyValue1 v, MyObject o) { + return get(v) == get(o); // only true if both null + } + + public boolean testEq07_1(MyValue1 v1, MyValue1 v2) { + return getNotNull(v1) == (Object)v2; // false + } + + public boolean testEq07_2(MyValue1 v1, MyValue1 v2) { + return (Object)v1 == getNotNull(v2); // false + } + + public boolean testEq07_3(MyValue1 v1, MyValue1 v2) { + return getNotNull(v1) == getNotNull(v2); // false + } + + public boolean testEq08_1(MyValue1 v, Object u) { + return getNotNull(v) == u; // false + } + + public boolean testEq08_2(MyValue1 v, Object u) { + return (Object)v == getNotNull(u); // false + } + + public boolean testEq08_3(MyValue1 v, Object u) { + return getNotNull(v) == getNotNull(u); // false + } + + public boolean testEq09_1(Object u, MyValue1 v) { + return getNotNull(u) == (Object)v; // false + } + + public boolean testEq09_2(Object u, MyValue1 v) { + return u == getNotNull(v); // false + } + + public boolean testEq09_3(Object u, MyValue1 v) { + return getNotNull(u) == getNotNull(v); // false + } + + public boolean testEq10_1(MyObject o, MyValue1 v) { + return getNotNull(o) == (Object)v; // false + } + + public boolean testEq10_2(MyObject o, MyValue1 v) { + return o == getNotNull(v); // false + } + + public boolean testEq10_3(MyObject o, MyValue1 v) { + return getNotNull(o) == getNotNull(v); // false + } + + public boolean testEq11_1(MyValue1 v, MyObject o) { + return getNotNull(v) == o; // false + } + + public boolean testEq11_2(MyValue1 v, MyObject o) { + return (Object)v == getNotNull(o); // false + } + + public boolean testEq11_3(MyValue1 v, MyObject o) { + return getNotNull(v) == getNotNull(o); // false + } + + public boolean testEq12_1(MyObject o1, MyObject o2) { + return get(o1) == o2; // old acmp + } + + public boolean testEq12_2(MyObject o1, MyObject o2) { + return o1 == get(o2); // old acmp + } + + public boolean testEq12_3(MyObject o1, MyObject o2) { + return get(o1) == get(o2); // old acmp + } + + public boolean testEq13_1(Object u, MyObject o) { + return get(u) == o; // old acmp + } + + public boolean testEq13_2(Object u, MyObject o) { + return u == get(o); // old acmp + } + + public boolean testEq13_3(Object u, MyObject o) { + return get(u) == get(o); // old acmp + } + + public boolean testEq14_1(MyObject o, Object u) { + return get(o) == u; // old acmp + } + + public boolean testEq14_2(MyObject o, Object u) { + return o == get(u); // old acmp + } + + public boolean testEq14_3(MyObject o, Object u) { + return get(o) == get(u); // old acmp + } + + public boolean testEq15_1(Object[] a, Object u) { + return get(a) == u; // old acmp + } + + public boolean testEq15_2(Object[] a, Object u) { + return a == get(u); // old acmp + } + + public boolean testEq15_3(Object[] a, Object u) { + return get(a) == get(u); // old acmp + } + + public boolean testEq16_1(Object u, Object[] a) { + return get(u) == a; // old acmp + } + + public boolean testEq16_2(Object u, Object[] a) { + return u == get(a); // old acmp + } + + public boolean testEq16_3(Object u, Object[] a) { + return get(u) == get(a); // old acmp + } + + public boolean testEq17_1(Object[] a, MyValue1 v) { + return get(a) == (Object)v; // only true if both null + } + + public boolean testEq17_2(Object[] a, MyValue1 v) { + return a == get(v); // only true if both null + } + + public boolean testEq17_3(Object[] a, MyValue1 v) { + return get(a) == get(v); // only true if both null + } + + public boolean testEq18_1(MyValue1 v, Object[] a) { + return get(v) == a; // only true if both null + } + + public boolean testEq18_2(MyValue1 v, Object[] a) { + return (Object)v == get(a); // only true if both null + } + + public boolean testEq18_3(MyValue1 v, Object[] a) { + return get(v) == get(a); // only true if both null + } + + public boolean testEq19_1(Object[] a, MyValue1 v) { + return getNotNull(a) == (Object)v; // false + } + + public boolean testEq19_2(Object[] a, MyValue1 v) { + return a == getNotNull(v); // false + } + + public boolean testEq19_3(Object[] a, MyValue1 v) { + return getNotNull(a) == getNotNull(v); // false + } + + public boolean testEq20_1(MyValue1 v, Object[] a) { + return getNotNull(v) == a; // false + } + + public boolean testEq20_2(MyValue1 v, Object[] a) { + return (Object)v == getNotNull(a); // false + } + + public boolean testEq20_3(MyValue1 v, Object[] a) { + return getNotNull(v) == getNotNull(a); // false + } + + public boolean testEq21_1(MyInterface u1, MyInterface u2) { + return get(u1) == u2; // new acmp + } + + public boolean testEq21_2(MyInterface u1, MyInterface u2) { + return u1 == get(u2); // new acmp + } + + public boolean testEq21_3(MyInterface u1, MyInterface u2) { + return get(u1) == get(u2); // new acmp + } + + @FalseIfNull + public boolean testEq21_4(MyInterface u1, MyInterface u2) { + return getNotNull(u1) == u2; // new acmp without null check + } + + @FalseIfNull + public boolean testEq21_5(MyInterface u1, MyInterface u2) { + return u1 == getNotNull(u2); // new acmp without null check + } + + @FalseIfNull + public boolean testEq21_6(MyInterface u1, MyInterface u2) { + return getNotNull(u1) == getNotNull(u2); // new acmp without null check + } + + public boolean testEq21_7(MyAbstract u1, MyAbstract u2) { + return get(u1) == u2; // new acmp + } + + public boolean testEq21_8(MyAbstract u1, MyAbstract u2) { + return u1 == get(u2); // new acmp + } + + public boolean testEq21_9(MyAbstract u1, MyAbstract u2) { + return get(u1) == get(u2); // new acmp + } + + @FalseIfNull + public boolean testEq21_10(MyAbstract u1, MyAbstract u2) { + return getNotNull(u1) == u2; // new acmp without null check + } + + @FalseIfNull + public boolean testEq21_11(MyAbstract u1, MyAbstract u2) { + return u1 == getNotNull(u2); // new acmp without null check + } + + @FalseIfNull + public boolean testEq21_12(MyAbstract u1, MyAbstract u2) { + return getNotNull(u1) == getNotNull(u2); // new acmp without null check + } + + public boolean testEq22_1(MyValue1 v, MyInterface u) { + return get(v) == u; // only true if both null + } + + public boolean testEq22_2(MyValue1 v, MyInterface u) { + return (Object)v == get(u); // only true if both null + } + + public boolean testEq22_3(MyValue1 v, MyInterface u) { + return get(v) == get(u); // only true if both null + } + + public boolean testEq22_4(MyValue1 v, MyAbstract u) { + return get(v) == u; // only true if both null + } + + public boolean testEq22_5(MyValue1 v, MyAbstract u) { + return (Object)v == get(u); // only true if both null + } + + public boolean testEq22_6(MyValue1 v, MyAbstract u) { + return get(v) == get(u); // only true if both null + } + + public boolean testEq23_1(MyInterface u, MyValue1 v) { + return get(u) == (Object)v; // only true if both null + } + + public boolean testEq23_2(MyInterface u, MyValue1 v) { + return u == get(v); // only true if both null + } + + public boolean testEq23_3(MyInterface u, MyValue1 v) { + return get(u) == get(v); // only true if both null + } + + public boolean testEq23_4(MyAbstract u, MyValue1 v) { + return get(u) == (Object)v; // only true if both null + } + + public boolean testEq23_5(MyAbstract u, MyValue1 v) { + return u == get(v); // only true if both null + } + + public boolean testEq23_6(MyAbstract u, MyValue1 v) { + return get(u) == get(v); // only true if both null + } + + public boolean testEq24_1(MyValue1 v, MyInterface u) { + return getNotNull(v) == u; // false + } + + public boolean testEq24_2(MyValue1 v, MyInterface u) { + return (Object)v == getNotNull(u); // false + } + + public boolean testEq24_3(MyValue1 v, MyInterface u) { + return getNotNull(v) == getNotNull(u); // false + } + + public boolean testEq24_4(MyValue1 v, MyAbstract u) { + return getNotNull(v) == u; // false + } + + public boolean testEq24_5(MyValue1 v, MyAbstract u) { + return (Object)v == getNotNull(u); // false + } + + public boolean testEq24_6(MyValue1 v, MyAbstract u) { + return getNotNull(v) == getNotNull(u); // false + } + + public boolean testEq25_1(MyInterface u, MyValue1 v) { + return getNotNull(u) == (Object)v; // false + } + + public boolean testEq25_2(MyInterface u, MyValue1 v) { + return u == getNotNull(v); // false + } + + public boolean testEq25_3(MyInterface u, MyValue1 v) { + return getNotNull(u) == getNotNull(v); // false + } + + public boolean testEq25_4(MyAbstract u, MyValue1 v) { + return getNotNull(u) == (Object)v; // false + } + + public boolean testEq25_5(MyAbstract u, MyValue1 v) { + return u == getNotNull(v); // false + } + + public boolean testEq25_6(MyAbstract u, MyValue1 v) { + return getNotNull(u) == getNotNull(v); // false + } + + public boolean testEq26_1(MyInterface u, MyObject o) { + return get(u) == o; // old acmp + } + + public boolean testEq26_2(MyInterface u, MyObject o) { + return u == get(o); // old acmp + } + + public boolean testEq26_3(MyInterface u, MyObject o) { + return get(u) == get(o); // old acmp + } + + public boolean testEq26_4(MyAbstract u, MyObject o) { + return get(u) == o; // old acmp + } + + public boolean testEq26_5(MyAbstract u, MyObject o) { + return u == get(o); // old acmp + } + + public boolean testEq26_6(MyAbstract u, MyObject o) { + return get(u) == get(o); // old acmp + } + + public boolean testEq27_1(MyObject o, MyInterface u) { + return get(o) == u; // old acmp + } + + public boolean testEq27_2(MyObject o, MyInterface u) { + return o == get(u); // old acmp + } + + public boolean testEq27_3(MyObject o, MyInterface u) { + return get(o) == get(u); // old acmp + } + + public boolean testEq27_4(MyObject o, MyAbstract u) { + return get(o) == u; // old acmp + } + + public boolean testEq27_5(MyObject o, MyAbstract u) { + return o == get(u); // old acmp + } + + public boolean testEq27_6(MyObject o, MyAbstract u) { + return get(o) == get(u); // old acmp + } + + public boolean testEq28_1(MyInterface[] a, MyInterface u) { + return get(a) == u; // old acmp + } + + public boolean testEq28_2(MyInterface[] a, MyInterface u) { + return a == get(u); // old acmp + } + + public boolean testEq28_3(MyInterface[] a, MyInterface u) { + return get(a) == get(u); // old acmp + } + + public boolean testEq28_4(MyAbstract[] a, MyAbstract u) { + return get(a) == u; // old acmp + } + + public boolean testEq28_5(MyAbstract[] a, MyAbstract u) { + return a == get(u); // old acmp + } + + public boolean testEq28_6(MyAbstract[] a, MyAbstract u) { + return get(a) == get(u); // old acmp + } + + public boolean testEq29_1(MyInterface u, MyInterface[] a) { + return get(u) == a; // old acmp + } + + public boolean testEq29_2(MyInterface u, MyInterface[] a) { + return u == get(a); // old acmp + } + + public boolean testEq29_3(MyInterface u, MyInterface[] a) { + return get(u) == get(a); // old acmp + } + + public boolean testEq29_4(MyAbstract u, MyAbstract[] a) { + return get(u) == a; // old acmp + } + + public boolean testEq29_5(MyAbstract u, MyAbstract[] a) { + return u == get(a); // old acmp + } + + public boolean testEq29_6(MyAbstract u, MyAbstract[] a) { + return get(u) == get(a); // old acmp + } + + public boolean testEq30_1(MyInterface[] a, MyValue1 v) { + return get(a) == (Object)v; // only true if both null + } + + public boolean testEq30_2(MyInterface[] a, MyValue1 v) { + return a == get(v); // only true if both null + } + + public boolean testEq30_3(MyInterface[] a, MyValue1 v) { + return get(a) == get(v); // only true if both null + } + + public boolean testEq30_4(MyAbstract[] a, MyValue1 v) { + return get(a) == (Object)v; // only true if both null + } + + public boolean testEq30_5(MyAbstract[] a, MyValue1 v) { + return a == get(v); // only true if both null + } + + public boolean testEq30_6(MyAbstract[] a, MyValue1 v) { + return get(a) == get(v); // only true if both null + } + + public boolean testEq31_1(MyValue1 v, MyInterface[] a) { + return get(v) == a; // only true if both null + } + + public boolean testEq31_2(MyValue1 v, MyInterface[] a) { + return (Object)v == get(a); // only true if both null + } + + public boolean testEq31_3(MyValue1 v, MyInterface[] a) { + return get(v) == get(a); // only true if both null + } + + public boolean testEq31_4(MyValue1 v, MyAbstract[] a) { + return get(v) == a; // only true if both null + } + + public boolean testEq31_5(MyValue1 v, MyAbstract[] a) { + return (Object)v == get(a); // only true if both null + } + + public boolean testEq31_6(MyValue1 v, MyAbstract[] a) { + return get(v) == get(a); // only true if both null + } + + public boolean testEq32_1(MyInterface[] a, MyValue1 v) { + return getNotNull(a) == (Object)v; // false + } + + public boolean testEq32_2(MyInterface[] a, MyValue1 v) { + return a == getNotNull(v); // false + } + + public boolean testEq32_3(MyInterface[] a, MyValue1 v) { + return getNotNull(a) == getNotNull(v); // false + } + + public boolean testEq32_4(MyAbstract[] a, MyValue1 v) { + return getNotNull(a) == (Object)v; // false + } + + public boolean testEq32_5(MyAbstract[] a, MyValue1 v) { + return a == getNotNull(v); // false + } + + public boolean testEq32_6(MyAbstract[] a, MyValue1 v) { + return getNotNull(a) == getNotNull(v); // false + } + + public boolean testEq33_1(MyValue1 v, MyInterface[] a) { + return getNotNull(v) == a; // false + } + + public boolean testEq33_2(MyValue1 v, MyInterface[] a) { + return (Object)v == getNotNull(a); // false + } + + public boolean testEq33_3(MyValue1 v, MyInterface[] a) { + return getNotNull(v) == getNotNull(a); // false + } + + public boolean testEq33_4(MyValue1 v, MyAbstract[] a) { + return getNotNull(v) == a; // false + } + + public boolean testEq33_5(MyValue1 v, MyAbstract[] a) { + return (Object)v == getNotNull(a); // false + } + + public boolean testEq33_6(MyValue1 v, MyAbstract[] a) { + return getNotNull(v) == getNotNull(a); // false + } + + + // Null tests + + public boolean testNull01_1(MyValue1 v) { + return (Object)v == null; // old acmp + } + + public boolean testNull01_2(MyValue1 v) { + return get(v) == null; // old acmp + } + + public boolean testNull01_3(MyValue1 v) { + return (Object)v == get((Object)null); // old acmp + } + + public boolean testNull01_4(MyValue1 v) { + return get(v) == get((Object)null); // old acmp + } + + public boolean testNull02_1(MyValue1 v) { + return null == (Object)v; // old acmp + } + + public boolean testNull02_2(MyValue1 v) { + return get((Object)null) == (Object)v; // old acmp + } + + public boolean testNull02_3(MyValue1 v) { + return null == get(v); // old acmp + } + + public boolean testNull02_4(MyValue1 v) { + return get((Object)null) == get(v); // old acmp + } + + public boolean testNull03_1(Object u) { + return u == null; // old acmp + } + + public boolean testNull03_2(Object u) { + return get(u) == null; // old acmp + } + + public boolean testNull03_3(Object u) { + return u == get((Object)null); // old acmp + } + + public boolean testNull03_4(Object u) { + return get(u) == get((Object)null); // old acmp + } + + public boolean testNull04_1(Object u) { + return null == u; // old acmp + } + + public boolean testNull04_2(Object u) { + return get((Object)null) == u; // old acmp + } + + public boolean testNull04_3(Object u) { + return null == get(u); // old acmp + } + + public boolean testNull04_4(Object u) { + return get((Object)null) == get(u); // old acmp + } + + public boolean testNull05_1(MyObject o) { + return o == null; // old acmp + } + + public boolean testNull05_2(MyObject o) { + return get(o) == null; // old acmp + } + + public boolean testNull05_3(MyObject o) { + return o == get((Object)null); // old acmp + } + + public boolean testNull05_4(MyObject o) { + return get(o) == get((Object)null); // old acmp + } + + public boolean testNull06_1(MyObject o) { + return null == o; // old acmp + } + + public boolean testNull06_2(MyObject o) { + return get((Object)null) == o; // old acmp + } + + public boolean testNull06_3(MyObject o) { + return null == get(o); // old acmp + } + + public boolean testNull06_4(MyObject o) { + return get((Object)null) == get(o); // old acmp + } + + public boolean testNull07_1(MyInterface u) { + return u == null; // old acmp + } + + public boolean testNull07_2(MyInterface u) { + return get(u) == null; // old acmp + } + + public boolean testNull07_3(MyInterface u) { + return u == get((Object)null); // old acmp + } + + public boolean testNull07_4(MyInterface u) { + return get(u) == get((Object)null); // old acmp + } + + public boolean testNull07_5(MyAbstract u) { + return u == null; // old acmp + } + + public boolean testNull07_6(MyAbstract u) { + return get(u) == null; // old acmp + } + + public boolean testNull07_7(MyAbstract u) { + return u == get((Object)null); // old acmp + } + + public boolean testNull07_8(MyAbstract u) { + return get(u) == get((Object)null); // old acmp + } + + public boolean testNull08_1(MyInterface u) { + return null == u; // old acmp + } + + public boolean testNull08_2(MyInterface u) { + return get((Object)null) == u; // old acmp + } + + public boolean testNull08_3(MyInterface u) { + return null == get(u); // old acmp + } + + public boolean testNull08_4(MyInterface u) { + return get((Object)null) == get(u); // old acmp + } + + public boolean testNull08_5(MyAbstract u) { + return null == u; // old acmp + } + + public boolean testNull08_6(MyAbstract u) { + return get((Object)null) == u; // old acmp + } + + public boolean testNull08_7(MyAbstract u) { + return null == get(u); // old acmp + } + + public boolean testNull08_8(MyAbstract u) { + return get((Object)null) == get(u); // old acmp + } + + // Same tests as above but negated + + public boolean testNotEq01_1(Object u1, Object u2) { + return get(u1) != u2; // new acmp + } + + public boolean testNotEq01_2(Object u1, Object u2) { + return u1 != get(u2); // new acmp + } + + public boolean testNotEq01_3(Object u1, Object u2) { + return get(u1) != get(u2); // new acmp + } + + @TrueIfNull + public boolean testNotEq01_4(Object u1, Object u2) { + return getNotNull(u1) != u2; // new acmp without null check + } + + @TrueIfNull + public boolean testNotEq01_5(Object u1, Object u2) { + return u1 != getNotNull(u2); // new acmp without null check + } + + @TrueIfNull + public boolean testNotEq01_6(Object u1, Object u2) { + return getNotNull(u1) != getNotNull(u2); // new acmp without null check + } + + public boolean testNotEq02_1(MyValue1 v1, MyValue1 v2) { + return get(v1) != (Object)v2; // only false if both null + } + + public boolean testNotEq02_2(MyValue1 v1, MyValue1 v2) { + return (Object)v1 != get(v2); // only false if both null + } + + public boolean testNotEq02_3(MyValue1 v1, MyValue1 v2) { + return get(v1) != get(v2); // only false if both null + } + + public boolean testNotEq03_1(MyValue1 v, Object u) { + return get(v) != u; // only false if both null + } + + public boolean testNotEq03_2(MyValue1 v, Object u) { + return (Object)v != get(u); // only false if both null + } + + public boolean testNotEq03_3(MyValue1 v, Object u) { + return get(v) != get(u); // only false if both null + } + + public boolean testNotEq04_1(Object u, MyValue1 v) { + return get(u) != (Object)v; // only false if both null + } + + public boolean testNotEq04_2(Object u, MyValue1 v) { + return u != get(v); // only false if both null + } + + public boolean testNotEq04_3(Object u, MyValue1 v) { + return get(u) != get(v); // only false if both null + } + + public boolean testNotEq05_1(MyObject o, MyValue1 v) { + return get(o) != (Object)v; // only false if both null + } + + public boolean testNotEq05_2(MyObject o, MyValue1 v) { + return o != get(v); // only false if both null + } + + public boolean testNotEq05_3(MyObject o, MyValue1 v) { + return get(o) != get(v); // only false if both null + } + + public boolean testNotEq06_1(MyValue1 v, MyObject o) { + return get(v) != o; // only false if both null + } + + public boolean testNotEq06_2(MyValue1 v, MyObject o) { + return (Object)v != get(o); // only false if both null + } + + public boolean testNotEq06_3(MyValue1 v, MyObject o) { + return get(v) != get(o); // only false if both null + } + + public boolean testNotEq07_1(MyValue1 v1, MyValue1 v2) { + return getNotNull(v1) != (Object)v2; // true + } + + public boolean testNotEq07_2(MyValue1 v1, MyValue1 v2) { + return (Object)v1 != getNotNull(v2); // true + } + + public boolean testNotEq07_3(MyValue1 v1, MyValue1 v2) { + return getNotNull(v1) != getNotNull(v2); // true + } + + public boolean testNotEq08_1(MyValue1 v, Object u) { + return getNotNull(v) != u; // true + } + + public boolean testNotEq08_2(MyValue1 v, Object u) { + return (Object)v != getNotNull(u); // true + } + + public boolean testNotEq08_3(MyValue1 v, Object u) { + return getNotNull(v) != getNotNull(u); // true + } + + public boolean testNotEq09_1(Object u, MyValue1 v) { + return getNotNull(u) != (Object)v; // true + } + + public boolean testNotEq09_2(Object u, MyValue1 v) { + return u != getNotNull(v); // true + } + + public boolean testNotEq09_3(Object u, MyValue1 v) { + return getNotNull(u) != getNotNull(v); // true + } + + public boolean testNotEq10_1(MyObject o, MyValue1 v) { + return getNotNull(o) != (Object)v; // true + } + + public boolean testNotEq10_2(MyObject o, MyValue1 v) { + return o != getNotNull(v); // true + } + + public boolean testNotEq10_3(MyObject o, MyValue1 v) { + return getNotNull(o) != getNotNull(v); // true + } + + public boolean testNotEq11_1(MyValue1 v, MyObject o) { + return getNotNull(v) != o; // true + } + + public boolean testNotEq11_2(MyValue1 v, MyObject o) { + return (Object)v != getNotNull(o); // true + } + + public boolean testNotEq11_3(MyValue1 v, MyObject o) { + return getNotNull(v) != getNotNull(o); // true + } + + public boolean testNotEq12_1(MyObject o1, MyObject o2) { + return get(o1) != o2; // old acmp + } + + public boolean testNotEq12_2(MyObject o1, MyObject o2) { + return o1 != get(o2); // old acmp + } + + public boolean testNotEq12_3(MyObject o1, MyObject o2) { + return get(o1) != get(o2); // old acmp + } + + public boolean testNotEq13_1(Object u, MyObject o) { + return get(u) != o; // old acmp + } + + public boolean testNotEq13_2(Object u, MyObject o) { + return u != get(o); // old acmp + } + + public boolean testNotEq13_3(Object u, MyObject o) { + return get(u) != get(o); // old acmp + } + + public boolean testNotEq14_1(MyObject o, Object u) { + return get(o) != u; // old acmp + } + + public boolean testNotEq14_2(MyObject o, Object u) { + return o != get(u); // old acmp + } + + public boolean testNotEq14_3(MyObject o, Object u) { + return get(o) != get(u); // old acmp + } + + public boolean testNotEq15_1(Object[] a, Object u) { + return get(a) != u; // old acmp + } + + public boolean testNotEq15_2(Object[] a, Object u) { + return a != get(u); // old acmp + } + + public boolean testNotEq15_3(Object[] a, Object u) { + return get(a) != get(u); // old acmp + } + + public boolean testNotEq16_1(Object u, Object[] a) { + return get(u) != a; // old acmp + } + + public boolean testNotEq16_2(Object u, Object[] a) { + return u != get(a); // old acmp + } + + public boolean testNotEq16_3(Object u, Object[] a) { + return get(u) != get(a); // old acmp + } + + public boolean testNotEq17_1(Object[] a, MyValue1 v) { + return get(a) != (Object)v; // only false if both null + } + + public boolean testNotEq17_2(Object[] a, MyValue1 v) { + return a != get(v); // only false if both null + } + + public boolean testNotEq17_3(Object[] a, MyValue1 v) { + return get(a) != get(v); // only false if both null + } + + public boolean testNotEq18_1(MyValue1 v, Object[] a) { + return get(v) != a; // only false if both null + } + + public boolean testNotEq18_2(MyValue1 v, Object[] a) { + return (Object)v != get(a); // only false if both null + } + + public boolean testNotEq18_3(MyValue1 v, Object[] a) { + return get(v) != get(a); // only false if both null + } + + public boolean testNotEq19_1(Object[] a, MyValue1 v) { + return getNotNull(a) != (Object)v; // true + } + + public boolean testNotEq19_2(Object[] a, MyValue1 v) { + return a != getNotNull(v); // true + } + + public boolean testNotEq19_3(Object[] a, MyValue1 v) { + return getNotNull(a) != getNotNull(v); // true + } + + public boolean testNotEq20_1(MyValue1 v, Object[] a) { + return getNotNull(v) != a; // true + } + + public boolean testNotEq20_2(MyValue1 v, Object[] a) { + return (Object)v != getNotNull(a); // true + } + + public boolean testNotEq20_3(MyValue1 v, Object[] a) { + return getNotNull(v) != getNotNull(a); // true + } + + public boolean testNotEq21_1(MyInterface u1, MyInterface u2) { + return get(u1) != u2; // new acmp + } + + public boolean testNotEq21_2(MyInterface u1, MyInterface u2) { + return u1 != get(u2); // new acmp + } + + public boolean testNotEq21_3(MyInterface u1, MyInterface u2) { + return get(u1) != get(u2); // new acmp + } + + @TrueIfNull + public boolean testNotEq21_4(MyInterface u1, MyInterface u2) { + return getNotNull(u1) != u2; // new acmp without null check + } + + @TrueIfNull + public boolean testNotEq21_5(MyInterface u1, MyInterface u2) { + return u1 != getNotNull(u2); // new acmp without null check + } + + @TrueIfNull + public boolean testNotEq21_6(MyInterface u1, MyInterface u2) { + return getNotNull(u1) != getNotNull(u2); // new acmp without null check + } + + public boolean testNotEq21_7(MyAbstract u1, MyAbstract u2) { + return get(u1) != u2; // new acmp + } + + public boolean testNotEq21_8(MyAbstract u1, MyAbstract u2) { + return u1 != get(u2); // new acmp + } + + public boolean testNotEq21_9(MyAbstract u1, MyAbstract u2) { + return get(u1) != get(u2); // new acmp + } + + @TrueIfNull + public boolean testNotEq21_10(MyAbstract u1, MyAbstract u2) { + return getNotNull(u1) != u2; // new acmp without null check + } + + @TrueIfNull + public boolean testNotEq21_11(MyAbstract u1, MyAbstract u2) { + return u1 != getNotNull(u2); // new acmp without null check + } + + @TrueIfNull + public boolean testNotEq21_12(MyAbstract u1, MyAbstract u2) { + return getNotNull(u1) != getNotNull(u2); // new acmp without null check + } + + public boolean testNotEq22_1(MyValue1 v, MyInterface u) { + return get(v) != u; // only false if both null + } + + public boolean testNotEq22_2(MyValue1 v, MyInterface u) { + return (Object)v != get(u); // only false if both null + } + + public boolean testNotEq22_3(MyValue1 v, MyInterface u) { + return get(v) != get(u); // only false if both null + } + + public boolean testNotEq22_4(MyValue1 v, MyAbstract u) { + return get(v) != u; // only false if both null + } + + public boolean testNotEq22_5(MyValue1 v, MyAbstract u) { + return (Object)v != get(u); // only false if both null + } + + public boolean testNotEq22_6(MyValue1 v, MyAbstract u) { + return get(v) != get(u); // only false if both null + } + + public boolean testNotEq23_1(MyInterface u, MyValue1 v) { + return get(u) != (Object)v; // only false if both null + } + + public boolean testNotEq23_2(MyInterface u, MyValue1 v) { + return u != get(v); // only false if both null + } + + public boolean testNotEq23_3(MyInterface u, MyValue1 v) { + return get(u) != get(v); // only false if both null + } + + public boolean testNotEq23_4(MyAbstract u, MyValue1 v) { + return get(u) != (Object)v; // only false if both null + } + + public boolean testNotEq23_5(MyAbstract u, MyValue1 v) { + return u != get(v); // only false if both null + } + + public boolean testNotEq23_6(MyAbstract u, MyValue1 v) { + return get(u) != get(v); // only false if both null + } + + public boolean testNotEq24_1(MyValue1 v, MyInterface u) { + return getNotNull(v) != u; // true + } + + public boolean testNotEq24_2(MyValue1 v, MyInterface u) { + return (Object)v != getNotNull(u); // true + } + + public boolean testNotEq24_3(MyValue1 v, MyInterface u) { + return getNotNull(v) != getNotNull(u); // true + } + + public boolean testNotEq24_4(MyValue1 v, MyAbstract u) { + return getNotNull(v) != u; // true + } + + public boolean testNotEq24_5(MyValue1 v, MyAbstract u) { + return (Object)v != getNotNull(u); // true + } + + public boolean testNotEq24_6(MyValue1 v, MyAbstract u) { + return getNotNull(v) != getNotNull(u); // true + } + + public boolean testNotEq25_1(MyInterface u, MyValue1 v) { + return getNotNull(u) != (Object)v; // true + } + + public boolean testNotEq25_2(MyInterface u, MyValue1 v) { + return u != getNotNull(v); // true + } + + public boolean testNotEq25_3(MyInterface u, MyValue1 v) { + return getNotNull(u) != getNotNull(v); // true + } + + public boolean testNotEq25_4(MyAbstract u, MyValue1 v) { + return getNotNull(u) != (Object)v; // true + } + + public boolean testNotEq25_5(MyAbstract u, MyValue1 v) { + return u != getNotNull(v); // true + } + + public boolean testNotEq25_6(MyAbstract u, MyValue1 v) { + return getNotNull(u) != getNotNull(v); // true + } + + public boolean testNotEq26_1(MyInterface u, MyObject o) { + return get(u) != o; // old acmp + } + + public boolean testNotEq26_2(MyInterface u, MyObject o) { + return u != get(o); // old acmp + } + + public boolean testNotEq26_3(MyInterface u, MyObject o) { + return get(u) != get(o); // old acmp + } + + public boolean testNotEq26_4(MyAbstract u, MyObject o) { + return get(u) != o; // old acmp + } + + public boolean testNotEq26_5(MyAbstract u, MyObject o) { + return u != get(o); // old acmp + } + + public boolean testNotEq26_6(MyAbstract u, MyObject o) { + return get(u) != get(o); // old acmp + } + + public boolean testNotEq27_1(MyObject o, MyInterface u) { + return get(o) != u; // old acmp + } + + public boolean testNotEq27_2(MyObject o, MyInterface u) { + return o != get(u); // old acmp + } + + public boolean testNotEq27_3(MyObject o, MyInterface u) { + return get(o) != get(u); // old acmp + } + + public boolean testNotEq27_4(MyObject o, MyAbstract u) { + return get(o) != u; // old acmp + } + + public boolean testNotEq27_5(MyObject o, MyAbstract u) { + return o != get(u); // old acmp + } + + public boolean testNotEq27_6(MyObject o, MyAbstract u) { + return get(o) != get(u); // old acmp + } + + public boolean testNotEq28_1(MyInterface[] a, MyInterface u) { + return get(a) != u; // old acmp + } + + public boolean testNotEq28_2(MyInterface[] a, MyInterface u) { + return a != get(u); // old acmp + } + + public boolean testNotEq28_3(MyInterface[] a, MyInterface u) { + return get(a) != get(u); // old acmp + } + + public boolean testNotEq28_4(MyAbstract[] a, MyAbstract u) { + return get(a) != u; // old acmp + } + + public boolean testNotEq28_5(MyAbstract[] a, MyAbstract u) { + return a != get(u); // old acmp + } + + public boolean testNotEq28_6(MyAbstract[] a, MyAbstract u) { + return get(a) != get(u); // old acmp + } + + public boolean testNotEq29_1(MyInterface u, MyInterface[] a) { + return get(u) != a; // old acmp + } + + public boolean testNotEq29_2(MyInterface u, MyInterface[] a) { + return u != get(a); // old acmp + } + + public boolean testNotEq29_3(MyInterface u, MyInterface[] a) { + return get(u) != get(a); // old acmp + } + + public boolean testNotEq29_4(MyAbstract u, MyAbstract[] a) { + return get(u) != a; // old acmp + } + + public boolean testNotEq29_5(MyAbstract u, MyAbstract[] a) { + return u != get(a); // old acmp + } + + public boolean testNotEq29_6(MyAbstract u, MyAbstract[] a) { + return get(u) != get(a); // old acmp + } + + public boolean testNotEq30_1(MyInterface[] a, MyValue1 v) { + return get(a) != (Object)v; // only false if both null + } + + public boolean testNotEq30_2(MyInterface[] a, MyValue1 v) { + return a != get(v); // only false if both null + } + + public boolean testNotEq30_3(MyInterface[] a, MyValue1 v) { + return get(a) != get(v); // only false if both null + } + + public boolean testNotEq30_4(MyAbstract[] a, MyValue1 v) { + return get(a) != (Object)v; // only false if both null + } + + public boolean testNotEq30_5(MyAbstract[] a, MyValue1 v) { + return a != get(v); // only false if both null + } + + public boolean testNotEq30_6(MyAbstract[] a, MyValue1 v) { + return get(a) != get(v); // only false if both null + } + + public boolean testNotEq31_1(MyValue1 v, MyInterface[] a) { + return get(v) != a; // only false if both null + } + + public boolean testNotEq31_2(MyValue1 v, MyInterface[] a) { + return (Object)v != get(a); // only false if both null + } + + public boolean testNotEq31_3(MyValue1 v, MyInterface[] a) { + return get(v) != get(a); // only false if both null + } + + public boolean testNotEq31_4(MyValue1 v, MyAbstract[] a) { + return get(v) != a; // only false if both null + } + + public boolean testNotEq31_5(MyValue1 v, MyAbstract[] a) { + return (Object)v != get(a); // only false if both null + } + + public boolean testNotEq31_6(MyValue1 v, MyAbstract[] a) { + return get(v) != get(a); // only false if both null + } + + public boolean testNotEq32_1(MyInterface[] a, MyValue1 v) { + return getNotNull(a) != (Object)v; // true + } + + public boolean testNotEq32_2(MyInterface[] a, MyValue1 v) { + return a != getNotNull(v); // true + } + + public boolean testNotEq32_3(MyInterface[] a, MyValue1 v) { + return getNotNull(a) != getNotNull(v); // true + } + + public boolean testNotEq32_4(MyAbstract[] a, MyValue1 v) { + return getNotNull(a) != (Object)v; // true + } + + public boolean testNotEq32_5(MyAbstract[] a, MyValue1 v) { + return a != getNotNull(v); // true + } + + public boolean testNotEq32_6(MyAbstract[] a, MyValue1 v) { + return getNotNull(a) != getNotNull(v); // true + } + + public boolean testNotEq33_1(MyValue1 v, MyInterface[] a) { + return getNotNull(v) != a; // true + } + + public boolean testNotEq33_2(MyValue1 v, MyInterface[] a) { + return (Object)v != getNotNull(a); // true + } + + public boolean testNotEq33_3(MyValue1 v, MyInterface[] a) { + return getNotNull(v) != getNotNull(a); // true + } + + public boolean testNotEq33_4(MyValue1 v, MyAbstract[] a) { + return getNotNull(v) != a; // true + } + + public boolean testNotEq33_5(MyValue1 v, MyAbstract[] a) { + return (Object)v != getNotNull(a); // true + } + + public boolean testNotEq33_6(MyValue1 v, MyAbstract[] a) { + return getNotNull(v) != getNotNull(a); // true + } + + // Null tests + + public boolean testNotNull01_1(MyValue1 v) { + return (Object)v != null; // old acmp + } + + public boolean testNotNull01_2(MyValue1 v) { + return get(v) != null; // old acmp + } + + public boolean testNotNull01_3(MyValue1 v) { + return (Object)v != get((Object)null); // old acmp + } + + public boolean testNotNull01_4(MyValue1 v) { + return get(v) != get((Object)null); // old acmp + } + + public boolean testNotNull02_1(MyValue1 v) { + return null != (Object)v; // old acmp + } + + public boolean testNotNull02_2(MyValue1 v) { + return get((Object)null) != (Object)v; // old acmp + } + + public boolean testNotNull02_3(MyValue1 v) { + return null != get(v); // old acmp + } + + public boolean testNotNull02_4(MyValue1 v) { + return get((Object)null) != get(v); // old acmp + } + + public boolean testNotNull03_1(Object u) { + return u != null; // old acmp + } + + public boolean testNotNull03_2(Object u) { + return get(u) != null; // old acmp + } + + public boolean testNotNull03_3(Object u) { + return u != get((Object)null); // old acmp + } + + public boolean testNotNull03_4(Object u) { + return get(u) != get((Object)null); // old acmp + } + + public boolean testNotNull04_1(Object u) { + return null != u; // old acmp + } + + public boolean testNotNull04_2(Object u) { + return get((Object)null) != u; // old acmp + } + + public boolean testNotNull04_3(Object u) { + return null != get(u); // old acmp + } + + public boolean testNotNull04_4(Object u) { + return get((Object)null) != get(u); // old acmp + } + + public boolean testNotNull05_1(MyObject o) { + return o != null; // old acmp + } + + public boolean testNotNull05_2(MyObject o) { + return get(o) != null; // old acmp + } + + public boolean testNotNull05_3(MyObject o) { + return o != get((Object)null); // old acmp + } + + public boolean testNotNull05_4(MyObject o) { + return get(o) != get((Object)null); // old acmp + } + + public boolean testNotNull06_1(MyObject o) { + return null != o; // old acmp + } + + public boolean testNotNull06_2(MyObject o) { + return get((Object)null) != o; // old acmp + } + + public boolean testNotNull06_3(MyObject o) { + return null != get(o); // old acmp + } + + public boolean testNotNull06_4(MyObject o) { + return get((Object)null) != get(o); // old acmp + } + + public boolean testNotNull07_1(MyInterface u) { + return u != null; // old acmp + } + + public boolean testNotNull07_2(MyInterface u) { + return get(u) != null; // old acmp + } + + public boolean testNotNull07_3(MyInterface u) { + return u != get((Object)null); // old acmp + } + + public boolean testNotNull07_4(MyInterface u) { + return get(u) != get((Object)null); // old acmp + } + + public boolean testNotNull07_5(MyAbstract u) { + return u != null; // old acmp + } + + public boolean testNotNull07_6(MyAbstract u) { + return get(u) != null; // old acmp + } + + public boolean testNotNull07_7(MyAbstract u) { + return u != get((Object)null); // old acmp + } + + public boolean testNotNull07_8(MyAbstract u) { + return get(u) != get((Object)null); // old acmp + } + + public boolean testNotNull08_1(MyInterface u) { + return null != u; // old acmp + } + + public boolean testNotNull08_2(MyInterface u) { + return get((Object)null) != u; // old acmp + } + + public boolean testNotNull08_3(MyInterface u) { + return null != get(u); // old acmp + } + + public boolean testNotNull08_4(MyInterface u) { + return get((Object)null) != get(u); // old acmp + } + + public boolean testNotNull08_5(MyAbstract u) { + return null != u; // old acmp + } + + public boolean testNotNull08_6(MyAbstract u) { + return get((Object)null) != u; // old acmp + } + + public boolean testNotNull08_7(MyAbstract u) { + return null != get(u); // old acmp + } + + public boolean testNotNull08_8(MyAbstract u) { + return get((Object)null) != get(u); // old acmp + } + + // The following methods are used with -XX:+AlwaysIncrementalInline to hide exact types during parsing + + public Object get(Object u) { + return u; + } + + public Object getNotNull(Object u) { + return (u != null) ? u : new Object(); + } + + public Object get(MyValue1 v) { + return v; + } + + public Object getNotNull(MyValue1 v) { + return ((Object)v != null) ? v : MyValue1.createDefault(); + } + + public Object get(MyObject o) { + return o; + } + + public Object getNotNull(MyObject o) { + return (o != null) ? o : MyValue1.createDefault(); + } + + public Object get(Object[] a) { + return a; + } + + public Object getNotNull(Object[] a) { + return (a != null) ? a : new Object[1]; + } + + public boolean trueIfNull(Method m) { + return m.isAnnotationPresent(TrueIfNull.class); + } + + public boolean falseIfNull(Method m) { + return m.isAnnotationPresent(FalseIfNull.class); + } + + public boolean isNegated(Method m) { + return m.getName().startsWith("testNot"); + } + + // Tests with profiling + public boolean cmpAlwaysEqual1(Object a, Object b) { + return a == b; + } + + public boolean cmpAlwaysEqual2(Object a, Object b) { + return a != b; + } + + public boolean cmpAlwaysEqual3(Object a) { + return a == a; + } + + public boolean cmpAlwaysEqual4(Object a) { + return a != a; + } + + public boolean cmpAlwaysUnEqual1(Object a, Object b) { + return a == b; + } + + public boolean cmpAlwaysUnEqual2(Object a, Object b) { + return a != b; + } + + public boolean cmpAlwaysUnEqual3(Object a) { + return a == a; + } + + public boolean cmpAlwaysUnEqual4(Object a) { + return a != a; + } + + public boolean cmpSometimesEqual1(Object a) { + return a == a; + } + + public boolean cmpSometimesEqual2(Object a) { + return a != a; + } + + static int get_full_opt_level() { + int n = (int)TieredStopAtLevel; + if (n >= 4) { + n = 4; + } + return n; + } + protected static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + protected static final long TieredStopAtLevel = (Long)WHITE_BOX.getVMFlag("TieredStopAtLevel"); + protected static final int COMP_LEVEL_FULL_OPTIMIZATION = get_full_opt_level(); + + public void runTest(Method m, Object[] args, int warmup, int nullMode, boolean[][] equalities) throws Exception { + Class[] parameterTypes = m.getParameterTypes(); + int parameterCount = parameterTypes.length; + // Nullness mode for first argument + // 0: default, 1: never null, 2: always null + int start = (nullMode != 1) ? 0 : 1; + int end = (nullMode != 2) ? args.length : 1; + for (int i = start; i < end; ++i) { + if (args[i] != null && !parameterTypes[0].isInstance(args[i])) { + continue; + } + if (args[i] == null && parameterTypes[0] == MyValue1.class) { + continue; + } + if (parameterCount == 1) { + // Null checks + System.out.print("Testing " + m.getName() + "(" + args[i] + ")"); + // Avoid acmp in the computation of the expected result! + boolean expected = isNegated(m) ? (i != 0) : (i == 0); + for (int run = 0; run < warmup; ++run) { + Boolean result = (Boolean)m.invoke(this, args[i]); + if (result != expected && WHITE_BOX.isMethodCompiled(m, false)) { + System.out.println(" = " + result); + throw new RuntimeException("Test failed: should return " + expected); + } + } + System.out.println(" = " + expected); + } else { + // Equality checks + for (int j = 0; j < args.length; ++j) { + if (args[j] != null && !parameterTypes[1].isInstance(args[j])) { + continue; + } + if (args[j] == null && parameterTypes[1] == MyValue1.class) { + continue; + } + System.out.print("Testing " + m.getName() + "(" + args[i] + ", " + args[j] + ")"); + // Avoid acmp in the computation of the expected result! + boolean equal = equalities[i][j]; + equal = isNegated(m) ? !equal : equal; + boolean expected = ((i == 0 || j == 0) && trueIfNull(m)) || (equal && !(i == 0 && falseIfNull(m))); + for (int run = 0; run < warmup; ++run) { + Boolean result = (Boolean)m.invoke(this, args[i], args[j]); + if (result != expected && WHITE_BOX.isMethodCompiled(m, false) && warmup == 1) { + System.out.println(" = " + result); + throw new RuntimeException("Test failed: should return " + expected); + } + } + System.out.println(" = " + expected); + } + } + } + } + + public void run(int nullMode) throws Exception { + // Prepare test arguments + Object[] args = { null, + new Object(), + new MyObject(), + MyValue1.setX(MyValue1.createDefault(), 42), + new Object[10], + new MyObject[10], + MyValue1.setX(MyValue1.createDefault(), 0x42), + MyValue1.setX(MyValue1.createDefault(), 42), + MyValue2.setX(MyValue2.createDefault(), 42), }; + + boolean[][] equalities = { { true, false, false, false, false, false, false, false, false }, + { false, true, false, false, false, false, false, false, false }, + { false, false, true, false, false, false, false, false, false }, + { false, false, false, true, false, false, false, true, false }, + { false, false, false, false, true, false, false, false, false }, + { false, false, false, false, false, true, false, false, false }, + { false, false, false, false, false, false, true, false, false }, + { false, false, false, true, false, false, false, true, false }, + { false, false, false, false, false, false, false, false, true } }; + + // Run tests + for (Method m : getClass().getMethods()) { + if (m.getName().startsWith("test")) { + // Do some warmup runs + runTest(m, args, 1000, nullMode, equalities); + // Make sure method is compiled + TestFramework.compile(m, CompLevel.ANY); + Asserts.assertTrue(WHITE_BOX.isMethodCompiled(m, false), m + " not compiled"); + // Run again to verify correctness of compiled code + runTest(m, args, 1, nullMode, equalities); + } + } + + Method cmpAlwaysUnEqual3_m = getClass().getMethod("cmpAlwaysUnEqual3", Object.class); + Method cmpAlwaysUnEqual4_m = getClass().getMethod("cmpAlwaysUnEqual4", Object.class); + Method cmpSometimesEqual1_m = getClass().getMethod("cmpSometimesEqual1", Object.class); + Method cmpSometimesEqual2_m = getClass().getMethod("cmpSometimesEqual2", Object.class); + + for (int i = 0; i < 20_000; ++i) { + Asserts.assertTrue(cmpAlwaysEqual1(args[1], args[1])); + Asserts.assertFalse(cmpAlwaysEqual2(args[1], args[1])); + Asserts.assertTrue(cmpAlwaysEqual3(args[1])); + Asserts.assertFalse(cmpAlwaysEqual4(args[1])); + + Asserts.assertFalse(cmpAlwaysUnEqual1(args[1], args[2])); + Asserts.assertTrue(cmpAlwaysUnEqual2(args[1], args[2])); + boolean res = cmpAlwaysUnEqual3(args[3]); + Asserts.assertTrue(res); + res = cmpAlwaysUnEqual4(args[3]); + Asserts.assertFalse(res); + + int idx = i % args.length; + res = cmpSometimesEqual1(args[idx]); + Asserts.assertTrue(res); + res = cmpSometimesEqual2(args[idx]); + Asserts.assertFalse(res); + } + } + + public static void main(String[] args) throws Exception { + if (args.length == 0) { + enumerateVMOptions(); + } else { + int nullMode = Integer.valueOf(args[0]); + TestNewAcmp t = new TestNewAcmp(); + t.run(nullMode); + } + } + + private static String[] addOptions(String prefix[], String... extra) { + ArrayList list = new ArrayList(); + if (prefix != null) { + for (String s : prefix) { + list.add(s); + } + } + if (extra != null) { + for (String s : extra) { + System.out.println(" " + s); + list.add(s); + } + } + + return list.toArray(new String[list.size()]); + } + + private static void enumerateVMOptions() throws Exception { + String[] baseOptions = { + "--enable-preview", + "-Xbootclasspath/a:.", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+WhiteBoxAPI", + "-Xbatch", + "-XX:TypeProfileLevel=222", + "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED", + "-XX:CompileCommand=dontinline,compiler.valhalla.inlinetypes.TestNewAcmp::test*", + "-XX:CompileCommand=dontinline,compiler.valhalla.inlinetypes.TestNewAcmp::cmp*"}; + + String SCENARIOS = System.getProperty("Scenarios", ""); + List scenarios = null; + if (!SCENARIOS.isEmpty()) { + scenarios = Arrays.asList(SCENARIOS.split(",")); + } + + int scenario = -1; + for (int nullMode = 0; nullMode <= 2; nullMode++) { // null mode + for (int incrInline = 0; incrInline < 2; incrInline++) { // 0 = default, 1 = -XX:+AlwaysIncrementalInline + scenario++; + System.out.println("Scenario #" + scenario + " -------------------"); + String[] cmds = baseOptions; + if (incrInline != 0) { + cmds = addOptions(cmds, "-XX:+IgnoreUnrecognizedVMOptions", "-XX:+AlwaysIncrementalInline"); + } + + cmds = addOptions(cmds, "compiler.valhalla.inlinetypes.TestNewAcmp"); + cmds = addOptions(cmds, Integer.toString(nullMode)); + + if (scenarios != null && !scenarios.contains(Integer.toString(scenario))) { + System.out.println("Scenario #" + scenario + " is skipped due to -Dscenarios=" + SCENARIOS); + continue; + } + + OutputAnalyzer oa = ProcessTools.executeTestJava(cmds); + String output = oa.getOutput(); + oa.shouldHaveExitValue(0); + System.out.println(output); + } + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestNullableArrays.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestNullableArrays.java new file mode 100644 index 00000000000..eaf8fa197e6 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestNullableArrays.java @@ -0,0 +1,3351 @@ +/* + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import jdk.test.lib.Asserts; +import compiler.lib.ir_framework.*; + +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.ALLOC_ARRAY_OF_MYVALUE_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.ALLOC_OF_MYVALUE_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.LOAD_OF_ANY_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.LOAD_UNKNOWN_INLINE; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.STORE_OF_ANY_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.STORE_UNKNOWN_INLINE; +import static compiler.valhalla.inlinetypes.InlineTypes.rI; +import static compiler.valhalla.inlinetypes.InlineTypes.rL; +import static compiler.valhalla.inlinetypes.InlineTypes.rD; + +import static compiler.lib.ir_framework.IRNode.ALLOC; +import static compiler.lib.ir_framework.IRNode.LOOP; +import static compiler.lib.ir_framework.IRNode.PREDICATE_TRAP; +import static compiler.lib.ir_framework.IRNode.UNSTABLE_IF_TRAP; + +import jdk.internal.value.ValueClass; + +import java.lang.reflect.Method; +import java.util.Arrays; + +/* + * @test + * @key randomness + * @summary Test nullable value class arrays. + * @library /test/lib / + * @requires (os.simpleArch == "x64" | os.simpleArch == "aarch64") + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm/timeout=600 compiler.valhalla.inlinetypes.TestNullableArrays + */ + +@ForceCompileClassInitializer +public class TestNullableArrays { + + public static void main(String[] args) { + + Scenario[] scenarios = InlineTypes.DEFAULT_SCENARIOS; + scenarios[2].addFlags("-XX:-MonomorphicArrayCheck", "-XX:-UncommonNullCast", "-XX:+StressArrayCopyMacroNode"); + scenarios[3].addFlags("-XX:-MonomorphicArrayCheck", "-XX:-UncommonNullCast"); + scenarios[4].addFlags("-XX:-MonomorphicArrayCheck", "-XX:-UncommonNullCast"); + scenarios[5].addFlags("-XX:-MonomorphicArrayCheck", "-XX:-UncommonNullCast", "-XX:+StressArrayCopyMacroNode"); + + InlineTypes.getFramework() + .addScenarios(scenarios) + .addHelperClasses(MyValue1.class, + MyValue2.class, + MyValue2Inline.class) + .start(); + } + + static { + // Make sure RuntimeException is loaded to prevent uncommon traps in IR verified tests + RuntimeException tmp = new RuntimeException("42"); + } + + // Helper methods + + protected long hash() { + return hash(rI, rL); + } + + protected long hash(int x, long y) { + return MyValue1.createWithFieldsInline(x, y).hash(); + } + + private static final MyValue1 testValue1 = MyValue1.createWithFieldsInline(rI, rL); + + // Test nullable value class array creation and initialization + @Test + @IR(applyIf = {"UseArrayFlattening", "true"}, + counts = {ALLOC_ARRAY_OF_MYVALUE_KLASS, "= 1"}) + @IR(applyIf = {"UseArrayFlattening", "false"}, + counts = {ALLOC_ARRAY_OF_MYVALUE_KLASS, "= 1"}, + failOn = {LOAD_OF_ANY_KLASS}) + public MyValue1[] test1(int len) { + MyValue1[] va = new MyValue1[len]; + if (len > 0) { + va[0] = null; + } + for (int i = 1; i < len; ++i) { + va[i] = MyValue1.createWithFieldsDontInline(rI, rL); + } + return va; + } + + @Run(test = "test1") + public void test1_verifier() { + int len = Math.abs(rI % 10); + MyValue1[] va = test1(len); + if (len > 0) { + Asserts.assertEQ(va[0], null); + } + for (int i = 1; i < len; ++i) { + Asserts.assertEQ(va[i].hash(), hash()); + } + } + + // Test creation of a value class array and element access + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, ALLOC_ARRAY_OF_MYVALUE_KLASS, LOOP, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test2() { + MyValue1[] va = new MyValue1[1]; + va[0] = MyValue1.createWithFieldsInline(rI, rL); + return va[0].hash(); + } + + @Run(test = "test2") + public void test2_verifier() { + long result = test2(); + Asserts.assertEQ(result, hash()); + } + + // Test receiving a value class array from the interpreter, + // updating its elements in a loop and computing a hash. + @Test + @IR(failOn = {ALLOC_ARRAY_OF_MYVALUE_KLASS}) + public long test3(MyValue1[] va) { + long result = 0; + for (int i = 0; i < 10; ++i) { + if (va[i] != null) { + result += va[i].hash(); + } + va[i] = MyValue1.createWithFieldsInline(rI + 1, rL + 1); + } + va[0] = null; + return result; + } + + @Run(test = "test3") + public void test3_verifier() { + MyValue1[] va = new MyValue1[10]; + long expected = 0; + for (int i = 1; i < 10; ++i) { + va[i] = MyValue1.createWithFieldsDontInline(rI + i, rL + i); + expected += va[i].hash(); + } + long result = test3(va); + Asserts.assertEQ(expected, result); + Asserts.assertEQ(va[0], null); + for (int i = 1; i < 10; ++i) { + if (va[i].hash() != hash(rI + 1, rL + 1)) { + Asserts.assertEQ(va[i].hash(), hash(rI + 1, rL + 1)); + } + } + } + + // Test returning a value class array received from the interpreter + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, ALLOC_ARRAY_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, LOOP, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public MyValue1[] test4(MyValue1[] va) { + return va; + } + + @Run(test = "test4") + public void test4_verifier() { + MyValue1[] va = new MyValue1[10]; + for (int i = 0; i < 10; ++i) { + va[i] = MyValue1.createWithFieldsDontInline(rI + i, rL + i); + } + va = test4(va); + for (int i = 0; i < 10; ++i) { + Asserts.assertEQ(va[i].hash(), hash(rI + i, rL + i)); + } + } + + // Merge value class arrays created from two branches + @Test + public MyValue1[] test5(boolean b) { + MyValue1[] va; + if (b) { + va = new MyValue1[5]; + for (int i = 0; i < 5; ++i) { + va[i] = MyValue1.createWithFieldsInline(rI, rL); + } + va[4] = null; + } else { + va = new MyValue1[10]; + for (int i = 0; i < 10; ++i) { + va[i] = MyValue1.createWithFieldsInline(rI + i, rL + i); + } + va[9] = null; + } + long sum = va[0].hashInterpreted(); + if (b) { + va[0] = MyValue1.createWithFieldsDontInline(rI, sum); + } else { + va[0] = MyValue1.createWithFieldsDontInline(rI + 1, sum + 1); + } + return va; + } + + @Run(test = "test5") + public void test5_verifier() { + MyValue1[] va = test5(true); + Asserts.assertEQ(va.length, 5); + Asserts.assertEQ(va[0].hash(), hash(rI, hash())); + for (int i = 1; i < 4; ++i) { + Asserts.assertEQ(va[i].hash(), hash()); + } + Asserts.assertEQ(va[4], null); + va = test5(false); + Asserts.assertEQ(va.length, 10); + Asserts.assertEQ(va[0].hash(), hash(rI + 1, hash(rI, rL) + 1)); + for (int i = 1; i < 9; ++i) { + Asserts.assertEQ(va[i].hash(), hash(rI + i, rL + i)); + } + Asserts.assertEQ(va[9], null); + } + + // Test creation of value class array with single element + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, ALLOC_ARRAY_OF_MYVALUE_KLASS, LOOP, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public MyValue1 test6() { + MyValue1[] va = new MyValue1[1]; + return va[0]; + } + + @Run(test = "test6") + public void test6_verifier() { + MyValue1[] va = new MyValue1[1]; + MyValue1 v = test6(); + Asserts.assertEQ(v, null); + } + + // Test initialization of value class arrays + @Test + @IR(failOn = {LOAD_OF_ANY_KLASS}) + public MyValue1[] test7(int len) { + return new MyValue1[len]; + } + + @Run(test = "test7") + public void test7_verifier() { + int len = Math.abs(rI % 10); + MyValue1[] va = test7(len); + for (int i = 0; i < len; ++i) { + Asserts.assertEQ(va[i], null); + va[i] = null; + } + } + + // Test creation of value class array with zero length + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, LOOP, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public MyValue1[] test8() { + return new MyValue1[0]; + } + + @Run(test = "test8") + public void test8_verifier() { + MyValue1[] va = test8(); + Asserts.assertEQ(va.length, 0); + } + + static MyValue1[] test9_va; + + // Test that value class array loaded from field has correct type + @Test + @IR(failOn = LOOP) + public long test9() { + return test9_va[0].hash(); + } + + @Run(test = "test9") + public void test9_verifier() { + test9_va = new MyValue1[1]; + test9_va[0] = testValue1; + long result = test9(); + Asserts.assertEQ(result, hash()); + } + + // Multi-dimensional arrays + @Test + public MyValue1[][][] test10(int len1, int len2, int len3) { + MyValue1[][][] arr = new MyValue1[len1][len2][len3]; + for (int i = 0; i < len1; i++) { + for (int j = 0; j < len2; j++) { + for (int k = 0; k < len3; k++) { + arr[i][j][k] = MyValue1.createWithFieldsDontInline(rI + i , rL + j + k); + if (k == 0) { + arr[i][j][k] = null; + } + } + } + } + return arr; + } + + @Run(test = "test10") + public void test10_verifier() { + MyValue1[][][] arr = test10(2, 3, 4); + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 3; j++) { + for (int k = 0; k < 4; k++) { + if (k == 0) { + Asserts.assertEQ(arr[i][j][k], null); + } else { + Asserts.assertEQ(arr[i][j][k].hash(), MyValue1.createWithFieldsDontInline(rI + i , rL + j + k).hash()); + } + arr[i][j][k] = null; + } + } + } + } + + @Test + public void test11(MyValue1[][][] arr, long[] res) { + int l = 0; + for (int i = 0; i < arr.length; i++) { + for (int j = 0; j < arr[i].length; j++) { + for (int k = 0; k < arr[i][j].length; k++) { + if (arr[i][j][k] != null) { + res[l] = arr[i][j][k].hash(); + } + arr[i][j][k] = null; + l++; + } + } + } + } + + @Run(test = "test11") + public void test11_verifier() { + MyValue1[][][] arr = new MyValue1[2][3][4]; + long[] res = new long[2*3*4]; + long[] verif = new long[2*3*4]; + int l = 0; + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 3; j++) { + for (int k = 0; k < 4; k++) { + if (j != 2) { + arr[i][j][k] = MyValue1.createWithFieldsDontInline(rI + i, rL + j + k); + verif[l] = arr[i][j][k].hash(); + } + l++; + } + } + } + test11(arr, res); + for (int i = 0; i < verif.length; i++) { + Asserts.assertEQ(res[i], verif[i]); + } + } + + // Array load out of bounds (upper bound) at compile time + @Test + public int test12() { + int arraySize = Math.abs(rI) % 10; + MyValue1[] va = new MyValue1[arraySize]; + + for (int i = 0; i < arraySize; i++) { + va[i] = MyValue1.createWithFieldsDontInline(rI + 1, rL); + } + + try { + return va[arraySize + 1].x; + } catch (ArrayIndexOutOfBoundsException e) { + return rI; + } + } + + @Run(test = "test12") + public void test12_verifier() { + Asserts.assertEQ(test12(), rI); + } + + // Array load out of bounds (lower bound) at compile time + @Test + public int test13() { + int arraySize = Math.abs(rI) % 10; + MyValue1[] va = new MyValue1[arraySize]; + + for (int i = 0; i < arraySize; i++) { + va[i] = MyValue1.createWithFieldsDontInline(rI + i, rL); + } + + try { + return va[-arraySize].x; + } catch (ArrayIndexOutOfBoundsException e) { + return rI; + } + } + + @Run(test = "test13") + public void test13_verifier() { + Asserts.assertEQ(test13(), rI); + } + + // Array load out of bound not known to compiler (both lower and upper bound) + @Test + public int test14(MyValue1[] va, int index) { + return va[index].x; + } + + @Run(test = "test14") + public void test14_verifier() { + int arraySize = Math.abs(rI) % 10; + MyValue1[] va = new MyValue1[arraySize]; + + for (int i = 0; i < arraySize; i++) { + va[i] = MyValue1.createWithFieldsDontInline(rI, rL); + } + + int result; + for (int i = -20; i < 20; i++) { + try { + result = test14(va, i); + } catch (ArrayIndexOutOfBoundsException e) { + result = rI; + } + Asserts.assertEQ(result, rI); + } + } + + // Array store out of bounds (upper bound) at compile time + @Test + public int test15() { + int arraySize = Math.abs(rI) % 10; + MyValue1[] va = new MyValue1[arraySize]; + + try { + for (int i = 0; i <= arraySize; i++) { + va[i] = MyValue1.createWithFieldsDontInline(rI + 1, rL); + } + return rI - 1; + } catch (ArrayIndexOutOfBoundsException e) { + return rI; + } + } + + @Run(test = "test15") + public void test15_verifier() { + Asserts.assertEQ(test15(), rI); + } + + // Array store out of bounds (lower bound) at compile time + @Test + public int test16() { + int arraySize = Math.abs(rI) % 10; + MyValue1[] va = new MyValue1[arraySize]; + + try { + for (int i = -1; i <= arraySize; i++) { + va[i] = MyValue1.createWithFieldsDontInline(rI + 1, rL); + } + return rI - 1; + } catch (ArrayIndexOutOfBoundsException e) { + return rI; + } + } + + @Run(test = "test16") + public void test16_verifier() { + Asserts.assertEQ(test16(), rI); + } + + // Array store out of bound not known to compiler (both lower and upper bound) + @Test + public int test17(MyValue1[] va, int index, MyValue1 vt) { + va[index] = vt; + return va[index].x; + } + + @Run(test = "test17") + public void test17_verifier() { + int arraySize = Math.abs(rI) % 10; + MyValue1[] va = new MyValue1[arraySize]; + + for (int i = 0; i < arraySize; i++) { + va[i] = MyValue1.createWithFieldsDontInline(rI, rL); + } + + MyValue1 vt = MyValue1.createWithFieldsDontInline(rI + 1, rL); + int result; + for (int i = -20; i < 20; i++) { + try { + result = test17(va, i, vt); + } catch (ArrayIndexOutOfBoundsException e) { + result = rI + 1; + } + Asserts.assertEQ(result, rI + 1); + } + + for (int i = 0; i < arraySize; i++) { + Asserts.assertEQ(va[i].x, rI + 1); + } + } + + // clone() as stub call + @Test + public MyValue1[] test18(MyValue1[] va) { + return va.clone(); + } + + @Run(test = "test18") + public void test18_verifier(RunInfo info) { + int len = Math.abs(rI) % 10; + MyValue1[] va1 = new MyValue1[len]; + MyValue1[] va2 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + for (int i = 1; i < len; ++i) { + va1[i] = testValue1; + va2[i] = testValue1; + } + MyValue1[] result1 = test18(va1); + if (len > 0) { + Asserts.assertEQ(result1[0], null); + } + for (int i = 1; i < len; ++i) { + Asserts.assertEQ(result1[i].hash(), va1[i].hash()); + } + // make sure we do deopt: GraphKit::new_array assumes an + // array of references + for (int j = 0; j < 10; j++) { + MyValue1[] result2 = test18(va2); + + for (int i = 0; i < len; ++i) { + Asserts.assertEQ(result2[i].hash(), va2[i].hash()); + } + } + if (compile_and_run_again_if_deoptimized(info)) { + MyValue1[] result2 = test18(va2); + for (int i = 0; i < len; ++i) { + Asserts.assertEQ(result2[i].hash(), va2[i].hash()); + } + } + } + + // clone() as series of loads/stores + static MyValue1[] test19_orig = null; + + @Test + public MyValue1[] test19() { + MyValue1[] va = new MyValue1[8]; + for (int i = 1; i < va.length; ++i) { + va[i] = MyValue1.createWithFieldsInline(rI, rL); + } + test19_orig = va; + + return va.clone(); + } + + @Run(test = "test19") + public void test19_verifier() { + MyValue1[] result = test19(); + Asserts.assertEQ(result[0], null); + for (int i = 1; i < test19_orig.length; ++i) { + Asserts.assertEQ(result[i].hash(), test19_orig[i].hash()); + } + } + + // arraycopy() of value class array with oop fields + @Test + public void test20(MyValue1[] src, MyValue1[] dst) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Run(test = "test20") + public void test20_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] src1 = new MyValue1[len]; + MyValue1[] src2 = new MyValue1[len]; + MyValue1[] src3 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + MyValue1[] src4 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + MyValue1[] dst1 = new MyValue1[len]; + MyValue1[] dst2 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + MyValue1[] dst3 = new MyValue1[len]; + MyValue1[] dst4 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + if (len > 0) { + src2[0] = testValue1; + } + for (int i = 1; i < len; ++i) { + src1[i] = testValue1; + src2[i] = testValue1; + src3[i] = testValue1; + src4[i] = testValue1; + } + test20(src1, dst1); + test20(src2, dst2); + test20(src3, dst3); + test20(src4, dst4); + if (len > 0) { + Asserts.assertEQ(dst1[0], null); + Asserts.assertEQ(dst2[0].hash(), src2[0].hash()); + Asserts.assertEQ(dst3[0].hash(), src3[0].hash()); + Asserts.assertEQ(dst4[0].hash(), src4[0].hash()); + } + for (int i = 1; i < len; ++i) { + Asserts.assertEQ(src1[i].hash(), dst1[i].hash()); + Asserts.assertEQ(src2[i].hash(), dst2[i].hash()); + Asserts.assertEQ(src3[i].hash(), dst3[i].hash()); + Asserts.assertEQ(src4[i].hash(), dst4[i].hash()); + } + } + + // arraycopy() of value class array with no oop field + @Test + public void test21(MyValue2[] src, MyValue2[] dst) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Run(test = "test21") + public void test21_verifier() { + int len = Math.abs(rI) % 10; + MyValue2[] src1 = new MyValue2[len]; + MyValue2[] src2 = new MyValue2[len]; + MyValue2[] src3 = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, len, MyValue2.DEFAULT); + MyValue2[] src4 = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, len, MyValue2.DEFAULT); + MyValue2[] dst1 = new MyValue2[len]; + MyValue2[] dst2 = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, len, MyValue2.DEFAULT); + MyValue2[] dst3 = new MyValue2[len]; + MyValue2[] dst4 = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, len, MyValue2.DEFAULT); + if (len > 0) { + src2[0] = MyValue2.createWithFieldsInline(rI, rD); + } + for (int i = 1; i < len; ++i) { + src1[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + src2[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + src3[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + src4[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test21(src1, dst1); + test21(src2, dst2); + test21(src3, dst3); + test21(src4, dst4); + if (len > 0) { + Asserts.assertEQ(dst1[0], null); + Asserts.assertEQ(dst2[0].hash(), src2[0].hash()); + Asserts.assertEQ(dst3[0].hash(), src3[0].hash()); + Asserts.assertEQ(dst4[0].hash(), src4[0].hash()); + } + for (int i = 1; i < len; ++i) { + Asserts.assertEQ(src1[i].hash(), dst1[i].hash()); + Asserts.assertEQ(src2[i].hash(), dst2[i].hash()); + Asserts.assertEQ(src3[i].hash(), dst3[i].hash()); + Asserts.assertEQ(src4[i].hash(), dst4[i].hash()); + } + } + + // arraycopy() of value class array with oop field and tightly + // coupled allocation as dest + @Test + public MyValue1[] test22(MyValue1[] src) { + MyValue1[] dst = new MyValue1[src.length]; + System.arraycopy(src, 0, dst, 0, src.length); + return dst; + } + + @Run(test = "test22") + public void test22_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] src1 = new MyValue1[len]; + MyValue1[] src2 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + for (int i = 1; i < len; ++i) { + src1[i] = testValue1; + src2[i] = testValue1; + } + MyValue1[] dst1 = test22(src1); + MyValue1[] dst2 = test22(src2); + if (len > 0) { + Asserts.assertEQ(dst1[0], null); + Asserts.assertEQ(dst2[0].hash(), MyValue1.createDefaultInline().hash()); + } + for (int i = 1; i < len; ++i) { + Asserts.assertEQ(src1[i].hash(), dst1[i].hash()); + Asserts.assertEQ(src2[i].hash(), dst2[i].hash()); + } + } + + // arraycopy() of value class array with oop fields and tightly + // coupled allocation as dest + @Test + public MyValue1[] test23(MyValue1[] src) { + MyValue1[] dst = new MyValue1[src.length + 10]; + System.arraycopy(src, 0, dst, 5, src.length); + return dst; + } + + @Run(test = "test23") + public void test23_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] src1 = new MyValue1[len]; + MyValue1[] src2 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + for (int i = 0; i < len; ++i) { + src1[i] = testValue1; + src2[i] = testValue1; + } + MyValue1[] dst1 = test23(src1); + MyValue1[] dst2 = test23(src2); + for (int i = 0; i < 5; ++i) { + Asserts.assertEQ(dst1[i], null); + Asserts.assertEQ(dst2[i], null); + } + for (int i = 5; i < len; ++i) { + Asserts.assertEQ(src1[i].hash(), dst1[i].hash()); + Asserts.assertEQ(src2[i].hash(), dst2[i].hash()); + } + } + + // arraycopy() of value class array passed as Object + @Test + public void test24(MyValue1[] src, Object dst) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Run(test = "test24") + public void test24_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] src1 = new MyValue1[len]; + MyValue1[] src2 = new MyValue1[len]; + MyValue1[] src3 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + MyValue1[] src4 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + MyValue1[] dst1 = new MyValue1[len]; + MyValue1[] dst2 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + MyValue1[] dst3 = new MyValue1[len]; + MyValue1[] dst4 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + if (len > 0) { + src2[0] = testValue1; + } + for (int i = 1; i < len; ++i) { + src1[i] = testValue1; + src2[i] = testValue1; + src3[i] = testValue1; + src4[i] = testValue1; + } + test24(src1, dst1); + test24(src2, dst2); + test24(src3, dst3); + test24(src4, dst4); + if (len > 0) { + Asserts.assertEQ(dst1[0], null); + Asserts.assertEQ(dst2[0].hash(), src2[0].hash()); + Asserts.assertEQ(dst3[0].hash(), src3[0].hash()); + Asserts.assertEQ(dst4[0].hash(), src4[0].hash()); + } + for (int i = 1; i < len; ++i) { + Asserts.assertEQ(src1[i].hash(), dst1[i].hash()); + Asserts.assertEQ(src2[i].hash(), dst2[i].hash()); + Asserts.assertEQ(src3[i].hash(), dst3[i].hash()); + Asserts.assertEQ(src4[i].hash(), dst4[i].hash()); + } + } + + // short arraycopy() with no oop field + @Test + public void test25(MyValue2[] src, MyValue2[] dst) { + System.arraycopy(src, 0, dst, 0, 8); + } + + @Run(test = "test25") + public void test25_verifier() { + MyValue2[] src1 = new MyValue2[8]; + MyValue2[] src2 = new MyValue2[8]; + MyValue2[] src3 = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 8, MyValue2.DEFAULT); + MyValue2[] src4 = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 8, MyValue2.DEFAULT); + MyValue2[] dst1 = new MyValue2[8]; + MyValue2[] dst2 = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 8, MyValue2.DEFAULT); + MyValue2[] dst3 = new MyValue2[8]; + MyValue2[] dst4 = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 8, MyValue2.DEFAULT); + src2[0] = MyValue2.createWithFieldsInline(rI, rD); + for (int i = 1; i < 8; ++i) { + src1[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + src2[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + src3[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + src4[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test25(src1, dst1); + test25(src2, dst2); + test25(src3, dst3); + test25(src4, dst4); + Asserts.assertEQ(dst1[0], null); + Asserts.assertEQ(dst2[0].hash(), src2[0].hash()); + Asserts.assertEQ(dst3[0].hash(), src3[0].hash()); + Asserts.assertEQ(dst4[0].hash(), src4[0].hash()); + for (int i = 1; i < 8; ++i) { + Asserts.assertEQ(src1[i].hash(), dst1[i].hash()); + Asserts.assertEQ(src2[i].hash(), dst2[i].hash()); + Asserts.assertEQ(src3[i].hash(), dst3[i].hash()); + Asserts.assertEQ(src4[i].hash(), dst4[i].hash()); + } + } + + // short arraycopy() with oop fields + @Test + public void test26(MyValue1[] src, MyValue1[] dst) { + System.arraycopy(src, 0, dst, 0, 8); + } + + @Run(test = "test26") + public void test26_verifier() { + MyValue1[] src1 = new MyValue1[8]; + MyValue1[] src2 = new MyValue1[8]; + MyValue1[] src3 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 8, MyValue1.DEFAULT); + MyValue1[] src4 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 8, MyValue1.DEFAULT); + MyValue1[] dst1 = new MyValue1[8]; + MyValue1[] dst2 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 8, MyValue1.DEFAULT); + MyValue1[] dst3 = new MyValue1[8]; + MyValue1[] dst4 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 8, MyValue1.DEFAULT); + src2[0] = testValue1; + for (int i = 1; i < 8; ++i) { + src1[i] = testValue1; + src2[i] = testValue1; + src3[i] = testValue1; + src4[i] = testValue1; + } + test26(src1, dst1); + test26(src2, dst2); + test26(src3, dst3); + test26(src4, dst4); + Asserts.assertEQ(dst1[0], null); + Asserts.assertEQ(dst2[0].hash(), src2[0].hash()); + Asserts.assertEQ(dst3[0].hash(), src3[0].hash()); + Asserts.assertEQ(dst4[0].hash(), src4[0].hash()); + for (int i = 1; i < 8; ++i) { + Asserts.assertEQ(src1[i].hash(), dst1[i].hash()); + Asserts.assertEQ(src2[i].hash(), dst2[i].hash()); + Asserts.assertEQ(src3[i].hash(), dst3[i].hash()); + Asserts.assertEQ(src4[i].hash(), dst4[i].hash()); + } + } + + // short arraycopy() with oop fields and offsets + @Test + public void test27(MyValue1[] src, MyValue1[] dst) { + System.arraycopy(src, 1, dst, 2, 6); + } + + @Run(test = "test27") + public void test27_verifier() { + MyValue1[] src1 = new MyValue1[8]; + MyValue1[] src2 = new MyValue1[8]; + MyValue1[] src3 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 8, MyValue1.DEFAULT); + MyValue1[] src4 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 8, MyValue1.DEFAULT); + MyValue1[] dst1 = new MyValue1[8]; + MyValue1[] dst2 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 8, MyValue1.DEFAULT); + MyValue1[] dst3 = new MyValue1[8]; + MyValue1[] dst4 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 8, MyValue1.DEFAULT); + for (int i = 1; i < 8; ++i) { + src1[i] = testValue1; + src2[i] = testValue1; + src3[i] = testValue1; + src4[i] = testValue1; + } + test27(src1, dst1); + test27(src2, dst2); + test27(src3, dst3); + test27(src4, dst4); + for (int i = 0; i < 2; ++i) { + Asserts.assertEQ(dst1[i], null); + Asserts.assertEQ(dst2[i].hash(), MyValue1.createDefaultInline().hash()); + Asserts.assertEQ(dst3[i], null); + Asserts.assertEQ(dst4[i].hash(), MyValue1.createDefaultInline().hash()); + } + for (int i = 2; i < 8; ++i) { + Asserts.assertEQ(src1[i].hash(), dst1[i].hash()); + Asserts.assertEQ(src2[i].hash(), dst2[i].hash()); + Asserts.assertEQ(src3[i].hash(), dst3[i].hash()); + Asserts.assertEQ(src4[i].hash(), dst4[i].hash()); + } + } + + // non escaping allocations + // TODO 8252027: Make sure this is optimized with ZGC + @Test + @IR(applyIf = {"UseZGC", "false"}, + failOn = {ALLOC_OF_MYVALUE_KLASS, ALLOC_ARRAY_OF_MYVALUE_KLASS, LOOP, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public MyValue2 test28() { + MyValue2[] src = new MyValue2[10]; + src[0] = null; + MyValue2[] dst = (MyValue2[])src.clone(); + return dst[0]; + } + + @Run(test = "test28") + public void test28_verifier() { + MyValue2 v = MyValue2.createWithFieldsInline(rI, rD); + MyValue2 result = test28(); + Asserts.assertEQ(result, null); + } + + // non escaping allocations + // TODO 8227588: shouldn't this have the same IR matching rules as test6? + @Test + @IR(failOn = {ALLOC_ARRAY_OF_MYVALUE_KLASS, LOOP, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public MyValue2 test29(MyValue2[] src) { + MyValue2[] dst = new MyValue2[10]; + System.arraycopy(src, 0, dst, 0, 10); + return dst[0]; + } + + @Run(test = "test29") + public void test29_verifier(RunInfo info) { + MyValue2[] src1 = new MyValue2[10]; + MyValue2[] src2 = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 10, MyValue2.DEFAULT); + for (int i = 0; i < 10; ++i) { + src1[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + src2[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + MyValue2 v = test29(src1); + Asserts.assertEQ(src1[0].hash(), v.hash()); + if (!info.isWarmUp()) { + v = test29(src2); + Asserts.assertEQ(src2[0].hash(), v.hash()); + } + } + + // non escaping allocation with uncommon trap that needs + // eliminated value class array element as debug info + @Test + public MyValue2 test30(MyValue2[] src, boolean flag) { + MyValue2[] dst = new MyValue2[10]; + System.arraycopy(src, 0, dst, 0, 10); + if (flag) { } + return dst[0]; + } + + @Run(test = "test30") + @Warmup(10000) + public void test30_verifier(RunInfo info) { + MyValue2[] src1 = new MyValue2[10]; + MyValue2[] src2 = (MyValue2[])ValueClass.newNullRestrictedNonAtomicArray(MyValue2.class, 10, MyValue2.DEFAULT); + for (int i = 0; i < 10; ++i) { + src1[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + src2[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + MyValue2 v = test30(src1, !info.isWarmUp()); + Asserts.assertEQ(src1[0].hash(), v.hash()); + if (!info.isWarmUp()) { + v = test30(src2, true); + Asserts.assertEQ(src2[0].hash(), v.hash()); + } + } + + // non escaping allocation with memory phi + @Test + // TODO 8227588 + // @Test(failOn = ALLOC_OF_MYVALUE_KLASS + ALLOC_ARRAY_OF_MYVALUE_KLASS + LOOP + LOAD_OF_ANY_KLASS + STORE_OF_ANY_KLASS + UNSTABLE_IF_TRAP, PREDICATE_TRAP) + public long test31(boolean b, boolean deopt, Method m) { + MyValue2[] src = new MyValue2[1]; + if (b) { + src[0] = MyValue2.createWithFieldsInline(rI, rD); + } else { + src[0] = MyValue2.createWithFieldsInline(rI+1, rD+1); + } + if (deopt) { + // uncommon trap + TestFramework.deoptimize(m); + } + return src[0].hash(); + } + + @Run(test = "test31") + public void test31_verifier(RunInfo info) { + MyValue2 v1 = MyValue2.createWithFieldsInline(rI, rD); + long result1 = test31(true, !info.isWarmUp(), info.getTest()); + Asserts.assertEQ(result1, v1.hash()); + MyValue2 v2 = MyValue2.createWithFieldsInline(rI+1, rD+1); + long result2 = test31(false, !info.isWarmUp(), info.getTest()); + Asserts.assertEQ(result2, v2.hash()); + } + + // Tests with Object arrays and clone/arraycopy + // clone() as stub call + @Test + public Object[] test32(Object[] va) { + return va.clone(); + } + + @Run(test = "test32") + public void test32_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] va1 = new MyValue1[len]; + MyValue1[] va2 = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, len, MyValue1.DEFAULT); + for (int i = 1; i < len; ++i) { + va1[i] = testValue1; + va2[i] = testValue1; + } + MyValue1[] result1 = (MyValue1[])test32(va1); + MyValue1[] result2 = (MyValue1[])test32(va2); + if (len > 0) { + Asserts.assertEQ(result1[0], null); + Asserts.assertEQ(result2[0].hash(), MyValue1.createDefaultInline().hash()); + } + for (int i = 1; i < len; ++i) { + Asserts.assertEQ(((MyValue1)result1[i]).hash(), ((MyValue1)va1[i]).hash()); + Asserts.assertEQ(((MyValue1)result2[i]).hash(), ((MyValue1)va2[i]).hash()); + } + } + + @Test + public Object[] test33(Object[] va) { + return va.clone(); + } + + @Run(test = "test33") + public void test33_verifier() { + int len = Math.abs(rI) % 10; + Object[] va = new Object[len]; + for (int i = 0; i < len; ++i) { + va[i] = testValue1; + } + Object[] result = test33(va); + for (int i = 0; i < len; ++i) { + Asserts.assertEQ(((MyValue1)result[i]).hash(), ((MyValue1)va[i]).hash()); + } + } + + // clone() as series of loads/stores + static Object[] test34_orig = null; + + @ForceInline + public Object[] test34_helper(boolean flag) { + Object[] va = null; + if (flag) { + va = new MyValue1[8]; + for (int i = 0; i < va.length; ++i) { + va[i] = MyValue1.createWithFieldsDontInline(rI, rL); + } + } else { + va = new Object[8]; + } + return va; + } + + @Test + public Object[] test34(boolean flag) { + Object[] va = test34_helper(flag); + test34_orig = va; + return va.clone(); + } + + @Run(test = "test34") + public void test34_verifier(RunInfo info) { + test34(false); + for (int i = 0; i < 10; i++) { // make sure we do deopt + Object[] result = test34(true); + verify(test34_orig, result); + } + if (compile_and_run_again_if_deoptimized(info)) { + Object[] result = test34(true); + verify(test34_orig, result); + } + } + + static void verify(Object[] src, Object[] dst) { + for (int i = 0; i < src.length; ++i) { + if (src[i] != null) { + Asserts.assertEQ(((MyInterface)src[i]).hash(), ((MyInterface)dst[i]).hash()); + } else { + Asserts.assertEQ(dst[i], null); + } + } + } + + static void verify(MyValue1[] src, MyValue1[] dst) { + for (int i = 0; i < src.length; ++i) { + if (src[i] != null) { + Asserts.assertEQ(src[i].hash(), dst[i].hash()); + } else { + Asserts.assertEQ(dst[i], null); + } + } + } + + static void verify(MyValue1[] src, Object[] dst) { + for (int i = 0; i < src.length; ++i) { + if (src[i] != null) { + Asserts.assertEQ(src[i].hash(), ((MyInterface)dst[i]).hash()); + } else { + Asserts.assertEQ(dst[i], null); + } + } + } + + static void verify(MyValue2[] src, MyValue2[] dst) { + for (int i = 0; i < src.length; ++i) { + if (src[i] != null) { + Asserts.assertEQ(src[i].hash(), dst[i].hash()); + } else { + Asserts.assertEQ(dst[i], null); + } + } + } + + static void verify(MyValue2[] src, Object[] dst) { + for (int i = 0; i < src.length; ++i) { + if (src[i] != null) { + Asserts.assertEQ(src[i].hash(), ((MyInterface)dst[i]).hash()); + } else { + Asserts.assertEQ(dst[i], null); + } + } + } + + static boolean compile_and_run_again_if_deoptimized(RunInfo info) { + if (!info.isWarmUp()) { + Method m = info.getTest(); + if (TestFramework.isCompiled(m)) { + TestFramework.compile(m, CompLevel.C2); + } + } + return false; + } + + // arraycopy() of value class array of unknown size + @Test + public void test35(Object src, Object dst, int len) { + System.arraycopy(src, 0, dst, 0, len); + } + + @Run(test = "test35") + public void test35_verifier(RunInfo info) { + int len = Math.abs(rI) % 10; + MyValue1[] src = new MyValue1[len]; + MyValue1[] dst = new MyValue1[len]; + for (int i = 1; i < len; ++i) { + src[i] = testValue1; + } + test35(src, dst, src.length); + verify(src, dst); + if (compile_and_run_again_if_deoptimized(info)) { + test35(src, dst, src.length); + verify(src, dst); + } + } + + @Test + public void test36(Object src, MyValue2[] dst) { + System.arraycopy(src, 0, dst, 0, dst.length); + } + + @Run(test = "test36") + public void test36_verifier(RunInfo info) { + int len = Math.abs(rI) % 10; + MyValue2[] src = new MyValue2[len]; + MyValue2[] dst = new MyValue2[len]; + for (int i = 1; i < len; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test36(src, dst); + verify(src, dst); + if (compile_and_run_again_if_deoptimized(info)) { + test36(src, dst); + verify(src, dst); + } + } + + @Test + public void test37(MyValue2[] src, Object dst) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Run(test = "test37") + public void test37_verifier(RunInfo info) { + int len = Math.abs(rI) % 10; + MyValue2[] src = new MyValue2[len]; + MyValue2[] dst = new MyValue2[len]; + for (int i = 1; i < len; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test37(src, dst); + verify(src, dst); + if (compile_and_run_again_if_deoptimized(info)) { + test37(src, dst); + verify(src, dst); + } + } + + @Test + public void test38(Object src, MyValue2[] dst) { + System.arraycopy(src, 0, dst, 0, dst.length); + } + + @Run(test = "test38") + @Warmup(1) // Avoid early compilation + public void test38_verifier(RunInfo info) { + int len = Math.abs(rI) % 10; + Object[] src = new Object[len]; + MyValue2[] dst = new MyValue2[len]; + for (int i = 1; i < len; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test38(src, dst); + verify(dst, src); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertDeoptimizedByC2(m); + TestFramework.compile(m, CompLevel.C2); + test38(src, dst); + verify(dst, src); + TestFramework.assertCompiled(m); + } + } + + @Test + public void test39(MyValue2[] src, Object dst) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Run(test = "test39") + public void test39_verifier(RunInfo info) { + int len = Math.abs(rI) % 10; + MyValue2[] src = new MyValue2[len]; + Object[] dst = new Object[len]; + for (int i = 1; i < len; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test39(src, dst); + verify(src, dst); + if (compile_and_run_again_if_deoptimized(info)) { + test39(src, dst); + verify(src, dst); + } + } + + @Test + public void test40(Object[] src, Object dst) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Run(test = "test40") + @Warmup(1) // Avoid early compilation + public void test40_verifier(RunInfo info) { + int len = Math.abs(rI) % 10; + Object[] src = new Object[len]; + MyValue2[] dst = new MyValue2[len]; + for (int i = 1; i < len; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test40(src, dst); + verify(dst, src); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertDeoptimizedByC2(m); + TestFramework.compile(m, CompLevel.C2); + test40(src, dst); + verify(dst, src); + TestFramework.assertCompiled(m); + } + } + + @Test + public void test41(Object src, Object[] dst) { + System.arraycopy(src, 0, dst, 0, dst.length); + } + + @Run(test = "test41") + public void test41_verifier(RunInfo info) { + int len = Math.abs(rI) % 10; + MyValue2[] src = new MyValue2[len]; + Object[] dst = new Object[len]; + for (int i = 1; i < len; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test41(src, dst); + verify(src, dst); + if (compile_and_run_again_if_deoptimized(info)) { + test41(src, dst); + verify(src, dst); + } + } + + @Test + public void test42(Object[] src, Object[] dst) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Run(test = "test42") + public void test42_verifier(RunInfo info) { + int len = Math.abs(rI) % 10; + Object[] src = new Object[len]; + Object[] dst = new Object[len]; + for (int i = 1; i < len; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test42(src, dst); + verify(src, dst); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertCompiled(m); + } + } + + // short arraycopy()'s + @Test + public void test43(Object src, Object dst) { + System.arraycopy(src, 0, dst, 0, 8); + } + + @Run(test = "test43") + public void test43_verifier(RunInfo info) { + MyValue1[] src = new MyValue1[8]; + MyValue1[] dst = new MyValue1[8]; + for (int i = 1; i < 8; ++i) { + src[i] = testValue1; + } + test43(src, dst); + verify(src, dst); + if (compile_and_run_again_if_deoptimized(info)) { + test43(src, dst); + verify(src, dst); + } + } + + @Test + public void test44(Object src, MyValue2[] dst) { + System.arraycopy(src, 0, dst, 0, 8); + } + + @Run(test = "test44") + public void test44_verifier(RunInfo info) { + MyValue2[] src = new MyValue2[8]; + MyValue2[] dst = new MyValue2[8]; + for (int i = 1; i < 8; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test44(src, dst); + verify(src, dst); + if (compile_and_run_again_if_deoptimized(info)) { + test44(src, dst); + verify(src, dst); + } + } + + @Test + public void test45(MyValue2[] src, Object dst) { + System.arraycopy(src, 0, dst, 0, 8); + } + + @Run(test = "test45") + public void test45_verifier(RunInfo info) { + MyValue2[] src = new MyValue2[8]; + MyValue2[] dst = new MyValue2[8]; + for (int i = 1; i < 8; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test45(src, dst); + verify(src, dst); + if (compile_and_run_again_if_deoptimized(info)) { + test45(src, dst); + verify(src, dst); + } + } + + @Test + public void test46(Object[] src, MyValue2[] dst) { + System.arraycopy(src, 0, dst, 0, 8); + } + + @Run(test = "test46") + @Warmup(1) // Avoid early compilation + public void test46_verifier(RunInfo info) { + Object[] src = new Object[8]; + MyValue2[] dst = new MyValue2[8]; + for (int i = 1; i < 8; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test46(src, dst); + verify(dst, src); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertDeoptimizedByC2(m); + TestFramework.compile(m, CompLevel.C2); + test46(src, dst); + verify(dst, src); + TestFramework.assertCompiled(m); + } + } + + @Test + public void test47(MyValue2[] src, Object[] dst) { + System.arraycopy(src, 0, dst, 0, 8); + } + + @Run(test = "test47") + public void test47_verifier(RunInfo info) { + MyValue2[] src = new MyValue2[8]; + Object[] dst = new Object[8]; + for (int i = 1; i < 8; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test47(src, dst); + verify(src, dst); + if (compile_and_run_again_if_deoptimized(info)) { + test47(src, dst); + verify(src, dst); + } + } + + @Test + public void test48(Object[] src, Object dst) { + System.arraycopy(src, 0, dst, 0, 8); + } + + @Run(test = "test48") + @Warmup(1) // Avoid early compilation + public void test48_verifier(RunInfo info) { + Object[] src = new Object[8]; + MyValue2[] dst = new MyValue2[8]; + for (int i = 1; i < 8; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test48(src, dst); + verify(dst, src); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertDeoptimizedByC2(m); + TestFramework.compile(m, CompLevel.C2); + test48(src, dst); + verify(dst, src); + TestFramework.assertCompiled(m); + } + } + + @Test + public void test49(Object src, Object[] dst) { + System.arraycopy(src, 0, dst, 0, 8); + } + + @Run(test = "test49") + public void test49_verifier(RunInfo info) { + MyValue2[] src = new MyValue2[8]; + Object[] dst = new Object[8]; + for (int i = 1; i < 8; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test49(src, dst); + verify(src, dst); + if (compile_and_run_again_if_deoptimized(info)) { + test49(src, dst); + verify(src, dst); + } + } + + @Test + public void test50(Object[] src, Object[] dst) { + System.arraycopy(src, 0, dst, 0, 8); + } + + @Run(test = "test50") + public void test50_verifier(RunInfo info) { + Object[] src = new Object[8]; + Object[] dst = new Object[8]; + for (int i = 1; i < 8; ++i) { + src[i] = MyValue2.createWithFieldsInline(rI+i, rD+i); + } + test50(src, dst); + verify(src, dst); + if (!info.isWarmUp()) { + Method m = info.getTest(); + TestFramework.assertCompiled(m); + } + } + + @Test + public MyValue1[] test51(MyValue1[] va) { + return Arrays.copyOf(va, va.length, MyValue1[].class); + } + + @Run(test = "test51") + public void test51_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] va = new MyValue1[len]; + for (int i = 1; i < len; ++i) { + va[i] = testValue1; + } + MyValue1[] result = test51(va); + verify(va, result); + } + + static final MyValue1[] test52_va = new MyValue1[8]; + + @Test + public MyValue1[] test52() { + return Arrays.copyOf(test52_va, 8, MyValue1[].class); + } + + @Run(test = "test52") + public void test52_verifier() { + for (int i = 1; i < 8; ++i) { + test52_va[i] = testValue1; + } + MyValue1[] result = test52(); + verify(test52_va, result); + } + + @Test + public MyValue1[] test53(Object[] va) { + return Arrays.copyOf(va, va.length, MyValue1[].class); + } + + @Run(test = "test53") + public void test53_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] va = new MyValue1[len]; + for (int i = 1; i < len; ++i) { + va[i] = testValue1; + } + MyValue1[] result = test53(va); + verify(result, va); + } + + @Test + public Object[] test54(MyValue1[] va) { + return Arrays.copyOf(va, va.length, Object[].class); + } + + @Run(test = "test54") + public void test54_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] va = new MyValue1[len]; + for (int i = 1; i < len; ++i) { + va[i] = testValue1; + } + Object[] result = test54(va); + verify(va, result); + } + + @Test + public Object[] test55(Object[] va) { + return Arrays.copyOf(va, va.length, Object[].class); + } + + @Run(test = "test55") + public void test55_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] va = new MyValue1[len]; + for (int i = 1; i < len; ++i) { + va[i] = testValue1; + } + Object[] result = test55(va); + verify(va, result); + } + + @Test + public MyValue1[] test56(Object[] va) { + return Arrays.copyOf(va, va.length, MyValue1[].class); + } + + @Run(test = "test56") + public void test56_verifier() { + int len = Math.abs(rI) % 10; + Object[] va = new Object[len]; + for (int i = 1; i < len; ++i) { + va[i] = testValue1; + } + MyValue1[] result = test56(va); + verify(result, va); + } + + @Test + public Object[] test57(Object[] va, Class klass) { + return Arrays.copyOf(va, va.length, klass); + } + + @Run(test = "test57") + public void test57_verifier() { + int len = Math.abs(rI) % 10; + Object[] va = new MyValue1[len]; + for (int i = 1; i < len; ++i) { + va[i] = testValue1; + } + Object[] result = test57(va, MyValue1[].class); + verify(va, result); + } + + @Test + public Object[] test58(MyValue1[] va, Class klass) { + return Arrays.copyOf(va, va.length, klass); + } + + @Run(test = "test58") + public void test58_verifier(RunInfo info) { + int len = Math.abs(rI) % 10; + MyValue1[] va = new MyValue1[len]; + for (int i = 1; i < len; ++i) { + va[i] = testValue1; + } + for (int i = 1; i < 10; i++) { + Object[] result = test58(va, MyValue1[].class); + verify(va, result); + } + if (compile_and_run_again_if_deoptimized(info)) { + Object[] result = test58(va, MyValue1[].class); + verify(va, result); + } + } + + @Test + public Object[] test59(MyValue1[] va) { + return Arrays.copyOf(va, va.length+1, MyValue1[].class); + } + + @Run(test = "test59") + public void test59_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] va = new MyValue1[len]; + MyValue1[] verif = new MyValue1[len+1]; + for (int i = 1; i < len; ++i) { + va[i] = testValue1; + verif[i] = va[i]; + } + Object[] result = test59(va); + verify(verif, result); + } + + @Test + public Object[] test60(Object[] va, Class klass) { + return Arrays.copyOf(va, va.length+1, klass); + } + + @Run(test = "test60") + public void test60_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] va = new MyValue1[len]; + MyValue1[] verif = new MyValue1[len+1]; + for (int i = 1; i < len; ++i) { + va[i] = testValue1; + verif[i] = (MyValue1)va[i]; + } + Object[] result = test60(va, MyValue1[].class); + verify(verif, result); + } + + @Test + public Object[] test61(Object[] va, Class klass) { + return Arrays.copyOf(va, va.length+1, klass); + } + + @Run(test = "test61") + public void test61_verifier() { + int len = Math.abs(rI) % 10; + Object[] va = new NonValueClass[len]; + for (int i = 1; i < len; ++i) { + va[i] = new NonValueClass(rI); + } + Object[] result = test61(va, NonValueClass[].class); + for (int i = 0; i < va.length; ++i) { + Asserts.assertEQ(va[i], result[i]); + } + } + + @ForceInline + public Object[] test62_helper(int i, MyValue1[] va, NonValueClass[] oa) { + Object[] arr = null; + if (i == 10) { + arr = oa; + } else { + arr = va; + } + return arr; + } + + @Test + public Object[] test62(MyValue1[] va, NonValueClass[] oa) { + int i = 0; + for (; i < 10; i++); + + Object[] arr = test62_helper(i, va, oa); + + return Arrays.copyOf(arr, arr.length+1, arr.getClass()); + } + + @Run(test = "test62") + public void test62_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] va = new MyValue1[len]; + NonValueClass[] oa = new NonValueClass[len]; + for (int i = 1; i < len; ++i) { + oa[i] = new NonValueClass(rI); + } + test62_helper(42, va, oa); + Object[] result = test62(va, oa); + for (int i = 0; i < va.length; ++i) { + Asserts.assertEQ(oa[i], result[i]); + } + } + + @ForceInline + public Object[] test63_helper(int i, MyValue1[] va, NonValueClass[] oa) { + Object[] arr = null; + if (i == 10) { + arr = va; + } else { + arr = oa; + } + return arr; + } + + @Test + public Object[] test63(MyValue1[] va, NonValueClass[] oa) { + int i = 0; + for (; i < 10; i++); + + Object[] arr = test63_helper(i, va, oa); + + return Arrays.copyOf(arr, arr.length+1, arr.getClass()); + } + + @Run(test = "test63") + public void test63_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] va = new MyValue1[len]; + MyValue1[] verif = new MyValue1[len+1]; + for (int i = 1; i < len; ++i) { + va[i] = testValue1; + verif[i] = va[i]; + } + NonValueClass[] oa = new NonValueClass[len]; + test63_helper(42, va, oa); + Object[] result = test63(va, oa); + verify(verif, result); + } + + // Test initialization of value class arrays: small array + @Test + public MyValue1[] test64() { + return new MyValue1[8]; + } + + @Run(test = "test64") + public void test64_verifier() { + MyValue1[] va = test64(); + for (int i = 0; i < 8; ++i) { + Asserts.assertEQ(va[i], null); + } + } + + // Test initialization of value class arrays: large array + @Test + public MyValue1[] test65() { + return new MyValue1[32]; + } + + @Run(test = "test65") + public void test65_verifier() { + MyValue1[] va = test65(); + for (int i = 0; i < 32; ++i) { + Asserts.assertEQ(va[i], null); + } + } + + // Check init store elimination + @Test + @IR(counts = {ALLOC_ARRAY_OF_MYVALUE_KLASS, "= 1"}) + public MyValue1[] test66(MyValue1 vt) { + MyValue1[] va = new MyValue1[1]; + va[0] = vt; + return va; + } + + @Run(test = "test66") + public void test66_verifier() { + MyValue1 vt = MyValue1.createWithFieldsDontInline(rI, rL); + MyValue1[] va = test66(vt); + Asserts.assertEQ(va[0].hashPrimitive(), vt.hashPrimitive()); + } + + // Zeroing elimination and arraycopy + @Test + public MyValue1[] test67(MyValue1[] src) { + MyValue1[] dst = new MyValue1[16]; + System.arraycopy(src, 0, dst, 0, 13); + return dst; + } + + @Run(test = "test67") + public void test67_verifier() { + MyValue1[] va = new MyValue1[16]; + MyValue1[] var = test67(va); + for (int i = 0; i < 16; ++i) { + Asserts.assertEQ(var[i], null); + } + } + + // A store with a zero value can be eliminated + @Test + public MyValue1[] test68() { + MyValue1[] va = new MyValue1[2]; + va[0] = va[1]; + return va; + } + + @Run(test = "test68") + public void test68_verifier() { + MyValue1[] va = test68(); + for (int i = 0; i < 2; ++i) { + Asserts.assertEQ(va[i], null); + } + } + + // Requires individual stores to init array + @Test + public MyValue1[] test69(MyValue1 vt) { + MyValue1[] va = new MyValue1[4]; + va[0] = vt; + va[3] = vt; + return va; + } + + @Run(test = "test69") + public void test69_verifier() { + MyValue1 vt = MyValue1.createWithFieldsDontInline(rI, rL); + MyValue1[] va = new MyValue1[4]; + va[0] = vt; + va[3] = vt; + MyValue1[] var = test69(vt); + for (int i = 0; i < va.length; ++i) { + Asserts.assertEQ(va[i], var[i]); + } + } + + // Same as test68 but store is further away from allocation + @Test + public MyValue1[] test70(MyValue1[] other) { + other[1] = other[0]; + MyValue1[] va = new MyValue1[2]; + other[0] = va[1]; + va[0] = va[1]; + return va; + } + + @Run(test = "test70") + public void test70_verifier() { + MyValue1[] va = new MyValue1[2]; + MyValue1[] var = test70(va); + for (int i = 0; i < 2; ++i) { + Asserts.assertEQ(va[i], var[i]); + } + } + + // EA needs to consider oop fields in flattened arrays + @Test + public void test71() { + int len = 10; + MyValue2[] src = new MyValue2[len]; + MyValue2[] dst = new MyValue2[len]; + for (int i = 1; i < len; ++i) { + src[i] = MyValue2.createWithFieldsDontInline(rI+i, rD+i); + } + System.arraycopy(src, 0, dst, 0, src.length); + for (int i = 0; i < len; ++i) { + if (src[i] == null) { + Asserts.assertEQ(dst[i], null); + } else { + Asserts.assertEQ(src[i].hash(), dst[i].hash()); + } + } + } + + @Run(test = "test71") + public void test71_verifier() { + test71(); + } + + // Test EA with leaf call to 'store_unknown_value' + @Test + public void test72(Object[] o, boolean b, Object element) { + Object[] arr1 = new Object[10]; + Object[] arr2 = new Object[10]; + if (b) { + arr1 = o; + } + arr1[0] = element; + arr2[0] = element; + } + + @Run(test = "test72") + public void test72_verifier() { + Object[] arr = new Object[1]; + Object elem = new Object(); + test72(arr, true, elem); + test72(arr, false, elem); + } + + @Test + public void test73(Object[] oa, MyValue1 v, Object o) { + // TestLWorld.test38 use a C1 Phi node for the array. This test + // adds the case where the stored value is a C1 Phi node. + Object o2 = (o == null) ? v : o; + oa[0] = v; // The stored value is known to be flattenable + oa[1] = o; // The stored value may be flattenable + oa[2] = o2; // The stored value may be flattenable (a C1 Phi node) + oa[0] = oa; // The stored value is known to be not flattenable (an Object[]) + } + + @Run(test = "test73") + public void test73_verifier() { + MyValue1 v0 = MyValue1.createWithFieldsDontInline(rI, rL); + MyValue1 v1 = MyValue1.createWithFieldsDontInline(rI+1, rL+1); + MyValue1[] arr = new MyValue1[3]; + try { + test73(arr, v0, v1); + throw new RuntimeException("ArrayStoreException expected"); + } catch (ArrayStoreException t) { + // expected + } + Asserts.assertEQ(arr[0].hash(), v0.hash()); + Asserts.assertEQ(arr[1].hash(), v1.hash()); + Asserts.assertEQ(arr[2].hash(), v1.hash()); + } + + // Some more array clone tests + @ForceInline + public Object[] test74_helper(int i, MyValue1[] va, NonValueClass[] oa) { + Object[] arr = null; + if (i == 10) { + arr = oa; + } else { + arr = va; + } + return arr; + } + + @Test + public Object[] test74(MyValue1[] va, NonValueClass[] oa) { + int i = 0; + for (; i < 10; i++); + + Object[] arr = test74_helper(i, va, oa); + return arr.clone(); + } + + @Run(test = "test74") + public void test74_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] va = new MyValue1[len]; + NonValueClass[] oa = new NonValueClass[len]; + for (int i = 1; i < len; ++i) { + oa[i] = new NonValueClass(rI); + } + test74_helper(42, va, oa); + Object[] result = test74(va, oa); + + for (int i = 0; i < va.length; ++i) { + Asserts.assertEQ(oa[i], result[i]); + // Check that array has correct properties (null-ok) + result[i] = null; + } + } + + @ForceInline + public Object[] test75_helper(int i, MyValue1[] va, NonValueClass[] oa) { + Object[] arr = null; + if (i == 10) { + arr = va; + } else { + arr = oa; + } + return arr; + } + + @Test + public Object[] test75(MyValue1[] va, NonValueClass[] oa) { + int i = 0; + for (; i < 10; i++); + + Object[] arr = test75_helper(i, va, oa); + return arr.clone(); + } + + @Run(test = "test75") + public void test75_verifier() { + int len = Math.abs(rI) % 10; + MyValue1[] va = new MyValue1[len]; + MyValue1[] verif = new MyValue1[len]; + for (int i = 1; i < len; ++i) { + va[i] = testValue1; + verif[i] = va[i]; + } + NonValueClass[] oa = new NonValueClass[len]; + test75_helper(42, va, oa); + Object[] result = test75(va, oa); + verify(verif, result); + if (len > 0) { + // Check that array has correct properties (null-ok) + result[0] = null; + } + } + + // Test mixing nullable and non-nullable arrays + @Test + public Object[] test76(MyValue1[] vva, MyValue1[] vba, MyValue1 vt, Object[] out, int n) { + Object[] result = null; + if (n == 0) { + result = vva; + } else if (n == 1) { + result = vba; + } else if (n == 2) { + result = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 42, MyValue1.DEFAULT); + } else if (n == 3) { + result = new MyValue1[42]; + } + result[0] = vt; + out[0] = result[1]; + return result; + } + + @Run(test = "test76") + public void test76_verifier() { + MyValue1 vt = testValue1; + Object[] out = new Object[1]; + MyValue1[] vva = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 42, MyValue1.DEFAULT); + MyValue1[] vva_r = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 42, MyValue1.DEFAULT); + vva_r[0] = vt; + MyValue1[] vba = new MyValue1[42]; + MyValue1[] vba_r = new MyValue1[42]; + vba_r[0] = vt; + Object[] result = test76(vva, vba, vt, out, 0); + verify(result, vva_r); + Asserts.assertEQ(out[0], vva_r[1]); + result = test76(vva, vba, vt, out, 1); + verify(result, vba_r); + Asserts.assertEQ(out[0], vba_r[1]); + result = test76(vva, vba, vt, out, 2); + verify(result, vva_r); + Asserts.assertEQ(out[0], vva_r[1]); + result = test76(vva, vba, vt, out, 3); + verify(result, vba_r); + Asserts.assertEQ(out[0], vba_r[1]); + } + + @Test + public Object[] test77(boolean b) { + Object[] va; + if (b) { + va = new MyValue1[5]; + for (int i = 0; i < 5; ++i) { + va[i] = testValue1; + } + } else { + va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 10, MyValue1.DEFAULT); + for (int i = 0; i < 10; ++i) { + va[i] = MyValue1.createWithFieldsInline(rI + i, rL + i); + } + } + long sum = ((MyValue1)va[0]).hashInterpreted(); + if (b) { + va[0] = MyValue1.createWithFieldsDontInline(rI, sum); + } else { + va[0] = MyValue1.createWithFieldsDontInline(rI + 1, sum + 1); + } + return va; + } + + @Run(test = "test77") + public void test77_verifier() { + Object[] va = test77(true); + Asserts.assertEQ(va.length, 5); + Asserts.assertEQ(((MyValue1)va[0]).hash(), hash(rI, hash())); + for (int i = 1; i < 5; ++i) { + Asserts.assertEQ(((MyValue1)va[i]).hash(), hash()); + } + va = test77(false); + Asserts.assertEQ(va.length, 10); + Asserts.assertEQ(((MyValue1)va[0]).hash(), hash(rI + 1, hash(rI, rL) + 1)); + for (int i = 1; i < 10; ++i) { + Asserts.assertEQ(((MyValue1)va[i]).hash(), hash(rI + i, rL + i)); + } + } + + // Same as test76 but with non value class array cases + @Test + public Object[] test78(MyValue1[] vva, MyValue1[] vba, Object val, Object[] out, int n) { + Object[] result = null; + if (n == 0) { + result = vva; + } else if (n == 1) { + result = vba; + } else if (n == 2) { + result = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 42, MyValue1.DEFAULT); + } else if (n == 3) { + result = new MyValue1[42]; + } else if (n == 4) { + result = new NonValueClass[42]; + } + result[0] = val; + out[0] = result[1]; + return result; + } + + @Run(test = "test78") + public void test78_verifier() { + MyValue1 vt = testValue1; + NonValueClass obj = new NonValueClass(42); + Object[] out = new Object[1]; + MyValue1[] vva = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 42, MyValue1.DEFAULT); + MyValue1[] vva_r = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 42, MyValue1.DEFAULT); + vva_r[0] = vt; + MyValue1[] vba = new MyValue1[42]; + MyValue1[] vba_r = new MyValue1[42]; + vba_r[0] = vt; + Object[] result = test78(vva, vba, vt, out, 0); + verify(result, vva_r); + Asserts.assertEQ(out[0], vva_r[1]); + result = test78(vva, vba, vt, out, 1); + verify(result, vba_r); + Asserts.assertEQ(out[0], vba_r[1]); + result = test78(vva, vba, vt, out, 2); + verify(result, vva_r); + Asserts.assertEQ(out[0], vva_r[1]); + result = test78(vva, vba, vt, out, 3); + verify(result, vba_r); + Asserts.assertEQ(out[0], vba_r[1]); + result = test78(vva, vba, obj, out, 4); + Asserts.assertEQ(result[0], obj); + Asserts.assertEQ(out[0], null); + } + + // Test widening conversions from [Q to [L + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, ALLOC_ARRAY_OF_MYVALUE_KLASS, LOOP, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public static MyValue1[] test79(MyValue1[] va) { + return va; + } + + @Run(test = "test79") + public void test79_verifier() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + va[0] = testValue1; + MyValue1[] res = test79(va); + Asserts.assertEquals(res[0].hash(), testValue1.hash()); + try { + res[0] = null; + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException npe) { + // Expected + } + res[0] = testValue1; + test79(null); // Should not throw NPE + } + + // Same as test79 but with explicit cast and Object return + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, ALLOC_ARRAY_OF_MYVALUE_KLASS, LOOP, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public static Object[] test80(MyValue1[] va) { + return (MyValue1[])va; + } + + @Run(test = "test80") + public void test80_verifier() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + va[0] = testValue1; + Object[] res = test80(va); + Asserts.assertEquals(((MyValue1)res[0]).hash(), testValue1.hash()); + try { + res[0] = null; + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException npe) { + // Expected + } + res[0] = testValue1; + test80(null); // Should not throw NPE + } + + // Test mixing widened and boxed array type + @Test + public static long test81(MyValue1[] va1, MyValue1[] va2, MyValue1 vt, boolean b, boolean shouldThrow) { + MyValue1[] result = b ? va1 : va2; + try { + result[0] = vt; + } catch (NullPointerException npe) { + // Ignored + } + return result[1].hash(); + } + + @Run(test = "test81") + public void test81_verifier() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 2, MyValue1.DEFAULT); + MyValue1[] vaB = new MyValue1[2]; + va[1] = testValue1; + vaB[1] = testValue1; + long res = test81(va, vaB, testValue1, true, true); + Asserts.assertEquals(va[0].hash(), testValue1.hash()); + Asserts.assertEquals(res, testValue1.hash()); + res = test81(va, vaB, testValue1, false, false); + Asserts.assertEquals(vaB[0].hash(), testValue1.hash()); + Asserts.assertEquals(res, testValue1.hash()); + res = test81(va, va, testValue1, false, true); + Asserts.assertEquals(va[0].hash(), testValue1.hash()); + Asserts.assertEquals(res, testValue1.hash()); + } + + // Same as test81 but more cases and null writes + @Test + public static long test82(MyValue1[] va1, MyValue1[] va2, MyValue1 vt1, MyValue1 vt2, int i, boolean shouldThrow) { + MyValue1[] result = null; + if (i == 0) { + result = va1; + } else if (i == 1) { + result = va2; + } else if (i == 2) { + result = new MyValue1[2]; + result[1] = vt1; + } else if (i == 3) { + result = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 2, MyValue1.DEFAULT); + result[1] = vt1; + } + try { + result[0] = (i <= 1) ? null : vt2; + if (shouldThrow) { + throw new RuntimeException("NullPointerException expected"); + } + } catch (NullPointerException npe) { + Asserts.assertTrue(shouldThrow, "NullPointerException thrown"); + } + result[0] = vt1; + return result[1].hash(); + } + + @Run(test = "test82") + public void test82_verifier() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 2, MyValue1.DEFAULT); + MyValue1[] vaB = new MyValue1[2]; + va[1] = testValue1; + vaB[1] = testValue1; + long res = test82(va, vaB, testValue1, testValue1, 0, true); + Asserts.assertEquals(va[0].hash(), testValue1.hash()); + Asserts.assertEquals(res, testValue1.hash()); + res = test82(va, vaB, testValue1, testValue1, 1, false); + Asserts.assertEquals(vaB[0].hash(), testValue1.hash()); + Asserts.assertEquals(res, testValue1.hash()); + res = test82(va, va, testValue1, testValue1, 1, true); + Asserts.assertEquals(va[0].hash(), testValue1.hash()); + Asserts.assertEquals(res, testValue1.hash()); + res = test82(va, va, testValue1, null, 2, false); + Asserts.assertEquals(va[0].hash(), testValue1.hash()); + Asserts.assertEquals(res, testValue1.hash()); + res = test82(va, va, testValue1, null, 3, true); + Asserts.assertEquals(va[0].hash(), testValue1.hash()); + Asserts.assertEquals(res, testValue1.hash()); + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, ALLOC_ARRAY_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public static long test83(MyValue1[] va) { + MyValue1[] result = va; + return result[0].hash(); + } + + @Run(test = "test83") + public void test83_verifier() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 42, MyValue1.DEFAULT); + va[0] = testValue1; + long res = test83(va); + Asserts.assertEquals(res, testValue1.hash()); + } + + @Test + @IR(applyIf = {"UseArrayFlattening", "true"}, + failOn = {ALLOC_OF_MYVALUE_KLASS, LOOP, UNSTABLE_IF_TRAP, PREDICATE_TRAP}, + counts = {STORE_OF_ANY_KLASS, "= 38"}) + public static MyValue1[] test84(MyValue1 vt1, MyValue1 vt2) { + MyValue1[] result = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 2, MyValue1.DEFAULT); + result[0] = vt1; + result[1] = vt2; + return result; + } + + @Run(test = "test84") + public void test84_verifier() { + MyValue1[] res = test84(testValue1, testValue1); + Asserts.assertEquals(res[0].hash(), testValue1.hash()); + Asserts.assertEquals(res[1].hash(), testValue1.hash()); + try { + test84(testValue1, null); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException npe) { + // Expected + } + } + + @Test + public static long test85(MyValue1[] va, MyValue1 val) { + va[0] = val; + return va[1].hash(); + } + + @Run(test = "test85") + public void test85_verifier() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 2, MyValue1.DEFAULT); + MyValue1[] vab = new MyValue1[2]; + va[1] = testValue1; + vab[1] = testValue1; + long res = test85(va, testValue1); + Asserts.assertEquals(res, testValue1.hash()); + Asserts.assertEquals(va[0].hash(), testValue1.hash()); + res = test85(vab, testValue1); + Asserts.assertEquals(res, testValue1.hash()); + Asserts.assertEquals(vab[0].hash(), testValue1.hash()); + } + + // Same as test85 but with ref value + @Test + public static long test86(MyValue1[] va, MyValue1 val) { + va[0] = val; + return va[1].hash(); + } + + @Run(test = "test86") + public void test86_verifier() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 2, MyValue1.DEFAULT); + MyValue1[] vab = new MyValue1[2]; + va[1] = testValue1; + vab[1] = testValue1; + long res = test86(va, testValue1); + Asserts.assertEquals(res, testValue1.hash()); + Asserts.assertEquals(va[0].hash(), testValue1.hash()); + try { + test86(va, null); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException npe) { + // Expected + } + res = test86(vab, testValue1); + Asserts.assertEquals(res, testValue1.hash()); + Asserts.assertEquals(vab[0].hash(), testValue1.hash()); + res = test86(vab, null); + Asserts.assertEquals(res, testValue1.hash()); + Asserts.assertEquals(vab[0], null); + } + + // Test initialization of nullable array with constant + @Test + public long test87() { + MyValue1[] va = new MyValue1[1]; + va[0] = testValue1; + return va[0].hash(); + } + + @Run(test = "test87") + public void test87_verifier() { + long result = test87(); + Asserts.assertEQ(result, hash()); + } + + + // Test casting to null restricted array + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, ALLOC_ARRAY_OF_MYVALUE_KLASS, LOOP, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public static MyValue1[] test88(Class c, MyValue1[] va) { + return (MyValue1[])c.cast(va); + } + + @Run(test = "test88") + public void test88_verifier() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + Class c = va.getClass(); + va[0] = testValue1; + MyValue1[] res = test88(c, va); + Asserts.assertEquals(res[0].hash(), testValue1.hash()); + Asserts.assertEquals(res, va); + res[0] = testValue1; + test88(c, null); // Should not throw NPE + va = new MyValue1[1]; + res = test88(c, va); + Asserts.assertEquals(res, va); + } + + // Same as test88 but with Object argument + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, ALLOC_ARRAY_OF_MYVALUE_KLASS, LOOP, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public static MyValue1[] test89(Class c, Object[] va) { + return (MyValue1[])c.cast(va); + } + + @Run(test = "test89") + public void test89_verifier() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + Class c = va.getClass(); + va[0] = testValue1; + MyValue1[] res = test89(c, va); + Asserts.assertEquals(((MyValue1)res[0]).hash(), testValue1.hash()); + res[0] = testValue1; + test89(c, null); // Should not throw NPE + va = new MyValue1[1]; + res = test89(c, va); + Asserts.assertEquals(res, va); + } + + // More cast tests + @Test + public static MyValue1[] test90(Object va) { + return (MyValue1[])va; + } + + @Run(test = "test90") + public void test90_verifier() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + MyValue1[] vab = new MyValue1[1]; + try { + // Trigger some ClassCastExceptions so C2 does not add an uncommon trap + test90(new NonValueClass[0]); + } catch (ClassCastException cce) { + // Expected + } + test90(va); + test90(vab); + test90(null); + } + + @Test + public static MyValue1[] test91(Object[] va) { + return (MyValue1[])va; + } + + @Run(test = "test91") + public void test91_verifier() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + MyValue1[] vab = new MyValue1[1]; + try { + // Trigger some ClassCastExceptions so C2 does not add an uncommon trap + test91(new NonValueClass[0]); + } catch (ClassCastException cce) { + // Expected + } + test91(va); + test91(vab); + test91(null); + } + + // Test if arraycopy intrinsic correctly checks for flattened source array + @Test + public static void test92(MyValue1[] src, MyValue1[] dst) { + System.arraycopy(src, 0, dst, 0, 2); + } + + @Run(test = "test92") + public void test92_verifier() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 2, MyValue1.DEFAULT); + MyValue1[] vab = new MyValue1[2]; + va[0] = testValue1; + vab[0] = testValue1; + test92(va, vab); + Asserts.assertEquals(va[0], vab[0]); + Asserts.assertEquals(va[1], vab[1]); + } + + @Test + public static void test93(Object src, MyValue1[] dst) { + System.arraycopy(src, 0, dst, 0, 2); + } + + @Run(test = "test93") + public void test93_verifier() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 2, MyValue1.DEFAULT); + MyValue1[] vab = new MyValue1[2]; + va[0] = testValue1; + vab[0] = testValue1; + test93(va, vab); + Asserts.assertEquals(va[0], vab[0]); + Asserts.assertEquals(va[1], vab[1]); + } + + // Test non-escaping allocation with arraycopy + // that does not modify loaded array element. + @Test + public static long test94() { + MyValue1[] src = new MyValue1[8]; + MyValue1[] dst = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 8, MyValue1.DEFAULT); + for (int i = 1; i < 8; ++i) { + src[i] = testValue1; + } + System.arraycopy(src, 1, dst, 2, 6); + return dst[0].hash(); + } + + @Run(test = "test94") + public static void test94_verifier() { + long result = test94(); + Asserts.assertEquals(result, MyValue1.createDefaultInline().hash()); + } + + // Test meeting constant TypeInstPtr with InlineTypeNode + @ForceInline + public long test95_callee() { + MyValue1[] va = new MyValue1[1]; + va[0] = testValue1; + return va[0].hashInterpreted(); + } + + @Test + public long test95() { + return test95_callee(); + } + + @Run(test = "test95") + @Warmup(0) + public void test95_verifier() { + long result = test95(); + Asserts.assertEQ(result, hash()); + } + + // Matrix multiplication test to exercise type flow analysis with nullable value class arrays + static value class Complex { + private final double re; + private final double im; + + Complex(double re, double im) { + this.re = re; + this.im = im; + } + + public Complex add(Complex that) { + return new Complex(this.re + that.re, this.im + that.im); + } + + public Complex mul(Complex that) { + return new Complex(this.re * that.re - this.im * that.im, + this.re * that.im + this.im * that.re); + } + } + + @Test + public Complex[][] test96(Complex[][] A, Complex[][] B) { + int size = A.length; + Complex[][] R = new Complex[size][size]; + for (int i = 0; i < size; i++) { + for (int k = 0; k < size; k++) { + Complex aik = A[i][k]; + for (int j = 0; j < size; j++) { + R[i][j] = B[i][j].add(aik.mul((Complex)B[k][j])); + } + } + } + return R; + } + + static Complex[][] test96_A = new Complex[10][10]; + static Complex[][] test96_B = new Complex[10][10]; + static Complex[][] test96_R; + + static { + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + test96_A[i][j] = new Complex(rI, rI); + test96_B[i][j] = new Complex(rI, rI); + } + } + } + + @Run(test = "test96") + public void test96_verifier() { + Complex[][] result = test96(test96_A, test96_B); + if (test96_R == null) { + test96_R = result; + } + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + Asserts.assertEQ(result[i][j], test96_R[i][j]); + } + } + } + + // Test loads from vararg arrays + @Test + @IR(failOn = {LOAD_UNKNOWN_INLINE}) + public static Object test97(Object... args) { + return args[0]; + } + + @Run(test = "test97") + public static void test97_verifier() { + Object obj = new Object(); + Object result = test97(obj); + Asserts.assertEquals(result, obj); + NonValueClass[] myInt = new NonValueClass[1]; + NonValueClass otherObj = new NonValueClass(rI); + myInt[0] = otherObj; + result = test97((Object[])myInt); + Asserts.assertEquals(result, otherObj); + } + + @Test + public static Object test98(Object... args) { + return args[0]; + } + + @Run(test = "test98") + public static void test98_verifier(RunInfo info) { + Object obj = new Object(); + Object result = test98(obj); + Asserts.assertEquals(result, obj); + NonValueClass[] myInt = new NonValueClass[1]; + NonValueClass otherObj = new NonValueClass(rI); + myInt[0] = otherObj; + result = test98((Object[])myInt); + Asserts.assertEquals(result, otherObj); + if (!info.isWarmUp()) { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + MyValue1[] vab = new MyValue1[1]; + result = test98((Object[])va); + Asserts.assertEquals(((MyValue1)result).hash(), MyValue1.createDefaultInline().hash()); + result = test98((Object[])vab); + Asserts.assertEquals(result, null); + } + } + + @Test + public static Object test99(Object... args) { + return args[0]; + } + + @Run(test = "test99") + public static void test99_verifier(RunInfo info) { + Object obj = new Object(); + Object result = test99(obj); + Asserts.assertEquals(result, obj); + NonValueClass[] myInt = new NonValueClass[1]; + NonValueClass otherObj = new NonValueClass(rI); + myInt[0] = otherObj; + result = test99((Object[])myInt); + Asserts.assertEquals(result, otherObj); + if (!info.isWarmUp()) { + try { + test99((Object[])null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException npe) { + // Expected + } + } + } + + @Test + public static Object test100(Object... args) { + return args[0]; + } + + @Run(test = "test100") + public static void test100_verifier(RunInfo info) { + Object obj = new Object(); + Object result = test100(obj); + Asserts.assertEquals(result, obj); + NonValueClass[] myInt = new NonValueClass[1]; + NonValueClass otherObj = new NonValueClass(rI); + myInt[0] = otherObj; + result = test100((Object[])myInt); + Asserts.assertEquals(result, otherObj); + if (!info.isWarmUp()) { + try { + test100(); + throw new RuntimeException("No AIOOBE thrown"); + } catch (ArrayIndexOutOfBoundsException aioobe) { + // Expected + } + } + } + + // Test stores to varag arrays + @Test + @IR(failOn = STORE_UNKNOWN_INLINE) + public static void test101(Object val, Object... args) { + args[0] = val; + } + + @Run(test = "test101") + public static void test101_verifier() { + Object obj = new Object(); + test101(obj, obj); + NonValueClass[] myInt = new NonValueClass[1]; + NonValueClass otherObj = new NonValueClass(rI); + test101(otherObj, (Object[])myInt); + Asserts.assertEquals(myInt[0], otherObj); + test101(null, (Object[])myInt); + Asserts.assertEquals(myInt[0], null); + } + + @Test + public static void test102(Object val, Object... args) { + args[0] = val; + } + + @Run(test = "test102") + public static void test102_verifier(RunInfo info) { + Object obj = new Object(); + test102(obj, obj); + NonValueClass[] myInt = new NonValueClass[1]; + NonValueClass otherObj = new NonValueClass(rI); + test102(otherObj, (Object[])myInt); + Asserts.assertEquals(myInt[0], otherObj); + test102(null, (Object[])myInt); + Asserts.assertEquals(myInt[0], null); + if (!info.isWarmUp()) { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + MyValue1[] vab = new MyValue1[1]; + test102(testValue1, (Object[])va); + Asserts.assertEquals(va[0].hash(), testValue1.hash()); + test102(testValue1, (Object[])vab); + Asserts.assertEquals(vab[0].hash(), testValue1.hash()); + test102(null, (Object[])vab); + Asserts.assertEquals(vab[0], null); + } + } + + @Test + public static void test103(Object val, Object... args) { + args[0] = val; + } + + @Run(test = "test103") + public static void test103_verifier(RunInfo info) { + Object obj = new Object(); + test103(obj, obj); + NonValueClass[] myInt = new NonValueClass[1]; + NonValueClass otherObj = new NonValueClass(rI); + test103(otherObj, (Object[])myInt); + Asserts.assertEquals(myInt[0], otherObj); + test103(null, (Object[])myInt); + Asserts.assertEquals(myInt[0], null); + if (!info.isWarmUp()) { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + try { + test103(null, (Object[])va); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException npe) { + // Expected + } + } + } + + @Test + public static void test104(Object val, Object... args) { + args[0] = val; + } + + @Run(test = "test104") + public static void test104_verifier(RunInfo info) { + Object obj = new Object(); + test104(obj, obj); + NonValueClass[] myInt = new NonValueClass[1]; + NonValueClass otherObj = new NonValueClass(rI); + test104(otherObj, (Object[])myInt); + Asserts.assertEquals(myInt[0], otherObj); + test104(null, (Object[])myInt); + Asserts.assertEquals(myInt[0], null); + if (!info.isWarmUp()) { + try { + test104(testValue1); + throw new RuntimeException("No AIOOBE thrown"); + } catch (ArrayIndexOutOfBoundsException aioobe) { + // Expected + } + } + } + + @Test + public static void test105(Object val, Object... args) { + args[0] = val; + } + + @Run(test = "test105") + public static void test105_verifier(RunInfo info) { + Object obj = new Object(); + test105(obj, obj); + NonValueClass[] myInt = new NonValueClass[1]; + NonValueClass otherObj = new NonValueClass(rI); + test105(otherObj, (Object[])myInt); + Asserts.assertEquals(myInt[0], otherObj); + test105(null, (Object[])myInt); + Asserts.assertEquals(myInt[0], null); + if (!info.isWarmUp()) { + try { + test105(testValue1, (Object[])null); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException npe) { + // Expected + } + } + } + + @Test + public static Object[] test106(Object[] dst, Object... args) { + // Access array to speculate on non-flatness + if (args[0] == null) { + args[0] = testValue1; + } + System.arraycopy(args, 0, dst, 0, args.length); + System.arraycopy(dst, 0, args, 0, dst.length); + Object[] clone = args.clone(); + if (clone[0] == null) { + throw new RuntimeException("Unexpected null"); + } + return Arrays.copyOf(args, args.length, Object[].class); + } + + @Run(test = "test106") + public static void test106_verifier(RunInfo info) { + Object[] dst = new Object[1]; + Object obj = new Object(); + Object[] result = test106(dst, obj); + Asserts.assertEquals(result[0], obj); + NonValueClass[] myInt = new NonValueClass[1]; + NonValueClass otherObj = new NonValueClass(rI); + myInt[0] = otherObj; + result = test106(myInt, (Object[])myInt); + Asserts.assertEquals(result[0], otherObj); + if (!info.isWarmUp()) { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + MyValue1[] vab = new MyValue1[1]; + result = test106(va, (Object[])va); + Asserts.assertEquals(((MyValue1)result[0]).hash(), MyValue1.createDefaultInline().hash()); + result = test106(vab, (Object[])vab); + Asserts.assertEquals(((MyValue1)result[0]).hash(), testValue1.hash()); + } + } + + // TODO 8325632 Fails with "matching stack sizes" in Scenario 5 with -XX:TypeProfileLevel=222 + /* + // Test that allocation is not replaced by non-dominating allocation + @ForceInline + public long test107_helper(MyValue1[] va, MyValue1 vt) { + try { + va[0] = vt; + } catch (NullPointerException npe) { } + return va[1].hash(); + } + + @Test + public void test107() { + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 2, MyValue1.DEFAULT); + MyValue1[] tmp = new MyValue1[2]; + long res1 = test107_helper(va, testValue1); + long res2 = test107_helper(va, testValue1); + Asserts.assertEquals(va[0].hash(), testValue1.hash()); + Asserts.assertEquals(res1, MyValue1.createDefaultInline().hash()); + Asserts.assertEquals(res2, MyValue1.createDefaultInline().hash()); + } + + @Run(test = "test107") + public void test107_verifier() { + test107(); + } + */ + + @Test + public Object test108(MyValue1[] src, boolean flag) { + MyValue1[] dst = new MyValue1[8]; + System.arraycopy(src, 1, dst, 2, 6); + if (flag) {} // uncommon trap + return dst[2]; + } + + @Run(test = "test108") + @Warmup(10000) + public void test108_verifier(RunInfo info) { + MyValue1[] src = new MyValue1[8]; + test108(src, !info.isWarmUp()); + } + + // Test LoadNode::can_see_arraycopy_value optimization + @Test + public static void test109() { + MyValue1[] src = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + MyValue1[] dst = new MyValue1[1]; + src[0] = testValue1; + System.arraycopy(src, 0, dst, 0, 1); + Asserts.assertEquals(src[0], dst[0]); + } + + @Run(test = "test109") + public void test109_verifier() { + test109(); + } + + // Same as test109 but with Object destination array + @Test + public static void test110() { + MyValue1[] src = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + Object[] dst = new Object[1]; + src[0] = testValue1; + System.arraycopy(src, 0, dst, 0, 1); + Asserts.assertEquals(src[0], dst[0]); + } + + @Run(test = "test110") + public void test110_verifier() { + test110(); + } + + // Same as test109 but with Arrays.copyOf + @Test + public static void test111() { + MyValue1[] src = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + src[0] = testValue1; + MyValue1[] dst = Arrays.copyOf(src, src.length, MyValue1[].class); + Asserts.assertEquals(src[0], dst[0]); + } + + @Run(test = "test111") + public void test111_verifier() { + test111(); + } + + static final MyValue1[] refArray = new MyValue1[2]; + static final MyValue1[] flatArray = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 1, MyValue1.DEFAULT); + + // Test scalarization + @Test + @IR(failOn = {ALLOC, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public int test112(boolean b) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + if (b) { + val = refArray[0]; + } + return val.x; + } + + @Run(test = "test112") + public void test112_verifier(RunInfo info) { + refArray[0] = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test112(true), refArray[0].x); + Asserts.assertEquals(test112(false), testValue1.x); + if (!info.isWarmUp()) { + refArray[0] = null; + try { + Asserts.assertEquals(test112(false), testValue1.x); + test112(true); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + } + + // Same as test112 but with call to hash() + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test113(boolean b) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + if (b) { + val = refArray[0]; + } + return val.hash(); + } + + @Run(test = "test113") + public void test113_verifier(RunInfo info) { + refArray[0] = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test113(true), refArray[0].hash()); + Asserts.assertEquals(test113(false), testValue1.hash()); + if (!info.isWarmUp()) { + refArray[0] = null; + try { + Asserts.assertEquals(test113(false), testValue1.hash()); + test113(true); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + } + + @Test + public MyValue1 test114(boolean b) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + if (b) { + val = refArray[0]; + } + return val; + } + + @Run(test = "test114") + public void test114_verifier(RunInfo info) { + refArray[0] = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test114(true).hash(), refArray[0].hash()); + Asserts.assertEquals(test114(false).hash(), testValue1.hash()); + if (!info.isWarmUp()) { + refArray[0] = null; + Asserts.assertEquals(test114(true), null); + } + } + + // Test scalarization when .ref is referenced in safepoint debug info + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public int test115(boolean b1, boolean b2, Method m) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + if (b1) { + val = refArray[0]; + } + if (b2) { + // Uncommon trap + TestFramework.deoptimize(m); + } + return val.x; + } + + @Run(test = "test115") + public void test115_verifier(RunInfo info) { + refArray[0] = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test115(true, false, info.getTest()), refArray[0].x); + Asserts.assertEquals(test115(false, false, info.getTest()), testValue1.x); + if (!info.isWarmUp()) { + refArray[0] = null; + try { + Asserts.assertEquals(test115(false, false, info.getTest()), testValue1.x); + test115(true, false, info.getTest()); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + refArray[0] = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test115(true, true, info.getTest()), refArray[0].x); + Asserts.assertEquals(test115(false, true, info.getTest()), testValue1.x); + } + } + + @Test + public MyValue1 test116(boolean b1, boolean b2, Method m) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + if (b1) { + val = refArray[0]; + } + if (b2) { + // Uncommon trap + TestFramework.deoptimize(m); + } + return val; + } + + @Run(test = "test116") + public void test116_verifier(RunInfo info) { + refArray[0] = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test116(true, false, info.getTest()).hash(), refArray[0].hash()); + Asserts.assertEquals(test116(false, false, info.getTest()).hash(), testValue1.hash()); + if (!info.isWarmUp()) { + refArray[0] = null; + Asserts.assertEquals(test116(true, false, info.getTest()), null); + refArray[0] = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test116(true, true, info.getTest()).hash(), refArray[0].hash()); + Asserts.assertEquals(test116(false, true, info.getTest()).hash(), testValue1.hash()); + } + } + + @Test + @IR(failOn = {ALLOC, STORE_OF_ANY_KLASS}) + public int test117(boolean b) { + MyValue1 val = null; + if (b) { + val = refArray[0]; + } + return val.x; + } + + @Run(test = "test117") + public void test117_verifier() { + refArray[0] = testValue1; + Asserts.assertEquals(test117(true), testValue1.x); + try { + test117(false); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + public MyValue1 test118(boolean b) { + MyValue1 val = null; + if (b) { + val = refArray[0]; + } + return val; + } + + @Run(test = "test118") + public void test118_verifier() { + refArray[0] = testValue1; + Asserts.assertEquals(test118(true).hash(), testValue1.hash()); + Asserts.assertEquals(test118(false), null); + } + + @Test + @IR(failOn = {ALLOC, STORE_OF_ANY_KLASS}) + public int test119(boolean b) { + MyValue1 val = refArray[0]; + if (b) { + val = null; + } + return val.x; + } + + @Run(test = "test119") + public void test119_verifier() { + refArray[0] = testValue1; + Asserts.assertEquals(test119(false), testValue1.x); + try { + test119(true); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + public MyValue1 test120(boolean b) { + MyValue1 val = refArray[0]; + if (b) { + val = null; + } + return val; + } + + @Run(test = "test120") + public void test120_verifier() { + refArray[0] = testValue1; + Asserts.assertEquals(test120(false).hash(), testValue1.hash()); + Asserts.assertEquals(test120(true), null); + } + + @ForceInline + public Object test121_helper() { + return flatArray[0]; + } + + @Test + @IR(applyIf = {"UseArrayFlattening", "true"}, + failOn = {ALLOC}, + counts = {STORE_OF_ANY_KLASS, "= 19"}) + public void test121(boolean b) { + Object o = null; + if (b) { + o = refArray[0]; + } else { + o = test121_helper(); + } + if (o == null) { + return; + } + flatArray[0] = (MyValue1)o; + } + + @Run(test = "test121") + public void test121_verifier() { + refArray[0] = testValue1; + MyValue1 vt = MyValue1.createWithFieldsInline(rI+1, rL+1); + flatArray[0] = vt; + test121(false); + Asserts.assertEquals(flatArray[0].hash(), vt.hash()); + test121(true); + Asserts.assertEquals(flatArray[0].hash(), testValue1.hash()); + } + + @ForceInline + public Object test122_helper() { + return refArray[0]; + } + + @Test + @IR(applyIf = {"UseArrayFlattening", "true"}, + failOn = {ALLOC}, + counts = {STORE_OF_ANY_KLASS, "= 19"}) + public void test122(boolean b) { + Object o = null; + if (b) { + o = flatArray[0]; + } else { + o = test122_helper(); + } + if (o == null) { + return; + } + flatArray[0] = (MyValue1)o; + } + + @Run(test = "test122") + public void test122_verifier() { + refArray[0] = testValue1; + test122(false); + Asserts.assertEquals(flatArray[0].hash(), testValue1.hash()); + MyValue1 vt = MyValue1.createWithFieldsInline(rI+1, rL+1); + flatArray[0] = vt; + test122(true); + Asserts.assertEquals(flatArray[0].hash(), vt.hash()); + } + + @ForceInline + public Object test123_helper() { + return refArray[0]; + } + + @Test + @IR(failOn = {ALLOC, STORE_OF_ANY_KLASS}) + public long test123(boolean b, MyValue1 val, Method m, boolean deopt) { + MyValue1[] array = new MyValue1[1]; + array[0] = val; + Object res = null; + if (b) { + res = array[0]; + } else { + res = test123_helper(); + } + if (deopt) { + // uncommon trap + TestFramework.deoptimize(m); + } + return ((MyValue1)res).hash(); + } + + @Run(test = "test123") + public void test123_verifier(RunInfo info) { + refArray[0] = MyValue1.createDefaultInline(); + Asserts.assertEquals(test123(true, testValue1, info.getTest(), false), testValue1.hash()); + Asserts.assertEquals(test123(false, testValue1, info.getTest(), false), MyValue1.createDefaultInline().hash()); + if (!info.isWarmUp()) { + Asserts.assertEquals(test123(true, testValue1, info.getTest(), true), testValue1.hash()); + } + } + + @ForceInline + public Object test124_helper(MyValue2 val) { + MyValue2[] array = new MyValue2[1]; + array[0] = val; + return array[0]; + } + + @Test + @IR(failOn = {ALLOC, STORE_OF_ANY_KLASS}) + public long test124(boolean b, MyValue2 val, Method m, boolean deopt) { + Object res = null; + if (b) { + res = MyValue2.createWithFieldsInline(rI+1, rD+1); + } else { + res = test124_helper(val); + } + if (deopt) { + // uncommon trap + TestFramework.deoptimize(m); + } + return ((MyValue2)res).hash(); + } + + @Run(test = "test124") + public void test124_verifier(RunInfo info) { + refArray[0] = MyValue1.createDefaultInline(); + MyValue2 val1 = MyValue2.createWithFieldsInline(rI, rD); + MyValue2 val2 = MyValue2.createWithFieldsInline(rI+1, rD+1); + Asserts.assertEquals(test124(true, val1, info.getTest(), false), val2.hash()); + Asserts.assertEquals(test124(false, val1, info.getTest(), false), val1.hash()); + if (!info.isWarmUp()) { + Asserts.assertEquals(test124(true, val1, info.getTest(), true), val2.hash()); + } + } + + @ForceInline + public void test125_helper(Object[] array, MyValue2 val) { + array[0] = val; + } + + @Test + @IR(failOn = {ALLOC, STORE_OF_ANY_KLASS}) + public long test125(boolean b, MyValue2 val, Method m, boolean deopt) { + Object[] res = new MyValue2[1]; + if (b) { + res[0] = MyValue2.createWithFieldsInline(rI+1, rD+1); + } else { + test125_helper(res, val); + } + val = ((MyValue2)res[0]); + if (deopt) { + // uncommon trap + TestFramework.deoptimize(m); + } + return val.hash(); + } + + @Run(test = "test125") + public void test125_verifier(RunInfo info) { + refArray[0] = MyValue1.createDefaultInline(); + MyValue2 val1 = MyValue2.createWithFieldsInline(rI, rD); + MyValue2 val2 = MyValue2.createWithFieldsInline(rI+1, rD+1); + Asserts.assertEquals(test125(true, val1, info.getTest(), false), val2.hash()); + Asserts.assertEquals(test125(false, val1, info.getTest(), false), val1.hash()); + if (!info.isWarmUp()) { + Asserts.assertEquals(test125(true, val1, info.getTest(), true), val2.hash()); + } + } + + static Object oFld = null; + + static value class MyValue126 { + int x; + + MyValue126(int x) { + this.x = x; + } + } + + // Test that result of access to unknown flat array is not marked as null-free + @Test + public void test126(Object[] array, int i) { + oFld = array[i]; + } + + @Run(test = "test126") + @Warmup(0) + public void test126_verifier() { + MyValue126[] array = (MyValue126[]) ValueClass.newNullableAtomicArray(MyValue126.class, 2); + array[1] = new MyValue126(rI); + test126(array, 1); + Asserts.assertEquals(oFld, new MyValue126(rI)); + test126(array, 0); + Asserts.assertEquals(oFld, null); + } + + // Same as test126 but different failure mode + @Test + public void test127(Object[] array, int i) { + oFld = (MyValue126)array[i]; + } + + @Run(test = "test127") + @Warmup(0) + public void test127_verifier() { + MyValue126[] array = (MyValue126[]) ValueClass.newNullableAtomicArray(MyValue126.class, 2); + array[1] = new MyValue126(rI); + test127(array, 1); + Asserts.assertEquals(oFld, new MyValue126(rI)); + test127(array, 0); + Asserts.assertEquals(oFld, null); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestNullableInlineTypes.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestNullableInlineTypes.java new file mode 100644 index 00000000000..f17c492feb5 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestNullableInlineTypes.java @@ -0,0 +1,3353 @@ +/* + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.*; +import jdk.test.lib.Asserts; +import test.java.lang.invoke.lib.InstructionHelper; + +import java.util.Objects; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.ALLOC_OF_MYVALUE_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.LOAD_OF_ANY_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.STORE_OF_ANY_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypes.*; + +import static compiler.lib.ir_framework.IRNode.ALLOC; +import static compiler.lib.ir_framework.IRNode.CMP_N; +import static compiler.lib.ir_framework.IRNode.CMP_P; +import static compiler.lib.ir_framework.IRNode.PREDICATE_TRAP; +import static compiler.lib.ir_framework.IRNode.UNSTABLE_IF_TRAP; + +/* + * @test + * @key randomness + * @summary Test correct handling of nullable value classes. + * @library /test/lib /test/jdk/java/lang/invoke/common / + * @requires (os.simpleArch == "x64" | os.simpleArch == "aarch64") + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @build test.java.lang.invoke.lib.InstructionHelper + * @run main/othervm/timeout=1000 compiler.valhalla.inlinetypes.TestNullableInlineTypes + */ + +@ForceCompileClassInitializer +public class TestNullableInlineTypes { + + public static void main(String[] args) { + + Scenario[] scenarios = InlineTypes.DEFAULT_SCENARIOS; + scenarios[3].addFlags("-XX:-MonomorphicArrayCheck", "-XX:+UseArrayFlattening"); + scenarios[4].addFlags("-XX:-MonomorphicArrayCheck"); + + InlineTypes.getFramework() + .addScenarios(scenarios) + .addHelperClasses(MyValue1.class, + MyValue2.class, + MyValue2Inline.class, + MyValue3.class, + MyValue3Inline.class) + .start(); + } + + static { + // Make sure RuntimeException is loaded to prevent uncommon traps in IR verified tests + RuntimeException tmp = new RuntimeException("42"); + try { + Class clazz = TestNullableInlineTypes.class; + MethodHandles.Lookup lookup = MethodHandles.lookup(); + + MethodType test18_mt = MethodType.methodType(void.class, MyValue1.class); + test18_mh1 = lookup.findStatic(clazz, "test18_target1", test18_mt); + test18_mh2 = lookup.findStatic(clazz, "test18_target2", test18_mt); + + MethodType test19_mt = MethodType.methodType(void.class, MyValue1.class); + test19_mh1 = lookup.findStatic(clazz, "test19_target1", test19_mt); + test19_mh2 = lookup.findStatic(clazz, "test19_target2", test19_mt); + } catch (NoSuchMethodException | IllegalAccessException e) { + e.printStackTrace(); + throw new RuntimeException("Method handle lookup failed"); + } + } + + @Strict + @NullRestricted + private static final MyValue1 testValue1 = MyValue1.createWithFieldsInline(rI, rL); + + private static final MyValue1[] testValue1Array = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 3, MyValue1.DEFAULT); + static { + for (int i = 0; i < 3; ++i) { + testValue1Array[i] = testValue1; + } + } + + MyValue1 nullField; + + @Strict + @NullRestricted + MyValue1 valueField1 = testValue1; + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public long test1(MyValue1 vt) { + long result = 0; + try { + result = vt.hash(); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + return result; + } + + @Run(test = "test1") + public void test1_verifier() { + long result = test1(null); + Asserts.assertEquals(result, 0L); + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public long test2(MyValue1 vt) { + long result = 0; + try { + result = vt.hashInterpreted(); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + return result; + } + + @Run(test = "test2") + public void test2_verifier() { + long result = test2(null); + Asserts.assertEquals(result, 0L); + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public long test3() { + long result = 0; + try { + if ((Object)nullField != null) { + throw new RuntimeException("nullField should be null"); + } + result = nullField.hash(); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + return result; + } + + @Run(test = "test3") + public void test3_verifier() { + long result = test3(); + Asserts.assertEquals(result, 0L); + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public void test4() { + try { + valueField1 = nullField; + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + + @Run(test = "test4") + public void test4_verifier() { + test4(); + } + + @Test + // TODO 8284443 When passing vt to test5_inline and incrementally inlining, we lose the oop + @IR(applyIfOr = {"InlineTypePassFieldsAsArgs", "false", "AlwaysIncrementalInline", "false"}, + failOn = {ALLOC_OF_MYVALUE_KLASS}) + public MyValue1 test5(MyValue1 vt) { + Object o = vt; + vt = (MyValue1)o; + vt = test5_dontinline(vt); + vt = test5_inline(vt); + return vt; + } + + @Run(test = "test5") + public void test5_verifier() { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + Asserts.assertEquals(test5(val), val); + Asserts.assertEquals(test5(null), null); + } + + @DontInline + public MyValue1 test5_dontinline(MyValue1 vt) { + return vt; + } + + @ForceInline + public MyValue1 test5_inline(MyValue1 vt) { + return vt; + } + + @Test + public MyValue1 test6(Object obj) { + MyValue1 vt = MyValue1.createWithFieldsInline(rI, rL); + try { + vt = (MyValue1)Objects.requireNonNull(obj); + } catch (NullPointerException e) { + // Expected + } + return vt; + } + + @Run(test = "test6") + public void test6_verifier() { + MyValue1 vt = test6(null); + Asserts.assertEquals(vt.hash(), testValue1.hash()); + } + + @ForceInline + public MyValue1 getNullInline() { + return null; + } + + @DontInline + public MyValue1 getNullDontInline() { + return null; + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public void test7() { + nullField = getNullInline(); // Should not throw + nullField = getNullDontInline(); // Should not throw + try { + valueField1 = getNullInline(); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + try { + valueField1 = getNullDontInline(); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + + @Run(test = "test7") + public void test7_verifier() { + test7(); + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public void test8() { + try { + valueField1 = nullField; + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + + @Run(test = "test8") + public void test8_verifier() { + test8(); + } + + // Merge of two value objects, one being null + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public void test9(boolean flag) { + MyValue1 v; + if (flag) { + v = valueField1; + } else { + v = nullField; + } + valueField1 = v; + } + + @Run(test = "test9") + public void test9_verifier() { + test9(true); + try { + test9(false); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + + // null constant + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public void test10(boolean flag) { + MyValue1 val = flag ? valueField1 : null; + valueField1 = val; + } + + @Run(test = "test10") + public void test10_verifier() { + test10(true); + try { + test10(false); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + + // null constant + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public void test11(boolean flag) { + MyValue1 val = flag ? null : valueField1; + valueField1 = val; + } + + @Run(test = "test11") + public void test11_verifier() { + test11(false); + try { + test11(true); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + + // null return + int test12_cnt; + + @DontInline + public MyValue1 test12_helper() { + test12_cnt++; + return nullField; + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public void test12() { + valueField1 = test12_helper(); + } + + @Run(test = "test12") + public void test12_verifier() { + try { + test12_cnt = 0; + test12(); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + if (test12_cnt != 1) { + throw new RuntimeException("call executed twice"); + } + } + + // null return at virtual call + class A { + public MyValue1 test13_helper() { + return nullField; + } + } + + class B extends A { + public MyValue1 test13_helper() { + return nullField; + } + } + + class C extends A { + public MyValue1 test13_helper() { + return nullField; + } + } + + class D extends C { + public MyValue1 test13_helper() { + return nullField; + } + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public void test13(A a) { + valueField1 = a.test13_helper(); + } + + @Run(test = "test13") + public void test13_verifier() { + A a = new A(); + A b = new B(); + A c = new C(); + A d = new D(); + try { + test13(a); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + try { + test13(b); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + try { + test13(c); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + try { + test13(d); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + + // Test writing null to a (flat) value class array + @ForceInline + public void test14_inline(Object[] oa, Object o, int index) { + oa[index] = o; + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public void test14(MyValue1[] va, int index) { + test14_inline(va, nullField, index); + } + + @Run(test = "test14") + public void test14_verifier() { + int index = Math.abs(rI) % 3; + try { + test14(testValue1Array, index); + throw new RuntimeException("No NPE thrown"); + } catch (NullPointerException e) { + // Expected + } + Asserts.assertEQ(testValue1Array[index].hash(), testValue1.hash()); + } + + @DontInline + MyValue1 getNullField1() { + return nullField; + } + + @DontInline + MyValue1 getNullField2() { + return null; + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public void test15() { + nullField = getNullField1(); // should not throw + try { + valueField1 = getNullField1(); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + try { + valueField1 = getNullField2(); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + + @Run(test = "test15") + public void test15_verifier() { + test15(); + } + + @DontInline + public boolean test16_dontinline(MyValue1 vt) { + return vt == null; + } + + // Test c2c call passing null for a value class + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public boolean test16(Object arg) throws Exception { + Method test16method = getClass().getMethod("test16_dontinline", MyValue1.class); + return (boolean)test16method.invoke(this, arg); + } + + @Run(test = "test16") + @Warmup(10000) // Warmup to make sure 'test17_dontinline' is compiled + public void test16_verifier() throws Exception { + boolean res = test16(null); + Asserts.assertTrue(res); + } + + // Test scalarization of value class with non-flattenable field + @LooselyConsistentValue + final value class Test17Value { + public final MyValue1 valueField; + + @ForceInline + public Test17Value(MyValue1 valueField) { + this.valueField = valueField; + } + } + + @Test + // TODO 8284443 When passing testValue1 to the constructor in scalarized form and incrementally inlining, we lose the oop + @IR(applyIfOr = {"InlineTypePassFieldsAsArgs", "false", "AlwaysIncrementalInline", "false"}, + failOn = {ALLOC_OF_MYVALUE_KLASS}) + public Test17Value test17(boolean b) { + Test17Value vt1 = new Test17Value(null); + Test17Value vt2 = new Test17Value(testValue1); + return b ? vt1 : vt2; + } + + @Run(test = "test17") + public void test17_verifier() { + test17(true); + test17(false); + } + + static final MethodHandle test18_mh1; + static final MethodHandle test18_mh2; + + static MyValue1 nullValue; + + @DontInline + static void test18_target1(MyValue1 vt) { + nullValue = vt; + } + + @ForceInline + static void test18_target2(MyValue1 vt) { + nullValue = vt; + } + + // Test passing null for a value class + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public void test18() throws Throwable { + test18_mh1.invokeExact(nullValue); + test18_mh2.invokeExact(nullValue); + } + + @Run(test = "test18") + @Warmup(11000) // Make sure lambda forms get compiled + public void test18_verifier() { + try { + test18(); + } catch (Throwable t) { + throw new RuntimeException("test18 failed", t); + } + } + + static MethodHandle test19_mh1; + static MethodHandle test19_mh2; + + @DontInline + static void test19_target1(MyValue1 vt) { + nullValue = vt; + } + + @ForceInline + static void test19_target2(MyValue1 vt) { + nullValue = vt; + } + + // Same as test12 but with non-final mh + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public void test19() throws Throwable { + test19_mh1.invokeExact(nullValue); + test19_mh2.invokeExact(nullValue); + } + + @Run(test = "test19") + @Warmup(11000) // Make sure lambda forms get compiled + public void test19_verifier() { + try { + test19(); + } catch (Throwable t) { + throw new RuntimeException("test19 failed", t); + } + } + + // Same as test12/13 but with constant null + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public void test20(MethodHandle mh) throws Throwable { + mh.invoke(null); + } + + @Run(test = "test20") + @Warmup(11000) // Make sure lambda forms get compiled + public void test20_verifier() { + try { + test20(test18_mh1); + test20(test18_mh2); + test20(test19_mh1); + test20(test19_mh2); + } catch (Throwable t) { + throw new RuntimeException("test20 failed", t); + } + } + + // Test writing null to a flattenable/non-flattenable value class field in a value class + @LooselyConsistentValue + value class Test21Value { + MyValue1 valueField1; + @Strict + @NullRestricted + MyValue1 valueField2; + + @ForceInline + public Test21Value(MyValue1 valueField1, MyValue1 valueField2) { + this.valueField1 = valueField1; + this.valueField2 = valueField2; + } + + @ForceInline + public Test21Value test1() { + return new Test21Value(null, this.valueField2); // Should not throw NPE + } + + @ForceInline + public Test21Value test2() { + return new Test21Value(this.valueField1, null); // Should throw NPE + } + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public Test21Value test21(Test21Value vt) { + vt = vt.test1(); + try { + vt = vt.test2(); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + return vt; + } + + @Run(test = "test21") + public void test21_verifier() { + test21(new Test21Value(null, MyValue1.createDefaultInline())); + } + + @DontInline + public MyValue1 test22_helper() { + return nullField; + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public void test22() { + valueField1 = test22_helper(); + } + + @Run(test = "test22") + public void test22_verifier() { + try { + test22(); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + @IR(applyIfAnd = {"UseArrayFlattening", "true", "InlineTypePassFieldsAsArgs", "true"}, + failOn = {ALLOC_OF_MYVALUE_KLASS}) + @IR(applyIfAnd = {"UseArrayFlattening", "false", "InlineTypePassFieldsAsArgs", "false"}, + failOn = {ALLOC_OF_MYVALUE_KLASS}) + public void test23(MyValue1 val) { + MyValue1[] arr = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, 2, MyValue1.DEFAULT); + arr[0] = val; + } + + @Run(test = "test23") + public void test23_verifier() { + MyValue1 val = null; + try { + test23(val); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + + static MyValue1 nullBox; + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public MyValue1 test24() { + return Objects.requireNonNull(nullBox); + } + + @Run(test = "test24") + public void test24_verifier() { + try { + test24(); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + + @DontInline + public void test25_callee(MyValue1 val) { } + + // Test that when checkcasting from null-ok to null-free and back to null-ok we + // keep track of the information that the value object can never be null. + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public int test25(boolean b, MyValue1 vt1, MyValue1 vt2) { + vt1 = (MyValue1)vt1; + Object obj = b ? vt1 : vt2; // We should not allocate here + test25_callee((MyValue1) vt1); + return ((MyValue1)obj).x; + } + + @Run(test = "test25") + public void test25_verifier(RunInfo info) { + int res = test25(true, testValue1, testValue1); + Asserts.assertEquals(res, testValue1.x); + res = test25(false, testValue1, testValue1); + Asserts.assertEquals(res, testValue1.x); + if (!info.isWarmUp()) { + try { + test25(true, null, testValue1); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + } + + // Test that chains of casts are folded and don't trigger an allocation + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public MyValue3 test26(MyValue3 vt) { + return ((MyValue3)((Object)((MyValue3)(MyValue3)((MyValue3)((Object)vt))))); + } + + @Run(test = "test26") + public void test26_verifier() { + MyValue3 vt = MyValue3.create(); + MyValue3 result = test26(vt); + Asserts.assertEquals(result, vt); + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public MyValue3 test27(MyValue3 vt) { + return ((MyValue3)((Object)((MyValue3)(MyValue3)((MyValue3)((Object)vt))))); + } + + @Run(test = "test27") + public void test27_verifier() { + MyValue3 vt = MyValue3.create(); + MyValue3 result = (MyValue3) test27(vt); + Asserts.assertEquals(result, vt); + } + + // Some more casting tests + @Test + public MyValue1 test28(MyValue1 vt, MyValue1 vtBox, int i) { + MyValue1 result = null; + if (i == 0) { + result = (MyValue1)vt; + result = null; + } else if (i == 1) { + result = (MyValue1)vt; + } else if (i == 2) { + result = vtBox; + } + return result; + } + + @Run(test = "test28") + public void test28_verifier() { + MyValue1 result = test28(testValue1, null, 0); + Asserts.assertEquals(result, null); + result = test28(testValue1, testValue1, 1); + Asserts.assertEquals(result, testValue1); + result = test28(testValue1, null, 2); + Asserts.assertEquals(result, null); + result = test28(testValue1, testValue1, 2); + Asserts.assertEquals(result, testValue1); + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public long test29(MyValue1 vt, MyValue1 vtBox) { + long result = 0; + for (int i = 0; i < 100; ++i) { + MyValue1 box; + if (i == 0) { + box = (MyValue1)vt; + box = null; + } else if (i < 99) { + box = (MyValue1)vt; + } else { + box = vtBox; + } + if (box != null) { + result += box.hash(); + } + } + return result; + } + + @Run(test = "test29") + public void test29_verifier() { + long result = test29(testValue1, null); + Asserts.assertEquals(result, testValue1.hash()*98); + result = test29(testValue1, testValue1); + Asserts.assertEquals(result, testValue1.hash()*99); + } + + // Test null check of value object receiver with incremental inlining + public long test30_callee(MyValue1 vt) { + long result = 0; + try { + result = vt.hashInterpreted(); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + return result; + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public long test30() { + return test30_callee(nullField); + } + + @Run(test = "test30") + public void test30_verifier() { + long result = test30(); + Asserts.assertEquals(result, 0L); + } + + // Test casting null to unloaded value class + value class Test31Value { + private int i = 0; + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public Object test31(Object o) { + return (Test31Value)o; + } + + @Run(test = "test31") + public void test31_verifier() { + test31(null); + } + + private static final MyValue1 constNullRefField = null; + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public MyValue1 test32() { + return constNullRefField; + } + + @Run(test = "test32") + public void test32_verifier() { + MyValue1 result = test32(); + Asserts.assertEquals(result, null); + } + + @LooselyConsistentValue + static value class Test33Value1 { + int x = 0; + } + + @LooselyConsistentValue + static value class Test33Value2 { + Test33Value1 vt; + + public Test33Value2() { + vt = new Test33Value1(); + } + } + + @Strict + @NullRestricted + public static final Test33Value2 test33Val = new Test33Value2(); + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public Test33Value2 test33() { + return test33Val; + } + + @Run(test = "test33") + public void test33_verifier() { + Test33Value2 result = test33(); + Asserts.assertEquals(result, test33Val); + } + + // Verify that static nullable inline-type fields are not + // treated as never-null by C2 when initialized at compile time. + private static MyValue1 test34Val; + + @Test + public void test34(MyValue1 vt) { + if (test34Val == null) { + test34Val = vt; + } + } + + @Run(test = "test34") + public void test34_verifier(RunInfo info) { + test34(testValue1); + if (!info.isWarmUp()) { + test34Val = null; + test34(testValue1); + Asserts.assertEquals(test34Val, testValue1); + } + } + + // Same as test17 but with non-allocated value object + @Test + public Test17Value test35(boolean b) { + Test17Value vt1 = new Test17Value(null); + if ((Object)vt1.valueField != null) { + throw new RuntimeException("Should be null"); + } + MyValue1 vt3 = MyValue1.createWithFieldsInline(rI, rL); + Test17Value vt2 = new Test17Value(vt3); + return b ? vt1 : vt2; + } + + @Run(test = "test35") + public void test35_verifier() { + test35(true); + test35(false); + } + + // Test that when explicitly null checking a value object, we keep + // track of the information that the value object can never be null. + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public int test37(boolean b, MyValue1 vt1, MyValue1 vt2) { + if (vt1 == null) { + return 0; + } + // vt1 should be scalarized because it's always non-null + Object obj = b ? vt1 : vt2; // We should not allocate vt2 here + test25_callee(vt1); + return ((MyValue1)obj).x; + } + + @Run(test = "test37") + public void test37_verifier() { + int res = test37(true, testValue1, testValue1); + Asserts.assertEquals(res, testValue1.x); + res = test37(false, testValue1, testValue1); + Asserts.assertEquals(res, testValue1.x); + } + + // Test that when explicitly null checking a value object receiver, + // we keep track of the information that the value object can never be null. + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public int test38(boolean b, MyValue1 vt1, MyValue1 vt2) { + vt1.hash(); // Inlined - Explicit null check + // vt1 should be scalarized because it's always non-null + Object obj = b ? vt1 : vt2; // We should not allocate vt2 here + test25_callee(vt1); + return ((MyValue1)obj).x; + } + + @Run(test = "test38") + public void test38_verifier() { + int res = test38(true, testValue1, testValue1); + Asserts.assertEquals(res, testValue1.x); + res = test38(false, testValue1, testValue1); + Asserts.assertEquals(res, testValue1.x); + } + + // Test that when implicitly null checking a value object receiver, + // we keep track of the information that the value object can never be null. + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public int test39(boolean b, MyValue1 vt1, MyValue1 vt2) { + vt1.hashInterpreted(); // Not inlined - Implicit null check + // vt1 should be scalarized because it's always non-null + Object obj = b ? vt1 : vt2; // We should not allocate vt2 here + test25_callee(vt1); + return ((MyValue1)obj).x; + } + + @Run(test = "test39") + public void test39_verifier() { + int res = test39(true, testValue1, testValue1); + Asserts.assertEquals(res, testValue1.x); + res = test39(false, testValue1, testValue1); + Asserts.assertEquals(res, testValue1.x); + } + + // Test NPE when casting constant null to a value class + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public MyValue1 test40() { + Object NULL = null; + MyValue1 val = (MyValue1)NULL; + return Objects.requireNonNull(val); + } + + @Run(test = "test40") + public void test40_verifier() { + try { + test40(); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + + MyValue1 refField; + @Strict + @NullRestricted + MyValue1 flatField = MyValue1.DEFAULT; + + // Test scalarization of .ref + @Test + @IR(failOn = {ALLOC, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public int test41(boolean b) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + if (b) { + val = refField; + } + return val.x; + } + + @Run(test = "test41") + public void test41_verifier(RunInfo info) { + refField = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test41(true), refField.x); + Asserts.assertEquals(test41(false), testValue1.x); + if (!info.isWarmUp()) { + refField = null; + try { + Asserts.assertEquals(test41(false), testValue1.x); + test41(true); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + } + + // Same as test41 but with call to hash() + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test42(boolean b) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + if (b) { + val = refField; + } + return val.hash(); + } + + @Run(test = "test42") + public void test42_verifier(RunInfo info) { + refField = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test42(true), refField.hash()); + Asserts.assertEquals(test42(false), testValue1.hash()); + if (!info.isWarmUp()) { + refField = null; + try { + Asserts.assertEquals(test42(false), testValue1.hash()); + test42(true); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + } + + @Test + public MyValue1 test43(boolean b) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + if (b) { + val = refField; + } + return val; + } + + @Run(test = "test43") + public void test43_verifier(RunInfo info) { + refField = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test43(true).hash(), refField.hash()); + Asserts.assertEquals(test43(false).hash(), testValue1.hash()); + if (!info.isWarmUp()) { + refField = null; + Asserts.assertEquals(test43(true), null); + } + } + + // Test scalarization when .ref is referenced in safepoint debug info + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public int test44(boolean b1, boolean b2, Method m) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + if (b1) { + val = refField; + } + if (b2) { + // Uncommon trap + TestFramework.deoptimize(m); + } + return val.x; + } + + @Run(test = "test44") + public void test44_verifier(RunInfo info) { + refField = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test44(true, false, info.getTest()), refField.x); + Asserts.assertEquals(test44(false, false, info.getTest()), testValue1.x); + if (!info.isWarmUp()) { + refField = null; + try { + Asserts.assertEquals(test44(false, false, info.getTest()), testValue1.x); + test44(true, false, info.getTest()); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + refField = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test44(true, true, info.getTest()), refField.x); + Asserts.assertEquals(test44(false, true, info.getTest()), testValue1.x); + } + } + + @Test + public MyValue1 test45(boolean b1, boolean b2, Method m) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + if (b1) { + val = refField; + } + if (b2) { + // Uncommon trap + TestFramework.deoptimize(m); + } + return val; + } + + @Run(test = "test45") + public void test45_verifier(RunInfo info) { + refField = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test45(true, false, info.getTest()).hash(), refField.hash()); + Asserts.assertEquals(test45(false, false, info.getTest()).hash(), testValue1.hash()); + if (!info.isWarmUp()) { + refField = null; + Asserts.assertEquals(test45(true, false, info.getTest()), null); + refField = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test45(true, true, info.getTest()).hash(), refField.hash()); + Asserts.assertEquals(test45(false, true, info.getTest()).hash(), testValue1.hash()); + } + } + + @Test + @IR(failOn = {ALLOC, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public int test46(boolean b) { + MyValue1 val = null; + if (b) { + val = MyValue1.createWithFieldsInline(rI, rL); + } + return val.x; + } + + @Run(test = "test46") + public void test46_verifier() { + Asserts.assertEquals(test46(true), testValue1.x); + try { + test46(false); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + public MyValue1 test47(boolean b) { + MyValue1 val = null; + if (b) { + val = MyValue1.createWithFieldsInline(rI, rL); + } + return val; + } + + @Run(test = "test47") + public void test47_verifier() { + Asserts.assertEquals(test47(true).hash(), testValue1.hash()); + Asserts.assertEquals(test47(false), null); + } + + @Test + @IR(failOn = {ALLOC, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public int test48(boolean b) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + if (b) { + val = null; + } + return val.x; + } + + @Run(test = "test48") + public void test48_verifier() { + Asserts.assertEquals(test48(false), testValue1.x); + try { + test48(true); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + public MyValue1 test49(boolean b) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + if (b) { + val = null; + } + return val; + } + + @Run(test = "test49") + public void test49_verifier() { + Asserts.assertEquals(test49(false).hash(), testValue1.hash()); + Asserts.assertEquals(test49(true), null); + } + + @ForceInline + public Object test50_helper() { + return flatField; + } + + @Test + @IR(failOn = {ALLOC, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public void test50(boolean b) { + Object o = null; + if (b) { + o = testValue1; + } else { + o = test50_helper(); + } + flatField = (MyValue1)o; + } + + @Run(test = "test50") + public void test50_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI+1, rL+1); + flatField = vt; + test50(false); + Asserts.assertEquals(flatField.hash(), vt.hash()); + test50(true); + Asserts.assertEquals(flatField.hash(), testValue1.hash()); + } + + @LooselyConsistentValue + static value class MyValue1Wrapper { + MyValue1 vt; + + @ForceInline + public MyValue1Wrapper(MyValue1 vt) { + this.vt = vt; + } + + @ForceInline + public long hash() { + return (vt != null) ? vt.hash() : 0; + } + } + + @Strict + @NullRestricted + MyValue1Wrapper wrapperField = new MyValue1Wrapper(testValue1); + + @Test + @IR(failOn = {ALLOC, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test51(boolean b) { + MyValue1Wrapper val = new MyValue1Wrapper(null); + if (b) { + val = wrapperField; + } + return val.hash(); + } + + @Run(test = "test51") + public void test51_verifier() { + Asserts.assertEquals(test51(true), wrapperField.hash()); + Asserts.assertEquals(test51(false), (new MyValue1Wrapper(null)).hash()); + } + + @Test + @IR(failOn = {ALLOC, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public boolean test52(boolean b) { + MyValue1 val = MyValue1.createDefaultInline(); + if (b) { + val = null; + } + MyValue1Wrapper w = new MyValue1Wrapper(val); + return w.vt == null; + } + + @Run(test = "test52") + public void test52_verifier() { + Asserts.assertTrue(test52(true)); + Asserts.assertFalse(test52(false)); + } + + @Test + @IR(failOn = {ALLOC, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public boolean test53(boolean b) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + if (b) { + val = null; + } + MyValue1Wrapper w = new MyValue1Wrapper(val); + return w.vt == null; + } + + @Run(test = "test53") + public void test53_verifier() { + Asserts.assertTrue(test53(true)); + Asserts.assertFalse(test53(false)); + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test54(boolean b1, boolean b2) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + if (b1) { + val = null; + } + MyValue1Wrapper w = new MyValue1Wrapper(null); + if (b2) { + w = new MyValue1Wrapper(val); + } + return w.hash(); + } + + @Run(test = "test54") + public void test54_verifier() { + MyValue1Wrapper w = new MyValue1Wrapper(MyValue1.createWithFieldsInline(rI, rL)); + Asserts.assertEquals(test54(false, false), (new MyValue1Wrapper(null)).hash()); + Asserts.assertEquals(test54(false, true), w.hash()); + Asserts.assertEquals(test54(true, false), (new MyValue1Wrapper(null)).hash()); + Asserts.assertEquals(test54(true, true), 0L); + } + + @Test + @IR(failOn = {ALLOC, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public int test55(boolean b) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + MyValue1Wrapper w = new MyValue1Wrapper(val); + if (b) { + w = new MyValue1Wrapper(refField); + } + return w.vt.x; + } + + @Run(test = "test55") + public void test55_verifier(RunInfo info) { + refField = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test55(true), refField.x); + Asserts.assertEquals(test55(false), testValue1.x); + if (!info.isWarmUp()) { + refField = null; + try { + Asserts.assertEquals(test55(false), testValue1.x); + test55(true); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + } + + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test56(boolean b) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + MyValue1Wrapper w = new MyValue1Wrapper(val); + if (b) { + w = new MyValue1Wrapper(refField); + } + return w.vt.hash(); + } + + @Run(test = "test56") + public void test56_verifier(RunInfo info) { + refField = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test56(true), refField.hash()); + Asserts.assertEquals(test56(false), testValue1.hash()); + if (!info.isWarmUp()) { + refField = null; + try { + Asserts.assertEquals(test56(false), testValue1.hash()); + test56(true); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + } + + @Test + public MyValue1 test57(boolean b) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + MyValue1Wrapper w = new MyValue1Wrapper(val); + if (b) { + w = new MyValue1Wrapper(refField); + } + return w.vt; + } + + @Run(test = "test57") + public void test57_verifier(RunInfo info) { + refField = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test57(true).hash(), refField.hash()); + Asserts.assertEquals(test57(false).hash(), testValue1.hash()); + if (!info.isWarmUp()) { + refField = null; + Asserts.assertEquals(test57(true), null); + } + } + + // Test scalarization when .ref is referenced in safepoint debug info + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public int test58(boolean b1, boolean b2, Method m) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + MyValue1Wrapper w = new MyValue1Wrapper(val); + if (b1) { + w = new MyValue1Wrapper(refField); + } + if (b2) { + // Uncommon trap + TestFramework.deoptimize(m); + } + return w.vt.x; + } + + @Run(test = "test58") + public void test58_verifier(RunInfo info) { + refField = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test58(true, false, info.getTest()), refField.x); + Asserts.assertEquals(test58(false, false, info.getTest()), testValue1.x); + if (!info.isWarmUp()) { + refField = null; + try { + Asserts.assertEquals(test58(false, false, info.getTest()), testValue1.x); + test58(true, false, info.getTest()); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + refField = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test58(true, true, info.getTest()), refField.x); + Asserts.assertEquals(test58(false, true, info.getTest()), testValue1.x); + } + } + + @Test + public MyValue1 test59(boolean b1, boolean b2, Method m) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + MyValue1Wrapper w = new MyValue1Wrapper(val); + if (b1) { + w = new MyValue1Wrapper(refField); + } + if (b2) { + // Uncommon trap + TestFramework.deoptimize(m); + } + return w.vt; + } + + @Run(test = "test59") + public void test59_verifier(RunInfo info) { + refField = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test59(true, false, info.getTest()).hash(), refField.hash()); + Asserts.assertEquals(test59(false, false, info.getTest()).hash(), testValue1.hash()); + if (!info.isWarmUp()) { + refField = null; + Asserts.assertEquals(test59(true, false, info.getTest()), null); + refField = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test59(true, true, info.getTest()).hash(), refField.hash()); + Asserts.assertEquals(test59(false, true, info.getTest()).hash(), testValue1.hash()); + } + } + + @Test + @IR(failOn = {ALLOC, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public int test60(boolean b) { + MyValue1Wrapper w = new MyValue1Wrapper(null); + if (b) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + w = new MyValue1Wrapper(val); + } + return w.vt.x; + } + + @Run(test = "test60") + public void test60_verifier() { + Asserts.assertEquals(test60(true), testValue1.x); + try { + test60(false); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + public MyValue1 test61(boolean b) { + MyValue1Wrapper w = new MyValue1Wrapper(null); + if (b) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + w = new MyValue1Wrapper(val); + } + return w.vt; + } + + @Run(test = "test61") + public void test61_verifier() { + Asserts.assertEquals(test61(true).hash(), testValue1.hash()); + Asserts.assertEquals(test61(false), null); + } + + @Test + @IR(failOn = {ALLOC, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public int test62(boolean b) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + MyValue1Wrapper w = new MyValue1Wrapper(val); + if (b) { + w = new MyValue1Wrapper(null); + } + return w.vt.x; + } + + @Run(test = "test62") + public void test62_verifier() { + Asserts.assertEquals(test62(false), testValue1.x); + try { + test62(true); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + public MyValue1 test63(boolean b) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + MyValue1Wrapper w = new MyValue1Wrapper(val); + if (b) { + w = new MyValue1Wrapper(null); + } + return w.vt; + } + + @Run(test = "test63") + public void test63_verifier() { + Asserts.assertEquals(test63(false).hash(), testValue1.hash()); + Asserts.assertEquals(test63(true), null); + } + + @ForceInline + public MyValue1 test64_helper() { + return flatField; + } + + @Test + @IR(failOn = {ALLOC, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public void test64(boolean b) { + MyValue1Wrapper w = new MyValue1Wrapper(null); + if (b) { + w = new MyValue1Wrapper(testValue1); + } else { + w = new MyValue1Wrapper(test64_helper()); + } + flatField = w.vt; + } + + @Run(test = "test64") + public void test64_verifier() { + MyValue1 vt = MyValue1.createWithFieldsInline(rI+1, rL+1); + flatField = vt; + test64(false); + Asserts.assertEquals(flatField.hash(), vt.hash()); + test64(true); + Asserts.assertEquals(flatField.hash(), testValue1.hash()); + } + + @Test + @IR(failOn = {ALLOC, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS, UNSTABLE_IF_TRAP, PREDICATE_TRAP}) + public long test65(boolean b) { + MyValue1 val = MyValue1.createWithFieldsInline(rI, rL); + if (b) { + val = null; + } + if (val != null) { + return val.hashPrimitive(); + } + return 42; + } + + @Run(test = "test65") + public void test65_verifier() { + Asserts.assertEquals(test65(true), 42L); + Asserts.assertEquals(test65(false), MyValue1.createWithFieldsInline(rI, rL).hashPrimitive()); + } + + @ForceInline + public Object test66_helper(Object arg) { + return arg; + } + + // Test that .ref arg does not block scalarization + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public int test66(boolean b1, boolean b2, MyValue1 arg, Method m) { + Object val = MyValue1.createWithFieldsInline(rI, rL); + if (b1) { + val = test66_helper(arg); + } + if (b2) { + // Uncommon trap + TestFramework.deoptimize(m); + } + return ((MyValue1)val).x; + } + + @Run(test = "test66") + public void test66_verifier(RunInfo info) { + MyValue1 arg = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test66(true, false, arg, info.getTest()), arg.x); + Asserts.assertEquals(test66(false, false, arg, info.getTest()), testValue1.x); + if (!info.isWarmUp()) { + try { + Asserts.assertEquals(test66(false, false, arg, info.getTest()), testValue1.x); + test66(true, false, null, info.getTest()); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + Asserts.assertEquals(test66(true, true, arg, info.getTest()), arg.x); + Asserts.assertEquals(test66(false, true, arg, info.getTest()), testValue1.x); + } + } + + @DontInline + public MyValue1 test67_helper1() { + return refField; + } + + @ForceInline + public Object test67_helper2() { + return test67_helper1(); + } + + // Test that .ref return does not block scalarization + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public long test67(boolean b1, boolean b2, Method m) { + Object val = MyValue1.createWithFieldsInline(rI, rL); + if (b1) { + val = test67_helper2(); + } + if (b2) { + // Uncommon trap + TestFramework.deoptimize(m); + } + return ((MyValue1)val).hash(); + } + + @Run(test = "test67") + public void test67_verifier(RunInfo info) { + refField = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test67(true, false, info.getTest()), refField.hash()); + Asserts.assertEquals(test67(false, false, info.getTest()), testValue1.hash()); + if (!info.isWarmUp()) { + refField = null; + try { + Asserts.assertEquals(test67(false, false, info.getTest()), testValue1.hash()); + test67(true, false, info.getTest()); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + refField = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test67(true, true, info.getTest()), refField.hash()); + Asserts.assertEquals(test67(false, true, info.getTest()), testValue1.hash()); + } + } + + @ForceInline + public Object test68_helper(Object arg) { + MyValue1 tmp = (MyValue1)arg; // Result of cast is unused + return arg; + } + + // Test that scalarization enabled by cast is applied to parsing map + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public int test68(boolean b1, boolean b2, Object arg, Method m) { + Object val = MyValue1.createWithFieldsInline(rI, rL); + if (b1) { + val = test68_helper(arg); + } + if (b2) { + // Uncommon trap + TestFramework.deoptimize(m); + } + return ((MyValue1)val).x; + } + + @Run(test = "test68") + public void test68_verifier(RunInfo info) { + MyValue1 arg = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test68(true, false, arg, info.getTest()), arg.x); + Asserts.assertEquals(test68(false, false, arg, info.getTest()), testValue1.x); + if (!info.isWarmUp()) { + try { + Asserts.assertEquals(test68(false, false, arg, info.getTest()), testValue1.x); + test68(true, false, null, info.getTest()); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + Asserts.assertEquals(test68(true, true, arg, info.getTest()), arg.x); + Asserts.assertEquals(test68(false, true, arg, info.getTest()), testValue1.x); + } + } + + @ForceInline + public Object test69_helper(Object arg) { + MyValue1 tmp = (MyValue1)arg; // Result of cast is unused + return arg; + } + + // Same as test68 but with ClassCastException + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public int test69(boolean b1, boolean b2, Object arg, Method m) { + Object val = MyValue1.createWithFieldsInline(rI, rL); + if (b1) { + val = test69_helper(arg); + } + if (b2) { + // Uncommon trap + TestFramework.deoptimize(m); + } + return ((MyValue1)val).x; + } + + @Run(test = "test69") + @Warmup(10000) // Make sure precise profile information is available + public void test69_verifier(RunInfo info) { + MyValue1 arg = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test69(true, false, arg, info.getTest()), arg.x); + Asserts.assertEquals(test69(false, false, arg, info.getTest()), testValue1.x); + try { + test69(true, false, 42, info.getTest()); + throw new RuntimeException("ClassCastException expected"); + } catch (ClassCastException e) { + // Expected + } + if (!info.isWarmUp()) { + try { + Asserts.assertEquals(test69(false, false, arg, info.getTest()), testValue1.x); + test69(true, false, null, info.getTest()); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + Asserts.assertEquals(test69(true, true, arg, info.getTest()), arg.x); + Asserts.assertEquals(test69(false, true, arg, info.getTest()), testValue1.x); + } + } + + @ForceInline + public Object test70_helper(Object arg) { + MyValue1 tmp = (MyValue1)arg; // Result of cast is unused + return arg; + } + + // Same as test68 but with ClassCastException and frequent NullPointerException + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public int test70(boolean b1, boolean b2, Object arg, Method m) { + Object val = MyValue1.createWithFieldsInline(rI, rL); + if (b1) { + val = test70_helper(arg); + } + if (b2) { + // Uncommon trap + TestFramework.deoptimize(m); + } + return ((MyValue1)val).x; + } + + @Run(test = "test70") + @Warmup(10000) // Make sure precise profile information is available + public void test70_verifier(RunInfo info) { + MyValue1 arg = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test70(true, false, arg, info.getTest()), arg.x); + Asserts.assertEquals(test70(false, false, arg, info.getTest()), testValue1.x); + try { + test70(true, false, 42, info.getTest()); + throw new RuntimeException("ClassCastException expected"); + } catch (ClassCastException e) { + // Expected + } + try { + Asserts.assertEquals(test70(false, false, arg, info.getTest()), testValue1.x); + test70(true, false, null, info.getTest()); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + if (!info.isWarmUp()) { + Asserts.assertEquals(test70(true, true, arg, info.getTest()), arg.x); + Asserts.assertEquals(test70(false, true, arg, info.getTest()), testValue1.x); + } + } + + @ForceInline + public Object test71_helper(Object arg) { + MyValue1 tmp = (MyValue1)arg; // Result of cast is unused + return arg; + } + + // Same as test68 but with .ref cast + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public int test71(boolean b1, boolean b2, Object arg, Method m) { + Object val = MyValue1.createWithFieldsInline(rI, rL); + if (b1) { + val = test71_helper(arg); + } + if (b2) { + // Uncommon trap + TestFramework.deoptimize(m); + } + return ((MyValue1)val).x; + } + + @Run(test = "test71") + public void test71_verifier(RunInfo info) { + MyValue1 arg = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test71(true, false, arg, info.getTest()), arg.x); + Asserts.assertEquals(test71(false, false, arg, info.getTest()), testValue1.x); + if (!info.isWarmUp()) { + try { + Asserts.assertEquals(test71(false, false, arg, info.getTest()), testValue1.x); + test71(true, false, null, info.getTest()); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + Asserts.assertEquals(test71(true, true, arg, info.getTest()), arg.x); + Asserts.assertEquals(test71(false, true, arg, info.getTest()), testValue1.x); + } + } + + @ForceInline + public Object test72_helper(Object arg) { + MyValue1 tmp = (MyValue1)arg; // Result of cast is unused + return arg; + } + + // Same as test71 but with ClassCastException and hash() call + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public long test72(boolean b1, boolean b2, Object arg, Method m) { + Object val = MyValue1.createWithFieldsInline(rI, rL); + if (b1) { + val = test72_helper(arg); + } + if (b2) { + // Uncommon trap + TestFramework.deoptimize(m); + } + return ((MyValue1)val).hash(); + } + + @Run(test = "test72") + @Warmup(10000) // Make sure precise profile information is available + public void test72_verifier(RunInfo info) { + MyValue1 arg = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test72(true, false, arg, info.getTest()), arg.hash()); + Asserts.assertEquals(test72(false, false, arg, info.getTest()), testValue1.hash()); + try { + test72(true, false, 42, info.getTest()); + throw new RuntimeException("ClassCastException expected"); + } catch (ClassCastException e) { + // Expected + } + if (!info.isWarmUp()) { + try { + Asserts.assertEquals(test72(false, false, arg, info.getTest()), testValue1.hash()); + test72(true, false, null, info.getTest()); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + Asserts.assertEquals(test72(true, true, arg, info.getTest()), arg.hash()); + Asserts.assertEquals(test72(false, true, arg, info.getTest()), testValue1.hash()); + } + } + + @ForceInline + public Object test73_helper(Object arg) { + MyValue1 tmp = (MyValue1)arg; // Result of cast is unused + return arg; + } + + // Same as test71 but with ClassCastException and frequent NullPointerException + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public int test73(boolean b1, boolean b2, Object arg, Method m) { + Object val = MyValue1.createWithFieldsInline(rI, rL); + if (b1) { + val = test73_helper(arg); + } + if (b2) { + // Uncommon trap + TestFramework.deoptimize(m); + } + return ((MyValue1)val).x; + } + + @Run(test = "test73") + @Warmup(10000) // Make sure precise profile information is available + public void test73_verifier(RunInfo info) { + MyValue1 arg = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test73(true, false, arg, info.getTest()), arg.x); + Asserts.assertEquals(test73(false, false, arg, info.getTest()), testValue1.x); + try { + test73(true, false, 42, info.getTest()); + throw new RuntimeException("ClassCastException expected"); + } catch (ClassCastException e) { + // Expected + } + try { + Asserts.assertEquals(test73(false, false, arg, info.getTest()), testValue1.x); + test73(true, false, null, info.getTest()); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + if (!info.isWarmUp()) { + Asserts.assertEquals(test73(true, true, arg, info.getTest()), arg.x); + Asserts.assertEquals(test73(false, true, arg, info.getTest()), testValue1.x); + } + } + + @ForceInline + public Object test74_helper(Object arg) { + return (MyValue1)arg; + } + + // Same as test73 but result of cast is used and hash() is called + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public long test74(boolean b1, boolean b2, Object arg, Method m) { + Object val = MyValue1.createWithFieldsInline(rI, rL); + if (b1) { + val = test74_helper(arg); + } + if (b2) { + // Uncommon trap + TestFramework.deoptimize(m); + } + return ((MyValue1)val).hash(); + } + + @Run(test = "test74") + @Warmup(10000) // Make sure precise profile information is available + public void test74_verifier(RunInfo info) { + MyValue1 arg = MyValue1.createWithFieldsInline(rI+1, rL+1); + Asserts.assertEquals(test74(true, false, arg, info.getTest()), arg.hash()); + Asserts.assertEquals(test74(false, false, arg, info.getTest()), testValue1.hash()); + try { + test74(true, false, 42, info.getTest()); + throw new RuntimeException("ClassCastException expected"); + } catch (ClassCastException e) { + // Expected + } + try { + Asserts.assertEquals(test74(false, false, arg, info.getTest()), testValue1.hash()); + test74(true, false, null, info.getTest()); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + if (!info.isWarmUp()) { + Asserts.assertEquals(test74(true, true, arg, info.getTest()), arg.hash()); + Asserts.assertEquals(test74(false, true, arg, info.getTest()), testValue1.hash()); + } + } + + // Test new merge path being added for exceptional control flow + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public MyValue1 test75(MyValue1 vt, Object obj) { + try { + vt = (MyValue1)obj; + throw new RuntimeException("ClassCastException expected"); + } catch (ClassCastException e) { + // Expected + } + return vt; + } + + @Run(test = "test75") + public void test75_verifier() { + MyValue1 vt = testValue1; + MyValue1 result = test75(vt, Integer.valueOf(rI)); + Asserts.assertEquals(result.hash(), vt.hash()); + } + + @ForceInline + public Object test76_helper() { + return constNullRefField; + } + + // Test that constant null field does not block scalarization + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS}) + public long test76(boolean b1, boolean b2, Method m) { + Object val = MyValue1.createWithFieldsInline(rI, rL); + if (b1) { + val = test76_helper(); + } + if (b2) { + // Uncommon trap + TestFramework.deoptimize(m); + } + val = Objects.requireNonNull(val); + return ((MyValue1)val).hash(); + } + + @Run(test = "test76") + public void test76_verifier(RunInfo info) { + Asserts.assertEquals(test76(false, false, info.getTest()), testValue1.hash()); + try { + test76(true, false, info.getTest()); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + if (!info.isWarmUp()) { + Asserts.assertEquals(test76(false, true, info.getTest()), testValue1.hash()); + try { + test76(true, true, info.getTest()); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + } + + private static final Object constObjectValField = MyValue1.createWithFieldsInline(rI+1, rL+1); + + @ForceInline + public Object test77_helper() { + return constObjectValField; + } + + // Test that constant object field with value class content does not block scalarization + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS}) + public long test77(boolean b1, boolean b2, Method m) { + Object val = MyValue1.createWithFieldsInline(rI, rL); + if (b1) { + val = test77_helper(); + } + if (b2) { + // Uncommon trap + TestFramework.deoptimize(m); + } + return ((MyValue1)val).hash(); + } + + @Run(test = "test77") + public void test77_verifier(RunInfo info) { + Asserts.assertEquals(test77(true, false, info.getTest()), ((MyValue1)constObjectValField).hash()); + Asserts.assertEquals(test77(false, false, info.getTest()), testValue1.hash()); + if (!info.isWarmUp()) { + Asserts.assertEquals(test77(true, false, info.getTest()), ((MyValue1)constObjectValField).hash()); + Asserts.assertEquals(test77(false, false, info.getTest()), testValue1.hash()); + } + } + + @ForceInline + public Object test78_helper() { + return null; + } + + // Test that constant null does not block scalarization + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS}) + public long test78(boolean b1, boolean b2, Method m) { + Object val = MyValue1.createWithFieldsInline(rI, rL); + if (b1) { + val = test78_helper(); + } + if (b2) { + // Uncommon trap + TestFramework.deoptimize(m); + } + val = Objects.requireNonNull(val); + return ((MyValue1)val).hash(); + } + + @Run(test = "test78") + public void test78_verifier(RunInfo info) { + Asserts.assertEquals(test78(false, false, info.getTest()), testValue1.hash()); + try { + test78(true, false, info.getTest()); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + if (!info.isWarmUp()) { + Asserts.assertEquals(test78(false, true, info.getTest()), testValue1.hash()); + try { + test78(true, true, info.getTest()); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + } + + @ForceInline + public Object test79_helper() { + return null; + } + + // Same as test78 but will trigger different order of PhiNode inputs + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS}) + public long test79(boolean b1, boolean b2, Method m) { + Object val = test79_helper(); + if (b1) { + val = MyValue1.createWithFieldsInline(rI, rL); + } + if (b2) { + // Uncommon trap + TestFramework.deoptimize(m); + } + val = Objects.requireNonNull(val); + return ((MyValue1)val).hash(); + } + + @Run(test = "test79") + public void test79_verifier(RunInfo info) { + Asserts.assertEquals(test79(true, false, info.getTest()), testValue1.hash()); + try { + test79(false, false, info.getTest()); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + if (!info.isWarmUp()) { + Asserts.assertEquals(test79(true, true, info.getTest()), testValue1.hash()); + try { + test79(false, true, info.getTest()); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + } + } + +// TODO 8325632 Fails with -XX:+UnlockExperimentalVMOptions -XX:PerMethodSpecTrapLimit=0 -XX:PerMethodTrapLimit=0 +/* + @ForceInline + public Object test80_helper(Object obj, int i) { + if ((i % 2) == 0) { + return MyValue1.createWithFieldsInline(i, i); + } + return obj; + } + + // Test that phi nodes referencing themselves (loops) do not block scalarization + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS}) + public long test80() { + Object val = MyValue1.createWithFieldsInline(rI, rL); + for (int i = 0; i < 100; ++i) { + val = test80_helper(val, i); + } + return ((MyValue1)val).hash(); + } + + private long test80Result = 0; + + @Run(test = "test80") + public void test80_verifier() { + if (test80Result == 0) { + test80Result = test80(); + } + Asserts.assertEquals(test80(), test80Result); + } + + @ForceInline + public Object test81_helper(Object obj, int i) { + if ((i % 2) == 0) { + return MyValue1.createWithFieldsInline(i, i); + } + return obj; + } + + // Test nested loops + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS}) + public long test81() { + Object val = null; + for (int i = 0; i < 10; ++i) { + for (int j = 0; j < 10; ++j) { + for (int k = 0; k < 10; ++k) { + val = test81_helper(val, i + j + k); + } + val = test81_helper(val, i + j); + } + val = test81_helper(val, i); + } + return ((MyValue1)val).hash(); + } + + private long test81Result = 0; + + @Run(test = "test81") + public void test81_verifier() { + if (test81Result == 0) { + test81Result = test81(); + } + Asserts.assertEquals(test81(), test81Result); + } + + @ForceInline + public Object test82_helper(Object obj, int i) { + if ((i % 2) == 0) { + return MyValue1.createWithFieldsInline(i, i); + } + return obj; + } + + // Test loops with casts + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS}) + public long test82() { + Object val = null; + for (int i = 0; i < 10; ++i) { + for (int j = 0; j < 10; ++j) { + for (int k = 0; k < 10; ++k) { + val = test82_helper(val, i + j + k); + } + if (val != null) { + val = test82_helper(val, i + j); + } + } + val = test82_helper(val, i); + } + return ((MyValue1)val).hash(); + } + + private long test82Result = 0; + + @Run(test = "test82") + public void test82_verifier() { + if (test82Result == 0) { + test82Result = test81(); + } + Asserts.assertEquals(test82(), test82Result); + } +*/ + + @ForceInline + public Object test83_helper(boolean b) { + if (b) { + return MyValue1.createWithFieldsInline(rI, rL); + } + return null; + } + + // Test that CastPP does not block sclarization in safepoints + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS}) + public long test83(boolean b, Method m) { + Object val = test83_helper(b); + if (val != null) { + // Uncommon trap + TestFramework.deoptimize(m); + return ((MyValue1)val).hash(); + } + return 0; + } + + @Run(test = "test83") + public void test83_verifier(RunInfo info) { + Asserts.assertEquals(test83(false, info.getTest()), 0L); + if (!info.isWarmUp()) { + Asserts.assertEquals(test83(true, info.getTest()), testValue1.hash()); + } + } + + @ForceInline + public Object test84_helper(Object obj, int i) { + if ((i % 2) == 0) { + return new MyValue1Wrapper(MyValue1.createWithFieldsInline(i, i)); + } + return obj; + } + + // Same as test80 but with wrapper + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS}) + public long test84() { + Object val = new MyValue1Wrapper(MyValue1.createWithFieldsInline(rI, rL)); + for (int i = 0; i < 100; ++i) { + val = test84_helper(val, i); + } + return ((MyValue1Wrapper)val).vt.hash(); + } + + private long test84Result = 0; + + @Run(test = "test84") + public void test84_verifier() { + if (test84Result == 0) { + test84Result = test84(); + } + Asserts.assertEquals(test84(), test84Result); + } + + @ForceInline + public Object test85_helper(Object obj, int i) { + if ((i % 2) == 0) { + return new MyValue1Wrapper(MyValue1.createWithFieldsInline(i, i)); + } + return obj; + } + + // Same as test81 but with wrapper + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS}) + public long test85() { + Object val = new MyValue1Wrapper(null); + for (int i = 0; i < 10; ++i) { + for (int j = 0; j < 10; ++j) { + for (int k = 0; k < 10; ++k) { + val = test85_helper(val, i + j + k); + } + val = test85_helper(val, i + j); + } + val = test85_helper(val, i); + } + MyValue1 vt = ((MyValue1Wrapper)val).vt; + vt = Objects.requireNonNull(vt); + return vt.hash(); + } + + private long test85Result = 0; + + @Run(test = "test85") + public void test85_verifier() { + if (test85Result == 0) { + test85Result = test85(); + } + Asserts.assertEquals(test85(), test85Result); + } + + static final class ObjectWrapper { + public Object obj; + + @ForceInline + public ObjectWrapper(Object obj) { + this.obj = obj; + } + } + + // Test scalarization with phi referencing itself + @Test + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "true"}, + failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}, + counts = {LOAD_OF_ANY_KLASS, " = 4"}) // 4 loads from the non-flattened MyValue1.v4 fields + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "false"}, + failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public long test86(MyValue1 vt) { + ObjectWrapper val = new ObjectWrapper(vt); + for (int i = 0; i < 10; ++i) { + for (int j = 0; j < 10; ++j) { + val.obj = val.obj; + } + } + return ((MyValue1)val.obj).hash(); + } + + @Run(test = "test86") + public void test86_verifier() { + test86(testValue1); + Asserts.assertEquals(test86(testValue1), testValue1.hash()); + } + + @LooselyConsistentValue + public static value class Test87C0 { + int x = 0; + } + + @LooselyConsistentValue + public static value class Test87C1 { + @Strict + @NullRestricted + Test87C0 field = new Test87C0(); + } + + @LooselyConsistentValue + public static value class Test87C2 { + @Strict + @NullRestricted + Test87C1 field = new Test87C1(); + } + + // Test merging field loads in return + @Test + public Test87C1 test87(boolean b, Test87C2 v1, Test87C2 v2) { + if (b) { + return v1.field; + } else { + return v2.field; + } + } + + @Run(test = "test87") + public void test87_verifier() { + Test87C2 v = new Test87C2(); + Asserts.assertEQ(test87(true, v, v), v.field); + Asserts.assertEQ(test87(false, v, v), v.field); + } + + @LooselyConsistentValue + static value class Test88Value { + int x = 0; + } + + static class Test88MyClass { + int x = 0; + int y = rI; + } + + @ForceInline + Object test88Helper() { + return new Test88Value(); + } + + // Test LoadNode::Identity optimization with always failing checkcast + @Test + public int test88() { + Object obj = test88Helper(); + return ((Test88MyClass)obj).y; + } + + @Run(test = "test88") + public void test88_verifier() { + try { + test88(); + throw new RuntimeException("No ClassCastException thrown"); + } catch (ClassCastException e) { + // Expected + } + } + + // Same as test88 but with Phi + @Test + public int test89(boolean b) { + Test88MyClass obj = b ? (Test88MyClass)test88Helper() : (Test88MyClass)test88Helper(); + return obj.y; + } + + @Run(test = "test89") + public void test89_verifier() { + try { + test89(false); + throw new RuntimeException("No ClassCastException thrown"); + } catch (ClassCastException e) { + // Expected + } + try { + test89(true); + throw new RuntimeException("No ClassCastException thrown"); + } catch (ClassCastException e) { + // Expected + } + } + + @ForceInline + public boolean test90_inline(MyValue1 vt) { + return vt == null; + } + + // Test scalarization with speculative NULL type + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS}) + public boolean test90(Method m) throws Exception { + Object arg = null; + return (boolean)m.invoke(this, arg); + } + + @Run(test = "test90") + @Warmup(10000) + public void test90_verifier() throws Exception { + Method m = getClass().getMethod("test90_inline", MyValue1.class); + Asserts.assertTrue(test90(m)); + } + + // Test that scalarization does not introduce redundant/unused checks + @Test + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "false"}, + failOn = {ALLOC_OF_MYVALUE_KLASS, CMP_N, CMP_P}) + public Object test91(MyValue1 vt) { + return vt; + } + + @Run(test = "test91") + public void test91_verifier() { + Asserts.assertEQ(test91(testValue1), testValue1); + } + + MyValue1 test92Field = testValue1; + + // Same as test91 but with field access + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, CMP_N, CMP_P}) + public Object test92() { + return test92Field; + } + + @Run(test = "test92") + public void test92_verifier() { + Asserts.assertEQ(test92(), testValue1); + } + + private static final MethodHandle refCheckCast = InstructionHelper.buildMethodHandle(MethodHandles.lookup(), + "refCheckCast", + MethodType.methodType(MyValue2.class, TestNullableInlineTypes.class, MyValue1.class), + CODE -> { + CODE. + aload(1). + checkcast(MyValue2.class.describeConstable().orElseThrow()). + areturn(); + }); + + // Test checkcast that only passes with null + @Test + public Object test93(MyValue1 vt) throws Throwable { + return refCheckCast.invoke(this, vt); + } + + @Run(test = "test93") + @Warmup(10000) + public void test93_verifier() throws Throwable { + Asserts.assertEQ(test93(null), null); + } + + @DontInline + public MyValue1 test94_helper1(MyValue1 vt) { + return vt; + } + + @ForceInline + public MyValue1 test94_helper2(MyValue1 vt) { + return test94_helper1(vt); + } + + @ForceInline + public MyValue1 test94_helper3(Object vt) { + return test94_helper2((MyValue1)vt); + } + + // Test that calling convention optimization prevents buffering of arguments + @Test + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "true"}, + counts = {ALLOC, " <= 2"}) // 1 MyValue2 allocation + 1 Integer allocation (if not the all-zero value) + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "false"}, + counts = {ALLOC, " <= 3"}) // 1 MyValue1 allocation + 1 MyValue2 allocation + 1 Integer allocation (if not the all-zero value) + public MyValue1 test94(MyValue1 vt) { + MyValue1 res = test94_helper1(vt); + vt = MyValue1.createWithFieldsInline(rI, rL); + test94_helper1(vt); + test94_helper2(vt); + test94_helper3(vt); + return res; + } + + @Run(test = "test94") + public void test94_verifier() { + Asserts.assertEQ(test94(testValue1), testValue1); + Asserts.assertEQ(test94(null), null); + } + + @DontInline + public static MyValue1 test95_helper1(MyValue1 vt) { + return vt; + } + + @ForceInline + public static MyValue1 test95_helper2(MyValue1 vt) { + return test95_helper1(vt); + } + + @ForceInline + public static MyValue1 test95_helper3(Object vt) { + return test95_helper2((MyValue1)vt); + } + + // Same as test94 but with static methods to trigger simple adapter logic + @Test + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "true"}, + counts = {ALLOC, " <= 2"}) // 1 MyValue2 allocation + 1 Integer allocation (if not the all-zero value) + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "false"}, + counts = {ALLOC, " <= 3"}) // 1 MyValue1 allocation + 1 MyValue2 allocation + 1 Integer allocation (if not the all-zero value) + public static MyValue1 test95(MyValue1 vt) { + MyValue1 res = test95_helper1(vt); + vt = MyValue1.createWithFieldsInline(rI, rL); + test95_helper1(vt); + test95_helper2(vt); + test95_helper3(vt); + return res; + } + + @Run(test = "test95") + public void test95_verifier() { + Asserts.assertEQ(test95(testValue1), testValue1); + Asserts.assertEQ(test95(null), null); + } + + @DontInline + public MyValue2 test96_helper1(boolean b) { + return b ? null : MyValue2.createWithFieldsInline(rI, rD); + } + + @ForceInline + public MyValue2 test96_helper2() { + return null; + } + + @ForceInline + public MyValue2 test96_helper3(boolean b) { + return b ? null : MyValue2.createWithFieldsInline(rI, rD); + } + + // Test that calling convention optimization prevents buffering of return values + @Test + @IR(applyIf = {"InlineTypeReturnedAsFields", "true"}, + failOn = {ALLOC}) + @IR(applyIf = {"InlineTypeReturnedAsFields", "false"}, + counts = {ALLOC, " <= 1"}) // No allocation required if the MyValue2 return is the all-zero value + public MyValue2 test96(int c, boolean b) { + MyValue2 res = null; + if (c == 1) { + res = test96_helper1(b); + } else if (c == 2) { + res = test96_helper2(); + } else if (c == 3) { + res = test96_helper3(b); + } + return res; + } + + @Run(test = "test96") + public void test96_verifier() { + Asserts.assertEQ(test96(0, false), null); + Asserts.assertEQ(test96(1, false).hash(), MyValue2.createWithFieldsInline(rI, rD).hash()); + Asserts.assertEQ(test96(1, true), null); + Asserts.assertEQ(test96(2, false), null); + Asserts.assertEQ(test96(3, false).hash(), MyValue2.createWithFieldsInline(rI, rD).hash()); + Asserts.assertEQ(test96(3, true), null); + } + + @DontInline + public MyValue3 test97_helper1(boolean b) { + return b ? null : test97_res1; + } + + @ForceInline + public MyValue3 test97_helper2() { + return null; + } + + @ForceInline + public MyValue3 test97_helper3(boolean b) { + return b ? null : test97_res3; + } + + @Strict + @NullRestricted + final MyValue3 test97_res1 = MyValue3.create(); + + @Strict + @NullRestricted + final MyValue3 test97_res3 = MyValue3.create(); + + // Same as test96 but with MyValue3 return + @Test + @IR(applyIf = {"InlineTypeReturnedAsFields", "true"}, + failOn = {ALLOC}) + @IR(applyIf = {"InlineTypeReturnedAsFields", "false"}, + counts = {ALLOC, " <= 1"}) // No allocation required if the MyValue3 return is the all-zero value + public MyValue3 test97(int c, boolean b) { + MyValue3 res = null; + if (c == 1) { + res = test97_helper1(b); + } else if (c == 2) { + res = test97_helper2(); + } else if (c == 3) { + res = test97_helper3(b); + } + return res; + } + + @Run(test = "test97") + public void test97_verifier() { + Asserts.assertEQ(test97(0, false), null); + Asserts.assertEQ(test97(1, false), test97_res1); + Asserts.assertEQ(test97(1, true), null); + Asserts.assertEQ(test97(2, false), null); + Asserts.assertEQ(test97(3, false), test97_res3); + Asserts.assertEQ(test97(3, true), null); + } + + @LooselyConsistentValue + static value class CircularValue1 { + CircularValue1 val; + int x; + + @ForceInline + public CircularValue1(CircularValue1 val) { + this.val = val; + this.x = rI; + } + } + + // Test scalarization of value class with circularity in fields + @Test + public CircularValue1 test98(CircularValue1 val) { + return new CircularValue1(val); + } + + @Run(test = "test98") + public void test98_verifier() { + CircularValue1 val = new CircularValue1(null); + CircularValue1 res = test98(val); + Asserts.assertEQ(res.x, rI); + Asserts.assertEQ(res.val, val); + } + + @LooselyConsistentValue + static value class CircularValue2 { + @Strict + @NullRestricted + CircularValue1 val; + + @ForceInline + public CircularValue2(CircularValue1 val) { + this.val = val; + } + } + + // Same as test98 but with circularity in class of flattened field + @Test + public CircularValue2 test99(CircularValue2 val) { + return new CircularValue2(val.val); + } + + @Run(test = "test99") + public void test99_verifier() { + CircularValue1 val1 = new CircularValue1(null); + CircularValue2 val2 = new CircularValue2(val1); + CircularValue2 res = test99(val2); + Asserts.assertEQ(res.val, val1); + } + + @LooselyConsistentValue + static value class CircularValue3 { + CircularValue4 val; + int x; + + @ForceInline + public CircularValue3(CircularValue4 val, int x) { + this.val = val; + this.x = x; + } + } + + @LooselyConsistentValue + static value class CircularValue4 { + @Strict + @NullRestricted + CircularValue3 val; + + @ForceInline + public CircularValue4(CircularValue3 val) { + this.val = val; + } + } + + // Same as test94 but with "indirect" circularity through field of flattened field + @Test + public CircularValue4 test100(CircularValue4 val) { + return new CircularValue4(new CircularValue3(val, rI)); + } + + @Run(test = "test100") + public void test100_verifier() { + CircularValue3 val3 = new CircularValue3(null, 42); + CircularValue4 val4 = new CircularValue4(val3); + CircularValue4 res = test100(val4); + Asserts.assertEQ(res.val, new CircularValue3(val4, rI)); + } + + @LooselyConsistentValue + static value class CircularValue5 { + @Strict + @NullRestricted + CircularValue6 val; + int x; + + @ForceInline + public CircularValue5(CircularValue6 val, int x) { + this.val = val; + this.x = x; + } + } + + @LooselyConsistentValue + static value class CircularValue6 { + CircularValue5 val; + + @ForceInline + public CircularValue6(CircularValue5 val) { + this.val = val; + } + } + + // Same as test100 but with different combination of field types + @Test + public CircularValue6 test101(CircularValue6 val) { + return new CircularValue6(new CircularValue5(val, rI)); + } + + @Run(test = "test101") + public void test101_verifier() { + CircularValue5 val5 = new CircularValue5(new CircularValue6(null), 42); + CircularValue6 val6 = new CircularValue6(val5); + CircularValue6 res = test101(val6); + Asserts.assertEQ(res.val, new CircularValue5(val6, rI)); + } + + // Test merging of fields with different scalarization depth + @Test + public CircularValue1 test102(boolean b) { + CircularValue1 val = new CircularValue1(new CircularValue1(null)); + if (b) { + val = null; + } + return val; + } + + @Run(test = "test102") + public void test102_verifier() { + Asserts.assertEQ(test102(false), new CircularValue1(new CircularValue1(null))); + Asserts.assertEQ(test102(true), null); + } + + // Might be incrementally inlined + public static Object hide(Object obj) { + return (MyValue1)obj; + } + + // Test that the ConstraintCastNode::Ideal transformation propagates null-free information + @Test + public MyValue1 test103() { + Object obj = hide(null); + return (MyValue1)obj; + } + + @Run(test = "test103") + public void test103_verifier() { + Asserts.assertEQ(test103(), null); + } + + // Test null restricted fields + + @LooselyConsistentValue + static value class MyValue104 { + @Strict + @NullRestricted + static MyValue105 field1 = new MyValue105(); + + @Strict + @NullRestricted + MyValue105 field2; + + @Strict + @NullRestricted + static MyValueEmpty field3 = new MyValueEmpty(); + + @Strict + @NullRestricted + MyValueEmpty field4; + + @ForceInline + public MyValue104() { + this.field1 = new MyValue105(); + this.field2 = new MyValue105(); + this.field3 = new MyValueEmpty(); + this.field4 = new MyValueEmpty(); + } + + @ForceInline + public MyValue104(MyValue105 val1, MyValue105 val2, MyValueEmpty val3, MyValueEmpty val4) { + this.field1 = val1; + this.field2 = val2; + this.field3 = val3; + this.field4 = val4; + } + } + + @LooselyConsistentValue + static value class MyValue105 { + int x = 42; + } + + @Strict + @NullRestricted + static MyValue104 field1 = new MyValue104(); + + @Strict + @NullRestricted + MyValue104 field2 = new MyValue104(); + + @Strict + @NullRestricted + static MyValueEmpty field3 = new MyValueEmpty(); + + @Strict + @NullRestricted + MyValueEmpty field4 = new MyValueEmpty(); + + @Test + void test105(MyValue104 arg) { + field1 = arg; + } + + @Run(test = "test105") + public void test105_verifier() { + try { + test105(null); + throw new RuntimeException("No exception thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + void test106() { + field1 = null; + } + + @Run(test = "test106") + public void test106_verifier() { + try { + test106(); + throw new RuntimeException("No exception thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + void test107(MyValue104 arg) { + field2 = arg; + } + + @Run(test = "test107") + public void test107_verifier() { + try { + test107(null); + throw new RuntimeException("No exception thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + void test108(TestNullableInlineTypes t, MyValue104 arg) { + t.field2 = arg; + } + + @Run(test = "test108") + public void test108_verifier() { + try { + test108(null, new MyValue104()); + throw new RuntimeException("No exception thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + void test109() { + TestNullableInlineTypes t = null; + t.field2 = null; + } + + @Run(test = "test109") + void test109_verifier() { + try { + test109(); + throw new RuntimeException("No exception thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + void test110() { + field2 = null; + } + + @Run(test = "test110") + public void test110_verifier() { + try { + test110(); + throw new RuntimeException("No exception thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + void test111(MyValueEmpty arg) { + field3 = arg; + } + + @Run(test = "test111") + public void test111_verifier() { + try { + test111(null); + throw new RuntimeException("No exception thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + void test112() { + field3 = null; + } + + @Run(test = "test112") + public void test112_verifier() { + try { + test112(); + throw new RuntimeException("No exception thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + void test113(MyValueEmpty arg) { + field4 = arg; + } + + @Run(test = "test113") + public void test113_verifier() { + try { + test113(null); + throw new RuntimeException("No exception thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + void test114(TestNullableInlineTypes t, MyValueEmpty arg) { + t.field4 = arg; + } + + @Run(test = "test114") + public void test114_verifier() { + try { + test114(null, new MyValueEmpty()); + throw new RuntimeException("No exception thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + void test115(MyValueEmpty arg) { + TestNullableInlineTypes t = null; + t.field4 = arg; + } + + @Run(test = "test115") + public void test115_verifier() { + try { + test115(new MyValueEmpty()); + throw new RuntimeException("No exception thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + void test116() { + field4 = null; + } + + @Run(test = "test116") + public void test116_verifier() { + try { + test116(); + throw new RuntimeException("No exception thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + MyValue104 test117(MyValue105 val1, MyValue105 val2, MyValueEmpty val3, MyValueEmpty val4) { + return new MyValue104(val1, val2, val3, val4); + } + + @Run(test = "test117") + public void test117_verifier() { + try { + test117(null, new MyValue105(), new MyValueEmpty(), new MyValueEmpty()); + throw new RuntimeException("No exception thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + MyValue104 test118(MyValue105 val1, MyValue105 val2, MyValueEmpty val3, MyValueEmpty val4) { + return new MyValue104(val1, val2, val3, val4); + } + + @Run(test = "test118") + public void test118_verifier() { + try { + test118(new MyValue105(), null, new MyValueEmpty(), new MyValueEmpty()); + throw new RuntimeException("No exception thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + MyValue104 test119(MyValue105 val1, MyValue105 val2, MyValueEmpty val3, MyValueEmpty val4) { + return new MyValue104(val1, val2, val3, val4); + } + + @Run(test = "test119") + public void test119_verifier() { + try { + test119(new MyValue105(), new MyValue105(), null, new MyValueEmpty()); + throw new RuntimeException("No exception thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + MyValue104 test120(MyValue105 val1, MyValue105 val2, MyValueEmpty val3, MyValueEmpty val4) { + return new MyValue104(val1, val2, val3, val4); + } + + @Run(test = "test120") + public void test120_verifier() { + try { + test120(new MyValue105(), new MyValue105(), new MyValueEmpty(), null); + throw new RuntimeException("No exception thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + MyValue104 test121(MyValue105 val2, MyValueEmpty val3, MyValueEmpty val4) { + return new MyValue104(null, val2, val3, val4); + } + + @Run(test = "test121") + public void test121_verifier() { + try { + test121(new MyValue105(), new MyValueEmpty(), new MyValueEmpty()); + throw new RuntimeException("No exception thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + MyValue104 test122(MyValue105 val1, MyValueEmpty val3, MyValueEmpty val4) { + return new MyValue104(val1, null, val3, val4); + } + + @Run(test = "test122") + public void test122_verifier() { + try { + test122(new MyValue105(), new MyValueEmpty(), new MyValueEmpty()); + throw new RuntimeException("No exception thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + MyValue104 test123(MyValue105 val1, MyValue105 val2, MyValueEmpty val4) { + return new MyValue104(val1, val2, null, val4); + } + + @Run(test = "test123") + public void test123_verifier() { + try { + test123(new MyValue105(), new MyValue105(), new MyValueEmpty()); + throw new RuntimeException("No exception thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + @Test + MyValue104 test124(MyValue105 val1, MyValue105 val2, MyValueEmpty val3) { + return new MyValue104(val1, val2, val3, null); + } + + @Run(test = "test124") + public void test124_verifier() { + try { + test124(new MyValue105(), new MyValue105(), new MyValueEmpty()); + throw new RuntimeException("No exception thrown"); + } catch (NullPointerException e) { + // Expected + } + } + + static value class CircularValue7 { + CircularValue7 v; + int i; + + public CircularValue7(int i) { + this.v = new CircularValue7(); // When is incrementally inlined: StoreN into object + dontInline(); // Not inlined -> safepoint which also saves StoreN. + this.i = i; + } + + public CircularValue7(boolean ignored) { + this.v = new CircularValue7(); + this.i = 23; + } + + public CircularValue7() { + this.v = null; + this.i = 34; + } + + @DontInline + static void dontInline() {} + } + + @Test + @IR(failOn = ALLOC) + int testCircularSafepointUse() { + CircularValue7 v = new CircularValue7(true); // v is non escaping -> EA can remove allocation + dontInline(); // Not inlined -> safepoint + return v.i; // Use v such that it is still required in the safepoint at dontInline() + } + + @DontInline + void dontInline() {} + + @Run(test = "testCircularSafepointUse") + public void testCircularSafepointUse_verifier() { + Asserts.assertEQ(testCircularSafepointUse(), new CircularValue7(true).i); + } + + + @Test + @IR(failOn = ALLOC) + int testCircularSafepointUse2(int i) { + // With AlwaysIncrementalInline: + // We allocate here because is not inlined at parsing. + // At late inline: The store of v.v is done with a StoreN into the allocation to make the effect visible. + CircularValue7 v = new CircularValue7(i); + return v.i; + } + + @Run(test = "testCircularSafepointUse2") + public void testCircularSafepointUse2_verifier() { + int rand = rI; + Asserts.assertEQ(testCircularSafepointUse2(rand), new CircularValue7(rand).i); + } + +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOnStackReplacement.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOnStackReplacement.java new file mode 100644 index 00000000000..88768d4d2f1 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOnStackReplacement.java @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.*; +import jdk.test.lib.Asserts; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.ALLOC_OF_MYVALUE_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.LOAD_OF_ANY_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.STORE_OF_ANY_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypes.rI; +import static compiler.valhalla.inlinetypes.InlineTypes.rL; + +import static compiler.lib.ir_framework.IRNode.LOAD_OF_CLASS; +import static compiler.lib.ir_framework.IRNode.STORE_OF_CLASS; + +/* + * @test + * @key randomness + * @summary Test on stack replacement (OSR) with value classes. + * @library /test/lib / + * @requires (os.simpleArch == "x64" | os.simpleArch == "aarch64") + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm/timeout=600 compiler.valhalla.inlinetypes.TestOnStackReplacement + */ + +public class TestOnStackReplacement { + + public static void main(String[] args) throws Throwable { + Scenario[] scenarios = InlineTypes.DEFAULT_SCENARIOS; + scenarios[3].addFlags("-XX:-UseArrayFlattening"); + + InlineTypes.getFramework() + .addScenarios(scenarios) + .addHelperClasses(MyValue1.class, + MyValue2.class, + MyValue2Inline.class, + MyValue3.class, + MyValue3Inline.class) + .start(); + } + + // Helper methods + + protected long hash() { + return hash(rI, rL); + } + + protected long hash(int x, long y) { + return MyValue1.createWithFieldsInline(x, y).hash(); + } + + // Test OSR compilation + @Test(compLevel = CompLevel.WAIT_FOR_COMPILATION) + public long test1() { + MyValue1 v = MyValue1.createWithFieldsInline(rI, rL); + MyValue1[] va = (MyValue1[])ValueClass.newNullRestrictedNonAtomicArray(MyValue1.class, Math.abs(rI) % 3, MyValue1.DEFAULT); + for (int i = 0; i < va.length; ++i) { + va[i] = MyValue1.createWithFieldsInline(rI, rL); + } + long result = 0; + // Long loop to trigger OSR compilation + for (int i = 0; i < 50_000; ++i) { + // Reference local value object in interpreter state + result = v.hash(); + for (int j = 0; j < va.length; ++j) { + result += va[j].hash(); + } + } + return result; + } + + @Run(test = "test1") + @Warmup(0) + public void test1_verifier() { + long result = test1(); + Asserts.assertEQ(result, ((Math.abs(rI) % 3) + 1) * hash()); + } + + // Test loop peeling + @Test(compLevel = CompLevel.WAIT_FOR_COMPILATION) + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, LOAD_OF_ANY_KLASS, STORE_OF_ANY_KLASS}) + public void test2() { + MyValue1 v = MyValue1.createWithFieldsInline(0, 1); + // Trigger OSR compilation and loop peeling + for (int i = 0; i < 50_000; ++i) { + if (v.x != i || v.y != i + 1) { + // Uncommon trap + throw new RuntimeException("test2 failed"); + } + v = MyValue1.createWithFieldsInline(i + 1, i + 2); + } + } + + @Run(test = "test2") + @Warmup(0) + public void test2_verifier() { + test2(); + } + + // Test loop peeling and unrolling + @Test(compLevel = CompLevel.WAIT_FOR_COMPILATION) + public void test3() { + MyValue1 v1 = MyValue1.createWithFieldsInline(0, 0); + MyValue1 v2 = MyValue1.createWithFieldsInline(1, 1); + // Trigger OSR compilation and loop peeling + for (int i = 0; i < 50_000; ++i) { + if (v1.x != 2*i || v2.x != i+1 || v2.y != i+1) { + // Uncommon trap + throw new RuntimeException("test3 failed"); + } + v1 = MyValue1.createWithFieldsInline(2*(i+1), 0); + v2 = MyValue1.createWithFieldsInline(i+2, i+2); + } + } + + //@Run(test = "test3") + //@Warmup(0) + public void test3_verifier() { + test3(); + } + + // OSR compilation with Object local + @DontCompile + public Object test4_init() { + return MyValue1.createWithFieldsInline(rI, rL); + } + + @DontCompile + public Object test4_body() { + return MyValue1.createWithFieldsInline(rI, rL); + } + + @Test(compLevel = CompLevel.WAIT_FOR_COMPILATION) + public Object test4() { + Object vt = test4_init(); + for (int i = 0; i < 50_000; i++) { + if (i % 2 == 1) { + vt = test4_body(); + } + } + return vt; + } + + @Run(test = "test4") + @Warmup(0) + public void test4_verifier() { + test4(); + } + + // OSR compilation with null value class local + + MyValue1 nullField; + + @Test(compLevel = CompLevel.WAIT_FOR_COMPILATION) + public void test5() { + MyValue1 vt = nullField; + for (int i = 0; i < 50_000; i++) { + if (vt != null) { + throw new RuntimeException("test5 failed: vt should be null"); + } + } + } + + @Run(test = "test5") + @Warmup(0) + public void test5_verifier() { + test5(); + } + + // Test OSR in method with value class receiver + @LooselyConsistentValue + value class Test6Value { + public int f = 0; + + public int test() { + int res = 0; + for (int i = 1; i < 20_000; ++i) { + res -= i; + } + return res; + } + } + + @Test(compLevel = CompLevel.WAIT_FOR_COMPILATION) + public void test6() { + Test6Value tmp = new Test6Value(); + for (int i = 0; i < 100; ++i) { + tmp.test(); + } + } + + @Run(test = "test6") + @Warmup(0) + public void test6_verifier() { + test6(); + } + + // Similar to test6 but with more fields and reserved stack entry + @LooselyConsistentValue + static value class Test7Value1 { + public int i1 = rI; + public int i2 = rI; + public int i3 = rI; + public int i4 = rI; + public int i5 = rI; + public int i6 = rI; + } + + @LooselyConsistentValue + static value class Test7Value2 { + public int i1 = rI; + public int i2 = rI; + public int i3 = rI; + public int i4 = rI; + public int i5 = rI; + public int i6 = rI; + public int i7 = rI; + public int i8 = rI; + public int i9 = rI; + public int i10 = rI; + public int i11 = rI; + public int i12 = rI; + public int i13 = rI; + public int i14 = rI; + public int i15 = rI; + public int i16 = rI; + public int i17 = rI; + public int i18 = rI; + public int i19 = rI; + public int i20 = rI; + public int i21 = rI; + + @Strict + @NullRestricted + public Test7Value1 vt = new Test7Value1(); + + public int test(String[] args) { + int res = 0; + for (int i = 1; i < 20_000; ++i) { + res -= i; + } + return res; + } + } + + @Test(compLevel = CompLevel.WAIT_FOR_COMPILATION) + public void test7() { + Test7Value2 tmp = new Test7Value2(); + for (int i = 0; i < 10; ++i) { + tmp.test(null); + } + } + + @Run(test = "test7") + @Warmup(0) + public void test7_verifier() { + test7(); + } + + // Test OSR with scalarized value class return + MyValue3 test8_vt; + + @DontInline + public MyValue3 test8_callee(int len) { + test8_vt = MyValue3.create(); + int val = 0; + for (int i = 0; i < len; ++i) { + val = i; + } + test8_vt = test8_vt.setI(test8_vt, val); + return test8_vt; + } + + @Test + public int test8(int start) { + MyValue3 vt = test8_callee(start); + test8_vt.verify(vt); + int result = 0; + for (int i = 0; i < 50_000; ++i) { + result += i; + } + return result; + } + + @Run(test = "test8") + @Warmup(2) + public void test8_verifier() { + test8(1); + test8(50_000); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOopsInReturnConvention.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOopsInReturnConvention.java new file mode 100644 index 00000000000..bf1c37fda22 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOopsInReturnConvention.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @summary Test that oop fields in scalarized returns are properly handled. + * @library /test/lib /compiler/whitebox / + * @enablePreview + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:-TieredCompilation + * -XX:CompileCommand=dontinline,TestOopsInReturnConvention::callee + * -XX:CompileCommand=dontinline,TestOopsInReturnConvention*::verify + * TestOopsInReturnConvention Interpreted + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch + * -XX:CompileCommand=dontinline,TestOopsInReturnConvention::callee + * -XX:CompileCommand=dontinline,TestOopsInReturnConvention*::verify + * TestOopsInReturnConvention C1 + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:-TieredCompilation + * -XX:CompileCommand=dontinline,TestOopsInReturnConvention::callee + * -XX:CompileCommand=dontinline,TestOopsInReturnConvention*::verify + * TestOopsInReturnConvention C2 + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:-TieredCompilation -XX:+IgnoreUnrecognizedVMOptions -XX:+StressCallingConvention + * -XX:CompileCommand=dontinline,TestOopsInReturnConvention::callee + * -XX:CompileCommand=dontinline,TestOopsInReturnConvention*::verify + * TestOopsInReturnConvention Interpreted + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:+IgnoreUnrecognizedVMOptions -XX:+StressCallingConvention + * -XX:CompileCommand=dontinline,TestOopsInReturnConvention::callee + * -XX:CompileCommand=dontinline,TestOopsInReturnConvention*::verify + * TestOopsInReturnConvention C1 + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:-TieredCompilation -XX:+IgnoreUnrecognizedVMOptions -XX:+StressCallingConvention + * -XX:CompileCommand=dontinline,TestOopsInReturnConvention::callee + * -XX:CompileCommand=dontinline,TestOopsInReturnConvention*::verify + * TestOopsInReturnConvention C2 + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:-TieredCompilation -XX:+IgnoreUnrecognizedVMOptions -XX:-PreloadClasses + * -XX:CompileCommand=dontinline,TestOopsInReturnConvention::callee + * -XX:CompileCommand=dontinline,TestOopsInReturnConvention*::verify + * TestOopsInReturnConvention Interpreted + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:+IgnoreUnrecognizedVMOptions -XX:-PreloadClasses + * -XX:CompileCommand=dontinline,TestOopsInReturnConvention::callee + * -XX:CompileCommand=dontinline,TestOopsInReturnConvention*::verify + * TestOopsInReturnConvention C1 + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:-TieredCompilation -XX:+IgnoreUnrecognizedVMOptions -XX:-PreloadClasses + * -XX:CompileCommand=dontinline,TestOopsInReturnConvention::callee + * -XX:CompileCommand=dontinline,TestOopsInReturnConvention*::verify + * TestOopsInReturnConvention C2 + **/ + +import java.lang.reflect.Method; + +import jdk.test.lib.Asserts; +import jdk.test.whitebox.WhiteBox; + +public class TestOopsInReturnConvention { + static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + static final int COMP_LEVEL_FULL_OPTIMIZATION = 4; // C2 or JVMCI + + // Large value class with oops + static value class LargeValueWithOops { + Object x1; + Object x2; + Object x3; + Object x4; + Object x5; + + public LargeValueWithOops(Object obj) { + this.x1 = obj; + this.x2 = obj; + this.x3 = obj; + this.x4 = obj; + this.x5 = obj; + } + + public static void verify(LargeValueWithOops val, Object obj, boolean useNull) { + if (useNull) { + Asserts.assertEQ(val, null); + } else { + Asserts.assertEQ(val.x1, obj); + Asserts.assertEQ(val.x2, obj); + Asserts.assertEQ(val.x3, obj); + Asserts.assertEQ(val.x4, obj); + Asserts.assertEQ(val.x5, obj); + } + } + } + + // Pass some unused args to make sure that the (return) registers are trashed + public static LargeValueWithOops callee(int unused1, int unused2, int unused3, int unused4, int unused5, LargeValueWithOops val) { + return val; + } + + public static void caller(LargeValueWithOops val, Object obj, boolean useNull) { + // Below call will return a LargeValueWithOops in scalarized form. + // If it's null, the x1 - x5 oop fields need to be zeroed to make the GC happy. + val = callee(1, 2, 3, 4, 5, val); + LargeValueWithOops.verify(val, obj, useNull); + } + + static class GarbageProducerThread extends Thread { + public void run() { + for (;;) { + // Produce some garbage and then let the GC do its work + Object[] arrays = new Object[1024]; + for (int i = 0; i < arrays.length; i++) { + arrays[i] = new int[1024]; + } + System.gc(); + } + } + } + + public static void main(String[] args) throws Exception { + if (args[0].equals("Interpreted") || args[0].equals("C1")) { + // Prevent callee method from being C2 compiled to ensure it's interpreted or C1 compiled + Method m = TestOopsInReturnConvention.class.getDeclaredMethod("callee", int.class, int.class, int.class, int.class, int.class, LargeValueWithOops.class); + WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_FULL_OPTIMIZATION, false); + } + + // Start another thread that does some allocations and calls System.gc() to trigger frequent GCs + Thread garbage_producer = new GarbageProducerThread(); + garbage_producer.setDaemon(true); + garbage_producer.start(); + + // Trigger compilation + for (int i = 0; i < 500_000; i++) { + boolean useNull = (i % 2) == 0; + LargeValueWithOops val = useNull ? null : new LargeValueWithOops(i); + caller(val, i, useNull); + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizeKlassCmp.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizeKlassCmp.java new file mode 100644 index 00000000000..be535938acb --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestOptimizeKlassCmp.java @@ -0,0 +1,71 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8209687 + * @summary Verify that Parse::optimize_cmp_with_klass() works with value classes. + * @library /test/lib + * @enablePreview + * @run main/othervm -Xbatch compiler.valhalla.inlinetypes.TestOptimizeKlassCmp + */ + +package compiler.valhalla.inlinetypes; + +import jdk.test.lib.Asserts; + +value class MyValue { + public int x; + + public MyValue(int x) { + this.x = x; + } +} + +public class TestOptimizeKlassCmp { + + public static boolean test1(MyValue v1, MyValue v2) { + return v1.equals(v2); + } + + public static boolean test2(MyValue v1, MyValue v2) { + return v1.getClass().equals(v2.getClass()); + } + + public static boolean test3(Object o1, Object o2) { + return o1.getClass().equals(o2.getClass()); + } + + public static void main(String[] args) { + MyValue v1 = new MyValue(0); + MyValue v2 = new MyValue(1); + for (int i = 0; i < 10_000; ++i) { + Asserts.assertFalse(test1(v1, v2)); + Asserts.assertTrue(test1(v1, v1)); + Asserts.assertTrue(test2(v1, v2)); + Asserts.assertTrue(test2(v1, v1)); + Asserts.assertTrue(test3(v1, v2)); + Asserts.assertTrue(test3(v1, v1)); + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestSafepointAtPollReturn.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestSafepointAtPollReturn.java new file mode 100644 index 00000000000..602e802df31 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestSafepointAtPollReturn.java @@ -0,0 +1,64 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @summary Test that oop fields of value classes are preserved over safepoints at returns. + * @enablePreview + * @run main/othervm -XX:CompileCommand=dontinline,TestSafepointAtPollReturn::test* -XX:+UnlockDiagnosticVMOptions + * -XX:+SafepointALot -XX:-TieredCompilation -XX:+UseTLAB TestSafepointAtPollReturn + */ + +public class TestSafepointAtPollReturn { + static Integer INT_VAL = 0; + + static value class MyValue { + Integer val = INT_VAL; + } + + static public MyValue testValueCallee(boolean b) { + return b ? null : new MyValue(); + } + + static public MyValue testValue(boolean b) { + return testValueCallee(b); + } + + public static void main(String[] args) { + for (int i = 0; i < 1_000_000_000; ++i) { + INT_VAL = i; + boolean b = (i % 2) == 0; + MyValue val = testValue(b); + if (b) { + if (val != null) { + throw new RuntimeException("testValue failed: result should be null"); + } + } else { + int res = val.val; + if (res != i) { + throw new RuntimeException("testValue failed: " + res + " != " + i); + } + } + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestSpeculateArrayAccess.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestSpeculateArrayAccess.java new file mode 100644 index 00000000000..d0c2dd6a7e0 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestSpeculateArrayAccess.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @key stress randomness + * @bug 8333889 + * @summary Test that speculative array access checks do not cause a load to be wrongly hoisted before its range check. + * @run main/othervm -XX:CompileCommand=dontinline,*::* -XX:CompileCommand=compileonly,*TestSpeculateArrayAccess::test + * -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM -XX:StressSeed=1202682944 + * compiler.valhalla.inlinetypes.TestSpeculateArrayAccess + * @run main/othervm -XX:CompileCommand=dontinline,*::* -XX:CompileCommand=compileonly,*TestSpeculateArrayAccess::test + * -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM + * compiler.valhalla.inlinetypes.TestSpeculateArrayAccess + */ + +package compiler.valhalla.inlinetypes; + +public class TestSpeculateArrayAccess { + static Object[] oArr = new Object[100]; + static int iFld = 100; + + public static void main(String[] args) { + for (int i = 0; i < 100; i++) { + oArr[i] = new Object(); + } + iFld = 1; + for (int i = 0; i < 1000; i++) { + test(); + } + iFld = -1; + + try { + test(); + } catch (ArrayIndexOutOfBoundsException e) { + // Expected. + } + } + + static void test() { + Object[] oA = oArr; // Load here to avoid G1 barriers being expanded inside loop which prevents Loop Predication. + for (float i = 0; i < 100; i++) { + // RangeCheck -> If-Speculative-Array-Type -> CastII + // + // At Loop Predication: + // - RangeCheck not hoisted because loop dependent + // - If-Speculative-Array-Type hoisted and CastII and LoadN ends up before loop + // + // Running with -XX:+StressGCM: We could execute the LoadN before entering the loop. + // This crashes when iFld = -1 because we then access an out-of-bounds element. + Object o = oA[(int)i*iFld]; + o.toString(); // Use the object with its speculated type. + } + } +} + diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestStressReturnBuffering.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestStressReturnBuffering.java new file mode 100644 index 00000000000..8ff49059dd8 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestStressReturnBuffering.java @@ -0,0 +1,76 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8233415 + * @summary Verify that TLAB allocated buffer initialization when returning a value object works properly with oops. + * @library /test/lib + * @enablePreview + * @run main/othervm -XX:CompileCommand=exclude,compiler.valhalla.inlinetypes.TestStressReturnBuffering::caller -Xmx6m + * compiler.valhalla.inlinetypes.TestStressReturnBuffering + */ + +package compiler.valhalla.inlinetypes; + +import jdk.test.lib.Asserts; + +value class MyValue { + public Integer o1; + public Integer o2; + public Integer o3; + public Integer o4; + public Integer o5; + + public MyValue(Integer o) { + this.o1 = o; + this.o2 = o; + this.o3 = o; + this.o4 = o; + this.o5 = o; + } +} + +public class TestStressReturnBuffering { + + static Integer integer = 42; + + public static MyValue callee() { + return new MyValue(integer); + } + + public static int caller() { + int res = 0; + for (int i = 0; i < 100_000; ++i) { + MyValue vt = callee(); + res += vt.o1 + vt.o2 + vt.o3 + vt.o4 + vt.o5; + } + return res; + } + + public static void main(String[] args) { + System.gc(); + int res = caller(); + Asserts.assertEQ(res, 100_000*5*42, "Unexpected result"); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestStrictFieldBarriers.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestStrictFieldBarriers.java new file mode 100644 index 00000000000..b5af631c49f --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestStrictFieldBarriers.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8367785 + * @summary Verify that compilers adhere to the new memory model rules for strict fields. + * @library /test/lib + * @enablePreview + * @modules java.base/jdk.internal.vm.annotation + * @run main/othervm compiler.valhalla.inlinetypes.TestStrictFieldBarriers + * @run main/othervm -Xbatch + * compiler.valhalla.inlinetypes.TestStrictFieldBarriers + * @run main/othervm -Xbatch -XX:CompileCommand=compileonly,java.lang.Object::*init* + * compiler.valhalla.inlinetypes.TestStrictFieldBarriers + * @run main/othervm -Xbatch -XX:CompileCommand=compileonly,StrictInitTest$A::*init* + * compiler.valhalla.inlinetypes.TestStrictFieldBarriers + * @run main/othervm -Xbatch -XX:CompileCommand=compileonly,StrictInitTest$B::*init* + * compiler.valhalla.inlinetypes.TestStrictFieldBarriers + * @run main/othervm -Xbatch -XX:CompileCommand=compileonly,StrictInitTest$C::*init* + * compiler.valhalla.inlinetypes.TestStrictFieldBarriers + * @run main/othervm -Xbatch -XX:CompileCommand=dontinline,*::*init* + * compiler.valhalla.inlinetypes.TestStrictFieldBarriers + */ + +package compiler.valhalla.inlinetypes; + +import jdk.internal.vm.annotation.Strict; + +import jdk.test.lib.Asserts; + +public class TestStrictFieldBarriers { + + static A1 sharedA1 = new A1(); + static B1 sharedB1 = new B1(); + static C1 sharedC1_1 = new C1(); + static B1 sharedC1_2 = new C1(); + static D1 sharedD1_1 = new D1(); + static E1 sharedD1_2 = new D1(); + + static A2 sharedA2 = new A2(); + static B2 sharedB2 = new B2(); + static C2 sharedC2_1 = new C2(); + static B2 sharedC2_2 = new C2(); + static D2 sharedD2_1 = new D2(); + static E2 sharedD2_2 = new D2(); + + static class A1 { + @Strict + final int x; + + A1() { + x = 1; + super(); + sharedA1 = this; + } + } + + static class B1 { + @Strict + final int x; + + B1() { + x = 1; + super(); + sharedB1 = this; + } + + B1(boolean unused) { + x = 1; + super(); + sharedC1_2 = this; + } + } + + static class C1 extends B1 { + @Strict + final int y; + + C1() { + y = 1; + super(true); + sharedC1_1 = this; + } + } + + static abstract value class E1 { + final int x; + + E1() { + x = 1; + super(); + sharedD1_2 = this; + } + } + + static class D1 extends E1 { + final int y; + final int z; + + D1() { + y = 2; + super(); + z = 3; + sharedD1_1 = this; + } + } + + // Non final versions + + static class A2 { + @Strict + int x; + + A2() { + x = 1; + super(); + sharedA2 = this; + } + } + + static class B2 { + @Strict + int x; + + B2() { + x = 1; + super(); + sharedB2 = this; + } + + B2(boolean unused) { + x = 1; + super(); + sharedC2_2 = this; + } + } + + static class C2 extends B2 { + @Strict + int y; + + C2() { + y = 1; + super(true); + sharedC2_1 = this; + } + } + + static abstract value class E2 { + final int x; + + E2() { + x = 1; + super(); + sharedD2_2 = this; + } + } + + static class D2 extends E2 { + final int y; + final int z; + + D2() { + y = 2; + super(); + z = 3; + sharedD2_1 = this; + } + } + + public static void main(String[] args) throws Exception { + // Spawn two threads, a reader and a writer and check that the + // reader thread never observes an unitialized strict field. + Thread reader = new Thread(() -> { + for (int i = 0; i < 100_000; ++i) { + // We don't check individual fields here because the checks need to be + // as fast a possible to increase the likelyhood of a race condition. + int res = sharedA1.x & sharedB1.x & sharedC1_1.x & sharedC1_1.y & sharedC1_2.x & ((C1)sharedC1_2).y & + sharedA2.x & sharedB2.x & sharedC2_1.x & sharedC2_1.y & sharedC2_2.x & ((C2)sharedC2_2).y & + sharedD1_1.x & ((D1)sharedD1_2).x & sharedD2_1.x & ((D2)sharedD2_2).x; + if (res != 1) { + System.err.println("Incorrect field value observed!"); + System.exit(1); + } + } + }); + + Thread writer = new Thread(() -> { + for (int i = 0; i < 100_000; ++i) { + new A1(); + new B1(); + new C1(); + new D1(); + new A2(); + new B2(); + new C2(); + new D2(); + } + }); + + reader.start(); + writer.start(); + reader.join(); + writer.join(); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestTearing.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestTearing.java new file mode 100644 index 00000000000..f30847ec85a --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestTearing.java @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import java.lang.invoke.*; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import jdk.test.lib.Asserts; +import jdk.internal.misc.Unsafe; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +/** + * @test id=tiered + * @key randomness + * @requires vm.compMode != "Xint" & vm.flavor == "server" & (vm.opt.TieredStopAtLevel == null | vm.opt.TieredStopAtLevel == 4) + * @summary Detect tearing on flat accesses and buffering. + * @library /testlibrary /test/lib / + * @enablePreview + * @modules java.base/jdk.internal.misc + * java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm -XX:-UseFieldFlattening -XX:-UseArrayFlattening + * -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM -XX:+StressLCM + * compiler.valhalla.inlinetypes.TestTearing + * @run main/othervm -XX:-UseFieldFlattening -XX:-UseArrayFlattening + * -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM -XX:+StressLCM + * -XX:+IgnoreUnrecognizedVMOptions -XX:+AlwaysIncrementalInline + * compiler.valhalla.inlinetypes.TestTearing + * @run main/othervm -XX:-UseFieldFlattening -XX:-UseArrayFlattening + * -XX:CompileCommand=dontinline,*::incrementAndCheck* + * -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM -XX:+StressLCM + * compiler.valhalla.inlinetypes.TestTearing + * @run main/othervm -XX:-UseFieldFlattening -XX:-UseArrayFlattening + * -XX:CompileCommand=dontinline,*::incrementAndCheck* + * -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM -XX:+StressLCM + * -XX:+IgnoreUnrecognizedVMOptions -XX:+AlwaysIncrementalInline + * compiler.valhalla.inlinetypes.TestTearing + * + * @run main/othervm/timeout=1000 -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseArrayFlattening + * -Xcomp -XX:-TieredCompilation + * compiler.valhalla.inlinetypes.TestTearing + * @run main/othervm -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseArrayFlattening + * -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM -XX:+StressLCM + * compiler.valhalla.inlinetypes.TestTearing + * @run main/othervm -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseArrayFlattening + * -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM -XX:+StressLCM + * -XX:+IgnoreUnrecognizedVMOptions -XX:+AlwaysIncrementalInline + * compiler.valhalla.inlinetypes.TestTearing + * @run main/othervm -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseArrayFlattening + * -XX:CompileCommand=dontinline,*::incrementAndCheck* + * -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM -XX:+StressLCM + * compiler.valhalla.inlinetypes.TestTearing + * @run main/othervm -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseArrayFlattening + * -XX:CompileCommand=dontinline,*::incrementAndCheck* + * -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM -XX:+StressLCM + * -XX:+IgnoreUnrecognizedVMOptions -XX:+AlwaysIncrementalInline + * compiler.valhalla.inlinetypes.TestTearing + */ + +/** + * @test id=c1 + * @key randomness + * @requires vm.compMode != "Xint" & (vm.opt.TieredStopAtLevel != null & vm.opt.TieredStopAtLevel < 4) + * @summary Detect tearing on flat accesses and buffering. These runs use a much smaller loop limit to avoid timeouts + * with C1 only. + * @library /testlibrary /test/lib / + * @enablePreview + * @modules java.base/jdk.internal.misc + * java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm -XX:-UseFieldFlattening -XX:-UseArrayFlattening + * -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM -XX:+StressLCM + * compiler.valhalla.inlinetypes.TestTearing C1 + * @run main/othervm -XX:-UseFieldFlattening -XX:-UseArrayFlattening + * -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM -XX:+StressLCM + * -XX:+IgnoreUnrecognizedVMOptions -XX:+AlwaysIncrementalInline + * compiler.valhalla.inlinetypes.TestTearing C1 + * @run main/othervm -XX:-UseFieldFlattening -XX:-UseArrayFlattening + * -XX:CompileCommand=dontinline,*::incrementAndCheck* + * -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM -XX:+StressLCM + * compiler.valhalla.inlinetypes.TestTearing C1 + * @run main/othervm -XX:-UseFieldFlattening -XX:-UseArrayFlattening + * -XX:CompileCommand=dontinline,*::incrementAndCheck* + * -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM -XX:+StressLCM + * -XX:+IgnoreUnrecognizedVMOptions -XX:+AlwaysIncrementalInline + * compiler.valhalla.inlinetypes.TestTearing C1 + * + * @run main/othervm/timeout=1000 -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseArrayFlattening + * -Xcomp compiler.valhalla.inlinetypes.TestTearing C1 + * @run main/othervm -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseArrayFlattening + * -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM -XX:+StressLCM + * -XX:+IgnoreUnrecognizedVMOptions -XX:+AlwaysIncrementalInline + * compiler.valhalla.inlinetypes.TestTearing C1 + * @run main/othervm -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseArrayFlattening + * -XX:CompileCommand=dontinline,*::incrementAndCheck* + * compiler.valhalla.inlinetypes.TestTearing C1 + * @run main/othervm -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseArrayFlattening + * -XX:CompileCommand=dontinline,*::incrementAndCheck* + * -XX:+IgnoreUnrecognizedVMOptions -XX:+AlwaysIncrementalInline + * compiler.valhalla.inlinetypes.TestTearing C1 + */ + +@LooselyConsistentValue +value class MyValue { + // Make sure the payload size is <= 64-bit to enable atomic flattening + short x; + short y; + + private static final Unsafe U = Unsafe.getUnsafe(); + private static final long X_OFFSET; + private static final long Y_OFFSET; + static { + try { + Field xField = MyValue.class.getDeclaredField("x"); + X_OFFSET = U.objectFieldOffset(xField); + Field yField = MyValue.class.getDeclaredField("y"); + Y_OFFSET = U.objectFieldOffset(yField); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + static final MyValue DEFAULT = new MyValue((short)0, (short)0); + + MyValue(short x, short y) { + this.x = x; + this.y = y; + } + + MyValue incrementAndCheck() { + Asserts.assertEQ(x, y, "Inconsistent field values"); + return new MyValue((short)(x + 1), (short)(y + 1)); + } + + MyValue incrementAndCheckUnsafe() { + Asserts.assertEQ(x, y, "Inconsistent field values"); + MyValue vt = U.makePrivateBuffer(this); + U.putShort(vt, X_OFFSET, (short)(x + 1)); + U.putShort(vt, Y_OFFSET, (short)(y + 1)); + return U.finishPrivateBuffer(vt); + } +} + +public class TestTearing { + // Null-free, volatile -> atomic access + @Strict + @NullRestricted + volatile static MyValue field1 = MyValue.DEFAULT; + @Strict + @NullRestricted + volatile MyValue field2 = MyValue.DEFAULT; + + // Nullable fields are always atomic + static MyValue field3 = new MyValue((short)0, (short)0); + MyValue field4 = new MyValue((short)0, (short)0); + + // Final arrays + static final MyValue[] array1 = (MyValue[])ValueClass.newNullRestrictedAtomicArray(MyValue.class, 1, MyValue.DEFAULT); + static final MyValue[] array2 = (MyValue[])ValueClass.newNullableAtomicArray(MyValue.class, 1); + static { + array2[0] = new MyValue((short)0, (short)0); + } + static final MyValue[] array3 = new MyValue[] { new MyValue((short)0, (short)0) }; + + // Non-final arrays + static MyValue[] array4 = (MyValue[])ValueClass.newNullRestrictedAtomicArray(MyValue.class, 1, MyValue.DEFAULT); + static MyValue[] array5 = (MyValue[])ValueClass.newNullableAtomicArray(MyValue.class, 1); + static { + array5[0] = new MyValue((short)0, (short)0); + } + static MyValue[] array6 = new MyValue[] { new MyValue((short)0, (short)0) }; + + // Object arrays + static Object[] array7 = (MyValue[])ValueClass.newNullRestrictedAtomicArray(MyValue.class, 1, MyValue.DEFAULT); + static Object[] array8 = (MyValue[])ValueClass.newNullableAtomicArray(MyValue.class, 1); + static { + array8[0] = new MyValue((short)0, (short)0); + } + static Object[] array9 = new MyValue[] { new MyValue((short)0, (short)0) }; + + // Object arrays stored in volatile fields (for safe publication) + static volatile Object[] array10 = (MyValue[])ValueClass.newNullRestrictedAtomicArray(MyValue.class, 1, MyValue.DEFAULT); + static volatile Object[] array11 = (MyValue[])ValueClass.newNullableAtomicArray(MyValue.class, 1); + static { + array11[0] = new MyValue((short)0, (short)0); + } + static volatile Object[] array12 = new MyValue[] { new MyValue((short)0, (short)0) }; + + static final MethodHandle incrementAndCheck_mh; + + static { + try { + Class clazz = MyValue.class; + MethodHandles.Lookup lookup = MethodHandles.lookup(); + + MethodType mt = MethodType.methodType(MyValue.class); + incrementAndCheck_mh = lookup.findVirtual(clazz, "incrementAndCheck", mt); + } catch (NoSuchMethodException | IllegalAccessException e) { + e.printStackTrace(); + throw new RuntimeException("Method handle lookup failed"); + } + } + + static class Runner extends Thread { + TestTearing test; + private final int loopLimit; + + public Runner(TestTearing test, int loopLimit) { + this.test = test; + this.loopLimit = loopLimit; + } + + public void run() { + for (int i = 0; i < loopLimit; ++i) { + // Create "local" arrays so that C2 has full type info + MyValue[] localArray1 = (MyValue[])ValueClass.newNullRestrictedAtomicArray(MyValue.class, 1, MyValue.DEFAULT); + MyValue[] localArray2 = (MyValue[])ValueClass.newNullableAtomicArray(MyValue.class, 1); + localArray2[0] = new MyValue((short)0, (short)0); + MyValue[] localArray3 = new MyValue[] { new MyValue((short)0, (short)0) }; + + Asserts.assertTrue(ValueClass.isAtomicArray(localArray1)); + Asserts.assertTrue(ValueClass.isAtomicArray(localArray2)); + Asserts.assertTrue(ValueClass.isAtomicArray(localArray3)); + Asserts.assertTrue(ValueClass.isNullRestrictedArray(localArray1)); + Asserts.assertFalse(ValueClass.isNullRestrictedArray(localArray2)); + Asserts.assertFalse(ValueClass.isNullRestrictedArray(localArray3)); + + // Let them escape safely via a volatile field + array10 = localArray1; + array11 = localArray2; + array12 = localArray3; + + localArray1[0] = localArray1[0].incrementAndCheck(); + localArray2[0] = localArray2[0].incrementAndCheck(); + localArray3[0] = localArray3[0].incrementAndCheck(); + + test.field1 = test.field1.incrementAndCheck(); + test.field2 = test.field2.incrementAndCheck(); + test.field3 = test.field3.incrementAndCheck(); + test.field4 = test.field4.incrementAndCheck(); + array1[0] = array1[0].incrementAndCheck(); + array2[0] = array2[0].incrementAndCheck(); + array3[0] = array3[0].incrementAndCheck(); + array4[0] = array4[0].incrementAndCheck(); + array5[0] = array5[0].incrementAndCheck(); + array6[0] = array6[0].incrementAndCheck(); + array7[0] = ((MyValue)array7[0]).incrementAndCheck(); + array8[0] = ((MyValue)array8[0]).incrementAndCheck(); + array9[0] = ((MyValue)array9[0]).incrementAndCheck(); + array10[0] = ((MyValue)array10[0]).incrementAndCheck(); + array11[0] = ((MyValue)array11[0]).incrementAndCheck(); + array12[0] = ((MyValue)array12[0]).incrementAndCheck(); + + test.field1 = test.field1.incrementAndCheckUnsafe(); + test.field2 = test.field2.incrementAndCheckUnsafe(); + test.field3 = test.field3.incrementAndCheckUnsafe(); + test.field4 = test.field4.incrementAndCheckUnsafe(); + array1[0] = array1[0].incrementAndCheckUnsafe(); + array2[0] = array2[0].incrementAndCheckUnsafe(); + array3[0] = array3[0].incrementAndCheckUnsafe(); + array4[0] = array4[0].incrementAndCheckUnsafe(); + array5[0] = array5[0].incrementAndCheckUnsafe(); + array6[0] = array6[0].incrementAndCheckUnsafe(); + array7[0] = ((MyValue)array7[0]).incrementAndCheckUnsafe(); + array8[0] = ((MyValue)array8[0]).incrementAndCheckUnsafe(); + array9[0] = ((MyValue)array9[0]).incrementAndCheckUnsafe(); + array10[0] = ((MyValue)array10[0]).incrementAndCheckUnsafe(); + array11[0] = ((MyValue)array11[0]).incrementAndCheckUnsafe(); + array12[0] = ((MyValue)array12[0]).incrementAndCheckUnsafe(); + + try { + test.field1 = (MyValue)incrementAndCheck_mh.invokeExact(test.field1); + test.field2 = (MyValue)incrementAndCheck_mh.invokeExact(test.field2); + test.field3 = (MyValue)incrementAndCheck_mh.invokeExact(test.field1); + test.field4 = (MyValue)incrementAndCheck_mh.invokeExact(test.field2); + array1[0] = (MyValue)incrementAndCheck_mh.invokeExact(array1[0]); + array2[0] = (MyValue)incrementAndCheck_mh.invokeExact(array2[0]); + array3[0] = (MyValue)incrementAndCheck_mh.invokeExact(array3[0]); + array4[0] = (MyValue)incrementAndCheck_mh.invokeExact(array4[0]); + array5[0] = (MyValue)incrementAndCheck_mh.invokeExact(array5[0]); + array6[0] = (MyValue)incrementAndCheck_mh.invokeExact(array6[0]); + array7[0] = (MyValue)incrementAndCheck_mh.invokeExact((MyValue)array7[0]); + array8[0] = (MyValue)incrementAndCheck_mh.invokeExact((MyValue)array8[0]); + array9[0] = (MyValue)incrementAndCheck_mh.invokeExact((MyValue)array9[0]); + array10[0] = (MyValue)incrementAndCheck_mh.invokeExact((MyValue)array10[0]); + array11[0] = (MyValue)incrementAndCheck_mh.invokeExact((MyValue)array11[0]); + array12[0] = (MyValue)incrementAndCheck_mh.invokeExact((MyValue)array12[0]); + } catch (Throwable t) { + throw new RuntimeException("Test failed", t); + } + } + } + } + + public static void main(String[] args) throws Exception { + // Create threads that concurrently update some value class (array) fields + // and check the fields of the value classes for consistency to detect tearing. + int loopLimit = 1_000_000; + if (args.length > 0) { + Asserts.assertTrue(args[0].equals("C1")); + loopLimit = 50_000; + } + TestTearing test = new TestTearing(); + Thread runner = null; + for (int i = 0; i < 10; ++i) { + runner = new Runner(test, loopLimit); + runner.start(); + } + runner.join(); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestTrivialMethods.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestTrivialMethods.java new file mode 100644 index 00000000000..612209bb0c3 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestTrivialMethods.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8275825 8289686 + * @summary Verify that trivial accessor methods operating on an inline type + * field are C2 compiled to enable scalarization of the arg/return value. + * @requires vm.compiler2.enabled + * @library /test/lib /compiler/whitebox / + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @build jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. + * -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch + * -XX:+InlineTypePassFieldsAsArgs -XX:+InlineTypeReturnedAsFields + * -XX:+IgnoreUnrecognizedVMOptions -XX:-StressCallingConvention + * -XX:CompileCommand=dontinline,*::getter* -XX:CompileCommand=dontinline,*::setter* + * -XX:CompileCommand=dontinline,*::constantGetter* + * compiler.valhalla.inlinetypes.TestTrivialMethods + */ + +package compiler.valhalla.inlinetypes; + +import compiler.whitebox.CompilerWhiteBoxTest; + +import java.lang.reflect.Method; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import jdk.test.lib.Asserts; +import jdk.test.lib.Utils; + +import jdk.test.whitebox.WhiteBox; + +public class TestTrivialMethods { + public static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + + @Strict + @NullRestricted + static MyValue3 staticField = MyValue3.create(); + static MyValue3 staticFieldRef = MyValue3.create(); + @Strict + @NullRestricted + MyValue3 field = MyValue3.create(); + MyValue3 fieldRef = MyValue3.create(); + Object objField = null; + + public MyValue3 getter1() { + return staticField; + } + + public static MyValue3 getter2() { + return staticField; + } + + public MyValue3 getter3() { + return field; + } + + public Object getter4(MyValue3 unusedArg) { + return objField; + } + + public int constantGetter(MyValue3 unusedArg) { + return 0; + } + + public MyValue3 getter1Ref() { + return staticFieldRef; + } + + public static MyValue3 getter2Ref() { + return staticFieldRef; + } + + public MyValue3 getter3Ref() { + return fieldRef; + } + + public Object getter4Ref(MyValue3 unusedArg) { + return objField; + } + + public int constantGetterRef(MyValue3 unusedArg) { + return 0; + } + + public void setter1(MyValue3 val) { + staticField = val; + } + + public static void setter2(MyValue3 val) { + staticField = val; + } + + public void setter1Ref(MyValue3 val) { + staticFieldRef = val; + } + + public static void setter2Ref(MyValue3 val) { + staticFieldRef = val; + } + + public void setter3Ref(MyValue3 val) { + fieldRef = val; + } + + public static void main(String[] args) throws Exception { + TestTrivialMethods t = new TestTrivialMethods(); + // Warmup to trigger compilation + for (int i = 0; i < 100_000; ++i) { + t.getter1(); + t.getter2(); + t.getter3(); + t.getter4(staticField); + t.constantGetter(staticField); + t.getter1Ref(); + t.getter2Ref(); + t.getter3Ref(); + t.getter3Ref(); + t.getter4Ref(null); + t.constantGetterRef(null); + t.setter1(staticField); + t.setter2(staticField); + t.setter1Ref(staticField); + t.setter2Ref(staticField); + t.setter3Ref(staticField); + } + Method m = TestTrivialMethods.class.getMethod("getter1"); + Asserts.assertEQ(WHITE_BOX.getMethodCompilationLevel(m, false), CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION, "getter1 is not C2 compiled"); + m = TestTrivialMethods.class.getMethod("getter2"); + Asserts.assertEQ(WHITE_BOX.getMethodCompilationLevel(m, false), CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION, "getter2 is not C2 compiled"); + m = TestTrivialMethods.class.getMethod("getter3"); + Asserts.assertEQ(WHITE_BOX.getMethodCompilationLevel(m, false), CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION, "getter3 is not C2 compiled"); + m = TestTrivialMethods.class.getMethod("setter1", MyValue3.class); + m = TestTrivialMethods.class.getMethod("getter4", MyValue3.class); + Asserts.assertEQ(WHITE_BOX.getMethodCompilationLevel(m, false), CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION, "getter4 is not C2 compiled"); + m = TestTrivialMethods.class.getMethod("constantGetter", MyValue3.class); + Asserts.assertEQ(WHITE_BOX.getMethodCompilationLevel(m, false), CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION, "constantGetter is not C2 compiled"); + m = TestTrivialMethods.class.getMethod("getter1Ref"); + Asserts.assertEQ(WHITE_BOX.getMethodCompilationLevel(m, false), CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION, "getter1Ref is not C2 compiled"); + m = TestTrivialMethods.class.getMethod("getter2Ref"); + Asserts.assertEQ(WHITE_BOX.getMethodCompilationLevel(m, false), CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION, "getter2Ref is not C2 compiled"); + m = TestTrivialMethods.class.getMethod("getter3Ref"); + Asserts.assertEQ(WHITE_BOX.getMethodCompilationLevel(m, false), CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION, "getter3Ref is not C2 compiled"); + m = TestTrivialMethods.class.getMethod("getter4Ref", MyValue3.class); + Asserts.assertEQ(WHITE_BOX.getMethodCompilationLevel(m, false), CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION, "getter4Ref is not C2 compiled"); + m = TestTrivialMethods.class.getMethod("constantGetterRef", MyValue3.class); + Asserts.assertEQ(WHITE_BOX.getMethodCompilationLevel(m, false), CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION, "constantGetterRef is not C2 compiled"); + m = TestTrivialMethods.class.getMethod("setter1", MyValue3.class); + Asserts.assertEQ(WHITE_BOX.getMethodCompilationLevel(m, false), CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION, "setter1 is not C2 compiled"); + m = TestTrivialMethods.class.getMethod("setter2", MyValue3.class); + Asserts.assertEQ(WHITE_BOX.getMethodCompilationLevel(m, false), CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION, "setter2 is not C2 compiled"); + m = TestTrivialMethods.class.getMethod("setter1Ref", MyValue3.class); + Asserts.assertEQ(WHITE_BOX.getMethodCompilationLevel(m, false), CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION, "setter1Ref is not C2 compiled"); + m = TestTrivialMethods.class.getMethod("setter2Ref", MyValue3.class); + Asserts.assertEQ(WHITE_BOX.getMethodCompilationLevel(m, false), CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION, "setter2Ref is not C2 compiled"); + m = TestTrivialMethods.class.getMethod("setter3Ref", MyValue3.class); + Asserts.assertEQ(WHITE_BOX.getMethodCompilationLevel(m, false), CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION, "setter3Ref is not C2 compiled"); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUnexpectedMemBar.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUnexpectedMemBar.java new file mode 100644 index 00000000000..f3d00a38ed3 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUnexpectedMemBar.java @@ -0,0 +1,124 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8270995 + * @summary Membars of non-escaping value class buffer allocations should be removed. + * @library /test/lib / + * @enablePreview + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions + * -XX:-TieredCompilation -XX:-ReduceInitialCardMarks + * -XX:+AlwaysIncrementalInline -Xbatch -XX:CompileCommand=compileonly,*TestUnexpectedMemBar::test* + * -XX:+StressIGVN -XX:+StressGCM -XX:+StressLCM -XX:StressSeed=851121348 + * compiler.valhalla.inlinetypes.TestUnexpectedMemBar + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions + * -XX:-TieredCompilation -XX:-ReduceInitialCardMarks -XX:+AlwaysIncrementalInline + * -Xbatch -XX:CompileCommand=compileonly,*TestUnexpectedMemBar::test* + * -XX:+StressIGVN -XX:+StressGCM -XX:+StressLCM + * compiler.valhalla.inlinetypes.TestUnexpectedMemBar + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions + * -Xbatch -XX:CompileCommand=compileonly,*TestUnexpectedMemBar::test* + * -XX:+StressIGVN -XX:+StressGCM -XX:+StressLCM + * compiler.valhalla.inlinetypes.TestUnexpectedMemBar + */ + +package compiler.valhalla.inlinetypes; + +import jdk.test.lib.Asserts; + +value class MyValue1 { + int a = 0; + int b = 0; + int c = 0; + int d = 0; + int e = 0; + + Integer i; + int[] array; + + public MyValue1(Integer i, int[] array) { + this.i = i; + this.array = array; + } +} + +value class MyValue2 { + int a = 0; + int b = 0; + int c = 0; + int d = 0; + int e = 0; + + NonValueClass obj; + int[] array; + + public MyValue2(NonValueClass obj, int[] array) { + this.obj = obj; + this.array = array; + } +} + +public class TestUnexpectedMemBar { + + public static int test1(Integer i) { + int[] array = new int[1]; + MyValue1 vt = new MyValue1(i, array); + vt = new MyValue1(vt.i, vt.array); + return vt.i + vt.array[0]; + } + + public static int test2(Integer i) { + int[] array = {i}; + MyValue1 vt = new MyValue1(i, array); + vt = new MyValue1(vt.i, vt.array); + return vt.i + vt.array[0]; + } + + public static int test3(NonValueClass obj) { + int[] array = new int[1]; + MyValue2 vt = new MyValue2(obj, array); + vt = new MyValue2(vt.obj, vt.array); + return vt.obj.x + vt.array[0]; + } + + public static int test4(NonValueClass obj) { + int[] array = {obj.x}; + MyValue2 vt = new MyValue2(obj, array); + vt = new MyValue2(vt.obj, vt.array); + return vt.obj.x + vt.array[0]; + } + + public static void main(String[] args) { + for (int i = 0; i < 100_000; ++i) { + int res = test1(i); + Asserts.assertEquals(res, i, "test1 failed"); + res = test2(i); + Asserts.assertEquals(res, 2*i, "test2 failed"); + res = test3(new NonValueClass(i)); + Asserts.assertEquals(res, i, "test3 failed"); + res = test4(new NonValueClass(i)); + Asserts.assertEquals(res, 2*i, "test4 failed"); + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUninitializedValueClass.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUninitializedValueClass.java new file mode 100644 index 00000000000..2533aff97ec --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUninitializedValueClass.java @@ -0,0 +1,45 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +/** + * @test + * @bug 8282569 + * @summary Test that uninitialized value class is properly handled by C2. + * @enablePreview + * @run main/othervm -XX:CompileCommand=compileonly,*:: -Xcomp -XX:-TieredCompilation + * compiler.valhalla.inlinetypes.TestUninitializedValueClass + */ + +value class MyValue { + static final MyValue EMPTY = new MyValue(); + int value = 0; +} + +public class TestUninitializedValueClass { + + public static void main(String[] args) { + MyValue unused = new MyValue(); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUnloadedCastType.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUnloadedCastType.java new file mode 100644 index 00000000000..d9dd67e7c41 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUnloadedCastType.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @summary Test casting of value objects to an unloaded type. + * @enablePreview + * @library /test/lib + * @run main/othervm -Xbatch -XX:CompileCommand=dontinline,TestUnloadedCastType::test* + * TestUnloadedCastType + */ + +import jdk.test.lib.Asserts; + +public class TestUnloadedCastType { + + static value class MyValue { + int x = 0; + } + + static class Unloaded { } + + public static Object test(MyValue val) { + Object obj = val; + return (Unloaded)obj; + } + + public static void main(String[] args) { + for (int i = 0; i < 50_000; ++i) { + Asserts.assertEQ(test(null), null); + } + try { + test(new MyValue()); + throw new RuntimeException("No ClassCastException thrown"); + } catch (ClassCastException e) { + // Expected + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUnloadedInlineTypeArray.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUnloadedInlineTypeArray.java new file mode 100644 index 00000000000..73e3d2240da --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUnloadedInlineTypeArray.java @@ -0,0 +1,472 @@ +/* + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2023, Red Hat, Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8182997 8214898 + * @library /test/lib + * @summary Test the handling of arrays of unloaded value classes. + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm -Xcomp + * -XX:CompileCommand=compileonly,TestUnloadedInlineTypeArray::test* + * TestUnloadedInlineTypeArray + * @run main/othervm -Xcomp -XX:-UseArrayFlattening + * -XX:CompileCommand=compileonly,TestUnloadedInlineTypeArray::test* + * TestUnloadedInlineTypeArray + * @run main/othervm -Xcomp + * TestUnloadedInlineTypeArray + * @run main/othervm -Xcomp -XX:-UseArrayFlattening + * TestUnloadedInlineTypeArray + * @run main/othervm -Xcomp -XX:-TieredCompilation + * -XX:CompileCommand=compileonly,TestUnloadedInlineTypeArray::test* + * TestUnloadedInlineTypeArray + * @run main/othervm -Xcomp -XX:-TieredCompilation -XX:-UseArrayFlattening + * -XX:CompileCommand=compileonly,TestUnloadedInlineTypeArray::test* + * TestUnloadedInlineTypeArray + * @run main/othervm -Xcomp -XX:-TieredCompilation + * TestUnloadedInlineTypeArray + * @run main/othervm -Xcomp -XX:-TieredCompilation -XX:-UseArrayFlattening + * TestUnloadedInlineTypeArray + */ + +import jdk.test.lib.Asserts; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; + +@LooselyConsistentValue +value class MyValue1 { + int foo; + + private MyValue1() { + foo = 0x42; + } +} + +@LooselyConsistentValue +value class MyValue2 { + int foo; + + public MyValue2(int n) { + foo = n; + } +} + +@LooselyConsistentValue +value class MyValue3 { + int foo; + + public MyValue3(int n) { + foo = n; + } +} + +@LooselyConsistentValue +value class MyValue4 { + int foo; + + public MyValue4(int n) { + foo = n; + } +} + +@LooselyConsistentValue +value class MyValue5 { + int foo; + + public MyValue5(int n) { + foo = n; + } +} + +@LooselyConsistentValue +value class MyValue6 { + int foo; + + public MyValue6(int n) { + foo = n; + } + + public MyValue6(MyValue6 v, MyValue6[] dummy) { + foo = v.foo + 1; + } +} + +@LooselyConsistentValue +value class MyValue7 { + int foo; + + public MyValue7(int n) { + foo = n; + } +} + +@LooselyConsistentValue +value class MyValue8 { + int foo = 123; + static { + TestUnloadedInlineTypeArray.MyValue8_inited = true; + } +} + +@LooselyConsistentValue +value class MyValue9 { + int foo = 123; + static { + TestUnloadedInlineTypeArray.MyValue9_inited = true; + } +} + +@LooselyConsistentValue +value class MyValue10 { + int foo = 42; +} + +@LooselyConsistentValue +value class MyValue11 { + int foo = 42; +} + +public class TestUnloadedInlineTypeArray { + static boolean MyValue8_inited = false; + static boolean MyValue9_inited = false; + + static MyValue1[] target1() { + return (MyValue1[])ValueClass.newNullableAtomicArray(MyValue1.class, 10); + } + + static void test1() { + target1(); + } + + static MyValue1[] target1Nullable() { + return new MyValue1[10]; + } + + static void test1Nullable() { + target1Nullable(); + } + + static int test2(MyValue2[] arr) { + if (arr != null) { + return arr[1].foo; + } else { + return 1234; + } + } + + static void verifyTest2() { + int n = 50000; + + int m = 9999; + for (int i = 0; i < n; i++) { + m = test2(null); + } + Asserts.assertEQ(m, 1234); + + MyValue2[] arr = (MyValue2[])ValueClass.newNullableAtomicArray(MyValue2.class, 2); + arr[1] = new MyValue2(5678); + m = 9999; + for (int i = 0; i < n; i++) { + m = test2(arr); + } + Asserts.assertEQ(m, 5678); + } + + static int test2Nullable(MyValue2[] arr) { + if (arr != null) { + return arr[1].foo; + } else { + return 1234; + } + } + + static void verifyTest2Nullable() { + int n = 50000; + + int m = 9999; + for (int i = 0; i < n; i++) { + m = test2Nullable(null); + } + Asserts.assertEQ(m, 1234); + + MyValue2[] arr = new MyValue2[2]; + arr[1] = new MyValue2(5678); + m = 9999; + for (int i = 0; i < n; i++) { + m = test2Nullable(arr); + } + Asserts.assertEQ(m, 5678); + } + + static void test3(MyValue3[] arr) { + if (arr != null) { + arr[1] = new MyValue3(2345); + } + } + + static void verifyTest3() { + int n = 50000; + + for (int i = 0; i < n; i++) { + test3(null); + } + + MyValue3[] arr = (MyValue3[])ValueClass.newNullableAtomicArray(MyValue3.class, 2); + for (int i = 0; i < n; i++) { + test3(arr); + } + Asserts.assertEQ(arr[1].foo, 2345); + } + + static void test3Nullable(MyValue3[] arr) { + if (arr != null) { + arr[0] = null; + arr[1] = new MyValue3(2345); + } + } + + static void verifyTest3Nullable() { + int n = 50000; + + for (int i = 0; i < n; i++) { + test3Nullable(null); + } + + MyValue3[] arr = new MyValue3[2]; + for (int i = 0; i < n; i++) { + test3Nullable(arr); + } + Asserts.assertEQ(arr[0], null); + Asserts.assertEQ(arr[1].foo, 2345); + } + + static MyValue4[] test4(boolean b) { + // range check elimination + if (b) { + MyValue4[] arr = (MyValue4[])ValueClass.newNullableAtomicArray(MyValue4.class, 10); + arr[1] = new MyValue4(2345); + return arr; + } else { + return null; + } + } + + static void verifyTest4() { + int n = 50000; + + for (int i = 0; i < n; i++) { + test4(false); + } + + MyValue4[] arr = null; + for (int i = 0; i < n; i++) { + arr = test4(true); + } + Asserts.assertEQ(arr[1].foo, 2345); + } + + static MyValue4[] test4Nullable(boolean b) { + // range check elimination + if (b) { + MyValue4[] arr = new MyValue4[10]; + arr[0] = null; + arr[1] = new MyValue4(2345); + return arr; + } else { + return null; + } + } + + static void verifyTest4Nullable() { + int n = 50000; + + for (int i = 0; i < n; i++) { + test4Nullable(false); + } + + MyValue4[] arr = null; + for (int i = 0; i < n; i++) { + arr = test4Nullable(true); + } + Asserts.assertEQ(arr[0], null); + Asserts.assertEQ(arr[1].foo, 2345); + arr[3] = null; + } + + static Object[] test5(int n) { + if (n == 0) { + return null; + } else if (n == 1) { + MyValue5[] arr = (MyValue5[])ValueClass.newNullableAtomicArray(MyValue5.class, 10); + arr[1] = new MyValue5(12345); + return arr; + } else { + MyValue5[] arr = new MyValue5[10]; + arr[1] = new MyValue5(22345); + return arr; + } + } + + static void verifyTest5() { + int n = 50000; + + for (int i = 0; i < n; i++) { + test5(0); + } + + { + MyValue5[] arr = null; + for (int i = 0; i < n; i++) { + arr = (MyValue5[])test5(1); + } + Asserts.assertEQ(arr[1].foo, 12345); + } + { + MyValue5[] arr = null; + for (int i = 0; i < n; i++) { + arr = (MyValue5[])test5(2); + } + Asserts.assertEQ(arr[1].foo, 22345); + } + } + + static Object test6() { + return new MyValue6(new MyValue6(123), null); + } + + static void verifyTest6() { + Object n = test6(); + Asserts.assertEQ(n.toString(), "MyValue6@" + Integer.toHexString(n.hashCode())); + } + + static int test7(MyValue7[][] arr) { + if (arr != null) { + return arr[0][1].foo; + } else { + return 1234; + } + } + + static void verifyTest7() { + int n = 50000; + + int m = 9999; + for (int i = 0; i < n; i++) { + m = test7(null); + } + Asserts.assertEQ(m, 1234); + + MyValue7[][] arr = { (MyValue7[])ValueClass.newNullableAtomicArray(MyValue7.class, 2), + (MyValue7[])ValueClass.newNullableAtomicArray(MyValue7.class, 2) }; + Object[] oa = arr[1]; + Asserts.assertEQ(oa[0], null); + + arr[0][1] = new MyValue7(5678); + m = 9999; + for (int i = 0; i < n; i++) { + m = test7(arr); + } + Asserts.assertEQ(m, 5678); + } + + static int test7Nullable(MyValue7[][] arr) { + if (arr != null) { + arr[0][0] = null; + return arr[0][1].foo; + } else { + return 1234; + } + } + + static void verifyTest7Nullable() { + int n = 50000; + + int m = 9999; + for (int i = 0; i < n; i++) { + m = test7Nullable(null); + } + Asserts.assertEQ(m, 1234); + + MyValue7[][] arr = new MyValue7[2][2]; + Object[] oa = arr[1]; + Asserts.assertEQ(oa[0], null); + + arr[0][1] = new MyValue7(5678); + m = 9999; + for (int i = 0; i < n; i++) { + m = test7Nullable(arr); + } + Asserts.assertEQ(m, 5678); + Asserts.assertEQ(arr[0][0], null); + } + + static void test8() { + MyValue8 a[] = new MyValue8[0]; + Asserts.assertEQ(MyValue8_inited, false); + + MyValue8 b[] = (MyValue8[])ValueClass.newNullableAtomicArray(MyValue8.class, 0); + Asserts.assertEQ(MyValue8_inited, true); + } + + static void test9() { + MyValue9 a[][] = new MyValue9[10][0]; + Asserts.assertEQ(MyValue9_inited, false); + + a[0] = (MyValue9[])ValueClass.newNullableAtomicArray(MyValue9.class, 0); + Asserts.assertEQ(MyValue9_inited, true); + } + + static void test10(MyValue10 dummy) { + MyValue10[][] a = { (MyValue10[])ValueClass.newNullRestrictedNonAtomicArray(MyValue10.class, 1, new MyValue10()) }; + if (a[0][0].equals(null)) throw new RuntimeException("test10 failed"); + Asserts.assertNE(a[0][0], null); + } + + static void test11(MyValue10 dummy) { + MyValue11[][] a = new MyValue11[1][1]; + Asserts.assertEQ(a[0][0], null); + } + + static public void main(String[] args) { + test1(); + test1Nullable(); + verifyTest2(); + verifyTest2Nullable(); + verifyTest3(); + verifyTest3Nullable(); + verifyTest4(); + verifyTest4Nullable(); + verifyTest5(); + verifyTest6(); + verifyTest7(); + verifyTest7Nullable(); + test8(); + test9(); + test10(null); + test11(null); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUnloadedInlineTypeField.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUnloadedInlineTypeField.java new file mode 100644 index 00000000000..cbfb743979b --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUnloadedInlineTypeField.java @@ -0,0 +1,1049 @@ +/* + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.*; +import jdk.test.lib.Asserts; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import static compiler.valhalla.inlinetypes.InlineTypes.rI; + +/* + * @test + * @key randomness + * @summary Test the handling of fields of unloaded value classes. + * @library /test/lib / + * @requires (os.simpleArch == "x64" | os.simpleArch == "aarch64") + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @compile hack/GetUnresolvedInlineFieldWrongSignature.java + * @compile TestUnloadedInlineTypeField.java + * @run main/othervm/timeout=300 compiler.valhalla.inlinetypes.TestUnloadedInlineTypeField + */ + +public class TestUnloadedInlineTypeField { + // Only prevent loading of classes when testing with C1. Load classes + // early when executing with C2 to prevent uncommon traps. It's still + // beneficial to execute this test with C2 because it also checks handling + // of type mismatches. + + public static void main(String[] args) { + final Scenario[] scenarios = { + new Scenario(0), + new Scenario(1, "-XX:-UseFieldFlattening"), + new Scenario(2, "-XX:+IgnoreUnrecognizedVMOptions", "-XX:+PatchALot"), + new Scenario(3, "-XX:-UseFieldFlattening", + "-XX:+IgnoreUnrecognizedVMOptions", "-XX:+PatchALot") + }; + InlineTypes.getFramework() + .addScenarios(scenarios) + .addFlags("--enable-preview", + // Prevent IR Test Framework from loading classes + "-DIgnoreCompilerControls=true", + // Some tests trigger frequent re-compilation. Don't mark them as non-compilable. + "-XX:PerMethodRecompilationCutoff=-1", "-XX:PerBytecodeRecompilationCutoff=-1") + .start(); + } + + // Test case 1: + // The value class field class has been loaded, but the holder class has not been loaded. + // + // aload_0 + // getfield MyValue1Holder.v:LMyValue1; + // ^ not loaded ^ already loaded + // + // MyValue1 has already been loaded, because it's in the preload attribute of + // TestUnloadedInlineTypeField, due to TestUnloadedInlineTypeField.test1_precondition(). + @LooselyConsistentValue + static value class MyValue1 { + int foo; + + MyValue1() { + foo = rI; + } + } + + static class MyValue1Holder { + @Strict + @NullRestricted + MyValue1 v; + + public MyValue1Holder() { + v = new MyValue1(); + } + } + + static MyValue1 test1_precondition() { + return new MyValue1(); + } + + @Test + public int test1(Object holder) { + if (holder != null) { + // Don't use MyValue1Holder in the signature, it might trigger class loading + return ((MyValue1Holder)holder).v.foo; + } else { + return 0; + } + } + + @Run(test = "test1") + public void test1_verifier(RunInfo info) { + if (info.isWarmUp() && !info.isC2CompilationEnabled()) { + test1(null); + } else { + MyValue1Holder holder = new MyValue1Holder(); + Asserts.assertEQ(test1(holder), rI); + } + } + + // Test case 2: + // Both the value class field class, and the holder class have not been loaded. + // + // aload_0 + // getfield MyValue2Holder.v:LMyValue2; + // ^ not loaded ^ not loaded + // + // MyValue2 has not been loaded, because it is not explicitly referenced by + // TestUnloadedInlineTypeField. + @LooselyConsistentValue + static value class MyValue2 { + int foo; + + public MyValue2(int n) { + foo = n; + } + } + + static class MyValue2Holder { + @Strict + @NullRestricted + MyValue2 v; + + public MyValue2Holder() { + v = new MyValue2(rI); + } + } + + @Test + public int test2(Object holder) { + if (holder != null) { + // Don't use MyValue2Holder in the signature, it might trigger class loading + return ((MyValue2Holder)holder).v.foo; + } else { + return 0; + } + } + + @Run(test = "test2") + public void test2_verifier(RunInfo info) { + if (info.isWarmUp() && !info.isC2CompilationEnabled()) { + test2(null); + } else { + MyValue2Holder holder = new MyValue2Holder(); + Asserts.assertEQ(test2(holder), rI); + } + } + + // Test case 4: + // Same as case 1, except we use putfield instead of getfield. + @LooselyConsistentValue + static value class MyValue4 { + int foo; + + MyValue4(int n) { + foo = n; + } + } + + static class MyValue4Holder { + @Strict + @NullRestricted + MyValue4 v; + + public MyValue4Holder() { + v = new MyValue4(0); + } + } + + @Test + public void test4(Object holder, MyValue4 v) { + if (holder != null) { + // Don't use MyValue4Holder in the signature, it might trigger class loading + ((MyValue4Holder)holder).v = v; + } + } + + @Run(test = "test4") + public void test4_verifier(RunInfo info) { + MyValue4 v = new MyValue4(rI); + if (info.isWarmUp() && !info.isC2CompilationEnabled()) { + test4(null, v); + } else { + MyValue4Holder holder = new MyValue4Holder(); + test4(holder, v); + Asserts.assertEQ(holder.v.foo, rI); + } + } + + // Test case 5: + // Same as case 2, except we use putfield instead of getfield. + @LooselyConsistentValue + static value class MyValue5 { + int foo; + + MyValue5(int n) { + foo = n; + } + } + + static class MyValue5Holder { + @Strict + @NullRestricted + MyValue5 v; + + public MyValue5Holder() { + v = new MyValue5(0); + } + + public Object make(int n) { + return new MyValue5(n); + } + } + + @Test + public void test5(Object holder, Object o) { + if (holder != null) { + // Don't use MyValue5 and MyValue5Holder in the signature, it might trigger class loading + MyValue5 v = (MyValue5)o; + ((MyValue5Holder)holder).v = v; + } + } + + @Run(test = "test5") + public void test5_verifier(RunInfo info) { + if (info.isWarmUp() && !info.isC2CompilationEnabled()) { + test5(null, null); + } else { + MyValue5Holder holder = new MyValue5Holder(); + Object v = holder.make(rI); + test5(holder, v); + Asserts.assertEQ(holder.v.foo, rI); + } + } + + + // Test case 6: (same as test1, except we use getstatic instead of getfield) + // The value class field class has been loaded, but the holder class has not been loaded. + // + // getstatic MyValue6Holder.v:LMyValue1; + // ^ not loaded ^ already loaded + // + // MyValue6 has already been loaded, because it's in the preload attribute of + // TestUnloadedInlineTypeField, due to TestUnloadedInlineTypeField.test1_precondition(). + @LooselyConsistentValue + static value class MyValue6 { + int foo; + + MyValue6() { + foo = rI; + } + } + + static class MyValue6Holder { + @Strict + @NullRestricted + static MyValue6 v = new MyValue6(); + } + + static MyValue6 test6_precondition() { + return new MyValue6(); + } + + @Test + public int test6(int n) { + if (n == 0) { + return 0; + } else { + return MyValue6Holder.v.foo + n; + } + } + + @Run(test = "test6") + public void test6_verifier(RunInfo info) { + if (info.isWarmUp() && !info.isC2CompilationEnabled()) { + test6(0); + } else { + Asserts.assertEQ(test6(rI), 2*rI); + } + } + + + // Test case 7: (same as test2, except we use getstatic instead of getfield) + // Both the value class field class, and the holder class have not been loaded. + // + // getstatic MyValue7Holder.v:LMyValue7; + // ^ not loaded ^ not loaded + // + // MyValue7 has not been loaded, because it is not explicitly referenced by + // TestUnloadedInlineTypeField. + @LooselyConsistentValue + static value class MyValue7 { + int foo; + + MyValue7(int n) { + foo = n; + } + } + + static class MyValue7Holder { + @Strict + @NullRestricted + static MyValue7 v = new MyValue7(rI); + } + + @Test + public int test7(int n) { + if (n == 0) { + return 0; + } else { + return MyValue7Holder.v.foo + n; + } + } + + @Run(test = "test7") + public void test7_verifier(RunInfo info) { + if (info.isWarmUp() && !info.isC2CompilationEnabled()) { + test7(0); + } else { + Asserts.assertEQ(test7(rI), 2*rI); + } + } + + // Test case 8: + // Same as case 1, except holder is allocated in test method (-> no holder null check required) + @LooselyConsistentValue + static value class MyValue8 { + int foo; + + MyValue8() { + foo = rI; + } + } + + static class MyValue8Holder { + @Strict + @NullRestricted + MyValue8 v; + + public MyValue8Holder() { + v = new MyValue8(); + } + } + + static MyValue8 test8_precondition() { + return new MyValue8(); + } + + @Test + public int test8(boolean warmup) { + if (!warmup) { + MyValue8Holder holder = new MyValue8Holder(); + return holder.v.foo; + } else { + return 0; + } + } + + @Run(test = "test8") + public void test8_verifier(RunInfo info) { + if (info.isWarmUp() && !info.isC2CompilationEnabled()) { + test8(true); + } else { + Asserts.assertEQ(test8(false), rI); + } + } + + // Test case 9: + // Same as case 2, except holder is allocated in test method (-> no holder null check required) + @LooselyConsistentValue + static value class MyValue9 { + int foo; + + public MyValue9(int n) { + foo = n; + } + } + + static class MyValue9Holder { + @Strict + @NullRestricted + MyValue9 v; + + public MyValue9Holder() { + v = new MyValue9(rI); + } + } + + @Test + public int test9(boolean warmup) { + if (!warmup) { + MyValue9Holder holder = new MyValue9Holder(); + return holder.v.foo; + } else { + return 0; + } + } + + @Run(test = "test9") + public void test9_verifier(RunInfo info) { + if (info.isWarmUp() && !info.isC2CompilationEnabled()) { + test9(true); + } else { + Asserts.assertEQ(test9(false), rI); + } + } + + // Test case 11: + // Same as case 4, except holder is allocated in test method (-> no holder null check required) + @LooselyConsistentValue + static value class MyValue11 { + int foo; + + MyValue11(int n) { + foo = n; + } + } + + static class MyValue11Holder { + @Strict + @NullRestricted + MyValue11 v; + + public MyValue11Holder() { + v = new MyValue11(0); + } + } + + @Test + public Object test11(boolean warmup, MyValue11 v) { + if (!warmup) { + MyValue11Holder holder = new MyValue11Holder(); + holder.v = v; + return holder; + } else { + return null; + } + } + + @Run(test = "test11") + public void test11_verifier(RunInfo info) { + MyValue11 v = new MyValue11(rI); + if (info.isWarmUp() && !info.isC2CompilationEnabled()) { + test11(true, v); + } else { + MyValue11Holder holder = (MyValue11Holder)test11(false, v); + Asserts.assertEQ(holder.v.foo, rI); + } + } + + // Test case 12: + // Same as case 5, except holder is allocated in test method (-> no holder null check required) + @LooselyConsistentValue + static value class MyValue12 { + int foo; + + MyValue12(int n) { + foo = n; + } + } + + static class MyValue12Holder { + @Strict + @NullRestricted + MyValue12 v; + + public MyValue12Holder() { + v = new MyValue12(0); + } + } + + @Test + public Object test12(boolean warmup, Object o) { + if (!warmup) { + // Don't use MyValue12 in the signature, it might trigger class loading + MyValue12Holder holder = new MyValue12Holder(); + holder.v = (MyValue12)o; + return holder; + } else { + return null; + } + } + + @Run(test = "test12") + public void test12_verifier(RunInfo info) { + if (info.isWarmUp() && !info.isC2CompilationEnabled()) { + test12(true, null); + } else { + MyValue12 v = new MyValue12(rI); + MyValue12Holder holder = (MyValue12Holder)test12(false, v); + Asserts.assertEQ(holder.v.foo, rI); + } + } + + // Test case 13: + // Same as case 10, except MyValue13 is allocated in test method + @LooselyConsistentValue + static value class MyValue13 { + int foo; + + public MyValue13() { + foo = rI; + } + } + + static class MyValue13Holder { + @Strict + @NullRestricted + MyValue13 v; + + public MyValue13Holder() { + v = new MyValue13(); + } + } + + static MyValue13 test13_precondition() { + return new MyValue13(); + } + + @Test + public void test13(Object holder) { + // Don't use MyValue13Holder in the signature, it might trigger class loading + GetUnresolvedInlineFieldWrongSignature.test13(holder); + } + + @Run(test = "test13") + public void test13_verifier(RunInfo info) { + if (info.isWarmUp() && !info.isC2CompilationEnabled()) { + test13(null); + } else { + // Make sure klass is resolved + for (int i = 0; i < 10; ++i) { + test13(new MyValue13Holder()); + } + } + } + + // Test case 15: + // Same as case 13, except MyValue15 is unloaded + @LooselyConsistentValue + static value class MyValue15 { + int foo; + + public MyValue15() { + foo = rI; + } + } + + static class MyValue15Holder { + @Strict + @NullRestricted + MyValue15 v; + + public MyValue15Holder() { + v = new MyValue15(); + } + } + + @Test + public void test15(Object holder) { + // Don't use MyValue15Holder in the signature, it might trigger class loading + GetUnresolvedInlineFieldWrongSignature.test15(holder); + } + + @Run(test = "test15") + public void test15_verifier(RunInfo info) { + if (info.isWarmUp() && !info.isC2CompilationEnabled()) { + test15(null); + } else { + // Make sure klass is resolved + for (int i = 0; i < 10; ++i) { + test15(new MyValue15Holder()); + } + } + } + + // Test case 16: + // aconst_init with type which is not a value class + static class MyValue16 { + int foo; + + public MyValue16() { + foo = rI; + } + } + + static MyValue16 test16_precondition() { + return new MyValue16(); + } + + @Test + public Object test16(boolean warmup) { + return GetUnresolvedInlineFieldWrongSignature.test16(warmup); + } + + @Run(test = "test16") + public void test16_verifier(RunInfo info) { + if (info.isWarmUp() && !info.isC2CompilationEnabled()) { + test16(true); + } else { + // Make sure klass is resolved + for (int i = 0; i < 10; ++i) { + test16(false); + } + } + } + + // Test case 17: + // Same as test16 but with unloaded type at aconst_init + static class MyValue17 { + int foo; + + public MyValue17() { + foo = rI; + } + } + + @Test + public Object test17(boolean warmup) { + return GetUnresolvedInlineFieldWrongSignature.test17(warmup); + } + + @Run(test = "test17") + public void test17_verifier(RunInfo info) { + if (info.isWarmUp() && !info.isC2CompilationEnabled()) { + test17(true); + } else { + // Make sure klass is resolved + for (int i = 0; i < 10; ++i) { + test17(false); + } + } + } + + // Test case 18: + // Same as test7 but with the holder being loaded + @LooselyConsistentValue + static value class MyValue18 { + int foo; + + MyValue18(int n) { + foo = n; + } + } + + static class MyValue18Holder { + @Strict + @NullRestricted + static MyValue18 v = new MyValue18(rI); + } + + @Test + public int test18(int n) { + if (n == 0) { + return 0; + } else { + return MyValue18Holder.v.foo + n; + } + } + + @Run(test = "test18") + public void test18_verifier(RunInfo info) { + // Make sure MyValue18Holder is loaded + MyValue18Holder holder = new MyValue18Holder(); + if (info.isWarmUp() && !info.isC2CompilationEnabled()) { + test18(0); + } else { + Asserts.assertEQ(test18(rI), 2*rI); + } + } + + // Test case 19: + // Same as test18 but uninitialized (null) static value class field + @LooselyConsistentValue + static value class MyValue19 { + int foo; + + MyValue19(int n) { + foo = n; + } + } + + static class MyValue19Holder { + @Strict + @NullRestricted + static MyValue19 v = new MyValue19(0); + } + + @Test + public int test19(int n) { + if (n == 0) { + return 0; + } else { + return MyValue19Holder.v.foo + n; + } + } + + @Run(test = "test19") + public void test19_verifier(RunInfo info) { + // Make sure MyValue19Holder is loaded + MyValue19Holder holder = new MyValue19Holder(); + if (info.isWarmUp() && !info.isC2CompilationEnabled()) { + test19(0); + } else { + Asserts.assertEQ(test19(rI), rI); + } + } + + // Test case 20: + // Value class with object field of unloaded type. + static class MyObject20 { + int x = 42; + } + + @LooselyConsistentValue + static value class MyValue20 { + MyObject20 obj; + + MyValue20() { + this.obj = null; + } + } + + @Test + public MyValue20 test20() { + return new MyValue20(); + } + + @Run(test = "test20") + public void test20_verifier() { + MyValue20 vt = test20(); + Asserts.assertEQ(vt.obj, null); + } + + @LooselyConsistentValue + static value class Test21ClassA { + static Test21ClassB b; + @Strict + @NullRestricted + static Test21ClassC c = new Test21ClassC(); + } + + @LooselyConsistentValue + static value class Test21ClassB { + static int x = Test21ClassA.c.x; + } + + @LooselyConsistentValue + static value class Test21ClassC { + int x = 42; + } + + // Test access to static value class field with unloaded type + @Test + public Object test21() { + return new Test21ClassA(); + } + + @Run(test = "test21") + public void test21_verifier() { + Object ret = test21(); + Asserts.assertEQ(Test21ClassA.b.x, 42); + Asserts.assertEQ(Test21ClassA.c.x, 42); + } + + static boolean test22FailInit = true; + + @LooselyConsistentValue + static value class Test22ClassA { + int x = 0; + @Strict + @NullRestricted + static Test22ClassB b = new Test22ClassB(); + } + + @LooselyConsistentValue + static value class Test22ClassB { + int x = 0; + static { + if (test22FailInit) { + throw new RuntimeException("Init failed"); + } + } + } + + // Test that load from static field of uninitialized value class throws an exception + @Test + public Object test22() { + return Test22ClassA.b; + } + + @Run(test = "test22") + public void test22_verifier() { + // Trigger initialization error in Test22ClassB + try { + Test22ClassB b = new Test22ClassB(); + throw new RuntimeException("Should have thrown error during initialization"); + } catch (ExceptionInInitializerError | NoClassDefFoundError e) { + // Expected + } + try { + test22(); + throw new RuntimeException("Should have thrown NoClassDefFoundError"); + } catch (NoClassDefFoundError e) { + // Expected + } + } + + static boolean test23FailInit = true; + + @LooselyConsistentValue + static value class Test23ClassA { + int x = 0; + @Strict + @NullRestricted + static Test23ClassB b = new Test23ClassB(); + } + + @LooselyConsistentValue + static value class Test23ClassB { + static { + if (test23FailInit) { + throw new RuntimeException("Init failed"); + } + } + } + + // Same as test22 but with empty ClassB + @Test + public Object test23() { + return Test23ClassA.b; + } + + @Run(test = "test23") + public void test23_verifier() { + // Trigger initialization error in Test23ClassB + try { + Test23ClassB b = new Test23ClassB(); + throw new RuntimeException("Should have thrown error during initialization"); + } catch (ExceptionInInitializerError | NoClassDefFoundError e) { + // Expected + } + try { + test23(); + throw new RuntimeException("Should have thrown NoClassDefFoundError"); + } catch (NoClassDefFoundError e) { + // Expected + } + } + + static boolean test24FailInit = true; + + @LooselyConsistentValue + static value class Test24ClassA { + @Strict + @NullRestricted + Test24ClassB b = new Test24ClassB(); + } + + @LooselyConsistentValue + static value class Test24ClassB { + int x = 0; + static { + if (test24FailInit) { + throw new RuntimeException("Init failed"); + } + } + } + + // Test that access to non-static field of uninitialized value class throws an exception + @Test + public Object test24() { + return (new Test24ClassA()).b.x; + } + + @Run(test = "test24") + public void test24_verifier() { + // Trigger initialization error in Test24ClassB + try { + Test24ClassB b = new Test24ClassB(); + throw new RuntimeException("Should have thrown error during initialization"); + } catch (ExceptionInInitializerError | NoClassDefFoundError e) { + // Expected + } + try { + test24(); + throw new RuntimeException("Should have thrown NoClassDefFoundError"); + } catch (NoClassDefFoundError e) { + // Expected + } + } + + static boolean test25FailInit = true; + + @LooselyConsistentValue + static value class Test25ClassA { + @Strict + @NullRestricted + Test25ClassB b = new Test25ClassB(); + } + + @LooselyConsistentValue + static value class Test25ClassB { + int x = 24; + static { + if (test25FailInit) { + throw new RuntimeException("Init failed"); + } + } + } + + // Same as test24 but with field access outside of test method + @Test + public Test25ClassB test25() { + return (new Test25ClassA()).b; + } + + @Run(test = "test25") + public void test25_verifier() { + // Trigger initialization error in Test25ClassB + try { + Test25ClassB b = new Test25ClassB(); + throw new RuntimeException("Should have thrown error during initialization"); + } catch (ExceptionInInitializerError | NoClassDefFoundError e) { + // Expected + } + try { + Test25ClassB res = test25(); + Asserts.assertEQ(res.x, 0); + throw new RuntimeException("Should have thrown NoClassDefFoundError"); + } catch (NoClassDefFoundError e) { + // Expected + } + } + + static boolean test26FailInit = true; + + @LooselyConsistentValue + static value class Test26ClassA { + @Strict + @NullRestricted + Test26ClassB b = new Test26ClassB(); + } + + @LooselyConsistentValue + static value class Test26ClassB { + static { + if (test26FailInit) { + throw new RuntimeException("Init failed"); + } + } + } + + // Same as test25 but with empty ClassB + @Test + public Object test26() { + return (new Test26ClassA()).b; + } + + @Run(test = "test26") + public void test26_verifier() { + // Trigger initialization error in Test26ClassB + try { + Test26ClassB b = new Test26ClassB(); + throw new RuntimeException("Should have thrown error during initialization"); + } catch (ExceptionInInitializerError | NoClassDefFoundError e) { + // Expected + } + try { + test26(); + throw new RuntimeException("Should have thrown NoClassDefFoundError"); + } catch (NoClassDefFoundError e) { + // Expected + } + } + + @LooselyConsistentValue + static value class MyValue27 { + int foo = 42; + } + + static class MyValue27Holder { + MyValue27 v; + } + + // Make sure MyValue27Holder is loaded but MyValue27 is not + Class test27Class = MyValue27Holder.class; + + // Test unloaded value class field load from loaded holder + @Test + public static Object test27() { + MyValue27Holder holder = new MyValue27Holder(); + return holder.v; + } + + @Run(test = "test27") + public void test27_verifier() { + Asserts.assertEQ(test27(), null); + } + + @LooselyConsistentValue + static value class MyValue28 { + @Strict + @NullRestricted + static MyValue28 field1 = new MyValue28(); + } + + // Test null store to null restricted field with unloaded holder + @Test + public static void test28() { + MyValue28.field1 = null; + } + + @Run(test = "test28") + @Warmup(0) // Make sure that MyValue28 is not loaded + public void test28_verifier() { + try { + test28(); + throw new RuntimeException("No exception thrown"); + } catch (NullPointerException e) { + // Expected + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUnloadedReturnTypes.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUnloadedReturnTypes.java new file mode 100644 index 00000000000..48b0449be17 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUnloadedReturnTypes.java @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @summary Test scalarization in returns with unloaded return types. + * @library /test/lib /compiler/whitebox / + * @enablePreview + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:CompileCommand=dontinline,*::test* + * TestUnloadedReturnTypes + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:+IgnoreUnrecognizedVMOptions -XX:-PreloadClasses + * -Xbatch -XX:CompileCommand=dontinline,*::test* + * TestUnloadedReturnTypes + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:+IgnoreUnrecognizedVMOptions -XX:-PreloadClasses -XX:+AlwaysIncrementalInline + * TestUnloadedReturnTypes + */ + +import java.lang.reflect.Method; + +import jdk.test.lib.Asserts; +import jdk.test.whitebox.WhiteBox; + +value class MyValue1 { + int x; + + public MyValue1(int x) { + this.x = x; + } +} + +class MyHolder1 { + static MyValue1 test1(boolean b) { + return b ? new MyValue1(42) : null; + } +} + +// Uses all registers available for scalarized return on x64 +value class MyValue2 { + int i1 = 42; + int i2 = 43; + int i3 = 44; + int i4 = 45; + int i5 = 46; + double d1 = 47; + double d2 = 48; + double d3 = 49; + double d4 = 50; + double d5 = 51; + double d6 = 52; + double d7 = 53; + double d8 = 54; +} + +class MyHolder2Super { + public MyValue2 test2Virtual(boolean loadIt) { + if (loadIt) { + return new MyValue2(); + } + return null; + } +} + +class MyHolder2 extends MyHolder2Super { + public MyValue2 test2(boolean loadIt) { + if (loadIt) { + return new MyValue2(); + } + return null; + } + + @Override + public MyValue2 test2Virtual(boolean loadIt) { + if (loadIt) { + return new MyValue2(); + } + return null; + } +} + +// Uses all registers available for scalarized return on AArch64 +value class MyValue3 { + int i1 = 42; + int i2 = 43; + int i3 = 44; + int i4 = 45; + int i5 = 46; + int i6 = 47; + int i7 = 48; + double d1 = 49; + double d2 = 50; + double d3 = 51; + double d4 = 52; + double d5 = 53; + double d6 = 54; + double d7 = 55; + double d8 = 56; +} + +class MyHolder3Super { + public MyValue3 test3Virtual(boolean loadIt) { + if (loadIt) { + return new MyValue3(); + } + return null; + } +} + +class MyHolder3 extends MyHolder3Super { + public MyValue3 test3(boolean loadIt) { + if (loadIt) { + return new MyValue3(); + } + return null; + } + + @Override + public MyValue3 test3Virtual(boolean loadIt) { + if (loadIt) { + return new MyValue3(); + } + return null; + } +} + +public class TestUnloadedReturnTypes { + public static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + + static Object res = null; + + public static void test1(boolean b) { + res = MyHolder1.test1(b); + } + + public static Object test2(MyHolder2 h, boolean loadIt) { + return h.test2(loadIt); + } + + public static Object test2Virtual(MyHolder2Super h, boolean loadIt) { + return h.test2Virtual(loadIt); + } + + public static Object test3(MyHolder3 h, boolean loadIt) { + return h.test3(loadIt); + } + + public static Object test3Virtual(MyHolder3Super h, boolean loadIt) { + return h.test3Virtual(loadIt); + } + + public static void main(String[] args) throws Exception { + // C1 compile caller method + Method m = TestUnloadedReturnTypes.class.getMethod("test1", boolean.class); + WHITE_BOX.enqueueMethodForCompilation(m, 3); + + MyHolder2 h2 = new MyHolder2(); + MyHolder2Super h2Super = new MyHolder2Super(); + MyHolder3 h3 = new MyHolder3(); + MyHolder3Super h3Super = new MyHolder3Super(); + + // Warmup + for (int i = 0; i < 100_000; ++i) { + MyHolder1.test1((i % 2) == 0); + Asserts.assertEquals(test2(h2, false), null); + Asserts.assertEquals(test2Virtual(h2, false), null); + Asserts.assertEquals(test2Virtual(h2Super, false), null); + Asserts.assertEquals(test3(h3, false), null); + Asserts.assertEquals(test3Virtual(h3, false), null); + Asserts.assertEquals(test3Virtual(h3Super, false), null); + } + + test1(true); + Asserts.assertEquals(((MyValue1)res).x, 42); + test1(false); + Asserts.assertEquals(res, null); + + // Deopt and re-compile callee at C2 so it returns scalarized, then deopt again + for (int i = 0; i < 100_000; ++i) { + Asserts.assertEquals(h2.test2(true), new MyValue2()); + Asserts.assertEquals(h2Super.test2Virtual(true), new MyValue2()); + Asserts.assertEquals(h3.test3(true), new MyValue3()); + Asserts.assertEquals(h3Super.test3Virtual(true), new MyValue3()); + } + Asserts.assertEquals(test2(h2, true), new MyValue2()); + Asserts.assertEquals(test2Virtual(h2, true), new MyValue2()); + Asserts.assertEquals(test2Virtual(h2Super, true), new MyValue2()); + Asserts.assertEquals(test3(h3, true), new MyValue3()); + Asserts.assertEquals(test3Virtual(h3, true), new MyValue3()); + Asserts.assertEquals(test3Virtual(h3Super, true), new MyValue3()); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUnresolvedInlineClass.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUnresolvedInlineClass.java new file mode 100644 index 00000000000..431bf20a5a4 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUnresolvedInlineClass.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8187679 + * @summary The VM should exit gracefully when unable to preload an inline type argument + * @enablePreview + * @requires vm.flagless + * @library /test/lib + * @compile SimpleInlineType.java TestUnresolvedInlineClass.java + * @run main/othervm TestUnresolvedInlineClass + */ + +import java.io.File; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class TestUnresolvedInlineClass { + final static String TEST_CLASSES = System.getProperty("test.classes") + File.separator; + + // Method with unresolved inline type argument + static void test1(SimpleInlineType vt) { + + } + + static public void main(String[] args) throws Exception { + if (args.length == 0) { + // Delete SimpleInlineType.class + File unresolved = new File(TEST_CLASSES, "SimpleInlineType.class"); + if (!unresolved.exists() || !unresolved.delete()) { + throw new RuntimeException("Could not delete: " + unresolved); + } + + // Run test in new VM instance + String[] arg = {"--enable-preview", "-XX:+InlineTypePassFieldsAsArgs", "TestUnresolvedInlineClass", "run"}; + OutputAnalyzer oa = ProcessTools.executeTestJava(arg); + + // Verify that a warning is printed + String output = oa.getOutput(); + oa.shouldContain("Preloading of class SimpleInlineType during linking of class TestUnresolvedInlineClass (cause: LoadableDescriptors attribute) failed"); + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestValueClasses.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestValueClasses.java new file mode 100644 index 00000000000..9a9e6c3cd60 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestValueClasses.java @@ -0,0 +1,963 @@ +/* + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +import compiler.lib.ir_framework.*; +import jdk.test.lib.Asserts; + +import java.lang.reflect.Method; + +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.ALLOC_OF_MYVALUE_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypeIRNode.STORE_OF_ANY_KLASS; +import static compiler.valhalla.inlinetypes.InlineTypes.*; + +import static compiler.lib.ir_framework.IRNode.ALLOC; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +/* + * @test + * @key randomness + * @summary Test correct handling of value classes. + * @library /test/lib /test/jdk/java/lang/invoke/common / + * @requires (os.simpleArch == "x64" | os.simpleArch == "aarch64") + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm/timeout=300 compiler.valhalla.inlinetypes.TestValueClasses + */ + +@ForceCompileClassInitializer +public class TestValueClasses { + + public static void main(String[] args) { + Scenario[] scenarios = InlineTypes.DEFAULT_SCENARIOS; + // Don't generate bytecodes but call through runtime for reflective calls + scenarios[0].addFlags("-Dsun.reflect.inflationThreshold=10000"); + scenarios[1].addFlags("-Dsun.reflect.inflationThreshold=10000"); + scenarios[3].addFlags("-XX:-MonomorphicArrayCheck", "-XX:+UseArrayFlattening"); + scenarios[4].addFlags("-XX:-UseTLAB", "-XX:-MonomorphicArrayCheck"); + + InlineTypes.getFramework() + .addScenarios(scenarios) + .addHelperClasses(MyValueClass1.class, + MyValueClass2.class, + MyValueClass2Inline.class) + .start(); + } + + static { + // Make sure RuntimeException is loaded to prevent uncommon traps in IR verified tests + RuntimeException tmp = new RuntimeException("42"); + } + + private static final MyValueClass1 testValue1 = MyValueClass1.createWithFieldsInline(rI, rL); + + MyValueClass1 nullValField = null; + MyValueClass1 testField1; + MyValueClass1 testField2; + MyValueClass1 testField3; + MyValueClass1 testField4; + static MyValueClass1 testField5; + static MyValueClass1 testField6; + static MyValueClass1 testField7; + static MyValueClass1 testField8; + + // Test field loads + @Test + public long test1(boolean b) { + MyValueClass1 val1 = b ? testField3 : MyValueClass1.createWithFieldsInline(rI, rL); + MyValueClass1 val2 = b ? testField7 : MyValueClass1.createWithFieldsInline(rI, rL); + long res = 0; + res += testField1.hash(); + res += ((Object)testField2 == null) ? 42 : testField2.hash(); + res += val1.hash(); + res += testField4.hash(); + + res += testField5.hash(); + res += ((Object)testField6 == null) ? 42 : testField6.hash(); + res += val2.hash(); + res += testField8.hash(); + return res; + } + + @Run(test = "test1") + public void test1_verifier() { + testField1 = testValue1; + testField2 = nullValField; + testField3 = testValue1; + testField4 = testValue1; + + testField5 = testValue1; + testField6 = nullValField; + testField7 = testValue1; + testField8 = testValue1; + long res = test1(true); + Asserts.assertEquals(res, 2*42 + 6*testValue1.hash()); + + testField2 = testValue1; + testField6 = testValue1; + res = test1(false); + Asserts.assertEquals(res, 8*testValue1.hash()); + } + + // Test field stores + @Test + public MyValueClass1 test2(MyValueClass1 val1) { + MyValueClass1 ret = MyValueClass1.createWithFieldsInline(rI, rL); + MyValueClass1 val2 = MyValueClass1.setV4(testValue1, null); + testField1 = testField4; + testField2 = val1; + testField3 = val2; + + testField5 = ret; + testField6 = val1; + testField7 = val2; + testField8 = testField4; + return ret; + } + + @Run(test = "test2") + public void test2_verifier() { + testField4 = testValue1; + MyValueClass1 ret = test2(null); + MyValueClass1 val2 = MyValueClass1.setV4(testValue1, null); + Asserts.assertEquals(testField1, testValue1); + Asserts.assertEquals(testField2, null); + Asserts.assertEquals(testField3, val2); + + Asserts.assertEquals(testField5, ret); + Asserts.assertEquals(testField6, null); + Asserts.assertEquals(testField7, val2); + Asserts.assertEquals(testField8, testField4); + + testField4 = null; + test2(null); + Asserts.assertEquals(testField1, testField4); + Asserts.assertEquals(testField8, testField4); + } + + // Non-value class Wrapper + static class Test3Wrapper { + MyValueClass1 val; + + public Test3Wrapper(MyValueClass1 val) { + this.val = val; + } + } + + // Test scalarization in safepoint debug info and re-allocation on deopt + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public long test3(boolean deopt, boolean b1, boolean b2, Method m) { + MyValueClass1 ret = MyValueClass1.createWithFieldsInline(rI, rL); + if (b1) { + ret = null; + } + if (b2) { + ret = MyValueClass1.setV4(ret, null); + } + Test3Wrapper wrapper = new Test3Wrapper(ret); + if (deopt) { + // Uncommon trap + TestFramework.deoptimize(m); + } + long res = ((Object)ret != null && (Object)ret.v4 != null) ? ret.hash() : 42; + res += ((Object)wrapper.val != null && (Object)wrapper.val.v4 != null) ? wrapper.val.hash() : 0; + return res; + } + + @Run(test = "test3") + public void test3_verifier(RunInfo info) { + Asserts.assertEquals(test3(false, false, false, info.getTest()), 2*testValue1.hash()); + Asserts.assertEquals(test3(false, true, false, info.getTest()), 42L); + if (!info.isWarmUp()) { + switch (rI % 4) { + case 0: + Asserts.assertEquals(test3(true, false, false, info.getTest()), 2*testValue1.hash()); + break; + case 1: + Asserts.assertEquals(test3(true, true, false, info.getTest()), 42L); + break; + case 2: + Asserts.assertEquals(test3(true, false, true, info.getTest()), 42L); + break; + case 3: + try { + Asserts.assertEquals(test3(true, true, true, info.getTest()), 42L); + throw new RuntimeException("NullPointerException expected"); + } catch (NullPointerException e) { + // Expected + } + break; + } + } + } + + // Test scalarization in safepoint debug info and re-allocation on deopt + @Test + @IR(failOn = {ALLOC_OF_MYVALUE_KLASS, STORE_OF_ANY_KLASS}) + public boolean test4(boolean deopt, boolean b, Method m) { + MyValueClass1 val = b ? null : MyValueClass1.createWithFieldsInline(rI, rL); + Test3Wrapper wrapper = new Test3Wrapper(val); + if (deopt) { + // Uncommon trap + TestFramework.deoptimize(m); + } + return (Object)wrapper.val == null; + } + + @Run(test = "test4") + public void test4_verifier(RunInfo info) { + Asserts.assertTrue(test4(false, true, info.getTest())); + Asserts.assertFalse(test4(false, false, info.getTest())); + if (!info.isWarmUp()) { + switch (rI % 2) { + case 0: + Asserts.assertTrue(test4(true, true, info.getTest())); + break; + case 1: + Asserts.assertFalse(test4(false, false, info.getTest())); + break; + } + } + } + + static value class SmallNullable2 { + float f1; + double f2; + + @ForceInline + public SmallNullable2() { + f1 = (float)rL; + f2 = (double)rL; + } + } + + static value class SmallNullable1 { + char c; + byte b; + short s; + int i; + SmallNullable2 vt; + + @ForceInline + public SmallNullable1(boolean useNull) { + c = (char)rL; + b = (byte)rL; + s = (short)rL; + i = (int)rL; + vt = useNull ? null : new SmallNullable2(); + } + } + + @DontCompile + public SmallNullable1 test5_interpreted(boolean b1, boolean b2) { + return b1 ? null : new SmallNullable1(b2); + } + + @DontInline + public SmallNullable1 test5_compiled(boolean b1, boolean b2) { + return b1 ? null : new SmallNullable1(b2); + } + + SmallNullable1 test5_field1; + SmallNullable1 test5_field2; + + // Test scalarization in returns + @Test + public SmallNullable1 test5(boolean b1, boolean b2) { + SmallNullable1 ret = test5_interpreted(b1, b2); + if (b1 != ((Object)ret == null)) { + throw new RuntimeException("test5 failed"); + } + test5_field1 = ret; + ret = test5_compiled(b1, b2); + if (b1 != ((Object)ret == null)) { + throw new RuntimeException("test5 failed"); + } + test5_field2 = ret; + return ret; + } + + @Run(test = "test5") + public void test5_verifier() { + SmallNullable1 vt = new SmallNullable1(false); + Asserts.assertEquals(test5(true, false), null); + Asserts.assertEquals(test5_field1, null); + Asserts.assertEquals(test5_field2, null); + Asserts.assertEquals(test5(false, false), vt); + Asserts.assertEquals(test5_field1, vt); + Asserts.assertEquals(test5_field2, vt); + vt = new SmallNullable1(true); + Asserts.assertEquals(test5(true, true), null); + Asserts.assertEquals(test5_field1, null); + Asserts.assertEquals(test5_field2, null); + Asserts.assertEquals(test5(false, true), vt); + Asserts.assertEquals(test5_field1, vt); + Asserts.assertEquals(test5_field2, vt); + } + + static value class Empty2 { + + } + + static value class Empty1 { + Empty2 empty2 = new Empty2(); + } + + static value class Container { + int x = 0; + Empty1 empty1; + Empty2 empty2 = new Empty2(); + + @ForceInline + public Container(Empty1 val) { + empty1 = val; + } + } + + @DontInline + public static Empty1 test6_helper1(Empty1 vt) { + return vt; + } + + @DontInline + public static Empty2 test6_helper2(Empty2 vt) { + return vt; + } + + @DontInline + public static Container test6_helper3(Container vt) { + return vt; + } + + // Test scalarization in calls and returns with empty value classes + @Test + public Empty1 test6(Empty1 vt) { + Empty1 empty1 = test6_helper1(vt); + test6_helper2((empty1 != null) ? empty1.empty2 : null); + Container c = test6_helper3(new Container(empty1)); + return c.empty1; + } + + @Run(test = "test6") + @Warmup(10000) // Warmup to make sure helper methods are compiled as well + public void test6_verifier() { + Asserts.assertEQ(test6(new Empty1()), new Empty1()); + Asserts.assertEQ(test6(null), null); + } + + @DontCompile + public void test7_helper2(boolean doit) { + if (doit) { + // uncommon trap + try { + TestFramework.deoptimize(getClass().getDeclaredMethod("test7", boolean.class, boolean.class, boolean.class)); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + } + + // Test deoptimization at call return with value object returned in registers + @DontInline + public SmallNullable1 test7_helper1(boolean deopt, boolean b1, boolean b2) { + test7_helper2(deopt); + return b1 ? null : new SmallNullable1(b2); + } + + @Test + public SmallNullable1 test7(boolean flag, boolean b1, boolean b2) { + return test7_helper1(flag, b1, b2); + } + + @Run(test = "test7") + @Warmup(10000) + public void test7_verifier(RunInfo info) { + boolean b1 = ((rI % 3) == 0); + boolean b2 = ((rI % 3) == 1); + SmallNullable1 result = test7(!info.isWarmUp(), b1, b2); + SmallNullable1 vt = new SmallNullable1(b2); + Asserts.assertEQ(result, b1 ? null : vt); + } + + // Test calling a method returning a value class as fields via reflection + @Test + public SmallNullable1 test8(boolean b1, boolean b2) { + return b1 ? null : new SmallNullable1(b2); + } + + @Run(test = "test8") + public void test8_verifier() throws Exception { + Method m = getClass().getDeclaredMethod("test8", boolean.class, boolean.class); + Asserts.assertEQ(m.invoke(this, false, true), new SmallNullable1(true)); + Asserts.assertEQ(m.invoke(this, false, false), new SmallNullable1(false)); + Asserts.assertEQ(m.invoke(this, true, false), null); + } + + // Test value classes as arg/return + @Test + public SmallNullable1 test9(MyValueClass1 vt1, MyValueClass1 vt2, boolean b1, boolean b2) { + Asserts.assertEQ(vt1, testValue1); + if (b1) { + Asserts.assertEQ(vt2, null); + } else { + Asserts.assertEQ(vt2, testValue1); + } + return b1 ? null : new SmallNullable1(b2); + } + + @Run(test = "test9") + public void test9_verifier() { + Asserts.assertEQ(test9(testValue1, testValue1, false, true), new SmallNullable1(true)); + Asserts.assertEQ(test9(testValue1, testValue1, false, false), new SmallNullable1(false)); + Asserts.assertEQ(test9(testValue1, null, true, false), null); + } + + // Class.cast + @Test + public Object test10(Class c, MyValueClass1 vt) { + return c.cast(vt); + } + + @Run(test = "test10") + public void test10_verifier() { + Asserts.assertEQ(test10(MyValueClass1.class, testValue1), testValue1); + Asserts.assertEQ(test10(MyValueClass1.class, null), null); + Asserts.assertEQ(test10(Integer.class, null), null); + try { + test10(MyValueClass2.class, testValue1); + throw new RuntimeException("ClassCastException expected"); + } catch (ClassCastException e) { + // Expected + } + } + + // Test acmp + @Test + public boolean test12(MyValueClass1 vt1, MyValueClass1 vt2) { + return vt1 == vt2; + } + + @Run(test = "test12") + public void test12_verifier() { + Asserts.assertTrue(test12(testValue1, testValue1)); + Asserts.assertTrue(test12(null, null)); + Asserts.assertFalse(test12(testValue1, null)); + Asserts.assertFalse(test12(null, testValue1)); + Asserts.assertFalse(test12(testValue1, MyValueClass1.createDefaultInline())); + } + + // Same as test13 but with Object argument + @Test + public boolean test13(Object obj, MyValueClass1 vt2) { + return obj == vt2; + } + + @Run(test = "test13") + public void test13_verifier() { + Asserts.assertTrue(test13(testValue1, testValue1)); + Asserts.assertTrue(test13(null, null)); + Asserts.assertFalse(test13(testValue1, null)); + Asserts.assertFalse(test13(null, testValue1)); + Asserts.assertFalse(test13(testValue1, MyValueClass1.createDefaultInline())); + } + + static MyValueClass1 test14_field1; + static MyValueClass1 test14_field2; + + // Test buffer checks emitted by acmp followed by buffering + @Test + public boolean test14(MyValueClass1 vt1, MyValueClass1 vt2) { + // Trigger buffer checks + if (vt1 != vt2) { + throw new RuntimeException("Should be equal"); + } + if (vt2 != vt1) { + throw new RuntimeException("Should be equal"); + } + // Trigger buffering + test14_field1 = vt1; + test14_field2 = vt2; + return vt1 == null; + } + + @Run(test = "test14") + public void test14_verifier() { + Asserts.assertFalse(test14(testValue1, testValue1)); + Asserts.assertTrue(test14(null, null)); + } + + @DontInline + public MyValueClass1 test15_helper1(MyValueClass1 vt) { + return vt; + } + + @ForceInline + public MyValueClass1 test15_helper2(MyValueClass1 vt) { + return test15_helper1(vt); + } + + @ForceInline + public MyValueClass1 test15_helper3(Object vt) { + return test15_helper2((MyValueClass1)vt); + } + + // Test that calling convention optimization prevents buffering of arguments + @Test + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "true"}, + counts = {ALLOC, " <= 7"}) // 6 MyValueClass2/MyValueClass2Inline allocations + 1 Integer allocation (if not the all-zero value) + @IR(applyIf = {"InlineTypePassFieldsAsArgs", "false"}, + counts = {ALLOC, " <= 8"}) // 1 MyValueClass1 allocation + 6 MyValueClass2/MyValueClass2Inline allocations + 1 Integer allocation (if not the all-zero value) + public MyValueClass1 test15(MyValueClass1 vt) { + MyValueClass1 res = test15_helper1(vt); + vt = MyValueClass1.createWithFieldsInline(rI, rL); + test15_helper1(vt); + test15_helper2(vt); + test15_helper3(vt); + vt.dontInline(vt); + return res; + } + + @Run(test = "test15") + public void test15_verifier() { + Asserts.assertEQ(test15(testValue1), testValue1); + Asserts.assertEQ(test15(null), null); + } + + @DontInline + public MyValueClass2 test16_helper1(boolean b) { + return b ? null : MyValueClass2.createWithFieldsInline(rI, rD); + } + + @ForceInline + public MyValueClass2 test16_helper2() { + return null; + } + + @ForceInline + public MyValueClass2 test16_helper3(boolean b) { + return b ? null : MyValueClass2.createWithFieldsInline(rI, rD); + } + + // Test that calling convention optimization prevents buffering of return values + @Test + @IR(applyIf = {"InlineTypeReturnedAsFields", "true"}, + counts = {ALLOC, " <= 1"}) // 1 MyValueClass2Inline allocation (if not the all-zero value) + @IR(applyIf = {"InlineTypeReturnedAsFields", "false"}, + counts = {ALLOC, " <= 2"}) // 1 MyValueClass2 + 1 MyValueClass2Inline allocation (if not the all-zero value) + public MyValueClass2 test16(int c, boolean b) { + MyValueClass2 res = null; + if (c == 1) { + res = test16_helper1(b); + } else if (c == 2) { + res = test16_helper2(); + } else if (c == 3) { + res = test16_helper3(b); + } + return res; + } + + @Run(test = "test16") + public void test16_verifier() { + Asserts.assertEQ(test16(0, false), null); + Asserts.assertEQ(test16(1, false).hash(), MyValueClass2.createWithFieldsInline(rI, rD).hash()); + Asserts.assertEQ(test16(1, true), null); + Asserts.assertEQ(test16(2, false), null); + Asserts.assertEQ(test16(3, false).hash(), MyValueClass2.createWithFieldsInline(rI, rD).hash()); + Asserts.assertEQ(test16(3, true), null); + } + + @LooselyConsistentValue + static value class MyPrimitive17 { + MyValueClass1 nonFlattened; + + public MyPrimitive17(MyValueClass1 val) { + this.nonFlattened = val; + } + } + + static value class MyValue17 { + @Strict + @NullRestricted + MyPrimitive17 flattened; + + public MyValue17(boolean b) { + this.flattened = new MyPrimitive17(b ? null : testValue1); + } + } + + @DontCompile + public MyValue17 test17_interpreted(boolean b1, boolean b2) { + return b1 ? null : new MyValue17(b2); + } + + @DontInline + public MyValue17 test17_compiled(boolean b1, boolean b2) { + return b1 ? null : new MyValue17(b2); + } + + MyValue17 test17_field1; + MyValue17 test17_field2; + + // Test handling of null when mixing nullable and null-restricted fields + @Test + public MyValue17 test17(boolean b1, boolean b2) { + MyValue17 ret = test17_interpreted(b1, b2); + if (b1 != ((Object)ret == null)) { + throw new RuntimeException("test17 failed"); + } + test17_field1 = ret; + ret = test17_compiled(b1, b2); + if (b1 != ((Object)ret == null)) { + throw new RuntimeException("test17 failed"); + } + test17_field2 = ret; + return ret; + } + + @Run(test = "test17") + public void test17_verifier() { + MyValue17 vt = new MyValue17(false); + Asserts.assertEquals(test17(true, false), null); + Asserts.assertEquals(test17_field1, null); + Asserts.assertEquals(test17_field2, null); + Asserts.assertEquals(test17(false, false), vt); + Asserts.assertEquals(test17_field1, vt); + Asserts.assertEquals(test17_field2, vt); + vt = new MyValue17(true); + Asserts.assertEquals(test17(true, true), null); + Asserts.assertEquals(test17_field1, null); + Asserts.assertEquals(test17_field2, null); + Asserts.assertEquals(test17(false, true), vt); + Asserts.assertEquals(test17_field1, vt); + Asserts.assertEquals(test17_field2, vt); + } + + // Uses all registers available for returning values on x86_64 + static value class UseAllRegs { + long l1; + long l2; + long l3; + long l4; + long l5; + long l6; + double d1; + double d2; + double d3; + double d4; + double d5; + double d6; + double d7; + double d8; + + @ForceInline + public UseAllRegs(long l1, long l2, long l3, long l4, long l5, long l6, + double d1, double d2, double d3, double d4, double d5, double d6, double d7, double d8) { + this.l1 = l1; + this.l2 = l2; + this.l3 = l3; + this.l4 = l4; + this.l5 = l5; + this.l6 = l6; + this.d1 = d1; + this.d2 = d2; + this.d3 = d3; + this.d4 = d4; + this.d5 = d5; + this.d6 = d6; + this.d7 = d7; + this.d8 = d8; + } + } + + @DontInline + public UseAllRegs test18_helper1(UseAllRegs val, long a, long b, long c, long d, long e, long f, long g, long h, long i, long j) { + Asserts.assertEquals(a & b & c & d & e & f & g & h & i & j, 0L); + return val; + } + + @DontCompile + public UseAllRegs test18_helper2(UseAllRegs val, long a, long b, long c, long d, long e, long f, long g, long h, long i, long j) { + Asserts.assertEquals(a & b & c & d & e & f & g & h & i & j, 0L); + return val; + } + + static boolean test18_b; + + // Methods with no arguments (no stack slots reserved for incoming args) + @DontInline + public static UseAllRegs test18_helper3() { + return test18_b ? null : new UseAllRegs(rL + 1, rL + 2, rL + 3, rL + 4, rL + 5, rL + 6, rL + 7, rL + 8, rL + 9, rL + 10, rL + 11, rL + 12, rL + 13, rL + 14); + } + + @DontCompile + public static UseAllRegs test18_helper4() { + return test18_b ? null : test18_helper3(); + } + + // Test proper register allocation of isInit projection of a call in C2 + @Test + public UseAllRegs test18(boolean b, long val1, long l1, long val2, long l2, long val3, long l3, long val4, long l4, long val5, long l5, long val6, long l6, + long val7, double d1, long val8, double d2, long val9, double d3, long val10, double d4, long val11, double d5, long val12, double d6, long val13, double d7, long val14, double d8, long val15) { + Asserts.assertEquals(val1, rL); + Asserts.assertEquals(val2, rL); + Asserts.assertEquals(val3, rL); + Asserts.assertEquals(val4, rL); + Asserts.assertEquals(val5, rL); + Asserts.assertEquals(val6, rL); + Asserts.assertEquals(val7, rL); + Asserts.assertEquals(val8, rL); + Asserts.assertEquals(val9, rL); + Asserts.assertEquals(val10, rL); + Asserts.assertEquals(val11, rL); + Asserts.assertEquals(val12, rL); + Asserts.assertEquals(val13, rL); + Asserts.assertEquals(val14, rL); + Asserts.assertEquals(val15, rL); + UseAllRegs val = b ? null : new UseAllRegs(l1, l2, l3, l4, l5, l6, d1, d2, d3, d4, d5, d6, d7, d8); + val = test18_helper1(val, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + val = test18_helper2(val, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + Asserts.assertEquals(val1, rL); + Asserts.assertEquals(val2, rL); + Asserts.assertEquals(val3, rL); + Asserts.assertEquals(val4, rL); + Asserts.assertEquals(val5, rL); + Asserts.assertEquals(val6, rL); + Asserts.assertEquals(val7, rL); + Asserts.assertEquals(val8, rL); + Asserts.assertEquals(val9, rL); + Asserts.assertEquals(val10, rL); + Asserts.assertEquals(val11, rL); + Asserts.assertEquals(val12, rL); + Asserts.assertEquals(val13, rL); + Asserts.assertEquals(val14, rL); + Asserts.assertEquals(val15, rL); + Asserts.assertEquals(test18_helper3(), val); + Asserts.assertEquals(test18_helper4(), val); + return val; + } + + @Run(test = "test18") + public void test18_verifier() { + UseAllRegs val = new UseAllRegs(rL + 1, rL + 2, rL + 3, rL + 4, rL + 5, rL + 6, rL + 7, rL + 8, rL + 9, rL + 10, rL + 11, rL + 12, rL + 13, rL + 14); + test18_b = false; + Asserts.assertEquals(test18(false, rL, rL + 1, rL, rL + 2, rL, rL + 3, rL, rL + 4, rL, rL + 5, rL, rL + 6, + rL, rL + 7, rL, rL + 8, rL, rL + 9, rL, rL + 10, rL, rL + 11, rL, rL + 12, rL, rL + 13, rL, rL + 14, rL), val); + test18_b = true; + Asserts.assertEquals(test18(true, rL, rL + 1, rL, rL + 2, rL, rL + 3, rL, rL + 4, rL, rL + 5, rL, rL + 6, + rL, rL + 7, rL, rL + 8, rL, rL + 9, rL, rL + 10, rL, rL + 11, rL, rL + 12, rL, rL + 13, rL, rL + 14, rL), null); + } + + @DontInline + static public UseAllRegs test19_helper() { + return new UseAllRegs(rL + 1, rL + 2, rL + 3, rL + 4, rL + 5, rL + 6, rL + 7, rL + 8, rL + 9, rL + 10, rL + 11, rL + 12, rL + 13, rL + 14); + } + + // Test proper register allocation of isInit projection of a call in C2 + @Test + static public void test19(long a, long b, long c, long d, long e, long f) { + if (test19_helper() == null) { + throw new RuntimeException("test19 failed: Unexpected null"); + } + if ((a & b & c & d & e & f) != 0) { + throw new RuntimeException("test19 failed: Unexpected argument values"); + } + } + + @Run(test = "test19") + public void test19_verifier() { + test19(0, 0, 0, 0, 0, 0); + } + + // Uses almost all registers available for returning values on x86_64 + static value class UseAlmostAllRegs { + long l1; + long l2; + long l3; + long l4; + long l5; + double d1; + double d2; + double d3; + double d4; + double d5; + double d6; + double d7; + + @ForceInline + public UseAlmostAllRegs(long l1, long l2, long l3, long l4, long l5, + double d1, double d2, double d3, double d4, double d5, double d6, double d7) { + this.l1 = l1; + this.l2 = l2; + this.l3 = l3; + this.l4 = l4; + this.l5 = l5; + this.d1 = d1; + this.d2 = d2; + this.d3 = d3; + this.d4 = d4; + this.d5 = d5; + this.d6 = d6; + this.d7 = d7; + } + } + + @DontInline + public UseAlmostAllRegs test20_helper1(UseAlmostAllRegs val, long a, long b, long c, long d, long e, long f, long g, long h, long i, long j) { + Asserts.assertEquals(a & b & c & d & e & f & g & h & i & j, 0L); + return val; + } + + @DontCompile + public UseAlmostAllRegs test20_helper2(UseAlmostAllRegs val, long a, long b, long c, long d, long e, long f, long g, long h, long i, long j) { + Asserts.assertEquals(a & b & c & d & e & f & g & h & i & j, 0L); + return val; + } + + static boolean test20_b; + + // Methods with no arguments (no stack slots reserved for incoming args) + @DontInline + public static UseAlmostAllRegs test20_helper3() { + return test20_b ? null : new UseAlmostAllRegs(rL + 1, rL + 2, rL + 3, rL + 4, rL + 5, rL + 6, rL + 7, rL + 8, rL + 9, rL + 10, rL + 11, rL + 12); + } + + @DontCompile + public static UseAlmostAllRegs test20_helper4() { + return test20_b ? null : test20_helper3(); + } + + // Test proper register allocation of isInit projection of a call in C2 + @Test + public UseAlmostAllRegs test20(boolean b, long val1, long l1, long val2, long l2, long val3, long l3, long val4, long l4, long val5, long l5, long val6, + long val7, double d1, long val8, double d2, long val9, double d3, long val10, double d4, long val11, double d5, long val12, double d6, long val13, double d7, long val14, long val15) { + Asserts.assertEquals(val1, rL); + Asserts.assertEquals(val2, rL); + Asserts.assertEquals(val3, rL); + Asserts.assertEquals(val4, rL); + Asserts.assertEquals(val5, rL); + Asserts.assertEquals(val6, rL); + Asserts.assertEquals(val7, rL); + Asserts.assertEquals(val8, rL); + Asserts.assertEquals(val9, rL); + Asserts.assertEquals(val10, rL); + Asserts.assertEquals(val11, rL); + Asserts.assertEquals(val12, rL); + Asserts.assertEquals(val13, rL); + Asserts.assertEquals(val14, rL); + Asserts.assertEquals(val15, rL); + UseAlmostAllRegs val = b ? null : new UseAlmostAllRegs(l1, l2, l3, l4, l5, d1, d2, d3, d4, d5, d6, d7); + val = test20_helper1(val, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + val = test20_helper2(val, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + Asserts.assertEquals(val1, rL); + Asserts.assertEquals(val2, rL); + Asserts.assertEquals(val3, rL); + Asserts.assertEquals(val4, rL); + Asserts.assertEquals(val5, rL); + Asserts.assertEquals(val6, rL); + Asserts.assertEquals(val7, rL); + Asserts.assertEquals(val8, rL); + Asserts.assertEquals(val9, rL); + Asserts.assertEquals(val10, rL); + Asserts.assertEquals(val11, rL); + Asserts.assertEquals(val12, rL); + Asserts.assertEquals(val13, rL); + Asserts.assertEquals(val14, rL); + Asserts.assertEquals(val15, rL); + Asserts.assertEquals(test20_helper3(), val); + Asserts.assertEquals(test20_helper4(), val); + return val; + } + + @Run(test = "test20") + public void test20_verifier() { + UseAlmostAllRegs val = new UseAlmostAllRegs(rL + 1, rL + 2, rL + 3, rL + 4, rL + 5, rL + 6, rL + 7, rL + 8, rL + 9, rL + 10, rL + 11, rL + 12); + test20_b = false; + Asserts.assertEquals(test20(false, rL, rL + 1, rL, rL + 2, rL, rL + 3, rL, rL + 4, rL, rL + 5, rL, + rL, rL + 6, rL, rL + 7, rL, rL + 8, rL, rL + 9, rL, rL + 10, rL, rL + 11, rL, rL + 12, rL, rL), val); + test20_b = true; + Asserts.assertEquals(test20(true, rL, rL + 1, rL, rL + 2, rL, rL + 3, rL, rL + 4, rL, rL + 5, rL, + rL, rL + 6, rL, rL + 7, rL, rL + 8, rL, rL + 9, rL, rL + 10, rL, rL + 11, rL, rL + 12, rL, rL), null); + } + + @DontInline + static public UseAlmostAllRegs test21_helper() { + return new UseAlmostAllRegs(rL + 1, rL + 2, rL + 3, rL + 4, rL + 5, rL + 6, rL + 7, rL + 8, rL + 9, rL + 10, rL + 11, rL + 12); + } + + // Test proper register allocation of isInit projection of a call in C2 + @Test + static public void test21(long a, long b, long c, long d, long e, long f) { + if (test21_helper() == null) { + throw new RuntimeException("test21 failed: Unexpected null"); + } + if ((a & b & c & d & e & f) != 0) { + throw new RuntimeException("test21 failed: Unexpected argument values"); + } + } + + @Run(test = "test21") + public void test21_verifier() { + test21(0, 0, 0, 0, 0, 0); + } + + static value class ManyOopsValue { + Integer i1 = 1; + Integer i2 = 2; + Integer i3 = 3; + Integer i4 = 4; + Integer i5 = 5; + Integer i6 = 6; + Integer i7 = 7; + Integer i8 = 8; + Integer i9 = 9; + Integer i10 = 10; + Integer i11 = 11; + Integer i12 = 12; + Integer i13 = 13; + Integer i14 = 14; + Integer i15 = 15; + + @DontInline + public int sum() { + return i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8 + i9 + i10 + i11 + i12 + i13 + i14 + i15; + } + } + + // Verify that C2 scratch buffer size is large enough to hold many GC barriers used by the entry points + @Test + static public int test22(ManyOopsValue val) { + return val.sum(); + } + + @Run(test = "test22") + @Warmup(10_000) + public void test22_verifier() { + Asserts.assertEquals(test22(new ManyOopsValue()), 120); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestValueConstruction.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestValueConstruction.java new file mode 100644 index 00000000000..c5211c5ea6d --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestValueConstruction.java @@ -0,0 +1,1832 @@ +/* + * 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. + */ + +package compiler.valhalla.inlinetypes; + +import java.lang.classfile.Label; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.Random; + +import jdk.test.lib.Asserts; +import jdk.test.lib.Utils; +import jdk.test.whitebox.WhiteBox; +import test.java.lang.invoke.lib.InstructionHelper; + +/** + * @test id=Xbatch + * @summary Test construction of value objects. + * @key randomness + * @library /testlibrary /test/lib /compiler/whitebox /test/jdk/java/lang/invoke/common / + * @enablePreview + * @build jdk.test.whitebox.WhiteBox test.java.lang.invoke.lib.InstructionHelper + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch + * -XX:CompileCommand=inline,TestValueConstruction::checkDeopt + * -XX:CompileCommand=MemLimit,*.*,2G~crash + * compiler.valhalla.inlinetypes.TestValueConstruction + */ + +/* + * @test id=DeoptimizeALot + * @key randomness + * @library /testlibrary /test/lib /compiler/whitebox /test/jdk/java/lang/invoke/common / + * @enablePreview + * @build jdk.test.whitebox.WhiteBox test.java.lang.invoke.lib.InstructionHelper + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch -XX:+IgnoreUnrecognizedVMOptions -XX:+DeoptimizeALot + * -XX:CompileCommand=inline,TestValueConstruction::checkDeopt + * -XX:CompileCommand=MemLimit,*.*,2G~crash + * compiler.valhalla.inlinetypes.TestValueConstruction + */ + +/* + * @test id=CompileonlyTest + * @key randomness + * @library /testlibrary /test/lib /compiler/whitebox /test/jdk/java/lang/invoke/common / + * @enablePreview + * @build jdk.test.whitebox.WhiteBox test.java.lang.invoke.lib.InstructionHelper + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:CompileCommand=compileonly,*TestValueConstruction::test* -Xbatch + * -XX:CompileCommand=inline,TestValueConstruction::checkDeopt + * compiler.valhalla.inlinetypes.TestValueConstruction + */ + +/** + * @test id=DontInlineHelper + * @summary Test construction of value objects. + * @key randomness + * @library /testlibrary /test/lib /compiler/whitebox /test/jdk/java/lang/invoke/common / + * @enablePreview + * @build jdk.test.whitebox.WhiteBox test.java.lang.invoke.lib.InstructionHelper + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch + * -XX:CompileCommand=dontinline,compiler*::helper* + * -XX:CompileCommand=inline,TestValueConstruction::checkDeopt + * compiler.valhalla.inlinetypes.TestValueConstruction + */ + +/* + * @test id=DontInlineMyValueInit + * @key randomness + * @library /testlibrary /test/lib /compiler/whitebox /test/jdk/java/lang/invoke/common / + * @enablePreview + * @build jdk.test.whitebox.WhiteBox test.java.lang.invoke.lib.InstructionHelper + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:CompileCommand=dontinline,*MyValue*:: -Xbatch + * -XX:CompileCommand=inline,TestValueConstruction::checkDeopt + * compiler.valhalla.inlinetypes.TestValueConstruction + */ + +/* + * @test id=DontInlineObjectInit + * @key randomness + * @library /testlibrary /test/lib /compiler/whitebox /test/jdk/java/lang/invoke/common / + * @enablePreview + * @build jdk.test.whitebox.WhiteBox test.java.lang.invoke.lib.InstructionHelper + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:CompileCommand=dontinline,*Object:: -Xbatch + * -XX:CompileCommand=inline,TestValueConstruction::checkDeopt + * compiler.valhalla.inlinetypes.TestValueConstruction + */ + +/* + * @test id=DontInlineObjectInitDeoptimizeALot + * @key randomness + * @library /testlibrary /test/lib /compiler/whitebox /test/jdk/java/lang/invoke/common / + * @enablePreview + * @build jdk.test.whitebox.WhiteBox test.java.lang.invoke.lib.InstructionHelper + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:+IgnoreUnrecognizedVMOptions + * -XX:+DeoptimizeALot -XX:CompileCommand=dontinline,*Object:: -Xbatch + * -XX:CompileCommand=inline,TestValueConstruction::checkDeopt + * compiler.valhalla.inlinetypes.TestValueConstruction + */ + +/* + * @test id=DontInlineMyAbstractInit + * @key randomness + * @library /testlibrary /test/lib /compiler/whitebox /test/jdk/java/lang/invoke/common / + * @enablePreview + * @build jdk.test.whitebox.WhiteBox test.java.lang.invoke.lib.InstructionHelper + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:CompileCommand=dontinline,*MyAbstract:: -Xbatch + * -XX:CompileCommand=inline,TestValueConstruction::checkDeopt + * -XX:CompileCommand=MemLimit,*.*,2G~crash + * compiler.valhalla.inlinetypes.TestValueConstruction + */ + +/* + * @test id=StressIncrementalInlining + * @key randomness + * @library /testlibrary /test/lib /compiler/whitebox /test/jdk/java/lang/invoke/common / + * @enablePreview + * @build jdk.test.whitebox.WhiteBox test.java.lang.invoke.lib.InstructionHelper + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch + * -XX:-TieredCompilation -XX:+StressIncrementalInlining + * -XX:CompileCommand=inline,TestValueConstruction::checkDeopt + * -XX:CompileCommand=MemLimit,*.*,2G~crash + * compiler.valhalla.inlinetypes.TestValueConstruction + */ + +/* + * @test id=StressIncrementalInliningCompileOnlyTest + * @key randomness + * @library /testlibrary /test/lib /compiler/whitebox /test/jdk/java/lang/invoke/common / + * @enablePreview + * @build jdk.test.whitebox.WhiteBox test.java.lang.invoke.lib.InstructionHelper + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:-TieredCompilation -XX:+StressIncrementalInlining + * -XX:CompileCommand=inline,TestValueConstruction::checkDeopt + * -XX:CompileCommand=compileonly,*TestValueConstruction::test* -Xbatch + * compiler.valhalla.inlinetypes.TestValueConstruction + */ + +/* @test id=StressIncrementalInliningDontInlineMyValueInit + * @key randomness + * @library /testlibrary /test/lib /compiler/whitebox /test/jdk/java/lang/invoke/common / + * @enablePreview + * @build jdk.test.whitebox.WhiteBox test.java.lang.invoke.lib.InstructionHelper + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:-TieredCompilation -XX:+StressIncrementalInlining + * -XX:CompileCommand=inline,TestValueConstruction::checkDeopt + * -XX:CompileCommand=dontinline,*MyValue*:: -Xbatch + * compiler.valhalla.inlinetypes.TestValueConstruction + */ + +/* + * @test id=StressIncrementalInliningDontInlineObjectInit + * @key randomness + * @library /testlibrary /test/lib /compiler/whitebox /test/jdk/java/lang/invoke/common / + * @enablePreview + * @build jdk.test.whitebox.WhiteBox test.java.lang.invoke.lib.InstructionHelper + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:-TieredCompilation -XX:+StressIncrementalInlining + * -XX:CompileCommand=inline,TestValueConstruction::checkDeopt + * -XX:CompileCommand=dontinline,*Object:: -Xbatch + * compiler.valhalla.inlinetypes.TestValueConstruction + */ + +/* + * @test id=StressIncrementalInliningDontInlineMyAbstractInit + * @key randomness + * @library /testlibrary /test/lib /compiler/whitebox /test/jdk/java/lang/invoke/common / + * @enablePreview + * @build jdk.test.whitebox.WhiteBox test.java.lang.invoke.lib.InstructionHelper + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:-TieredCompilation -XX:+StressIncrementalInlining + * -XX:CompileCommand=inline,TestValueConstruction::checkDeopt + * -XX:CompileCommand=dontinline,*MyAbstract:: -Xbatch + * -XX:CompileCommand=MemLimit,*.*,2G~crash + * compiler.valhalla.inlinetypes.TestValueConstruction + */ + +/* + * @test id=StressIncrementalInliningOnStackReplacement + * @key randomness + * @library /testlibrary /test/lib /compiler/whitebox /test/jdk/java/lang/invoke/common / + * @enablePreview + * @build jdk.test.whitebox.WhiteBox test.java.lang.invoke.lib.InstructionHelper + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=300 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:-TieredCompilation -XX:+StressIncrementalInlining + * -XX:Tier0BackedgeNotifyFreqLog=0 -XX:Tier2BackedgeNotifyFreqLog=0 -XX:Tier3BackedgeNotifyFreqLog=0 + * -XX:Tier2BackEdgeThreshold=1 -XX:Tier3BackEdgeThreshold=1 -XX:Tier4BackEdgeThreshold=1 -Xbatch + * -XX:CompileCommand=MemLimit,*.*,2G~crash + * compiler.valhalla.inlinetypes.TestValueConstruction + */ + +public class TestValueConstruction { + static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + + static boolean VERBOSE = false; + static boolean[] deopt = new boolean[14]; + static boolean[] deoptBig = new boolean[24]; + static boolean[] deoptHuge = new boolean[37]; + + static Object o = new Object(); + + static void reportDeopt(int deoptNum) { + System.out.println("Deopt " + deoptNum + " triggered"); + if (VERBOSE) { + new Exception().printStackTrace(System.out); + } + } + + // Trigger deopts at various places + static void checkDeopt(int deoptNum) { + if (deopt[deoptNum]) { + // C2 will add an uncommon trap here + reportDeopt(deoptNum); + } + } + + // Trigger deopts at various places + static void checkDeoptBig(int deoptNum) { + if (deoptBig[deoptNum]) { + // C2 will add an uncommon trap here + reportDeopt(deoptNum); + } + } + + // Trigger deopts at various places + static void checkDeoptHuge(int deoptNum) { + if (deoptHuge[deoptNum]) { + // C2 will add an uncommon trap here + reportDeopt(deoptNum); + } + } + + static interface MyInterface { + + } + + static value class MyValue1 implements MyInterface { + int x; + + public MyValue1(int x) { + checkDeopt(0); + this.x = x; + checkDeopt(1); + super(); + checkDeopt(2); + } + + public MyValue1(int x, int deoptNum1, int deoptNum2, int deoptNum3) { + checkDeopt(deoptNum1); + this.x = x; + checkDeopt(deoptNum2); + super(); + checkDeopt(deoptNum3); + } + + public String toString() { + return "x: " + x; + } + } + + static value class MyValue1a extends MyAbstract1 implements MyInterface { + int x; + + public MyValue1a(int x) { + checkDeopt(0); + this.x = x; + checkDeopt(1); + super(); + checkDeopt(2); + } + + public MyValue1a(int x, int deoptNum1, int deoptNum2, int deoptNum3) { + checkDeopt(deoptNum1); + this.x = x; + checkDeopt(deoptNum2); + super(); + checkDeopt(deoptNum3); + } + + public String toString() { + return "x: " + x; + } + } + + static abstract value class AMyValue1 implements MyInterface { + int x; + + public AMyValue1(int x) { + checkDeopt(3); + this.x = x; + checkDeopt(4); + super(); + checkDeopt(5); + } + + public AMyValue1(int x, int deoptNum1, int deoptNum2, int deoptNum3) { + checkDeopt(deoptNum1); + this.x = x; + checkDeopt(deoptNum2); + super(); + checkDeopt(deoptNum3); + } + + public String toString() { + return "x: " + x; + } + } + + static value class MyValue1b extends AMyValue1 implements MyInterface { + int x; + + public MyValue1b(int x) { + checkDeopt(0); + this.x = x; + checkDeopt(1); + super(x); + checkDeopt(2); + } + + public MyValue1b(int x, int deoptNum1, int deoptNum2, int deoptNum3) { + checkDeopt(deoptNum1); + this.x = x; + checkDeopt(deoptNum2); + super(x, deoptNum1 + 3, deoptNum2 + 3, deoptNum3 + 3); + checkDeopt(deoptNum3); + } + + public String toString() { + return "x: " + x; + } + } + + + static abstract value class MyAbstract1 { } + + static value class MyValue2 extends MyAbstract1 { + int x; + + public MyValue2(int x) { + checkDeopt(0); + this.x = x; + checkDeopt(1); + super(); + checkDeopt(2); + } + + public String toString() { + return "x: " + x; + } + } + + static abstract value class MyAbstract2 { + public MyAbstract2(int x) { + checkDeopt(0); + } + } + + static value class MyValue3 extends MyAbstract2 { + int x; + + public MyValue3(int x) { + checkDeopt(1); + this(x, 0); + helper1(this, x, 2); // 'this' escapes through argument + helper2(x, 3); // 'this' escapes through receiver + checkDeopt(4); + } + + public MyValue3(int x, int unused) { + this.x = helper3(x, 5); + super(x); + helper1(this, x, 6); // 'this' escapes through argument + helper2(x, 7); // 'this' escapes through receiver + checkDeopt(8); + } + + public static void helper1(MyValue3 obj, int x, int deoptNum) { + checkDeopt(deoptNum); + Asserts.assertEQ(obj.x, x); + } + + public void helper2(int x, int deoptNum) { + checkDeopt(deoptNum); + Asserts.assertEQ(this.x, x); + } + + public static int helper3(int x, int deoptNum) { + checkDeopt(deoptNum); + return x; + } + + public String toString() { + return "x: " + x; + } + } + + static abstract value class AMyValue3a { + int x; + + public AMyValue3a(int x) { + this.x = helper3(x, 5); + super(); + helper1(this, x, 6); // 'this' escapes through argument + helper2(x, 7); // 'this' escapes through receiver + checkDeopt(8); + } + + public static void helper1(AMyValue3a obj, int x, int deoptNum) { + checkDeopt(deoptNum); + Asserts.assertEQ(obj.x, x); + } + + public void helper2(int x, int deoptNum) { + checkDeopt(deoptNum); + Asserts.assertEQ(this.x, x); + } + + public static int helper3(int x, int deoptNum) { + checkDeopt(deoptNum); + return x; + } + } + + static value class MyValue3a extends AMyValue3a { + int y; + + public MyValue3a(int y) { + checkDeopt(1); + this.y = helper3(y, 5); + super(y); + helper1(this, y, 2); // 'this' escapes through argument + helper2(y, 3); // 'this' escapes through receiver + checkDeopt(4); + } + + + public static void helper1(MyValue3a obj, int y, int deoptNum) { + checkDeopt(deoptNum); + Asserts.assertEQ(obj.y, y); + } + + public void helper2(int y, int deoptNum) { + checkDeopt(deoptNum); + Asserts.assertEQ(this.y, y); + } + + public static int helper3(int y, int deoptNum) { + checkDeopt(deoptNum); + return y; + } + + public String toString() { + return "x: " + y; + } + } + + static value class MyValue4 { + Integer x; + + public MyValue4(int x) { + checkDeopt(0); + this.x = x; + checkDeopt(1); + super(); + checkDeopt(2); + } + + public String toString() { + return "x: " + x; + } + } + + abstract static value class AMyValue4a { + Integer y; + + public AMyValue4a(int y) { + checkDeopt(3); + this.y = y; + checkDeopt(4); + super(); + checkDeopt(5); + } + + public String toString() { + return "y: " + y; + } + } + + static value class MyValue4a extends AMyValue4a { + Integer x; + + public MyValue4a(int x) { + checkDeopt(0); + this.x = x; + checkDeopt(1); + super(x); + checkDeopt(2); + } + + public String toString() { + return "x: " + x; + } + } + + static value class MyValue5 extends MyAbstract1 { + int x; + + public MyValue5(int x, boolean b) { + checkDeopt(0); + if (b) { + checkDeopt(1); + this.x = 42; + checkDeopt(2); + } else { + checkDeopt(3); + this.x = x; + checkDeopt(4); + } + checkDeopt(5); + super(); + checkDeopt(6); + } + + public String toString() { + return "x: " + x; + } + } + + static abstract value class AMyValue5a { + int y; + + public AMyValue5a(int y, boolean b) { + checkDeopt(7); + if (b) { + checkDeopt(8); + this.y = 42; + checkDeopt(9); + } else { + checkDeopt(10); + this.y = y; + checkDeopt(11); + } + checkDeopt(12); + super(); + checkDeopt(13); + } + + public String toString() { + return "y: " + y; + } + } + + static value class MyValue5a extends AMyValue5a { + int x; + + public MyValue5a(int x, boolean b) { + checkDeopt(0); + if (b) { + checkDeopt(1); + this.x = 42; + checkDeopt(2); + } else { + checkDeopt(3); + this.x = x; + checkDeopt(4); + } + checkDeopt(5); + super(x, b); + checkDeopt(6); + } + + public String toString() { + return "x: " + x; + } + } + + static value class MyValue6 { + int x; + MyValue1 val1; + MyValue1 val2; + + public MyValue6(int x) { + checkDeopt(0); + this.x = x; + checkDeopt(1); + this.val1 = new MyValue1(x, 2, 3, 4); + checkDeopt(5); + this.val2 = new MyValue1(x + 1, 6, 7, 8); + checkDeopt(9); + super(); + checkDeopt(10); + } + + public String toString() { + return "x: " + x + ", val1: [" + val1 + "], val2: [" + val2 + "]"; + } + } + + static value class MyValue6a { + int x; + MyValue1a val1; + MyValue1a val2; + + public MyValue6a(int x) { + checkDeopt(0); + this.x = x; + checkDeopt(1); + this.val1 = new MyValue1a(x, 2, 3, 4); + checkDeopt(5); + this.val2 = new MyValue1a(x + 1, 6, 7, 8); + checkDeopt(9); + super(); + checkDeopt(10); + } + + public String toString() { + return "x: " + x + ", val1: [" + val1 + "], val2: [" + val2 + "]"; + } + } + + static value class MyValue6b { + int x; + MyValue1b val1; + MyValue1b val2; + + public MyValue6b(int x) { + checkDeopt(0); + this.x = x; + checkDeopt(1); + this.val1 = new MyValue1b(x, 2, 3, 4); + checkDeopt(5); + this.val2 = new MyValue1b(x + 1, 6, 7, 8); + checkDeopt(12); + super(); + checkDeopt(13); + } + + public String toString() { + return "x: " + x + ", val1: [" + val1 + "], val2: [" + val2 + "]"; + } + } + + // Same as MyValue6 but unused MyValue1 construction + static value class MyValue7 { + int x; + + public MyValue7(int x) { + checkDeopt(0); + this.x = x; + checkDeopt(1); + new MyValue1(42, 2, 3, 4); + checkDeopt(5); + new MyValue1(43, 6, 7, 8); + checkDeopt(9); + super(); + checkDeopt(10); + } + + public String toString() { + return "x: " + x; + } + } + + // Same as MyValue6 but unused MyValue1 construction + static value class MyValue7a { + int x; + + public MyValue7a(int x) { + checkDeopt(0); + this.x = x; + checkDeopt(1); + new MyValue1a(42, 2, 3, 4); + checkDeopt(5); + new MyValue1a(43, 6, 7, 8); + checkDeopt(9); + super(); + checkDeopt(10); + } + + public String toString() { + return "x: " + x; + } + } + + // Same as MyValue6 but unused MyValue1 construction + static value class MyValue7b { + int x; + + public MyValue7b(int x) { + checkDeopt(0); + this.x = x; + checkDeopt(1); + new MyValue1b(42, 2, 3, 4); + checkDeopt(5); + new MyValue1b(43, 6, 7, 8); + checkDeopt(12); + super(); + checkDeopt(13); + } + + public String toString() { + return "x: " + x; + } + } + + // Constructor calling another constructor of the same value class with control flow dependent initialization + static value class MyValue8 { + int x; + + public MyValue8(int x) { + checkDeopt(0); + this(x, 0); + checkDeopt(1); + } + + public MyValue8(int x, int unused1) { + checkDeopt(2); + if ((x % 2) == 0) { + checkDeopt(3); + this.x = 42; + checkDeopt(4); + } else { + checkDeopt(5); + this.x = x; + checkDeopt(6); + } + checkDeopt(7); + super(); + checkDeopt(8); + } + + public MyValue8(int x, int unused1, int unused2) { + checkDeopt(3); + this.x = x; + checkDeopt(4); + } + + public static MyValue8 valueOf(int x) { + checkDeopt(0); + if ((x % 2) == 0) { + checkDeopt(1); + return new MyValue8(42, 0, 0); + } else { + checkDeopt(2); + return new MyValue8(x, 0, 0); + } + } + + public String toString() { + return "x: " + x; + } + } + + // Constructor calling another constructor of the same value class with control flow dependent initialization + static abstract value class AMyValue8a { + int y; + + public AMyValue8a(int y) { + checkDeoptBig(9); + this(y, 0); + checkDeoptBig(10); + } + + public AMyValue8a(int y, int unused1) { + checkDeoptBig(11); + if ((y % 2) == 0) { + checkDeoptBig(12); + this.y = 42; + checkDeoptBig(13); + } else { + checkDeoptBig(14); + this.y = y; + checkDeoptBig(15); + } + checkDeoptBig(16); + super(); + checkDeoptBig(17); + } + + public AMyValue8a(int y, int unused1, int unused2) { + checkDeoptBig(12); + this.y = y; + checkDeoptBig(13); + } + + public static AMyValue8a valueOf(int y) { + checkDeoptBig(0); + if ((y % 2) == 0) { + checkDeoptBig(1); + return new MyValue8a(42, 0, 0); + } else { + checkDeoptBig(2); + return new MyValue8a(y, 0, 0); + } + } + + public String toString() { + return "y: " + y; + } + } + + // Constructor calling another constructor of the same value class with control flow dependent initialization + static value class MyValue8a extends AMyValue8a { + int x; + + public MyValue8a(int x) { + checkDeoptBig(0); + this(x, 0); + checkDeoptBig(1); + } + + public MyValue8a(int x, int unused1) { + checkDeoptBig(2); + if ((x % 2) == 0) { + checkDeoptBig(3); + this.x = 42; + checkDeoptBig(4); + } else { + checkDeoptBig(5); + this.x = x; + checkDeoptBig(6); + } + checkDeoptBig(7); + super(unused1); + checkDeoptBig(8); + } + + public MyValue8a(int x, int unused1, int unused2) { + checkDeoptBig(3); + this.x = x; + checkDeoptBig(4); + super(x, unused1, unused2); + } + + public static MyValue8a valueOf(int x) { + checkDeoptBig(0); + if ((x % 2) == 0) { + checkDeoptBig(1); + return new MyValue8a(42, 0, 0); + } else { + checkDeoptBig(2); + return new MyValue8a(x, 0, 0); + } + } + + public String toString() { + return "x: " + x; + } + } + + // Constructor calling another constructor of a different value class + static value class MyValue9 { + MyValue8 val; + + public MyValue9(int x) { + checkDeopt(9); + this(x, 0); + checkDeopt(10); + } + + public MyValue9(int i, int unused1) { + checkDeopt(11); + val = new MyValue8(i); + checkDeopt(12); + } + + public MyValue9(int x, int unused1, int unused2) { + checkDeopt(5); + this(x, 0, 0, 0); + checkDeopt(6); + } + + public MyValue9(int i, int unused1, int unused2, int unused3) { + checkDeopt(7); + val = MyValue8.valueOf(i); + checkDeopt(8); + } + + public String toString() { + return "val: [" + val + "]"; + } + } + + abstract static value class AMyValue9a { + AMyValue8a valA; + + public AMyValue9a(int x) { + checkDeoptHuge(22); + this(x, 0); + checkDeoptHuge(23); + } + + public AMyValue9a(int i, int unused1) { + checkDeoptHuge(24); + valA = new MyValue8a(i); + checkDeoptHuge(25); + } + + public AMyValue9a(int x, int unused1, int unused2) { + checkDeoptHuge(18); + this(x, 0, 0, 0); + checkDeoptHuge(19); + } + + public AMyValue9a(int i, int unused1, int unused2, int unused3) { + checkDeoptHuge(20); + valA = MyValue8a.valueOf(i); + checkDeoptHuge(21); + } + + public String toString() { + return "valA: [" + valA + "]"; + } + } + + // Constructor calling another constructor of a different value class + static value class MyValue9a extends AMyValue9a { + MyValue8a val; + + public MyValue9a(int x) { + checkDeoptHuge(18); + this(x, 0); + checkDeoptHuge(19); + } + + public MyValue9a(int i, int unused1) { + checkDeoptHuge(20); + val = new MyValue8a(i); + checkDeoptHuge(21); + super(i, unused1); + checkDeoptHuge(26); + } + + public MyValue9a(int x, int unused1, int unused2) { + checkDeoptHuge(14); + this(x, 0, 0, 0); + checkDeoptHuge(15); + } + + public MyValue9a(int i, int unused1, int unused2, int unused3) { + checkDeoptHuge(16); + val = MyValue8a.valueOf(i); + checkDeoptHuge(17); + super(i, unused1, unused2, unused3); + checkDeoptHuge(27); + } + + public String toString() { + return "val: [" + val + "]"; + } + } + + // Constructor with a loop + static value class MyValue10 { + int x; + int y; + + public MyValue10(int x, int cnt) { + checkDeopt(0); + this.x = x; + checkDeopt(1); + int res = 0; + for (int i = 0; i < cnt; ++i) { + checkDeopt(2); + res += x; + checkDeopt(3); + } + checkDeopt(4); + this.y = res; + checkDeopt(5); + super(); + checkDeopt(6); + } + + public String toString() { + return "x: " + x + ", y: " + y; + } + } + + // Constructor with a loop + static abstract value class AMyValue10a { + int a; + int b; + + public AMyValue10a(int a, int cnt) { + checkDeopt(7); + this.a = a; + checkDeopt(8); + int res = 0; + for (int i = 0; i < cnt; ++i) { + checkDeopt(9); + res += a; + checkDeopt(10); + } + checkDeopt(11); + this.b = res; + checkDeopt(12); + super(); + checkDeopt(13); + } + + public String toString() { + return "x: " + a + ", y: " + b; + } + } + + // Constructor with a loop + static value class MyValue10a extends AMyValue10a { + int x; + int y; + + public MyValue10a(int x, int cnt) { + checkDeopt(0); + this.x = x; + checkDeopt(1); + int res = 0; + for (int i = 0; i < cnt; ++i) { + checkDeopt(2); + res += x; + checkDeopt(3); + } + checkDeopt(4); + this.y = res; + checkDeopt(5); + super(x, cnt); + checkDeopt(6); + } + + public String toString() { + return "x: " + x + ", y: " + y; + } + } + + // Value class with recursive field definitions + static value class MyValue11 { + int x; + MyValue11 val1; + MyValue11 val2; + + public MyValue11(int x) { + checkDeopt(0); + this.x = x; + checkDeopt(1); + this.val1 = new MyValue11(x + 1, 2, 3, 4, 5); + checkDeopt(6); + this.val2 = new MyValue11(x + 2, 7, 8, 9, 10); + checkDeopt(11); + } + + public MyValue11(int x, int deoptNum1, int deoptNum2, int deoptNum3, int deoptNum4) { + checkDeopt(deoptNum1); + this.x = x; + checkDeopt(deoptNum2); + this.val1 = null; + checkDeopt(deoptNum3); + this.val2 = null; + checkDeopt(deoptNum4); + } + + public String toString() { + return "x: " + x + ", val1: [" + (val1 != this ? val1 : "this") + "], val2: [" + (val2 != this ? val2 : "this") + "]"; + } + } + + // Value class with recursive field definitions + static abstract value class AMyValue11a { + int y; + AMyValue11a valA1; + AMyValue11a valA2; + + public AMyValue11a(int y) { + checkDeoptHuge(19); + this.y = y; + checkDeoptHuge(20); + this.valA1 = new MyValue11a(y + 1, 21, 22, 23, 24, 25); + checkDeoptHuge(26); + this.valA2 = new MyValue11a(y + 2, 27, 28, 29, 30, 31); + checkDeoptHuge(36); + } + + public AMyValue11a(int y, int deoptNum1, int deoptNum2, int deoptNum3, int deoptNum4) { + checkDeoptHuge(deoptNum1); + this.y = y; + checkDeoptHuge(deoptNum2); + this.valA1 = null; + checkDeoptHuge(deoptNum3); + this.valA2 = null; + checkDeoptHuge(deoptNum4); + } + + public String toString() { + return "x: " + y + ", val1: [" + (valA1 != this ? valA1 : "this") + "], val2: [" + (valA2 != this ? valA2 : "this") + "]"; + } + } + + // Value class with recursive field definitions + static value class MyValue11a extends AMyValue11a { + int x; + MyValue11a val1; + MyValue11a val2; + + public MyValue11a(int x) { + checkDeoptHuge(0); + this.x = x; + checkDeoptHuge(1); + this.val1 = new MyValue11a(x + 1, 2, 3, 4, 5, 6); + checkDeoptHuge(7); + this.val2 = new MyValue11a(x + 2, 8, 9, 10, 11, 12); + checkDeoptHuge(17); + super(x); + checkDeoptHuge(18); + } + + public MyValue11a(int x, int deoptNum1, int deoptNum2, int deoptNum3, int deoptNum4, int deoptNum5) { + checkDeoptHuge(deoptNum1); + this.x = x; + checkDeoptHuge(deoptNum2); + this.val1 = null; + checkDeoptHuge(deoptNum3); + this.val2 = null; + checkDeoptHuge(deoptNum4); + super(x, deoptNum5 + 1, deoptNum5 + 2, deoptNum5 + 3, deoptNum5 + 4); + checkDeoptHuge(deoptNum5); + } + + public String toString() { + return "x: " + x + ", val1: [" + (val1 != this ? val1 : "this") + "], val2: [" + (val2 != this ? val2 : "this") + "]"; + } + } + + static value class MyValue12 { + Object o; + + public MyValue12() { + checkDeopt(0); + this.o = new Object(); + checkDeopt(1); + super(); + checkDeopt(2); + } + } + + static abstract value class MyAbstract13b { + MyAbstract13b() { + checkDeopt(4); + super(); + checkDeopt(5); + } + } + + static abstract value class MyAbstract13a extends MyAbstract13b { + MyAbstract13a() { + checkDeopt(2); + super(); + checkDeopt(3); + } + } + + static value class MyValue13 extends MyAbstract13a { + public MyValue13() { + checkDeopt(0); + super(); + checkDeopt(1); + } + } + + static value class MyValue14 { + private Object o; + + public MyValue14(Object o) { + this.o = o; + } + + public static MyValue14 get(Object o) { + return new MyValue14(getO(o)); + } + + public static Object getO(Object obj) { + return obj; + } + } + + static abstract value class MyAbstract15 { + int i; + + public MyAbstract15(int i) { + checkDeoptBig(2); + this.i = 34; + checkDeoptBig(3); + super(); + checkDeoptBig(4); + MyValue15 v = new MyValue15(); + checkDeoptBig(11); + foo(v); + checkDeoptBig(13); + } + + MyValue15 foo(MyValue15 v) { + checkDeoptBig(12); + return v; + } + + public MyAbstract15() { + checkDeoptBig(17); + this.i = 4; + checkDeoptBig(18); + super(); + checkDeoptBig(19); + } + } + + static value class MyValue15 extends MyAbstract15 { + int i; + + public MyValue15(int i) { + checkDeoptBig(0); + this.i = 3; + checkDeoptBig(1); + super(i); + checkDeoptBig(14); + MyValue15 v = new MyValue15(); + checkDeoptBig(21); + getO(v); + checkDeoptBig(23); + } + + + public MyValue15() { + checkDeoptBig( 15); + this.i = 43; + checkDeoptBig(16); + super(); + checkDeoptBig(20); + } + + static Object getO(Object o) { + checkDeoptBig(22); + return o; + } + } + + static abstract value class MyAbstract16 { + int i; + public MyAbstract16() { + checkDeoptBig(8); + this.i = 4; + checkDeoptBig(9); + super(); + checkDeoptBig(10); + } + + public MyAbstract16(int i) { + checkDeoptBig(2); + this.i = 34; + checkDeoptBig(3); + super(); + checkDeoptBig(4); + getV(); + checkDeoptBig(13); + } + + public MyAbstract16(boolean ignore) { + checkDeoptBig(17); + this.i = 4; + checkDeoptBig(18); + super(); + checkDeoptBig(19); + } + + public static MyValue16 getV() { + checkDeoptBig(5); + MyValue16 v = new MyValue16(); + checkDeoptBig(12); + return v; + } + } + + static value class MyValue16 extends MyAbstract16 { + int i; + + public MyValue16(int i) { + checkDeoptBig(0); + this.i = 3; + checkDeoptBig(1); + super(i); + checkDeoptBig(14); + MyValue16 v = new MyValue16(true); + checkDeoptBig(21); + getO(v); + checkDeoptBig(23); + } + + public MyValue16() { + checkDeoptBig( 6); + this.i = 34; + checkDeoptBig(7); + super(); + checkDeoptBig(11); + } + + public MyValue16(boolean ignore) { + checkDeoptBig( 15); + this.i = 43; + checkDeoptBig(16); + super(true); + checkDeoptBig(20); + } + + static Object getO(Object o) { + checkDeoptBig(22); + return o; + } + } + + public static int test1(int x) { + MyValue1 val = new MyValue1(x); + checkDeopt(3); + return val.x; + } + + public static int test1a(int x) { + MyValue1a val = new MyValue1a(x); + checkDeopt(3); + return val.x; + } + + public static int test1b(int x) { + MyValue1b val = new MyValue1b(x); + checkDeopt(6); + return val.x; + } + + public static MyValue1 helper1(int x) { + return new MyValue1(x); + } + + public static MyValue1a helper1a(int x) { + return new MyValue1a(x); + } + + public static MyValue1b helper1b(int x) { + return new MyValue1b(x); + } + + public static Object test2(int x) { + return helper1(x); + } + public static Object test2a(int x) { + return helper1a(x); + } + public static Object test2b(int x) { + return helper1b(x); + } + + public static Object test3(int limit) { + MyValue1 res = null; + for (int i = 0; i <= 10; ++i) { + res = new MyValue1(i); + checkDeopt(3); + } + return res; + } + + public static Object test3a(int limit) { + MyValue1a res = null; + for (int i = 0; i <= 10; ++i) { + res = new MyValue1a(i); + checkDeopt(3); + } + return res; + } + + public static Object test3b(int limit) { + MyValue1b res = null; + for (int i = 0; i <= 10; ++i) { + res = new MyValue1b(i); + checkDeopt(6); + } + return res; + } + + public static MyValue1 test4(int x) { + MyValue1 v = new MyValue1(x); + checkDeopt(3); + v = new MyValue1(x); + return v; + } + + public static MyValue1a test4a(int x) { + MyValue1a v = new MyValue1a(x); + checkDeopt(3); + v = new MyValue1a(x); + return v; + } + + public static MyValue1b test4b(int x) { + MyValue1b v = new MyValue1b(x); + checkDeopt(6); + v = new MyValue1b(x); + return v; + } + + public static int test5(int x) { + MyValue2 val = new MyValue2(x); + checkDeopt(3); + return val.x; + } + + public static MyValue2 helper2(int x) { + return new MyValue2(x); + } + + public static Object test6(int x) { + return helper2(x); + } + + public static Object test7(int limit) { + MyValue2 res = null; + for (int i = 0; i <= 10; ++i) { + res = new MyValue2(i); + checkDeopt(3); + } + return res; + } + + public static MyValue2 test8(int x) { + MyValue2 v = new MyValue2(x); + checkDeopt(3); + v = new MyValue2(x); + return v; + } + + public static int test9(int x) { + MyValue3 val = new MyValue3(x); + checkDeopt(9); + return val.x; + } + + public static int test9a(int x) { + MyValue3a val = new MyValue3a(x); + checkDeopt(9); + return val.x + val.y; + } + + public static MyValue3 helper3(int x) { + return new MyValue3(x); + } + + public static Object test10(int x) { + return helper3(x); + } + + public static MyValue3a helper3a(int x) { + return new MyValue3a(x); + } + + public static Object test10a(int x) { + return helper3a(x); + } + + public static Object test11(int limit) { + MyValue3 res = null; + for (int i = 0; i <= 10; ++i) { + checkDeopt(9); + res = new MyValue3(i); + } + return res; + } + + public static Object test11a(int limit) { + MyValue3a res = null; + for (int i = 0; i <= 10; ++i) { + checkDeopt(9); + res = new MyValue3a(i); + } + return res; + } + + public static MyValue3 test12(int x) { + MyValue3 v = new MyValue3(x); + checkDeopt(9); + v = new MyValue3(x); + return v; + } + + public static MyValue3a test12a(int x) { + MyValue3a v = new MyValue3a(x); + checkDeopt(9); + v = new MyValue3a(x); + return v; + } + + public static MyValue4 test13(int x) { + return new MyValue4(x); + } + + public static MyValue4a test13a(int x) { + return new MyValue4a(x); + } + + public static MyValue5 test14(int x, boolean b) { + return new MyValue5(x, b); + } + + public static MyValue5a test14a(int x, boolean b) { + return new MyValue5a(x, b); + } + + public static Object test15(int x) { + return new MyValue6(x); + } + + public static Object test15a(int x) { + return new MyValue6a(x); + } + + public static Object test15b(int x) { + return new MyValue6b(x); + } + + public static Object test16(int x) { + return new MyValue7(x); + } + + public static Object test16a(int x) { + return new MyValue7a(x); + } + + public static Object test16b(int x) { + return new MyValue7b(x); + } + + public static MyValue8 test17(int x) { + return new MyValue8(x); + } + + public static MyValue8a test17a(int x) { + return new MyValue8a(x); + } + + public static MyValue8 test18(int x) { + return new MyValue8(x, 0); + } + + public static MyValue8a test18a(int x) { + return new MyValue8a(x, 0); + } + + public static MyValue8 test19(int x) { + return MyValue8.valueOf(x); + } + + public static MyValue8a test19a(int x) { + return MyValue8a.valueOf(x); + } + + public static AMyValue8a test19b(int x) { + return AMyValue8a.valueOf(x); + } + + public static MyValue9 test20(int x) { + return new MyValue9(x); + } + + public static MyValue9a test20a(int x) { + return new MyValue9a(x); + } + + public static MyValue9 test21(int x) { + return new MyValue9(x, 0); + } + + public static MyValue9a test21a(int x) { + return new MyValue9a(x, 0); + } + + public static MyValue9 test22(int x) { + return new MyValue9(x, 0, 0); + } + + public static MyValue9a test22a(int x) { + return new MyValue9a(x, 0, 0); + } + + public static MyValue9 test23(int x) { + return new MyValue9(x, 0, 0, 0); + } + + public static MyValue9a test23a(int x) { + return new MyValue9a(x, 0, 0, 0); + } + + public static MyValue10 test24(int x, int cnt) { + return new MyValue10(x, cnt); + } + + public static MyValue10a test24a(int x, int cnt) { + return new MyValue10a(x, cnt); + } + + public static MyValue11 test25(int x) { + return new MyValue11(x); + } + + public static MyValue11a test25a(int x) { + return new MyValue11a(x); + } + + public static MyValue12 testObjectCallInsideConstructor() { + return new MyValue12(); + } + + public static MyValue13 testMultipleAbstract() { + return new MyValue13(); + } + + public static MyValue14 testCallAsConstructorArgument() { + return MyValue14.get(o); + } + + public static MyValue15 testBackAndForthAbstract(int x) { + return new MyValue15(x); + } + + public static MyValue16 testBackAndForthAbstract2(int x) { + return new MyValue16(x); + } + + private static final MethodHandle MULTIPLE_OCCURRENCES_IN_JVMS = InstructionHelper.buildMethodHandle(MethodHandles.lookup(), + "multipleOccurrencesInJVMSReturnStack", + MethodType.methodType(MyValue1.class, int.class), + CODE -> { + Label loopHead = CODE.newLabel(); + Label loopExit = CODE.newLabel(); + CODE. + new_(MyValue1.class.describeConstable().get()). + dup(). + astore(1). + astore(2). + iconst_0(). + istore(3). + labelBinding(loopHead). + iload(3). + ldc(100). + if_icmpge(loopExit). + iinc(3, 1). + goto_(loopHead). + labelBinding(loopExit). + aload(2). + iload(0). + invokespecial(MyValue1.class.describeConstable().get(), "", MethodType.methodType(void.class, int.class).describeConstable().get()). + aload(2). + areturn(); + }); + + public static MyValue1 testMultipleOccurrencesInJVMS(int x) throws Throwable { + return (MyValue1) MULTIPLE_OCCURRENCES_IN_JVMS.invokeExact(x); + } + + public static void main(String[] args) throws Throwable { + Random rand = Utils.getRandomInstance(); + + // Randomly exclude some constructors from inlining via the WhiteBox API because CompileCommands don't match on different signatures. + WHITE_BOX.testSetDontInlineMethod(MyValue1.class.getConstructor(int.class), rand.nextBoolean()); + WHITE_BOX.testSetDontInlineMethod(MyValue1a.class.getConstructor(int.class), rand.nextBoolean()); + WHITE_BOX.testSetDontInlineMethod(MyValue1b.class.getConstructor(int.class), rand.nextBoolean()); + WHITE_BOX.testSetDontInlineMethod(MyValue1.class.getConstructor(int.class, int.class, int.class, int.class), rand.nextBoolean()); + WHITE_BOX.testSetDontInlineMethod(MyValue1a.class.getConstructor(int.class, int.class, int.class, int.class), rand.nextBoolean()); + WHITE_BOX.testSetDontInlineMethod(MyValue1b.class.getConstructor(int.class, int.class, int.class, int.class), rand.nextBoolean()); + WHITE_BOX.testSetDontInlineMethod(MyValue3.class.getConstructor(int.class), rand.nextBoolean()); + WHITE_BOX.testSetDontInlineMethod(MyValue3.class.getConstructor(int.class, int.class), rand.nextBoolean()); + WHITE_BOX.testSetDontInlineMethod(MyValue8.class.getConstructor(int.class), rand.nextBoolean()); + WHITE_BOX.testSetDontInlineMethod(MyValue8.class.getConstructor(int.class, int.class), rand.nextBoolean()); + WHITE_BOX.testSetDontInlineMethod(MyValue8.class.getConstructor(int.class, int.class, int.class), rand.nextBoolean()); + WHITE_BOX.testSetDontInlineMethod(MyValue9.class.getConstructor(int.class), rand.nextBoolean()); + WHITE_BOX.testSetDontInlineMethod(MyValue9.class.getConstructor(int.class, int.class), rand.nextBoolean()); + WHITE_BOX.testSetDontInlineMethod(MyValue9.class.getConstructor(int.class, int.class, int.class), rand.nextBoolean()); + WHITE_BOX.testSetDontInlineMethod(MyValue9.class.getConstructor(int.class, int.class, int.class, int.class), rand.nextBoolean()); + WHITE_BOX.testSetDontInlineMethod(MyValue11.class.getConstructor(int.class), rand.nextBoolean()); + WHITE_BOX.testSetDontInlineMethod(MyValue11.class.getConstructor(int.class, int.class, int.class, int.class, int.class), rand.nextBoolean()); + int randValue = rand.nextInt(0, 4); + if (randValue > 0) { + // Some variation + WHITE_BOX.testSetDontInlineMethod(MyValue15.class.getConstructor(), rand.nextBoolean()); + WHITE_BOX.testSetDontInlineMethod(MyValue15.class.getConstructor(int.class), rand.nextBoolean()); + WHITE_BOX.testSetDontInlineMethod(MyValue16.class.getConstructor(), rand.nextBoolean()); + WHITE_BOX.testSetDontInlineMethod(MyValue16.class.getConstructor(int.class), rand.nextBoolean()); + if (randValue > 1) { + WHITE_BOX.testSetDontInlineMethod(MyAbstract15.class.getConstructor(), rand.nextBoolean()); + WHITE_BOX.testSetDontInlineMethod(MyAbstract15.class.getConstructor(int.class), rand.nextBoolean()); + WHITE_BOX.testSetDontInlineMethod(MyAbstract16.class.getConstructor(), rand.nextBoolean()); + WHITE_BOX.testSetDontInlineMethod(MyAbstract16.class.getConstructor(int.class), rand.nextBoolean()); + } + } + + Integer deoptNum = Integer.getInteger("deoptNum"); + Integer deoptNumBig = Integer.getInteger("deoptNumBig"); + Integer deoptNumHuge = Integer.getInteger("deoptNumHuge"); + if (deoptNum == null) { + deoptNum = rand.nextInt(deopt.length); + System.out.println("deoptNum = " + deoptNum); + } + if (deoptNumBig == null) { + deoptNumBig = rand.nextInt(deoptBig.length); + System.out.println("deoptNumBig = " + deoptNumBig); + } + if (deoptNumHuge == null) { + deoptNumHuge = rand.nextInt(deoptHuge.length); + System.out.println("deoptNumHuge = " + deoptNumHuge); + } + run(0, true); + for (int x = 1; x <= 50_000; ++x) { + if (x == 50_000) { + // Last iteration, trigger deoptimization + run(x, true); + deopt[deoptNum] = true; + deoptBig[deoptNumBig] = true; + deoptHuge[deoptNumHuge] = true; + run(x, true); + } else { + run(x, false); + } + } + } + + private static void run(int x, boolean doCheck) throws Throwable { + check(test1(x), x, doCheck); + check(test1a(x), x, doCheck); + check(test1b(x), x, doCheck); + check(test2(x), new MyValue1(x), doCheck); + check(test2a(x), new MyValue1a(x), doCheck); + check(test2b(x), new MyValue1b(x), doCheck); + check(test3(10), new MyValue1(10), doCheck); + check(test3a(10), new MyValue1a(10), doCheck); + check(test3b(10), new MyValue1b(10), doCheck); + check(test4(x), new MyValue1(x), doCheck); + check(test4a(x), new MyValue1a(x), doCheck); + check(test4b(x), new MyValue1b(x), doCheck); + check(test5(x), x, doCheck); + check(test5(x), x, doCheck); + check(test6(x), new MyValue2(x), doCheck); + check(test6(x), new MyValue2(x), doCheck); + check(test7(10), new MyValue2(10), doCheck); + check(test8(x), new MyValue2(x), doCheck); + check(test9(x), x, doCheck); + check(test9a(x), x + x, doCheck); + check(test10(x), new MyValue3(x), doCheck); + check(test10a(x), new MyValue3a(x), doCheck); + check(test11(10), new MyValue3(10), doCheck); + check(test11a(10), new MyValue3a(10), doCheck); + check(test12(x), new MyValue3(x), doCheck); + check(test12a(x), new MyValue3a(x), doCheck); + check(test13(x), new MyValue4(x), doCheck); + check(test13a(x), new MyValue4a(x), doCheck); + check(test14(x, (x % 2) == 0), new MyValue5(x, (x % 2) == 0), doCheck); + check(test14a(x, (x % 2) == 0), new MyValue5a(x, (x % 2) == 0), doCheck); + check(test15(x), new MyValue6(x), doCheck); + check(test15a(x), new MyValue6a(x), doCheck); + check(test15b(x), new MyValue6b(x), doCheck); + check(test16(x), new MyValue7(x), doCheck); + check(test16a(x), new MyValue7a(x), doCheck); + check(test16b(x), new MyValue7b(x), doCheck); + check(test17(x), new MyValue8(x), doCheck); + check(test17a(x), new MyValue8a(x), doCheck); + check(test18(x), new MyValue8(x), doCheck); + check(test18a(x), new MyValue8a(x), doCheck); + check(test19(x), new MyValue8(x), doCheck); + check(test19a(x), new MyValue8a(x), doCheck); + check(test19b(x), new MyValue8a(x), doCheck); + check(test20(x), new MyValue9(x), doCheck); + check(test20a(x), new MyValue9a(x), doCheck); + check(test21(x), new MyValue9(x), doCheck); + check(test21a(x), new MyValue9a(x), doCheck); + check(test22(x), new MyValue9(x), doCheck); + check(test22a(x), new MyValue9a(x), doCheck); + check(test23(x), new MyValue9(x), doCheck); + check(test23a(x), new MyValue9a(x), doCheck); + check(test24(x, x % 10), new MyValue10(x, x % 10), doCheck); + check(test24a(x, x % 10), new MyValue10a(x, x % 10), doCheck); + check(test25(x), new MyValue11(x), doCheck); + check(test25a(x), new MyValue11a(x), doCheck); + testObjectCallInsideConstructor(); // Creates a new Object each time - cannot compare on equality. + check(testMultipleAbstract(), new MyValue13(), doCheck); + check(testCallAsConstructorArgument(), new MyValue14(o), doCheck); + check(testBackAndForthAbstract(x), new MyValue15(x), doCheck); + check(testBackAndForthAbstract2(x), new MyValue16(x), doCheck); + check(testMultipleOccurrencesInJVMS(x), new MyValue1(x), doCheck); + } + + private static void check(Object testResult, Object expectedResult, boolean check) { + if (check) { + Asserts.assertEQ(testResult, expectedResult); + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestValueRematDuringTypeSharpening.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestValueRematDuringTypeSharpening.java new file mode 100644 index 00000000000..0cb099a0e00 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestValueRematDuringTypeSharpening.java @@ -0,0 +1,88 @@ +/* + * 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. + */ + +package compiler.valhalla.inlinetypes; + +import jdk.test.lib.Asserts; +import jdk.internal.misc.Unsafe; +import compiler.lib.ir_framework.*; + +/* + * @test + * @summary Missing InlineTypeNode re-materialization during type sharpening. + * @library /test/lib / + * @enablePreview + * @modules java.base/jdk.internal.misc + * @run main/othervm/timeout=300 compiler.valhalla.inlinetypes.TestValueRematDuringTypeSharpening + */ + +abstract value class topValue { +} + +value class dummyValue1 extends topValue { + int field; + public dummyValue1(int val) { + field = val; + } +} + +value class dummyValue2 extends topValue { + int field; + public dummyValue2(int val) { + field = val; + } +} + +public class TestValueRematDuringTypeSharpening { + + public static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + @DontInline + public static int getUnsafeFieldValue(topValue obj, int incr) { + return UNSAFE.getInt(obj, 12) + incr; + } + + @Test + @IR(phase = {CompilePhase.AFTER_PARSING}, counts = {IRNode.INLINE_TYPE, " > 0 "}) + public static int test(topValue obj) { + int val = 0; + if (obj.getClass() != dummyValue1.class) { + val += 10; + } else if (obj.getClass() != dummyValue2.class) { + val += 20; + } + return getUnsafeFieldValue(obj, val); + } + + @Run(test = {"test"}, mode = RunMode.NORMAL) + public static void kernel() { + test(new dummyValue1(10)); + test(new dummyValue2(20)); + } + + public static void main(String [] args) { + TestFramework.runWithFlags("-XX:-TieredCompilation", "--enable-preview", "--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED"); + System.out.println("PASS"); + } +} + diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestVirtualThreads.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestVirtualThreads.java new file mode 100644 index 00000000000..44cc5f6b722 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestVirtualThreads.java @@ -0,0 +1,686 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @key randomness + * @summary Test that Virtual Threads work well with Value Objects. + * @library /test/lib /compiler/whitebox / + * @enablePreview + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=600 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * TestVirtualThreads + * + * @run main/othervm/timeout=600 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:CompileCommand=compileonly,TestVirtualThreads*::* + * TestVirtualThreads + * @run main/othervm/timeout=600 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:CompileCommand=compileonly,TestVirtualThreads*::test* + * TestVirtualThreads + * @run main/othervm/timeout=600 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:CompileCommand=dontinline,*::dontinline -XX:CompileCommand=compileonly,TestVirtualThreads*::test* -XX:CompileCommand=dontinline,*::test* + * TestVirtualThreads + * @run main/othervm/timeout=600 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:CompileCommand=dontinline,*::dontinline -XX:CompileCommand=compileonly,TestVirtualThreads*::test* -XX:CompileCommand=dontinline,*::*Helper + * TestVirtualThreads + * @run main/othervm/timeout=600 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:CompileCommand=dontinline,*::dontinline -XX:CompileCommand=compileonly,TestVirtualThreads*::test* -XX:CompileCommand=exclude,*::*Helper + * TestVirtualThreads + * @run main/othervm/timeout=600 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xcomp -XX:CompileCommand=compileonly,TestVirtualThreads*::* + * TestVirtualThreads + * @run main/othervm/timeout=600 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xcomp -XX:CompileCommand=compileonly,TestVirtualThreads*::test* + * TestVirtualThreads + * @run main/othervm/timeout=600 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xcomp -XX:CompileCommand=dontinline,*::dontinline -XX:CompileCommand=compileonly,TestVirtualThreads*::test* -XX:CompileCommand=dontinline,*::test* + * TestVirtualThreads + * @run main/othervm/timeout=600 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xcomp -XX:CompileCommand=dontinline,*::dontinline -XX:CompileCommand=compileonly,TestVirtualThreads*::test* -XX:CompileCommand=dontinline,*::*Helper + * TestVirtualThreads + * @run main/othervm/timeout=600 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xcomp -XX:CompileCommand=dontinline,*::dontinline -XX:CompileCommand=compileonly,TestVirtualThreads*::test* -XX:CompileCommand=exclude,*::*Helper + * TestVirtualThreads + * @run main/othervm/timeout=600 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:CompileCommand=dontinline,*::* -XX:CompileCommand=compileonly,TestVirtualThreads*::* + * TestVirtualThreads 250000 + * @run main/othervm/timeout=600 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xcomp -XX:CompileCommand=dontinline,*::* -XX:CompileCommand=compileonly,TestVirtualThreads*::* + * TestVirtualThreads 250000 + **/ + +import java.lang.reflect.Method; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.concurrent.locks.LockSupport; +import java.util.concurrent.CountDownLatch; +import java.util.Random; + +import jdk.test.lib.Asserts; +import jdk.test.lib.Platform; +import jdk.test.lib.Utils; +import jdk.test.whitebox.WhiteBox; + +public class TestVirtualThreads { + static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + static final int COMP_LEVEL_SIMPLE = 1; // C1 + static final int COMP_LEVEL_FULL_OPTIMIZATION = 4; // C2 or JVMCI + static final Random RAND = Utils.getRandomInstance(); + static final int PARK_DURATION = 10; + + static value class SmallValue { + int x1; + int x2; + + public SmallValue(int i) { + this.x1 = i; + this.x2 = i; + } + + public String toString() { + return "x1 = " + x1 + ", x2 = " + x2; + } + + public void verify(String loc, int i) { + if (x1 != i || x2 != i) { + throw new RuntimeException("Incorrect result at " + loc + " for i = " + i + ": " + this); + } + } + + public static void verify(SmallValue val, String loc, int i, boolean useNull) { + if (useNull) { + if (val != null) { + throw new RuntimeException("Incorrect result at " + loc + " for i = " + i + ": " + val); + } + } else { + val.verify(loc, i); + } + } + } + + // Large value class + static value class LargeValue { + int x1; + int x2; + int x3; + int x4; + int x5; + int x6; + int x7; + + public LargeValue(int i) { + this.x1 = i; + this.x2 = i; + this.x3 = i; + this.x4 = i; + this.x5 = i; + this.x6 = i; + this.x7 = i; + } + + public String toString() { + return "x1 = " + x1 + ", x2 = " + x2 + ", x3 = " + x3 + ", x4 = " + x4 + ", x5 = " + x5 + + ", x6 = " + x6 + ", x7 = " + x7; + } + + public void verify(String loc, int i) { + if (x1 != i || x2 != i || x3 != i || x4 != i || x5 != i || + x6 != i || x7 != i) { + throw new RuntimeException("Incorrect result at " + loc + " for i = " + i + ": " + this); + } + } + + public static void verify(LargeValue val, String loc, int i, boolean useNull) { + if (useNull) { + if (val != null) { + throw new RuntimeException("Incorrect result at " + loc + " for i = " + i + ": " + val); + } + } else { + val.verify(loc, i); + } + } + } + + // Large value class with fields of different types + static value class LargeValue2 { + byte x1; + short x2; + int x3; + long x4; + double x5; + boolean x6; + + public LargeValue2(int i) { + this.x1 = (byte)i; + this.x2 = (short)i; + this.x3 = i; + this.x4 = i; + this.x5 = i; + this.x6 = (i % 2) == 0; + } + + public String toString() { + return "x1 = " + x1 + ", x2 = " + x2 + ", x3 = " + x3 + ", x4 = " + x4 + ", x5 = " + x5 + + ", x6 = " + x6; + } + + public void verify(String loc, int i) { + if (x1 != (byte)i || x2 != (short)i || x3 != i || x4 != i || x5 != i || + x6 != ((i % 2) == 0)) { + throw new RuntimeException("Incorrect result at " + loc + " for i = " + i + ": " + this); + } + } + + public static void verify(LargeValue2 val, String loc, int i, boolean useNull) { + if (useNull) { + if (val != null) { + throw new RuntimeException("Incorrect result at " + loc + " for i = " + i + ": " + val); + } + } else { + val.verify(loc, i); + } + } + } + + // Large value class with oops (and different number of fields) that requires stack extension/repair + static value class LargeValueWithOops { + Object x1; + Object x2; + Object x3; + Object x4; + Object x5; + + public LargeValueWithOops(Object obj) { + this.x1 = obj; + this.x2 = obj; + this.x3 = obj; + this.x4 = obj; + this.x5 = obj; + } + + public String toString() { + return "x1 = " + x1 + ", x2 = " + x2 + ", x3 = " + x3 + ", x4 = " + x4 + ", x5 = " + x5; + } + + public void verify(String loc, Object obj) { + if (x1 != obj || x2 != obj || x3 != obj || x4 != obj || x5 != obj) { + throw new RuntimeException("Incorrect result at " + loc + " for obj = " + obj + ": " + this); + } + } + + public static void verify(LargeValueWithOops val, String loc, Object obj, boolean useNull) { + if (useNull) { + if (val != null) { + throw new RuntimeException("Incorrect result at " + loc + " for obj = " + obj + ": " + val); + } + } else { + val.verify(loc, obj); + } + } + } + + public static value class DoubleValue { + double d; + + public DoubleValue(double d) { + this.d = d; + } + + public String toString() { + return "d = " + d; + } + + public void verify(String loc, double d) { + if (this.d != d) { + throw new RuntimeException("Incorrect result at " + loc + " for d = " + d + ": " + this); + } + } + + public static void verify(DoubleValue val, String loc, double d, boolean useNull) { + if (useNull) { + if (val != null) { + throw new RuntimeException("Incorrect result at " + loc + " for d = " + d+ ": " + val); + } + } else { + val.verify(loc, d); + } + } + } + + public static value class DoubleValue2 { + double d1; + double d2; + double d3; + double d4; + double d5; + + public DoubleValue2(double d) { + this.d1 = d; + this.d2 = d + 1; + this.d3 = d + 2; + this.d4 = d + 3; + this.d5 = d + 4; + } + + public String toString() { + return "d1 = " + d1 + ", d2 = " + d2 + ", d3 = " + d3 + ", d4= " + d4 + ", d5= " + d5; + } + + public void verify(String loc, double d) { + if (this.d1 != d || this.d2 != (d+1) || this.d3 != (d+2) || this.d4 != (d+3) || this.d5 != (d+4)) { + throw new RuntimeException("Incorrect result at " + loc + " for d = " + d + ": " + this); + } + } + + public static void verify(DoubleValue2 val, String loc, double d, boolean useNull) { + if (useNull) { + if (val != null) { + throw new RuntimeException("Incorrect result at " + loc + " for d = " + d + ": " + val); + } + } else { + val.verify(loc, d); + } + } + } + + static abstract value class BaseValue { + public abstract void verify(String loc, int i); + }; + + static value class ValueExtendsAbstract extends BaseValue { + int x1; + int x2; + int x3; + int x4; + int x5; + int x6; + int x7; + + public ValueExtendsAbstract(int i) { + this.x1 = i; + this.x2 = i; + this.x3 = i; + this.x4 = i; + this.x5 = i; + this.x6 = i; + this.x7 = i; + } + + public String toString() { + return "x1 = " + x1 + ", x2 = " + x2 + ", x3 = " + x3 + ", x4 = " + x4 + ", x5 = " + x5 + + ", x6 = " + x6 + ", x7 = " + x7; + } + + public void verify(String loc, int i) { + if (x1 != i || x2 != i || x3 != i || x4 != i || x5 != i || + x6 != i || x7 != i) { + throw new RuntimeException("Incorrect result at " + loc + " for i = " + i + ": " + this); + } + } + } + + public static void dontInline() { } + + public static SmallValue testSmall(SmallValue val, int i, boolean useNull, boolean park) { + SmallValue.verify(val, "entry", i, useNull); + if (park) { + LockSupport.parkNanos(PARK_DURATION); + } + SmallValue.verify(val, "exit", i, useNull); + return val; + } + + public static SmallValue testSmallHelper(int i, boolean useNull, boolean park) { + SmallValue val = useNull ? null : new SmallValue(i); + val = testSmall(val, i, useNull, park); + SmallValue.verify(val, "helper", i, useNull); + return val; + } + + public static LargeValue testLarge(LargeValue val, int i, boolean useNull, boolean park) { + LargeValue.verify(val, "entry", i, useNull); + if (park) { + LockSupport.parkNanos(PARK_DURATION); + } + dontInline(); // Prevent C2 from optimizing out below checks + LargeValue.verify(val, "exit", i, useNull); + return val; + } + + public static LargeValue testLargeHelper(int i, boolean useNull, boolean park) { + LargeValue val = useNull ? null : new LargeValue(i); + val = testLarge(val, i, useNull, park); + LargeValue.verify(val, "helper", i, useNull); + return val; + } + + // Version that already has values on the stack even before stack extensions + public static LargeValue testLargeManyArgs(LargeValue val1, LargeValue val2, LargeValue val3, LargeValue val4, int i, boolean useNull, boolean park) { + LargeValue.verify(val1, "entry", i, useNull); + LargeValue.verify(val2, "entry", i + 1, useNull); + LargeValue.verify(val3, "entry", i + 2, useNull); + LargeValue.verify(val4, "entry", i + 3, useNull); + if (park) { + LockSupport.parkNanos(PARK_DURATION); + } + dontInline(); // Prevent C2 from optimizing out below checks + LargeValue.verify(val1, "exit", i, useNull); + LargeValue.verify(val2, "exit", i + 1, useNull); + LargeValue.verify(val3, "exit", i + 2, useNull); + LargeValue.verify(val4, "exit", i + 3, useNull); + return val4; + } + + public static LargeValue testLargeManyArgsHelper(int i, boolean useNull, boolean park) { + LargeValue val1 = useNull ? null : new LargeValue(i); + LargeValue val2 = useNull ? null : new LargeValue(i + 1); + LargeValue val3 = useNull ? null : new LargeValue(i + 2); + LargeValue val4 = useNull ? null : new LargeValue(i + 3); + LargeValue val = testLargeManyArgs(val1, val2, val3, val4, i, useNull, park); + LargeValue.verify(val, "helper", i + 3, useNull); + return val; + } + + public static LargeValue2 testLarge2(LargeValue2 val, int i, boolean useNull, boolean park) { + LargeValue2.verify(val, "entry", i, useNull); + if (park) { + LockSupport.parkNanos(PARK_DURATION); + } + dontInline(); // Prevent C2 from optimizing out below checks + LargeValue2.verify(val, "exit", i, useNull); + return val; + } + + public static LargeValue2 testLarge2Helper(int i, boolean useNull, boolean park) { + LargeValue2 val = useNull ? null : new LargeValue2(i); + val = testLarge2(val, i, useNull, park); + LargeValue2.verify(val, "helper", i, useNull); + return val; + } + + // Version that already has values on the stack even before stack extensions + public static LargeValue2 testLarge2ManyArgs(LargeValue2 val1, LargeValue2 val2, LargeValue2 val3, LargeValue2 val4, int i, boolean useNull, boolean park) { + LargeValue2.verify(val1, "entry", i, useNull); + LargeValue2.verify(val2, "entry", i + 1, useNull); + LargeValue2.verify(val3, "entry", i + 2, useNull); + LargeValue2.verify(val4, "entry", i + 3, useNull); + if (park) { + LockSupport.parkNanos(PARK_DURATION); + } + dontInline(); // Prevent C2 from optimizing out below checks + LargeValue2.verify(val1, "exit", i, useNull); + LargeValue2.verify(val2, "exit", i + 1, useNull); + LargeValue2.verify(val3, "exit", i + 2, useNull); + LargeValue2.verify(val4, "exit", i + 3, useNull); + return val4; + } + + public static LargeValue2 testLarge2ManyArgsHelper(int i, boolean useNull, boolean park) { + LargeValue2 val1 = useNull ? null : new LargeValue2(i); + LargeValue2 val2 = useNull ? null : new LargeValue2(i + 1); + LargeValue2 val3 = useNull ? null : new LargeValue2(i + 2); + LargeValue2 val4 = useNull ? null : new LargeValue2(i + 3); + LargeValue2 val = testLarge2ManyArgs(val1, val2, val3, val4, i, useNull, park); + return val; + } + + public static ValueExtendsAbstract testExtendsAbstractHelper(int i, boolean park) { + ValueExtendsAbstract val1 = new ValueExtendsAbstract(i); + ValueExtendsAbstract val2 = new ValueExtendsAbstract(i + 1); + ValueExtendsAbstract val3 = new ValueExtendsAbstract(i + 2); + ValueExtendsAbstract val4 = new ValueExtendsAbstract(i + 3); + + val1.verify("entry", i); + val2.verify("entry", i + 1); + val3.verify("entry", i + 2); + val4.verify("entry", i + 3); + if (park) { + LockSupport.parkNanos(PARK_DURATION); + } + dontInline(); // Prevent C2 from optimizing out below checks + val1.verify("exit", i); + val2.verify("exit", i + 1); + val3.verify("exit", i + 2); + val4.verify("exit", i + 3); + return val4; + } + + public static LargeValueWithOops testLargeValueWithOops(LargeValueWithOops val, Object obj, boolean useNull, boolean park) { + LargeValueWithOops.verify(val, "entry", obj, useNull); + if (park) { + LockSupport.parkNanos(PARK_DURATION); + } + dontInline(); // Prevent C2 from optimizing out below checks + LargeValueWithOops.verify(val, "exit", obj, useNull); + return val; + } + + public static LargeValueWithOops testLargeValueWithOopsHelper(Object obj, boolean useNull, boolean park) { + LargeValueWithOops val = useNull ? null : new LargeValueWithOops(obj); + val = testLargeValueWithOops(val, obj, useNull, park); + LargeValueWithOops.verify(val, "helper", obj, useNull); + return val; + } + + // Version that already has values on the stack even before stack extensions + public static LargeValueWithOops testLargeValueWithOops2(LargeValueWithOops val1, LargeValueWithOops val2, LargeValueWithOops val3, LargeValueWithOops val4, LargeValueWithOops val5, Object obj, boolean useNull, boolean park) { + LargeValueWithOops.verify(val1, "entry", obj, useNull); + LargeValueWithOops.verify(val2, "entry", obj, useNull); + LargeValueWithOops.verify(val3, "entry", obj, useNull); + LargeValueWithOops.verify(val4, "entry", obj, useNull); + LargeValueWithOops.verify(val5, "entry", obj, useNull); + if (park) { + LockSupport.parkNanos(PARK_DURATION); + } + dontInline(); // Prevent C2 from optimizing out below checks + LargeValueWithOops.verify(val1, "exit", obj, useNull); + LargeValueWithOops.verify(val2, "exit", obj, useNull); + LargeValueWithOops.verify(val3, "exit", obj, useNull); + LargeValueWithOops.verify(val4, "exit", obj, useNull); + LargeValueWithOops.verify(val5, "exit", obj, useNull); + return val5; + } + + public static LargeValueWithOops testLargeValueWithOops2Helper(Object obj, boolean useNull, boolean park) { + LargeValueWithOops val1 = useNull ? null : new LargeValueWithOops(obj); + LargeValueWithOops val2 = useNull ? null : new LargeValueWithOops(obj); + LargeValueWithOops val3 = useNull ? null : new LargeValueWithOops(obj); + LargeValueWithOops val4 = useNull ? null : new LargeValueWithOops(obj); + LargeValueWithOops val5 = useNull ? null : new LargeValueWithOops(obj); + LargeValueWithOops val = testLargeValueWithOops2(val1, val2, val3, val4, val5, obj, useNull, park); + LargeValueWithOops.verify(val, "helper", obj, useNull); + return val; + } + + // Pass via fields to not affect number of arguments + static double testDoubleValueDP; + static boolean testDoubleValueUseNullP; + static boolean testDoubleValueParkP; + static double testDoubleValueDV; + static boolean testDoubleValueUseNullV; + static boolean testDoubleValueParkV; + + // This method needs less stack space when scalarized because (some of) the arguments can then be passed in floating point registers + public static DoubleValue testDoubleValue(DoubleValue val1, DoubleValue val2, DoubleValue val3, DoubleValue val4, DoubleValue val5, DoubleValue val6, DoubleValue val7) { + boolean isVirtual = Thread.currentThread().isVirtual(); + double d = isVirtual ? testDoubleValueDV : testDoubleValueDP; + boolean useNull = isVirtual ? testDoubleValueUseNullV : testDoubleValueUseNullP; + boolean park = isVirtual ? testDoubleValueParkV : testDoubleValueParkP; + + DoubleValue.verify(val1, "entry", d, useNull); + DoubleValue.verify(val2, "entry", d + 1, useNull); + DoubleValue.verify(val3, "entry", d + 2, useNull); + DoubleValue.verify(val4, "entry", d + 3, useNull); + DoubleValue.verify(val5, "entry", d + 4, useNull); + DoubleValue.verify(val6, "entry", d + 4, useNull); + DoubleValue.verify(val7, "entry", d + 4, useNull); + if (park) { + LockSupport.parkNanos(PARK_DURATION); + } + DoubleValue.verify(val1, "exit", d, useNull); + DoubleValue.verify(val2, "exit", d + 1, useNull); + DoubleValue.verify(val3, "exit", d + 2, useNull); + DoubleValue.verify(val4, "exit", d + 3, useNull); + DoubleValue.verify(val5, "exit", d + 4, useNull); + DoubleValue.verify(val6, "exit", d + 4, useNull); + DoubleValue.verify(val7, "exit", d + 4, useNull); + return val1; + } + + public static DoubleValue testDoubleValueHelper(double d, boolean useNull, boolean park) { + if (Thread.currentThread().isVirtual()) { + testDoubleValueDV = d; + testDoubleValueUseNullV = useNull; + testDoubleValueParkV = park; + } else { + testDoubleValueDP = d; + testDoubleValueUseNullP = useNull; + testDoubleValueParkP = park; + } + + DoubleValue val1 = useNull ? null : new DoubleValue(d); + DoubleValue val2 = useNull ? null : new DoubleValue(d + 1); + DoubleValue val3 = useNull ? null : new DoubleValue(d + 2); + DoubleValue val4 = useNull ? null : new DoubleValue(d + 3); + DoubleValue val5 = useNull ? null : new DoubleValue(d + 4); + DoubleValue val6 = useNull ? null : new DoubleValue(d + 4); + DoubleValue val7 = useNull ? null : new DoubleValue(d + 4); + val1 = testDoubleValue(val1, val2, val3, val4, val5, val6, val7); + DoubleValue.verify(val1, "helper", d, useNull); + return val1; + } + + public static DoubleValue2 recurseTestDoubleValue2(double d, boolean useNull, boolean park, int depth) { + if (depth > 0) { + DoubleValue2 val = recurseTestDoubleValue2(d, useNull, park, depth - 1); + DoubleValue2.verify(val, "entry", d, useNull); + dontInline(); // Prevent C2 from optimizing out below checks + DoubleValue2.verify(val, "exit", d, useNull); + return val; + } else { + if (park) { + LockSupport.parkNanos(PARK_DURATION); + } + return useNull ? null : new DoubleValue2(d); + } + } + + public static DoubleValue2 testDoubleValue2Helper(double d, boolean useNull, boolean park) { + DoubleValue2 val = recurseTestDoubleValue2(d, useNull, park, 4); + DoubleValue2.verify(val, "helper", d, useNull); + return val; + } + + static class GarbageProducerThread extends Thread { + public void run() { + for (;;) { + // Produce some garbage and then let the GC do its work + Object[] arrays = new Object[1024]; + for (int i = 0; i < arrays.length; i++) { + arrays[i] = new int[1024]; + } + System.gc(); + } + } + } + + public static void startTest(CountDownLatch cdl, Thread.Builder builder, int iterations) { + builder.start(() -> { + try { + // Trigger compilation + boolean isVirtual = Thread.currentThread().isVirtual(); + for (int i = 0; i < iterations; i++) { + boolean park = (i % 1000) == 0; + boolean useNull = RAND.nextBoolean(); + Object val = useNull ? null : new SmallValue(i); + SmallValue.verify(testSmallHelper(i, useNull, park), "return", i, useNull); + LargeValue.verify(testLargeHelper(i, useNull, park), "return", i, useNull); + LargeValue.verify(testLargeManyArgsHelper(i, useNull, park), "return", i + 3, useNull); + LargeValue2.verify(testLarge2Helper(i, useNull, park), "return", i, useNull); + LargeValue2.verify(testLarge2ManyArgsHelper(i, useNull, park), "return", i + 3, useNull); + testExtendsAbstractHelper(i, park).verify("return", i + 3); + LargeValueWithOops.verify(testLargeValueWithOopsHelper(val, useNull, park), "return", val, useNull); + LargeValueWithOops.verify(testLargeValueWithOops2Helper(val, useNull, park), "return", val, useNull); + DoubleValue.verify(testDoubleValueHelper(i, useNull, park), "return", i, useNull); + DoubleValue2.verify(testDoubleValue2Helper(i, useNull, park), "return", i, useNull); + if (i % 1000 == 0) { + System.out.format("%s => %s %d of %d%n", Instant.now(), isVirtual ? "Virtual: " : "Platform:", i, iterations); + } + } + cdl.countDown(); + } catch (Exception e) { + System.out.println("Exception thrown: " + e); + e.printStackTrace(System.out); + System.exit(1); + } + }); + } + + public static void main(String[] args) throws Exception { + // Sometimes, exclude some methods from compilation with C1 and/or C2 to stress test the calling convention + if (Utils.getRandomInstance().nextBoolean()) { + ArrayList methods = new ArrayList(); + Collections.addAll(methods, SmallValue.class.getDeclaredMethods()); + Collections.addAll(methods, LargeValue.class.getDeclaredMethods()); + Collections.addAll(methods, LargeValue2.class.getDeclaredMethods()); + Collections.addAll(methods, LargeValueWithOops.class.getDeclaredMethods()); + Collections.addAll(methods, DoubleValue.class.getDeclaredMethods()); + Collections.addAll(methods, TestVirtualThreads.class.getDeclaredMethods()); + System.out.println("Excluding methods from C1 compilation:"); + for (Method m : methods) { + if (Utils.getRandomInstance().nextBoolean()) { + System.out.println(m); + WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_SIMPLE, false); + } + } + System.out.println("Excluding methods from C2 compilation:"); + for (Method m : methods) { + if (Utils.getRandomInstance().nextBoolean()) { + System.out.println(m); + WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_FULL_OPTIMIZATION, false); + } + } + } + + // Start another thread that does some allocations and calls System.gc() + // to trigger GCs while virtual threads are parked. + Thread garbage_producer = new GarbageProducerThread(); + garbage_producer.setDaemon(true); + garbage_producer.start(); + + int iterations = args.length > 0 ? Integer.parseInt(args[0]) : 300_000; + if (Platform.isDebugBuild()) { + iterations /= 4; + } + CountDownLatch cdlPlatform = new CountDownLatch(1); + CountDownLatch cdlVirtual = new CountDownLatch(1); + startTest(cdlPlatform, Thread.ofPlatform(), iterations); + startTest(cdlVirtual, Thread.ofVirtual(), iterations); + cdlPlatform.await(); + cdlVirtual.await(); + } +} + diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestWithSpeculativeTypes.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestWithSpeculativeTypes.java new file mode 100644 index 00000000000..7f3a4b8db43 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestWithSpeculativeTypes.java @@ -0,0 +1,65 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8280440 + * @summary Test that speculative types are properly handled by scalarization. + * @library /test/lib + * @enablePreview + * @run main/othervm -XX:CompileCommand=dontinline,TestWithSpeculativeTypes::* + * -XX:TypeProfileLevel=222 -XX:-TieredCompilation -Xbatch + * TestWithSpeculativeTypes + */ + +import jdk.test.lib.Asserts; + +public class TestWithSpeculativeTypes { + + static value class MyValue { + int x = 0; + } + + static MyValue getNull() { + return null; + } + + // Return value has speculative type NULL + static boolean test1() { + return getNull() == null; + } + + // Argument has speculative type NULL + static boolean test2(MyValue vt) { + return vt == null; + } + + public static void main(String[] args) { + // Make sure class is loaded + MyValue val = new MyValue(); + for (int i = 0; i < 100_000; ++i) { + Asserts.assertTrue(test1()); + Asserts.assertTrue(test2(null)); + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestWrongFlatArrayCopyStubWithZGC.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestWrongFlatArrayCopyStubWithZGC.java new file mode 100644 index 00000000000..eefa1e2220c --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestWrongFlatArrayCopyStubWithZGC.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @requires vm.gc.Z + * @bug 8313667 + * @summary Test that GenZ uses correct array copy stub for flat value class arrays when expanding ArrayCopyNode. + * @library /test/lib + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @run main/othervm -Xbatch -XX:+UseZGC + * -XX:CompileCommand=exclude,compiler.valhalla.inlinetypes.TestWrongFlatArrayCopyStubWithZGC::check + * -XX:CompileCommand=dontinline,compiler.valhalla.inlinetypes.TestWrongFlatArrayCopyStubWithZGC::test* + * compiler.valhalla.inlinetypes.TestWrongFlatArrayCopyStubWithZGC + */ + +package compiler.valhalla.inlinetypes; + +import jdk.test.lib.Asserts; +import jdk.test.lib.Utils; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; + +public class TestWrongFlatArrayCopyStubWithZGC { + + public static void main(String[] args) { + ValueWithLong[] arrWithLong = (ValueWithLong[])ValueClass.newNullRestrictedNonAtomicArray(ValueWithLong.class, 3, new ValueWithLong(0)); + arrWithLong[0] = new ValueWithLong(0x408BE000000fffffL); + arrWithLong[1] = new ValueWithLong(0x408BE0000000000L); + long randomValue = Utils.getRandomInstance().nextLong(); + arrWithLong[2] = new ValueWithLong(randomValue); + + for (int i = 0; i < 10000; i++) { + ValueWithLong[] result = testLong(arrWithLong); + check(result[0].l, 0x408BE000000fffffL); + check(result[1].l, 0x408BE0000000000L); + check(result[2].l, randomValue); + } + + ValueWithOop[] arrWithOop = (ValueWithOop[])ValueClass.newNullRestrictedNonAtomicArray(ValueWithOop.class, 2, new ValueWithOop()); + for (int i = 0; i < 10000; i++) { + testOop(arrWithOop); + } + } + + static void check(long result, long expected) { + Asserts.assertEQ(result, expected); + } + + static ValueWithLong[] testLong(ValueWithLong[] arr) { + return arr.clone(); + } + + static ValueWithOop[] testOop(ValueWithOop[] arr) { + return arr.clone(); + } +} + +@LooselyConsistentValue +value class ValueWithLong { + long l; + + public ValueWithLong(long l) { + this.l = l; + } +} + +@LooselyConsistentValue +value class ValueWithOop { + Object v; + + public ValueWithOop() { + this.v = new Object(); + } +} + diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/bootstrap/InstallBootstrapClasses.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/bootstrap/InstallBootstrapClasses.java new file mode 100644 index 00000000000..901166107a1 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/bootstrap/InstallBootstrapClasses.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + +import jdk.test.lib.Utils; + +// Copy classes into a separate folder to put them on the bootclasspath +public class InstallBootstrapClasses { + + private static void copyClass(String name) throws IOException { + Path source = Path.of(Utils.TEST_CLASSES).resolve(name); + Path dest = Path.of("boot"); + Path target = dest.resolve(name); + Files.createDirectories(dest); + Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); + } + + public static void main(String[] args) throws IOException { + copyClass(ValueOnBootclasspath.class.getSimpleName() + ".class"); + copyClass(MyClass.class.getSimpleName() + ".class"); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/bootstrap/TestBootClassloader.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/bootstrap/TestBootClassloader.java new file mode 100644 index 00000000000..19bc1d0914e --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/bootstrap/TestBootClassloader.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.lang.reflect.Method; +import jdk.test.lib.Asserts; +import jdk.test.whitebox.WhiteBox; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +/* + * @test + * @key randomness + * @bug 8280006 + * @summary Test that field flattening works as expected if value classes of + * holder and field were loaded by different class loaders (bootstrap + app). + * @library /test/lib / + * @requires (os.simpleArch == "x64" | os.simpleArch == "aarch64") + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @compile ValueOnBootclasspath.java InstallBootstrapClasses.java TestBootClassloader.java + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm InstallBootstrapClasses + * @run main/othervm -Xbootclasspath/a:boot -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch -XX:-TieredCompilation -XX:CompileCommand=compileonly,TestBootClassloader::test* + * -XX:CompileCommand=inline,*::get* TestBootClassloader + */ + +public class TestBootClassloader { + private static final WhiteBox WB = WhiteBox.getWhiteBox(); + private static final int COMP_LEVEL_FULL_OPTIMIZATION = 4; + + @LooselyConsistentValue + static value class Wrapper1 { + @Strict + @NullRestricted + ValueOnBootclasspath val; // Type will be loaded by boot classloader + + public Wrapper1(ValueOnBootclasspath val) { + this.val = val; + } + + Object get() { + return val.get(); + } + } + + @LooselyConsistentValue + static value class Wrapper2 { + @Strict + @NullRestricted + Wrapper1 val; + + public Wrapper2(Wrapper1 val) { + this.val = val; + } + + Object get() { + return val.get(); + } + } + + static Object test1(Wrapper1 w) { + return w.get(); + } + + static Object test2(Wrapper2 w) { + return w.get(); + } + + public static void main(String[] args) throws Exception { + Wrapper1 wrapper1 = new Wrapper1(new ValueOnBootclasspath()); + Wrapper2 wrapper2 = new Wrapper2(wrapper1); + for (int i = 0; i < 50_000; ++i) { + test1(wrapper1); + test2(wrapper2); + } + Method method = TestBootClassloader.class.getDeclaredMethod("test1", Wrapper1.class); + Asserts.assertTrue(WB.isMethodCompilable(method, COMP_LEVEL_FULL_OPTIMIZATION, false), "Test1 method not compilable"); + Asserts.assertTrue(WB.isMethodCompiled(method), "Test1 method not compiled"); + + method = TestBootClassloader.class.getDeclaredMethod("test2", Wrapper2.class); + Asserts.assertTrue(WB.isMethodCompilable(method, COMP_LEVEL_FULL_OPTIMIZATION, false), "Test2 method not compilable"); + Asserts.assertTrue(WB.isMethodCompiled(method), "Test2 method not compiled"); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/bootstrap/ValueOnBootclasspath.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/bootstrap/ValueOnBootclasspath.java new file mode 100644 index 00000000000..053859fefa1 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/bootstrap/ValueOnBootclasspath.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import jdk.internal.vm.annotation.LooselyConsistentValue; + +class MyClass { + +} + +// Loaded by boot classloader +@LooselyConsistentValue +public value class ValueOnBootclasspath { + private MyClass field = new MyClass(); + + private MyClass getField() { + return field; + } + + public Object get() { + return getField(); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/hack/GetUnresolvedInlineFieldWrongSignature.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/hack/GetUnresolvedInlineFieldWrongSignature.java new file mode 100644 index 00000000000..4e0d73f2e84 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/hack/GetUnresolvedInlineFieldWrongSignature.java @@ -0,0 +1,108 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.valhalla.inlinetypes; + +class TestUnloadedInlineTypeField { + static class MyValue3 { + int foo; + } + + static class MyValue3Holder { + MyValue3 v; + } + + static class MyValue10 { + int foo; + } + + static class MyValue10Holder { + MyValue10 v1; + MyValue10 v2; + } + + static class MyValue13 { + int foo; + } + + static class MyValue13Holder { + MyValue13 v; + } + + static class MyValue14 { + int foo; + } + + static class MyValue14Holder { + MyValue14 v; + } + + static class MyValue15 { + int foo; + } + + static class MyValue15Holder { + MyValue15 v; + } + + static value class MyValue16 { + int foo = 42; + } + + static value class MyValue17 { + int foo = 42; + } +} + +class GetUnresolvedInlineFieldWrongSignature { + + static void test13(Object holder) { + if (holder != null) { + // Don't use MyValue13Holder in the signature, it might trigger class loading + ((TestUnloadedInlineTypeField.MyValue13Holder)holder).v = new TestUnloadedInlineTypeField.MyValue13(); + } + } + + static void test15(Object holder) { + if (holder != null) { + // Don't use MyValue15Holder in the signature, it might trigger class loading + ((TestUnloadedInlineTypeField.MyValue15Holder)holder).v = new TestUnloadedInlineTypeField.MyValue15(); + } + } + + static Object test16(boolean warmup) { + if (!warmup) { + return new TestUnloadedInlineTypeField.MyValue16(); + } else { + return null; + } + } + + static Object test17(boolean warmup) { + if (!warmup) { + return new TestUnloadedInlineTypeField.MyValue17(); + } else { + return null; + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/libTestJNICalls.c b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/libTestJNICalls.c new file mode 100644 index 00000000000..4bc451bb973 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/libTestJNICalls.c @@ -0,0 +1,45 @@ +/* + * 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 + * 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 + +JNIEXPORT jobject JNICALL +Java_compiler_valhalla_inlinetypes_TestJNICalls_testMethod1(JNIEnv *env, jobject receiver, jobject vt) { + jclass cls = (*env)->GetObjectClass(env, receiver); + jmethodID mid = (*env)->GetMethodID(env, cls, "test1", "(Lcompiler/valhalla/inlinetypes/MyValue1;Z)Lcompiler/valhalla/inlinetypes/MyValue1;"); + return (*env)->CallObjectMethod(env, receiver, mid, vt, JNI_TRUE); +} + +JNIEXPORT jlong JNICALL +Java_compiler_valhalla_inlinetypes_TestJNICalls_testMethod2(JNIEnv *env, jobject receiver, jobject vt) { + jclass cls = (*env)->GetObjectClass(env, vt); + jmethodID mid = (*env)->GetMethodID(env, cls, "hash", "()J"); + return (*env)->CallLongMethod(env, vt, mid); +} + +JNIEXPORT jint JNICALL +Java_compiler_valhalla_inlinetypes_TestJNICalls_00024MyValueWithNative_testMethod3(JNIEnv *env, jobject receiver) { + jclass cls = (*env)->GetObjectClass(env, receiver); + jfieldID fid = (*env)->GetFieldID(env, cls, "x", "I"); + return (*env)->GetIntField(env, receiver, fid); +} diff --git a/test/hotspot/jtreg/gc/TestBigObj.java b/test/hotspot/jtreg/gc/TestBigObj.java index 224f0bd39fb..71802027e27 100644 --- a/test/hotspot/jtreg/gc/TestBigObj.java +++ b/test/hotspot/jtreg/gc/TestBigObj.java @@ -27,6 +27,7 @@ * @test TestBigObj * @bug 6845368 * @summary ensure gc updates references > 64K bytes from the start of the obj + * @compile -XDnoTopInterfaceInjection TestBigObj.java * @run main/othervm/timeout=720 -Xmx256m -verbose:gc gc.TestBigObj */ diff --git a/test/hotspot/jtreg/gc/stress/gcbasher/Bytecode.java b/test/hotspot/jtreg/gc/stress/gcbasher/Bytecode.java index 724b6953046..53704e7dd47 100644 --- a/test/hotspot/jtreg/gc/stress/gcbasher/Bytecode.java +++ b/test/hotspot/jtreg/gc/stress/gcbasher/Bytecode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved. * 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,8 @@ class Bytecode { public static final int INSTANCEOF = 193; public static final int MULTIANEWARRAY = 197; public static final int WIDE = 196; + public static final int ACONSTINIT = 203; + public static final int WITHFIELD = 204; private static final int lengths[] = { 1, @@ -246,7 +248,9 @@ class Bytecode { 3, 5, 5, - 1 + 1, + 3, + 3 }; public static int getLength(int bc) throws IllegalArgumentException { diff --git a/test/hotspot/jtreg/runtime/ClassFile/ClassAccessFlagsRawTest.java b/test/hotspot/jtreg/runtime/ClassFile/ClassAccessFlagsRawTest.java index ee3531868e6..09e117a9e8f 100644 --- a/test/hotspot/jtreg/runtime/ClassFile/ClassAccessFlagsRawTest.java +++ b/test/hotspot/jtreg/runtime/ClassFile/ClassAccessFlagsRawTest.java @@ -24,9 +24,10 @@ /** * @test - * @bug 8291360 + * @bug 8291360 8293448 * @summary Test getting a class's raw access flags using java.lang.Class API * @modules java.base/java.lang:open + * @enablePreview * @compile classAccessFlagsRaw.jcod * @run main/othervm ClassAccessFlagsRawTest */ @@ -52,8 +53,9 @@ public static void main(String argv[]) throws Throwable { m = cl.getDeclaredMethod("getClassFileAccessFlags", new Class[0]); m.setAccessible(true); - testIt("SUPERset", 0x21); // ACC_SUPER 0x20 + ACC_PUBLIC 0x1 - testIt("SUPERnotset", Modifier.PUBLIC); + testIt("SUPERset", Modifier.PUBLIC | Modifier.IDENTITY); + // Because of the repurposing of ACC_SUPER into ACC_IDENTITY by JEP 401, the VM now fixes missing ACC_IDENTITY flags in old class files + testIt("SUPERnotset", Modifier.PUBLIC | Modifier.IDENTITY); // Test that primitive should return ACC_ABSTRACT | ACC_FINAL | ACC_PUBLIC. int[] arr = new int[3]; @@ -75,9 +77,9 @@ public static void main(String argv[]) throws Throwable { // Test that the modifier flags return element type flags. flags = (int)arr.getClass().getModifiers(); - if (flags != (Modifier.ABSTRACT | Modifier.FINAL | Modifier.PUBLIC)) { + if (flags != (Modifier.ABSTRACT | Modifier.FINAL | Modifier.PUBLIC | Modifier.IDENTITY)) { throw new RuntimeException( - "expected 0x411, got 0x" + Integer.toHexString(flags) + " for primitive type"); + "expected 0x431, got 0x" + Integer.toHexString(flags) + " for primitive type"); } // Test that AccessFlags set will return element type access flags. @@ -96,9 +98,17 @@ public static void main(String argv[]) throws Throwable { // Test object array component type. flags = (int)m.invoke((new SUPERnotset[2]).getClass().getComponentType()); - if (flags != Modifier.PUBLIC) { + // Because of the repurposing of ACC_SUPER into ACC_IDENTITY by JEP 401, the VM now fixes missing ACC_IDENTITY flags in old class files + if (flags != (Modifier.PUBLIC | Modifier.IDENTITY)) { throw new RuntimeException( "expected 0x1, got 0x" + Integer.toHexString(flags) + " for object array"); } + + // test multi-dimensional object array. should return flags of component. + flags = (int)m.invoke((new SUPERnotset[4][2]).getClass()); + if (flags != 0) { + throw new RuntimeException( + "expected 0x0, got 0x" + Integer.toHexString(flags) + " for object array"); + } } } diff --git a/test/hotspot/jtreg/runtime/ErrorHandling/AccessZeroNKlassHitsProtectionZone.java b/test/hotspot/jtreg/runtime/ErrorHandling/AccessZeroNKlassHitsProtectionZone.java index 0e36fdce7d8..67b1ffd4a3b 100644 --- a/test/hotspot/jtreg/runtime/ErrorHandling/AccessZeroNKlassHitsProtectionZone.java +++ b/test/hotspot/jtreg/runtime/ErrorHandling/AccessZeroNKlassHitsProtectionZone.java @@ -206,8 +206,9 @@ public static void main(String[] args) throws Exception { case runwb -> WhiteBox.getWhiteBox().decodeNKlassAndAccessKlass(0); case no_coh_no_cds -> run_test(false, false); case no_coh_cds -> run_test(false, true); - case coh_no_cds -> run_test(true, false); - case coh_cds -> run_test(true, true); + // TODO 8348568 Re-enable + // case coh_no_cds -> run_test(true, false); + // case coh_cds -> run_test(true, true); } } } diff --git a/test/hotspot/jtreg/runtime/ErrorHandling/MachCodeFramesInErrorFile.java b/test/hotspot/jtreg/runtime/ErrorHandling/MachCodeFramesInErrorFile.java index 5717a576e65..ff9f99d6e17 100644 --- a/test/hotspot/jtreg/runtime/ErrorHandling/MachCodeFramesInErrorFile.java +++ b/test/hotspot/jtreg/runtime/ErrorHandling/MachCodeFramesInErrorFile.java @@ -163,7 +163,7 @@ private static void run(boolean crashInJava) throws Exception { return; } - Matcher matcher = Pattern.compile("\\[MachCode\\]\\s*\\[Verified Entry Point\\]\\s* # \\{method\\} \\{[^}]*\\} '([^']+)' '([^']+)' in '([^']+)'", Pattern.DOTALL).matcher(hsErr); + Matcher matcher = Pattern.compile("\\[MachCode\\]\\s[^{]+\\{method\\} \\{[^}]*\\} '([^']+)' '([^']+)' in '([^']+)'", Pattern.DOTALL).matcher(hsErr); List machCodeHeaders = matcher.results().map(mr -> String.format("'%s' '%s' in '%s'", mr.group(1), mr.group(2), mr.group(3))).collect(Collectors.toList()); int minExpectedMachCodeSections = Math.max(1, compiledJavaFrames); if (machCodeHeaders.size() < minExpectedMachCodeSections) { diff --git a/test/hotspot/jtreg/runtime/ErrorHandling/UncaughtNativeExceptionTest.java b/test/hotspot/jtreg/runtime/ErrorHandling/UncaughtNativeExceptionTest.java index 14aca26f39f..bf17148b254 100644 --- a/test/hotspot/jtreg/runtime/ErrorHandling/UncaughtNativeExceptionTest.java +++ b/test/hotspot/jtreg/runtime/ErrorHandling/UncaughtNativeExceptionTest.java @@ -22,7 +22,7 @@ */ /* - * @test id + * @test * @enablePreview * @requires os.family == "windows" * @library /test/lib @@ -57,6 +57,7 @@ public void testNativeExceptionReporting() throws Exception { OutputAnalyzer output = ProcessTools.executeTestJava( // executeTestJava doesn't seem to forward 'java.library.path' "-Djava.library.path=" + System.getProperty("java.library.path"), + "--enable-preview", Crasher.class.getName()); File hsErrFile = HsErrFileUtils.openHsErrFileFromOutput(output); diff --git a/test/hotspot/jtreg/runtime/cds/appcds/ArchiveRelocationTest.java b/test/hotspot/jtreg/runtime/cds/appcds/ArchiveRelocationTest.java index b433458a059..baa519a42ae 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/ArchiveRelocationTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/ArchiveRelocationTest.java @@ -71,12 +71,12 @@ static void test(boolean run_reloc) throws Exception { String relocMsg2 = "Try to map archive(s) at an alternative address"; OutputAnalyzer out = TestCommon.dump(appJar, - TestCommon.list(mainClass), - unlockArg, logArg, nmtArg); + TestCommon.list(mainClass), + unlockArg, logArg, nmtArg); out.shouldContain("Relocating archive from"); TestCommon.run("-cp", appJar, unlockArg, runRelocArg, logArg, mainClass) - .assertNormalExit(output -> { + .assertNormalExit(output -> { if (run_reloc) { if (!output.contains(relocMsg1) && !output.contains(relocMsg2)) { throw new RuntimeException("Relocation messages \"" + relocMsg1 + diff --git a/test/hotspot/jtreg/runtime/cds/appcds/HelloInlineClassTest.java b/test/hotspot/jtreg/runtime/cds/appcds/HelloInlineClassTest.java new file mode 100644 index 00000000000..a1215023482 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/HelloInlineClassTest.java @@ -0,0 +1,84 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary Hello World test for using inline classes with CDS + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @compile test-classes/HelloInlineClassApp.java + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar hello_inline.jar + * HelloInlineClassApp HelloInlineClassApp$Point + * HelloInlineClassApp$Rectangle HelloInlineClassApp$ValueRecord + * @run main/othervm HelloInlineClassTest + */ + +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; +public class HelloInlineClassTest { + public static void main(String[] args) throws Exception { + String appJar = ClassFileInstaller.getJarPath("hello_inline.jar"); + String mainClass = "HelloInlineClassApp"; + OutputAnalyzer output = + TestCommon.dump(appJar, TestCommon.list(mainClass, + "HelloInlineClassApp$Point", + "HelloInlineClassApp$Rectangle"), + "--enable-preview"); + output.shouldHaveExitValue(0); + + TestCommon.run("--enable-preview", + "-Xint", "-cp", appJar, mainClass) + .assertNormalExit(); + + TestCommon.run("--enable-preview", + "-cp", appJar, mainClass) + .assertNormalExit(); + + String compFlag = "-XX:CompileCommand=compileonly,HelloInlineClassApp*::*"; + + TestCommon.run("--enable-preview", + "-Xcomp", compFlag, + "-cp", appJar, mainClass) + .assertNormalExit(); + + TestCommon.run("--enable-preview", + "-Xcomp", compFlag, + "-XX:TieredStopAtLevel=1", + "-XX:+TieredCompilation", + "-XX:-Inline", + "-cp", appJar, mainClass) + .assertNormalExit(); + + TestCommon.run("--enable-preview", + "-Xcomp", compFlag, + "-XX:TieredStopAtLevel=4", + "-XX:-TieredCompilation", + "-XX:-Inline", + "-cp", appJar, mainClass) + .assertNormalExit(); + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/RewriteBytecodesInlineTest.java b/test/hotspot/jtreg/runtime/cds/appcds/RewriteBytecodesInlineTest.java new file mode 100644 index 00000000000..7d397921753 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/RewriteBytecodesInlineTest.java @@ -0,0 +1,65 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary Use Lookup.defineClass() to load a class with rewritten bytecode. Make sure + * the archived class with the same name is not loaded. + * @requires vm.cds + * @library /test/lib + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @compile test-classes/RewriteBytecodesInline.java test-classes/Util.java test-classes/Point.java test-classes/WithInlinedField.java RewriteBytecodesInlineTest.java + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm RewriteBytecodesInlineTest + */ + +import java.io.File; +import jdk.test.lib.process.OutputAnalyzer; + +public class RewriteBytecodesInlineTest { + public static void main(String[] args) throws Exception { + String wbJar = JarBuilder.build(true, "WhiteBox", "jdk/test/whitebox/WhiteBox"); + String use_whitebox_jar = "-Xbootclasspath/a:" + wbJar; + + String appJar = JarBuilder.build("dynamic_define", "RewriteBytecodesInline", "Util", "Point", "WithInlinedField"); + String superClsFile = (new File(System.getProperty("test.classes", "."), "Point.class")).getPath(); + + TestCommon.dump(appJar, TestCommon.list("RewriteBytecodesInline", "Point", "WithInlinedField"), + // command-line arguments ... + use_whitebox_jar, + "--enable-preview"); + + OutputAnalyzer output = TestCommon.exec(appJar, + // command-line arguments ... + "--enable-preview", + use_whitebox_jar, + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+WhiteBoxAPI", + "RewriteBytecodesInline", superClsFile); + TestCommon.checkExec(output); + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/condy/CondyHello.jasm b/test/hotspot/jtreg/runtime/cds/appcds/condy/CondyHello.jasm index ed42f13de3e..57c3c14f4b7 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/condy/CondyHello.jasm +++ b/test/hotspot/jtreg/runtime/cds/appcds/condy/CondyHello.jasm @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. * 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,7 +25,7 @@ * This test uses ldc to load an integer value via a condy bootstrap method. */ -class CondyHello +super class CondyHello version 55:0 { diff --git a/test/hotspot/jtreg/runtime/cds/appcds/customLoader/ClassListFormatE.java b/test/hotspot/jtreg/runtime/cds/appcds/customLoader/ClassListFormatE.java index 2beb39d0b39..36382d729df 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/customLoader/ClassListFormatE.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/customLoader/ClassListFormatE.java @@ -76,7 +76,7 @@ appJar, classlist( "Hello", "java/lang/Object id: 1", "CustomInterface2_ia id: 2 super: 1 source: " + customJarPath, - "CustomLoadee id: 2 super: 1 interfaces: 2 source: " + customJarPath + "CustomLoadee id: 3 super: 1 interfaces: 2 source: " + customJarPath ), "The number of interfaces (1) specified in class list does not match the class file (0)"); diff --git a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/DynamicArchiveRelocationTest.java b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/DynamicArchiveRelocationTest.java index 085a380b203..26abad36bb7 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/DynamicArchiveRelocationTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/DynamicArchiveRelocationTest.java @@ -30,9 +30,12 @@ * @comment JDK-8231610 Relocate the CDS archive if it cannot be mapped to the requested address * @bug 8231610 * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds /test/hotspot/jtreg/runtime/cds/appcds/test-classes - * @build Hello + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @compile ../test-classes/HelloInlineClassApp.java ../test-classes/HelloRelocation.java * @build jdk.test.whitebox.WhiteBox - * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar hello.jar Hello + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar hello.jar HelloRelocation HelloInlineClassApp HelloInlineClassApp$Point HelloInlineClassApp$Rectangle HelloInlineClassApp$ValueRecord * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. DynamicArchiveRelocationTest */ @@ -78,7 +81,7 @@ static void doTest() throws Exception { System.out.println("============================================================"); String appJar = ClassFileInstaller.getJarPath("hello.jar"); - String mainClass = "Hello"; + String mainClass = "HelloRelocation"; String maybeRelocation = "-XX:ArchiveRelocationMode=0"; String alwaysRelocation = "-XX:ArchiveRelocationMode=1"; String dumpTopRelocArg = dump_top_reloc ? alwaysRelocation : maybeRelocation; @@ -94,12 +97,13 @@ static void doTest() throws Exception { // (1) Dump base archive (static) - TestCommon.dumpBaseArchive(baseArchiveName, unlockArg, logArg) + TestCommon.dumpBaseArchive(baseArchiveName, "--enable-preview", unlockArg, logArg) .shouldContain("Relocating archive from"); // (2) Dump top archive (dynamic) dump2(baseArchiveName, topArchiveName, + "--enable-preview", unlockArg, dumpTopRelocArg, logArg, @@ -113,6 +117,7 @@ static void doTest() throws Exception { }); run2(baseArchiveName, topArchiveName, + "--enable-preview", unlockArg, runRelocArg, logArg, diff --git a/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/HelloDynamicInlineClass.java b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/HelloDynamicInlineClass.java new file mode 100644 index 00000000000..59835b5ba6e --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/dynamicArchive/HelloDynamicInlineClass.java @@ -0,0 +1,75 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary Hello World test for dynamic archive + * @requires vm.cds + * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds /test/hotspot/jtreg/runtime/cds/appcds/test-classes + * @enablePreview + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @compile ../test-classes/HelloInlineClassApp.java + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar hello_inline.jar HelloInlineClassApp HelloInlineClassApp$Point HelloInlineClassApp$Rectangle HelloInlineClassApp$ValueRecord + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. HelloDynamicInlineClass + */ + +import jdk.test.lib.helpers.ClassFileInstaller; + +public class HelloDynamicInlineClass extends DynamicArchiveTestBase { + public static void main(String[] args) throws Exception { + runTest(HelloDynamicInlineClass::test); + } + + static void test() throws Exception { + String topArchiveName = getNewArchiveName("top"); + String baseArchiveName = getNewArchiveName("base"); + TestCommon.dumpBaseArchive(baseArchiveName, "--enable-preview", "-Xlog:cds"); + doTest(baseArchiveName, topArchiveName); + } + + private static void doTest(String baseArchiveName, String topArchiveName) throws Exception { + String appJar = ClassFileInstaller.getJarPath("hello_inline.jar"); + String mainClass = "HelloInlineClassApp"; + dump2(baseArchiveName, topArchiveName, + "--enable-preview", + "-Xlog:cds", + "-Xlog:cds+dynamic=debug", + "-cp", appJar, mainClass) + .assertNormalExit(output -> { + output.shouldContain("Written dynamic archive 0x"); + }); + run2(baseArchiveName, topArchiveName, + "--enable-preview", + "-Xlog:class+load", + "-Xlog:cds+dynamic=debug,cds=debug", + "-cp", appJar, mainClass) + .assertNormalExit(output -> { + output.shouldContain("HelloInlineClassApp$Point source: shared objects file") + .shouldHaveExitValue(0); + }); + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/javaldr/LockDuringDump.java b/test/hotspot/jtreg/runtime/cds/appcds/javaldr/LockDuringDump.java index 391596160d6..0a98f42420b 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/javaldr/LockDuringDump.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/javaldr/LockDuringDump.java @@ -57,7 +57,7 @@ public static void main(String[] args) throws Throwable { String appJar = ClassFileInstaller.writeJar("LockDuringDumpApp.jar", appClasses); - for (int i = 0; i < 3; i++) { + for (int i = 0; i < 2; i++) { // i = 0 -- dump without agent // i = 1 -- dump with agent diff --git a/test/hotspot/jtreg/runtime/cds/appcds/jigsaw/module/ModuleOption.java b/test/hotspot/jtreg/runtime/cds/appcds/jigsaw/module/ModuleOption.java index 3001b8dd2ca..1a2d98b0574 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/jigsaw/module/ModuleOption.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/jigsaw/module/ModuleOption.java @@ -43,6 +43,7 @@ public static void main(String[] args) throws Exception { // JDK 22.0.1: "java 22.0.1" final String versionPattern = "java.[0-9][0-9].*"; final String subgraphCannotBeUsed = "subgraph jdk.internal.module.ArchivedBootLayer cannot be used because full module graph is disabled"; + final String noOptimizedModuleHandling = "optimized module handling: disabled because archive was created without optimized module handling"; String archiveName = TestCommon.getNewArchiveName("module-option"); TestCommon.setCurrentArchiveName(archiveName); @@ -61,8 +62,10 @@ public static void main(String[] args) throws Exception { "-version"); oa.shouldHaveExitValue(0) // version of the jdk.httpserver module, e.g. java 22-ea - .shouldMatch(versionPattern) - .shouldMatch("aot,module.*Restored from archive: entry.0x.*name jdk.httpserver"); + .shouldMatch(versionPattern); + if (!oa.contains(noOptimizedModuleHandling)) { + oa.shouldMatch("aot,module.*Restored from archive: entry.0x.*name jdk.httpserver"); + } // different module specified during runtime oa = TestCommon.execCommon( @@ -70,16 +73,20 @@ public static void main(String[] args) throws Exception { "-m", "jdk.compiler/com.sun.tools.javac.Main", "-version"); oa.shouldHaveExitValue(0) - .shouldContain("Mismatched values for property jdk.module.main: runtime jdk.compiler dump time jdk.httpserver") - .shouldContain(subgraphCannotBeUsed); + .shouldContain("Mismatched values for property jdk.module.main: runtime jdk.compiler dump time jdk.httpserver"); + if (!oa.contains(noOptimizedModuleHandling)) { + oa.shouldContain(subgraphCannotBeUsed); + } // no module specified during runtime oa = TestCommon.execCommon( loggingOption, "-version"); oa.shouldHaveExitValue(0) - .shouldContain("Mismatched values for property jdk.module.main: jdk.httpserver specified during dump time but not during runtime") - .shouldContain(subgraphCannotBeUsed); + .shouldContain("Mismatched values for property jdk.module.main: jdk.httpserver specified during dump time but not during runtime"); + if (!oa.contains(noOptimizedModuleHandling)) { + oa.shouldContain(subgraphCannotBeUsed); + } // dump an archive without the module option archiveName = TestCommon.getNewArchiveName("no-module-option"); @@ -98,8 +105,10 @@ public static void main(String[] args) throws Exception { oa.shouldHaveExitValue(0) .shouldContain("Mismatched values for property jdk.module.main: jdk.httpserver specified during runtime but not during dump time") // version of the jdk.httpserver module, e.g. java 22-ea - .shouldMatch(versionPattern) - .shouldContain(subgraphCannotBeUsed); + .shouldMatch(versionPattern); + if (!oa.contains(noOptimizedModuleHandling)) { + oa.shouldContain(subgraphCannotBeUsed); + } // dump an archive with an incubator module, -m jdk.incubator.vector archiveName = TestCommon.getNewArchiveName("incubator-module"); diff --git a/test/hotspot/jtreg/runtime/cds/appcds/test-classes/HelloInlineClassApp.java b/test/hotspot/jtreg/runtime/cds/appcds/test-classes/HelloInlineClassApp.java new file mode 100644 index 00000000000..c3a05185bab --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/test-classes/HelloInlineClassApp.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +public class HelloInlineClassApp { + + @LooselyConsistentValue + static value class Point { + int x, y; + + public String toString() { + return "(" + x + ", " + y + ")"; + } + + public Point(int x, int y) { + this.x = x; + this.y = y; + } + + Point add(Point p1) { + return new Point(x + p1.x, y + p1.y); + } + + Point add(Point p1, Point p2) { + return new Point(x + p1.x + p2.x, y + p1.y + p2.y); + } + + Point add(Point p1, int x2, int y2, Point p3) { + return new Point(x + p1.x + x2 + p3.x, y + p1.y + y2 + p3.y); + } + } + + @LooselyConsistentValue + static value class Rectangle { + @NullRestricted + Point p0 = new Point(0,0); + @NullRestricted + Point p1 = new Point(1,1); + } + + @Strict + @NullRestricted + Point point = new Point(0, 0); + + @Strict + @NullRestricted + static Rectangle rectangle = new Rectangle(); + + static value record ValueRecord(int i, String name) {} + + public static void main(String[] args) throws Exception { + Point p = new Point(0, 123); + System.out.println("Point = " + p); + String req = "(0, 123)"; + if (!p.toString().equals(req)) { + throw new RuntimeException("Expected " + req + " but got " + p); + } + + Point p1 = new Point(1, 1); + Point p2 = new Point(2, 2); + Point p3 = new Point(3, 3); + int x2 = 200; + int y2 = 200; + + int loops = 100000; + for (int i=0; i { - void run(T arg, Runnable runnable); + void run(T arg, Runnable runnable, MyValue val); } enum Phase { BEFORE_INIT, IN_PROGRESS, FINISHED, INIT_FAILURE } @@ -339,24 +350,25 @@ interface Factory { static final Factory BLOCKING = () -> disposableAction(Phase.FINISHED, BLOCKING_COUNTER, BLOCKING_ACTIONS); static void checkBlockingAction(TestCase0 r) { + MyValue val = new MyValue(); switch (phase) { case IN_PROGRESS: { // Barrier during class initalization. - r.run(NON_BLOCKING.get()); // initializing thread + r.run(NON_BLOCKING.get(), val); // initializing thread checkBlocked(ON_BLOCK, ON_FAILURE, r); // different thread break; } case FINISHED: { // No barrier after class initalization is over. - r.run(NON_BLOCKING.get()); // initializing thread + r.run(NON_BLOCKING.get(), val); // initializing thread checkNotBlocked(r); // different thread break; } case INIT_FAILURE: { // Exception is thrown after class initialization failed. - TestCase0 test = action -> execute(NoClassDefFoundError.class, () -> r.run(action)); + TestCase0 test = (action, valarg) -> execute(NoClassDefFoundError.class, () -> r.run(action, valarg)); - test.run(NON_BLOCKING.get()); // initializing thread + test.run(NON_BLOCKING.get(), val); // initializing thread checkNotBlocked(test); // different thread break; } @@ -365,17 +377,17 @@ static void checkBlockingAction(TestCase0 r) { } static void checkNonBlockingAction(TestCase0 r) { - r.run(NON_BLOCKING.get()); // initializing thread + r.run(NON_BLOCKING.get(), new MyValue()); // initializing thread checkNotBlocked(r); // different thread } static void checkNonBlockingAction(T recv, TestCase1 r) { - r.run(recv, NON_BLOCKING.get()); // initializing thread - checkNotBlocked((action) -> r.run(recv, action)); // different thread + r.run(recv, NON_BLOCKING.get(), new MyValue()); // initializing thread + checkNotBlocked((action, val) -> r.run(recv, action, val)); // different thread } static void checkFailingAction(TestCase0 r) { - r.run(NON_BLOCKING.get()); // initializing thread + r.run(NON_BLOCKING.get(), new MyValue()); // initializing thread checkNotBlocked(r); // different thread } @@ -393,7 +405,7 @@ static void triggerInitialization(Class cls) { static void checkBlocked(Consumer onBlockHandler, Thread.UncaughtExceptionHandler onException, TestCase0 r) { Thread thr = new Thread(() -> { try { - r.run(BLOCKING.get()); + r.run(BLOCKING.get(), new MyValue()); System.out.println("Thread " + Thread.currentThread() + ": Finished successfully"); } catch(Throwable e) { System.out.println("Thread " + Thread.currentThread() + ": Exception thrown: " + e); @@ -421,7 +433,7 @@ static void checkBlocked(Consumer onBlockHandler, Thread.UncaughtExcepti } static void checkNotBlocked(TestCase0 r) { - final Thread thr = new Thread(() -> r.run(NON_BLOCKING.get())); + final Thread thr = new Thread(() -> r.run(NON_BLOCKING.get(), new MyValue())); final Throwable[] ex = new Throwable[1]; thr.setUncaughtExceptionHandler((t, e) -> { if (thr != t) { diff --git a/test/hotspot/jtreg/runtime/clinit/libClassInitBarrier.cpp b/test/hotspot/jtreg/runtime/clinit/libClassInitBarrier.cpp index dce5c1057a8..3243399368e 100644 --- a/test/hotspot/jtreg/runtime/clinit/libClassInitBarrier.cpp +++ b/test/hotspot/jtreg/runtime/clinit/libClassInitBarrier.cpp @@ -54,13 +54,13 @@ extern "C" { test_class_B = (jclass)env->NewGlobalRef(arg1); if (test_class_B == nullptr) return JNI_FALSE; - test_staticM_id = env->GetStaticMethodID(test_class_A, "staticM", "(Ljava/lang/Runnable;)V"); + test_staticM_id = env->GetStaticMethodID(test_class_A, "staticM", "(Ljava/lang/Runnable;LClassInitBarrier$MyValue;)V"); if (test_staticM_id == nullptr) return JNI_FALSE; - test_staticS_id = env->GetStaticMethodID(test_class_A, "staticS", "(Ljava/lang/Runnable;)V"); + test_staticS_id = env->GetStaticMethodID(test_class_A, "staticS", "(Ljava/lang/Runnable;LClassInitBarrier$MyValue;)V"); if (test_staticS_id == nullptr) return JNI_FALSE; - test_staticN_id = env->GetStaticMethodID(test_class_A, "staticN", "(Ljava/lang/Runnable;)V"); + test_staticN_id = env->GetStaticMethodID(test_class_A, "staticN", "(Ljava/lang/Runnable;LClassInitBarrier$MyValue;)V"); if (test_staticN_id == nullptr) return JNI_FALSE; test_A_m_id = env->GetMethodID(test_class_A, "m", "()V"); @@ -79,16 +79,16 @@ extern "C" { env->CallVoidMethod(action, methodId); } - JNIEXPORT void JNICALL Java_ClassInitBarrier_00024Test_testInvokeStaticJNI(JNIEnv* env, jclass cls, jobject action) { - env->CallStaticVoidMethod(test_class_A, test_staticM_id, action); + JNIEXPORT void JNICALL Java_ClassInitBarrier_00024Test_testInvokeStaticJNI(JNIEnv* env, jclass cls, jobject action, jobject val) { + env->CallStaticVoidMethod(test_class_A, test_staticM_id, action, val); } - JNIEXPORT void JNICALL Java_ClassInitBarrier_00024Test_testInvokeStaticSyncJNI(JNIEnv* env, jclass cls, jobject action) { - env->CallStaticVoidMethod(test_class_A, test_staticS_id, action); + JNIEXPORT void JNICALL Java_ClassInitBarrier_00024Test_testInvokeStaticSyncJNI(JNIEnv* env, jclass cls, jobject action, jobject val) { + env->CallStaticVoidMethod(test_class_A, test_staticS_id, action, val); } - JNIEXPORT void JNICALL Java_ClassInitBarrier_00024Test_testInvokeStaticNativeJNI(JNIEnv* env, jclass cls, jobject action) { - env->CallStaticVoidMethod(test_class_A, test_staticN_id, action); + JNIEXPORT void JNICALL Java_ClassInitBarrier_00024Test_testInvokeStaticNativeJNI(JNIEnv* env, jclass cls, jobject action, jobject val) { + env->CallStaticVoidMethod(test_class_A, test_staticN_id, action, val); } JNIEXPORT jint JNICALL Java_ClassInitBarrier_00024Test_testGetStaticJNI(JNIEnv* env, jclass cls, jobject action) { diff --git a/test/hotspot/jtreg/runtime/condy/CondyBadBSMArrayTest.java b/test/hotspot/jtreg/runtime/condy/CondyBadBSMArrayTest.java index b6b23336377..b544685eaec 100644 --- a/test/hotspot/jtreg/runtime/condy/CondyBadBSMArrayTest.java +++ b/test/hotspot/jtreg/runtime/condy/CondyBadBSMArrayTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -57,7 +57,7 @@ public static void main(String args[]) throws Throwable { throw new RuntimeException("Expected ClassFormatError exception not thrown"); } catch (java.lang.ClassFormatError e) { if (!e.getMessage().contains("Short length on BootstrapMethods in class file")) { - throw new RuntimeException("ClassFormatError thrown, incorrect message"); + throw new RuntimeException("ClassFormatError thrown, incorrect message: " + e.getMessage()); } System.out.println("Test CondyEmptyBSMArray1 passed: " + e.getMessage()); } catch (Throwable e) { diff --git a/test/hotspot/jtreg/runtime/condy/CondyBadBSMIndex.jcod b/test/hotspot/jtreg/runtime/condy/CondyBadBSMIndex.jcod index ffc14daba52..657f4689169 100644 --- a/test/hotspot/jtreg/runtime/condy/CondyBadBSMIndex.jcod +++ b/test/hotspot/jtreg/runtime/condy/CondyBadBSMIndex.jcod @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -71,7 +71,7 @@ class CondyBadBSMIndex { class #24; // #25 } // Constant Pool - 0x0000; // access + 0x0020; // access ACC_SUPER #25;// this_cpx #2;// super_cpx diff --git a/test/hotspot/jtreg/runtime/condy/CondyBadLDC.jasm b/test/hotspot/jtreg/runtime/condy/CondyBadLDC.jasm index 4ae2a1bdf9c..a5f3ff3c609 100644 --- a/test/hotspot/jtreg/runtime/condy/CondyBadLDC.jasm +++ b/test/hotspot/jtreg/runtime/condy/CondyBadLDC.jasm @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,7 +26,7 @@ * constant is a double. VerifyError expected. */ -class CondyBadLDC +super class CondyBadLDC version 55:0 { diff --git a/test/hotspot/jtreg/runtime/condy/CondyBadLDC2_W.jasm b/test/hotspot/jtreg/runtime/condy/CondyBadLDC2_W.jasm index 07d5094c9c7..f960f0297b8 100644 --- a/test/hotspot/jtreg/runtime/condy/CondyBadLDC2_W.jasm +++ b/test/hotspot/jtreg/runtime/condy/CondyBadLDC2_W.jasm @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. * 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,7 +25,7 @@ * This test contains a ldc2_w instruction of a condy which returns a loadable float * constant. VerifyError expected. */ -class CondyBadLDC2_W +super class CondyBadLDC2_W version 55:0 { diff --git a/test/hotspot/jtreg/runtime/condy/CondyEmptyBSMArray1.jcod b/test/hotspot/jtreg/runtime/condy/CondyEmptyBSMArray1.jcod index 9b181045793..779f8c7b697 100644 --- a/test/hotspot/jtreg/runtime/condy/CondyEmptyBSMArray1.jcod +++ b/test/hotspot/jtreg/runtime/condy/CondyEmptyBSMArray1.jcod @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -71,7 +71,7 @@ class CondyEmptyBSMArray1 { class #24; // #25 } // Constant Pool - 0x0000; // access + 0x0020; // access ACC_SUPER #25;// this_cpx #2;// super_cpx diff --git a/test/hotspot/jtreg/runtime/condy/CondyNoBSMArray.jcod b/test/hotspot/jtreg/runtime/condy/CondyNoBSMArray.jcod index 0e4ec580df0..1983777f9b2 100644 --- a/test/hotspot/jtreg/runtime/condy/CondyNoBSMArray.jcod +++ b/test/hotspot/jtreg/runtime/condy/CondyNoBSMArray.jcod @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -71,7 +71,7 @@ class CondyNoBSMArray { class #24; // #25 } // Constant Pool - 0x0000; // access + 0x0020; // access ACC_SUPER #25;// this_cpx #2;// super_cpx diff --git a/test/hotspot/jtreg/runtime/condy/CondyUseLDC_W.jasm b/test/hotspot/jtreg/runtime/condy/CondyUseLDC_W.jasm index 96f3d436170..14dce01c385 100644 --- a/test/hotspot/jtreg/runtime/condy/CondyUseLDC_W.jasm +++ b/test/hotspot/jtreg/runtime/condy/CondyUseLDC_W.jasm @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. * 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,7 +25,7 @@ * This test contains ldc_w instructions of condy's who generate dynamic constants * of the following types: byte, char, short, int, float, boolean. */ -class CondyUseLDC_W +super class CondyUseLDC_W version 55:0 { diff --git a/test/hotspot/jtreg/runtime/condy/CondyUsesIndyBSM.jcod b/test/hotspot/jtreg/runtime/condy/CondyUsesIndyBSM.jcod index 9cb91ba4fbb..92d97e62a93 100644 --- a/test/hotspot/jtreg/runtime/condy/CondyUsesIndyBSM.jcod +++ b/test/hotspot/jtreg/runtime/condy/CondyUsesIndyBSM.jcod @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -161,7 +161,7 @@ class CondyUsesIndyBSM { class #87; // #88 } // Constant Pool - 0x0000; // access + 0x0020; // access ACC_SUPER #88;// this_cpx #2;// super_cpx diff --git a/test/hotspot/jtreg/runtime/condy/IndyUsesCondyBSM.jcod b/test/hotspot/jtreg/runtime/condy/IndyUsesCondyBSM.jcod index 78949cf00f7..4b2b6fb2366 100644 --- a/test/hotspot/jtreg/runtime/condy/IndyUsesCondyBSM.jcod +++ b/test/hotspot/jtreg/runtime/condy/IndyUsesCondyBSM.jcod @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -160,7 +160,7 @@ class IndyUsesCondyBSM { NameAndType #40 #35; // #87 at 0x0501 } // Constant Pool - 0x0000; // access [ ] + 0x0020; // access ACC_SUPER #5;// this_cpx #62;// super_cpx diff --git a/test/hotspot/jtreg/runtime/condy/staticInit/Example.jasm b/test/hotspot/jtreg/runtime/condy/staticInit/Example.jasm index 708f5c65b45..152ea8d03e2 100644 --- a/test/hotspot/jtreg/runtime/condy/staticInit/Example.jasm +++ b/test/hotspot/jtreg/runtime/condy/staticInit/Example.jasm @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved. * 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,7 +25,7 @@ // initialized before the condy call. // Test that second invocation gets same error as first. -public class Example +super public class Example version 55:0 { diff --git a/test/hotspot/jtreg/runtime/exceptionMsgs/NullPointerException/NullPointerExceptionTest.java b/test/hotspot/jtreg/runtime/exceptionMsgs/NullPointerException/NullPointerExceptionTest.java index e56d283783a..56018a46833 100644 --- a/test/hotspot/jtreg/runtime/exceptionMsgs/NullPointerException/NullPointerExceptionTest.java +++ b/test/hotspot/jtreg/runtime/exceptionMsgs/NullPointerException/NullPointerExceptionTest.java @@ -304,7 +304,7 @@ public void testFailedAction() { } catch (NullPointerException e) { checkMessage(e, "oa1[0] = new Object();", e.getMessage(), "Cannot store to object array because " + - (hasDebugInfo ? "\"oa1\"" : "\"\"") + " is null"); + (hasDebugInfo ? "\"oa1\"" : "\"\"") + " is null or is a null-free array and there's an attempt to store null in it"); } // bastore (boolean) try { diff --git a/test/hotspot/jtreg/runtime/modules/PatchModule/PatchModuleDupModule.java b/test/hotspot/jtreg/runtime/modules/PatchModule/PatchModuleDupModule.java index 464a15f78bd..b7422baebb9 100644 --- a/test/hotspot/jtreg/runtime/modules/PatchModule/PatchModuleDupModule.java +++ b/test/hotspot/jtreg/runtime/modules/PatchModule/PatchModuleDupModule.java @@ -23,7 +23,7 @@ /* * @test - * @summary Module system initialization exception results if a module is specificed twice to --patch-module. + * @summary If a module is specificed twice to --patch-module, it should print an error * @requires vm.flagless * @modules java.base/jdk.internal.misc * @library /test/lib @@ -35,8 +35,8 @@ public class PatchModuleDupModule { - // The module system initialization should generate an ExceptionInInitializerError // if --patch-module is specified with the same module more than once. + // The launcher should print an error. public static void main(String args[]) throws Exception { ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder( @@ -44,7 +44,7 @@ public static void main(String args[]) throws Exception { "--patch-module=module_one=module_one_dir", "-version"); OutputAnalyzer output = new OutputAnalyzer(pb.start()); - output.shouldContain("java.lang.ExceptionInInitializerError"); + output.shouldContain("Cannot specify a module more than once to --patch-module"); output.shouldHaveExitValue(1); } } diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/AbstractValueClassTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/AbstractValueClassTest.java new file mode 100644 index 00000000000..16a9636bd74 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/AbstractValueClassTest.java @@ -0,0 +1,52 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8293134 + * @summary Test that a super abstract value class is allowed. + * @enablePreview + * @run main AbstractValueClassTest + */ + +public class AbstractValueClassTest { + + public static void main(String[] args) { + A a = new A(); + a.doit(a); + } + public static abstract value class T { + public abstract void doit(T t); + } + + public static final value class A extends T { + public void doit(T t) { + System.out.println(t); + } + } + public static final value class B extends T { + public void doit(T t) { + System.out.println(t); + } + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/AnnotationsTests.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/AnnotationsTests.java new file mode 100644 index 00000000000..a0c52d5bfa9 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/AnnotationsTests.java @@ -0,0 +1,361 @@ +/* + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import jdk.test.lib.Asserts; +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.lang.reflect.Method; +import java.lang.reflect.Field; +import java.util.List; +import jdk.internal.misc.Unsafe; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.Strict; +import jdk.test.whitebox.WhiteBox; + +/* + * @test + * @summary Test of NullRestricted, Strict and LooselyConsistentValue annotations + * @modules java.base/jdk.internal.misc + * java.base/jdk.internal.vm.annotation + * @library /test/lib + * @enablePreview + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI AnnotationsTests + */ + + public class AnnotationsTests { + private static final WhiteBox WHITEBOX = WhiteBox.getWhiteBox(); + private static final boolean UseNullableValueFlattening = WHITEBOX.getBooleanVMFlag("UseNullableValueFlattening"); + + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + public static void main(String[] args) { + RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean(); + AnnotationsTests tests = new AnnotationsTests(); + Class c = tests.getClass(); + for (Method m : c.getDeclaredMethods()) { + if (m.getName().startsWith("test_")) { + try { + System.out.println("Running " + m.getName()); + m.invoke(tests); + } catch (Throwable t) { + t.printStackTrace(); + throw new RuntimeException(t); + } + } + } + } + + static class BadClass0 { + @Strict + @NullRestricted + String s = new String("bad"); + } + + // Test detection of illegal usage of NullRestricted on an identity field + void test_0() { + Throwable exception = null; + try { + BadClass0 bc = new BadClass0(); + } catch (IncompatibleClassChangeError e) { + exception = e; + System.out.println("Received " + e); + } + Asserts.assertNotNull(exception, "Failed to detect illegal use of @NullRestricted"); + } + + // Test detection of non-static field annotated with @NullRestricted but not @Strict + static value class ValueClass1 { + int i = 0; + int j = 0; + } + + static class BadClass1 { + @NullRestricted + ValueClass1 vc; + } + + void test_1() { + Throwable exception = null; + try { + BadClass1 tc = new BadClass1(); + } catch (ClassFormatError e) { + exception = e; + System.out.println("Received " + e); + } + Asserts.assertNotNull(exception, "Failed to detect illegal use of @NullRestricted without @Strict on a non-static field"); + } + + // Invalid usage of @NullRestricted on a non-strict non-static field + static value class ValueClass2 { + int i = 0; + int j = 0; + } + + static class BadClass2 { + @NullRestricted + static ValueClass2 val; + } + + void test_2() { + Throwable exception = null; + try { + BadClass2 tc = new BadClass2(); + } catch (ClassFormatError e) { + exception = e; + System.out.println("Received " + e); + } + Asserts.assertNotNull(exception, "FFailed to detect illegal use of @NullRestricted without @Strict on a static field"); + } + + // Test invalid usage of @LooselyConsistentValue on an identity class + @LooselyConsistentValue + static class BadClass4 { + + } + + void test_4() { + Throwable exception = null; + try { + BadClass4 tc = new BadClass4(); + } catch (ClassFormatError e) { + exception = e; + System.out.println("Received " + e); + } + Asserts.assertNotNull(exception, "Failed to detect illegal use of @LooselyConsistentValue"); + } + + // Test field flattening of @NullRestricted annotated fields + + @LooselyConsistentValue + static value class ValueClass5 { + int i = 0; + } + + static class GoodClass5 { + ValueClass5 f0 = new ValueClass5(); + + @Strict + @NullRestricted + ValueClass5 f1 = new ValueClass5(); + } + + void test_5() { + Throwable exception = null; + try { + GoodClass5 vc = new GoodClass5(); + Field f0 = vc.getClass().getDeclaredField("f0"); + if (UseNullableValueFlattening) { + Asserts.assertTrue(UNSAFE.isFlatField(f0), "Flat field expected, but field is not flat"); + } else { + Asserts.assertFalse(UNSAFE.isFlatField(f0), "Unexpected flat field"); + } + Field f1 = vc.getClass().getDeclaredField("f1"); + Asserts.assertTrue(UNSAFE.isFlatField(f1), "Flat field expected, but field is not flat"); + } catch (IncompatibleClassChangeError e) { + exception = e; + System.out.println("Received " + e); + } catch(NoSuchFieldException e) { + Asserts.fail("Test error"); + } + Asserts.assertNull(exception, "Unexpected exception: " + exception); + } + + + // Test detection/handling of circularity + + static value class ValueClass6a { + @Strict + @NullRestricted + ValueClass6b val = new ValueClass6b(); + } + + static value class ValueClass6b { + @Strict + @NullRestricted + ValueClass6a val = new ValueClass6a(); + } + + static class BadClass6 { + @Strict + @NullRestricted + ValueClass6a val = new ValueClass6a(); + } + + void test_6() { + Throwable exception = null; + try { + BadClass6 bc = new BadClass6(); + } catch (ClassCircularityError e) { + exception = e; + System.out.println("Received " + e); + } + Asserts.assertNotNull(exception, "Failed to detect circularity"); + } + + // Test null restricted static field + static value class ValueClass7 { + int i = 0; + } + + static class GoodClass7 { + @Strict + @NullRestricted + static ValueClass7 sval = new ValueClass7(); + } + + void test_7() { + Throwable exception = null; + try { + ValueClass7 val = GoodClass7.sval; + Asserts.assertNotNull(val, "Unexpected null value"); + } catch (Throwable e) { + exception = e; + System.out.println("Received " + e); + } + Asserts.assertNull(exception, "Unexpected exception: " + exception); + } + + // Test circularity on static fields + static value class ValueClass8 { + @Strict + @NullRestricted + static ValueClass8 sval = new ValueClass8(); + } + + void test_8() { + Throwable exception = null; + try { + ValueClass8 val = ValueClass8.sval; + Asserts.assertNotNull(val, "Unexpected null value"); + } catch (Throwable e) { + exception = e; + System.out.println("Received " + e); + } + Asserts.assertNull(exception, "Unexpected exception: " + exception); + } + + // Test that writing null to a @NullRestricted non-static field throws an exception + static value class ValueClass9 { + int i = 0; + } + + static class GoodClass9 { + @Strict + @NullRestricted + ValueClass9 val = new ValueClass9(); + } + + void test_9() { + Throwable exception = null; + try { + GoodClass9 gc = new GoodClass9(); + gc.val = null; + } catch(NullPointerException e) { + exception = e; + System.out.println("Received " + e); + } + Asserts.assertNotNull(exception, "Expected NullPointerException not received"); + } + + // Test that writing null to a @NullRestricted static field throws an exception + static value class ValueClass10 { + @Strict + @NullRestricted + static ValueClass10 sval = new ValueClass10(); + } + + void test_10() { + Throwable exception = null; + try { + ValueClass10.sval = null; + } catch(NullPointerException e) { + exception = e; + System.out.println("Received " + e); + } + Asserts.assertNotNull(exception, "Expected NullPointerException not received"); + } + + // Test static null restricted field that is not declared strict + static value class ValueClass11 { + int i = 0; + int j = 0; + } + + static class BadClass11 { + @NullRestricted + static ValueClass11 val = new ValueClass11(); + } + + void test_11() { + Throwable exception = null; + try { + ValueClass11 val = BadClass11.val; + System.out.println(val); + } catch(ClassFormatError e) { + exception = e; + System.out.println("Received " + e); + } + Asserts.assertNotNull(exception, "Expected ClassFormatError not received"); + } + + // Test illegal use of @NullRestricted on a primitive field + static class BadClass12 { + @Strict + @NullRestricted + int i = 0; + } + void test_12() { + Throwable exception = null; + try { + BadClass12 val = new BadClass12(); + System.out.println(val); + } catch(ClassFormatError e) { + exception = e; + System.out.println("Received " + e); + } + Asserts.assertNotNull(exception, "Expected ClassFormatError not received"); + } + + // Test illegal use of @NullRestricted on an array field + static class BadClass13 { + @Strict + @NullRestricted + int[] intArray = new int[1]; + } + void test_13() { + Throwable exception = null; + try { + BadClass13 val = new BadClass13(); + System.out.println(val); + } catch(ClassFormatError e) { + exception = e; + System.out.println("Received " + e); + } + Asserts.assertNotNull(exception, "Expected ClassFormatError not received"); + } + + } + diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ArrayQueryTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ArrayQueryTest.java new file mode 100644 index 00000000000..e903cee8ec0 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ArrayQueryTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Test ValueClass APIs to get array properties + * @library /test/lib + * @requires vm.flagless + * @modules java.base/jdk.internal.vm.annotation + * java.base/jdk.internal.value + * @enablePreview + * @run main/othervm -XX:+UseArrayFlattening -XX:+UseFieldFlattening + * -XX:+UseNonAtomicValueFlattening -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening + * ArrayQueryTest + */ + + + import jdk.internal.value.ValueClass; + import jdk.internal.vm.annotation.LooselyConsistentValue; + import jdk.test.lib.Asserts; + + + public class ArrayQueryTest { + + static value class SmallValue { + short i = 0; + short j = 0; + } + + @LooselyConsistentValue + static value class WeakValue { + short i = 0; + short j = 0; + } + + static value class NaturallyAtomic { + int i = 0; + } + + @LooselyConsistentValue + static value class BigValue { + long l0 = 0L; + long l1 = 0L; + long l2 = 0L; + } + + public static void main(String[] args) { + SmallValue[] array0 = new SmallValue[10]; + Asserts.assertFalse(ValueClass.isNullRestrictedArray(array0)); + Asserts.assertTrue(ValueClass.isFlatArray(array0)); + Asserts.assertTrue(ValueClass.isAtomicArray(array0)); + + Object[] array1 = ValueClass.newNullRestrictedAtomicArray(SmallValue.class, 10, new SmallValue()); + Asserts.assertTrue(ValueClass.isNullRestrictedArray(array1)); + Asserts.assertTrue(ValueClass.isFlatArray(array1)); + Asserts.assertTrue(ValueClass.isAtomicArray(array1)); + + Object[] array2 = ValueClass.newNullableAtomicArray(SmallValue.class, 10); + Asserts.assertFalse(ValueClass.isNullRestrictedArray(array2)); + Asserts.assertTrue(ValueClass.isFlatArray(array2)); + Asserts.assertTrue(ValueClass.isAtomicArray(array2)); + + Object[] array3 = ValueClass.newNullRestrictedNonAtomicArray(WeakValue.class, 10, new WeakValue()); + Asserts.assertTrue(ValueClass.isNullRestrictedArray(array3)); + Asserts.assertTrue(ValueClass.isFlatArray(array3)); + Asserts.assertFalse(ValueClass.isAtomicArray(array3)); + + Object[] array4 = ValueClass.newNullRestrictedAtomicArray(WeakValue.class, 10, new WeakValue()); + Asserts.assertTrue(ValueClass.isNullRestrictedArray(array4)); + Asserts.assertTrue(ValueClass.isFlatArray(array4)); + Asserts.assertTrue(ValueClass.isAtomicArray(array4)); + + Object[] array5 = ValueClass.newNullRestrictedAtomicArray(NaturallyAtomic.class, 10, new NaturallyAtomic()); + Asserts.assertTrue(ValueClass.isNullRestrictedArray(array5)); + Asserts.assertTrue(ValueClass.isFlatArray(array5)); + Asserts.assertTrue(ValueClass.isAtomicArray(array5)); + + Object[] array6 = ValueClass.newNullRestrictedNonAtomicArray(BigValue.class, 10, new BigValue()); + Asserts.assertTrue(ValueClass.isNullRestrictedArray(array6)); + Asserts.assertTrue(ValueClass.isFlatArray(array6)); + Asserts.assertFalse(ValueClass.isAtomicArray(array6)); + + Object[] array7 = ValueClass.newNullRestrictedAtomicArray(BigValue.class, 10, new BigValue()); + Asserts.assertTrue(ValueClass.isNullRestrictedArray(array7)); + Asserts.assertFalse(ValueClass.isFlatArray(array7)); + Asserts.assertTrue(ValueClass.isAtomicArray(array7)); + + Object[] array8 = ValueClass.newNullableAtomicArray(BigValue.class, 10); + Asserts.assertFalse(ValueClass.isNullRestrictedArray(array8)); + Asserts.assertFalse(ValueClass.isFlatArray(array8)); + Asserts.assertTrue(ValueClass.isAtomicArray(array8)); + } + + } diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ClassInitializationFailuresTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ClassInitializationFailuresTest.java new file mode 100644 index 00000000000..9ee3fcbe775 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ClassInitializationFailuresTest.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package runtime.valhalla.inlinetypes; + +import jdk.internal.value.ValueClass; +import jdk.test.lib.Asserts; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +/* + * @test + * @summary Test several scenarios of class initialization failures + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * java.base/jdk.internal.value + * @enablePreview + * @compile ClassInitializationFailuresTest.java + * @run main/othervm runtime.valhalla.inlinetypes.ClassInitializationFailuresTest +*/ +public class ClassInitializationFailuresTest { + static boolean failingInitialization = true; + static Object bo = null; + + @LooselyConsistentValue + static value class BadOne { + int i = 0; + static { + if (ClassInitializationFailuresTest.failingInitialization) { + throw new RuntimeException("Failing initialization"); + } + } + } + + @LooselyConsistentValue + static value class TestClass1 { + @Strict + @NullRestricted + BadOne badField = new BadOne(); + } + + // Test handling of errors during the initialization of a value class + // Initialization of TestClass1 triggers the initialization of classes + // of all its value class typed fields, in this case BadOne + // Static initializer of BadOne throws an exception, so BadOne's initialization + // fails, which must caused the initialization of TestClass1 to fail too + // First attempt to instantiate TestClass1 must fail with an ExceptionInInitializerError + // because an exception has been thrown during the initialization process + // Second attempt to instantiate TestClass1 must fail with a NoClassDefFoundError + // because TestClass1 must already be in a failed initialization state (so no new + // attempt to initialize the class) + static void testClassInitialization() { + Throwable e = null; + try { + TestClass1 t1 = new TestClass1(); + } catch(Throwable t) { + e = t; + } + Asserts.assertNotNull(e, "Exception should have been thrown"); + Asserts.assertTrue(e.getClass() == ExceptionInInitializerError.class, "Must be an ExceptionInInitializerError"); + Asserts.assertTrue(e.getCause().getClass() == RuntimeException.class, "Must be the exception thown in the static initializer of BadOne"); + // Second attempt because it doesn't fail the same way + e = null; + try { + TestClass1 t1 = new TestClass1(); + } catch(Throwable t) { + e = t; + } + Asserts.assertNotNull(e, "Error should have been thrown"); + Asserts.assertTrue(e.getClass() == NoClassDefFoundError.class, "Must be a NoClassDefFoundError"); + Asserts.assertTrue(e.getCause().getClass() == ExceptionInInitializerError.class, "Must be an ExceptionInInitializerError"); + } + + @LooselyConsistentValue + static value class BadTwo { + int i = 0; + static { + if (ClassInitializationFailuresTest.failingInitialization) { + throw new RuntimeException("Failing initialization"); + } + } + } + + @LooselyConsistentValue + static value class BadThree { + int i = 0; + static { + if (ClassInitializationFailuresTest.failingInitialization) { + throw new RuntimeException("Failing initialization"); + } + } + } + + // Same test as above, but for arrays of value objects + static void testArrayInitialization() { + // Testing anewarray when the value element class fails to initialize properly + Throwable e = null; + try { + BadTwo[] array = (BadTwo[]) ValueClass.newNullRestrictedNonAtomicArray(BadTwo.class, 10, new BadTwo()); + } catch(Throwable t) { + e = t; + } + Asserts.assertNotNull(e, "Error should have been thrown"); + Asserts.assertTrue(e.getClass() == ExceptionInInitializerError.class, " Must be an ExceptionInInitializerError"); + // Second attempt because it doesn't fail the same way + try { + BadTwo[] array = (BadTwo[]) ValueClass.newNullRestrictedNonAtomicArray(BadTwo.class, 10, new BadTwo()); + } catch(Throwable t) { + e = t; + } + Asserts.assertNotNull(e, "Error should have been thrown"); + Asserts.assertTrue(e.getClass() == NoClassDefFoundError.class, "Must be a NoClassDefFoundError"); + Asserts.assertTrue(e.getCause().getClass() == ExceptionInInitializerError.class, "Must be an ExceptionInInitializerError"); + /* + Transition model (annotations and array factory) doesn't permit multi-dimentional arrays tests + Disabling those tests for now + Testing multianewarray when the value element class fails to initialize properly + try { + BadThree[][] array = new BadThree[10][20]; + } catch(Throwable t) { + e = t; + } + Asserts.assertNotNull(e, "Error should have been thrown"); + Asserts.assertTrue(e.getClass() == ExceptionInInitializerError.class, " Must be an ExceptionInInitializerError"); + // Second attempt because it doesn't fail the same way + try { + BadThree[][][] array = new BadThree[10][30][10]; + } catch(Throwable t) { + e = t; + } + Asserts.assertNotNull(e, "Error should have been thrown"); + Asserts.assertTrue(e.getClass() == NoClassDefFoundError.class, "Must be a NoClassDefFoundError"); + Asserts.assertTrue(e.getCause().getClass() == ExceptionInInitializerError.class, "Must be an ExceptionInInitializerError"); + */ + } + + @LooselyConsistentValue + static value class BadFour { + int i = 0; + static BadFour[] array; + static { + array = (BadFour[]) ValueClass.newNullRestrictedNonAtomicArray(BadFour.class, 10, new BadFour()); + if (ClassInitializationFailuresTest.failingInitialization) { + throw new RuntimeException("Failing initialization"); + } + } + } + + // Even if a value class fails to initialize properly, some instances + // of this class can escape and be accessible. The JVM must be able to + // deal with those instances without crashes. The test below checks that + // escaped values stored in an array are handled correctly + static void testEscapedValueInArray() { + Throwable e = null; + try { + BadFour bt = new BadFour(); + } catch (Throwable t) { + e = t; + } + Asserts.assertNotNull(e, "Error must have been thrown"); + Asserts.assertTrue(e.getClass() == ExceptionInInitializerError.class, " Must be an ExceptionInInitializerError"); + e = null; + try { + BadFour t = BadFour.array[0]; + } catch(Throwable t) { + e = t; + } + Asserts.assertNotNull(e, "Error should have been thrown"); + Asserts.assertTrue(e.getClass() == NoClassDefFoundError.class, "Must be a NoClassDefFoundError"); + Asserts.assertTrue(e.getCause().getClass() == ExceptionInInitializerError.class, "Must be an ExceptionInInitializerError"); + } + + @LooselyConsistentValue + static value class BadFive { + int i = 0; + static { + ClassInitializationFailuresTest.bo = new BadSix(); + if (ClassInitializationFailuresTest.failingInitialization) { + throw new RuntimeException("Failing initialization"); + } + } + } + + static class BadSix { + BadFive bf = new BadFive(); + } + + // Same test as above, but escaped values are stored in an object + static void testEscapedValueInObject() { + Throwable e = null; + try { + BadSix bt = new BadSix(); + } catch (Throwable t) { + e = t; + } + Asserts.assertNotNull(e, "Error must have been thrown"); + Asserts.assertNotNull(ClassInitializationFailuresTest.bo, "bo object should have been set"); + BadFive bf = ((BadSix)ClassInitializationFailuresTest.bo).bf; + } + + public static void main(String[] args) { + testClassInitialization(); + testArrayInitialization(); + testEscapedValueInArray(); + testEscapedValueInObject(); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ClassPrintLayoutDcmd.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ClassPrintLayoutDcmd.java new file mode 100644 index 00000000000..93ad13723fd --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ClassPrintLayoutDcmd.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2021, Alibaba Group Holding Limited. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package runtime.valhalla.inlinetypes; + +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.JDKToolFinder; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + + +/* + * @test + * @summary Test the VM.class_print_layout command + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile ClassPrintLayoutDcmd.java + * @run main/othervm runtime.valhalla.inlinetypes.ClassPrintLayoutDcmd + */ + +public value class ClassPrintLayoutDcmd { + + @LooselyConsistentValue + static value class Point { + int i = 0; + int j = 0; + } + + @LooselyConsistentValue + static value class Line { + @Strict + @NullRestricted + Point p1; + @Strict + @NullRestricted + Point p2; + Line() { + this.p1 = this.p2 = new Point(); + } + } + static { + try { + Class.forName(Line.class.getName()); + } catch(Throwable e) { + throw new RuntimeException("failed to find class"); + } + } + static ProcessBuilder pb = new ProcessBuilder(); + + static void testCmd(String arg, int expectExitCode, String... expectStrings) throws Exception { + // Grab my own PID + String pid = Long.toString(ProcessTools.getProcessId()); + + pb.command(new String[] { JDKToolFinder.getJDKTool("jcmd"), pid, "VM.class_print_layout", arg }); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + output.shouldHaveExitValue(expectExitCode); + for (String expectString : expectStrings) { + output.shouldContain(expectString); + } + } + + public static void main(String args[]) throws Exception { + testCmd("foo/bar", 0, ""); + testCmd("", 1, "IllegalArgumentException", "mandatory"); + testCmd("java/lang/Object", 0, "java/lang/Object", "@bootstrap"); + testCmd("java/lang/Class", 0, "java/lang/Class", "@bootstrap"); + testCmd("runtime/valhalla/inlinetypes/ClassPrintLayoutDcmd$Line", 0, "@app", "p1", "p2"); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/DirectMethodTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/DirectMethodTest.java new file mode 100644 index 00000000000..498f8bba0e1 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/DirectMethodTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test DirectMethodTest + * @summary Test arguments to JVM_InvokeMethod not flattened into an args array. + * @requires vm.flagless + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * java.base/jdk.internal.misc + * @library /test/lib + * @enablePreview + * @compile --source 26 DirectMethodTest.java + * @run main/othervm -Djdk.reflect.useNativeAccessorOnly=true -XX:+UseArrayFlattening -XX:+UseFieldFlattening -XX:+UseAtomicValueFlattening -XX:+UseNullableValueFlattening DirectMethodTest + * @run main/othervm -Djdk.reflect.useNativeAccessorOnly=true -XX:-UseArrayFlattening -XX:+UseAtomicValueFlattening -XX:+UseNullableValueFlattening DirectMethodTest + + */ + +import java.lang.reflect.Array; +import java.lang.reflect.Method; +import jdk.internal.value.ValueClass; + +public class DirectMethodTest { + + public int method1(int i, int j, int k) { + System.out.println("i = " + i + " j = " + j + " k = " + k); + return i + j * k; + } + + public static void printFlat(Object[] array) { + if (!ValueClass.isFlatArray(array)) { + System.out.println("not flat " + array); + } else { + System.out.println("yay flat " + array); + } + } + + static value class SmallValue { + byte b; + short s; + + SmallValue(short i) { b = 0; s = i; } + } + + public int method2(SmallValue i, SmallValue j, SmallValue k) { + System.out.println("i = " + i + " j = " + j + " k = " + k); + return i.s + j.s * k.s; + } + + static final int ARRAY_SIZE = 3; + + public static void main(java.lang.String[] unused) throws Exception { + DirectMethodTest d = new DirectMethodTest(); + + Method m = DirectMethodTest.class.getMethod("method1", int.class, int.class, int.class); + Integer[] intarray = new Integer[]{1, 2, 3}; // is this flattened? + printFlat(intarray); + Object[] array = (Object[])Array.newInstance(Integer.class, 3); + printFlat(array); + array = ValueClass.newNullableAtomicArray(Integer.class, ARRAY_SIZE); + printFlat(array); + System.out.println("value is " + m.invoke(d, 1, 2, 3)); + + Method m2 = DirectMethodTest.class.getMethod("method2", SmallValue.class, SmallValue.class, SmallValue.class); + Object[] smallValueArray = (Object[])Array.newInstance(SmallValue.class, ARRAY_SIZE); + printFlat(smallValueArray); + smallValueArray = ValueClass.newNullableAtomicArray(SmallValue.class, ARRAY_SIZE); + printFlat(smallValueArray); + System.out.println("value is " + m2.invoke(d, new SmallValue((short)1), new SmallValue((short)2), new SmallValue((short)3))); + } +} + diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/EmptyInlineTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/EmptyInlineTest.java new file mode 100644 index 00000000000..3284386b61a --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/EmptyInlineTest.java @@ -0,0 +1,177 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package runtime.valhalla.inlinetypes; + +import jdk.test.lib.Asserts; + +import java.lang.reflect.Field; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + + +/* + * @test + * @summary Test support for empty inline types (no instance fields) + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * java.base/jdk.internal.value + * @enablePreview + * @compile EmptyInlineTest.java + * @run main/othervm -XX:+UseFieldFlattening runtime.valhalla.inlinetypes.EmptyInlineTest + */ + +public class EmptyInlineTest { + + @LooselyConsistentValue + static value class EmptyInline { + public boolean isEmpty() { + return true; + } + } + + @LooselyConsistentValue + static value class EmptyField { + @Strict + @NullRestricted + EmptyInline empty; + + EmptyField() { + this.empty = new EmptyInline(); + } + } + + static class WithInt { + int i; + } + + static class WithEmptyField extends WithInt { + // With current layout strategy for reference classs, the empty + // inline field would be placed between the int and the Object + // fields, along with some padding. + Object o; + @Strict + @NullRestricted + EmptyInline empty = new EmptyInline(); + } + + public static void main(String[] args) { + // Create an empty inline + EmptyInline empty = new EmptyInline(); + Asserts.assertTrue(empty.isEmpty()); + + // Create an inline with an empty inline field + EmptyField emptyField = new EmptyField(); + Asserts.assertEquals(emptyField.empty.getClass(), EmptyInline.class); + Asserts.assertTrue(emptyField.empty.isEmpty()); + System.out.println(emptyField.empty.isEmpty()); + + // Regular instance with an empty field inside + WithEmptyField w = new WithEmptyField(); + Asserts.assertEquals(w.empty.getClass(), EmptyInline.class); + Asserts.assertTrue(w.empty.isEmpty()); + w.empty = new EmptyInline(); + Asserts.assertEquals(w.empty.getClass(), EmptyInline.class); + Asserts.assertTrue(w.empty.isEmpty()); + + // Create an array of empty inlines + EmptyInline[] emptyArray = (EmptyInline[])ValueClass.newNullRestrictedNonAtomicArray(EmptyInline.class, 100, new EmptyInline()); + for(EmptyInline element : emptyArray) { + Asserts.assertEquals(element.getClass(), EmptyInline.class); + Asserts.assertTrue(element.isEmpty()); + } + + // Testing arrayCopy + EmptyInline[] array2 = (EmptyInline[])ValueClass.newNullRestrictedNonAtomicArray(EmptyInline.class, 100, new EmptyInline()); + // with two arrays + System.arraycopy(emptyArray, 10, array2, 20, 50); + for(EmptyInline element : array2) { + Asserts.assertEquals(element.getClass(), EmptyInline.class); + Asserts.assertTrue(element.isEmpty()); + } + // single array, no overlap + System.arraycopy(emptyArray, 10, emptyArray, 50, 20); + for(EmptyInline element : emptyArray) { + Asserts.assertEquals(element.getClass(), EmptyInline.class); + Asserts.assertTrue(element.isEmpty()); + } + // single array with overlap + System.arraycopy(emptyArray, 10, emptyArray, 20, 50); + for(EmptyInline element : emptyArray) { + Asserts.assertEquals(element.getClass(), EmptyInline.class); + Asserts.assertTrue(element.isEmpty()); + } + + // Passing an empty inline in argument + assert isEmpty(empty); + + // Returning an empty inline + assert getEmpty().isEmpty(); + + // Checking fields with reflection + Class c = empty.getClass(); + try { + Field[] fields = c.getDeclaredFields(); + Asserts.assertTrue(fields.length == 0); + } catch (Throwable t) { + t.printStackTrace(); + throw t; + } + WithEmptyField w0 = new WithEmptyField(); + Class c2 = w0.getClass(); + try { + Field emptyfield = c2.getDeclaredField("empty"); + EmptyInline e = (EmptyInline)emptyfield.get(w0); + Asserts.assertEquals(e.getClass(), EmptyInline.class); + Asserts.assertTrue(e.isEmpty()); + emptyfield.set(w0, new EmptyInline()); + e = (EmptyInline)emptyfield.get(w0); + Asserts.assertEquals(e.getClass(), EmptyInline.class); + Asserts.assertTrue(e.isEmpty()); + } catch(Throwable t) { + t.printStackTrace(); + throw new RuntimeException("Reflection tests failed: " + t); + } + + // Testing JIT compiler + // for(int i=0; i < 100000; i++) { + // test(); + // } + } + + static boolean isEmpty(EmptyInline empty) { + return empty.isEmpty(); + } + + static EmptyInline getEmpty() { + return new EmptyInline(); + } + + static void test() { + for(int i=0; i < 10000; i++) { + Asserts.assertTrue(getEmpty().isEmpty()); + } + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/FlatArraysTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/FlatArraysTest.java new file mode 100644 index 00000000000..28cc86d1b93 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/FlatArraysTest.java @@ -0,0 +1,556 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package runtime.valhalla.inlinetypes; + +import jdk.internal.misc.Unsafe; +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; + + + +import static jdk.test.lib.Asserts.*; + +/* + * @test FlatArraysTest + * @summary Plain array test for Inline Types + * @requires vm.flagless + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * java.base/jdk.internal.misc + * @library /test/lib + * @enablePreview + * @compile --source 26 FlatArraysTest.java + * @run main/othervm -XX:+UseArrayFlattening -XX:+UseFieldFlattening -XX:+UseAtomicValueFlattening -XX:+UseNullableValueFlattening runtime.valhalla.inlinetypes.FlatArraysTest + * @run main/othervm -XX:-UseArrayFlattening -XX:+UseAtomicValueFlattening -XX:+UseNullableValueFlattening runtime.valhalla.inlinetypes.FlatArraysTest + */ + +public class FlatArraysTest { + static final int ARRAY_SIZE = 100; + static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + @LooselyConsistentValue + static value class SmallValue { + byte b; + short s; + + SmallValue() { b = 0 ; s = 0; } + SmallValue(byte b0, short s0) { b = b0; s = s0; } + + public static Object getTestValue() { return new SmallValue(Byte.MIN_VALUE, Short.MIN_VALUE); } + + public static boolean expectingFlatNullRestrictedArray() { return true; } + public static boolean expectingFlatNullRestrictedAtomicArray() { return true; } + public static boolean expectingFlatNullableAtomicArray() { return true; } + } + + @LooselyConsistentValue + static value class MediumValue { + int x; + int y; + + MediumValue() { + x = 0; + y = 0; + } + MediumValue(int x0, int y0) { + x = x0; + y = y0; + } + + public static Object getTestValue() { + return new MediumValue(Integer.MIN_VALUE, Integer.MIN_VALUE); + } + + public static boolean expectingFlatNullRestrictedArray() { return true; } + public static boolean expectingFlatNullRestrictedAtomicArray() { return true; } + public static boolean expectingFlatNullableAtomicArray() { return false; } + } + + @LooselyConsistentValue + static value class BigValue { + long x; + long y; + long z; + + BigValue() { + x = 0; + y = 0; + z = 0; + } + BigValue(long x0, long y0, long z0) { + x = x0; + y = y0; + z = z0; + } + + public static Object getTestValue() { + return new BigValue(Long.MIN_VALUE, Long.MIN_VALUE, Long.MIN_VALUE); + } + + public static boolean expectingFlatNullRestrictedArray() { return true; } + public static boolean expectingFlatNullRestrictedAtomicArray() { return false; } + public static boolean expectingFlatNullableAtomicArray() { return false; } + } + + static void testNullFreeArray(Object[] array, Object value) { + testErrorCases(array); + assertNotNull(value, "Test needs a not null value"); + // Test 1 : check initial element value is not null + for (int i = 0 ; i < array.length; i++) { + assertNotNull(array[i], "Initial value must not be null"); + } + // Test 2 : try to write null + for (int i = 0 ; i < array.length; i++) { + try { + array[i] = null; + throw new RuntimeException("Missing NullPointerException"); + } catch (NullPointerException e) { } + } + // Test 3 : overwrite initial value with new value + for (int i = 0 ; i < array.length; i++) { + array[i] = value; + } + for (int i = 0 ; i < array.length; i++) { + assertEquals(array[i], value); + } + } + + static void testNullableArray(Object[] array, Object value) { + testErrorCases(array); + assertNotNull(value, "Test needs a not null value"); + // Test 1 : check that initial element value is null + System.gc(); + System.out.println("Test 1"); + for (int i = 0 ; i < array.length; i++) { + assertNull(array[i], "Initial value should be null"); + } + // Test 2 : write new value to all elements + System.gc(); + System.out.println("Test 2a"); + for (int i = 0 ; i < array.length; i++) { + array[i] = value; + assertEquals(array[i], value, "Value mismatch"); + } + System.gc(); + System.out.println("Test 2b"); + for (int i = 0 ; i < array.length; i++) { + assertEquals(array[i], value, "Value mismatch"); + } + // Test 3 : write null to all elements + System.gc(); + System.out.println("Test 3a"); + for (int i = 0 ; i < array.length; i++) { + array[i] = null; + } + System.gc(); + System.out.println("Test 3b"); + for (int i = 0 ; i < array.length; i++) { + assertNull(array[i], "Value mismatch"); + } + // Test 4 : write alternate null / not null values + System.gc(); + System.out.println("Test 4a"); + for (int i = 0 ; i < array.length; i++) { + if (i%2 == 0) { + array[i] = null; + } else { + array[i] = value; + } + } + System.gc(); + System.out.println("Test 4b"); + for (int i = 0 ; i < array.length; i++) { + if (i%2 == 0) { + assertNull(array[i], "Value mismatch"); + } else { + assertEquals(array[i], value, "Value mismatch"); + } + } + } + + static void testErrorCases(Object[] array) { + try { + Object o = array[-1]; + throw new RuntimeException("Missing IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException e) { } + + try { + Object o = array[array.length]; + throw new RuntimeException("Missing IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException e) { } + + assertTrue(array.getClass().getComponentType() != String.class, "Must be for the test"); + assertTrue(array.length > 0, "Must be for the test"); + try { + array[0] = new String("Bad"); + throw new RuntimeException("Missing ArrayStoreException"); + } catch (ArrayStoreException e) { } + } + + static void testArrayCopy() { + + Object[] objArray = new Object[ARRAY_SIZE]; + for (int i = 0; i < ARRAY_SIZE; i++) { + objArray[i] = SmallValue.getTestValue(); + } + SmallValue[] nonAtomicArray = (SmallValue[])ValueClass.newNullRestrictedNonAtomicArray(SmallValue.class, ARRAY_SIZE, new SmallValue()); + SmallValue[] atomicArray = (SmallValue[])ValueClass.newNullRestrictedAtomicArray(SmallValue.class, ARRAY_SIZE, new SmallValue()); + SmallValue[] nullableArray = (SmallValue[])ValueClass.newNullableAtomicArray(SmallValue.class, ARRAY_SIZE); + + // obj -> non-atomic + testArrayCopyInternal(objArray, nonAtomicArray); + + // obj -> atomic + testArrayCopyInternal(objArray, atomicArray); + + // obj -> nullable + testArrayCopyInternal(objArray, nullableArray); + + objArray[45] = null; + // obj with null -> non-atomic => NPE + try { + testArrayCopyInternal(objArray, nonAtomicArray); + throw new RuntimeException("Missing NullPointerException"); + } catch (NullPointerException e) { } + + // obj with null -> atomic => NPE + try { + testArrayCopyInternal(objArray, atomicArray); + throw new RuntimeException("Missing NullPointerException"); + } catch (NullPointerException e) { } + + // obj with null -> nullable + try { + testArrayCopyInternal(objArray, nullableArray); + } catch (NullPointerException e) { + throw new RuntimeException("Unexpected NullPointerException"); + } + + objArray[45] = new String("bad"); + // obj with wrong type value -> non-atomic => ASE + try { + testArrayCopyInternal(objArray, nonAtomicArray); + throw new RuntimeException("Missing ArrayStoreException"); + } catch (ArrayStoreException e) { } + + // obj with wrong type value -> atomic => ASE + try { + testArrayCopyInternal(objArray, atomicArray); + throw new RuntimeException("Missing ArrayStoreException"); + } catch (ArrayStoreException e) { } + + // obj with wrong type value -> nullable => ASE + try { + testArrayCopyInternal(objArray, nullableArray); + throw new RuntimeException("Missing ArrayStoreException"); + } catch (ArrayStoreException e) { } + + // Reset all arrays + objArray = new Object[ARRAY_SIZE]; + nonAtomicArray = (SmallValue[])ValueClass.newNullRestrictedNonAtomicArray(SmallValue.class, ARRAY_SIZE, new SmallValue()); + atomicArray = (SmallValue[])ValueClass.newNullRestrictedAtomicArray(SmallValue.class, ARRAY_SIZE, new SmallValue()); + nullableArray = (SmallValue[])ValueClass.newNullableAtomicArray(SmallValue.class, ARRAY_SIZE); + + // non-atomic -> obj + testArrayCopyInternal(nonAtomicArray, objArray); + + // non-atomic -> non-atomic + SmallValue[] nonAtomicArray2 = (SmallValue[])ValueClass.newNullRestrictedNonAtomicArray(SmallValue.class, ARRAY_SIZE, new SmallValue()); + testArrayCopyInternal(nonAtomicArray, nonAtomicArray2); + + // non-atomic -> non-atomic same array + testArrayCopyInternal(nonAtomicArray, nonAtomicArray); + + // non-atomic -> atomic + testArrayCopyInternal(nonAtomicArray, atomicArray); + + // non-atomic -> nullable + testArrayCopyInternal(nonAtomicArray, nullableArray); + + // Reset all arrays + objArray = new Object[ARRAY_SIZE]; + nonAtomicArray = (SmallValue[])ValueClass.newNullRestrictedNonAtomicArray(SmallValue.class, ARRAY_SIZE, new SmallValue()); + atomicArray = (SmallValue[])ValueClass.newNullRestrictedAtomicArray(SmallValue.class, ARRAY_SIZE, new SmallValue()); + nullableArray = (SmallValue[])ValueClass.newNullableAtomicArray(SmallValue.class, ARRAY_SIZE); + + for (int i = 0 ; i < ARRAY_SIZE; i++) { + atomicArray[i] = (SmallValue)SmallValue.getTestValue(); + } + + // atomic -> obj + testArrayCopyInternal(atomicArray, objArray); + + // atomic -> non-atomic + testArrayCopyInternal(atomicArray, nonAtomicArray); + + // atomic -> atomic + SmallValue[] atomicArray2 = (SmallValue[])ValueClass.newNullRestrictedAtomicArray(SmallValue.class, ARRAY_SIZE, new SmallValue()); + testArrayCopyInternal(atomicArray, atomicArray2); + + // atomic -> atomic same array + testArrayCopyInternal(atomicArray, atomicArray); + + // atomic -> nullable + testArrayCopyInternal(atomicArray, nullableArray); + + // Reset all arrays + objArray = new Object[ARRAY_SIZE]; + nonAtomicArray = (SmallValue[])ValueClass.newNullRestrictedNonAtomicArray(SmallValue.class, ARRAY_SIZE, new SmallValue()); + atomicArray = (SmallValue[])ValueClass.newNullRestrictedAtomicArray(SmallValue.class, ARRAY_SIZE, new SmallValue()); + nullableArray = (SmallValue[])ValueClass.newNullableAtomicArray(SmallValue.class, ARRAY_SIZE); + + for (int i = 0 ; i < ARRAY_SIZE; i++) { + nullableArray[i] = (SmallValue)SmallValue.getTestValue(); + } + + // nullable -> obj + testArrayCopyInternal(nullableArray, objArray); + + // nullable -> non-atomic + testArrayCopyInternal(nullableArray, nonAtomicArray); + + // nullable -> atomic + testArrayCopyInternal(nullableArray, atomicArray); + + // nullable -> nullable + SmallValue[] nullableArray2 = (SmallValue[])ValueClass.newNullableAtomicArray(SmallValue.class, ARRAY_SIZE); + testArrayCopyInternal(nullableArray, nullableArray2); + + // nullable -> nullable same array + testArrayCopyInternal(nullableArray, nullableArray); + + nullableArray[45] = null; + + // nullable with null -> obj + testArrayCopyInternal(nullableArray, objArray); + + // nullable with null -> non-atomic => NPE + try { + testArrayCopyInternal(nullableArray, nonAtomicArray); + throw new RuntimeException("Missing NullPointerException"); + } catch (NullPointerException e) { } + + // nullable with null -> atomic => NPE + try { + testArrayCopyInternal(nullableArray, atomicArray); + throw new RuntimeException("Missing NullPointerException"); + } catch (NullPointerException e) { } + + // nullable with null -> nullable + nullableArray2 = (SmallValue[])ValueClass.newNullableAtomicArray(SmallValue.class, ARRAY_SIZE); + testArrayCopyInternal(nullableArray, nullableArray2); + + // nullable with null -> nullable same array + testArrayCopyInternal(nullableArray, nullableArray); + } + + static void testArrayCopyInternal(Object[] src, Object[] dst) { + // When using this method for cases that should trigger a NPE or an ASE, + // it is recommended to put the faulty value at index 45 in the src array + assertTrue(src.length >= ARRAY_SIZE, "Must be for the test"); + assertTrue(dst.length >= ARRAY_SIZE, "Must be for the test"); + // Test 1 : good copy without indexes overlap + System.arraycopy(src, 3, dst, 51, 40); + for (int i = 0; i < 40; i++) { + assertEquals(src[3+i], dst[51+i], "Mismatch after copying"); + } + // Test 2 : good copy with indexes overlap + System.arraycopy(src, 42, dst, 53, 45); + if (src != dst) { // Verification doesn't make sense if src and dst are the same + for (int i = 0; i < 45; i++) { + assertEquals(src[42+i], dst[53+i], "Mismatch after copying"); + } + } + // Test 3 : IOOB errors + try { + System.arraycopy(src, -1, dst, 3, 10); + throw new RuntimeException("Missing IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException e) { } + try { + System.arraycopy(src, src.length - 5, dst, 3, 10); + throw new RuntimeException("Missing IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException e) { } + try { + System.arraycopy(src, 10, dst, -1, 10); + throw new RuntimeException("Missing IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException e) { } + try { + System.arraycopy(src, 10, dst, dst.length - 5, 10); + throw new RuntimeException("Missing IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException e) { } + } + + static void testArrayAccesses() throws NoSuchMethodException, InstantiationException, + IllegalAccessException, InvocationTargetException { + RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean(); + List arguments = runtimeMxBean.getInputArguments(); + boolean UseArrayFlattening = !arguments.contains("-XX:-UseArrayFlattening"); + System.out.println("UseArrayFlattening: " + UseArrayFlattening); + Class[] valueClasses = {SmallValue.class, MediumValue.class, BigValue.class}; + for (Class c: valueClasses) { + System.out.println("Testing class " + c.getName()); + Method gtv = c.getMethod("getTestValue", null); + Object o = gtv.invoke(null, null); + assertNotNull(o); + + System.out.println("Regular reference array"); + Object[] array = (Object[])Array.newInstance(c, ARRAY_SIZE); + Method ef = c.getMethod("expectingFlatNullableAtomicArray", null); + boolean expectFlat = (Boolean) ef.invoke(null, null); + assertTrue(ValueClass.isFlatArray(array) == (UseArrayFlattening && expectFlat)); + testNullableArray(array, o); + + System.out.println("NonAtomic NullRestricted array"); + array = ValueClass.newNullRestrictedNonAtomicArray(c, ARRAY_SIZE, c.newInstance()); + ef = c.getMethod("expectingFlatNullRestrictedArray", null); + expectFlat = (Boolean)ef.invoke(null, null); + assertTrue(ValueClass.isFlatArray(array) == (UseArrayFlattening && expectFlat)); + testNullFreeArray(array, o); + + System.out.println("NullRestricted Atomic array"); + array = ValueClass.newNullRestrictedAtomicArray(c, ARRAY_SIZE, c.newInstance()); + ef = c.getMethod("expectingFlatNullRestrictedAtomicArray", null); + expectFlat = (Boolean)ef.invoke(null, null); + assertTrue(ValueClass.isFlatArray(array) == (UseArrayFlattening && expectFlat)); + testNullFreeArray(array, o); + + System.out.println("Nullable Atomic array"); + array = ValueClass.newNullableAtomicArray(c, ARRAY_SIZE); + ef = c.getMethod("expectingFlatNullableAtomicArray", null); + expectFlat = (Boolean)ef.invoke(null, null); + assertTrue(ValueClass.isFlatArray(array) == (UseArrayFlattening && expectFlat)); + testNullableArray(array, o); + } + } + + static value class AtomicValue { + int i = 0; + } + + static value class FieldsHolder { + @NullRestricted + SmallValue sv = new SmallValue(); + + @NullRestricted + AtomicValue av = new AtomicValue(); + + AtomicValue nav = new AtomicValue(); + } + + static void testSpecialArrayLayoutFromArray(Object[] array, boolean expectException) { + int lk = UNSAFE.arrayLayout(array); + boolean exception = false; + try { + Object[] newArray = UNSAFE.newSpecialArray(array.getClass().getComponentType(), 10, lk); + int newLk = UNSAFE.arrayLayout(newArray); + assertEquals(newLk, lk); + } catch(IllegalArgumentException e) { + e.printStackTrace(); + exception = true; + } + assertEquals(exception, expectException, "Exception not matching expectations"); + } + + static void testSpecialArrayFromFieldLayout(Class c, int layout, boolean expectException) { + boolean exception = false; + try { + Object[] array = UNSAFE.newSpecialArray(c, 10, layout); + int lk = UNSAFE.arrayLayout(array); + assertEquals(lk, layout); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } catch (UnsupportedOperationException e) { + e.printStackTrace(); + exception = true; + } + assertEquals(exception, expectException, "Exception not matching expectations"); + } + + static void testSpecialArrayCreation() { + RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); + List jvmArgs = runtimeMXBean.getInputArguments(); + boolean arrayFlatteningEnabled = true; + for (String s : jvmArgs) { + if (s.compareTo("-XX:-UseArrayFlattening") == 0) arrayFlatteningEnabled = false; + } + + // Test array creation from another array + Object[] array0 = new SmallValue[10]; + testSpecialArrayLayoutFromArray(array0, !arrayFlatteningEnabled); + if (arrayFlatteningEnabled) { + Object[] array1 = ValueClass.newNullRestrictedNonAtomicArray(SmallValue.class, 10, new SmallValue()); + testSpecialArrayLayoutFromArray(array1, false); + Object[] array2 = ValueClass.newNullRestrictedAtomicArray(SmallValue.class, 10, new SmallValue()); + testSpecialArrayLayoutFromArray(array2, false); + Object[] array3 = ValueClass.newNullableAtomicArray(SmallValue.class, 10); + testSpecialArrayLayoutFromArray(array3, false); + } + + // Test array creation from a field layout + try { + Class c = FieldsHolder.class; + Field f0 = c.getDeclaredField("sv"); + int layout0 = UNSAFE.fieldLayout(f0); + testSpecialArrayFromFieldLayout(f0.getType(), layout0, !arrayFlatteningEnabled); + Field f1 = c.getDeclaredField("av"); + int layout1 = UNSAFE.fieldLayout(f1); + testSpecialArrayFromFieldLayout(f1.getType(), layout1, !arrayFlatteningEnabled); + Field f2 = c.getDeclaredField("nav"); + int layout2 = UNSAFE.fieldLayout(f2); + testSpecialArrayFromFieldLayout(f2.getType(), layout2, !arrayFlatteningEnabled); + } catch(NoSuchFieldException e) { + e.printStackTrace(); + } + + // Testing an invalid layout value + boolean exception = false; + try { + UNSAFE.newSpecialArray(SmallValue.class, 10, 100); + } catch(IllegalArgumentException e) { + e.printStackTrace(); + exception = true; + } + assertEquals(exception, true, "Exception not received"); + } + + public static void main(String[] args) throws NoSuchMethodException, InstantiationException, + IllegalAccessException, InvocationTargetException { + testArrayAccesses(); + testArrayCopy(); + testSpecialArrayCreation(); + } + + } diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/FlattenableSemanticTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/FlattenableSemanticTest.java new file mode 100644 index 00000000000..7d74bd23f48 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/FlattenableSemanticTest.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package runtime.valhalla.inlinetypes; + + +import jdk.test.lib.Asserts; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + + +/* + * @test + * @summary Flattenable field semantic test + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FlattenableSemanticTest.java + * @run main/othervm -XX:+UseFieldFlattening runtime.valhalla.inlinetypes.FlattenableSemanticTest + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:ForceNonTearable=* runtime.valhalla.inlinetypes.FlattenableSemanticTest + */ +public class FlattenableSemanticTest { + + @LooselyConsistentValue + static value class Point { + final int x = 0; + final int y = 0; + } + + @LooselyConsistentValue + public static value class JumboInline { + final long l0 = 0; + final long l1 = 1; + final long l2 = 2; + final long l3 = 3; + final long l4 = 4; + final long l5 = 5; + final long l6 = 6; + final long l7 = 7; + final long l8 = 8; + final long l9 = 9; + final long l10 = 10; + final long l11 = 11; + final long l12 = 12; + final long l13 = 13; + final long l14 = 14; + final long l15 = 15; + final long l16 = 16; + final long l17 = 17; + final long l18 = 18; + final long l19 = 19; + } + + static Point nfsp; + @Strict + @NullRestricted + static Point fsp = new Point(); + + Point nfip; + @Strict + @NullRestricted + Point fip; + + static JumboInline nfsj; + @Strict + @NullRestricted + static JumboInline fsj = new JumboInline(); + + JumboInline nfij; + @Strict + @NullRestricted + JumboInline fij; + + static Object getNull() { + return null; + } + + FlattenableSemanticTest() { + fip = new Point(); + fij = new JumboInline(); + } + + public static void main(String[] args) { + FlattenableSemanticTest test = new FlattenableSemanticTest(); + + // Uninitialized inline fields must be null for non flattenable fields + Asserts.assertNull(nfsp, "Invalid non null value for uninitialized non flattenable field"); + Asserts.assertNull(nfsj, "Invalid non null value for uninitialized non flattenable field"); + Asserts.assertNull(test.nfip, "Invalid non null value for uninitialized non flattenable field"); + Asserts.assertNull(test.nfij, "Invalid non null value for uninitialized non flattenable field"); + + // fsp.equals(null); + + // Uninitialized inline fields must be non null for flattenable fields + Asserts.assertNotNull(fsp, "Invalid null value for uninitialized flattenable field"); + Asserts.assertNotNull(fsj, "Invalid null value for uninitialized flattenable field"); + Asserts.assertNotNull(test.fip, "Invalid null value for uninitialized flattenable field"); + Asserts.assertNotNull(test.fij, "Invalid null value for uninitialized flattenable field"); + + // Assigning null must be allowed for non flattenable inline fields + boolean exception = true; + try { + nfsp = (Point)getNull(); + nfsp = null; + exception = false; + } catch (NullPointerException e) { + exception = true; + } + Asserts.assertFalse(exception, "Invalid NPE when assigning null to a non flattenable field"); + + try { + nfsj = (JumboInline)getNull(); + nfsj = null; + exception = false; + } catch (NullPointerException e) { + exception = true; + } + Asserts.assertFalse(exception, "Invalid NPE when assigning null to a non flattenable field"); + + try { + test.nfip = (Point)getNull(); + test.nfip = null; + exception = false; + } catch (NullPointerException e) { + exception = true; + } + Asserts.assertFalse(exception, "Invalid NPE when assigning null to a non flattenable field"); + + try { + test.nfij = (JumboInline)getNull(); + test.nfij = null; + exception = false; + } catch (NullPointerException e) { + exception = true; + } + Asserts.assertFalse(exception, "Invalid NPE when assigning null to a non flattenable field"); + + // Assigning null to a flattenable inline field must trigger a NPE + exception = false; + try { + fsp = (Point)getNull(); + } catch(NullPointerException e) { + exception = true; + } + Asserts.assertTrue(exception, "NPE not thrown when assigning null to a flattenable field"); + exception = false; + try { + fsj = (JumboInline)getNull(); + } catch(NullPointerException e) { + exception = true; + } + Asserts.assertTrue(exception, "NPE not thrown when assigning null to a flattenable field"); + exception = false; + try { + test.fip = (Point)getNull(); + } catch(NullPointerException e) { + exception = true; + } + Asserts.assertTrue(exception, "NPE not thrown when assigning null to a flattenable field"); + exception = false; + try { + test.fij = (JumboInline)getNull(); + } catch(NullPointerException e) { + exception = true; + } + Asserts.assertTrue(exception, "NPE not thrown when assigning null to a flattenable field"); + exception = false; + } + +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/HiddenInlineClassTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/HiddenInlineClassTest.java new file mode 100644 index 00000000000..3226821bd04 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/HiddenInlineClassTest.java @@ -0,0 +1,76 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Test a hidden inline class. + * @library /test/lib + * @enablePreview + * @run main/othervm HiddenInlineClassTest + */ + +import java.io.File; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class HiddenInlineClassTest { + static final Path CLASSES_DIR = Paths.get(System.getProperty("test.classes", ".")); + + static byte[] readClassFile(String classFileName) throws Exception { + Path path = CLASSES_DIR.resolve(classFileName.replace('.', File.separatorChar) + ".class"); + return Files.readAllBytes(path); + } + + public static void main(String[] args) throws Throwable { + Lookup lookup = MethodHandles.lookup(); + byte[] bytes = readClassFile("HiddenPoint"); + + // Define a hidden class that is an inline type. + Class c = lookup.defineHiddenClass(bytes, true).lookupClass(); + Object hp = c.newInstance(); + String s = (String)c.getMethod("getValue").invoke(hp); + if (!s.equals("x: 0, y: 0")) { + throw new RuntimeException( + "wrong value returned by method getValue() in inline hidden class: " + s); + } + } + +} + +value class HiddenPoint { + int x; + int y; + + HiddenPoint() { + this.x = 0; + this.y = 0; + } + + public String getValue() { + return "x: " + x + ", y: " + y; + } +} + diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/Ifacmp.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/Ifacmp.java new file mode 100644 index 00000000000..f8fbc773cf7 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/Ifacmp.java @@ -0,0 +1,216 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package runtime.valhalla.inlinetypes; + +import java.lang.ref.*; +import jdk.internal.vm.annotation.LooselyConsistentValue; + + +/* + * @test Ifacmp + * @requires vm.gc == null + * @summary if_acmpeq/ne bytecode test + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile --source 26 Ifacmp.java + * @run main/othervm -Xms16m -Xmx16m -XX:+UseSerialGC runtime.valhalla.inlinetypes.Ifacmp + */ +public class Ifacmp { + + @LooselyConsistentValue + static value class MyValue { + int value; + public MyValue(int v) { this.value = v; } + }; + + @LooselyConsistentValue + static value class MyValue2 { + int value; + public MyValue2(int v) { this.value = v; } + }; + + boolean acmpModeInlineAlwaysFalse = false; + + Object aNull = null; + Object bNull = null; + + Object aObject = new String("Hi"); + Object bObject = new String("Hi"); + + Object aValue = new MyValue(1); + Object bValue = new MyValue(1); + Object cValue = new MyValue(0); + Object aValue2 = new MyValue2(4711); + + Object[][] equalUseCases = { + { aNull, bNull }, + { aObject, aObject }, + { aValue, bValue }, + { cValue, cValue }, + { aValue2, aValue2 } + }; + + int objectEqualsUseCases = 2; // Nof object equals use cases + + // Would just generate these fail case from the "equal set" above, + // but to do so needs ==, so write out by hand it is... + Object[][] notEqualUseCases = { + { aNull, aObject }, + { aNull, bObject }, + { aNull, aValue }, + { aNull, bValue }, + { aNull, cValue }, + { aNull, aValue2 }, + { aObject, bObject }, + { aObject, aValue }, + { aObject, bValue }, + { aObject, cValue }, + { aObject, aValue2 }, + { bObject, cValue }, + { bObject, aValue2 }, + { aValue, cValue }, + { aValue, aValue2 }, + }; + + public Ifacmp() { this(false); } + public Ifacmp(boolean acmpModeInlineAlwaysFalse) { + this.acmpModeInlineAlwaysFalse = acmpModeInlineAlwaysFalse; + if (acmpModeInlineAlwaysFalse) { + System.out.println("ifacmp always false for inline types"); + } else { + System.out.println("ifacmp substitutability inline types"); + } + } + + public void test() { + testAllUseCases(); + } + + public void testUntilGc(int nofGc) { + for (int i = 0; i < nofGc; i++) { + System.out.println("GC num " + (i + 1)); + testUntilGc(); + } + } + + public void testUntilGc() { + Reference ref = new WeakReference(new Object(), new ReferenceQueue<>()); + do { + test(); + } while (ref.get() != null); + } + + public void testAllUseCases() { + int useCase = 0; + for (Object[] pair : equalUseCases) { + useCase++; + boolean equal = acmpModeInlineAlwaysFalse ? (useCase <= objectEqualsUseCases) : true; + checkEqual(pair[0], pair[1], equal); + } + for (Object[] pair : notEqualUseCases) { + checkEqual(pair[0], pair[1], false); + } + testLocalValues(); + testAlot(); + } + + public void testValues() { + checkEqual(aValue, bValue, true); + + checkEqual(aValue, cValue, false); + checkEqual(aValue, aValue2, false); + checkEqual(aValue2, bValue, false); + checkEqual(aValue2, cValue, false); + testLocalValues(); + } + + public void testLocalValues() { + // "aload + ifacmp" should be same as "aaload + ifamcp" + // but let's be paranoid... + MyValue a = new MyValue(11); + MyValue b = new MyValue(11); + MyValue c = a; + MyValue a1 = new MyValue(7); + MyValue2 a2 = new MyValue2(13); + + if (acmpModeInlineAlwaysFalse) { + if (a == b) throw new RuntimeException("Always false fail " + a + " == " + b); + if (a == c) throw new RuntimeException("Always false fail " + a + " == " + c); + } else { + if (a != b) throw new RuntimeException("Substitutability test failed" + a + " != " + b); + if (a != c) throw new RuntimeException("Substitutability test failed"); + } + if (a == a1) throw new RuntimeException(); + checkEqual(a, a2, false); + } + + public void testAlot() { + MyValue a = new MyValue(4711); + Reference ref = new WeakReference(new Object(), new ReferenceQueue<>()); + do { + for (int i = 0; i < 1000; i++) { + MyValue b = new MyValue(4711); + if (acmpModeInlineAlwaysFalse) { + if (a == b) throw new RuntimeException("Always false fail " + a + " == " + b); + } else { + if (a != b) throw new RuntimeException("Substitutability test failed" + a + " != " + b); + } + } + System.gc(); + } while (ref.get() != null); + } + + boolean shouldEqualSelf(Object a) { + return acmpModeInlineAlwaysFalse ? (!(a != null && a.getClass().isValue())) : true; + } + + void checkEqual(Object a, Object b, boolean isEqual) { + testEquals(a, a, shouldEqualSelf(a)); + testEquals(b, b, shouldEqualSelf(b)); + testEquals(a, b, isEqual); + testNotEquals(a, b, !isEqual); + } + + public static void testEquals(Object a, Object b, boolean expected) { + boolean isEqual = (a == b); + if (isEqual != expected) { + throw new RuntimeException("Expected " + expected + " : " + + a + " == " + b); + } + } + + public static void testNotEquals(Object a, Object b, boolean expected) { + boolean isNotEqual = (a != b); + if (isNotEqual != expected) { + throw new RuntimeException("Expected " + expected + " : " + + a + " != " + b); + } + } + + public static void main(String[] args) { + boolean inlineTypesAlwaysFalse = (args.length > 0) && args[0].equals("alwaysFalse"); + new Ifacmp(inlineTypesAlwaysFalse).test(); + new Ifacmp(inlineTypesAlwaysFalse).testUntilGc(3); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineOops.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineOops.java new file mode 100644 index 00000000000..142bffa5806 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineOops.java @@ -0,0 +1,705 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package runtime.valhalla.inlinetypes; + +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.lang.invoke.*; +import java.lang.ref.*; +import java.util.concurrent.*; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import static jdk.test.lib.Asserts.*; +import jdk.test.lib.Utils; +import jdk.test.whitebox.WhiteBox; +import runtime.valhalla.inlinetypes.InlineOops.FooValue; +import test.java.lang.invoke.lib.InstructionHelper; +import static test.java.lang.invoke.lib.InstructionHelper.classDesc; + +/** + * @test id=Serial + * @requires vm.gc.Serial + * @summary Test embedding oops into Inline types + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @library /test/lib /test/jdk/java/lang/invoke/common + * @enablePreview + * @requires vm.flagless + * @compile Person.java InlineOops.java + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -XX:+UseSerialGC -Xmx128m -XX:+UseFieldFlattening + * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * runtime.valhalla.inlinetypes.InlineOops + */ + +/** + * @test id=G1 + * @requires vm.gc.G1 + * @summary Test embedding oops into Inline types + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @library /test/lib /test/jdk/java/lang/invoke/common + * @enablePreview + * @requires vm.flagless + * @compile Person.java InlineOops.java + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -XX:+UseG1GC -Xmx128m -XX:+UseFieldFlattening + * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * runtime.valhalla.inlinetypes.InlineOops 20 + */ + +/** + * @test id=Parallel + * @requires vm.gc.Parallel + * @summary Test embedding oops into Inline types + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @library /test/lib /test/jdk/java/lang/invoke/common + * @enablePreview + * @requires vm.flagless + * @compile Person.java InlineOops.java + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -XX:+UseParallelGC -Xmx128m -XX:+UseFieldFlattening + * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * runtime.valhalla.inlinetypes.InlineOops + */ + +/** + * @test id=Z + * @requires vm.gc.Z + * @summary Test embedding oops into Inline types + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @library /test/lib /test/jdk/java/lang/invoke/common + * @enablePreview + * @requires vm.flagless + * @compile Person.java InlineOops.java + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -Xmx128m + * -XX:+UnlockDiagnosticVMOptions -XX:+UseFieldFlattening + * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * runtime.valhalla.inlinetypes.InlineOops + */ + +/** + * @test id=ZXint + * @requires vm.gc.Z + * @summary Test embedding oops into Inline types (sanity check with interpreter only the most complex GC) + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @library /test/lib /test/jdk/java/lang/invoke/common + * @enablePreview + * @requires vm.flagless + * @compile Person.java InlineOops.java + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xint -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -Xmx128m + * -XX:+UnlockDiagnosticVMOptions -XX:+UseFieldFlattening + * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * runtime.valhalla.inlinetypes.InlineOops + */ +public class InlineOops { + + // Extra debug: -XX:+VerifyOops -XX:+VerifyStack -XX:+VerifyLastFrame -XX:+VerifyBeforeGC -XX:+VerifyAfterGC -XX:+VerifyDuringGC -XX:VerifySubSet=threads,heap + // Even more debugging: -XX:+TraceNewOopMapGeneration -Xlog:gc*=info + + static final int NOF_PEOPLE = 10000; // Exercise arrays of this size + + static int MIN_ACTIVE_GC_COUNT = 10; // Run active workload for this number of GC passes + + static int MED_ACTIVE_GC_COUNT = 4; // Medium life span in terms of GC passes + + static final String TEST_STRING1 = "Test String 1"; + static final String TEST_STRING2 = "Test String 2"; + + static WhiteBox WB = WhiteBox.getWhiteBox(); + + static boolean USE_COMPILER = WB.getBooleanVMFlag("UseCompiler"); + + static MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + public static void main(String[] args) { + if (args.length > 0) { + MIN_ACTIVE_GC_COUNT = Integer.parseInt(args[0]); + } + testClassLoad(); + testValues(); + + if (!USE_COMPILER) { + testOopMaps(); + } + + // Check we survive GC... + testOverGc(); // Exercise root scan / oopMap + testActiveGc(); // Brute force + } + + /** + * Test ClassFileParser can load inline types with reference fields + */ + public static void testClassLoad() { + String s = Person.class.toString(); + new Bar(); + new BarWithValue(); + s = BarValue.class.toString(); + s = ObjectWithObjectValue.class.toString(); + s = ObjectWithObjectValues.class.toString(); + } + + + static class Couple { + @Strict + @NullRestricted + public Person onePerson = new Person(0, null, null); + @Strict + @NullRestricted + public Person otherPerson = new Person(0, null, null); + } + + @LooselyConsistentValue + static value class Composition { + @Strict + @NullRestricted + public Person onePerson; + @Strict + @NullRestricted + public Person otherPerson; + + public Composition(Person onePerson, Person otherPerson) { + this.onePerson = onePerson; + this.otherPerson = otherPerson; + } + } + + /** + * Check inline type operations with "Valhalla Inline Types" (VVT) + */ + public static void testValues() { + // Exercise creation, getfield, vreturn with null refs + validateDefaultPerson(createDefaultPerson()); + + // anewarray, aaload, aastore + int index = 7; + Person[] array = (Person[])ValueClass.newNullRestrictedNonAtomicArray(Person.class, NOF_PEOPLE, new Person(0, null, null)); + validateDefaultPerson(array[index]); + + // Now with refs... + validateIndexedPerson(createIndexedPerson(index), index); + array[index] = createIndexedPerson(index); + validateIndexedPerson(array[index], index); + + // Check the neighbours + validateDefaultPerson(array[index - 1]); + validateDefaultPerson(array[index + 1]); + + // getfield/putfield + Couple couple = new Couple(); + validateDefaultPerson(couple.onePerson); + validateDefaultPerson(couple.otherPerson); + + couple.onePerson = createIndexedPerson(index); + validateIndexedPerson(couple.onePerson, index); + + Composition composition = new Composition(couple.onePerson, couple.onePerson); + validateIndexedPerson(composition.onePerson, index); + validateIndexedPerson(composition.otherPerson, index); + } + + /** + * Check oop map generation for klass layout and frame... + */ + public static void testOopMaps() { + Object[] objects = WB.getObjectsViaKlassOopMaps(new Couple()); + assertTrue(objects.length == 4, "Expected 4 oops"); + for (int i = 0; i < objects.length; i++) { + assertTrue(objects[i] == null, "not-null"); + } + + String fn1 = "Sam"; + String ln1 = "Smith"; + String fn2 = "Jane"; + String ln2 = "Jones"; + Couple couple = new Couple(); + couple.onePerson = new Person(0, fn1, ln1); + couple.otherPerson = new Person(1, fn2, ln2); + objects = WB.getObjectsViaKlassOopMaps(couple); + assertTrue(objects.length == 4, "Expected 4 oops"); + assertTrue(objects[0] == fn1, "Bad oop fn1"); + assertTrue(objects[1] == ln1, "Bad oop ln1"); + assertTrue(objects[2] == fn2, "Bad oop fn2"); + assertTrue(objects[3] == ln2, "Bad oop ln2"); + + objects = WB.getObjectsViaOopIterator(couple); + assertTrue(objects.length == 4, "Expected 4 oops"); + assertTrue(objects[0] == fn1, "Bad oop fn1"); + assertTrue(objects[1] == ln1, "Bad oop ln1"); + assertTrue(objects[2] == fn2, "Bad oop fn2"); + assertTrue(objects[3] == ln2, "Bad oop ln2"); + + // Array.. + objects = WB.getObjectsViaOopIterator(createPeople()); + assertTrue(objects.length == NOF_PEOPLE * 2, "Unexpected length: " + objects.length); + int o = 0; + for (int i = 0; i < NOF_PEOPLE; i++) { + assertTrue(objects[o++].equals(firstName(i)), "Bad firstName"); + assertTrue(objects[o++].equals(lastName(i)), "Bad lastName"); + } + + // Sanity check, FixMe need more test cases + objects = testFrameOops(couple); + assertTrue(objects.length == 5, "Number of frame oops incorrect = " + objects.length); + assertTrue(objects[0] == couple, "Bad oop 0"); + assertTrue(objects[1] == fn1, "Bad oop 1"); + assertTrue(objects[2] == ln1, "Bad oop 2"); + assertTrue(objects[3] == TEST_STRING1, "Bad oop 3"); + assertTrue(objects[4] == TEST_STRING2, "Bad oop 4"); + + testFrameOopsVBytecodes(); + } + + static final String GET_OOP_MAP_NAME = "getOopMap"; + static final MethodTypeDesc GET_OOP_MAP_DESC = MethodTypeDesc.ofDescriptor("()[Ljava/lang/Object;"); + + public static Object[] getOopMap() { + Object[] oopMap = WB.getObjectsViaFrameOopIterator(2); + /* Remove this frame (class mirror for this method), and above class mirror */ + Object[] trimmedOopMap = new Object[oopMap.length - 2]; + System.arraycopy(oopMap, 2, trimmedOopMap, 0, trimmedOopMap.length); + return trimmedOopMap; + } + + // Expecting Couple couple, Person couple.onePerson, and Person (created here) + public static Object[] testFrameOops(Couple couple) { + int someId = 89898; + Person person = couple.onePerson; + assertTrue(person.getId() == 0, "Bad Person"); + Person anotherPerson = new Person(someId, TEST_STRING1, TEST_STRING2); + assertTrue(anotherPerson.getId() == someId, "Bad Person"); + return getOopMap(); + } + + // Debug... + static void dumpOopMap(Object[] oopMap) { + System.out.println("Oop Map len: " + oopMap.length); + for (int i = 0; i < oopMap.length; i++) { + System.out.println("[" + i + "] = " + oopMap[i]); + } + } + + /** + * Just some check sanity checks with aconst_init, withfield, astore and aload + * + * Changes to javac slot usage may well break this test + */ + public static void testFrameOopsVBytecodes() { + int nofOopMaps = 4; + Object[][] oopMaps = new Object[nofOopMaps][]; + String[] inputArgs = new String[] { "aName", "aDescription", "someNotes" }; + + FooValue.testFrameOopsDefault(oopMaps); + + // Test-D0 Slots=R Stack=Q(RRR)RV + assertTrue(oopMaps[0].length == 5 && + oopMaps[0][1] == null && + oopMaps[0][2] == null && + oopMaps[0][3] == null, "Test-D0 incorrect"); + + // Test-D1 Slots=R Stack=RV + assertTrue(oopMaps[1].length == 2, "Test-D1 incorrect"); + + // Test-D2 Slots=RQ(RRR) Stack=RV + assertTrue(oopMaps[2].length == 5 && + oopMaps[2][1] == null && + oopMaps[2][2] == null && + oopMaps[2][3] == null, "Test-D2 incorrect"); + + // Test-D3 Slots=R Stack=Q(RRR)RV + assertTrue(oopMaps[3].length == 6 && + oopMaps[3][1] == null && + oopMaps[3][2] == null && + oopMaps[3][3] == null && + oopMaps[3][4] == null, "Test-D3 incorrect"); + + // With ref fields... + String name = "TestName"; + String desc = "TestDesc"; + String note = "TestNotes"; + FooValue.testFrameOopsRefs(name, desc, note, oopMaps); + + // Test-R0 Slots=RR Stack=Q(RRR)RV + assertTrue(oopMaps[0].length == 6 && + oopMaps[0][2] == name && + oopMaps[0][3] == desc && + oopMaps[0][4] == note, "Test-R0 incorrect"); + + /** + * TODO: vwithfield from method handle cooked from anonymous class within the inline class + * even with "MethodHandles.privateLookupIn()" will fail final putfield rules + */ + } + + /** + * Check forcing GC for combination of VT on stack/LVT etc works + */ + public static void testOverGc() { + try { + Class vtClass = Person.class; + + System.out.println("vtClass="+vtClass); + + doGc(); + + // VT on stack and lvt, null refs, see if GC flies + MethodHandle moveValueThroughStackAndLvt = InstructionHelper.buildMethodHandle( + LOOKUP, + "gcOverPerson", + MethodType.methodType(vtClass, vtClass), + CODE->{ + CODE + .aload(0) + .invokestatic(classDesc(InlineOops.class), "doGc", MethodTypeDesc.ofDescriptor("()V")) // Stack + .astore(0) + .invokestatic(classDesc(InlineOops.class), "doGc", MethodTypeDesc.ofDescriptor("()V")) // LVT + .aload(0) + .astore(1024) // LVT wide index + .aload(1024) + .iconst_1() // push a litte further down + .invokestatic(classDesc(InlineOops.class), "doGc", MethodTypeDesc.ofDescriptor("()V")) // Stack,LVT + .pop() + .areturn(); + }); + Person person = (Person) moveValueThroughStackAndLvt.invokeExact(createDefaultPerson()); + validateDefaultPerson(person); + doGc(); + + int index = 4711; + person = (Person) moveValueThroughStackAndLvt.invokeExact(createIndexedPerson(index)); + validateIndexedPerson(person, index); + doGc(); + person = createDefaultPerson(); + doGc(); + } + catch (Throwable t) { fail("testOverGc", t); } + } + + static void submitNewWork(ForkJoinPool fjPool) { + for (int j = 0; j < 100; j++) { + fjPool.execute(InlineOops::testValues); + } + } + + static void sleepNoThrow(long ms) { + try { + Thread.sleep(ms); + } + catch (Throwable t) {} + } + + /** + * Run some workloads with different object/value life times... + */ + public static void testActiveGc() { + try { + int nofThreads = 1; + + Object longLivedObjects = createLongLived(); + Object longLivedPeople = createPeople(); + + Object medLivedObjects = createLongLived(); + Object medLivedPeople = createPeople(); + + doGc(); + + // Setup some background work, where GC roots are stack local only, short lifetimes... + ForkJoinPool fjPool = new ForkJoinPool(nofThreads, ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true); + + // Work on this stack's long and medium lived objects + for (int nofActiveGc = 0; nofActiveGc < MIN_ACTIVE_GC_COUNT; nofActiveGc++) { + // Medium lifetime, check and renew + if (nofActiveGc % MED_ACTIVE_GC_COUNT == 0) { + validateLongLived(medLivedObjects); + validatePeople(medLivedPeople); + + medLivedObjects = createLongLived(); + medLivedPeople = createPeople(); + } + // More short lived background, if needed + if (!fjPool.hasQueuedSubmissions()) { + submitNewWork(fjPool); + } + // Forced, synchronous GC + doGc(); + } + + fjPool.shutdown(); + + validateLongLived(medLivedObjects); + validatePeople(medLivedPeople); + medLivedObjects = null; + medLivedPeople = null; + + validateLongLived(longLivedObjects); + validatePeople(longLivedPeople); + + longLivedObjects = null; + longLivedPeople = null; + + doGc(); + } + catch (Throwable t) { fail("testActiveGc", t); } + } + + static final ReferenceQueue REFQ = new ReferenceQueue<>(); + + public static void doGc() { + WB.fullGC(); + } + + static void validatePerson(Person person, int id, String fn, String ln, boolean equals) { + assertTrue(person.id == id); + if (equals) { + assertTrue(fn.equals(person.getFirstName()), "Invalid field firstName"); + assertTrue(ln.equals(person.getLastName()), "Invalid field lastName"); + } + else { + assertTrue(person.getFirstName() == fn, "Invalid field firstName"); + assertTrue(person.getLastName() == ln, "Invalid field lastName"); + } + } + + static Person createIndexedPerson(int i) { + return new Person(i, firstName(i), lastName(i)); + } + + static void validateIndexedPerson(Person person, int i) { + validatePerson(person, i, firstName(i), lastName(i), true); + } + + static Person createDefaultPerson() { + return (Person)ValueClass.newNullRestrictedNonAtomicArray(Person.class, 1, new Person(0, null, null))[0]; + } + + static void validateDefaultPerson(Person person) { + validatePerson(person, 0, null, null, false); + } + + static String firstName(int i) { + return "FirstName-" + i; + } + + static String lastName(int i) { + return "LastName-" + i; + } + + static Object createLongLived() throws Throwable { + Object[] population = new Object[1]; + population[0] = createPeople(); + return population; + } + + static void validateLongLived(Object pop) throws Throwable { + Object[] population = (Object[]) pop; + validatePeople(population[0]); + } + + static Object createPeople() { + int arrayLength = NOF_PEOPLE; + Person[] people = new Person[arrayLength]; + for (int i = 0; i < arrayLength; i++) { + people[i] = createIndexedPerson(i); + } + return people; + } + + static void validatePeople(Object array) { + Person[] people = (Person[]) array; + int arrayLength = people.length; + assertTrue(arrayLength == NOF_PEOPLE); + for (int i = 0; i < arrayLength; i++) { + validateIndexedPerson(people[i], i); + } + } + + // Various field layouts...sanity testing, see MVTCombo testing for full-set + + @LooselyConsistentValue + static value class ObjectValue { + final Object object; + + private ObjectValue(Object obj) { + object = obj; + } + } + + static class ObjectWithObjectValue { + ObjectValue value1; + Object ref1; + } + + static class ObjectWithObjectValues { + ObjectValue value1; + ObjectValue value2; + Object ref1; + } + + static class Foo { + int id; + String name; + String description; + long timestamp; + String notes; + } + + static class Bar extends Foo { + long extendedId; + String moreNotes; + int count; + String otherStuff; + } + + @LooselyConsistentValue + public static value class FooValue { + public final int id; + public final String name; + public final String description; + public final long timestamp; + public final String notes; + + public FooValue() { + id = 0; + name = null; + description = null; + timestamp = 0L; + notes = null; + } + + public FooValue(int id, String name, String description, long timestamp, String notes) { + this.id = id; + this.name = name; + this.description = description; + this.timestamp = timestamp; + this.notes = notes; + } + + public static void testFrameOopsDefault(Object[][] oopMaps) { + MethodType mt = MethodType.methodType(Void.TYPE, oopMaps.getClass()); + int oopMapsSlot = 0; + int vtSlot = 1; + + // Slots 1=oopMaps + // OopMap Q=RRR (.name .description .someNotes) + try { + InstructionHelper.buildMethodHandle( + LOOKUP, "exerciseVBytecodeExprStackWithDefault", mt, + CODE->{ + CODE + .new_(classDesc(FooValue.class)) + .dup() + .invokespecial(classDesc(FooValue.class), "", MethodTypeDesc.ofDescriptor("()V")) + .aload(oopMapsSlot) + .iconst_0() // Test-D0 Slots=R Stack=Q(RRR)RV + .invokestatic(classDesc(InlineOops.class), GET_OOP_MAP_NAME, GET_OOP_MAP_DESC) + .aastore() + .pop() + .aload(oopMapsSlot) + .iconst_1() // Test-D1 Slots=R Stack=RV + .invokestatic(classDesc(InlineOops.class), GET_OOP_MAP_NAME, GET_OOP_MAP_DESC) + .aastore() + .new_(classDesc(FooValue.class)) + .dup() + .invokespecial(classDesc(FooValue.class), "", MethodTypeDesc.ofDescriptor("()V")) + .astore(vtSlot) + .aload(oopMapsSlot) + .iconst_2() // Test-D2 Slots=RQ(RRR) Stack=RV + .invokestatic(classDesc(InlineOops.class), GET_OOP_MAP_NAME, GET_OOP_MAP_DESC) + .aastore() + .aload(vtSlot) + .aconst_null() + .astore(vtSlot) // Storing null over the Q slot won't remove the ref, but should be single null ref + .aload(oopMapsSlot) + .iconst_3() // Test-D3 Slots=R Stack=Q(RRR)RV + .invokestatic(classDesc(InlineOops.class), GET_OOP_MAP_NAME, GET_OOP_MAP_DESC) + .aastore() + .pop() + .return_(); + }).invoke(oopMaps); + } catch (Throwable t) { fail("exerciseVBytecodeExprStackWithDefault", t); } + } + + public static void testFrameOopsRefs(String name, String description, String notes, Object[][] oopMaps) { + FooValue f = new FooValue(4711, name, description, 9876543231L, notes); + FooValue[] fa = (FooValue[])ValueClass.newNullRestrictedNonAtomicArray(FooValue.class, 1, new FooValue()); + fa[0] = f; + MethodType mt = MethodType.methodType(Void.TYPE, fa.getClass(), oopMaps.getClass()); + int fooArraySlot = 0; + int oopMapsSlot = 1; + try { + InstructionHelper.buildMethodHandle(LOOKUP, "exerciseVBytecodeExprStackWithRefs", mt, + CODE->{ + CODE + .aload(fooArraySlot) + .iconst_0() + .aaload() + .aload(oopMapsSlot) + .iconst_0() // Test-R0 Slots=RR Stack=Q(RRR)RV + .invokestatic(classDesc(InlineOops.class), GET_OOP_MAP_NAME, GET_OOP_MAP_DESC) + .aastore() + .pop() + .return_(); + }).invoke(fa, oopMaps); + } catch (Throwable t) { fail("exerciseVBytecodeExprStackWithRefs", t); } + } + } + + static class BarWithValue { + FooValue foo; + long extendedId; + String moreNotes; + int count; + String otherStuff; + } + + @LooselyConsistentValue + static value class BarValue { + @Strict + @NullRestricted + FooValue foo; + long extendedId; + String moreNotes; + int count; + String otherStuff; + + private BarValue(FooValue f, long extId, String mNotes, int c, String other) { + foo = f; + extendedId = extId; + moreNotes = mNotes; + count = c; + otherStuff = other; + } + } + +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineTypeArray.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineTypeArray.java new file mode 100644 index 00000000000..2007df2d54d --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineTypeArray.java @@ -0,0 +1,869 @@ +/* + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package runtime.valhalla.inlinetypes; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; + +import static jdk.test.lib.Asserts.*; + +/* + * @test InlineTypeArray + * @summary Plain array test for Inline Types + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @library /test/lib + * @enablePreview + * @compile --source 26 InlineTypeArray.java Point.java Long8Inline.java Person.java + * @run main/othervm -XX:+UseArrayFlattening -XX:+UseFieldFlattening runtime.valhalla.inlinetypes.InlineTypeArray + * @run main/othervm -XX:-UseArrayFlattening runtime.valhalla.inlinetypes.InlineTypeArray + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:ForceNonTearable=* runtime.valhalla.inlinetypes.InlineTypeArray + * @run main/othervm -XX:+UseArrayFlattening -XX:+UseFieldFlattening -XX:+UseNullableValueFlattening runtime.valhalla.inlinetypes.InlineTypeArray + */ +public class InlineTypeArray { + public static void main(String[] args) { + InlineTypeArray inlineTypeArray = new InlineTypeArray(); + inlineTypeArray.run(); + } + + public void run() { + testClassForName(); + testSimplePointArray(); + testLong8Array(); + testMixedPersonArray(); + testMultiDimPointArray(); + testComposition(); + + testSanityCheckcasts(); + testObjectArrayOfInlines(); + + testReflectArray(); + testUtilArraysOnNullRestrictedNonAtomicArrays(); + testUtilArraysOnNullRestrictedAtomicArrays(); + testUtilArraysOnNullableAtomicArrays(); + + testInlineArrayOom(); + } + + void testClassForName() { + String arrayClsName = "[Lruntime.valhalla.inlinetypes.Point;"; + try { + Class arrayCls = Class.forName(arrayClsName); + assertTrue(arrayCls.isArray(), "Expected an array class"); + + arrayClsName = "[" + arrayClsName; + Class mulArrayCls = Class.forName(arrayClsName); + assertTrue(mulArrayCls.isArray()); + assertTrue(mulArrayCls.getComponentType() == arrayCls); + } + catch (ClassNotFoundException cnfe) { + fail("Class.forName(" + arrayClsName + ") failed", cnfe); + } + } + + void testSimplePointArray() { + Point[] defaultPoint = (Point[])ValueClass.newNullRestrictedNonAtomicArray(Point.class, 1, new Point(0, 0)); + Point p = defaultPoint[0]; + assertEquals(p.x, 0, "invalid default loaded from array"); + assertEquals(p.y, 0, "invalid default loaded from array"); + boolean gotNpe = false; + try { + defaultPoint[0] = (Point) getNull(); + } catch (NullPointerException npe) { + gotNpe = true; + } + assertTrue(gotNpe, "Expected NullPointerException"); + + Point[] points = createSimplePointArray(); + System.gc(); // check that VTs survive GC + checkSimplePointArray(points); + assertTrue(points instanceof Point[], "Instance of"); + + testSimplePointArrayCopy(); + + // Locked/unlocked flat array type checks + points = createSimplePointArray(); + Point[] pointsCopy = (Point[])ValueClass.newNullRestrictedNonAtomicArray(Point.class, points.length, new Point(0, 0)); + synchronized (points) { + assertTrue(points instanceof Point[], "Instance of"); + checkSimplePointArray(points); + System.arraycopy(points, 0, pointsCopy, 0, points.length); + synchronized (pointsCopy) { + assertTrue(pointsCopy instanceof Point[], "Instance of"); + checkSimplePointArray(pointsCopy); + System.gc(); + } + System.gc(); + } + assertTrue(pointsCopy instanceof Point[], "Instance of"); + checkSimplePointArray(pointsCopy); + } + + void testSimplePointArrayCopy() { + Point[] points = createSimplePointArray(); + Point[] pointsCopy = (Point[])ValueClass.newNullRestrictedNonAtomicArray(Point.class, points.length, new Point(0, 0)); + System.arraycopy(points, 0, pointsCopy, 0, points.length); + checkSimplePointArray(pointsCopy); + + // Conjoint, overlap...left + System.arraycopy(points, 0, points, 1, 2); + checkArrayElementsEqual(points, new Point[] { pointsCopy[0], pointsCopy[0], pointsCopy[1], pointsCopy[3] }); + + // Conjoint, overlap...right + points = createSimplePointArray(); + System.arraycopy(points, 2, points, 1, 2); + checkArrayElementsEqual(points, new Point[] { pointsCopy[0], pointsCopy[2], pointsCopy[3], pointsCopy[3] }); + } + + static Point[] createSimplePointArray() { + Point[] ps = (Point[])ValueClass.newNullRestrictedNonAtomicArray(Point.class, 4, new Point(0, 0)); + assertEquals(ps.length, 4, "Length"); + ps.toString(); + ps[0] = new Point(1, 2); + ps[1] = new Point(3, 4); + ps[2] = new Point(5, 6); + ps[3] = new Point(7, 8); + boolean sawOob = false; + try { + ps[ps.length] = new Point(0, 0); + } catch (ArrayIndexOutOfBoundsException aioobe) { sawOob = true; } + assertTrue(sawOob, "Didn't see AIOOBE"); + return ps; + } + + static void checkSimplePointArray(Point[] points) { + assertEquals(points[0].x, 1, "invalid 0 point x value"); + assertEquals(points[0].y, 2, "invalid 0 point y value"); + assertEquals(points[1].x, 3, "invalid 1 point x value"); + assertEquals(points[1].y, 4, "invalid 1 point y value"); + assertEquals(points[2].x, 5, "invalid 2 point x value"); + assertEquals(points[2].y, 6, "invalid 2 point y value"); + assertEquals(points[3].x, 7, "invalid 3 point x value"); + assertEquals(points[3].y, 8, "invalid 3 point y value"); + } + + void testLong8Array() { + Long8Inline[] values = (Long8Inline[])ValueClass.newNullRestrictedNonAtomicArray(Long8Inline.class, 3, new Long8Inline()); + assertEquals(values.length, 3, "length"); + values.toString(); + Long8Inline value = values[1]; + long zl = 0; + Long8Inline.check(value, zl, zl, zl, zl, zl, zl, zl, zl); + values[1] = new Long8Inline(1, 2, 3, 4, 5, 6, 7, 8); + value = values[1]; + Long8Inline.check(value, 1, 2, 3, 4, 5, 6, 7, 8); + + Long8Inline[] copy = (Long8Inline[])ValueClass.newNullRestrictedNonAtomicArray(Long8Inline.class, values.length, new Long8Inline()); + System.arraycopy(values, 0, copy, 0, values.length); + value = copy[1]; + Long8Inline.check(value, 1, 2, 3, 4, 5, 6, 7, 8); + } + + void testMixedPersonArray() { + Person[] people = (Person[])ValueClass.newNullRestrictedNonAtomicArray(Person.class, 3, new Person(0, null, null)); + + people[0] = new Person(1, "First", "Last"); + assertEquals(people[0].getId(), 1, "Invalid Id person"); + assertEquals(people[0].getFirstName(), "First", "Invalid First Name"); + assertEquals(people[0].getLastName(), "Last", "Invalid Last Name"); + + people[1] = new Person(2, "Jane", "Wayne"); + people[2] = new Person(3, "Bob", "Dobalina"); + + Person[] peopleCopy = (Person[])ValueClass.newNullRestrictedNonAtomicArray(Person.class, people.length, new Person(0, null, null)); + System.arraycopy(people, 0, peopleCopy, 0, people.length); + assertEquals(peopleCopy[2].getId(), 3, "Invalid Id"); + assertEquals(peopleCopy[2].getFirstName(), "Bob", "Invalid First Name"); + assertEquals(peopleCopy[2].getLastName(), "Dobalina", "Invalid Last Name"); + } + + void testMultiDimPointArray() { + /* + Point[][][] multiPoints = new Point[2][3][4]; + assertEquals(multiPoints.length, 2, "1st dim length"); + assertEquals(multiPoints[0].length, 3, "2st dim length"); + assertEquals(multiPoints[0][0].length, 4, "3rd dim length"); + + Point defaultPoint = multiPoints[1][2][3]; + assertEquals(defaultPoint.x, 0, "invalid point x value"); + assertEquals(defaultPoint.y, 0, "invalid point x value"); + */ + } + + void testReflectArray() { + // Check the java.lang.reflect.Array.newInstance methods... + Class cls = (Class) Point[].class; + Point[][] array = (Point[][]) Array.newInstance(cls, 1); + assertEquals(array.length, 1, "Incorrect length"); + assertTrue(array[0] == null, "Expected NULL"); + + Point[][][] array3 = (Point[][][]) Array.newInstance(cls, 1, 2); + assertEquals(array3.length, 1, "Incorrect length"); + assertEquals(array3[0].length, 2, "Incorrect length"); + assertTrue(array3[0][0] == null, "Expected NULL"); + + // Now create ObjArrays of InlineArray... + Point[][] barray = (Point[][]) Array.newInstance(Point.class, 1, 2); + assertEquals(barray.length, 1, "Incorrect length"); + assertEquals(barray[0].length, 2, "Incorrect length"); + barray[0][1] = new Point(1, 2); + Point pb = barray[0][1]; + int x = pb.getX(); + assertEquals(x, 1, "Bad Point Value"); + } + + @LooselyConsistentValue + static value class MyInt implements Comparable { + int value; + + private MyInt() { this(0); } + private MyInt(int v) { value = v; } + public int getValue() { return value; } + public String toString() { return "MyInt: " + getValue(); } + public int compareTo(MyInt that) { return Integer.compare(this.getValue(), that.getValue()); } + public boolean equals(Object o) { + if (o instanceof MyInt) { + return this.getValue() == ((MyInt) o).getValue(); + } + return false; + } + + public static MyInt create(int v) { + return new MyInt(v); + } + + public static final MyInt MIN = MyInt.create(Integer.MIN_VALUE); + public static final MyInt ZERO = MyInt.create(0); + public static final MyInt MAX = MyInt.create(Integer.MAX_VALUE); + } + + static MyInt staticMyInt; + static MyInt[] staticMyIntArray; + static MyInt[][] staticMyIntArrayArray; + + static { + staticMyInt = MyInt.create(-1); + staticMyIntArray = (MyInt[])ValueClass.newNullRestrictedNonAtomicArray(MyInt.class, 1, new MyInt()); + staticMyIntArray[0] = staticMyInt; + staticMyIntArrayArray = new MyInt[][] { staticMyIntArray, staticMyIntArray }; + } + + static value class MyShorts implements Comparable { + short s0, s1; + + private MyShorts() { this((short)0, (short)0); } + private MyShorts(short sa, short sb) { s0 = sa; s1 = sb; } + public short getS0() { return s0; } + public short getS1() { return s1; } + public String toString() { return "MyShorts: " + getS0() + " " + getS1(); } + public int compareTo(MyShorts that) { + int r = Short.compare(this.getS0(), that.getS0()); + return r != 0 ? r : Short.compare(this.getS1(), that.getS1()); + } + public boolean equals(Object o) { + if (o instanceof MyShorts) { + return this.getS0() == ((MyShorts) o).getS0() && this.getS1() == ((MyShorts) o).getS1(); + } + return false; + } + + public static MyShorts create(short s0, short s1) { + return new MyShorts(s0, s1); + } + + public static final MyShorts MIN = MyShorts.create(Short.MIN_VALUE, Short.MIN_VALUE); + public static final MyShorts ZERO = MyShorts.create((short)0, (short)0); + public static final MyShorts MAX = MyShorts.create(Short.MAX_VALUE, Short.MAX_VALUE); + } + + static interface SomeSecondaryType { + default String hi() { return "Hi"; } + } + + @LooselyConsistentValue + static value class MyOtherInt implements SomeSecondaryType { + final int value; + private MyOtherInt() { value = 0; } + } + + void testSanityCheckcasts() { + MyInt[] myInts = (MyInt[])ValueClass.newNullRestrictedNonAtomicArray(MyInt.class, 1, new MyInt()); + assertTrue(myInts instanceof Object[]); + assertTrue(myInts instanceof Comparable[]); + assertTrue(myInts instanceof MyInt[]); + + Class cls = MyInt.class; + assertTrue(cls.isValue()); + Object arrObj = Array.newInstance(cls, 1); + assertTrue(arrObj instanceof Object[], "Not Object array"); + assertTrue(arrObj instanceof Comparable[], "Not Comparable array"); + assertTrue(arrObj instanceof MyInt[], "Not MyInt array"); + + Object[] arr = (Object[]) arrObj; + assertTrue(arr instanceof Comparable[], "Not Comparable array"); + assertTrue(arr instanceof MyInt[], "Not MyInt array"); + Comparable[] comparables = (Comparable[])arr; + MyInt[] myIntArr = (MyInt[]) arr; + + // multi-dim, check secondary array types are setup... + MyOtherInt[][] matrix = new MyOtherInt[1][1]; + assertTrue(matrix[0] instanceof MyOtherInt[]); + assertTrue(matrix[0] instanceof SomeSecondaryType[]); + assertTrue(matrix[0] instanceof MyOtherInt[]); + + // Box types vs Inline... + MyInt[] myValueRefs = new MyInt[1]; + assertTrue(myValueRefs instanceof MyInt[]); + assertTrue(myValueRefs instanceof Object[]); + assertTrue(myValueRefs instanceof Comparable[]); + + MyInt[][] myMdValueRefs = new MyInt[1][1]; + assertTrue(myMdValueRefs[0] instanceof MyInt[]); + assertTrue(myMdValueRefs[0] instanceof Object[]); + assertTrue(myMdValueRefs[0] instanceof Comparable[]); + + // Did we break checkcast... + MyInt[] va1 = (MyInt[])null; + MyInt[] va2 = null; + MyInt[][] va3 = (MyInt[][])null; + MyInt[][][] va4 = (MyInt[][][])null; + } + + + void testUtilArraysOnNullRestrictedNonAtomicArrays() { + // Sanity check j.u.Arrays + + // Testing Arrays.copyOf() + MyInt[] myInts = (MyInt[])ValueClass.newNullRestrictedNonAtomicArray(MyInt.class, 3, new MyInt()); + myInts[0] = MyInt.MAX; + myInts[1] = MyInt.MIN; + myInts[2] = MyInt.ZERO; + + // Copy of same length, must work + MyInt[] copyMyInts = (MyInt[]) Arrays.copyOf(myInts, myInts.length); + MyInt[] expected = (MyInt[])ValueClass.newNullRestrictedNonAtomicArray(MyInt.class, 3, new MyInt()); + expected[0] = myInts[0]; + expected[1] = myInts[1]; + expected[2] = myInts[2]; + checkArrayElementsEqual(copyMyInts, expected); + + // Copy of shorter length, must work + MyInt[] smallCopyMyInts = (MyInt[]) Arrays.copyOf(myInts, myInts.length - 1); + MyInt[] expected2 = (MyInt[])ValueClass.newNullRestrictedNonAtomicArray(MyInt.class, 2, new MyInt()); + expected2[0] = myInts[0]; + expected2[1] = myInts[1]; + checkArrayElementsEqual(smallCopyMyInts, expected2); + + // Copy of zero length on a zero-length array, must work + IllegalArgumentException iae = null; + MyShorts[] zeroCopyMyShorts = (MyShorts[])ValueClass.newNullRestrictedNonAtomicArray(MyShorts.class, 0, new MyShorts()); + try { + MyShorts[] res = (MyShorts[]) Arrays.copyOf(zeroCopyMyShorts, 0); + } catch (IllegalArgumentException e) { + iae = e; + } + assertTrue(iae == null, "Unexpected exception"); + + // Copy of bigger length, must fail for null-restricted arrays + try { + MyInt[] bigCopyMyInts = (MyInt[]) Arrays.copyOf(myInts, myInts.length + 1); + } catch (IllegalArgumentException e) { + iae = e; + } + assertTrue(iae != null, "Exception not received"); + + // Testing Arrays.copyOfRange() + MyInt[] fullRangeCopy = (MyInt[]) Arrays.copyOfRange(myInts, 0, myInts.length); + checkArrayElementsEqual(copyMyInts, expected); + + MyInt[] beginningRangeCopy = (MyInt[]) Arrays.copyOfRange(myInts, 0, 2); + checkArrayElementsEqual(beginningRangeCopy, expected2); + + + MyInt[] endingRangeCopy = (MyInt[]) Arrays.copyOfRange(myInts, 1, myInts.length); + MyInt[] expected3 = (MyInt[])ValueClass.newNullRestrictedNonAtomicArray(MyInt.class, 2, new MyInt()); + expected3[0] = myInts[1]; + expected3[1] = myInts[2]; + checkArrayElementsEqual(endingRangeCopy, expected3); + + // Range exceeding initial array's length, must fail for null-restricted arrays + iae = null; + try { + MyInt[] exceedingRangeCopy = (MyInt[]) Arrays.copyOfRange(myInts, 1, myInts.length + 1); + } catch (IllegalArgumentException e) { + iae = e; + } + assertTrue(iae != null, "Exception not received"); + + // Range starting after the end of the original array, must fail for null-restricted arrays + iae = null; + try { + MyInt[] farRangeCopy = (MyInt[]) Arrays.copyOfRange(myInts, myInts.length, myInts.length + 1); + } catch (IllegalArgumentException e) { + iae = e; + } + assertTrue(iae != null, "Exception not received"); + + Arrays.sort(copyMyInts); + expected = (MyInt[])ValueClass.newNullRestrictedNonAtomicArray(MyInt.class, 3, new MyInt()); + expected[0] = (MyInt) MyInt.MIN; + expected[1] = (MyInt) MyInt.ZERO; + expected[2] = (MyInt) MyInt.MAX; + checkArrayElementsEqual(copyMyInts, expected); + + List myIntList = Arrays.asList(copyMyInts); + + MyInt[] dest = (MyInt[])ValueClass.newNullRestrictedNonAtomicArray(MyInt.class, copyMyInts.length, new MyInt()); + checkArrayElementsEqual(copyMyInts, myIntList.toArray(dest)); + // This next line needs testMixedLayoutArrays to work + checkArrayElementsEqual(copyMyInts, myIntList.toArray()); + + // Sanity check j.u.ArrayList + ArrayList aList = new ArrayList(Arrays.asList(copyMyInts)); + assertTrue(aList.indexOf(MyInt.MIN) == 0, "Bad Index"); + assertTrue(aList.indexOf(MyInt.ZERO) == 1, "Bad Index"); + assertTrue(aList.indexOf(MyInt.MAX) == 2, "Bad Index"); + + aList.remove(2); + aList.add(MyInt.create(5)); + } + + void testUtilArraysOnNullRestrictedAtomicArrays() { + // Sanity check j.u.Arrays + + // Testing Arrays.copyOf() + MyShorts[] myShorts = (MyShorts[])ValueClass.newNullRestrictedAtomicArray(MyShorts.class, 3, new MyShorts()); + myShorts[0] = MyShorts.MAX; + myShorts[1] = MyShorts.MIN; + myShorts[2] = MyShorts.ZERO; + + // Copy of same length, must work + MyShorts[] copyMyInts = (MyShorts[]) Arrays.copyOf(myShorts, myShorts.length); + MyShorts[] expected = (MyShorts[])ValueClass.newNullRestrictedAtomicArray(MyShorts.class, 3, new MyShorts()); + expected[0] = myShorts[0]; + expected[1] = myShorts[1]; + expected[2] = myShorts[2]; + checkArrayElementsEqual(copyMyInts, expected); + + // Copy of shorter length, must work + MyShorts[] smallCopyMyInts = (MyShorts[]) Arrays.copyOf(myShorts, myShorts.length - 1); + MyShorts[] expected2 = (MyShorts[])ValueClass.newNullRestrictedAtomicArray(MyShorts.class, 2, new MyShorts()); + expected2[0] = myShorts[0]; + expected2[1] = myShorts[1]; + checkArrayElementsEqual(smallCopyMyInts, expected2); + + // Copy of zero length on a zero-length array, must work + IllegalArgumentException iae = null; + MyShorts[] zeroCopyMyShorts = (MyShorts[])ValueClass.newNullRestrictedAtomicArray(MyShorts.class, 0, new MyShorts()); + try { + MyShorts[] res = (MyShorts[]) Arrays.copyOf(zeroCopyMyShorts, 0); + } catch (IllegalArgumentException e) { + iae = e; + } + assertTrue(iae == null, "Unexpected exception"); + + // Copy of bigger length, must fail for null-restricted arrays + try { + MyShorts[] bigCopyMyInts = (MyShorts[]) Arrays.copyOf(myShorts, myShorts.length + 1); + } catch (IllegalArgumentException e) { + iae = e; + } + assertTrue(iae != null, "Exception not received"); + + // Testing Arrays.copyOfRange() + MyShorts[] fullRangeCopy = (MyShorts[]) Arrays.copyOfRange(myShorts, 0, myShorts.length); + checkArrayElementsEqual(copyMyInts, expected); + + MyShorts[] beginningRangeCopy = (MyShorts[]) Arrays.copyOfRange(myShorts, 0, 2); + checkArrayElementsEqual(beginningRangeCopy, expected2); + + + MyShorts[] endingRangeCopy = (MyShorts[]) Arrays.copyOfRange(myShorts, 1, myShorts.length); + MyShorts[] expected3 = (MyShorts[])ValueClass.newNullRestrictedAtomicArray(MyShorts.class, 2, new MyShorts()); + expected3[0] = myShorts[1]; + expected3[1] = myShorts[2]; + checkArrayElementsEqual(endingRangeCopy, expected3); + + // Range exceeding initial array's length, must fail for null-restricted arrays + iae = null; + try { + MyShorts[] exceedingRangeCopy = (MyShorts[]) Arrays.copyOfRange(myShorts, 1, myShorts.length + 1); + } catch (IllegalArgumentException e) { + iae = e; + } + assertTrue(iae != null, "Exception not received"); + + // Range starting after the end of the original array, must fail for null-restricted arrays + iae = null; + try { + MyShorts[] farRangeCopy = (MyShorts[]) Arrays.copyOfRange(myShorts, myShorts.length, myShorts.length + 1); + } catch (IllegalArgumentException e) { + iae = e; + } + assertTrue(iae != null, "Exception not received"); + + Arrays.sort(copyMyInts); + expected = (MyShorts[])ValueClass.newNullRestrictedAtomicArray(MyShorts.class, 3, new MyShorts()); + expected[0] = (MyShorts) MyShorts.MIN; + expected[1] = (MyShorts) MyShorts.ZERO; + expected[2] = (MyShorts) MyShorts.MAX; + checkArrayElementsEqual(copyMyInts, expected); + + List myIntList = Arrays.asList(copyMyInts); + + MyShorts[] dest = (MyShorts[])ValueClass.newNullRestrictedAtomicArray(MyShorts.class, copyMyInts.length, new MyShorts()); + checkArrayElementsEqual(copyMyInts, myIntList.toArray(dest)); + // This next line needs testMixedLayoutArrays to work + checkArrayElementsEqual(copyMyInts, myIntList.toArray()); + + // Sanity check j.u.ArrayList + ArrayList aList = new ArrayList(Arrays.asList(copyMyInts)); + assertTrue(aList.indexOf(MyShorts.MIN) == 0, "Bad Index"); + assertTrue(aList.indexOf(MyShorts.ZERO) == 1, "Bad Index"); + assertTrue(aList.indexOf(MyShorts.MAX) == 2, "Bad Index"); + + aList.remove(2); + aList.add(MyShorts.create((short)5, (short)7)); + } + + void testUtilArraysOnNullableAtomicArrays() { + // Sanity check j.u.Arrays + + // Testing Arrays.copyOf() + MyInt[] myInts = (MyInt[])ValueClass.newNullableAtomicArray(MyInt.class, 3); + myInts[0] = MyInt.MAX; + myInts[1] = MyInt.MIN; + myInts[2] = MyInt.ZERO; + + // Copy of same length, must work + MyInt[] copyMyInts = (MyInt[]) Arrays.copyOf(myInts, myInts.length); + MyInt[] expected = (MyInt[])ValueClass.newNullableAtomicArray(MyInt.class, 3); + expected[0] = myInts[0]; + expected[1] = myInts[1]; + expected[2] = myInts[2]; + checkArrayElementsEqual(copyMyInts, expected); + + // Copy of shorter length, must work + MyInt[] smallCopyMyInts = (MyInt[]) Arrays.copyOf(myInts, myInts.length - 1); + MyInt[] expected2 = (MyInt[])ValueClass.newNullableAtomicArray(MyInt.class, 2); + expected2[0] = myInts[0]; + expected2[1] = myInts[1]; + checkArrayElementsEqual(smallCopyMyInts, expected2); + + // Copy of bigger length, must work for nullable arrays + MyInt[] bigCopyMyInts = (MyInt[]) Arrays.copyOf(myInts, myInts.length + 1); + MyInt[] expected2b = (MyInt[])ValueClass.newNullableAtomicArray(MyInt.class, 4); + expected2b[0] = myInts[0]; + expected2b[1] = myInts[1]; + expected2b[2] = myInts[2]; + expected2b[3] = null; + checkArrayElementsEqual(bigCopyMyInts, expected2b); + + // Testing Arrays.copyOfRange() + MyInt[] fullRangeCopy = (MyInt[]) Arrays.copyOfRange(myInts, 0, myInts.length); + checkArrayElementsEqual(copyMyInts, expected); + + MyInt[] beginningRangeCopy = (MyInt[]) Arrays.copyOfRange(myInts, 0, 2); + checkArrayElementsEqual(beginningRangeCopy, expected2); + + MyInt[] endingRangeCopy = (MyInt[]) Arrays.copyOfRange(myInts, 1, myInts.length); + MyInt[] expected3 = (MyInt[])ValueClass.newNullableAtomicArray(MyInt.class, 2); + expected3[0] = myInts[1]; + expected3[1] = myInts[2]; + checkArrayElementsEqual(endingRangeCopy, expected3); + + // Range exceeding initial array's length, must succeed for nullable arrays + MyInt[] exceedingRangeCopy = (MyInt[]) Arrays.copyOfRange(myInts, 1, myInts.length + 1); + MyInt[] expected3b = (MyInt[])ValueClass.newNullableAtomicArray(MyInt.class, 3); + expected3b[0] = myInts[1]; + expected3b[1] = myInts[2]; + expected3b[2] = null; + checkArrayElementsEqual(exceedingRangeCopy, expected3b); + + // Range starting after the end of the original array, must suceed for nullable arrays + MyInt[] farRangeCopy = (MyInt[]) Arrays.copyOfRange(myInts, myInts.length, myInts.length + 1); + MyInt[] expected3c = (MyInt[])ValueClass.newNullableAtomicArray(MyInt.class, 1); + expected3c[0] = null; + checkArrayElementsEqual(farRangeCopy, expected3c); + + Arrays.sort(copyMyInts); + expected = (MyInt[])ValueClass.newNullableAtomicArray(MyInt.class, 3); + expected[0] = (MyInt) MyInt.MIN; + expected[1] = (MyInt) MyInt.ZERO; + expected[2] = (MyInt) MyInt.MAX; + checkArrayElementsEqual(copyMyInts, expected); + + List myIntList = Arrays.asList(copyMyInts); + + MyInt[] dest = (MyInt[])ValueClass.newNullableAtomicArray(MyInt.class, copyMyInts.length); + checkArrayElementsEqual(copyMyInts, myIntList.toArray(dest)); + // This next line needs testMixedLayoutArrays to work + checkArrayElementsEqual(copyMyInts, myIntList.toArray()); + + // Sanity check j.u.ArrayList + ArrayList aList = new ArrayList(Arrays.asList(copyMyInts)); + assertTrue(aList.indexOf(MyInt.MIN) == 0, "Bad Index"); + assertTrue(aList.indexOf(MyInt.ZERO) == 1, "Bad Index"); + assertTrue(aList.indexOf(MyInt.MAX) == 2, "Bad Index"); + + aList.remove(2); + aList.add(MyInt.create(5)); + } + + void testObjectArrayOfInlines() { + testSanityObjectArrays(); + testMixedLayoutArrays(); + } + + void testSanityObjectArrays() { + Object[] objects = new Object[2]; + assertTrue(objects[0] == null && objects[1] == null, "Not null ?"); + + objects[0] = MyInt.create(1); + objects[1] = Integer.valueOf(2); + assertTrue(objects[0].equals(MyInt.create(1)), "Bad Value"); + assertTrue(objects[1].equals(Integer.valueOf(2)), "Bad Object"); + + Comparable[] copyComparables = new Comparable[objects.length]; + System.arraycopy(objects, 0, copyComparables, 0, objects.length); + checkArrayElementsEqual(objects, copyComparables); + + objects[0] = null; + objects[1] = null; + assertTrue(objects[0] == null && objects[1] == null, "Not null ?"); + + Comparable[] comparables = new Comparable[2]; + assertTrue(comparables[0] == null && comparables[1] == null, "Not null ?"); + comparables[0] = MyInt.create(3); + comparables[1] = Integer.valueOf(4); + assertTrue(comparables[0].equals(MyInt.create(3)), "Bad Value"); + assertTrue(comparables[1].equals(Integer.valueOf(4)), "Bad Object"); + + Object[] copyObjects = new Object[2]; + System.arraycopy(comparables, 0, copyObjects, 0, comparables.length); + checkArrayElementsEqual(comparables, copyObjects); + + comparables[0] = null; + comparables[1] = null; + assertTrue(comparables[0] == null && comparables[1] == null, "Not null ?"); + + MyInt[] myIntRefArray = new MyInt[1]; + assertTrue(myIntRefArray[0] == null, "Got: " + myIntRefArray[0]); + myIntRefArray[0] = null; + + MyInt[] srcNulls = new MyInt[2]; + MyInt[] dstNulls = new MyInt[2]; + System.arraycopy(srcNulls, 0, dstNulls, 0, 2); + checkArrayElementsEqual(srcNulls, dstNulls); + srcNulls[1] = MyInt.create(1); + System.arraycopy(srcNulls, 0, dstNulls, 0, 2); + checkArrayElementsEqual(srcNulls, dstNulls); + + + // Locked/unlocked flat array type checks + synchronized (srcNulls) { + System.arraycopy(srcNulls, 0, dstNulls, 0, 2); + checkArrayElementsEqual(srcNulls, dstNulls); + System.gc(); + } + System.gc(); + checkArrayElementsEqual(srcNulls, dstNulls); + } + + void testMixedLayoutArrays() { + Object[] objArray = new Object[3]; + Comparable[] compArray = new Comparable[3]; + MyInt[] valArray = new MyInt[] { (MyInt) MyInt.MIN, (MyInt) MyInt.ZERO, (MyInt) MyInt.MAX }; + + arrayCopy(valArray, 0, objArray, 0, 3); + checkArrayElementsEqual(valArray, objArray); + arrayCopy(valArray, 0, objArray, 0, 3); + + objArray = new Object[3]; + System.arraycopy(valArray, 0, objArray, 0, 3); + checkArrayElementsEqual(valArray, objArray); + + System.arraycopy(valArray, 0, compArray, 0, 3); + checkArrayElementsEqual(valArray, compArray); + + valArray = (MyInt[])ValueClass.newNullRestrictedNonAtomicArray(MyInt.class, 3, new MyInt()); + valArray[0] = (MyInt) MyInt.ZERO; + valArray[1] = (MyInt) MyInt.ZERO; + valArray[2] = (MyInt) MyInt.ZERO; + System.arraycopy(compArray, 0, valArray, 0, 3); + checkArrayElementsEqual(valArray, compArray); + + valArray = (MyInt[])ValueClass.newNullRestrictedNonAtomicArray(MyInt.class, 3, new MyInt()); + valArray[0] = (MyInt) MyInt.ZERO; + valArray[1] = (MyInt) MyInt.ZERO; + valArray[2] = (MyInt) MyInt.ZERO; + System.arraycopy(objArray, 0, valArray, 0, 3); + checkArrayElementsEqual(valArray, objArray); + + // Sanity check dst == src + System.arraycopy(valArray, 0, valArray, 0, 3); + checkArrayElementsEqual(valArray, objArray); + + objArray[0] = "Not an inline object"; + try { + System.arraycopy(objArray, 0, valArray, 0, 3); + throw new RuntimeException("Expected ArrayStoreException"); + } catch (ArrayStoreException ase) {} + + MyInt[] myIntRefArray = new MyInt[3]; + System.arraycopy(valArray, 0, myIntRefArray, 0, 3); + checkArrayElementsEqual(valArray, myIntRefArray); + + myIntRefArray[0] = null; + try { + System.arraycopy(myIntRefArray, 0, valArray, 0, 3); + throw new RuntimeException("Expected NullPointerException"); + } catch (NullPointerException npe) {} + } + + @LooselyConsistentValue + static value class MyPoint { + @Strict + @NullRestricted + MyInt x; + @Strict + @NullRestricted + MyInt y; + + private MyPoint() { this(0, 0); } + private MyPoint(int x, int y) { + this.x = new MyInt(x); + this.y = new MyInt(y); + } + public boolean equals(Object that) { + if (that instanceof MyPoint) { + MyPoint thatPoint = (MyPoint) that; + return x.equals(thatPoint.x) && java.util.Objects.equals(y, thatPoint.y); + } + return false; + } + static MyPoint create(int x) { + return new MyPoint(x, x); + } + static MyPoint create(int x, int y) { + return new MyPoint(x, y); + } + @Strict + @NullRestricted + static final MyPoint ORIGIN = create(0); + } + + void testComposition() { + // Test array operations with compostion of inline types, check element payload is correct... + MyPoint a = MyPoint.create(1, 2); + MyPoint b = MyPoint.create(7, 21); + MyPoint c = MyPoint.create(Integer.MAX_VALUE, Integer.MIN_VALUE); + + MyPoint[] pts = (MyPoint[])ValueClass.newNullRestrictedNonAtomicArray(MyPoint.class, 3, new MyPoint()); + if (!pts[0].equals(MyPoint.ORIGIN)) { + throw new RuntimeException("Equals failed: " + pts[0] + " vs " + MyPoint.ORIGIN); + } + pts = (MyPoint[])ValueClass.newNullRestrictedNonAtomicArray(MyPoint.class, 3, new MyPoint()); + pts[0] = a; + pts[1] = b; + pts[2] = c; + checkArrayElementsEqual(pts, new Object[] { a, b, c}); + Object[] oarr = new Object[3]; + + arrayCopy(pts, 0, oarr, 0, 3); + checkArrayElementsEqual(pts, oarr); + + oarr = new Object[3]; + System.arraycopy(pts, 0, oarr, 0, 3); + checkArrayElementsEqual(pts, oarr); + + System.arraycopy(oarr, 0, pts, 0, 3); + checkArrayElementsEqual(pts, oarr); + + oarr = new Object[3]; + try { + System.arraycopy(oarr, 0, pts, 0, 3); + throw new RuntimeException("Expected NPE"); + } + catch (NullPointerException npe) {} + + oarr = new Object[3]; + oarr[0] = new Object(); + try { + System.arraycopy(oarr, 0, pts, 0, 3); + throw new RuntimeException("Expected ASE"); + } + catch (ArrayStoreException ase) {} + } + + void checkArrayElementsEqual(MyInt[] arr1, Object[] arr2) { + assertTrue(arr1.length == arr2.length, "Bad length"); + for (int i = 0; i < arr1.length; i++) { + assertTrue(java.util.Objects.equals(arr1[i], arr2[i]), "Element " + i + " not equal"); + } + } + + void checkArrayElementsEqual(MyPoint[] arr1, Object[] arr2) { + assertTrue(arr1.length == arr2.length, "Bad length"); + for (int i = 0; i < arr1.length; i++) { + assertTrue(java.util.Objects.equals(arr1[i], arr2[i]), "Element " + i + " not equal"); + } + } + + void checkArrayElementsEqual(Object[] arr1, Object[] arr2) { + assertTrue(arr1.length == arr2.length, "Bad length"); + for (int i = 0; i < arr1.length; i++) { + assertTrue(java.util.Objects.equals(arr1[i], arr2[i]), "Element " + i + " not equal"); + } + } + + void arrayCopy(MyInt[] src, int srcPos, Object[] dst, int dstPos, int length) { + for (int i = 0; i < length ; i++) { + dst[dstPos++] = src[srcPos++]; + } + } + void arrayCopy(MyPoint[] src, int srcPos, Object[] dst, int dstPos, int length) { + for (int i = 0; i < length ; i++) { + dst[dstPos++] = src[srcPos++]; + } + } + + Object getNull() { return null; } + + + void testInlineArrayOom() { + int size = Integer.MAX_VALUE; + try { + MyPoint[] pts = new MyPoint[size]; + throw new RuntimeException("Excepted OOM"); + } catch (OutOfMemoryError oom) {} + } + +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineTypeCreation.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineTypeCreation.java new file mode 100644 index 00000000000..a9a779a7dd7 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineTypeCreation.java @@ -0,0 +1,108 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package runtime.valhalla.inlinetypes; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.test.lib.Asserts; + +/* + * @test InlineTypeCreation + * @summary Inline Type creation test + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile InlineTypeCreation.java Point.java Long8Inline.java Person.java + * @run main/othervm runtime.valhalla.inlinetypes.InlineTypeCreation + */ +public class InlineTypeCreation { + public static void main(String[] args) { + InlineTypeCreation inlineTypeCreation = new InlineTypeCreation(); + inlineTypeCreation.run(); + } + + public void run() { + testPoint(); + testLong8(); + testPerson(); + StaticSelf.test(); + testUnresolvedAndResolvedNew(); + } + + void testPoint() { + Point p = new Point(1, 2); + Asserts.assertEquals(p.x, 1, "invalid point x value"); + Asserts.assertEquals(p.y, 2, "invalid point y value"); + Point p2 = clonePoint(p); + Asserts.assertEquals(p2.x, 1, "invalid point clone x value"); + Asserts.assertEquals(p2.y, 2, "invalid point clone y value"); + } + + static Point clonePoint(Point p) { + Point q = p; + return q; + } + + void testLong8() { + Long8Inline long8Inline = new Long8Inline(1, 2, 3, 4, 5, 6, 7, 8); + Asserts.assertEquals(long8Inline.getLongField1(), 1L, "Field 1 incorrect"); + Asserts.assertEquals(long8Inline.getLongField8(), 8L, "Field 8 incorrect"); + Long8Inline.check(long8Inline, 1, 2, 3, 4, 5, 6, 7, 8); + } + + void testPerson() { + Person person = new Person(1, "John", "Smith"); + Asserts.assertEquals(person.getId(), 1, "Id field incorrect"); + Asserts.assertEquals(person.getFirstName(), "John", "First name incorrect"); + Asserts.assertEquals(person.getLastName(), "Smith", "Last name incorrect"); + } + + @LooselyConsistentValue + static value class StaticSelf { + + static final StaticSelf DEFAULT = new StaticSelf(0); + int f1; + + public StaticSelf(int f1) { this.f1 = f1; } + public String toString() { return "StaticSelf f1=" + f1; } + + public static void test() { + String s = DEFAULT.toString(); + } + + } + + static value class MyPoint { + int x,y; + MyPoint(int x, int y) { + this.x = x; + this.y = y; + } + } + + // Two instantiations of the same class to exercise both the unresolved and resolved paths + // in bytecode 'new' implementation + void testUnresolvedAndResolvedNew(){ + MyPoint p1 = new MyPoint(10, 20); + MyPoint p2 = new MyPoint(20, 20); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineTypeDensity.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineTypeDensity.java new file mode 100644 index 00000000000..60df75cf0f3 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineTypeDensity.java @@ -0,0 +1,323 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +import java.lang.management.MemoryPoolMXBean; + +import com.sun.jdi.NativeMethodException; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import jdk.test.lib.Asserts; +import jdk.test.whitebox.WhiteBox; + + +/** + * @test InlineTypeDensity + * @summary Heap density test for InlineTypes + * @library /test/lib + * @requires vm.flagless + * @modules java.base/jdk.internal.vm.annotation + * java.base/jdk.internal.value + * @enablePreview + * @compile InlineTypeDensity.java + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -XX:+UseArrayFlattening -XX:+UseCompressedOops + * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI InlineTypeDensity + * @run main/othervm -XX:+UseArrayFlattening -XX:-UseCompressedOops + * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI InlineTypeDensity + * @run main/othervm -XX:+UseArrayFlattening + * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI InlineTypeDensity + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UseArrayFlattening + * -Xbootclasspath/a:. -XX:ForceNonTearable=* + * -XX:+WhiteBoxAPI InlineTypeDensity + */ + +public class InlineTypeDensity { + + private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + private static final boolean VM_FLAG_FORCENONTEARABLE = WHITE_BOX.getStringVMFlag("ForceNonTearable").equals("*"); + + public InlineTypeDensity() { + if (WHITE_BOX.getBooleanVMFlag("UseArrayFlattening") != true) { + throw new IllegalStateException("UseArrayFlattening should be true"); + } + } + + interface LocalDate { + public int getYear(); + public short getMonth(); + public short getDay(); + } + + interface LocalTime { + public byte getHour(); + public byte getMinute(); + public byte getSecond(); + public int getNano(); + } + + interface LocalDateTime extends LocalDate, LocalTime {} + + @LooselyConsistentValue + static value class LocalDateValue implements LocalDate { + final int year; + final short month; + final short day; + + public LocalDateValue(int year, short month, short day) { + this.year = year; + this.month = month; + this.day = day; + } + + public int getYear() { return year; } + public short getMonth() { return month; } + public short getDay() { return day; } + + } + + @LooselyConsistentValue + static value class LocalTimeValue implements LocalTime { + final byte hour; + final byte minute; + final byte second; + final int nano; + + public LocalTimeValue(byte hour, byte minute, byte second, int nano) { + this.hour = hour; + this.minute = minute; + this.second = second; + this.nano = nano; + } + + public byte getHour() { return hour; } + public byte getMinute() { return minute; } + public byte getSecond() { return second; } + public int getNano() { return nano; } + + } + + @LooselyConsistentValue + static value class LocalDateTimeValue implements LocalDateTime { + @Strict + @NullRestricted + LocalDateValue date; + @Strict + @NullRestricted + LocalTimeValue time; + + public LocalDateTimeValue(LocalDateValue date, LocalTimeValue time) { + this.date = date; + this.time = time; + } + + public int getYear() { return date.year; } + public short getMonth() { return date.month; } + public short getDay() { return date.day; } + + public byte getHour() { return time.hour; } + public byte getMinute() { return time.minute; } + public byte getSecond() { return time.second; } + public int getNano() { return time.nano; } + + } + + static final class LocalDateClass implements LocalDate { + final int year; + final short month; + final short day; + + LocalDateClass(int year, short month, short day) { + this.year = year; + this.month = month; + this.day = day; + } + + public int getYear() { return year; } + public short getMonth() { return month; } + public short getDay() { return day; } + } + + static final class LocalTimeClass implements LocalTime { + final byte hour; + final byte minute; + final byte second; + final int nano; + + LocalTimeClass(byte hour, byte minute, byte second, int nano) { + this.hour = hour; + this.minute = minute; + this.second = second; + this.nano = nano; + } + + public byte getHour() { return hour; } + public byte getMinute() { return minute; } + public byte getSecond() { return second; } + public int getNano() { return nano; } + } + + static final class LocalDateTimeClass implements LocalDateTime { + final LocalDateClass date; + final LocalTimeClass time; + + LocalDateTimeClass(LocalDateClass date, LocalTimeClass time) { + this.date = date; + this.time = time; + } + + public LocalDateClass getDate() { return date; } + public LocalTimeClass getTime() { return time; } + + public int getYear() { return date.year; } + public short getMonth() { return date.month; } + public short getDay() { return date.day; } + + public byte getHour() { return time.hour; } + public byte getMinute() { return time.minute; } + public byte getSecond() { return time.second; } + public int getNano() { return time.nano; } + } + + public void ensureArraySizeWin() { + int arrayLength = 1000; + System.out.println("ensureArraySizeWin for length " + arrayLength); + LocalDateTimeClass[] objectArray = new LocalDateTimeClass[arrayLength]; + for (int i = 0; i < arrayLength; i++) { + objectArray[i] = new LocalDateTimeClass(new LocalDateClass(0, (short)0, (short)0), + new LocalTimeClass((byte)0, (byte)0, (byte)0, 0)); + } + + long objectArraySize = WHITE_BOX.getObjectSize(objectArray); + System.out.println("Empty object array size: " + objectArraySize); + objectArraySize += (arrayLength * + (WHITE_BOX.getObjectSize(objectArray[0]) + + WHITE_BOX.getObjectSize(objectArray[0].getDate()) + + WHITE_BOX.getObjectSize(objectArray[0].getTime()))); + + LocalDateTimeValue[] flatArray = new LocalDateTimeValue[arrayLength]; + // CMH: add "isFlatValueArray" to WhiteBox API, to ensure we are correctly account size + + long flatArraySize = WHITE_BOX.getObjectSize(flatArray); + System.out.println("Object array and elements: " + objectArraySize + " versus Flat Array: " + flatArraySize); + Asserts.assertLessThan(flatArraySize, objectArraySize, "Flat array accounts for more heap than object array + elements !"); + } + + @LooselyConsistentValue + static value class MyByte { byte v = 0; } + @LooselyConsistentValue + static value class MyShort { short v = 0; } + @LooselyConsistentValue + static value class MyInt { int v = 0; } + @LooselyConsistentValue + static value class MyLong { long v = 0; } + + void assertArraySameSize(Object a, Object b, int nofElements) { + long aSize = WHITE_BOX.getObjectSize(a); + long bSize = WHITE_BOX.getObjectSize(b); + Asserts.assertEquals(aSize, bSize, + a + "(" + aSize + " bytes) not equivalent size " + + b + "(" + bSize + " bytes)" + + (nofElements >= 0 ? " (array of " + nofElements + " elements)" : "")); + } + + void testByteArraySizesSame(int[] testSizes) { + for (int testSize : testSizes) { + byte[] ba = new byte[testSize]; + MyByte[] mba = (MyByte[])ValueClass.newNullRestrictedNonAtomicArray(MyByte.class, testSize, new MyByte()); + assertArraySameSize(ba, mba, testSize); + } + } + + void testShortArraySizesSame(int[] testSizes) { + for (int testSize : testSizes) { + short[] sa = new short[testSize]; + MyShort[] msa = (MyShort[])ValueClass.newNullRestrictedNonAtomicArray(MyShort.class, testSize, new MyShort()); + assertArraySameSize(sa, msa, testSize); + } + } + + void testIntArraySizesSame(int[] testSizes) { + for (int testSize : testSizes) { + int[] ia = new int[testSize]; + MyInt[] mia = (MyInt[])ValueClass.newNullRestrictedNonAtomicArray(MyInt.class, testSize, new MyInt()); + assertArraySameSize(ia, mia, testSize); + } + } + + void testLongArraySizesSame(int[] testSizes) { + for (int testSize : testSizes) { + long[] la = new long[testSize]; + MyLong[] mla = (MyLong[])ValueClass.newNullRestrictedNonAtomicArray(MyLong.class, testSize, new MyLong()); + assertArraySameSize(la, mla, testSize); + } + } + + public void testPrimitiveArraySizesSame() { + int[] testSizes = new int[] { 0, 1, 2, 3, 4, 7, 10, 257 }; + testByteArraySizesSame(testSizes); + testShortArraySizesSame(testSizes); + testIntArraySizesSame(testSizes); + testLongArraySizesSame(testSizes); + } + + @LooselyConsistentValue + static value class bbValue { byte b = 0; byte b2 = 0;} + @LooselyConsistentValue + static value class bsValue { byte b = 0; short s = 0;} + @LooselyConsistentValue + static value class siValue { short s = 0; int i = 0;} + @LooselyConsistentValue + static value class ssiValue { short s = 0; short s2 = 0; int i = 0;} + @LooselyConsistentValue + static value class blValue { byte b = 0; long l = 0; } + + // Expect aligned array addressing to nearest pow2 + void testAlignedSize() { + int testSize = 10; + assertArraySameSize(new short[testSize], ValueClass.newNullRestrictedNonAtomicArray(bbValue.class, testSize, new bbValue()), testSize); + assertArraySameSize(new long[testSize], ValueClass.newNullRestrictedNonAtomicArray(siValue.class, testSize, new siValue()), testSize); + assertArraySameSize(new long[testSize], ValueClass.newNullRestrictedNonAtomicArray(ssiValue.class, testSize, new ssiValue()), testSize); + assertArraySameSize(new long[testSize*2], ValueClass.newNullRestrictedNonAtomicArray(blValue.class, testSize, new blValue()), testSize); + assertArraySameSize(new int[testSize], ValueClass.newNullRestrictedNonAtomicArray(bsValue.class, testSize, new bsValue()), testSize); + } + + public void test() { + ensureArraySizeWin(); + testPrimitiveArraySizesSame(); + if (!VM_FLAG_FORCENONTEARABLE) { + testAlignedSize(); + } + } + + public static void main(String[] args) { + new InlineTypeDensity().test(); + } + +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineTypeGetField.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineTypeGetField.java new file mode 100644 index 00000000000..c1a0c6d20e5 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineTypeGetField.java @@ -0,0 +1,103 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package runtime.valhalla.inlinetypes; + +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import jdk.test.lib.Asserts; + +/* + * @test InlineTypeGetField + * @summary Inline Type get field test + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile Point.java InlineTypeGetField.java + * @run main/othervm runtime.valhalla.inlinetypes.InlineTypeGetField + */ +public class InlineTypeGetField { + + @Strict + @NullRestricted + static Point staticPoint0; + @Strict + @NullRestricted + static Point staticPoint1; + @Strict + @NullRestricted + Point instancePoint0; + @Strict + @NullRestricted + Point instancePoint1; + + static { + staticPoint0 = new Point(358, 406); + staticPoint1 = new Point(101, 2653); + } + + InlineTypeGetField() { + instancePoint0 = new Point(1890, 1918); + instancePoint1 = new Point(91, 102); + } + + public static void main(String[] args) { + InlineTypeGetField inlineTypeGetField = new InlineTypeGetField(); + System.gc(); // check that VTs survive GC + inlineTypeGetField.run(); + } + + public void run() { + // testing initial configuration + checkPoint(staticPoint0, 358, 406); + checkPoint(staticPoint1, 101, 2653); + checkPoint(instancePoint0, 1890, 1918); + checkPoint(instancePoint1, 91, 102); + // swapping static fields + Point p = staticPoint1; + staticPoint1 = staticPoint0; + staticPoint0 = p; + System.gc(); + checkPoint(staticPoint0, 101, 2653); + checkPoint(staticPoint1, 358, 406); + //swapping instance fields + p = instancePoint1; + instancePoint1 = instancePoint0; + instancePoint0 = p; + System.gc(); + checkPoint(instancePoint0, 91, 102); + checkPoint(instancePoint1, 1890, 1918); + // instance to static + staticPoint0 = instancePoint0; + System.gc(); + checkPoint(staticPoint0, 91, 102); + // static to instance + instancePoint1 = staticPoint1; + System.gc(); + checkPoint(instancePoint1, 358, 406); + } + + static void checkPoint(Point p , int x, int y) { + Asserts.assertEquals(p.x, x, "invalid x value"); + Asserts.assertEquals(p.y, y, "invalid y value"); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineTypesTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineTypesTest.java new file mode 100644 index 00000000000..7287f012ced --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineTypesTest.java @@ -0,0 +1,504 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package runtime.valhalla.inlinetypes; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.lang.invoke.*; +import java.lang.ref.*; +import java.nio.ByteBuffer; +import java.time.chrono.ThaiBuddhistChronology; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.*; + +import static jdk.test.lib.Asserts.*; + +import java.lang.classfile.Label; +import java.lang.classfile.TypeKind; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import jdk.test.lib.Platform; + +import javax.tools.*; + +import test.java.lang.invoke.lib.InstructionHelper; +import static test.java.lang.invoke.lib.InstructionHelper.classDesc; + +/** + * @test InlineTypesTest + * @summary Test data movement with inline types + * @modules java.base/jdk.internal.value + * @library /test/lib /test/jdk/java/lang/invoke/common + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile InlineTypesTest.java + * @run main/othervm -Xmx128m -XX:+ExplicitGCInvokesConcurrent + * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * runtime.valhalla.inlinetypes.InlineTypesTest + * @run main/othervm -Xmx128m -XX:+ExplicitGCInvokesConcurrent + * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:ForceNonTearable=* + * runtime.valhalla.inlinetypes.InlineTypesTest + */ + + final class ContainerValue1 { + static TestValue1 staticInlineField; + @Strict + @NullRestricted + TestValue1 nonStaticInlineField = new TestValue1(); + TestValue1[] valueArray; +} + +@LooselyConsistentValue +value class TestValue1 { + + static TestValue1 staticValue = getInstance(); + + final int i; + final String name; + + public TestValue1() { + int now = (int)System.nanoTime(); + i = now; + name = Integer.valueOf(now).toString(); + } + + public TestValue1(int i) { + this.i = i; + name = Integer.valueOf(i).toString(); + } + + public static TestValue1 getInstance() { + return new TestValue1(); + } + + public static TestValue1 getNonBufferedInstance() { + return (TestValue1) staticValue; + } + + public boolean verify() { + if (name == null) return i == 0; + return Integer.valueOf(i).toString().compareTo(name) == 0; + } +} + +final class ContainerValue2 { + static TestValue2 staticInlineField; + @Strict + @NullRestricted + TestValue2 nonStaticInlineField = new TestValue2(); + TestValue2[] valueArray; +} + +@LooselyConsistentValue +value class TestValue2 { + static TestValue2 staticValue = getInstance(); + + final long l; + final double d; + final String s; + + public TestValue2() { + long now = System.nanoTime(); + l = now; + String stringNow = Long.valueOf(now).toString(); + s = stringNow; + d = Double.parseDouble(stringNow); + } + + public TestValue2(long l) { + this.l = l; + String txt = Long.valueOf(l).toString(); + s = txt; + d = Double.parseDouble(txt); + } + + public static TestValue2 getInstance() { + return new TestValue2(); + } + + public static TestValue2 getNonBufferedInstance() { + return (TestValue2) staticValue; + } + + public boolean verify() { + if (s == null) { + return d == 0 && l == 0; + } + return Long.valueOf(l).toString().compareTo(s) == 0 + && Double.parseDouble(s) == d; + } +} + +final class ContainerValue3 { + static TestValue3 staticInlineField; + @Strict + @NullRestricted + TestValue3 nonStaticInlineField = new TestValue3(); + TestValue3[] valueArray; +} + +@LooselyConsistentValue +value class TestValue3 { + + static TestValue3 staticValue = getInstance(); + + final byte b; + + public TestValue3() { + b = 123; + } + + public TestValue3(byte b) { + this.b = b; + } + + public static TestValue3 getInstance() { + return new TestValue3(); + } + + public static TestValue3 getNonBufferedInstance() { + return (TestValue3) staticValue; + } + + public boolean verify() { + return b == 0 || b == 123; + } +} + +final class ContainerValue4 { + static TestValue4 staticInlineField; + @Strict + @NullRestricted + TestValue4 nonStaticInlineField = new TestValue4(); + TestValue4[] valueArray; +} + +@LooselyConsistentValue +value class TestValue4 { + + static TestValue4 staticValue = getInstance(); + + final byte b1; + final byte b2; + final byte b3; + final byte b4; + final short s1; + final short s2; + final int i; + final long l; + final String val; + + public TestValue4() { + this((int) System.nanoTime()); + } + + public TestValue4(int i) { + this.i = i; + val = Integer.valueOf(i).toString(); + ByteBuffer bf = ByteBuffer.allocate(8); + bf.putInt(0, i); + bf.putInt(4, i); + l = bf.getLong(0); + s1 = bf.getShort(2); + s2 = bf.getShort(0); + b1 = bf.get(3); + b2 = bf.get(2); + b3 = bf.get(1); + b4 = bf.get(0); + } + + public static TestValue4 getInstance() { + return new TestValue4(); + } + + public static TestValue4 getNonBufferedInstance() { + return (TestValue4) staticValue; + } + + public boolean verify() { + if (val == null) { + return i == 0 && l == 0 && b1 == 0 && b2 == 0 && b3 == 0 && b4 == 0 + && s1 == 0 && s2 == 0; + } + ByteBuffer bf = ByteBuffer.allocate(8); + bf.putInt(0, i); + bf.putInt(4, i); + long nl = bf.getLong(0); + bf.clear(); + bf.putShort(0, s2); + bf.putShort(2, s1); + int from_s = bf.getInt(0); + bf.clear(); + bf.put(0, b4); + bf.put(1, b3); + bf.put(2, b2); + bf.put(3, b1); + int from_b = bf.getInt(0); + return l == nl && Integer.valueOf(i).toString().compareTo(val) == 0 + && from_s == i && from_b == i; + } +} + +public class InlineTypesTest { + + public static void main(String[] args) { + Class inlineClass = runtime.valhalla.inlinetypes.TestValue1.class; + Class testClasses[] = { + runtime.valhalla.inlinetypes.TestValue1.class, + runtime.valhalla.inlinetypes.TestValue2.class, + runtime.valhalla.inlinetypes.TestValue3.class, + runtime.valhalla.inlinetypes.TestValue4.class + }; + Class containerClasses[] = { + runtime.valhalla.inlinetypes.ContainerValue1.class, + runtime.valhalla.inlinetypes.ContainerValue2.class, + runtime.valhalla.inlinetypes.ContainerValue3.class, + runtime.valhalla.inlinetypes.ContainerValue4.class + }; + + for (int i = 0; i < testClasses.length; i++) { + try { + testExecutionStackToLocalVariable(testClasses[i]); + testExecutionStackToFields(testClasses[i], containerClasses[i]); + testExecutionStackToInlineArray(testClasses[i], containerClasses[i]); + } catch (Throwable t) { + t.printStackTrace(); + throw new RuntimeException(t); + } + } + } + + static MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + static void testExecutionStackToLocalVariable(Class inlineClass) throws Throwable { + String sig = "()L" + inlineClass.getName().replace('.', '/') + ";"; + final MethodTypeDesc voidReturnClass = MethodTypeDesc.ofDescriptor(sig); + final ClassDesc systemClassDesc = classDesc(System.class); + final ClassDesc inlineClassDesc = classDesc(inlineClass); + MethodHandle fromExecStackToLocalVar = InstructionHelper.buildMethodHandle( + LOOKUP, + "execStackToLocalVar", + MethodType.methodType(boolean.class), + CODE -> { + CODE.invokestatic(systemClassDesc, "gc", MethodTypeDesc.ofDescriptor("()V")); + int n = -1; + while (n < 1024) { + n++; + CODE + .invokestatic(inlineClassDesc, "getInstance", voidReturnClass) + .astore(n); + n++; + CODE + .invokestatic(inlineClassDesc, "getNonBufferedInstance", voidReturnClass) + .astore(n); + } + CODE.invokestatic(systemClassDesc, "gc", MethodTypeDesc.ofDescriptor("()V")); + Label endLabel = CODE.newLabel(); + while (n > 0) { + CODE + .aload(n) + .invokevirtual(inlineClassDesc, "verify", MethodTypeDesc.ofDescriptor("()Z")) + .iconst_1() + .if_icmpne(endLabel); + n--; + } + CODE + .iconst_1() + .return_(TypeKind.BOOLEAN) + .labelBinding(endLabel) + .iconst_0() + .return_(TypeKind.BOOLEAN); + }); + boolean result = (boolean) fromExecStackToLocalVar.invokeExact(); + System.out.println(result); + assertTrue(result, "Invariant"); + } + + static void testExecutionStackToFields(Class inlineClass, Class containerClass) throws Throwable { + final int ITERATIONS = Platform.isDebugBuild() ? 3 : 512; + String sig = "()L" + inlineClass.getName().replace('.', '/') + ";"; + final MethodTypeDesc voidReturnClass = MethodTypeDesc.ofDescriptor(sig); + final ClassDesc systemClassDesc = classDesc(System.class); + final ClassDesc inlineClassDesc = classDesc(inlineClass); + final ClassDesc containerClassDesc = classDesc(containerClass); + + MethodHandle fromExecStackToFields = InstructionHelper.buildMethodHandle( + LOOKUP, + "execStackToFields", + MethodType.methodType(boolean.class), + CODE -> { + Label loop = CODE.newLabel(); + Label end = CODE.newLabel(); + Label failed = CODE.newLabel(); + CODE + .invokestatic(systemClassDesc, "gc", MethodTypeDesc.ofDescriptor("()V"), false) + .new_(containerClassDesc) + .dup() + .invokespecial(containerClassDesc, "", MethodTypeDesc.ofDescriptor("()V")) + .astore(1) + .iconst_m1() + .istore(2) + .labelBinding(loop) + .iload(2) + .ldc(ITERATIONS) + .if_icmpeq(end) + .aload(1) + .invokestatic(inlineClassDesc, "getInstance", voidReturnClass) + .putfield(containerClassDesc, "nonStaticInlineField", inlineClassDesc) + .invokestatic(systemClassDesc, "gc", MethodTypeDesc.ofDescriptor("()V")) + .aload(1) + .getfield(containerClassDesc, "nonStaticInlineField", inlineClassDesc) + .invokevirtual(inlineClassDesc, "verify", MethodTypeDesc.ofDescriptor("()Z")) + .iconst_1() + .if_icmpne(failed) + .aload(1) + .invokestatic(inlineClassDesc, "getNonBufferedInstance", voidReturnClass) + .putfield(containerClassDesc, "nonStaticInlineField", inlineClassDesc) + .invokestatic(systemClassDesc, "gc", MethodTypeDesc.ofDescriptor("()V")) + .aload(1) + .getfield(containerClassDesc, "nonStaticInlineField", inlineClassDesc) + .invokevirtual(inlineClassDesc, "verify", MethodTypeDesc.ofDescriptor("()Z")) + .iconst_1() + .if_icmpne(failed) + .invokestatic(inlineClassDesc, "getInstance", voidReturnClass) + .putstatic(containerClassDesc, "staticInlineField", inlineClassDesc) + .invokestatic(systemClassDesc, "gc", MethodTypeDesc.ofDescriptor("()V")) + .getstatic(containerClassDesc, "staticInlineField", inlineClassDesc) + .checkcast(inlineClassDesc) + .invokevirtual(inlineClassDesc, "verify", MethodTypeDesc.ofDescriptor("()Z")) + .iconst_1() + .if_icmpne(failed) + .invokestatic(inlineClassDesc, "getNonBufferedInstance", voidReturnClass) + .putstatic(containerClassDesc, "staticInlineField", inlineClassDesc) + .invokestatic(systemClassDesc, "gc", MethodTypeDesc.ofDescriptor("()V")) + .getstatic(containerClassDesc, "staticInlineField", inlineClassDesc) + .checkcast(inlineClassDesc) + .invokevirtual(inlineClassDesc, "verify", MethodTypeDesc.ofDescriptor("()Z")) + .iconst_1() + .if_icmpne(failed) + .iinc(2, 1) + .goto_(loop) + .labelBinding(end) + .iconst_1() + .return_(TypeKind.BOOLEAN) + .labelBinding(failed) + .iconst_0() + .return_(TypeKind.BOOLEAN); + }); + boolean result = (boolean) fromExecStackToFields.invokeExact(); + System.out.println(result); + assertTrue(result, "Invariant"); + } + + static void testExecutionStackToInlineArray(Class inlineClass, Class containerClass) throws Throwable { + final int ITERATIONS = Platform.isDebugBuild() ? 3 : 100; + String sig = "()L" + inlineClass.getName().replace('.', '/') + ";"; + final MethodTypeDesc voidReturnClass = MethodTypeDesc.ofDescriptor(sig); + final ClassDesc systemClassDesc = classDesc(System.class); + final ClassDesc inlineClassDesc = classDesc(inlineClass); + final ClassDesc containerClassDesc = classDesc(containerClass); + + MethodHandle fromExecStackToInlineArray = InstructionHelper.buildMethodHandle( + LOOKUP, + "execStackToInlineArray", + MethodType.methodType(boolean.class), + CODE -> { + Label loop1 = CODE.newLabel(); + Label loop2 = CODE.newLabel(); + Label end1 = CODE.newLabel(); + Label end2 = CODE.newLabel(); + Label failed = CODE.newLabel(); + CODE + .invokestatic(systemClassDesc, "gc", MethodTypeDesc.ofDescriptor("()V")) + .new_(containerClassDesc) + .dup() + .invokespecial(containerClassDesc, "", MethodTypeDesc.ofDescriptor("()V")) + .astore(1) + .ldc(ITERATIONS * 3) + .anewarray(inlineClassDesc) + .astore(2) + .aload(2) + .aload(1) + .swap() + .putfield(containerClassDesc, "valueArray", inlineClassDesc.arrayType()) + .iconst_0() + .istore(3) + .labelBinding(loop1) + .iload(3) + .ldc(ITERATIONS *3) + .if_icmpge(end1) + .aload(2) + .iload(3) + .invokestatic(inlineClassDesc, "getInstance", voidReturnClass) + .aastore() + .iinc(3, 1) + .aload(2) + .iload(3) + .invokestatic(inlineClassDesc, "getNonBufferedInstance", voidReturnClass) + .aastore() + .iinc(3, 1) + .aload(2) + .iload(3) + .new_(inlineClassDesc) + .dup() + .invokespecial(inlineClassDesc, "", MethodTypeDesc.ofDescriptor("()V")) + .aastore() + .iinc(3, 1) + .goto_(loop1) + .labelBinding(end1) + .invokestatic(systemClassDesc, "gc", MethodTypeDesc.ofDescriptor("()V")) + .iconst_0() + .istore(3) + .labelBinding(loop2) + .iload(3) + .ldc(ITERATIONS * 3) + .if_icmpge(end2) + .aload(2) + .iload(3) + .aaload() + .invokevirtual(inlineClassDesc, "verify", MethodTypeDesc.ofDescriptor("()Z")) + .iconst_1() + .if_icmpne(failed) + .iinc(3, 1) + .goto_(loop2) + .labelBinding(end2) + .iconst_1() + .return_(TypeKind.BOOLEAN) + .labelBinding(failed) + .iconst_0() + .return_(TypeKind.BOOLEAN); + }); + boolean result = (boolean) fromExecStackToInlineArray.invokeExact(); + System.out.println(result); + assertTrue(result, "Invariant"); + } + +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineWithJni.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineWithJni.java new file mode 100644 index 00000000000..71e4f113f53 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineWithJni.java @@ -0,0 +1,365 @@ +/* + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package runtime.valhalla.inlinetypes; + +/* @test + * @summary test JNI functions with instances of value classes + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * java.base/jdk.internal.value + * @enablePreview + * @run main/othervm/native --enable-native-access=ALL-UNNAMED -XX:+UseNullableValueFlattening runtime.valhalla.inlinetypes.InlineWithJni + */ + + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import java.lang.reflect.Array; +import java.lang.reflect.Method; + +import jdk.test.lib.Asserts; + +public value class InlineWithJni { + + static int returnValue = 999; + static final int JNI_ERR = -1; // from jni.h + + static { + System.loadLibrary("InlineWithJni"); + } + + public static void main(String[] args) { + testJniMonitorOps(); + testJniFieldAccess(); + testJniArrayAccess(); + } + + final int x; + + public InlineWithJni(int x) { + this.x = x; + } + + public native void doJniMonitorEnter(); + public native void doJniMonitorExit(); + + public static void testJniMonitorOps() { + boolean sawIe = false; + boolean sawImse = false; + try { + new InlineWithJni(0).doJniMonitorEnter(); + } catch (IdentityException ie) { + sawIe = true; + } + Asserts.assertTrue(sawIe, "Missing IdentityException"); + Asserts.assertEQ(returnValue, JNI_ERR); + try { + new InlineWithJni(0).doJniMonitorExit(); + } catch (IllegalMonitorStateException imse) { + sawImse = true; + } + Asserts.assertTrue(sawImse, "Missing IllegalMonitorStateException"); + } + + public static native Object readInstanceField(Object obj, String name, String signature); + public static native void writeInstanceField(Object obj, String name, String signature, Object value); + + public static native Object readArrayElement(Object[] array, int index); + public static native void writeArrayElement(Object[] array, int index, Object value); + + + static value class SmallValue { + byte b; + SmallValue() { b = 1; } + SmallValue(byte b0) { b = b0; } + static public SmallValue getValueA() { return new SmallValue((byte)42); } + static public SmallValue getValueB() { return new SmallValue((byte)111); } + } + + static value class MediumValue { + int i0; + int i1; + MediumValue() { + i0 = 2; + i1 = 3; + } + MediumValue(int ia, int ib) { + i0 = ia; + i1 = ib; + } + static public MediumValue getValueA() { return new MediumValue(23, 64); } + static public MediumValue getValueB() { return new MediumValue(-51, -1023); } + } + + static value class BigValue { + long l0; + long l1; + long l2; + BigValue() { + l0 = 4L; + l1 = 5L; + l2 = 6L; + } + BigValue(long la, long lb, long lc) { + l0 = la; + l1 = lb; + l2 = lc; + } + static public BigValue getValueA() { return new BigValue(0L, 65525L, Long.MIN_VALUE); } + static public BigValue getValueB() { return new BigValue(Long.MIN_VALUE, 32000L, 0L); } + } + + static value class ValueWithOop { + String s; + byte b; + ValueWithOop() { + s = "Hello Duke!"; + b = (byte)7; + } + ValueWithOop(String s0, byte b0) { + s = s0; + b = b0; + } + static public ValueWithOop getValueA() { return new ValueWithOop("Bretagne", (byte)123); } + static public ValueWithOop getValueB() { return new ValueWithOop("Alsace", (byte)-31); } + } + + // Container with nullable fields (potentially flattened) + static class Container0 { + SmallValue sv = new SmallValue(); + MediumValue mv = new MediumValue(); + BigValue bv = new BigValue(); + ValueWithOop vwo = new ValueWithOop(); + } + + // Container with null-restricted fields (potentially flattened) + static class Container1 { + @Strict + @NullRestricted + SmallValue sv = new SmallValue(); + @Strict + @NullRestricted + MediumValue mv = new MediumValue(); + @Strict + @NullRestricted + BigValue bv = new BigValue(); + @Strict + @NullRestricted + ValueWithOop vwo = new ValueWithOop(); + } + + static String getFieldSignature(Class c, String name) { + try { + return "L"+c.getDeclaredField(name).getType().getName().replaceAll("\\.", "/")+";"; + } catch(NoSuchFieldException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + static void testJniFieldAccessHelper(Object c, boolean nullRestriction) { + + String smallSignature = getFieldSignature(c.getClass(), "sv"); + String mediumSignature = getFieldSignature(c.getClass(), "mv"); + String bigSignature = getFieldSignature(c.getClass(), "bv"); + String withOopSignature = getFieldSignature(c.getClass(), "vwo"); + + + // Reading nullable value fields + SmallValue sv = (SmallValue)readInstanceField(c, "sv", smallSignature); + Asserts.assertEQ(sv, new SmallValue()); + Asserts.assertTrue(sv.b == 1); + MediumValue mv = (MediumValue)readInstanceField(c, "mv", mediumSignature); + Asserts.assertEQ(mv, new MediumValue()); + Asserts.assertTrue(mv.i0 == 2); + Asserts.assertTrue(mv.i1 == 3); + BigValue bv = (BigValue)readInstanceField(c, "bv", bigSignature); + Asserts.assertEQ(bv, new BigValue()); + Asserts.assertTrue(bv.l0 == 4); + Asserts.assertTrue(bv.l1 == 5); + Asserts.assertTrue(bv.l2 == 6); + ValueWithOop vwo = (ValueWithOop)readInstanceField(c, "vwo", withOopSignature); + Asserts.assertEQ(vwo, new ValueWithOop()); + Asserts.assertTrue(vwo.s.equals("Hello Duke!")); + Asserts.assertTrue(vwo.b == 7); + + + // Writing non-null value to nullable field + SmallValue nsv = new SmallValue((byte)8); + writeInstanceField(c, "sv", smallSignature, nsv); + sv = (SmallValue)readInstanceField(c, "sv", smallSignature); + Asserts.assertTrue(sv == nsv); + MediumValue nmv = new MediumValue(9, 10); + writeInstanceField(c, "mv", mediumSignature, nmv); + mv = (MediumValue)readInstanceField(c, "mv", mediumSignature); + Asserts.assertTrue(mv == nmv); + BigValue nbv = new BigValue(11L, 12L, 13L); + writeInstanceField(c, "bv", bigSignature, nbv); + bv = (BigValue)readInstanceField(c, "bv", bigSignature); + Asserts.assertTrue(bv == nbv); + ValueWithOop nvwo = new ValueWithOop("Bye Duke!", (byte)14); + writeInstanceField(c, "vwo", withOopSignature, nvwo); + vwo = (ValueWithOop)readInstanceField(c, "vwo", withOopSignature); + Asserts.assertTrue(vwo == nvwo); + + + // Writing null to nullable field + Exception ex = null; + try { + writeInstanceField(c, "sv", smallSignature, null); + sv = (SmallValue)readInstanceField(c, "sv", smallSignature); + Asserts.assertTrue(sv == null); + } catch(NullPointerException npe) { + ex = npe; + } + Asserts.assertTrue((nullRestriction && ex != null) || (!nullRestriction && ex == null)); + ex = null; + try { + writeInstanceField(c, "mv", mediumSignature, null); + mv = (MediumValue)readInstanceField(c, "mv", mediumSignature); + Asserts.assertTrue(mv == null); + } catch(NullPointerException npe) { + ex = npe; + } + System.out.println(ex + " / " + nullRestriction); + Asserts.assertTrue((nullRestriction && ex != null) || (!nullRestriction && ex == null)); + ex = null; + try { + writeInstanceField(c, "bv", bigSignature, null); + bv = (BigValue)readInstanceField(c, "bv", bigSignature); + Asserts.assertTrue(bv == null); + } catch(NullPointerException npe) { + ex = npe; + } + Asserts.assertTrue((nullRestriction && ex != null) || (!nullRestriction && ex == null)); + ex = null; + try { + writeInstanceField(c, "vwo", withOopSignature, null); + vwo = (ValueWithOop)readInstanceField(c, "vwo", withOopSignature); + Asserts.assertTrue(vwo == null); + } catch(NullPointerException npe) { + ex = npe; + } + Asserts.assertTrue((nullRestriction && ex != null) || (!nullRestriction && ex == null)); + } + + static void testJniFieldAccess() { + // Reading nullable field + try { + Container0 c0 = new Container0(); + testJniFieldAccessHelper(c0, false); + Container1 c1 = new Container1(); + testJniFieldAccessHelper(c1, true); + } catch (Throwable t) { + t.printStackTrace(); + throw new RuntimeException(t); + } + } + + static void testJniArrayAccessHelper(Object[] array, boolean nullRestriction) { + Object valueA = getValueA(array.getClass().getComponentType()); + Object valueB = getValueB(array.getClass().getComponentType()); + int length = array.length; + + // Reading elements + for (int i = 0; i < length; i++) { + Object element = readArrayElement(array, i); + Asserts.assertTrue(element == valueA); + } + + // Writing elements + for (int i = 0; i < length; i++) { + writeArrayElement(array, i, valueB); + } + for (int i = 0; i < length; i++) { + Object element = readArrayElement(array, i); + Asserts.assertTrue(element == valueB); + } + + // Writing null + for (int i = 0; i < length; i++) { + Exception ex = null; + try { + writeArrayElement(array, i, null); + Object element = readArrayElement(array, i); + Asserts.assertTrue(element == null); + } catch(NullPointerException npe) { + ex = npe; + } + Asserts.assertTrue((nullRestriction && ex != null) || (!nullRestriction && ex == null)); + } + } + + static Object getValueA(Class c) { + try { + Method mA = c.getMethod("getValueA"); + return mA.invoke(null); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + static Object getValueB(Class c) { + try { + Method mB = c.getMethod("getValueB"); + return mB.invoke(null); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + static void fillArrayWithValueA(Object[] array) { + Object valueA = getValueA(array.getClass().getComponentType()); + for (int i = 0; i < array.length; i++) { + array[i] = valueA; + } + } + + static void testJniArrayAccessHelper2(Class c) { + + Object[] array0 = (Object[])Array.newInstance(c, 10); + fillArrayWithValueA(array0); + testJniArrayAccessHelper(array0, false); + + Object[] array1 = ValueClass.newNullableAtomicArray(c, 31); + fillArrayWithValueA(array1); + testJniArrayAccessHelper(array1, false); + + Object[] array2 = ValueClass.newNullRestrictedAtomicArray(c, 127, getValueA(c)); + fillArrayWithValueA(array2); + testJniArrayAccessHelper(array2, true); + } + + static void testJniArrayAccess() { + testJniArrayAccessHelper2(SmallValue.class); + testJniArrayAccessHelper2(MediumValue.class); + testJniArrayAccessHelper2(BigValue.class); + testJniArrayAccessHelper2(ValueWithOop.class); + + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/JumboInline.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/JumboInline.java new file mode 100644 index 00000000000..b4cdeec30d8 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/JumboInline.java @@ -0,0 +1,94 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package runtime.valhalla.inlinetypes; + +import jdk.internal.vm.annotation.LooselyConsistentValue; + +@LooselyConsistentValue +public value class JumboInline { + final long l0; + final long l1; + final long l2; + final long l3; + final long l4; + final long l5; + final long l6; + final long l7; + final long l8; + final long l9; + final long l10; + final long l11; + final long l12; + final long l13; + final long l14; + final long l15; + final long l16; + final long l17; + final long l18; + final long l19; + + public JumboInline(long l0Val, long l1Val) { + l0 = l0Val; + l1 = l1Val; + l2 = l0Val+1; + l3 = l1Val+2; + l4 = l0Val+3; + l5 = l1Val+4; + l6 = l0Val+5; + l7 = l1Val+6; + l8 = l0Val+7; + l9 = l1Val+8; + l10 = l0Val+9; + l11 = l1Val+10; + l12 = l0Val+11; + l13 = l1Val+12; + l14 = l0Val+13; + l15 = l1Val+14; + l16 = l0Val+15; + l17 = l1Val+16; + l18 = l0Val+17; + l19 = l1Val+18; + } + + public boolean verify() { + return (l2 == (l0 + 1) && l3 == (l1 + 2) && l4 == (l0 + 3) + && l5 == (l1 + 4) && l6 == (l0 + 5) && l7 == (l1 + 6) && l8 == (l0 + 7) + && l9 == (l1 + 8) && l10 == (l0 + 9) && l11 == (l1 + 10) + && l12 == (l0 + 11) && l13 == (l1 + 12) && l14 == (l1 + 13) + && l15 == (l1 + 14) && l16 == (l0 + l15) && l17 == (l1 + 16) + && l18 == (l0 + 17) && l19 == (l1 + 18)); + } + + public boolean equals(Object o) { + if(o instanceof JumboInline) { + JumboInline j = (JumboInline)o; + return (l0 == j.l0 && l1 == j.l1 && l2 == j.l2 && l3 == j.l3 + && l4 == j.l4 && l5 == j.l5 && l6 == j.l6 && l7 == j.l7 + && l8 == j.l8 && l9 == j.l9 && l10 == j.l10 && l11 == j.l11 + && l12 == j.l12 && l13 == j.l13 && l14 == j.l14 && l15 == j.l15 + && l16 == j.l16 && l17 == j.l17 && l18 == j.l18 && l19 == j.l19); + } else { + return false; + } + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/Long8Inline.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/Long8Inline.java new file mode 100644 index 00000000000..32fb5b4559d --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/Long8Inline.java @@ -0,0 +1,84 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package runtime.valhalla.inlinetypes; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.test.lib.Asserts; + +@LooselyConsistentValue +public final value class Long8Inline { + + final long longField1; + final long longField2; + final long longField3; + final long longField4; + final long longField5; + final long longField6; + final long longField7; + final long longField8; + + public Long8Inline() { + this(0, 0, 0, 0, 0, 0, 0, 0); + } + + public Long8Inline(long l1, long l2, long l3, long l4, long l5, long l6, long l7, long l8) { + longField1 = l1; + longField2 = l2; + longField3 = l3; + longField4 = l4; + longField5 = l5; + longField6 = l6; + longField7 = l7; + longField8 = l8; + } + + public long getLongField1() { return longField1; } + public long getLongField2() { return longField2; } + public long getLongField3() { return longField3; } + public long getLongField4() { return longField4; } + public long getLongField5() { return longField5; } + public long getLongField6() { return longField6; } + public long getLongField7() { return longField7; } + public long getLongField8() { return longField8; } + + static void check(Long8Inline value, + long long1, + long long2, + long long3, + long long4, + long long5, + long long6, + long long7, + long long8) { + Asserts.assertEquals(value.getLongField1(), long1, "Field 1 incorrect"); + Asserts.assertEquals(value.getLongField2(), long2, "Field 2 incorrect"); + Asserts.assertEquals(value.getLongField3(), long3, "Field 3 incorrect"); + Asserts.assertEquals(value.getLongField4(), long4, "Field 4 incorrect"); + Asserts.assertEquals(value.getLongField5(), long5, "Field 5 incorrect"); + Asserts.assertEquals(value.getLongField6(), long6, "Field 6 incorrect"); + Asserts.assertEquals(value.getLongField7(), long7, "Field 7 incorrect"); + Asserts.assertEquals(value.getLongField8(), long8, "Field 8 incorrect"); + } + +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/MonitorEnterTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/MonitorEnterTest.java new file mode 100644 index 00000000000..68e7c2c4dc4 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/MonitorEnterTest.java @@ -0,0 +1,63 @@ +/* +* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* This code is free software; you can redistribute it and/or modify it +* under the terms of the GNU General Public License version 2 only, as +* published by the Free Software Foundation. +* +* This code is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +* version 2 for more details (a copy is included in the LICENSE file that +* accompanied this code). +* +* You should have received a copy of the GNU General Public License version +* 2 along with this work; if not, write to the Free Software Foundation, +* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +* +* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +* or visit www.oracle.com if you need additional information or have any +* questions. +*/ + +/** +* @test MonitorEnterTest +* @library /test/lib +* @enablePreview +* @compile MonitorEnterTest.java +* @run main/othervm runtime.valhalla.inlinetypes.MonitorEnterTest +*/ + +package runtime.valhalla.inlinetypes; + +import jdk.test.lib.Asserts; + +public class MonitorEnterTest { + + static void monitorEnter(Object o, boolean expectSuccess, String message) { + try { + synchronized(o) { + Asserts.assertTrue(expectSuccess, "MonitorEnter should not have succeeded on an instance of " + o.getClass().getName()); + } + } catch (IdentityException e) { + Asserts.assertFalse(expectSuccess, "Unexpected IdentityException with an instance of " + o.getClass().getName()); + if (message != null) { + Asserts.assertEQ(e.getMessage(), message, "Exception message mismatch"); + } + } + } + + static value class MyValue { } + + public static void main(String[] args) { + // Attempts to lock the instance are repeated many time to ensure that the different paths + // are used: interpreter, C1, and C2 (which deopt to the interpreter in this case) + for (int i = 0; i < 20000; i++) { + monitorEnter(new Object(), true, ""); + monitorEnter(new String(), true, ""); + monitorEnter(new MyValue(), false, "Cannot synchronize on an instance of value class runtime.valhalla.inlinetypes.MonitorEnterTest$MyValue"); + monitorEnter(Integer.valueOf(42), false, "Cannot synchronize on an instance of value class java.lang.Integer"); + } + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/NullRestrictedArrayTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/NullRestrictedArrayTest.java new file mode 100644 index 00000000000..6e24c4642d8 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/NullRestrictedArrayTest.java @@ -0,0 +1,154 @@ +/* + * 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. + */ + + + +import jdk.internal.value.ValueClass; +import jdk.test.lib.Asserts; +import java.lang.reflect.Method; +import jdk.internal.misc.Unsafe; +import jdk.internal.vm.annotation.LooselyConsistentValue; + + +/* + * @test + * @summary Test of VM.newNullRestrictedArray API + * @library /test/lib + * @requires vm.flagless + * @modules java.base/jdk.internal.vm.annotation + * java.base/jdk.internal.misc + * java.base/jdk.internal.value + * @enablePreview + * @run main/othervm -XX:+UseArrayFlattening NullRestrictedArrayTest + */ + + +public class NullRestrictedArrayTest { + + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + + public static void main(String[] args) { + NullRestrictedArrayTest tests = new NullRestrictedArrayTest(); + Class c = tests.getClass(); + for (Method m : c.getDeclaredMethods()) { + if (m.getName().startsWith("test_")) { + try { + System.out.println("Running " + m.getName()); + m.invoke(tests); + } catch (Throwable t) { + t.printStackTrace(); + throw new RuntimeException(t); + } + } + } + } + + // Test illegal attempt to create a null restricted array with an identity class + public void test_0() { + Throwable exception = null; + try { + ValueClass.newNullRestrictedNonAtomicArray(String.class, 4, new String()); + } catch (IllegalArgumentException e) { + System.out.println("Received: " + e); + exception = e; + } + Asserts.assertNotNull(exception, "Expected IllegalArgumentException not received"); + } + + // Test illegal array length + @LooselyConsistentValue + static value class ValueClass1 { + int i = 0; + int j = 0; + } + + public void test_1() { + Throwable exception = null; + try { + ValueClass.newNullRestrictedNonAtomicArray(ValueClass1.class, -1, new ValueClass1()); + } catch (IllegalArgumentException e) { + System.out.println("Received: " + e); + exception = e; + } + Asserts.assertNotNull(exception, "Expected IllegalArgumentException not received"); + } + + // Test valid creation of a flat array + @LooselyConsistentValue + static value class ValueClass3 { + int i = 0; + int j = 0; + } + + public void test_3() { + Throwable exception = null; + try { + Object array = ValueClass.newNullRestrictedNonAtomicArray(ValueClass3.class, 8, new ValueClass3()); + Asserts.assertTrue(ValueClass.isFlatArray(array), "Expecting flat array but array is not flat"); + } catch (Throwable e) { + System.out.println("Received: " + e); + exception = e; + } + Asserts.assertNull(exception, "Unexpected exception: " + exception); + } + + // Test that elements are not null + @LooselyConsistentValue + static value class ValueClass4 { + int i = 0; + int j = 0; + } + + public void test_4() { + Throwable exception = null; + try { + Object[] array = ValueClass.newNullRestrictedNonAtomicArray(ValueClass4.class, 8, new ValueClass4()); + Asserts.assertNotNull(array[1], "Expecting non null element but null found instead"); + } catch (Throwable e) { + System.out.println("Received: " + e); + exception = e; + } + Asserts.assertNull(exception, "Unexpected exception: " + exception); + } + + // Test that writing null to a null restricted array throws an exception + @LooselyConsistentValue + static value class ValueClass5 { + int i = 0; + int j = 0; + } + + public void test_5() { + Throwable exception = null; + try { + Object[] array = ValueClass.newNullRestrictedNonAtomicArray(ValueClass5.class, 8, new ValueClass5()); + array[1] = null; + } catch (NullPointerException e) { + System.out.println("Received: " + e); + exception = e; + } + Asserts.assertNotNull(exception, "Expected NullPointerException not received"); + } + +} \ No newline at end of file diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/NullableFlatFieldTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/NullableFlatFieldTest.java new file mode 100644 index 00000000000..87cf32b940b --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/NullableFlatFieldTest.java @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /* + * @test NullableFlatFieldTest + * @requires vm.debug == true + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @run main/othervm -XX:+UseNullableValueFlattening -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlineLayout NullableFlatFieldTest + */ + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; + +import jdk.test.lib.Asserts; + +public class NullableFlatFieldTest { + + @LooselyConsistentValue + static value class Value0 { + long l; + int i; + short s; + byte b; + + Value0() { + l = 0; + i = 0; + s = 0; + b = 0; + } + + Value0(long l0, int i0, short s0, byte b0) { + l = l0; + i = i0; + s = s0; + b = b0; + } + } + + static class Container0 { + Value0 val; + long l; + int i; + } + + // Container0 had a external null marker located just between two Java fields, + // and test0 checks that updating the null marker doesn't corrupt + // the surrounding fields and vice-versa. + static void test0() { + Container0 c = new Container0(); + Asserts.assertNull(c.val); + Asserts.assertEquals(c.l, 0L); + Asserts.assertEquals(c.i, 0); + c.l = -1L; + c.i = -1; + Asserts.assertNull(c.val); + Value0 v = new Value0(-1L, -1, (short)-1, (byte)-1); + c.val = v; + Asserts.assertEquals(c.l, -1L); + Asserts.assertEquals(c.i, -1); + Value0 vr = c.val; + Asserts.assertEquals(vr.l, -1L); + Asserts.assertEquals(vr.i, -1); + Asserts.assertEquals(vr.s, (short)-1); + Asserts.assertEquals(vr.b, (byte)-1); + c.val = null; + Asserts.assertEquals(c.l, -1L); + Asserts.assertEquals(c.i, -1); + } + + @LooselyConsistentValue + static value class Value1a { + long l; + short s; + byte b; + + Value1a() { + l = 0; + s = 0; + b = 0; + } + + Value1a(long l0, short s0, byte b0) { + l = l0; + s = s0; + b = b0; + } + } + + @LooselyConsistentValue + static value class Value1b { + @NullRestricted + Value1a vala; + long l; + int i; + + Value1b() { + vala = new Value1a(); + l = 1L; + i = 1; + } + + Value1b(Value1a v0, long l0, int i0) { + vala = v0; + l = l0; + i = i0; + } + } + + static class Container1 { + Value1b valb; + } + + // Container1 has a nullable flat field with an internal null marker, + // test1 checks that updating the null marker doesn't corrupt the + // flat field's values + static void test1() { + Container1 c = new Container1(); + Asserts.assertNull(c.valb); + Value1a va = new Value1a(-1L, (short)-1, (byte)-1); + Asserts.assertEquals(va.l, -1L); + Asserts.assertEquals(va.s, (short)-1); + Asserts.assertEquals(va.b, (byte)-1); + Value1b vb = new Value1b(va, -1L, -1); + Asserts.assertNotNull(vb.vala); + Asserts.assertEquals(vb.vala.l, -1L); + Asserts.assertEquals(vb.vala.s, (short)-1); + Asserts.assertEquals(vb.vala.b, (byte)-1); + c.valb = vb; + Asserts.assertNotNull(c.valb); + Asserts.assertEquals(c.valb, vb); + Asserts.assertEquals(c.valb.vala, va); + Asserts.assertEquals(c.valb.l, -1L); + Asserts.assertEquals(c.valb.i, -1); + c.valb = null; + Asserts.assertNull(c.valb); + } + + @LooselyConsistentValue + static value class Value2a { + long l; + int i; + byte b; + Value2a() { + l = 0; + i = 0; + b = 0; + } + Value2a(long l0, int i0, byte b0) { + l = l0; + i = i0; + b = b0; + } + } + + @LooselyConsistentValue + static value class Value2b { + long l; + Value2b() { + l = 0; + } + Value2b(long l0) { + l = l0; + } + } + + static class Container2 { + Value2a vala; + Value2b valb0; + Value2b valb1; + int i; + } + + // Container2 has 3 contiguous null markers, + // test2 checks that updating a null marker doesn't affect the other markers + public static void test2() { + Container2 c = new Container2(); + Asserts.assertNull(c.vala); + Asserts.assertNull(c.valb0); + Asserts.assertNull(c.valb1); + Value2a va = new Value2a(-1L, -1, (byte)-1); + c.vala = va; + Asserts.assertNotNull(c.vala); + Asserts.assertNull(c.valb0); + Asserts.assertNull(c.valb1); + c.vala = null; + Asserts.assertNull(c.vala); + Asserts.assertNull(c.valb0); + Asserts.assertNull(c.valb1); + Value2b vb = new Value2b(-1L); + c.valb0 = vb; + Asserts.assertNull(c.vala); + Asserts.assertNotNull(c.valb0); + Asserts.assertNull(c.valb1); + c.valb0 = null; + Asserts.assertNull(c.vala); + Asserts.assertNull(c.valb0); + Asserts.assertNull(c.valb1); + c.valb1 = vb; + Asserts.assertNull(c.vala); + Asserts.assertNull(c.valb0); + Asserts.assertNotNull(c.valb1); + c.valb1 = null; + Asserts.assertNull(c.vala); + Asserts.assertNull(c.valb0); + Asserts.assertNull(c.valb1); + c.vala = va; + c.valb0 = vb; + c.valb1 = vb; + Asserts.assertNotNull(c.vala); + Asserts.assertNotNull(c.valb0); + Asserts.assertNotNull(c.valb1); + c.vala = null; + Asserts.assertNull(c.vala); + Asserts.assertNotNull(c.valb0); + Asserts.assertNotNull(c.valb1); + c.vala = va; + Asserts.assertNotNull(c.vala); + Asserts.assertNotNull(c.valb0); + Asserts.assertNotNull(c.valb1); + c.valb0 = null; + Asserts.assertNotNull(c.vala); + Asserts.assertNull(c.valb0); + Asserts.assertNotNull(c.valb1); + c.valb0 = vb; + Asserts.assertNotNull(c.vala); + Asserts.assertNotNull(c.valb0); + Asserts.assertNotNull(c.valb1); + c.valb1 = null; + Asserts.assertNotNull(c.vala); + Asserts.assertNotNull(c.valb0); + Asserts.assertNull(c.valb1); + c.valb1 = vb; + Asserts.assertNotNull(c.vala); + Asserts.assertNotNull(c.valb0); + Asserts.assertNotNull(c.valb1); + } + + @LooselyConsistentValue + static value class Value3 { + int i = 0; + } + + static class Container3 { + Value3 val; + } + + static Container3 getNullContainer3() { + return null; + } + + public static void test3() { + NullPointerException npe = null; + Container3 c = getNullContainer3(); + try { + Value3 v = c.val; + } catch(NullPointerException e) { + npe = e; + } + Asserts.assertNotNull(npe); + } + + public static void main(String[] args) { + // All tests are run twice to exercise both the unresolved bytecodes and the rewritten ones + for (int i = 0; i < 2; i++) { + System.out.println("Pass " + i); + test0(); + test1(); + test2(); + test3(); + } + } +} + diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ObjectMethods.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ObjectMethods.java new file mode 100644 index 00000000000..8c8f832ff9d --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ObjectMethods.java @@ -0,0 +1,269 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package runtime.valhalla.inlinetypes; + +import java.lang.invoke.*; + +import test.java.lang.invoke.lib.InstructionHelper; + +/* + * @test ObjectMethods + * @summary Check object methods implemented by the VM behave with value types + * @library /test/lib /test/jdk/java/lang/invoke/common + * @enablePreview + * @compile ObjectMethods.java + * @run main/othervm -XX:+UseCompressedClassPointers runtime.valhalla.inlinetypes.ObjectMethods + * @run main/othervm -XX:-UseCompressedClassPointers runtime.valhalla.inlinetypes.ObjectMethods + * @run main/othervm -noverify runtime.valhalla.inlinetypes.ObjectMethods noverify + */ + +public class ObjectMethods { + + public static void main(String[] args) { + testObjectMethods((args.length > 0 && args[0].equals("noverify"))); + } + + public static void testObjectMethods(boolean verifierDisabled) { + MyInt val = new MyInt(7); + MyInt sameVal = new MyInt(7); + + // Exercise all the Object native/VM methods... + + if (verifierDisabled) { // Just noverifier... + checkMonitorExit(val); + return; + } + + // getClass() + checkGetClass(val, MyInt.class); + + //hashCode()/identityHashCode() + checkHashCodes(val, sameVal.hashCode()); + + // clone() - test the default implementation from j.l.Object + checkNotCloneable(val); + checkCloneable(new MyCloneableInt(78)); + + // synchronized + checkSynchronized(val); + + // wait/notify() + checkWait(val); + checkNotify(val); + + System.gc(); + } + + + static void checkGetClass(Object val, Class expectedClass) { + Class clazz = val.getClass(); + if (clazz == null) { + throw new RuntimeException("getClass return null"); + } else if (clazz != expectedClass) { + throw new RuntimeException("getClass (" + clazz + ") doesn't match " + expectedClass); + } + } + + // Just check we don't crash the VM + static void checkHashCodes(Object val, int expectedHashCode) { + int hash = val.hashCode(); + if (hash != expectedHashCode) { + throw new RuntimeException("Hash code mismatch value: " + hash + + " expected: " + expectedHashCode); + } + hash = System.identityHashCode(val); + if (hash != expectedHashCode) { + throw new RuntimeException("Identity hash code mismatch value: " + hash + + " expected: " + expectedHashCode); + } + } + + static void checkNotCloneable(MyInt val) { + boolean sawCnse = false; + try { + val.attemptClone(); + } catch (CloneNotSupportedException cnse) { + sawCnse = true; + } + if (!sawCnse) { + throw new RuntimeException("clone() did not fail"); + } + } + + static void checkCloneable(MyCloneableInt val) { + boolean sawCnse = false; + MyCloneableInt val2 = null; + try { + val2 = (MyCloneableInt)val.attemptClone(); + } catch (CloneNotSupportedException cnse) { + sawCnse = true; + } + if (sawCnse) { + throw new RuntimeException("clone() did fail"); + } + if (val != val2) { + throw new RuntimeException("Cloned value is not identical to the original"); + } + } + + static void checkSynchronized(Object val) { + boolean sawIe = false; + try { + synchronized (val) { + throw new IdentityException("Unreachable code, reached"); + } + } catch (IdentityException ie) { + sawIe = true; + } + if (!sawIe) { + throw new RuntimeException("monitorenter did not fail"); + } + // synchronized method modifiers tested by "BadInlineTypes" CFP tests + // jni monitor ops tested by "InlineWithJni" + } + + // Check we haven't broken the mismatched monitor block check... + static void checkMonitorExit(Object val) { + boolean sawIe = false; + try { + InstructionHelper.buildMethodHandle(MethodHandles.lookup(), + "mismatchedMonitorExit", + MethodType.methodType(Void.TYPE, Object.class), + CODE-> { + CODE + .aload(0) + .monitorexit(); + CODE.return_(); + }).invokeExact(val); + throw new IllegalStateException("Unreachable code, reached"); + } catch (Throwable t) { + if (t instanceof IllegalMonitorStateException) { + sawIe = true; + } else { + throw new RuntimeException(t); + } + } + if (!sawIe) { + throw new RuntimeException("monitorexit did not fail"); + } + } + + static void checkWait(Object val) { + boolean sawImse = false; + try { + val.wait(); + } catch (IllegalMonitorStateException imse) { + sawImse = true; + } catch (InterruptedException intExc) { + throw new RuntimeException(intExc); + } + if (!sawImse) { + throw new RuntimeException("wait() did not fail"); + } + + sawImse = false; + try { + val.wait(1l); + } catch (IllegalMonitorStateException imse) { + sawImse = true; + } catch (InterruptedException intExc) { + throw new RuntimeException(intExc); + } + if (!sawImse) { + throw new RuntimeException("wait() did not fail"); + } + + sawImse = false; + try { + val.wait(0l, 100); + } catch (IllegalMonitorStateException imse) { + sawImse = true; + } catch (InterruptedException intExc) { + throw new RuntimeException(intExc); + } + if (!sawImse) { + throw new RuntimeException("wait() did not fail"); + } + } + + static void checkNotify(Object val) { + boolean sawImse = false; + try { + val.notify(); + } catch (IllegalMonitorStateException imse) { + sawImse = true; + } + if (!sawImse) { + throw new RuntimeException("notify() did not fail"); + } + + sawImse = false; + try { + val.notifyAll(); + } catch (IllegalMonitorStateException imse) { + sawImse = true; + } + if (!sawImse) { + throw new RuntimeException("notifyAll() did not fail"); + } + } + + static value class MyInt { + int value; + public MyInt(int v) { value = v; } + public Object attemptClone() throws CloneNotSupportedException { + try { // Check it is not possible to clone... + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodHandle mh = lookup.findVirtual(getClass(), + "clone", + MethodType.methodType(Object.class)); + return mh.invokeExact(this); + } catch (Throwable t) { + if (t instanceof CloneNotSupportedException) { + throw (CloneNotSupportedException) t; + } + throw new RuntimeException(t); + } + } + } + + static value class MyCloneableInt implements Cloneable { + int value; + public MyCloneableInt(int v) { value = v; } + public Object attemptClone() throws CloneNotSupportedException { + try { // Check it is not possible to clone... + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodHandle mh = lookup.findVirtual(getClass(), + "clone", + MethodType.methodType(Object.class)); + return mh.invokeExact(this); + } catch (Throwable t) { + if (t instanceof CloneNotSupportedException) { + throw (CloneNotSupportedException) t; + } + throw new RuntimeException(t); + } + } + } + +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/Person.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/Person.java new file mode 100644 index 00000000000..8c8320cab26 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/Person.java @@ -0,0 +1,48 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package runtime.valhalla.inlinetypes; + +import jdk.internal.vm.annotation.LooselyConsistentValue; + +@LooselyConsistentValue +public value class Person { + + final int id; + final String firstName; + final String lastName; + + public Person(int id, String firstName, String lastName) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + } + + public int getId() { return id; } + public String getFirstName() { return firstName; } + public String getLastName() { return lastName; } + + public String toString() { + return getFirstName() + " " + getLastName() + " (id=" + getId() + ")"; + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/Point.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/Point.java new file mode 100644 index 00000000000..2e4d80a9741 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/Point.java @@ -0,0 +1,56 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package runtime.valhalla.inlinetypes; + +import jdk.internal.vm.annotation.LooselyConsistentValue; + +@LooselyConsistentValue +public value class Point { + final int x; + final int y; + + public Point(int x, int y) { + this.x = x; + this.y = y; + } + + public int getX() { return x; } + public int getY() { return y; } + + public boolean isSamePoint(Point that) { + return this.getX() == that.getX() && this.getY() == that.getY(); + } + + public String toString() { + return "Point: x=" + getX() + " y=" + getY(); + } + + public boolean equals(Object o) { + if(o instanceof Point) { + return ((Point)o).x == x && ((Point)o).y == y; + } else { + return false; + } + } + +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/PreloadCircularityTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/PreloadCircularityTest.java new file mode 100644 index 00000000000..bf0f7d72fc2 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/PreloadCircularityTest.java @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test PreloadCircularityTest + * @modules java.base/jdk.internal.vm.annotation + * java.base/jdk.internal.value + * @library /test/lib + * @requires vm.flagless + * @enablePreview + * @compile PreloadCircularityTest.java + * @run main/othervm PreloadCircularityTest + */ + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.management.relation.RelationServiceNotRegisteredException; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import jdk.test.lib.Asserts; +import jdk.test.lib.Utils; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class PreloadCircularityTest { + + // Testing preload due to non-static fields + + static value class Class0a { + @Strict + @NullRestricted + Class0b vb = new Class0b(); + } + + static value class Class0b { + @Strict + @NullRestricted + Class0c vc = new Class0c(); + } + + static value class Class0c { + int i = 0; + } + + void test_0() throws Exception { + OutputAnalyzer out = tryLoadingClass("PreloadCircularityTest$Class0a"); + out.shouldHaveExitValue(0); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class0b during loading of class PreloadCircularityTest$Class0a. Cause: a null-free non-static field is declared with this type"); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class0c during loading of class PreloadCircularityTest$Class0b. Cause: a null-free non-static field is declared with this type"); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class0c during loading of class PreloadCircularityTest$Class0b (cause: null-free non-static field) succeeded"); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class0b during loading of class PreloadCircularityTest$Class0a (cause: null-free non-static field) succeeded"); + out.shouldNotContain("(cause: null-free non-static field) failed"); + } + + static value class Class1a { + @Strict + @NullRestricted + Class1b vb = new Class1b(); + } + + static value class Class1b { + Class1c vc = new Class1c(); + } + + static value class Class1c { + int i = 0; + } + + void test_1() throws Exception { + OutputAnalyzer out = tryLoadingClass("PreloadCircularityTest$Class1a"); + out.shouldHaveExitValue(0); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class1b during loading of class PreloadCircularityTest$Class1a. Cause: a null-free non-static field is declared with this type"); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class1c during loading of class PreloadCircularityTest$Class1b. Cause: field type in LoadableDescriptors attribute"); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class1c during loading of class PreloadCircularityTest$Class1b (cause: field type in LoadableDescriptors attribute) succeeded"); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class1b during loading of class PreloadCircularityTest$Class1a (cause: null-free non-static field) succeeded"); + } + + static value class Class2a { + @Strict + @NullRestricted + Class2b vb = new Class2b(); + } + + static value class Class2b { + @Strict + @NullRestricted + Class2c vc = new Class2c(); + } + + static value class Class2c { + @Strict + @NullRestricted + Class2b vb = new Class2b(); + } + + void test_2() throws Exception { + OutputAnalyzer out = tryLoadingClass("PreloadCircularityTest$Class2a"); + out.shouldHaveExitValue(1); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class2b during loading of class PreloadCircularityTest$Class2a. Cause: a null-free non-static field is declared with this type"); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class2c during loading of class PreloadCircularityTest$Class2b. Cause: a null-free non-static field is declared with this type"); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class2b during loading of class PreloadCircularityTest$Class2c. Cause: a null-free non-static field is declared with this type"); + out.shouldContain("[warning][class,preload] Preloading of class PreloadCircularityTest$Class2b during loading of class PreloadCircularityTest$Class2c (cause: null-free non-static field) failed: java/lang/ClassCircularityError"); + out.shouldContain("[warning][class,preload] Preloading of class PreloadCircularityTest$Class2c during loading of class PreloadCircularityTest$Class2b (cause: null-free non-static field) failed: java/lang/ClassCircularityError"); + out.shouldContain("[warning][class,preload] Preloading of class PreloadCircularityTest$Class2b during loading of class PreloadCircularityTest$Class2a (cause: null-free non-static field) failed: java/lang/ClassCircularityError"); + } + + static value class Class3a { + @Strict + @NullRestricted + Class3b vb = new Class3b(); + } + + static value class Class3b { + @Strict + @NullRestricted + Class3c vc = new Class3c(); + } + + static value class Class3c { + Class3b vb = new Class3b(); + } + + void test_3() throws Exception { + OutputAnalyzer out = tryLoadingClass("PreloadCircularityTest$Class3a"); + out.shouldHaveExitValue(0); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class3b during loading of class PreloadCircularityTest$Class3a. Cause: a null-free non-static field is declared with this type"); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class3c during loading of class PreloadCircularityTest$Class3b. Cause: a null-free non-static field is declared with this type"); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class3b during loading of class PreloadCircularityTest$Class3c. Cause: field type in LoadableDescriptors attribute"); + out.shouldContain("[warning][class,preload] Preloading of class PreloadCircularityTest$Class3b during loading of class PreloadCircularityTest$Class3c (cause: field type in LoadableDescriptors attribute) failed : java/lang/ClassCircularityError"); + out.shouldContain("[info ][class,preload] Preloading of class PreloadCircularityTest$Class3c during loading of class PreloadCircularityTest$Class3b (cause: null-free non-static field) succeeded"); + out.shouldContain("[info ][class,preload] Preloading of class PreloadCircularityTest$Class3b during loading of class PreloadCircularityTest$Class3a (cause: null-free non-static field) succeeded"); + } + + static value class Class4a { + @Strict + @NullRestricted + Class4b vb = new Class4b(); + } + + static value class Class4b { + @Strict + @NullRestricted + Class4a vc = new Class4a(); + } + + void test_4() throws Exception { + OutputAnalyzer out = tryLoadingClass("PreloadCircularityTest$Class4a"); + out.shouldHaveExitValue(1); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class4b during loading of class PreloadCircularityTest$Class4a. Cause: a null-free non-static field is declared with this type"); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class4a during loading of class PreloadCircularityTest$Class4b. Cause: a null-free non-static field is declared with this type"); + out.shouldContain("[warning][class,preload] Preloading of class PreloadCircularityTest$Class4a during loading of class PreloadCircularityTest$Class4b (cause: null-free non-static field) failed: java/lang/ClassCircularityError"); + out.shouldContain("[warning][class,preload] Preloading of class PreloadCircularityTest$Class4b during loading of class PreloadCircularityTest$Class4a (cause: null-free non-static field) failed: java/lang/ClassCircularityError"); + } + + static value class Class5a { + Class5b vb = new Class5b(); + + @Strict + @NullRestricted + Class5c vc = new Class5c(); + } + + static value class Class5b { + @Strict + @NullRestricted + Class5d vd = new Class5d(); + } + + static value class Class5c { + Class5b vb = new Class5b(); + } + + static value class Class5d { + @Strict + @NullRestricted + Class5b vb = new Class5b(); + } + + void test_5() throws Exception { + OutputAnalyzer out = tryLoadingClass("PreloadCircularityTest$Class5a"); + out.shouldHaveExitValue(0); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class5b during loading of class PreloadCircularityTest$Class5a. Cause: field type in LoadableDescriptors attribute"); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class5d during loading of class PreloadCircularityTest$Class5b. Cause: a null-free non-static field is declared with this type"); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class5b during loading of class PreloadCircularityTest$Class5d. Cause: a null-free non-static field is declared with this type"); + out.shouldContain("[warning][class,preload] Preloading of class PreloadCircularityTest$Class5b during loading of class PreloadCircularityTest$Class5d (cause: null-free non-static field) failed: java/lang/ClassCircularityError"); + out.shouldContain("[warning][class,preload] Preloading of class PreloadCircularityTest$Class5d during loading of class PreloadCircularityTest$Class5b (cause: null-free non-static field) failed: java/lang/ClassCircularityError"); + out.shouldContain("[warning][class,preload] Preloading of class PreloadCircularityTest$Class5b during loading of class PreloadCircularityTest$Class5a (cause: field type in LoadableDescriptors attribute) failed : java/lang/ClassCircularityError"); + out.shouldContain("[info ][class,preload] Preloading of class PreloadCircularityTest$Class5c during loading of class PreloadCircularityTest$Class5a. Cause: a null-free non-static field is declared with this type"); + out.shouldContain("[info ][class,preload] Preloading of class PreloadCircularityTest$Class5b during loading of class PreloadCircularityTest$Class5c. Cause: field type in LoadableDescriptors attribute"); + out.shouldContain("[info ][class,preload] Preloading of class PreloadCircularityTest$Class5d during loading of class PreloadCircularityTest$Class5b. Cause: a null-free non-static field is declared with this type"); + out.shouldContain("[info ][class,preload] Preloading of class PreloadCircularityTest$Class5b during loading of class PreloadCircularityTest$Class5d. Cause: a null-free non-static field is declared with this type"); + out.shouldContain("[warning][class,preload] Preloading of class PreloadCircularityTest$Class5b during loading of class PreloadCircularityTest$Class5d (cause: null-free non-static field) failed: java/lang/ClassCircularityError"); + out.shouldContain("[warning][class,preload] Preloading of class PreloadCircularityTest$Class5d during loading of class PreloadCircularityTest$Class5b (cause: null-free non-static field) failed: java/lang/ClassCircularityError"); + out.shouldContain("[warning][class,preload] Preloading of class PreloadCircularityTest$Class5b during loading of class PreloadCircularityTest$Class5c (cause: field type in LoadableDescriptors attribute) failed : java/lang/ClassCircularityError"); + out.shouldContain("[info ][class,preload] Preloading of class PreloadCircularityTest$Class5c during loading of class PreloadCircularityTest$Class5a (cause: null-free non-static field) succeeded"); + } + + static value class Class6a { + @Strict + @NullRestricted + Class6b vb = new Class6b(); + } + + static value class Class6b { + Class6c vc = new Class6c(); + + @Strict + @NullRestricted + Class6d vd = new Class6d(); + } + + static value class Class6c { + int i = 0; + } + + static value class Class6d { + @Strict + @NullRestricted + Class6b vb = new Class6b(); + } + + void test_6() throws Exception { + OutputAnalyzer out = tryLoadingClass("PreloadCircularityTest$Class6a"); + out.shouldHaveExitValue(1); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class6b during loading of class PreloadCircularityTest$Class6a. Cause: a null-free non-static field is declared with this type"); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class6c during loading of class PreloadCircularityTest$Class6b. Cause: field type in LoadableDescriptors attribute"); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class6c during loading of class PreloadCircularityTest$Class6b (cause: field type in LoadableDescriptors attribute) succeeded"); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class6d during loading of class PreloadCircularityTest$Class6b. Cause: a null-free non-static field is declared with this type"); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class6b during loading of class PreloadCircularityTest$Class6d. Cause: a null-free non-static field is declared with this type"); + out.shouldContain("[warning][class,preload] Preloading of class PreloadCircularityTest$Class6b during loading of class PreloadCircularityTest$Class6d (cause: null-free non-static field) failed: java/lang/ClassCircularityError"); + out.shouldContain("[warning][class,preload] Preloading of class PreloadCircularityTest$Class6d during loading of class PreloadCircularityTest$Class6b (cause: null-free non-static field) failed: java/lang/ClassCircularityError"); + out.shouldContain("[warning][class,preload] Preloading of class PreloadCircularityTest$Class6b during loading of class PreloadCircularityTest$Class6a (cause: null-free non-static field) failed: java/lang/ClassCircularityError"); + } + + static value class Class7a { + @Strict + @NullRestricted + Class7a va = new Class7a(); + } + + void test_7() throws Exception { + OutputAnalyzer out = tryLoadingClass("PreloadCircularityTest$Class7a"); + out.shouldHaveExitValue(1); + out.shouldNotContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class7a during loading of class PreloadCircularityTest$Class7a. Cause: a null-free non-static field is declared with this type"); + } + + static value class Class8a { + Class8a va = new Class8a(); + } + + void test_8() throws Exception { + OutputAnalyzer out = tryLoadingClass("PreloadCircularityTest$Class8a"); + out.shouldHaveExitValue(0); + out.shouldNotContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class8a during loading of class PreloadCircularityTest$Class8a. Cause: a null-free non-static field is declared with this type"); + } + + static value class Class9a { + @Strict + @NullRestricted + Class9b vb = new Class9b(); + } + + static class Class9b { } + + void test_9() throws Exception { + OutputAnalyzer out = tryLoadingClass("PreloadCircularityTest$Class9a"); + out.shouldHaveExitValue(1); + out.shouldContain("[info][class,preload] Preloading of class PreloadCircularityTest$Class9b during loading of class PreloadCircularityTest$Class9a. Cause: a null-free non-static field is declared with this type"); + out.shouldContain("java.lang.IncompatibleClassChangeError: Class PreloadCircularityTest$Class9a expects class PreloadCircularityTest$Class9b to be a value class, but it is an identity class"); + } + + public static class TestHelper { + public static void main(String[] args) { + try { + Class c = Class.forName(args[0]); + } catch (Throwable ex) { + ex.printStackTrace(); + System.exit(1); + } + System.exit(0); + } + } + + static OutputAnalyzer tryLoadingClass(String className) throws Exception { + ProcessBuilder pb = exec("PreloadCircularityTest$TestHelper", className); + OutputAnalyzer out = new OutputAnalyzer(pb.start()); + System.out.println(out.getOutput()); + return out; + } + + static ProcessBuilder exec(String... args) throws Exception { + List argsList = new ArrayList<>(); + Collections.addAll(argsList, "--enable-preview"); + Collections.addAll(argsList, "-Dtest.class.path=" + System.getProperty("test.class.path", ".")); + Collections.addAll(argsList, "-Xlog:class+preload=info"); + Collections.addAll(argsList, args); + return ProcessTools.createTestJavaProcessBuilder(argsList); + } + + public static void main(String[] args) throws Exception { + System.out.println("Crerating tests"); + PreloadCircularityTest tests = new PreloadCircularityTest(); + Class c = tests.getClass(); + System.out.println("Iterating over test methods"); + boolean hasFailedTest = false; + StringBuilder sb = new StringBuilder("Following tests have failed: "); + for (Method m : c.getDeclaredMethods()) { + if (m.getName().startsWith("test_")) { + boolean failed = false; + try { + System.out.println("Running " + m.getName()); + m.invoke(tests); + } catch (Throwable t) { + t.printStackTrace(); + failed = true; + } + System.out.println("Test " + m.getName() + " : " + (failed ? "FAILED" : "PASSED")); + hasFailedTest = failed ? true : hasFailedTest; + if (failed) sb.append(m.getName()).append(", "); + } + } + if (hasFailedTest) { + throw new RuntimeException(sb.toString()); + } + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/PreloadShared.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/PreloadShared.java new file mode 100644 index 00000000000..968433dd3e8 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/PreloadShared.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.time.LocalDateTime; + +public class PreloadShared { + public static LocalDateTime test() { + return null; + } + + public static void main(String[] args) { + test(); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/PreloadValue0.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/PreloadValue0.java new file mode 100644 index 00000000000..7e2ec9f661a --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/PreloadValue0.java @@ -0,0 +1,24 @@ +/* + * 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 + * 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. + */ + +public value class PreloadValue0 { } diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/QuickeningTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/QuickeningTest.java new file mode 100644 index 00000000000..79b3b4622c8 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/QuickeningTest.java @@ -0,0 +1,226 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package runtime.valhalla.inlinetypes; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import jdk.test.lib.Asserts; + +/* + * @test QuickeningTest + * @summary Test quickening of getfield and putfield applied to inline fields + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile QuickeningTest.java + * @run main/othervm runtime.valhalla.inlinetypes.QuickeningTest + */ + +public class QuickeningTest { + + @LooselyConsistentValue + static value class Point { + final int x; + final int y; + + public Point(int x, int y) { + this.x = x; + this.y = y; + } + } + + @LooselyConsistentValue + static value class JumboInline { + final long l0; + final long l1; + final long l2; + final long l3; + final long l4; + final long l5; + final long l6; + final long l7; + final long l8; + final long l9; + final long l10; + final long l11; + final long l12; + final long l13; + final long l14; + final long l15; + final long l16; + final long l17; + final long l18; + final long l19; + + public JumboInline(long l0Val, long l1Val) { + l0 = l0Val; + l1 = l1Val; + l2 = l0Val+1; + l3 = l1Val+2; + l4 = l0Val+3; + l5 = l1Val+4; + l6 = l0Val+5; + l7 = l1Val+6; + l8 = l0Val+7; + l9 = l1Val+8; + l10 = l0Val+9; + l11 = l1Val+10; + l12 = l0Val+11; + l13 = l1Val+12; + l14 = l0Val+13; + l15 = l1Val+14; + l16 = l0Val+15; + l17 = l1Val+16; + l18 = l0Val+17; + l19 = l1Val+18; + } + } + + static class Parent { + Point nfp; /* Not flattenable inline field */ + @Strict + @NullRestricted + Point fp = new Point(1, 2); /* Flattenable and flattened inline field */ + @Strict + @NullRestricted + JumboInline fj = new JumboInline(3L, 4L); /* Flattenable not flattened inline field */ + + public void setNfp(Point p) { nfp = p; } + public void setFp(Point p) { fp = p; } + public void setFj(JumboInline j) { fj = j; } + } + + static class Child extends Parent { + // This class inherited fields from the Parent class + Point nfp2; /* Not flattenable inline field */ + @Strict + @NullRestricted + Point fp2 = new Point(5, 6); /* Flattenable and flattened inline field */ + @Strict + @NullRestricted + JumboInline fj2 = new JumboInline(7L, 8L); /* Flattenable not flattened inline field */ + + public void setNfp2(Point p) { nfp2 = p; } + public void setFp2(Point p) { fp2 = p; } + public void setFj2(JumboInline j) { fj2 = j; } + } + + @LooselyConsistentValue + static value class Value { + final Point nfp; /* Not flattenable inline field */ + @Strict + @NullRestricted + final Point fp; /* Flattenable and flattened inline field */ + @Strict + @NullRestricted + final JumboInline fj; /* Flattenable not flattened inline field */ + + private Value() { + nfp = null; + fp = new Point(9, 10); + fj = new JumboInline(11L, 12L); + } + } + + static void testUninitializedFields() { + Parent p = new Parent(); + Asserts.assertEquals(p.nfp, null, "invalid uninitialized not flattenable"); + Asserts.assertEquals(p.fp.x, 1, "invalid value for uninitialized flattened field"); + Asserts.assertEquals(p.fp.y, 2, "invalid value for uninitialized flattened field"); + Asserts.assertEquals(p.fj.l0, 3L, "invalid value for uninitialized flattened field"); + Asserts.assertEquals(p.fj.l1, 4L, "invalid value for uninitialized flattened field"); + + Child c = new Child(); + Asserts.assertEquals(c.nfp, null, "invalid uninitialized not flattenable field"); + Asserts.assertEquals(c.fp.x, 1, "invalid value for uninitialized flattened field"); + Asserts.assertEquals(c.fp.y, 2, "invalid value for uninitialized flattened field"); + Asserts.assertEquals(c.fj.l0, 3L, "invalid value for uninitialized flattened field"); + Asserts.assertEquals(c.fj.l1, 4L, "invalid value for uninitialized flattened field"); + Asserts.assertEquals(c.nfp2, null, "invalid uninitialized not flattenable"); + Asserts.assertEquals(c.fp2.x, 5, "invalid value for uninitialized flattened field"); + Asserts.assertEquals(c.fp2.y, 6, "invalid value for uninitialized flattened field"); + Asserts.assertEquals(c.fj2.l0, 7L, "invalid value for uninitialized not flattened field"); + Asserts.assertEquals(c.fj2.l1, 8L, "invalid value for uninitialized not flattened field"); + + Value v = new Value(); + Asserts.assertEquals(v.nfp, null, "invalid uninitialized not flattenable"); + Asserts.assertEquals(v.fp.x, 9, "invalid value for uninitialized flattened field"); + Asserts.assertEquals(v.fp.y, 10, "invalid value for uninitialized flattened field"); + Asserts.assertEquals(v.fj.l0, 11L, "invalid value for uninitialized not flattened field"); + Asserts.assertEquals(v.fj.l1, 12L, "invalid value for uninitialized not flattened field"); + } + + static void testPutfieldAndGetField() { + Point p1 = new Point(16, 47); + Point p2 = new Point(32, 64); + + JumboInline j1 = new JumboInline(4, 5); + JumboInline j2 = new JumboInline(7, 9); + + Parent p = new Parent(); + // executing each setter twice to test quickened bytecodes + p.setNfp(p1); + p.setNfp(p2); + p.setFp(p2); + p.setFp(p1); + p.setFj(j1); + p.setFj(j2); + + Asserts.assertTrue(p.nfp.equals(p2), "invalid updated not flattenable field"); + Asserts.assertEquals(p.fp.x, 16, "invalid value for updated flattened field"); + Asserts.assertEquals(p.fp.y, 47, "invalid value for updated flattened field"); + Asserts.assertTrue(p.fj.equals(j2), "invalid value for updated not flattened field"); + + Child c = new Child(); + c.setNfp(p1); + c.setNfp(p2); + c.setFp(p2); + c.setFp(p1); + c.setFj(j1); + c.setFj(j2); + c.setNfp2(p2); + c.setNfp2(p1); + c.setFp2(p1); + c.setFp2(p2); + c.setFj2(j2); + c.setFj2(j1); + + Asserts.assertTrue(c.nfp.equals(p2), "invalid updated not flattenable field"); + Asserts.assertEquals(c.fp.x, 16, "invalid value for updated flattened field"); + Asserts.assertEquals(c.fp.y, 47, "invalid value for updated flattened field"); + Asserts.assertTrue(c.fj.equals(j2), "invalid value for updated not flattened field"); + + Asserts.assertTrue(c.nfp2.equals(p1), "invalid updated not flattenable field"); + Asserts.assertEquals(c.fp2.x, 32, "invalid value for updated flattened field"); + Asserts.assertEquals(c.fp2.y, 64, "invalid value for updated flattened field"); + Asserts.assertTrue(c.fj2.equals(j1), "invalid value for updated not flattened field"); + } + + public static void main(String[] args) { + testUninitializedFields(); + testUninitializedFields(); // run twice to test quickened bytecodes + testPutfieldAndGetField(); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/StaticFieldsTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/StaticFieldsTest.java new file mode 100644 index 00000000000..39b7f7c59f8 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/StaticFieldsTest.java @@ -0,0 +1,167 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package runtime.valhalla.inlinetypes; + +import jdk.test.lib.Asserts; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + + +/* + * @test + * @summary Test circularity in static fields + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile StaticFieldsTest.java + * @run main/othervm runtime.valhalla.inlinetypes.StaticFieldsTest + */ + +public class StaticFieldsTest { + + + // ClassA and ClassB have a simple cycle in their static fields, but they should + // be able to load and initialize themselves successfully. Access to these + // static fields after their initialization should return the default value. + @LooselyConsistentValue + static value class ClassA { + @Strict + @NullRestricted + static ClassB b = new ClassB(); + public int i; + + public ClassA() { + i = 3; + } + } + + @LooselyConsistentValue + static value class ClassB { + @Strict + @NullRestricted + static ClassA a = new ClassA(); + public int i; + + public ClassB() { + i = 700; + } + } + + // ClassC has a reference to itself in its static field, but it should be able + // to initialize itself successfully. Access to this static field after initialization + // should return the default value. + @LooselyConsistentValue + static value class ClassC { + @Strict + @NullRestricted + static ClassC c = new ClassC(); + int i; + + public ClassC() { + i = 42; + } + } + + + // ClassD and ClassE have circular references in their static fields, and they + // read these static fields during their initialization, the value read from + // these fields should be the default value. Both classes should initialize + // successfully. + @LooselyConsistentValue + static value class ClassD { + @Strict + @NullRestricted + static ClassE e = new ClassE(); + int i; + + static { + Asserts.assertEquals(e.i, 42, "Static field e.i incorrect"); + } + + public ClassD() { + i = 42; + } + } + + @LooselyConsistentValue + static value class ClassE { + @Strict + @NullRestricted + static ClassD d = new ClassD(); + int i; + + static { + Asserts.assertEquals(d.i, 42, "Static field d.i incorrect"); + } + + public ClassE() { + i = 42; + } + } + + // ClassF and ClassG have circular references in their static fields, and they + // create new instances of each other type to initialize these static fields + // during their initialization. Both classes should initialize successfully. + @LooselyConsistentValue + static value class ClassF { + @Strict + @NullRestricted + static ClassG g = new ClassG(); + int i; + + static { + g = new ClassG(); + Asserts.assertEquals(g.i, 64, "Static field ClassF.g.i incorrect"); + } + + ClassF() { + i = 314; + } + } + + @LooselyConsistentValue + static value class ClassG { + @Strict + @NullRestricted + static ClassF f = new ClassF(); + int i; + + static { + f = new ClassF(); + Asserts.assertEquals(f.i, 314, "Static field ClassG.f.i incorrect"); + } + + ClassG() { + i = 64; + } + } + + public static void main(String[] args) { + Asserts.assertEquals(ClassA.b.i, 700, "Static field ClassA.b.i incorrect"); + Asserts.assertEquals(ClassB.a.i, 3, "Static field Classb.a.i incorrect"); + Asserts.assertEquals(ClassC.c.i, 42, "Static field ClassC.c.i incorrect"); + new ClassD(); + new ClassF(); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/Test8186715.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/Test8186715.java new file mode 100644 index 00000000000..ce9584c20b4 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/Test8186715.java @@ -0,0 +1,73 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package runtime.valhalla.inlinetypes; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.Strict; + +/* + * @test Test8186715 + * @summary test return of buffered inline type passed in argument by caller + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * java.base/jdk.internal.value + * @enablePreview + * @compile Test8186715.java + * @run main/othervm runtime.valhalla.inlinetypes.Test8186715 + */ + +public class Test8186715 { + + public static void main(String[] args) { + MyValueType v = MyValueType.testDefault(); + + for (int i = 0; i < 1000000; i++) { + MyValueType.testBranchArg1(false, v); + } + } +} + +@LooselyConsistentValue +value class MyValueType { + final int i; + final int j; + + private MyValueType(int i, int j) { + this.i = i; + this.j = j; + } + + static MyValueType testDefault() { + MyValueType[] array = (MyValueType[])ValueClass.newNullRestrictedNonAtomicArray(MyValueType.class, 1, new MyValueType(0, 0)); + return array[0]; + } + + static MyValueType testBranchArg1(boolean flag, MyValueType v1) { + if (flag) { + v1 = new MyValueType(3, 4); + } + return v1; + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/TestCloneableValue.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/TestCloneableValue.java new file mode 100644 index 00000000000..0e2eb43d8f8 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/TestCloneableValue.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test TestCloneableValue + * @library /test/lib + * @enablePreview + * @compile TestCloneableValue.java + * @run main/othervm runtime.valhalla.inlinetypes.TestCloneableValue + */ + + package runtime.valhalla.inlinetypes; + + import jdk.test.lib.Asserts; + + import java.util.ArrayList; + + public class TestCloneableValue { + + static value class SimpleValue implements Cloneable { + int i; + double j; + + public SimpleValue() { + i = 42; + j = Math.E; + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); // delegate to Object's method performing a shallow copy + } + } + + static value class NotSoSimpleValue implements Cloneable { + ArrayList list; + + public NotSoSimpleValue() { + list = new ArrayList<>(); + } + + private NotSoSimpleValue(ArrayList l) { + list = l; + } + + @Override + public Object clone() throws CloneNotSupportedException { + return new NotSoSimpleValue((ArrayList)list.clone()); + } + } + + public static void main(String[] args) { + var sv = new SimpleValue(); + try { + var c1 = sv.clone(); + Asserts.assertEQ(sv, c1); + Asserts.assertEQ(sv.hashCode(), c1.hashCode()); + } catch(CloneNotSupportedException e) { + Asserts.fail("Unexpected exception", e); + } + + var nssv = new NotSoSimpleValue(); + try { + var c2 = nssv.clone(); + Asserts.assertNE(nssv, c2); + Asserts.assertEQ(nssv.hashCode(), c2.hashCode()); + } catch(CloneNotSupportedException e) { + Asserts.fail("Unexpected exception", e); + } + } + } \ No newline at end of file diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/TestFieldNullability.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/TestFieldNullability.java new file mode 100644 index 00000000000..7f6bd17005a --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/TestFieldNullability.java @@ -0,0 +1,100 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test TestFieldNullability + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile TestFieldNullability.java + * @run main/othervm -Xmx128m -XX:+UseFieldFlattening + * runtime.valhalla.inlinetypes.TestFieldNullability + */ + +package runtime.valhalla.inlinetypes; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import jdk.test.lib.Asserts; + + +public class TestFieldNullability { + @LooselyConsistentValue + static value class MyValue { + int x; + + public MyValue() { + x = 314; + } + } + + @LooselyConsistentValue + static value class MyBigValue { + long l0, l1, l2, l3, l4, l5, l6, l7, l8, l9; + long l10, l11, l12, l13, l14, l15, l16, l17, l18, l19; + + public MyBigValue() { + l0 = l1 = l2 = l3 = l4 = l5 = l6 = l7 = l8 = l9 = 271; + l10 = l11 = l12 = l13 = l14 = l15 = l16 = l17 = l18 = l19 = 271; + } + } + + static class TestIdentityClass { + MyValue nullableField; + @Strict + @NullRestricted + MyValue nullfreeField = new MyValue(); // flattened + MyValue nullField; + @Strict + @NullRestricted + MyBigValue nullfreeBigField = new MyBigValue(); // not flattened + MyBigValue nullBigField; + } + + public static void main(String[] args) { + TestIdentityClass that = new TestIdentityClass(); + Asserts.assertNull(that.nullField, "Invalid non null value for uninitialized non flattenable field"); + Asserts.assertNull(that.nullBigField, "Invalid non null value for uninitialized non flattenable field"); + boolean NPE = false; + try { + that.nullableField = that.nullField; + } catch(NullPointerException e) { + NPE = true; + } + Asserts.assertFalse(NPE, "Invalid NPE when assigning null to a non flattenable field"); + try { + that.nullfreeField = (MyValue) that.nullField; + } catch(NullPointerException e) { + NPE = true; + } + Asserts.assertTrue(NPE, "Missing NPE when assigning null to a flattened field"); + try { + that.nullfreeBigField = (MyBigValue) that.nullBigField; + } catch(NullPointerException e) { + NPE = true; + } + Asserts.assertTrue(NPE, "Missing NPE when assigning null to a flattenable field"); + } + +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/TestFlatArrayElementMaxOops.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/TestFlatArrayElementMaxOops.java new file mode 100644 index 00000000000..c00979dc7ff --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/TestFlatArrayElementMaxOops.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package runtime.valhalla.inlinetypes; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; + +import jdk.test.lib.Asserts; + +/* + * @test TestFlatArrayElementMaxOops + * @summary Test that the FlatArrayElementMaxOops flag works as expected. + * @requires vm.flagless + * @modules java.base/jdk.internal.vm.annotation + * java.base/jdk.internal.value + * @library /test/lib + * @enablePreview + * @run main/othervm -XX:+UseArrayFlattening -XX:FlatArrayElementMaxOops=0 + * -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseNonAtomicValueFlattening + * runtime.valhalla.inlinetypes.TestFlatArrayElementMaxOops 0 + * @run main/othervm -XX:+UseArrayFlattening -XX:FlatArrayElementMaxOops=1 + * -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseNonAtomicValueFlattening + * runtime.valhalla.inlinetypes.TestFlatArrayElementMaxOops 1 + * @run main/othervm -XX:+UseArrayFlattening -XX:FlatArrayElementMaxOops=2 + * -XX:+UseNullableValueFlattening -XX:+UseAtomicValueFlattening -XX:+UseNonAtomicValueFlattening + * runtime.valhalla.inlinetypes.TestFlatArrayElementMaxOops 2 + */ + +public class TestFlatArrayElementMaxOops { + + @LooselyConsistentValue + static value class ValueWithOneOoop { + Object obj1 = null; + } + + @LooselyConsistentValue + static value class ValueWithTwoOoops { + Object obj1 = null; + Object obj2 = null; + } + + public static void main(String[] args) { + int FlatArrayElementMaxOops = Integer.valueOf(args[0]); + Object[] array = ValueClass.newNullRestrictedNonAtomicArray(ValueWithOneOoop.class, 1, new ValueWithOneOoop()); + Asserts.assertEquals(ValueClass.isFlatArray(array), FlatArrayElementMaxOops >= 1); + array = ValueClass.newNullRestrictedAtomicArray(ValueWithOneOoop.class, 1, new ValueWithOneOoop()); + Asserts.assertEquals(ValueClass.isFlatArray(array), FlatArrayElementMaxOops >= 1); + array = ValueClass.newNullableAtomicArray(ValueWithOneOoop.class, 1); + Asserts.assertEquals(ValueClass.isFlatArray(array), FlatArrayElementMaxOops >= 1); + + array = ValueClass.newNullRestrictedNonAtomicArray(ValueWithTwoOoops.class, 1, new ValueWithTwoOoops()); + Asserts.assertEquals(ValueClass.isFlatArray(array), FlatArrayElementMaxOops >= 2); + array = ValueClass.newNullRestrictedAtomicArray(ValueWithTwoOoops.class, 1, new ValueWithTwoOoops()); + Asserts.assertEquals(ValueClass.isFlatArray(array), FlatArrayElementMaxOops >= 2); + array = ValueClass.newNullableAtomicArray(ValueWithTwoOoops.class, 1); + Asserts.assertFalse(ValueClass.isFlatArray(array)); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/TestInheritedInlineTypeFields.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/TestInheritedInlineTypeFields.java new file mode 100644 index 00000000000..60ae5d50aef --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/TestInheritedInlineTypeFields.java @@ -0,0 +1,102 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package runtime.valhalla.inlinetypes; + +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import jdk.test.lib.Asserts; + +/* + * @test TestInheritedInlineTypeFields + * @summary Test if inline field klasses are correctly retrieved for inherited fields + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile Point.java TestInheritedInlineTypeFields.java + * @run main/othervm runtime.valhalla.inlinetypes.TestInheritedInlineTypeFields + */ + +class A { + @Strict + @NullRestricted + Point p = new Point(1, 2); +} + +class B extends A { + +} + +class C extends B { + int i; +} + +class D { + long l; +} + +class E extends D { + @Strict + @NullRestricted + Point p1 = new Point(3, 4); +} + +class F extends E { + +} + +class G extends F { + @Strict + @NullRestricted + Point p2 = new Point(5, 6); +} + +public class TestInheritedInlineTypeFields { + + public static void main(String[] args) { + for (int i = 0; i < 100000; i++) { + run(); + } + } + + public static void run() { + B b = new B(); + Asserts.assertEquals(b.p.x, 1); + Asserts.assertEquals(b.p.y, 2); + b.p = new Point(2,3); + Asserts.assertEquals(b.p.x, 2); + Asserts.assertEquals(b.p.y, 3); + + G g = new G(); + Asserts.assertEquals(g.p1.x, 3); + Asserts.assertEquals(g.p1.y, 4); + Asserts.assertEquals(g.p2.x, 5); + Asserts.assertEquals(g.p2.y, 6); + g.p1 = new Point(1,2); + g.p2 = new Point(3,4); + Asserts.assertEquals(g.p1.x, 1); + Asserts.assertEquals(g.p1.y, 2); + Asserts.assertEquals(g.p2.x, 3); + Asserts.assertEquals(g.p2.y, 4); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/TestJNIIsSameObject.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/TestJNIIsSameObject.java new file mode 100644 index 00000000000..c5d755955e4 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/TestJNIIsSameObject.java @@ -0,0 +1,84 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.test.lib.Asserts; + + +/* + * @test + * @summary Test JNI IsSameObject semantic with inline types + * @library /testlibrary /test/lib + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile TestJNIIsSameObject.java + * @run main/othervm/native TestJNIIsSameObject + */ + +public class TestJNIIsSameObject { + @LooselyConsistentValue + static value class Value { + int i; + + public Value(int i) { + this.i = i; + } + } + native static boolean isSameObject(Object o0, Object o1); + + static { + System.loadLibrary("JNIIsSameObject"); + } + + public static void main(String[] args) { + // Same value in different instances + Value v0 = new Value(3); + Value v1 = new Value(3); + Asserts.assertTrue(isSameObject(v0, v1)); + + // Different values + Value v2 = new Value(4); + Asserts.assertFalse(isSameObject(v0, v2)); + + // Same object + TestJNIIsSameObject t0 = new TestJNIIsSameObject(); + Object o = t0; + Asserts.assertTrue(isSameObject(t0, o)); + + // Different objects + TestJNIIsSameObject t1 = new TestJNIIsSameObject(); + Asserts.assertFalse(isSameObject(t0, t1)); + + // Comparing against null + Asserts.assertFalse(isSameObject(v0, null)); + Asserts.assertFalse(isSameObject(null, v0)); + Asserts.assertFalse(isSameObject(t0, null)); + Asserts.assertFalse(isSameObject(null, t0)); + + // Object vs inline + Asserts.assertFalse(isSameObject(v0, t0)); + Asserts.assertFalse(isSameObject(t0, v0)); + + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/UnsafeTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/UnsafeTest.java new file mode 100644 index 00000000000..c419a00492c --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/UnsafeTest.java @@ -0,0 +1,420 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package runtime.valhalla.inlinetypes; + + + +/* + * @test UnsafeTest + * @requires vm.debug == true + * @summary unsafe get/put/with inline type + * @modules java.base/jdk.internal.misc + * @modules java.base/jdk.internal.value + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @requires vm.flagless + * @compile Point.java UnsafeTest.java + * @run main/othervm -Xint -XX:+UseNullableValueFlattening -XX:+UseArrayFlattening -XX:+UseFieldFlattening -XX:+PrintInlineLayout runtime.valhalla.inlinetypes.UnsafeTest + */ + +// TODO 8350865 Implement unsafe intrinsics for nullable flat fields/arrays in C2 + +import jdk.internal.misc.Unsafe; +import jdk.internal.misc.VM; +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.test.lib.Asserts; + +import java.lang.reflect.*; +import java.util.List; +import static jdk.test.lib.Asserts.*; + +public class UnsafeTest { + static final Unsafe U = Unsafe.getUnsafe(); + + @LooselyConsistentValue + static value class Value1 { + @NullRestricted + Point point; + Point[] array; + Value1(Point p, Point... points) { + this.point = p; + this.array = points; + } + } + + @LooselyConsistentValue + static value class Value2 { + int i; + @NullRestricted + Value1 v; + + Value2(Value1 v, int i) { + this.v = v; + this.i = i; + } + } + + @LooselyConsistentValue + static value class Value3 { + Object o; + @NullRestricted + Value2 v; + + Value3(Value2 v, Object ref) { + this.v = v; + this.o = ref; + } + + } + + public static void test0() throws Throwable { + printValueClass(Value3.class, 0); + + Value1 v1 = new Value1(new Point(10,10), new Point(20,20), new Point(30,30)); + Value2 v2 = new Value2(v1, 20); + Value3 v3 = new Value3(v2, List.of("Value3")); + long off_o = U.objectFieldOffset(Value3.class, "o"); + long off_v = U.objectFieldOffset(Value3.class, "v"); + long off_i = U.objectFieldOffset(Value2.class, "i"); + long off_v2 = U.objectFieldOffset(Value2.class, "v"); + + long off_point = U.objectFieldOffset(Value1.class, "point"); + + List list = List.of("Value1", "Value2", "Value3"); + Value3 v = v3; + try { + v = U.makePrivateBuffer(v); + // patch v3.o + U.putReference(v, off_o, list); + // patch v3.v.i; + U.putInt(v, off_v + off_i - U.valueHeaderSize(Value2.class), 999); + // patch v3.v.v.point + U.putValue(v, off_v + off_v2 - U.valueHeaderSize(Value2.class) + off_point - U.valueHeaderSize(Value1.class), + Point.class, new Point(100, 100)); + } finally { + v = U.finishPrivateBuffer(v); + } + + assertEquals(v.v.v.point, new Point(100, 100)); + assertEquals(v.v.i, 999); + assertEquals(v.o, list); + assertEquals(v.v.v.array, v1.array); + + Value1 nv1 = new Value1(new Point(70,70), new Point(80,80), new Point(90,90)); + Value2 nv2 = new Value2(nv1, 100); + Value3 nv3 = new Value3(nv2, list); + + try { + v = U.makePrivateBuffer(v); + // patch v3.v + U.putValue(v, off_v2, Value2.class, nv2); + } finally { + v = U.finishPrivateBuffer(v); + } + assertEquals(v, nv3); + } + + static void printValueClass(Class vc, int level) { + String indent = ""; + for (int i=0; i < level; i++) { + indent += " "; + } + System.out.format("%s%s header size %d%n", indent, vc, U.valueHeaderSize(vc)); + for (Field f : vc.getDeclaredFields()) { + System.out.format("%s%s: %s%s offset %d%n", indent, f.getName(), + U.isFlatField(f) ? "flattened " : "", f.getType(), + U.objectFieldOffset(vc, f.getName())); + if (U.isFlatField(f)) { + printValueClass(f.getType(), level+1); + } + } + } + + // Requires -XX:+UseNullableValueFlattening + static value class MyValue0 { + int val; + + public MyValue0(int i) { + val = i; + } + } + + static class Container0 { + MyValue0 v; + } + + public static void test1() throws Throwable { + Container0 c = new Container0(); + Class cc = Container0.class; + Field[] fields = cc.getDeclaredFields(); + Asserts.assertEquals(fields.length, 1); + Field f = fields[0]; + System.out.println("Field found: " + f); + Asserts.assertTrue(U.isFlatField(f)); + Asserts.assertTrue(U.hasNullMarker(f)); + int nmOffset = U.nullMarkerOffset(f); + Asserts.assertNotEquals(nmOffset, -1); + byte nm = U.getByte(c, nmOffset); + Asserts.assertEquals(nm, (byte)0); + c.v = new MyValue0(42); + Asserts.assertNotNull(c.v); + nm = U.getByte(c, nmOffset); + Asserts.assertNotEquals(nm, 0); + U.getAndSetByteRelease(c, nmOffset, (byte)0); + Asserts.assertNull(c.v); + } + + static value class TestValue1 { + short s0,s1; + + TestValue1() { + s0 = 0; + s1 = 0; + } + + TestValue1(short v0, short v1) { + s0 = v0; + s1 = v1; + } + } + + static class Container1 { + TestValue1 value; + } + + // Testing of nullable flat field supports in Unsafe.getFlatValue()/Unsafe.putValue() + public static void testNullableFlatFields() throws Throwable { + Container1 c = new Container1(); + Class cc = Container1.class; + Field field = cc.getDeclaredField("value"); + Class fc = TestValue1.class; + long offset = U.objectFieldOffset(field); + if (!U.isFlatField(field)) return; // Field not flattened (due to VM flags?), test doesn't apply + // Initial value of the field must be null + Asserts.assertNull(U.getValue(c, offset, fc)); + // Writing all zero value to the field, field must become non-null + TestValue1 val0 = new TestValue1((short)0, (short)0); + U.putValue(c, offset, fc, val0); + TestValue1 rval = U.getValue(c, offset, fc); + Asserts.assertNotNull(rval); + Asserts.assertEQ((short)0, rval.s0); + Asserts.assertEQ((short)0, rval.s1); + Asserts.assertEQ((short)0, c.value.s0); + Asserts.assertEQ((short)0, c.value.s1); + // Writing null to the field, field must become null again + U.putValue(c, offset, fc, null); + Asserts.assertNull(U.getValue(c, offset, fc)); + Asserts.assertNull(c.value); + // Writing non zero value to the field + TestValue1 val1 = new TestValue1((short)-1, (short)-2); + U.putValue(c, offset, fc, val1); + rval = U.getValue(c, offset, fc); + Asserts.assertNotNull(rval); + Asserts.assertNotNull(c.value); + Asserts.assertEQ((short)-1, rval.s0); + Asserts.assertEQ((short)-2, rval.s1); + Asserts.assertEQ((short)-1, c.value.s0); + Asserts.assertEQ((short)-2, c.value.s1); + // Writing a different non zero value + TestValue1 val2 = new TestValue1((short)Short.MAX_VALUE, (short)3); + U.putValue(c, offset, fc, val2); + rval = U.getValue(c, offset, fc); + Asserts.assertNotNull(rval); + Asserts.assertNotNull(c.value); + Asserts.assertEQ(Short.MAX_VALUE, c.value.s0); + Asserts.assertEQ((short)3, rval.s1); + Asserts.assertEQ(Short.MAX_VALUE, c.value.s0); + Asserts.assertEQ((short)3, rval.s1); + } + + public static void testNullableFlatFields2() throws Throwable { + Container1 c = new Container1(); + Class cc = Container1.class; + Field field = cc.getDeclaredField("value"); + Class fc = TestValue1.class; + long offset = U.objectFieldOffset(field); + int layoutKind = U.fieldLayout(field); + if (!U.isFlatField(field)) return; // Field not flattened (due to VM flags?), test doesn't apply + // Initial value of the field must be null + Asserts.assertNull(U.getFlatValue(c, offset, layoutKind, fc)); + // Writing all zero value to the field, field must become non-null + TestValue1 val0 = new TestValue1((short)0, (short)0); + U.putFlatValue(c, offset, layoutKind, fc, val0); + TestValue1 rval = U.getFlatValue(c, offset, layoutKind, fc); + Asserts.assertNotNull(rval); + Asserts.assertEQ((short)0, rval.s0); + Asserts.assertEQ((short)0, rval.s1); + Asserts.assertEQ((short)0, c.value.s0); + Asserts.assertEQ((short)0, c.value.s1); + // Writing null to the field, field must become null again + U.putFlatValue(c, offset, layoutKind, fc, null); + Asserts.assertNull(U.getFlatValue(c, offset, layoutKind, fc)); + Asserts.assertNull(c.value); + // Writing non zero value to the field + TestValue1 val1 = new TestValue1((short)-1, (short)-2); + U.putFlatValue(c, offset, layoutKind, fc, val1); + rval = U.getFlatValue(c, offset, layoutKind, fc); + Asserts.assertNotNull(rval); + Asserts.assertNotNull(c.value); + Asserts.assertEQ((short)-1, rval.s0); + Asserts.assertEQ((short)-2, rval.s1); + Asserts.assertEQ((short)-1, c.value.s0); + Asserts.assertEQ((short)-2, c.value.s1); + // Writing a different non zero value + TestValue1 val2 = new TestValue1((short)Short.MAX_VALUE, (short)3); + U.putFlatValue(c, offset, layoutKind, fc, val2); + rval = U.getFlatValue(c, offset, layoutKind, fc); + Asserts.assertNotNull(rval); + Asserts.assertNotNull(c.value); + Asserts.assertEQ(Short.MAX_VALUE, c.value.s0); + Asserts.assertEQ((short)3, rval.s1); + Asserts.assertEQ(Short.MAX_VALUE, c.value.s0); + Asserts.assertEQ((short)3, rval.s1); + } + + // Testing of nullable flat arrays supports in Unsafe.getValue()/Unsafe.putValue() + public static void testNullableFlatArrays() throws Throwable { + final int ARRAY_LENGTH = 10; + TestValue1[] array = (TestValue1[])ValueClass.newNullableAtomicArray(TestValue1.class, ARRAY_LENGTH); + Asserts.assertTrue(ValueClass.isFlatArray(array)); + long baseOffset = U.arrayBaseOffset(array); + int scaleIndex = U.arrayIndexScale(array); + for (int i = 0; i < ARRAY_LENGTH; i++) { + Asserts.assertNull(U.getValue(array, baseOffset + i * scaleIndex, TestValue1.class)); + } + TestValue1 val = new TestValue1((short)0, (short)0); + for (int i = 0; i < ARRAY_LENGTH; i++) { + if (i % 2 == 0) { + U.putValue(array, baseOffset + i * scaleIndex, TestValue1.class, val ); + } + } + for (int i = 0; i < ARRAY_LENGTH; i++) { + if (i % 2 == 0) { + Asserts.assertNotNull(U.getValue(array, baseOffset + i * scaleIndex, TestValue1.class)); + Asserts.assertNotNull(array[i]); + } else { + Asserts.assertNull(U.getValue(array, baseOffset + i * scaleIndex, TestValue1.class)); + Asserts.assertNull(array[i]); + } + } + TestValue1 val2 = new TestValue1((short)Short.MAX_VALUE, (short)Short.MIN_VALUE); + for (int i = 0; i < ARRAY_LENGTH; i++) { + if (i % 2 != 0) { + U.putValue(array, baseOffset + i * scaleIndex, TestValue1.class, val2 ); + } else { + U.putValue(array, baseOffset + i * scaleIndex, TestValue1.class, null ); + } + } + for (int i = 0; i < ARRAY_LENGTH; i++) { + if (i % 2 != 0) { + TestValue1 rval = U.getValue(array, baseOffset + i * scaleIndex, TestValue1.class); + Asserts.assertNotNull(rval); + Asserts.assertEQ(val2.s0, rval.s0); + Asserts.assertEQ(val2.s1, rval.s1); + Asserts.assertNotNull(array[i]); + Asserts.assertEQ(val2.s0, array[i].s0); + Asserts.assertEQ(val2.s1, array[i].s1); + } else { + Asserts.assertNull(U.getValue(array, baseOffset + i * scaleIndex, TestValue1.class)); + Asserts.assertNull(array[i]); + } + } + for (int i = 0; i < ARRAY_LENGTH; i++) { + U.putValue(array, baseOffset + i * scaleIndex, TestValue1.class, null ); + } + for (int i = 0; i < ARRAY_LENGTH; i++) { + Asserts.assertNull(U.getValue(array, baseOffset + i * scaleIndex, TestValue1.class)); + Asserts.assertNull(array[i]); + } + } + + // Testing of nullable flat arrays supports in Unsafe.getFlatValue()/Unsafe.putFlatValue() + public static void testNullableFlatArrays2() throws Throwable { + final int ARRAY_LENGTH = 10; + TestValue1[] array = (TestValue1[])ValueClass.newNullableAtomicArray(TestValue1.class, ARRAY_LENGTH); + long baseOffset = U.arrayBaseOffset(array); + int scaleIndex = U.arrayIndexScale(array); + int layoutKind = U.arrayLayout(array); + for (int i = 0; i < ARRAY_LENGTH; i++) { + Asserts.assertNull(U.getFlatValue(array, baseOffset + i * scaleIndex, layoutKind, TestValue1.class)); + } + TestValue1 val = new TestValue1((short)0, (short)0); + for (int i = 0; i < ARRAY_LENGTH; i++) { + if (i % 2 == 0) { + U.putFlatValue(array, baseOffset + i * scaleIndex, layoutKind, TestValue1.class, val ); + } + } + for (int i = 0; i < ARRAY_LENGTH; i++) { + if (i % 2 == 0) { + Asserts.assertNotNull(U.getFlatValue(array, baseOffset + i * scaleIndex, layoutKind, TestValue1.class)); + Asserts.assertNotNull(array[i]); + } else { + Asserts.assertNull(U.getFlatValue(array, baseOffset + i * scaleIndex, layoutKind, TestValue1.class)); + Asserts.assertNull(array[i]); + } + } + TestValue1 val2 = new TestValue1((short)Short.MAX_VALUE, (short)Short.MIN_VALUE); + for (int i = 0; i < ARRAY_LENGTH; i++) { + if (i % 2 != 0) { + U.putFlatValue(array, baseOffset + i * scaleIndex, layoutKind, TestValue1.class, val2 ); + } else { + U.putFlatValue(array, baseOffset + i * scaleIndex, layoutKind, TestValue1.class, null ); + } + } + for (int i = 0; i < ARRAY_LENGTH; i++) { + if (i % 2 != 0) { + TestValue1 rval = U.getFlatValue(array, baseOffset + i * scaleIndex, layoutKind, TestValue1.class); + Asserts.assertNotNull(rval); + Asserts.assertEQ(val2.s0, rval.s0); + Asserts.assertEQ(val2.s1, rval.s1); + Asserts.assertNotNull(array[i]); + Asserts.assertEQ(val2.s0, array[i].s0); + Asserts.assertEQ(val2.s1, array[i].s1); + } else { + Asserts.assertNull(U.getFlatValue(array, baseOffset + i * scaleIndex, layoutKind, TestValue1.class)); + Asserts.assertNull(array[i]); + } + } + for (int i = 0; i < ARRAY_LENGTH; i++) { + U.putFlatValue(array, baseOffset + i * scaleIndex, layoutKind, TestValue1.class, null ); + } + for (int i = 0; i < ARRAY_LENGTH; i++) { + Asserts.assertNull(U.getFlatValue(array, baseOffset + i * scaleIndex, layoutKind, TestValue1.class)); + Asserts.assertNull(array[i]); + } + } + + public static void main(String[] args) throws Throwable { + test0(); + test1(); + testNullableFlatFields(); + testNullableFlatFields2(); + testNullableFlatArrays(); + testNullableFlatArrays2(); + } + +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ValueCopyingTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ValueCopyingTest.java new file mode 100644 index 00000000000..1ed095bea42 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ValueCopyingTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /** + * @test ValueCopyingTest + * @summary Verify that interpreter doesn't tear up primitive fields when copying a value + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile ValueCopyingTest.java + * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlineLayout runtime.valhalla.inlinetypes.ValueCopyingTest + */ + +package runtime.valhalla.inlinetypes; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import jdk.test.lib.Asserts; + +public class ValueCopyingTest { + + static final int NUM_WORKERS = 16; + + static ValueCopyingTest target = new ValueCopyingTest(); + + @LooselyConsistentValue + static value class TestValue { + int i; + byte b; + TestValue(int i0) { + i = i0; + b = 0; + } + } + + @Strict + @NullRestricted + TestValue tv = new TestValue(0); + + static class Worker implements Runnable { + int i; + TestValue v; + Worker(byte b) { + i = b | (b << 8) | (b << 16) | (b << 24); + v = new TestValue(i); + } + + static void checkValue(int i) { + byte b = (byte)(i & 0xFF); + Asserts.assertTrue(((i >> 8) & 0xFF) == b, "Tearing detected"); + Asserts.assertTrue(((i >> 16) & 0xFF) == b, "Tearing detected"); + Asserts.assertTrue(((i >> 24) & 0xFF) == b, "Tearing detected"); + } + + public void run() { + for (int n = 0; n < 10000000; n++) { + ValueCopyingTest.target.tv = v; + int ri = ValueCopyingTest.target.tv.i; + checkValue(ri); + } + } + } + + static public void main(String[] args) throws InterruptedException { + Thread[] workers = new Thread[NUM_WORKERS]; + for (int i = 0; i < NUM_WORKERS; i++) { + workers[i] = new Thread(new Worker((byte)i)); + } + for (int i = 0; i < NUM_WORKERS; i++) { + workers[i].start(); + } + try { + for (int i = 0; i < NUM_WORKERS; i++) { + workers[i].join(); + } + } catch(InterruptedException e) { + e.printStackTrace(); + throw e; + } + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ValuePreloadClient0.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ValuePreloadClient0.java new file mode 100644 index 00000000000..44925f76db0 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ValuePreloadClient0.java @@ -0,0 +1,30 @@ +/* + * 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 + * 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. + */ + +public class ValuePreloadClient0 { + PreloadValue0 value; + + public static void main(String[] args) { + System.out.print("Success"); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ValuePreloadClient1.jcod b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ValuePreloadClient1.jcod new file mode 100644 index 00000000000..848554a598e --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ValuePreloadClient1.jcod @@ -0,0 +1,154 @@ +/* + * 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 + * 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. + */ + +// The test class is based in the Java source code below, but the constant +// pool entry #33 (used by the Preload attribute) has been modified to +// contain the name of a non-existing class. +// +// public class ValuePreloadClient1 { +// PreloadValue0 value; +// +// public static void main(String[] args) { +// System.out.print("Success"); +// } +// } + + class ValuePreloadClient1 { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + Field #8 #9; // #7 + class #10; // #8 + NameAndType #11 #12; // #9 + Utf8 "java/lang/System"; // #10 + Utf8 "out"; // #11 + Utf8 "Ljava/io/PrintStream;"; // #12 + String #14; // #13 + Utf8 "Success"; // #14 + Method #16 #17; // #15 + class #18; // #16 + NameAndType #19 #20; // #17 + Utf8 "java/io/PrintStream"; // #18 + Utf8 "print"; // #19 + Utf8 "(Ljava/lang/String;)V"; // #20 + class #22; // #21 + Utf8 "ValuePreloadClient1"; // #22 + Utf8 "value"; // #23 + Utf8 "LPreloadValue0;"; // #24 + Utf8 "Code"; // #25 + Utf8 "LineNumberTable"; // #26 + Utf8 "main"; // #27 + Utf8 "([Ljava/lang/String;)V"; // #28 + Utf8 "SourceFile"; // #29 + Utf8 "ValuePreloadClient1.java"; // #30 + Utf8 "LoadableDescriptors"; // #31 + Utf8 "LPreloadValue1;"; // #32 + } // Constant Pool + + 0x0021; // access + #21;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + { // field + 0x0000; // access + #23; // name_index + #24; // descriptor_index + [] { // Attributes + } // Attributes + } + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#25) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#26) { // LineNumberTable + [] { // line_number_table + 0 1; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0009; // access + #27; // name_index + #28; // descriptor_index + [] { // Attributes + Attr(#25) { // Code + 2; // max_stack + 1; // max_locals + Bytes[]{ + 0xB20007120DB6000F; + 0xB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#26) { // LineNumberTable + [] { // line_number_table + 0 5; + 8 6; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#29) { // SourceFile + #30; + } // end SourceFile + ; + Attr(#31) { // LoadableDescriptors + 0x00010020; + } // end LoadableDescriptors + } // Attributes +} // end class ValuePreloadClient1 diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ValuePreloadTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ValuePreloadTest.java new file mode 100644 index 00000000000..acf88de230c --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ValuePreloadTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test ValuePreloadTest + * @library /test/lib + * @requires vm.flagless + * @enablePreview + * @compile ValuePreloadClient0.java PreloadValue0.java ValuePreloadClient1.jcod PreloadShared.java + * @run main ValuePreloadTest + */ + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class ValuePreloadTest { + + static ProcessBuilder exec(String... args) throws Exception { + List argsList = new ArrayList<>(); + Collections.addAll(argsList, "--enable-preview"); + Collections.addAll(argsList, "-Dtest.class.path=" + System.getProperty("test.class.path", ".")); + Collections.addAll(argsList, args); + return ProcessTools.createTestJavaProcessBuilder(argsList); + } + + static void checkFor(ProcessBuilder pb, String expected) throws Exception { + OutputAnalyzer out = new OutputAnalyzer(pb.start()); + out.shouldHaveExitValue(0); + out.shouldContain(expected); + } + + public static void main(String[] args) throws Exception { + ProcessBuilder pb = exec("-Xlog:class+preload","ValuePreloadClient0"); + checkFor(pb, "[info][class,preload] Preloading of class PreloadValue0 during loading of class ValuePreloadClient0. Cause: field type in LoadableDescriptors attribute"); + + pb = exec("-Xlog:class+preload","ValuePreloadClient1"); + checkFor(pb, "[warning][class,preload] Preloading of class PreloadValue1 during linking of class ValuePreloadClient1 (cause: LoadableDescriptors attribute) failed"); + + pb = exec("-Xlog:class+preload", "PreloadShared"); + // Class java/time/LocalDate is already preloaded, so the LoadableDescriptor attribute does not trigger it + // to be loaded from the shared archive. + // checkFor(pb, "[info][class,preload] Preloading of class java/time/LocalDate during loading of shared class java/time/LocalDateTime (cause: field type in LoadableDescriptors attribute) succeeded"); + checkFor(pb, "[info][class,preload] Preloading of class java/time/LocalDateTime during linking of class PreloadShared (cause: LoadableDescriptors attribute) succeeded"); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ValueTearing.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ValueTearing.java new file mode 100644 index 00000000000..8fe472291ff --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/ValueTearing.java @@ -0,0 +1,295 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package runtime.valhalla.inlinetypes; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +import javax.swing.LayoutFocusTraversalPolicy; + +import java.util.Optional; + +import jdk.internal.misc.Unsafe; +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import jdk.test.whitebox.WhiteBox; +import static jdk.test.lib.Asserts.*; + +/* + * @test ValueTearing + * @ignore + * @summary Test tearing of inline fields and array elements + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @requires vm.flagless + * @modules java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile ValueTearing.java + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:ForceNonTearable= + * -DSTEP_COUNT=10000 -XX:+UseFieldFlattening -XX:+UseArrayFlattening + * -Xbootclasspath/a:. -XX:+WhiteBoxAPI + * runtime.valhalla.inlinetypes.ValueTearing + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:ForceNonTearable=* + * -DSTEP_COUNT=10000 -XX:+UseFieldFlattening -XX:+UseArrayFlattening + * -Xbootclasspath/a:. -XX:+WhiteBoxAPI + * runtime.valhalla.inlinetypes.ValueTearing + * @run main/othervm -DSTEP_COUNT=10000000 -XX:+UseFieldFlattening -XX:+UseArrayFlattening + * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * runtime.valhalla.inlinetypes.ValueTearing + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:ForceNonTearable= + * -DTEAR_MODE=fieldonly -XX:+UseFieldFlattening -XX:+UseArrayFlattening + * -Xbootclasspath/a:. -XX:+WhiteBoxAPI + * runtime.valhalla.inlinetypes.ValueTearing + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:ForceNonTearable= + * -DTEAR_MODE=arrayonly -XX:+UseFieldFlattening -XX:+UseArrayFlattening + * -Xbootclasspath/a:. -XX:+WhiteBoxAPI + * runtime.valhalla.inlinetypes.ValueTearing + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:ForceNonTearable=* + * -DTEAR_MODE=both -XX:+UseFieldFlattening -XX:+UseArrayFlattening + * -Xbootclasspath/a:. -XX:+WhiteBoxAPI + * runtime.valhalla.inlinetypes.ValueTearing + */ +public class ValueTearing { + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + private static final boolean USE_COMPILER = WHITE_BOX.getBooleanVMFlag("UseCompiler"); + private static final boolean ALWAYS_ATOMIC = WHITE_BOX.getStringVMFlag("ForceNonTearable").contains("*"); + private static final String TEAR_MODE = System.getProperty("TEAR_MODE", "both"); + private static final boolean TEAR_FIELD = !TEAR_MODE.equals("arrayonly"); + private static final boolean TEAR_ARRAY = !TEAR_MODE.equals("fieldonly"); + private static final int STEP_COUNT = Integer.getInteger("STEP_COUNT", 100_000); + private static final boolean TFIELD_FLAT, TARRAY_FLAT; + private static final boolean NTFIELD_FLAT, NTARRAY_FLAT; + static { + try { + Field TPB_field = TPointBox.class.getDeclaredField("field"); + Field TPB_array = TPointBox.class.getDeclaredField("array"); + Field NTPB_field = NTPointBox.class.getDeclaredField("field"); + Field NTPB_array = NTPointBox.class.getDeclaredField("array"); + TFIELD_FLAT = UNSAFE.isFlatField(TPB_field); + TARRAY_FLAT = UNSAFE.isFlatArray(TPB_array.getType()); + NTFIELD_FLAT = UNSAFE.isFlatField(NTPB_field); + NTARRAY_FLAT = UNSAFE.isFlatArray(NTPB_array.getType()); + } catch (ReflectiveOperationException ex) { + throw new AssertionError(ex); + } + } + private static final String SETTINGS = + String.format("USE_COMPILER=%s ALWAYS_ATOMIC=%s TEAR_MODE=%s STEP_COUNT=%s FLAT TF/TA=%s/%s NTF/NTA=%s/%s", + USE_COMPILER, ALWAYS_ATOMIC, TEAR_MODE, STEP_COUNT, + TFIELD_FLAT, TARRAY_FLAT, NTFIELD_FLAT, NTARRAY_FLAT); + private static final String NOTE_TORN_POINT = "Note: torn point"; + + public static void main(String[] args) throws Exception { + System.out.println(SETTINGS); + ValueTearing valueTearing = new ValueTearing(); + valueTearing.run(); + // Extra representation check: + assert(!NTFIELD_FLAT) : "NT field must be indirect not flat"; + assert(!NTARRAY_FLAT) : "NT array must be indirect not flat"; + if (ALWAYS_ATOMIC) { + assert(!TFIELD_FLAT) : "field must be indirect not flat"; + assert(!TARRAY_FLAT) : "array must be indirect not flat"; + } + } + + // A tearable value. + @LooselyConsistentValue + static value class TPoint { + TPoint(long x, long y) { this.x = x; this.y = y; } + final long x, y; + public String toString() { return String.format("(%d,%d)", x, y); } + } + + static class TooTearable extends AssertionError { + final Object badPoint; + TooTearable(String msg, Object badPoint) { + super(msg); + this.badPoint = badPoint; + } + } + + interface PointBox { + void step(); // mutate inline value state + void check(); // check sanity of inline value state + } + + class TPointBox implements PointBox { + @Strict + @NullRestricted + TPoint field = new TPoint(0, 0); + TPoint[] array = (TPoint[])ValueClass.newNullRestrictedNonAtomicArray(TPoint.class, 1, new TPoint(0, 0)); + // Step the points forward by incrementing their components + // "simultaneously". A racing thread will catch flaws in the + // simultaneity. + TPoint step(TPoint p) { + return new TPoint(p.x + 1, p.y + 1); + } + public @Override + void step() { + if (TEAR_FIELD) { + field = step(field); + } + if (TEAR_ARRAY) { + array[0] = step(array[0]); + } + check(); + } + // Invariant: The components of each point are "always" equal. + // As long as simultaneity is preserved, this is true. + public @Override + void check() { + if (TEAR_FIELD) { + check(field, "field"); + } + if (TEAR_ARRAY) { + check(array[0], "array element"); + } + } + void check(TPoint p, String where) { + if (p.x == p.y) return; + String msg = String.format("%s %s in %s; settings = %s", + NOTE_TORN_POINT, + p, where, SETTINGS); + throw new TooTearable(msg, p); + } + public String toString() { + return String.format("TPB[%s, {%s}]", field, array[0]); + } + } + + + // A non-tearable version of TPoint. + static value class NTPoint { + NTPoint(long x, long y) { this.x = x; this.y = y; } + final long x, y; + public String toString() { return String.format("(%d,%d)", x, y); } + } + + class NTPointBox implements PointBox { + @Strict + @NullRestricted + NTPoint field = new NTPoint(0, 0); + NTPoint[] array = (NTPoint[])ValueClass.newNullRestrictedNonAtomicArray(NTPoint.class, 1, new NTPoint(0, 0)); + // Step the points forward by incrementing their components + // "simultaneously". A racing thread will catch flaws in the + // simultaneity. + NTPoint step(NTPoint p) { + return new NTPoint(p.x + 1, p.y + 1); + } + public @Override + void step() { + field = step(field); + array[0] = step(array[0]); + check(); + } + // Invariant: The components of each point are "always" equal. + public @Override + void check() { + check(field, "field"); + check(array[0], "array element"); + } + void check(NTPoint p, String where) { + if (p.x == p.y) return; + String msg = String.format("%s *NonTearable* %s in %s; settings = %s", + NOTE_TORN_POINT, + p, where, SETTINGS); + throw new TooTearable(msg, p); + } + public String toString() { + return String.format("NTPB[%s, {%s}]", field, array[0]); + } + } + + class AsyncObserver extends Thread { + volatile boolean done; + long observationCount; + final PointBox pointBox; + volatile Object badPointObserved; + AsyncObserver(PointBox pointBox) { + this.pointBox = pointBox; + } + public void run() { + try { + while (!done) { + observationCount++; + pointBox.check(); + } + } catch (TooTearable ex) { + done = true; + badPointObserved = ex.badPoint; + System.out.println(ex); + if (ALWAYS_ATOMIC || !badPointObserved.getClass().isAnnotationPresent(LooselyConsistentValue.class)) { + throw ex; + } + } + } + } + + public void run() throws Exception { + System.out.println("Test for tearing of NTPoint, which must not happen..."); + run(new NTPointBox(), false); + System.out.println("Test for tearing of TPoint, which "+ + (ALWAYS_ATOMIC ? "must not" : "is allowed to")+ + " happen..."); + run(new TPointBox(), ALWAYS_ATOMIC ? false : true); + } + public void run(PointBox pointBox, boolean canTear) throws Exception { + var observer = new AsyncObserver(pointBox); + observer.start(); + for (int i = 0; i < STEP_COUNT; i++) { + pointBox.step(); + if (observer.done) break; + } + observer.done = true; + observer.join(); + var obCount = observer.observationCount; + var badPoint = observer.badPointObserved; + System.out.println(String.format("finished after %d observations at %s; %s", + obCount, pointBox, + (badPoint == null + ? "no tearing observed" + : "bad point = " + badPoint))); + if (canTear && badPoint == null) { + var complain = String.format("%s NOT observed after %d observations", + NOTE_TORN_POINT, obCount); + System.out.println("?????? "+complain); + if (STEP_COUNT >= 3_000_000) { + // If it's a small count, OK, but if it's big the test is broken. + throw new AssertionError(complain + ", but it should have been"); + } + } + if (!canTear && badPoint != null) { + throw new AssertionError("should not reach here; other thread must throw"); + } + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/VarArgsArray.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/VarArgsArray.java new file mode 100644 index 00000000000..d72660d1e73 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/VarArgsArray.java @@ -0,0 +1,216 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +package runtime.valhalla.inlinetypes; + +import java.lang.reflect.*; +import static jdk.test.lib.Asserts.*; + +import jdk.internal.vm.annotation.LooselyConsistentValue; + +/* + * @test VarArgsArray + * @summary Test if JVM API using varargs work with inline type arrays + * @modules java.base/jdk.internal.value + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile VarArgsArray.java + * @run main/othervm runtime.valhalla.inlinetypes.VarArgsArray + */ + +@LooselyConsistentValue +value class IntValue { + int val; + public IntValue() { this(0); } + public IntValue(int v) { val = v; } + public int getInt() { return val; } +} + + +class NewInstanceFromConstructor { + + int value; + static int consCalls = 0; + + public NewInstanceFromConstructor() { + this(0); + } + + public NewInstanceFromConstructor(int v) { + value = v; + consCalls++; + } + + public NewInstanceFromConstructor(IntValue v) { + this(v.getInt()); + } + + public NewInstanceFromConstructor(IntValue v1, + IntValue v2) { + this(v1.getInt() + v2.getInt()); + } + + public NewInstanceFromConstructor(IntValue v1, + String s) { + this(v1); + throw new RuntimeException(s); + } + + public int getValue() { return value; } + + public static int getConsCalls() { return consCalls; } +} + +public class VarArgsArray { + + static final int TOKEN_VALUE = 4711; + + int methodACnt = 0; + int methodBCnt = 0; + int methodCCnt = 0; + + public VarArgsArray() { + } + + public void test() throws Throwable { + // test publicly accessable API in the VM...given an inline type array + testJvmInvokeMethod(); + testJvmNewInstanceFromConstructor(); + } + + public void testJvmInvokeMethod() throws Throwable { + MyInt[] array0 = new MyInt[0]; + MyInt[] array1 = new MyInt[] { new MyInt(TOKEN_VALUE) }; + MyInt[] array2 = new MyInt[] { new MyInt(TOKEN_VALUE), new MyInt(TOKEN_VALUE) }; + + Method methodARef = getClass().getDeclaredMethod("methodA", MyInt.class); + Method methodBRef = getClass().getDeclaredMethod("methodB", MyInt.class, MyInt.class); + Method methodCRef = getClass().getDeclaredMethod("methodC", MyInt.class, String.class); + + // Positive tests... + methodARef.invoke(this, (Object[])array1); + assertWithMsg(methodACnt == 1, "methodA did not invoke"); + + methodARef.invoke(this, array1[0]); + assertWithMsg(methodACnt == 2, "methodA did not invoke"); + + methodBRef.invoke(this, (Object[]) array2); + assertWithMsg(methodBCnt == 1, "methodB did not invoke"); + + methodBRef.invoke(this, array2[0], array2[1]); + assertWithMsg(methodBCnt == 2, "methodB did not invoke"); + + // Negative tests... + int argExCnt = 0; + try { + methodARef.invoke(this, (Object[]) array0); + throw new RuntimeException("Expected fail"); + } catch (IllegalArgumentException argEx) { argExCnt++; } + try { + methodARef.invoke(this, (Object[]) array2); + throw new RuntimeException("Expected fail"); + } catch (IllegalArgumentException argEx) { argExCnt++; } + try { + methodCRef.invoke(this, (Object[]) array2); + throw new RuntimeException("Expected fail"); + } catch (IllegalArgumentException argEx) { argExCnt++; } + assertWithMsg(argExCnt == 3, "Did not see the correct number of exceptions"); + assertWithMsg(methodACnt == 2, "methodA bad invoke count"); + assertWithMsg(methodBCnt == 2, "methodB bad invoke count"); + assertWithMsg(methodCCnt == 0, "methodC bad invoke count"); + } + + public void testJvmNewInstanceFromConstructor() throws Throwable { + // Inner classes use outer in param list, so these won't exercise inline type array + Class tc = NewInstanceFromConstructor.class; + Class pt = IntValue.class; + Constructor consARef = tc.getConstructor(pt); + Constructor consBRef = tc.getConstructor(pt, pt); + Constructor consCRef = tc.getConstructor(pt, String.class); + IntValue[] array0 = new IntValue[0]; + IntValue[] array1 = new IntValue[] { new IntValue(TOKEN_VALUE) }; + IntValue[] array2 = new IntValue[] { new IntValue(TOKEN_VALUE), + new IntValue(TOKEN_VALUE) }; + + // Positive tests... + consARef.newInstance((Object[])array1); + consARef.newInstance(array1[0]); + NewInstanceFromConstructor test = (NewInstanceFromConstructor) + consBRef.newInstance((Object[])array2); + assertWithMsg(test.getValue() == (2 * TOKEN_VALUE), "Param corrrupt"); + consBRef.newInstance(array2[0], array2[1]); + assertWithMsg(NewInstanceFromConstructor.getConsCalls() == 4, "Constructor did not invoke"); + + // Negative tests... + int argExCnt = 0; + try { + consARef.newInstance((Object[])array0); + throw new RuntimeException("Expected fail"); + } catch (IllegalArgumentException argEx) { argExCnt++; } + try { + consARef.newInstance((Object[])array2); + throw new RuntimeException("Expected fail"); + } catch (IllegalArgumentException argEx) { argExCnt++; } + try { + consCRef.newInstance((Object[])array2); + throw new RuntimeException("Expected fail"); + } catch (IllegalArgumentException argEx) { argExCnt++; } + assertWithMsg(argExCnt == 3, "Did not see the correct number of exceptions"); + assertWithMsg(NewInstanceFromConstructor.getConsCalls() == 4, "Constructor should have been invoked"); + } + + public void methodA(MyInt a) { + assertWithMsg(a.value == TOKEN_VALUE, "Bad arg"); + methodACnt++; + } + + public void methodB(MyInt a, MyInt b) { + assertWithMsg(a.value == TOKEN_VALUE, "Bad arg"); + assertWithMsg(b.value == TOKEN_VALUE, "Bad arg"); + methodBCnt++; + } + + public void methodC(MyInt a, String b) { + assertWithMsg(a.value == TOKEN_VALUE, "Bad arg"); + methodCCnt++; + } + + static void assertWithMsg(boolean expr, String msg) throws RuntimeException { + assertTrue(expr, msg); + } + + public static void main(String[] args) throws Throwable { + new VarArgsArray().test(); + } + + @LooselyConsistentValue + value class MyInt { + int value; + public MyInt() { this(0); } + public MyInt(int v) { this.value = v; } + } + + +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/VolatileTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/VolatileTest.java new file mode 100644 index 00000000000..1cf558d6d43 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/VolatileTest.java @@ -0,0 +1,82 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package runtime.valhalla.inlinetypes; + +/* + * @test VolatileTest + * @summary check effect of volatile keyword on flattenable fields + * @modules java.base/jdk.internal.misc + * java.base/jdk.internal.vm.annotation + * @library /test/lib + * @enablePreview + * @compile VolatileTest.java + * @run main/othervm -XX:+UseFieldFlattening runtime.valhalla.inlinetypes.VolatileTest + */ + +import jdk.internal.misc.Unsafe; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.lang.reflect.*; +import java.util.List; +import jdk.test.lib.Asserts; + +public class VolatileTest { + static final Unsafe U = Unsafe.getUnsafe(); + static boolean atomicLayoutEnabled; + + + @LooselyConsistentValue + static value class MyValue { + int i = 0; + int j = 0; + } + + static class MyContainer { + @Strict + @NullRestricted + MyValue mv0 = new MyValue(); + @Strict + @NullRestricted + volatile MyValue mv1 = new MyValue(); + } + + static public void main(String[] args) { + Class c = MyContainer.class; + Field f0 = null; + Field f1 = null; + try { + f0 = c.getDeclaredField("mv0"); + f1 = c.getDeclaredField("mv1"); + } catch(NoSuchFieldException e) { + e.printStackTrace(); + return; + } + Asserts.assertTrue(U.isFlatField(f0), "mv0 should be flattened"); + Asserts.assertFalse(U.isFlatField(f1), "mv1 should not be flattened"); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/WrappersOffsetTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/WrappersOffsetTest.java new file mode 100644 index 00000000000..82897b68d06 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/WrappersOffsetTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +/* + * @test + * @summary Test verification of wrappers classes' field offset with various layout + * @enablePreview + * @run main/othervm -XX:-UseFieldFlattening -XX:-UseArrayFlattening -XX:-UseNullableValueFlattening WrappersOffsetTest + */ + + /* + * @test + * @summary Test verification of wrappers classes' field offset with various layout + * @enablePreview + * @run main/othervm -XX:-UseFieldFlattening -XX:-UseArrayFlattening -XX:+UseNullableValueFlattening WrappersOffsetTest + */ + + /* + * @test + * @summary Test verification of wrappers classes' field offset with various layout + * @enablePreview + * @run main/othervm -XX:-UseFieldFlattening -XX:+UseArrayFlattening -XX:-UseNullableValueFlattening WrappersOffsetTest + */ + + /* + * @test + * @summary Test verification of wrappers classes' field offset with various layout + * @enablePreview + * @run main/othervm -XX:+UseFieldFlattening -XX:-UseArrayFlattening -XX:-UseNullableValueFlattening WrappersOffsetTest + */ + + /* + * @test + * @summary Test verification of wrappers classes' field offset with various layout + * @enablePreview + * @run main/othervm -XX:+UseFieldFlattening -XX:-UseArrayFlattening -XX:+UseNullableValueFlattening WrappersOffsetTest + */ + + /* + * @test + * @summary Test verification of wrappers classes' field offset with various layout + * @enablePreview + * @run main/othervm -XX:-UseFieldFlattening -XX:+UseArrayFlattening -XX:+UseNullableValueFlattening WrappersOffsetTest + */ + + /* + * @test + * @summary Test verification of wrappers classes' field offset with various layout + * @enablePreview + * @run main/othervm -XX:+UseFieldFlattening -XX:+UseArrayFlattening -XX:-UseNullableValueFlattening WrappersOffsetTest + */ + +/* + * @test + * @summary Test verification of wrappers classes' field offset with various layout + * @enablePreview + * @run main/othervm -XX:+UseFieldFlattening -XX:+UseArrayFlattening -XX:+UseNullableValueFlattening WrappersOffsetTest + */ + +public class WrappersOffsetTest { + public static void main(String[] args) { + // The test just ensures that wrappers classes are loaded + // The verification code is inside the JVM (javaClasses.cpp) + // and is executed in VM debug builds + Boolean z = Boolean.valueOf(true); + Byte b = Byte.valueOf((byte)1); + Character c = Character.valueOf('c'); + Short s = Short.valueOf((short)2); + Integer i = Integer.valueOf(3); + Float f = Float.valueOf(0.4f); + Long l = Long.valueOf(5L); + Double d = Double.valueOf(0.6); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classfileparser/IllegalFieldModifiers.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classfileparser/IllegalFieldModifiers.java new file mode 100644 index 00000000000..1b4de16f40e --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classfileparser/IllegalFieldModifiers.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +/* + * @test + * @summary test that illegal field modifiers are detected correctly + * @enablePreview + * @compile fieldModifiersTest.jcod + * @run main/othervm -Xverify:remote IllegalFieldModifiers + */ + + +public class IllegalFieldModifiers { + + public static void runTest(String test_name, String message) throws Exception { + System.out.println("Testing: " + test_name); + boolean gotException = false; + try { + Class newClass = Class.forName(test_name); + } catch (java.lang.ClassFormatError e) { + gotException = true; + if (!e.getMessage().contains(message)) { + throw new RuntimeException( "Wrong ClassFormatError: " + e.getMessage()); + } + } + if (!gotException) { + throw new RuntimeException("Missing ClassFormatError in test " + test_name); + } + } + + public static void main(String[] args) throws Exception { + + // Test that ACC_FINAL with ACC_VOLATILE is illegal. + runTest("FinalAndVolatile", "Illegal field modifiers (fields cannot be final and volatile) in class FinalAndVolatile: 0x850"); + + // Test that ACC_STATIC with ACC_STRICT is illegal. + // runTest("StrictAndStatic", "Illegal field modifiers (field cannot be strict and static) in class StrictAndStatic: 0x808"); + + // Test that ACC_STRICT without ACC_FINAL is illegal. + // runTest("StrictNotFinal", "Illegal field modifiers (strict field must be final) in class StrictNotFinal: 0x800"); + + // Test that a concrete value class cannot have field without ACC_STATIC or ACC_STRICT + runTest("NotStaticNotStrict", "Illegal field modifiers (value class fields must be either non-static final and strict, or static) in class NotStaticNotStrict: 0x10"); + + // Test that an abstract value class cannot have field without ACC_STATIC or ACC_STRICT + runTest("NotStaticNotStrictInAbstract", "Illegal field modifiers (value class fields must be either non-static final and strict, or static) in class NotStaticNotStrictInAbstract: 0x10"); + + } + +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classfileparser/LDTest.jcod b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classfileparser/LDTest.jcod new file mode 100644 index 00000000000..8d65113774d --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classfileparser/LDTest.jcod @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +// This file contains a class file with an invalid entry in its +// LoadableDescriptors attribute. The class file has been generated +// from the source below with editing to the constant pool and +// the LoadableDescriptors attribute. +// +// +// import jdk.internal.vm.annotation.NullRestricted; +// import jdk.internal.vm.annotation.Strict; +// +// +// public class LDTest { +// static value class Point { +// short s0, s1; +// Point(short sa, short sb) { +// s0 = sa; +// s1 = sb; +// } +// } +// +// @Strict +// @NullRestricted +// Point p = new Point((short)0, (short)0); +// +// public static void main(String[] args) { +// LDTest test = new LDTest(); +// } +// } + + +class LDTest { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + class #2; // #1 + Utf8 "LDTest$Point"; // #2 + Method #1 #4; // #3 + NameAndType #5 #6; // #4 + Utf8 ""; // #5 + Utf8 "(SS)V"; // #6 + Field #8 #9; // #7 + class #10; // #8 + NameAndType #11 #12; // #9 + Utf8 "LDTest"; // #10 + Utf8 "p"; // #11 + Utf8 "LLDTest$Point;"; // #12 + Method #14 #15; // #13 + class #16; // #14 + NameAndType #5 #17; // #15 + Utf8 "java/lang/Object"; // #16 + Utf8 "()V"; // #17 + Method #8 #15; // #18 + Utf8 "RuntimeVisibleAnnotations"; // #19 + Utf8 "Ljdk/internal/vm/annotation/NullRestricted;"; // #20 + Utf8 "RuntimeInvisibleAnnotations"; // #21 + Utf8 "Ljdk/internal/vm/annotation/Strict;"; // #22 + Utf8 "Code"; // #23 + Utf8 "LineNumberTable"; // #24 + Utf8 "main"; // #25 + Utf8 "([Ljava/lang/String;)V"; // #26 + Utf8 "SourceFile"; // #27 + Utf8 "LDTest.java"; // #28 + Utf8 "NestMembers"; // #29 + Utf8 "InnerClasses"; // #30 + Utf8 "Point"; // #31 + Utf8 "LoadableDescriptors"; // #32 + Utf8 "[V"; // #33 // <== new invalid descriptor + } // Constant Pool + + 0x0021; // access + #8;// this_cpx + #14;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + { // field + 0x0800; // access + #11; // name_index + #12; // descriptor_index + [] { // Attributes + Attr(#19) { // RuntimeVisibleAnnotations + [] { // annotations + { // annotation + #20; + [] { // element_value_pairs + } // element_value_pairs + } // annotation + } + } // end RuntimeVisibleAnnotations + ; + Attr(#21) { // RuntimeInvisibleAnnotations + [] { // annotations + { // annotation + #22; + [] { // element_value_pairs + } // element_value_pairs + } // annotation + } + } // end RuntimeInvisibleAnnotations + } // Attributes + } + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #5; // name_index + #17; // descriptor_index + [] { // Attributes + Attr(#23) { // Code + 5; // max_stack + 1; // max_locals + Bytes[]{ + 0x2ABB0001590303B7; + 0x0003B500072AB700; + 0x0DB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#24) { // LineNumberTable + [] { // line_number_table + 0 14; + 13 5; + 17 14; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0009; // access + #25; // name_index + #26; // descriptor_index + [] { // Attributes + Attr(#23) { // Code + 2; // max_stack + 2; // max_locals + Bytes[]{ + 0xBB000859B700124C; + 0xB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#24) { // LineNumberTable + [] { // line_number_table + 0 19; + 8 20; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#27) { // SourceFile + #28; + } // end SourceFile + ; + Attr(#29) { // NestMembers + [] { // classes + #1; + } + } // end NestMembers + ; + Attr(#30) { // InnerClasses + [] { // classes + #1 #8 #31 24; + } + } // end InnerClasses + ; + Attr(#32) { // LoadableDescriptors + 0x00010021; // <== modified index to contant pool + } // end LoadableDescriptors + } // Attributes +} // end class LDTest diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classfileparser/TestIllegalLoadableDescriptors.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classfileparser/TestIllegalLoadableDescriptors.java new file mode 100644 index 00000000000..9c0e323b953 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classfileparser/TestIllegalLoadableDescriptors.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +/* + * @test + * @summary test a LoadableDescriptors attribute with an invalid entry + * @enablePreview + * @compile LDTest.jcod TestIllegalLoadableDescriptors.java + * @run main/othervm TestIllegalLoadableDescriptors + */ + + +public class TestIllegalLoadableDescriptors { + + public static void main(String[] args) throws ClassNotFoundException { + boolean gotException = false; + try { + Class newClass = Class.forName("LDTest"); + } catch (java.lang.ClassFormatError e) { + gotException = true; + if (!e.getMessage().contains("Descriptor from LoadableDescriptors attribute at index \"33\" in class LDTest has illegal signature \"[V")) { + throw new RuntimeException( "Wrong ClassFormatError: " + e.getMessage()); + } + } + if (!gotException) { + throw new RuntimeException("Missing ClassFormatError"); + } + } + +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classfileparser/ValueClassValidation.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classfileparser/ValueClassValidation.java new file mode 100644 index 00000000000..9b0a7d52fc2 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classfileparser/ValueClassValidation.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +/* + * @test + * @summary test validation of value classes + * @enablePreview + * @compile cfpValueClassValidation.jcod + * @run main/othervm -Xverify:remote ValueClassValidation + */ + +import javax.management.RuntimeErrorException; + +public class ValueClassValidation { + public static void runTest(String test_name, String cfe_message, String icce_message) throws Exception { + System.out.println("Testing: " + test_name); + boolean gotException = false; + try { + Class newClass = Class.forName(test_name); + } catch (java.lang.ClassFormatError e) { + gotException = true; + if (cfe_message != null) { + if (!e.getMessage().contains(cfe_message)) { + throw new RuntimeException( "Wrong ClassFormatError: " + e.getMessage()); + } + } else { + throw new RuntimeException( "Unexpected ClassFormatError: " + e.getMessage()); + } + } catch (java.lang.IncompatibleClassChangeError e) { + gotException = true; + if (icce_message != null) { + if (!e.getMessage().contains(icce_message)) { + throw new RuntimeException( "Wrong IncompatibleClassChangeError: " + e.getMessage()); + } + } else { + throw new RuntimeException( "Unexpected IncompatibleClassChangeError: " + e.getMessage()); + } + } + if (!gotException) { + if (cfe_message != null) { + throw new RuntimeException("Missing ClassFormatError in test" + test_name); + } else if (icce_message != null) { + throw new RuntimeException("Missing IncompatibleClassChangeError in test" + test_name); + } + } + } + + public static void main(String[] args) throws Exception { + + // Test none of ACC_ABSTRACT, ACC_FINAL or ACC_IDENTITY is illegal. + runTest("InvalidClassFlags", "Illegal class modifiers in class InvalidClassFlags", null); + + // Test ACC_ABSTRACT without ACC_IDENTITY is legal + runTest("AbstractValue", null, null); + + // Test ACC_FINAL without ACC_IDENTITY is legal + runTest("FinalValue", null, null); + + // Test a concrete value class extending an abstract identity class + runTest("ValueClass", null, "Value type ValueClass has an identity type as supertype"); + + // Test a concrete identity class without ACC_IDENTITY but with an older class file version, extending an abstract identity class + // (Test that the VM fixes missing ACC_IDENTITY in old class files) + runTest("IdentityClass", null, null); + + // Test a concrete value class extending a concrete (i.e. final) value class + runTest("ValueClass2", null, "class ValueClass2 cannot inherit from final class FinalValue"); + + // Test an abstract value class extending an abstract identity class + runTest("AbstractValueClass2", null, "Value type AbstractValueClass2 has an identity type as supertype"); + + // Test an abstract identity class without ACC_IDENTITY but with an older class file version, extending an abstract identity class + // (Test that the VM fixes missing ACC_IDENTITY in old class files) + runTest("AbstractIdentityClass2", null, null); + + // Test an abstract value class extending a concrete (i.e. final) value class + runTest("AbstractValueClass3", null, "class AbstractValueClass3 cannot inherit from final class FinalValue"); + + //Test a concrete class without ACC_IDENTITY but with an older class file version, declaring a field without ACC_STATIC nor ACC_STRICT + // (Test that the VM fixes missing ACC_IDENTITY in old class files) + runTest("NotStaticNotStrictInOldClass", null, null); + + // Test a concrete value class with a static synchronized method + runTest("StaticSynchMethod", null, null); + + // Test a concrete value class with a non-static synchronized method + runTest("SynchMethod", "Method m in class SynchMethod (not an identity class) has illegal modifiers: 0x21", null); + + // Test an abstract value class with a static synchronized method + runTest("StaticSynchMethodInAbstractValue", null, null); + + // Test an abstract value class with a non-static synchronized method + runTest("SynchMethodInAbstractValue", "Method m in class SynchMethodInAbstractValue (not an identity class) has illegal modifiers: 0x21", null); + + // Test a class with a primitive descriptor in its LoadableDescriptors attribute: + runTest("PrimitiveInLoadableDescriptors", null, null); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classfileparser/cfpValueClassValidation.jcod b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classfileparser/cfpValueClassValidation.jcod new file mode 100644 index 00000000000..4aa61c0a42e --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classfileparser/cfpValueClassValidation.jcod @@ -0,0 +1,1258 @@ +/* + * 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 + * 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. + * + */ + +// Simple class generated from the following source: +// public class SimpleClass { } +// The jcod version has been altered to create a class without any of ACC_IDENTITY, ACC_ABSTRACT or ACC_FINAL flags, +// which is illegal according to JVMS 4.1. + +class InvalidClassFlags { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + class #8; // #7 + Utf8 "InvalidClassFlags"; // #8 + Utf8 "Code"; // #9 + Utf8 "LineNumberTable"; // #10 + Utf8 "SourceFile"; // #11 + Utf8 "InvalidClassFlags.java"; // #12 + } // Constant Pool + + 0x0001; // access + #7;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#9) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#10) { // LineNumberTable + [] { // line_number_table + 0 1; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#11) { // SourceFile + #12; + } // end SourceFile + } // Attributes +} // end class InvalidClassFlags + +// Valid class with access flags ACC_PUBLIC, ACC_ABSTRACT and no ACC_IDENTITY +class AbstractValue { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + class #8; // #7 + Utf8 "AbstractValue"; // #8 + Utf8 "Code"; // #9 + Utf8 "LineNumberTable"; // #10 + Utf8 "SourceFile"; // #11 + Utf8 "AbstractValue.java"; // #12 + } // Constant Pool + + 0x0401; // access + #7;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#9) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#10) { // LineNumberTable + [] { // line_number_table + 0 1; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#11) { // SourceFile + #12; + } // end SourceFile + } // Attributes +} // end class AbstractValue + +// Valid class with access flags ACC_PUBLIC, ACC_FINAL and no ACC_IDENTITY +class FinalValue { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + class #8; // #7 + Utf8 "FinalValue"; // #8 + Utf8 "Code"; // #9 + Utf8 "LineNumberTable"; // #10 + Utf8 "SourceFile"; // #11 + Utf8 "FinalValue.java"; // #12 + } // Constant Pool + + 0x0011; // access + #7;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#9) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#10) { // LineNumberTable + [] { // line_number_table + 0 1; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#11) { // SourceFile + #12; + } // end SourceFile + } // Attributes +} // end class FinalValue + +// Abstract class with ACC_IDENTITY flag added to its access flags +class AbstractClass { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + class #8; // #7 + Utf8 "AbstractClass"; // #8 + Utf8 "Code"; // #9 + Utf8 "LineNumberTable"; // #10 + Utf8 "SourceFile"; // #11 + Utf8 "AbstractClass.java"; // #12 + } // Constant Pool + + 0x0421; // access + #7;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#9) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#10) { // LineNumberTable + [] { // line_number_table + 0 1; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#11) { // SourceFile + #12; + } // end SourceFile + } // Attributes +} // end class AbstractClass + +// Value class extending the abstract (with identity) class define above +class ValueClass { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "AbstractClass"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + class #8; // #7 + Utf8 "ValueClass"; // #8 + Utf8 "Code"; // #9 + Utf8 "LineNumberTable"; // #10 + Utf8 "SourceFile"; // #11 + Utf8 "ValueClass.java"; // #12 + } // Constant Pool + + 0x0011; // access + #7;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#9) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#10) { // LineNumberTable + [] { // line_number_table + 0 1; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#11) { // SourceFile + #12; + } // end SourceFile + } // Attributes +} // end class ValueClass + +// Concrete identity class from old class file without ACC_IDENTITY extending the abstract (with identity) class define above +class IdentityClass { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "AbstractClass"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + class #8; // #7 + Utf8 "IdentityClass"; // #8 + Utf8 "Code"; // #9 + Utf8 "LineNumberTable"; // #10 + Utf8 "SourceFile"; // #11 + Utf8 "IdentityClass.java"; // #12 + } // Constant Pool + + 0x0011; // access + #7;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#9) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#10) { // LineNumberTable + [] { // line_number_table + 0 1; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#11) { // SourceFile + #12; + } // end SourceFile + } // Attributes +} // end class ValueClass + +// Concrete value class extending a final value class +class ValueClass2 { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "FinalValue"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + class #8; // #7 + Utf8 "ValueClass2"; // #8 + Utf8 "Code"; // #9 + Utf8 "LineNumberTable"; // #10 + Utf8 "SourceFile"; // #11 + Utf8 "ValueClass2.java"; // #12 + } // Constant Pool + + 0x0011; // access + #7;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#9) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#10) { // LineNumberTable + [] { // line_number_table + 0 1; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#11) { // SourceFile + #12; + } // end SourceFile + } // Attributes +} // end class ValueClass2 + +// Abstract value class extending an abstract (with identity) class +class AbstractValueClass2 { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "AbstractClass"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + class #8; // #7 + Utf8 "AbstractValueClass2"; // #8 + Utf8 "Code"; // #9 + Utf8 "LineNumberTable"; // #10 + Utf8 "SourceFile"; // #11 + Utf8 "AbstractValueClass2.java"; // #12 + } // Constant Pool + + 0x0401; // access + #7;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#9) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#10) { // LineNumberTable + [] { // line_number_table + 0 1; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#11) { // SourceFile + #12; + } // end SourceFile + } // Attributes +} // end class AbstractValueClass2 + +// Abstract identity class from older class file without ACC_IDENTITY, extending an abstract (with identity) class +class AbstractIdentityClass2 { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "AbstractClass"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + class #8; // #7 + Utf8 "AbstractIdentityClass2"; // #8 + Utf8 "Code"; // #9 + Utf8 "LineNumberTable"; // #10 + Utf8 "SourceFile"; // #11 + Utf8 "AbstractIdentityClass2.java"; // #12 + } // Constant Pool + + 0x0401; // access + #7;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#9) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#10) { // LineNumberTable + [] { // line_number_table + 0 1; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#11) { // SourceFile + #12; + } // end SourceFile + } // Attributes +} // end class AbstractValueClass2 + +// Abstract value class extending a final value class +class AbstractValueClass3 { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "FinalValue"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + class #8; // #7 + Utf8 "AbstractValueClass3"; // #8 + Utf8 "Code"; // #9 + Utf8 "LineNumberTable"; // #10 + Utf8 "SourceFile"; // #11 + Utf8 "AbstractValueClass3.java"; // #12 + } // Constant Pool + + 0x0401; // access + #7;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#9) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#10) { // LineNumberTable + [] { // line_number_table + 0 1; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#11) { // SourceFile + #12; + } // end SourceFile + } // Attributes +} // end class AbstractValueClass3 + +// A concrete class without ACC_IDENTITY but with an older class file version, declaring a field without ACC_STATIC nor ACC_STRICT +// +class NotStaticNotStrictInOldClass { + 0xCAFEBABE; + 0; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Field #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "NotStaticNotStrictInOldClass"; // #4 + Utf8 "i"; // #5 + Utf8 "I"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #12; // #9 + Utf8 "java/lang/Object"; // #10 + Utf8 ""; // #11 + Utf8 "()V"; // #12 + Field #2 #14; // #13 + NameAndType #15 #6; // #14 + Utf8 "si"; // #15 + Utf8 "ConstantValue"; // #16 + int 0x00000000; // #17 + Utf8 "Code"; // #18 + Utf8 "LineNumberTable"; // #19 + Utf8 ""; // #20 + Utf8 "SourceFile"; // #21 + Utf8 "NotStaticNotStrictInOldClass.java"; // #22 + } // Constant Pool + + 0x0011; // access + #2;// this_cpx + #8;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + { // field + 0x0008; // access + #15; // name_index + #6; // descriptor_index + [] { // Attributes + } // Attributes + } + ; + { // field + 0x0010; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#16) { // ConstantValue + #17; + } // end ConstantValue + } // Attributes + } + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #11; // name_index + #12; // descriptor_index + [] { // Attributes + Attr(#18) { // Code + 2; // max_stack + 1; // max_locals + Bytes[]{ + 0x2A03B500012AB700; + 0x07B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#19) { // LineNumberTable + [] { // line_number_table + 0 3; + 5 1; + 9 3; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0008; // access + #20; // name_index + #12; // descriptor_index + [] { // Attributes + Attr(#18) { // Code + 1; // max_stack + 0; // max_locals + Bytes[]{ + 0x03B3000DB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#19) { // LineNumberTable + [] { // line_number_table + 0 2; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#21) { // SourceFile + #22; + } // end SourceFile + } // Attributes +} // end class NotStaticNotStrictInOldClass + +// Valid concrete value class with a synchronized static method +class StaticSynchMethod { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + class #8; // #7 + Utf8 "StaticSynchMethod"; // #8 + Utf8 "Code"; // #9 + Utf8 "LineNumberTable"; // #10 + Utf8 "m"; // #11 + Utf8 "()I"; // #12 + Utf8 "SourceFile"; // #13 + Utf8 "StaticSynchMethod.java"; // #14 + } // Constant Pool + + 0x0011; // access + #7;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#9) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#10) { // LineNumberTable + [] { // line_number_table + 0 1; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0029; // access + #11; // name_index + #12; // descriptor_index + [] { // Attributes + Attr(#9) { // Code + 1; // max_stack + 0; // max_locals + Bytes[]{ + 0x102AAC; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#10) { // LineNumberTable + [] { // line_number_table + 0 2; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#13) { // SourceFile + #14; + } // end SourceFile + } // Attributes +} // end class StaticSynchMethod + +// Invalid concrete value class with synchronized non-static method +class SynchMethod { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + class #8; // #7 + Utf8 "SynchMethod"; // #8 + Utf8 "Code"; // #9 + Utf8 "LineNumberTable"; // #10 + Utf8 "m"; // #11 + Utf8 "()I"; // #12 + Utf8 "SourceFile"; // #13 + Utf8 "SynchMethod.java"; // #14 + } // Constant Pool + + 0x0011; // access + #7;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#9) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#10) { // LineNumberTable + [] { // line_number_table + 0 1; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0021; // access // Modified to make it synchronized + #11; // name_index + #12; // descriptor_index + [] { // Attributes + Attr(#9) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x102AAC; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#10) { // LineNumberTable + [] { // line_number_table + 0 2; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#13) { // SourceFile + #14; + } // end SourceFile + } // Attributes +} // end class SynchMethod + +// Valid abstract value class with synchronized static method +class StaticSynchMethodInAbstractValue { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + class #8; // #7 + Utf8 "StaticSynchMethodInAbstractValue"; // #8 + Utf8 "Code"; // #9 + Utf8 "LineNumberTable"; // #10 + Utf8 "m"; // #11 + Utf8 "()I"; // #12 + Utf8 "SourceFile"; // #13 + Utf8 "StaticSynchMethodInAbstractValue.java"; // #14 + } // Constant Pool + + 0x0401; // access + #7;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#9) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#10) { // LineNumberTable + [] { // line_number_table + 0 1; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0029; // access + #11; // name_index + #12; // descriptor_index + [] { // Attributes + Attr(#9) { // Code + 1; // max_stack + 0; // max_locals + Bytes[]{ + 0x102AAC; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#10) { // LineNumberTable + [] { // line_number_table + 0 2; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#13) { // SourceFile + #14; + } // end SourceFile + } // Attributes +} // end class StaticSynchMethodInAbstractValue + +// Invalid abstract value class with a synchronized non-static method +class SynchMethodInAbstractValue { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + class #8; // #7 + Utf8 "SynchMethodInAbstractValue"; // #8 + Utf8 "Code"; // #9 + Utf8 "LineNumberTable"; // #10 + Utf8 "m"; // #11 + Utf8 "()I"; // #12 + Utf8 "SourceFile"; // #13 + Utf8 "SynchMethodInAbstractValue.java"; // #14 + } // Constant Pool + + 0x0401; // access + #7;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#9) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#10) { // LineNumberTable + [] { // line_number_table + 0 1; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0021; // access // modified to make it synchronized + #11; // name_index + #12; // descriptor_index + [] { // Attributes + Attr(#9) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x102AAC; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#10) { // LineNumberTable + [] { // line_number_table + 0 2; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#13) { // SourceFile + #14; + } // end SourceFile + } // Attributes +} // end class SynchMethodInAbstractValue + +// Class to test invalid entries in LoadableDescriptors Attributes +// Class generated from this source: +// public class PrimitiveInLoadableDescriptors { +// int i; +// void foo(Integer i) { } +// } + +class PrimitiveInLoadableDescriptors { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Method #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "java/lang/Object"; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + class #8; // #7 + Utf8 "PrimitiveInLoadableDescriptors"; // #8 + Utf8 "i"; // #9 + Utf8 "I"; // #10 + Utf8 "Code"; // #11 + Utf8 "LineNumberTable"; // #12 + Utf8 "foo"; // #13 + Utf8 "(Ljava/lang/Integer;)V"; // #14 + Utf8 "SourceFile"; // #15 + Utf8 "PrimitiveInLoadableDescriptors.java"; // #16 + Utf8 "LoadableDescriptors"; // #17 + Utf8 "I"; // #18 // Modified from "Ljava/lang/Integer;" to "I" + } // Constant Pool + + 0x0021; // access + #7;// this_cpx + #2;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + { // field + 0x0000; // access + #9; // name_index + #10; // descriptor_index + [] { // Attributes + } // Attributes + } + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#11) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70001B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#12) { // LineNumberTable + [] { // line_number_table + 0 1; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0000; // access + #13; // name_index + #14; // descriptor_index + [] { // Attributes + Attr(#11) { // Code + 0; // max_stack + 2; // max_locals + Bytes[]{ + 0xB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#12) { // LineNumberTable + [] { // line_number_table + 0 3; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#15) { // SourceFile + #16; + } // end SourceFile + ; + Attr(#17) { // LoadableDescriptors + 0x00010012; + } // end LoadableDescriptors + } // Attributes +} // end class PrimitiveInLoadableDescriptors diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classfileparser/fieldModifiersTest.jcod b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classfileparser/fieldModifiersTest.jcod new file mode 100644 index 00000000000..db93624d0ef --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classfileparser/fieldModifiersTest.jcod @@ -0,0 +1,637 @@ +/* + * 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. + * + */ + + // This file contains multiple illegal inline type classes that should cause +// ClassFormatError exceptions when attempted to be loaded. +// +// Many of these test were originally generated from this Java file and then +// field modifiers are changed to cause a ClassFormatError exceptions. +// +// public value class Value { +// static int si = 0; +// int i = 0; +//} + + +// A field has both ACC_FINAL and ACC_VOLATILE set +// +class FinalAndVolatile { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Field #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "FinalAndVolatile"; // #4 + Utf8 "i"; // #5 + Utf8 "I"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #12; // #9 + Utf8 "java/lang/Object"; // #10 + Utf8 ""; // #11 + Utf8 "()V"; // #12 + Field #2 #14; // #13 + NameAndType #15 #6; // #14 + Utf8 "si"; // #15 + Utf8 "ConstantValue"; // #16 + int 0x00000000; // #17 + Utf8 "Code"; // #18 + Utf8 "LineNumberTable"; // #19 + Utf8 ""; // #20 + Utf8 "SourceFile"; // #21 + Utf8 "FinalAndVolatile.java"; // #22 + } // Constant Pool + + 0x0011; // access + #2;// this_cpx + #8;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + { // field + 0x0008; // access + #15; // name_index + #6; // descriptor_index + [] { // Attributes + } // Attributes + } + ; + { // field + 0x0850; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#16) { // ConstantValue + #17; + } // end ConstantValue + } // Attributes + } + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #11; // name_index + #12; // descriptor_index + [] { // Attributes + Attr(#18) { // Code + 2; // max_stack + 1; // max_locals + Bytes[]{ + 0x2A03B500012AB700; + 0x07B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#19) { // LineNumberTable + [] { // line_number_table + 0 3; + 5 1; + 9 3; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0008; // access + #20; // name_index + #12; // descriptor_index + [] { // Attributes + Attr(#18) { // Code + 1; // max_stack + 0; // max_locals + Bytes[]{ + 0x03B3000DB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#19) { // LineNumberTable + [] { // line_number_table + 0 2; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#21) { // SourceFile + #22; + } // end SourceFile + } // Attributes +} // end class FinalAndVolatile + + +// A field has both ACC_STRICT and ACC_STATIC set +// +class StrictAndStatic { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Field #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "StrictAndStatic"; // #4 + Utf8 "i"; // #5 + Utf8 "I"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #12; // #9 + Utf8 "java/lang/Object"; // #10 + Utf8 ""; // #11 + Utf8 "()V"; // #12 + Field #2 #14; // #13 + NameAndType #15 #6; // #14 + Utf8 "si"; // #15 + Utf8 "ConstantValue"; // #16 + int 0x00000000; // #17 + Utf8 "Code"; // #18 + Utf8 "LineNumberTable"; // #19 + Utf8 ""; // #20 + Utf8 "SourceFile"; // #21 + Utf8 "StrictAndStatic.java"; // #22 + } // Constant Pool + + 0x0011; // access + #2;// this_cpx + #8;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + { // field + 0x0808; // access + #15; // name_index + #6; // descriptor_index + [] { // Attributes + } // Attributes + } + ; + { // field + 0x0810; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#16) { // ConstantValue + #17; + } // end ConstantValue + } // Attributes + } + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #11; // name_index + #12; // descriptor_index + [] { // Attributes + Attr(#18) { // Code + 2; // max_stack + 1; // max_locals + Bytes[]{ + 0x2A03B500012AB700; + 0x07B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#19) { // LineNumberTable + [] { // line_number_table + 0 3; + 5 1; + 9 3; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0008; // access + #20; // name_index + #12; // descriptor_index + [] { // Attributes + Attr(#18) { // Code + 1; // max_stack + 0; // max_locals + Bytes[]{ + 0x03B3000DB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#19) { // LineNumberTable + [] { // line_number_table + 0 2; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#21) { // SourceFile + #22; + } // end SourceFile + } // Attributes +} // end class StrictAndStatic + + +// A field has ACC_STRICT set without ACC_FINAL being set +// +class StrictNotFinal { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Field #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "StrictNotFinal"; // #4 + Utf8 "i"; // #5 + Utf8 "I"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #12; // #9 + Utf8 "java/lang/Object"; // #10 + Utf8 ""; // #11 + Utf8 "()V"; // #12 + Field #2 #14; // #13 + NameAndType #15 #6; // #14 + Utf8 "si"; // #15 + Utf8 "ConstantValue"; // #16 + int 0x00000000; // #17 + Utf8 "Code"; // #18 + Utf8 "LineNumberTable"; // #19 + Utf8 ""; // #20 + Utf8 "SourceFile"; // #21 + Utf8 "StrictNotFinal.java"; // #22 + } // Constant Pool + + 0x0011; // access + #2;// this_cpx + #8;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + { // field + 0x0008; // access + #15; // name_index + #6; // descriptor_index + [] { // Attributes + } // Attributes + } + ; + { // field + 0x0800; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#16) { // ConstantValue + #17; + } // end ConstantValue + } // Attributes + } + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #11; // name_index + #12; // descriptor_index + [] { // Attributes + Attr(#18) { // Code + 2; // max_stack + 1; // max_locals + Bytes[]{ + 0x2A03B500012AB700; + 0x07B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#19) { // LineNumberTable + [] { // line_number_table + 0 3; + 5 1; + 9 3; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0008; // access + #20; // name_index + #12; // descriptor_index + [] { // Attributes + Attr(#18) { // Code + 1; // max_stack + 0; // max_locals + Bytes[]{ + 0x03B3000DB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#19) { // LineNumberTable + [] { // line_number_table + 0 2; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#21) { // SourceFile + #22; + } // end SourceFile + } // Attributes +} // end class StrictNotFinal + +// A concrete value class declaring a field without ACC_STATIC nor ACC_STRICT +// +class NotStaticNotStrict { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Field #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "NotStaticNotStrict"; // #4 + Utf8 "i"; // #5 + Utf8 "I"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #12; // #9 + Utf8 "java/lang/Object"; // #10 + Utf8 ""; // #11 + Utf8 "()V"; // #12 + Field #2 #14; // #13 + NameAndType #15 #6; // #14 + Utf8 "si"; // #15 + Utf8 "ConstantValue"; // #16 + int 0x00000000; // #17 + Utf8 "Code"; // #18 + Utf8 "LineNumberTable"; // #19 + Utf8 ""; // #20 + Utf8 "SourceFile"; // #21 + Utf8 "NotStaticNotStrict.java"; // #22 + } // Constant Pool + + 0x0011; // access + #2;// this_cpx + #8;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + { // field + 0x0008; // access + #15; // name_index + #6; // descriptor_index + [] { // Attributes + } // Attributes + } + ; + { // field + 0x0010; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#16) { // ConstantValue + #17; + } // end ConstantValue + } // Attributes + } + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #11; // name_index + #12; // descriptor_index + [] { // Attributes + Attr(#18) { // Code + 2; // max_stack + 1; // max_locals + Bytes[]{ + 0x2A03B500012AB700; + 0x07B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#19) { // LineNumberTable + [] { // line_number_table + 0 3; + 5 1; + 9 3; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0008; // access + #20; // name_index + #12; // descriptor_index + [] { // Attributes + Attr(#18) { // Code + 1; // max_stack + 0; // max_locals + Bytes[]{ + 0x03B3000DB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#19) { // LineNumberTable + [] { // line_number_table + 0 2; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#21) { // SourceFile + #22; + } // end SourceFile + } // Attributes +} // end class NotStaticNotStrict + +// An abstract value class declaring a field without ACC_STATIC nor ACC_STRICT +// +class NotStaticNotStrictInAbstract { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Field #2 #3; // #1 + class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "NotStaticNotStrictInAbstract"; // #4 + Utf8 "i"; // #5 + Utf8 "I"; // #6 + Method #8 #9; // #7 + class #10; // #8 + NameAndType #11 #12; // #9 + Utf8 "java/lang/Object"; // #10 + Utf8 ""; // #11 + Utf8 "()V"; // #12 + Field #2 #14; // #13 + NameAndType #15 #6; // #14 + Utf8 "si"; // #15 + Utf8 "ConstantValue"; // #16 + int 0x00000000; // #17 + Utf8 "Code"; // #18 + Utf8 "LineNumberTable"; // #19 + Utf8 ""; // #20 + Utf8 "SourceFile"; // #21 + Utf8 "NotStaticNotStrictInAbstract.java"; // #22 + } // Constant Pool + + 0x0401; // access + #2;// this_cpx + #8;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + { // field + 0x0008; // access + #15; // name_index + #6; // descriptor_index + [] { // Attributes + } // Attributes + } + ; + { // field + 0x0010; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#16) { // ConstantValue + #17; + } // end ConstantValue + } // Attributes + } + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #11; // name_index + #12; // descriptor_index + [] { // Attributes + Attr(#18) { // Code + 2; // max_stack + 1; // max_locals + Bytes[]{ + 0x2A03B500012AB700; + 0x07B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#19) { // LineNumberTable + [] { // line_number_table + 0 3; + 5 1; + 9 3; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method + 0x0008; // access + #20; // name_index + #12; // descriptor_index + [] { // Attributes + Attr(#18) { // Code + 1; // max_stack + 0; // max_locals + Bytes[]{ + 0x03B3000DB1; + } + [] { // Traps + } // end Traps + [] { // Attributes + Attr(#19) { // LineNumberTable + [] { // line_number_table + 0 2; + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + Attr(#21) { // SourceFile + #22; + } // end SourceFile + } // Attributes +} // end class NotStaticNotStrictInAbstract diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/BigClassTreeClassLoader.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/BigClassTreeClassLoader.java new file mode 100644 index 00000000000..d557e6fd170 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/BigClassTreeClassLoader.java @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + + /* + * @test + * @summary Sanity test for BigClassTreeClassLoader + * @enablePreview + * @run main BigClassTreeClassLoader + */ + +import java.lang.classfile.*; +import java.lang.classfile.attribute.*; +import java.lang.classfile.constantpool.*; +import java.lang.constant.*; +import java.util.Arrays; +import java.util.Map; +import java.util.HashMap; +import java.util.Optional; +import java.util.function.Consumer; + +import static java.lang.classfile.ClassFile.*; +import static java.lang.constant.ConstantDescs.*; + +// A classloader that will generate a big value class inheritance tree (depth, +// and possibly breadth) of classes on the fly. For example, with a +// maximum depth limit of 3, one can load "Gen3" via this classloader, +// which will generate the following: +// +// public value class Gen2 --> Gen1 --> Gen0 --> java.lang.Object +// +// Optionally, a long field chain can also be generated in one of the Gen classes. +// This creates a chain of Field value classes which have other Field objects as +// fields up to the maximum depth. Class depth = field width. Only the Gen's field +// is static, the rest are not. For example, with a maximum depth limit of 3 and +// field at 1, this classloader will generate the following: +// +// public value class Gen2 --> Gen1 --> Gen0 --> java.lang.Object +// | public static Field2 theField; +// public value class Field2 +// | theField +// Field1 +// | theField +// Field0 +// +// Field0 will have a field theField of java.lang.Object. It is possible to change +// both the field class as well as the superclass of Field0 to introduce interesting +// class circularity. +// +// This classloader is parallel capable. It uses the built in classloading lock via +// loadClass to ensure that it defines a given GenX or FieldX only once. +public class BigClassTreeClassLoader extends ClassLoader { + + // Sanity test, this should never fail. + public static void main(String[] args) throws ClassNotFoundException { + var fields = new FieldGeneration(1, Optional.empty(), Optional.empty()); + Class.forName("Gen2", false, new BigClassTreeClassLoader(3, fields)); + } + + // A field generation strategy that disables field generation. + public static FieldGeneration NO_FIELD_GEN = new FieldGeneration(-1, Optional.empty(), Optional.empty()); + + // A sane depth/width limit. + private static final int SANE_LIMIT = 100; + + // We want to perform different things depending on what kind of class we are + // generating. Therefore, we utilize a strategy pattern. + private final Strategy[] availableStrategies; + + // A store of all the classes already defined. Existing classes must be reused + // otherwise an exception will be raised. + private final Map> defined; + + private final int limitInclusive; + + // Create the generator with no fields. + public BigClassTreeClassLoader(int depthLimitInclusive) { + this(depthLimitInclusive, NO_FIELD_GEN); + } + + // Create the generator with fields. + public BigClassTreeClassLoader(int depthLimitInclusive, + FieldGeneration fields) { + if (depthLimitInclusive < 0 || depthLimitInclusive > SANE_LIMIT) { + throw new IllegalArgumentException("depth limit beyond sane bounds"); + } + // Make it compatible with zero indices. + this.limitInclusive = depthLimitInclusive - 1; + this.defined = new HashMap<>(); + if (fields.index > limitInclusive) { + throw new IllegalArgumentException("field generation index invalid"); + } + this.availableStrategies = new Strategy[] { new GenStrategy(fields.index), new FieldStrategy(fields) }; + // Finally, register as a parallel capable classloader for stress tests. + if(!registerAsParallelCapable() || !isRegisteredAsParallelCapable()) { + throw new IllegalStateException("could not register parallel classloader"); + } + } + + // The index X means GenX will have the field. Set to -1 to disable. + // The furthest chained field Field0 can have an optional superclass/declared field. + public static record FieldGeneration (int index, + Optional deepestParentClass, + Optional deepestFieldClass) {} + + // We will bottom-up generate a class tree. It knows what to do for a + // specific class based on the provided name. This is not thread-safe itself, + // but since it is called safely via a synchronized block in loadClass, + // adding custom synchronization primitives can yield in a deadlock. + public Class findClass(final String name) throws ClassNotFoundException { + // We only generate classes starting with our known prefix. + final Strategy strategy = Arrays.stream(availableStrategies) + .filter(st -> name.startsWith(st.prefix())) + .findFirst() + .orElseThrow(ClassNotFoundException::new); + // Derive the correct parameters (or error). + String prefix = strategy.prefix(); + int depth; + try { + String itersString = name.substring(prefix.length()); + depth = Integer.parseInt(itersString); + // Some bounds sanity checking. + if (depth < 0 || depth > limitInclusive) { + throw new IllegalArgumentException("attempting to generate beyond limits"); + } + } catch (IllegalArgumentException | IndexOutOfBoundsException e) { + throw new ClassNotFoundException("can't generate class since it does not conform to limits", e); + } + // If we have already generated this class, reuse it. + Class clazz = defined.get(name); + if (clazz != null) { + return clazz; + } + // Make the actual and define it. + clazz = makeClass(name, + strategy.parent(depth), + strategy.flags(limitInclusive, depth), + clb -> strategy.process(limitInclusive, depth, clb), + cob -> strategy.constructorPre(limitInclusive, depth, cob) + ); + return clazz; + } + + private interface Strategy { + String prefix(); + String parent(int depth); + int flags(int limitInclusive, int depth); + void process(int limitInclusive, int depth, ClassBuilder builder); + default void constructorPre(int limitInclusive, int depth, CodeBuilder builder) {} + } + + // The Gen classes generate classes that have a large inheritance tree. + // GenX has Gen(X-1) as a superclass. Gen0 inherits from Object. + private static final class GenStrategy implements Strategy { + private final int fieldIndex; + + public GenStrategy(int fieldIndex) { + this.fieldIndex = fieldIndex; + } + + public String prefix() { + return "Gen"; + } + + public String parent(int depth) { + return depth == 0 ? Object.class.getName() : prefix() + (depth - 1); + } + + public int flags(int limitInclusive, int depth) { + return depth == limitInclusive ? ACC_FINAL : ACC_ABSTRACT; + } + + public void process(int limitInclusive, int depth, ClassBuilder builder) { + // Is this the generation that will have the field chain? + if (depth == fieldIndex) { + ClassDesc fieldClass = ClassDesc.of(FieldStrategy.PREFIX + "" + limitInclusive); + // We use an uninitialized static field to denote the outermost Field class. + builder.withField("theField", fieldClass, ACC_PUBLIC | ACC_STATIC) + .with(LoadableDescriptorsAttribute.of(builder.constantPool().utf8Entry(fieldClass))); + } + } + } + + // The field strategy allows generating deep fields, including potential circularity. + // FieldX has Field(X-1) as a field. Field0 is special as it can inherit from something + // other than Object, and contain a custom field. + private static final class FieldStrategy implements Strategy { + public static final String PREFIX = "Field"; + private final FieldGeneration fields; + + public FieldStrategy(FieldGeneration fields) { + this.fields = fields; + } + + public String prefix() { + return PREFIX; + } + + public String parent(int depth) { + // Only the deepest class has a custom parent. + return fields.deepestParentClass().filter(_ -> depth == 0).orElse(Object.class.getName()); + } + + public int flags(int limitInclusive, int depth) { + // Every field class is final. + return ACC_FINAL; + } + + public void process(int limitInclusive, int depth, ClassBuilder builder) { + ClassDesc fieldClass = computeFieldClass(depth); + if (depth != 0) { + builder.with(LoadableDescriptorsAttribute.of(builder.constantPool().utf8Entry(fieldClass))); + } + // The field is non-static, final, and therefore needs ACC_STRICT_INIT + builder.withField("theField", fieldClass, ACC_PUBLIC | ACC_FINAL | ACC_STRICT_INIT); + } + + public void constructorPre(int limitInclusive, int depth, CodeBuilder builder) { + ClassDesc thisField = ClassDesc.of(prefix() + "" + depth); + ClassDesc fieldClass = computeFieldClass(depth); + // We need to make sure to initialize the field as the first thing in the constructor. + builder.aload(0) + .aconst_null() + .putfield(thisField, "theField", fieldClass); + } + + private ClassDesc computeFieldClass(int depth) { + if (depth == 0) { + return ClassDesc.of(fields.deepestFieldClass().orElse(Object.class.getName())); + } else { + return ClassDesc.of(prefix() + (depth - 1)); + } + } + } + + // Make the class. Not thread-safe, should be called when obtaining a + // classloading lock for the particular class. + private Class makeClass(String thisGen, + String parentGen, + int addFlags, + Consumer classBuilder, + Consumer constructorBuilder) { + ClassDesc parentDesc = ClassDesc.of(parentGen); + // A class that has itself as a loadable descriptor. + byte[] bytes = ClassFile.of().build(ClassDesc.of(thisGen), clb -> { + clb + // Use Valhalla version. + .withVersion(latestMajorVersion(), PREVIEW_MINOR_VERSION) + // Explicitly do not add ACC_SUPER or ACC_ABSTRACT. + .withFlags(ACC_PUBLIC | addFlags) + // Not strictly necessary for java/lang/Object. + .withSuperclass(parentDesc) + // Make sure to init the correct superclass. + .withMethodBody(INIT_NAME, MTD_void, ACC_PUBLIC, cob -> { + constructorBuilder.accept(cob); + cob.aload(0) + .invokespecial(parentDesc, INIT_NAME, MTD_void) + .return_(); + }); + // Do the additional things defined by the strategy. + classBuilder.accept(clb); + }); + // Define the actual class and register it. + Class clazz = defineClass(thisGen, bytes, 0, bytes.length); + defined.put(thisGen, clazz); + return clazz; + } + +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/Child.jcod b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/Child.jcod new file mode 100644 index 00000000000..d8b89fc5a47 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/Child.jcod @@ -0,0 +1,51 @@ +class Child { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Utf8 "Child"; // #1 + class #1; // #2 + Utf8 "Parent"; // #3 + class #3; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + NameAndType #5 #6; // #7 + Method #4 #7; // #8 + Utf8 "Code"; // #9 + } // Constant Pool + + 0x0011; // access NOTE: this is a value class, which cannot inherit from identity + #2;// this_cpx + #4;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#9) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70008B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + } // Attributes +} // end class Child diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/ConcurrentClassLoadingTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/ConcurrentClassLoadingTest.java new file mode 100644 index 00000000000..af9b2d0e61b --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/ConcurrentClassLoadingTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary Invokes eight threads that concurrently have to resolve the same + set of classes, thereby putting stress on the classloader and + deadlocks will be noticed. This execution is iterated many times. + * @library /test/lib + * @enablePreview + * @compile BigClassTreeClassLoader.java + * @run junit/othervm/timeout=480 -XX:ReservedCodeCacheSize=2G ConcurrentClassLoadingTest + */ + +import java.util.Optional; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; + +import org.junit.jupiter.api.Test; + +// This test makes use of BigClassTreeClassLoader. Please refer to its documentation. +class ConcurrentClassLoadingTest { + private static final boolean DEBUG = false; + private static final int N_ITER = 125; + private static final int DEPTH = 100; + + @Test + void test() throws InterruptedException { + for (int i = 1; i <= N_ITER; i++) { + if (DEBUG) System.out.println("Iteration " + i); + doIteration(8); + } + } + + // Should crash the VM if it fails/deadlocks. + private void doIteration(int n) throws InterruptedException { + // Use a barrier to ensure all threads reach a certain point before calling + // the method that defines the class (which internally calls native code). + final CyclicBarrier barrier = new CyclicBarrier(n); + // Every iteration has a new instance of a class loader, to make sure we + // create unique (Class, ClassLoader) pairs to force loading. + // We generate DEPTH fields, and they are defined in childmost class. + var fields = new BigClassTreeClassLoader.FieldGeneration(DEPTH - 1, Optional.empty(), Optional.empty()); + // Instantiate the class generating classloader. + final var cl = new BigClassTreeClassLoader(DEPTH, fields); + Thread[] threads = new Thread[n]; + // Spawn all the threads with their respective worker classes. + for (int i = 0; i < n; i++) { + Thread thread = new Thread(() -> { + try { + // Wait for all threads to reach this point. + barrier.await(); + // This will trigger the generation and loading of the childmost class. + // That itself will trigger loading of many field value classes. + Class workerClass = Class.forName("Gen" + (DEPTH - 1), false, cl); + Object worker = workerClass.getDeclaredConstructor().newInstance(); + } catch (InterruptedException | BrokenBarrierException e) { + throw new IllegalStateException("test setup: waiting for barrier saw error", e); + } catch (ReflectiveOperationException e) { + // A ReflectiveOperationException could get thrown if + // something goes wrong internally. This should make the test + // case fail as it represents a real problem. + throw new IllegalStateException("reflective exception, could be an underlying bug", e); + } + }); + threads[i] = thread; + thread.start(); + } + for (Thread thread : threads) { + thread.join(); + } + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/InnerValue.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/InnerValue.java new file mode 100644 index 00000000000..2249bd992fc --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/InnerValue.java @@ -0,0 +1,3 @@ +public value class InnerValue { + public int x = 19; +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/LoadableDescriptorsTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/LoadableDescriptorsTest.java new file mode 100644 index 00000000000..b4f7360b534 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/LoadableDescriptorsTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary Ensures preloading. + * @library /test/lib + * @enablePreview + * @run junit LoadableDescriptorsTest + */ + +import java.lang.classfile.ClassFile; +import java.lang.classfile.attribute.LoadableDescriptorsAttribute; +import java.lang.constant.ClassDesc; +import java.lang.reflect.Field; + +import jdk.test.lib.ByteCodeLoader; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static java.lang.classfile.ClassFile.*; +import static java.lang.constant.ConstantDescs.*; + +// NOTE: Needs further work for JDK-8367134. +class LoadableDescriptorsTest { + private static final boolean DEBUG = false; + + @ParameterizedTest + @ValueSource(strings = { + "LTest;", + "I", + "[[LTest;", + }) + void test(String descriptorString) throws ReflectiveOperationException { + ClassDesc loadableClass = ClassDesc.ofDescriptor(descriptorString); + var bytes = ClassFile.of().build(ClassDesc.of("Test"), clb -> + clb + .withVersion(latestMajorVersion(), PREVIEW_MINOR_VERSION) + .withFlags(ACC_PUBLIC | ACC_IDENTITY) + .withMethodBody(INIT_NAME, MTD_void, ACC_PUBLIC, cob -> + cob.aload(0) + .invokespecial(CD_Object, INIT_NAME, MTD_void) + .return_()) + .withField("theField", loadableClass, ACC_PUBLIC) + .with(LoadableDescriptorsAttribute.of(clb.constantPool().utf8Entry(loadableClass))) + ); + + Class clazz = ByteCodeLoader.load("Test", bytes); + Object instance = clazz.getDeclaredConstructor().newInstance(); + Field field = clazz.getDeclaredField("theField"); + field.get(instance); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/OuterValue.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/OuterValue.java new file mode 100644 index 00000000000..bad70397096 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/OuterValue.java @@ -0,0 +1,3 @@ +public value class OuterValue { + public InnerValue inner = null; +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/Parent.jcod b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/Parent.jcod new file mode 100644 index 00000000000..e0e9729e3a1 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/Parent.jcod @@ -0,0 +1,51 @@ +class Parent { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Utf8 "Parent"; // #1 + class #1; // #2 + Utf8 "java/lang/Object"; // #3 + class #3; // #4 + Utf8 ""; // #5 + Utf8 "()V"; // #6 + NameAndType #5 #6; // #7 + Method #4 #7; // #8 + Utf8 "Code"; // #9 + } // Constant Pool + + 0x0021; // access NOTE: this is an identity class + #2;// this_cpx + #4;// super_cpx + + [] { // Interfaces + } // Interfaces + + [] { // Fields + } // Fields + + [] { // Methods + { // method + 0x0001; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#9) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2AB70008B1; + } + [] { // Traps + } // end Traps + [] { // Attributes + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [] { // Attributes + } // Attributes +} // end class Parent diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/PreLoadCircularityTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/PreLoadCircularityTest.java new file mode 100644 index 00000000000..7af0bca3564 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/PreLoadCircularityTest.java @@ -0,0 +1,70 @@ + +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary Ensures circularity does not cause crashes. + * @enablePreview + * @compile BigClassTreeClassLoader.java + * @run junit PreLoadCircularityTest + */ + +import java.util.Optional; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +// This test makes use of BigClassTreeClassLoader. Please refer to its documentation. +class PreLoadCircularityTest { + + @ParameterizedTest + @MethodSource("constellations") + void test(int depth, int fieldIndex, Optional fieldClass, Optional parentClass) + throws ClassNotFoundException, ReflectiveOperationException { + // Create the generator. + var fg = new BigClassTreeClassLoader.FieldGeneration(fieldIndex, fieldClass, parentClass); + BigClassTreeClassLoader cl = new BigClassTreeClassLoader(depth, fg); + // Generate the classes! + Class clazz = Class.forName("Gen" + (depth - 1), false, cl); + clazz.getDeclaredConstructor().newInstance(); + } + + private static Stream constellations() { + return Stream.of( + // Class Gen10 will have 30 fields and Field0 will inherit from Gen15. + // This forms a cycle through field preloading and inheritance. + Arguments.of(30, 10, Optional.of(Object.class.getName()), Optional.of("Gen15")), + // Class Gen10 will have 30 fields and Field0 will refer to Gen10. + // This forms a cycle through field preloading. + Arguments.of(30, 10, Optional.of("Gen10"), Optional.empty()), + // Class Gen10 will have 30 fields and Field0 will inherit from + // Gen15 and refer to Gen13. This forms a cycle through field and + // inheritance preloading. + Arguments.of(30, 10, Optional.of("Gen13"), Optional.of("Gen15")) + ); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/PreLoadDoesNotInitTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/PreLoadDoesNotInitTest.java new file mode 100644 index 00000000000..f0c60009f1f --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/PreLoadDoesNotInitTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary Ensures that a value class is not initialized when preloaded. + * @enablePreview + * @run junit PreLoadDoesNotInitTest + */ + +import org.junit.jupiter.api.Test; + +class PreLoadDoesNotInitTest { + + @Test + void test() { + Outer outer = new Outer(); + outer.doSomething(); + if (Outer.THE_FIELD != 19) { + throw new IllegalStateException("class was initialized when it should not have been"); + } + // Sanity: make sure it loads when we actually use it. + new Inner(); + if (Outer.THE_FIELD != 0) { + throw new IllegalStateException("class was not initialized when it should have been"); + } + } + + public static class Outer { + // Value class as a field should ensure that Outer contains a loadable + // descriptor for it. We will preload Inner. + private Inner inner; + + // This is a static field that gets updated by Inner's static + // initializer. We expect this to remain as 19. + public static int THE_FIELD = 19; + + private void doSomething() {} + } + + public static value class Inner { + private int x = 0; + + static { + // This static field only gets updated once Inner is initialized. + // In this test case, this should NOT happen. + Outer.THE_FIELD = 0; + } + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/PreLoadFailuresDoNotImpactApplicationTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/PreLoadFailuresDoNotImpactApplicationTest.java new file mode 100644 index 00000000000..41554a54045 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/PreLoadFailuresDoNotImpactApplicationTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary Even if LoadableDescriptors fail, we observe correct operation. + This uses a custom classloader to ensure that classloading/linking will + initially fail due to LoadableDescriptors. + @compile OuterValue.java + @compile InnerValue.java + * @enablePreview + * @run junit PreLoadFailuresDoNotImpactApplicationTest + */ + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.nio.file.Files; +import java.nio.file.Paths; + +import org.junit.jupiter.api.Test; + +class PreLoadFailuresDoNotImpactApplicationTest { + + @Test + void test() throws ReflectiveOperationException, InterruptedException { + // Create an instance of the Outer class with a custom classloader. + // This should trigger the first preload. + Class clazz = Class.forName("OuterValue", false, new CL()); + Object outer = clazz.getDeclaredConstructor().newInstance(); + // Each thread will try to load the field inner. We have to do getDeclaredField in each thread + // as this attempts to load the class Inner which is what we want to fail on the first time. + Thread t1 = new Thread(() -> { + try { + // Here the classloader will instigate a failure twice: + // 1) when linking Inner due to LoadableDescriptor failing, this failure is quiet; and + // 2) a "normal" ClassNotFound error that should get picked up by the application. + var theField = clazz.getDeclaredField("inner"); + Object theInner = theField.get(outer); + throw new RuntimeException("should have failed classloading Inner fist time, VM bug"); + } catch (NoClassDefFoundError workingAsIntended) { + } catch (IllegalAccessException | NoSuchFieldException e) { + throw new RuntimeException("test bug: field accessing", e); + } + }); + // Execution need not be concurrent or parallel. + // Let this thread fail at classloading Inner. + t1.start(); + t1.join(); + Thread t2 = new Thread(() -> { + try { + // Here, the classloader will not fail, this should be business as usual. + var theField = clazz.getDeclaredField("inner"); + Object theInner = theField.get(outer); + } catch (NoClassDefFoundError e) { + throw new IllegalStateException("should not have failed classloading Inner second time, VM bug", e); + } catch (IllegalAccessException | NoSuchFieldException e) { + throw new RuntimeException("test bug: field accessing", e); + } + }); + // This execution should succeed. + t2.start(); + t2.join(); + } + + // This classloader uses loadClass instead of overriding findClass because it makes the test logic + // a bit easier to write and understand. For the purpose of this test, overriding loadClass should + // be fine. + static class CL extends ClassLoader { + private int attempts = 0; + + public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + switch (name) { + case "OuterValue": { + return customLoadSimple(name); + } + case "InnerValue": { + // First access failure: when Outer is preloading Inner via LoadableDescriptor. + // Second access failure: when we try to load Inner the first time (LoadableDescriptor). + // Third access failure: when class linking occurs (LoadableDescriptor). + // Fourth access and onwards should succeed. + if (attempts++ < 3) { + throw new ClassNotFoundException("purposeful exception: we can't find this class"); + } + return customLoadSimple(name); + } + default: + // Delegate loading to the parent classloader. + return super.loadClass(name, resolve); + } + } + + // This will get the class data as a byte[]. DOES NOT support packages. + private Class customLoadSimple(String name) { + byte[] bytes; + try { + bytes = PreLoadFailuresDoNotImpactApplicationTest.class + .getClassLoader() + .getResourceAsStream(name + ".class") + .readAllBytes(); + } catch (IOException e) { + throw new IllegalStateException("test bug: IO exception trying to load custom class " + name, e); + } + return defineClass(name, bytes, 0, bytes.length, null); + } + } + +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/ValueClassInheritanceTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/ValueClassInheritanceTest.java new file mode 100644 index 00000000000..63467c309d6 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/classloading/ValueClassInheritanceTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + + /* + * @test + * @summary Ensures a non-abstract value class cannot inherit from an identity class. + * @enablePreview + * @clean Parent + * @clean Child + * @compile Parent.jcod + * @compile Child.jcod + * @run junit ValueClassInheritanceTest + */ + +import java.lang.reflect.InvocationTargetException; + +import org.junit.jupiter.api.Test; + +class ValueClassInheritanceTest { + private final boolean DEBUG = false; + + @Test + void test() throws ReflectiveOperationException { + try { + // We create a new instance of the child class. + // This should see that the class hierarchy is illegal and throw an exception. + Class clazz = Class.forName("Child"); + Object instance = clazz.getDeclaredConstructor().newInstance(); + if (DEBUG) System.out.println(instance); + } catch (IncompatibleClassChangeError weWantThis) { + // The error that we are looking for. + return; + } + // A lack of exception will fail this test. + throw new IllegalStateException("expected IncompatibleClassChangeError to be thrown"); + } + +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/FieldAlignmentTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/FieldAlignmentTest.java new file mode 100644 index 00000000000..cf43d6adde7 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/FieldAlignmentTest.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test id=Oops32 + * @requires vm.bits == 32 + * @requires vm.flagless + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java FieldAlignmentTest.java + * @run main/othervm FieldAlignmentTest 0 + */ + + /* + * @test id=CompressedOops + * @requires vm.bits == 64 + * @requires vm.flagless + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java FieldAlignmentTest.java + * @run main/othervm FieldAlignmentTest 1 + */ + + /* + * @test id=NoCompressedOops + * @requires vm.bits == 64 + * @requires vm.flagless + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java FieldAlignmentTest.java + * @run main/othervm FieldAlignmentTest 2 + */ + + import java.util.ArrayList; + import java.util.Collections; + import java.util.List; + + import jdk.internal.vm.annotation.LooselyConsistentValue; + import jdk.internal.vm.annotation.NullRestricted; + import jdk.internal.vm.annotation.Strict; + + + import jdk.test.lib.Asserts; + import jdk.test.lib.ByteCodeLoader; + import jdk.test.lib.helpers.ClassFileInstaller; + import jdk.test.lib.compiler.InMemoryJavaCompiler; + import jdk.test.lib.process.OutputAnalyzer; + import jdk.test.lib.process.ProcessTools; + + public class FieldAlignmentTest { + public static class ZeroByte { } + public static class OneByte { byte b; } + public static class TwoByte { byte b0; byte b1; } + public static class ThreeByte { byte b0; byte b1; byte b2; } + public static class FourByte { byte b0; byte b1; byte b2; byte b3; } + public static class FiveByte { byte b0; byte b1; byte b2; byte b3; byte b4; } + public static class SixByte { byte b0; byte b1; byte b2; byte b3; byte b4; byte b5; } + public static class SevenByte { byte b0; byte b1; byte b2; byte b3; byte b4; byte b5; byte b6; } + public static final String[] superNames = { ZeroByte.class.getCanonicalName(), + OneByte.class.getCanonicalName(), + TwoByte.class.getCanonicalName(), + ThreeByte.class.getCanonicalName(), + FourByte.class.getCanonicalName(), + FiveByte.class.getCanonicalName(), + SixByte.class.getCanonicalName(), + SevenByte.class.getCanonicalName() }; + public static final String[] valueNames = { ValueOneByte.class.getCanonicalName(), + ValueOneChar.class.getCanonicalName(), + ValueOneShort.class.getCanonicalName(), + ValueOneInt.class.getCanonicalName(), + ValueOneLong.class.getCanonicalName(), + ValueOneFloat.class.getCanonicalName(), + ValueOneDouble.class.getCanonicalName(), + ValueByteLong.class.getCanonicalName(), + ValueByteInt.class.getCanonicalName() }; + + List testNames = new ArrayList(); + + @LooselyConsistentValue static value class ValueOneByte { byte val = 0; } + @LooselyConsistentValue static value class ValueOneChar { char val = 0; } + @LooselyConsistentValue static value class ValueOneShort { short val = 0; } + @LooselyConsistentValue static value class ValueOneInt { int val = 0; } + @LooselyConsistentValue static value class ValueOneLong { long val = 0; } + @LooselyConsistentValue static value class ValueOneFloat { float val = 0f; } + @LooselyConsistentValue static value class ValueOneDouble { double val = 0d; } + + @LooselyConsistentValue static value class ValueByteLong { byte b = 0; long l = 0; } + @LooselyConsistentValue static value class ValueByteInt { byte b = 0; int i = 0; } + + void generateTests() throws Exception { + for (String vName : valueNames) { + for (String sName : superNames) { + String vNameShort = vName.substring(vName.lastIndexOf('.') + 1); + String sNameShort = sName.substring(sName.lastIndexOf('.') + 1); + String className = "Test" + vNameShort + "With" + sNameShort; + String sourceCode = "import jdk.internal.vm.annotation.NullRestricted;" + + "import jdk.internal.vm.annotation.Strict;" + + "public class " + className + " extends " + sName + " { " + + " @Strict" + + " @NullRestricted" + + " " + vName + " v1 = new " + vName + "();" + + "}"; + String java_version = System.getProperty("java.specification.version"); + byte[] byteCode = InMemoryJavaCompiler.compile(className, sourceCode, + "-source", java_version, "--enable-preview", + "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED"); + jdk.test.lib.helpers.ClassFileInstaller.writeClassToDisk(className, byteCode); + testNames.add(className); + } + } + } + + void generateTestRunner() throws Exception { + String className = "TestRunner"; + StringBuilder sb = new StringBuilder(); + sb.append("public class ").append(className).append(" {"); + sb.append(" public void main(String[] args) {"); + for (String name : testNames) { + sb.append(" ").append(name).append(" var").append(name).append(" = new ").append(name).append("();"); + } + sb.append(" }"); + sb.append("}"); + String java_version = System.getProperty("java.specification.version"); + byte[] byteCode = InMemoryJavaCompiler.compile(className, sb.toString(), + "-source", java_version, "--enable-preview", + "-cp", "."); + jdk.test.lib.helpers.ClassFileInstaller.writeClassToDisk(className, byteCode); + } + + static ProcessBuilder exec(String compressedOopsArg, String... args) throws Exception { + List argsList = new ArrayList<>(); + Collections.addAll(argsList, "--enable-preview"); + Collections.addAll(argsList, "-XX:+UnlockDiagnosticVMOptions"); + Collections.addAll(argsList, "-XX:+PrintFieldLayout"); + Collections.addAll(argsList, "-Xshare:off"); + if (compressedOopsArg != null) { + Collections.addAll(argsList, compressedOopsArg); + } + Collections.addAll(argsList, "-Xmx256m"); + Collections.addAll(argsList, "-cp", System.getProperty("java.class.path") + System.getProperty("path.separator") +"."); + Collections.addAll(argsList, args); + return ProcessTools.createTestJavaProcessBuilder(argsList); + } + + public static void main(String[] args) throws Exception { + String compressedOopsArg; + + switch(args[0]) { + case "0": compressedOopsArg = null; + break; + case "1": compressedOopsArg = "-XX:+UseCompressedOops"; + break; + case "2": compressedOopsArg = "-XX:-UseCompressedOops"; + break; + default: throw new RuntimeException("Unrecognized configuration"); + } + + // Generate test classes + FieldAlignmentTest fat = new FieldAlignmentTest(); + fat.generateTests(); + fat.generateTestRunner(); + + // Execute the test runner in charge of loading all test classes + ProcessBuilder pb = exec(compressedOopsArg, "TestRunner"); + OutputAnalyzer out = new OutputAnalyzer(pb.start()); + + if (out.getExitValue() != 0) { + out.outputTo(System.out); + } + Asserts.assertEquals(out.getExitValue(), 0, "Something went wrong while running the tests"); + + // Analyze the test runner output + FieldLayoutAnalyzer.LogOutput lo = new FieldLayoutAnalyzer.LogOutput(out.asLines()); + + FieldLayoutAnalyzer fla = FieldLayoutAnalyzer.createFieldLayoutAnalyzer(lo); + try { + fla.check(); + } catch (Throwable t) { + out.outputTo(System.out); + throw t; + } + } + } diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/FieldLayoutAnalyzer.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/FieldLayoutAnalyzer.java new file mode 100644 index 00000000000..5b37933a8b2 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/FieldLayoutAnalyzer.java @@ -0,0 +1,700 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +import javax.management.RuntimeErrorException; + +import jdk.test.lib.Asserts; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class FieldLayoutAnalyzer { + + // Mutable wrapper around log output to manage the cursor while parsing + static class LogOutput { + List lines; + int cursor; + + public LogOutput(List lines) { + this.lines = lines; + cursor = 0; + } + + String getCurrentLine() { return lines.get(cursor); } + String get(int idx) { return lines.get(idx); } + int size() { return lines.size(); } + void moveToNextLine() { cursor = cursor + 1; } + boolean hasMoreLines() { return cursor < lines.size(); } + } + + static enum BlockType { + RESERVED, + INHERITED, + EMPTY, + REGULAR, + PADDING, + FLAT, + NULL_MARKER; + + static BlockType parseType(String s) { + switch(s) { + case "RESERVED" : return RESERVED; + case "INHERITED" : return INHERITED; + case "EMPTY" : return EMPTY; + case "REGULAR" : return REGULAR; + case "PADDING" : return PADDING; + case "FLAT" : return FLAT; + case "NULL_MARKER" : return NULL_MARKER; + default: + throw new RuntimeException("Unknown block type: " + s); + } + } + } + + static enum LayoutKind { + NON_FLAT, + NON_ATOMIC_FLAT, + ATOMIC_FLAT, + NULLABLE_FLAT; + + static LayoutKind parseLayoutKind(String s) { + switch(s) { + case "" : return NON_FLAT; + case "NON_ATOMIC_FLAT" : return NON_ATOMIC_FLAT; + case "ATOMIC_FLAT" : return ATOMIC_FLAT; + case "NULLABLE_ATOMIC_FLAT" : return NULLABLE_FLAT; + default: + throw new RuntimeException("Unknown layout kind: " + s); + } + } + } + + static public record FieldBlock (int offset, + BlockType type, + int size, + int alignment, + String name, + String signature, + String fieldClass, + LayoutKind layoutKind) { + + static FieldBlock createSpecialBlock(int offset, BlockType type, int size, int alignment) { + return new FieldBlock(offset, type, size, alignment, null, null, null, LayoutKind.NON_FLAT); + } + + static FieldBlock createJavaFieldBlock(int offset, BlockType type, int size, int alignment, String name, String signature, String fieldClass, LayoutKind layoutKind) { + return new FieldBlock(offset, type, size, alignment, name, signature, fieldClass, layoutKind); + } + + void print(PrintStream out) { + out.println("Offset=" + offset+ + " type=" + type + + " size=" + size + + " alignment=" + alignment + + " name=" + name + + " signature=" + signature + + " fieldClass=" + fieldClass); + } + + boolean isFlat() { return type == BlockType.FLAT; } // Warning: always return false for inherited fields, even flat ones + + static FieldBlock parseField(String line) { + String[] fieldLine = line.split("\\s+"); + // for(String s : fieldLine) { + // System.out.print("["+s+"]"); // debugging statement to be removed + // } + // System.out.println(); + int offset = Integer.parseInt(fieldLine[1].substring(1, fieldLine[1].length())); + BlockType type = BlockType.parseType(fieldLine[2]); + String[] size_align = fieldLine[3].split("/"); + int size = Integer.parseInt(size_align[0]); + int alignment = -1; + if (type != BlockType.RESERVED) { + alignment = Integer.parseInt(size_align[1]); + } else { + Asserts.assertTrue(size_align[1].equals("-")); + } + FieldBlock block = null; + switch(type) { + case BlockType.RESERVED: + case BlockType.EMPTY: + case BlockType.PADDING: { + block = FieldBlock.createSpecialBlock(offset, type, size, alignment); + break; + } + case BlockType.REGULAR: + case BlockType.INHERITED: + case BlockType.FLAT: { + String name = fieldLine[4]; + String signature = fieldLine[5]; + String fieldClass = ""; + String layoutKind = ""; + int nullMarkerOffset = -1; + if (type == BlockType.FLAT) { + fieldClass = fieldLine[6]; + layoutKind = fieldLine[7]; + } + block = FieldBlock.createJavaFieldBlock(offset, type, size, alignment, name, signature, fieldClass, LayoutKind.parseLayoutKind(layoutKind)); + break; + } + case BlockType.NULL_MARKER: { + block = FieldBlock.createSpecialBlock(offset, type, size, alignment); + break; + } + } + Asserts.assertNotNull(block); + return block; + } + + } + + static class ClassLayout { + String name; + String superName; + boolean isValue; + int instanceSize; + int payloadSize; + int payloadAlignment; + int firstFieldOffset; + int nonAtomicLayoutSize; // -1 if no non-nullable layout + int nonAtomicLayoutAlignment; // -1 if no non-nullable layout + int atomicLayoutSize; // -1 if no atomic layout + int atomicLayoutAlignment; // -1 if no atomic layout + int nullableLayoutSize; // -1 if no nullable layout + int nullableLayoutAlignment; // -1 if no nullable layout + int nullMarkerOffset; // -1 if no nullable layout + String[] lines; + ArrayList staticFields; + ArrayList nonStaticFields; + + private ClassLayout() { + staticFields = new ArrayList(); + nonStaticFields = new ArrayList(); + } + + boolean hasNonAtomicLayout() { return nonAtomicLayoutSize != -1; } + boolean hasAtomicLayout() { return atomicLayoutSize != -1; } + boolean hasNullableLayout() { return nullableLayoutSize != -1; } + boolean hasNullMarker() {return nullMarkerOffset != -1; } + + int getSize(LayoutKind layoutKind) { + switch(layoutKind) { + case NON_FLAT: + throw new RuntimeException("Should not be called on non-flat fields"); + case NON_ATOMIC_FLAT: + Asserts.assertTrue(nonAtomicLayoutSize != -1); + return nonAtomicLayoutSize; + case ATOMIC_FLAT: + Asserts.assertTrue(atomicLayoutSize != -1); + return atomicLayoutSize; + case NULLABLE_FLAT: + Asserts.assertTrue(nullableLayoutSize != -1); + return nullableLayoutSize; + default: + throw new RuntimeException("Unknown LayoutKind " + layoutKind); + } + } + + int getAlignment(LayoutKind layoutKind) { + switch(layoutKind) { + case NON_FLAT: + throw new RuntimeException("Should not be called on non-flat fields"); + case NON_ATOMIC_FLAT: + Asserts.assertTrue(nonAtomicLayoutSize != -1); + return nonAtomicLayoutAlignment; + case ATOMIC_FLAT: + Asserts.assertTrue(atomicLayoutSize != -1); + return atomicLayoutAlignment; + case NULLABLE_FLAT: + Asserts.assertTrue(nullableLayoutSize != -1); + return nullableLayoutAlignment; + default: + throw new RuntimeException("Unknown LayoutKind " + layoutKind); + } + } + + void processField(String line, boolean isStatic) { + FieldBlock block = FieldBlock.parseField(line); + if (isStatic) { + Asserts.assertTrue(block.type != BlockType.INHERITED); // static fields cannotbe inherited + staticFields.add(block); + } else { + nonStaticFields.add(block); + } + } + + static ClassLayout parseClassLayout(LogOutput lo) { + ClassLayout cl = new ClassLayout(); + // Parsing class name + Asserts.assertTrue(lo.getCurrentLine().startsWith("Layout of class"), lo.getCurrentLine()); + String[] first = lo.getCurrentLine().split("\\s+"); + cl.name = first[3]; + if (first.length == 6) { + Asserts.assertEquals(first[4], "extends"); + cl.superName = first[5]; + } else { + cl.superName = null; + } + // System.out.println("Class name: " + cl.name); + lo.moveToNextLine(); + Asserts.assertTrue(lo.getCurrentLine().startsWith("Instance fields:"), lo.getCurrentLine()); + lo.moveToNextLine(); + // Parsing instance fields + while (lo.getCurrentLine().startsWith(" @")) { + cl.processField(lo.getCurrentLine(), false); + lo.moveToNextLine(); + } + Asserts.assertTrue(lo.getCurrentLine().startsWith("Static fields:"), lo.getCurrentLine()); + lo.moveToNextLine(); + // Parsing static fields + while (lo.getCurrentLine().startsWith(" @")) { + cl.processField(lo.getCurrentLine(), true); + lo.moveToNextLine(); + } + Asserts.assertTrue(lo.getCurrentLine().startsWith("Instance size ="), lo.getCurrentLine()); + String[] sizeLine = lo.getCurrentLine().split("\\s+"); + cl.instanceSize = Integer.parseInt(sizeLine[3]); + lo.moveToNextLine(); + if (lo.getCurrentLine().startsWith("First field offset =")) { + // The class is a value class, more lines to parse + cl.isValue = true; + // First field offset = xx + String[] firstOffsetLine = lo.getCurrentLine().split("\\s+"); + cl.firstFieldOffset = Integer.parseInt(firstOffsetLine[4]); + lo.moveToNextLine(); + // Payload layout: x/y + Asserts.assertTrue(lo.getCurrentLine().startsWith("Payload layout")); + String[] payloadLayoutLine = lo.getCurrentLine().split("\\s+"); + String[] size_align = payloadLayoutLine[2].split("/"); + cl.payloadSize = Integer.parseInt(size_align[0]); + cl.payloadAlignment = Integer.parseInt(size_align[1]); + lo.moveToNextLine(); + // Non atomic flat layout: x/y + Asserts.assertTrue(lo.getCurrentLine().startsWith("Non atomic flat layout")); + String[] nonAtomicLayoutLine = lo.getCurrentLine().split("\\s+"); + size_align = nonAtomicLayoutLine[4].split("/"); + if (size_align[0].contentEquals("-")) { + Asserts.assertTrue(size_align[1].contentEquals("-"), "Size/Alignment mismatch"); + cl.nonAtomicLayoutSize = -1; + cl.nonAtomicLayoutAlignment = -1; + } else { + cl.nonAtomicLayoutSize = Integer.parseInt(size_align[0]); + cl.nonAtomicLayoutAlignment = Integer.parseInt(size_align[1]); + } + lo.moveToNextLine(); + // Atomic flat layout: x/y + Asserts.assertTrue(lo.getCurrentLine().startsWith("Atomic flat layout")); + String[] atomicLayoutLine = lo.getCurrentLine().split("\\s+"); + size_align = atomicLayoutLine[3].split("/"); + if (size_align[0].contentEquals("-")) { + Asserts.assertTrue(size_align[1].contentEquals("-"), "Size/Alignment mismatch"); + cl.atomicLayoutSize = -1; + cl.atomicLayoutAlignment = -1; + } else { + cl.atomicLayoutSize = Integer.parseInt(size_align[0]); + cl.atomicLayoutAlignment = Integer.parseInt(size_align[1]); + } + lo.moveToNextLine(); + // Nullable flat layout: x/y + Asserts.assertTrue(lo.getCurrentLine().startsWith("Nullable flat layout")); + String[] nullableLayoutLine = lo.getCurrentLine().split("\\s+"); + size_align = nullableLayoutLine[3].split("/"); + if (size_align[0].contentEquals("-")) { + Asserts.assertTrue(size_align[1].contentEquals("-"), "Size/Alignment mismatch"); + cl.nullableLayoutSize = -1; + cl.nullableLayoutAlignment = -1; + } else { + cl.nullableLayoutSize = Integer.parseInt(size_align[0]); + cl.nullableLayoutAlignment = Integer.parseInt(size_align[1]); + } + lo.moveToNextLine(); + // Null marker offset = 15 (if class has a nullable flat layout) + if (cl.nullableLayoutSize != -1) { + Asserts.assertTrue(lo.getCurrentLine().startsWith("Null marker offset")); + String[] nullMarkerLine = lo.getCurrentLine().split("\\s+"); + cl.nullMarkerOffset = Integer.parseInt(nullMarkerLine[4]); + lo.moveToNextLine(); + } else { + cl.nullMarkerOffset = -1; + } + } else { + cl.isValue = false; + } + + Asserts.assertTrue(lo.getCurrentLine().startsWith("---"), lo.getCurrentLine()); + lo.moveToNextLine(); + return cl; + } + + FieldBlock getFieldAtOffset(int offset, boolean isStatic) { + ArrayList fields = isStatic ? staticFields : nonStaticFields; + for (FieldBlock block : fields) { + if (block.offset == offset) return block; + } + throw new RuntimeException("No " + (isStatic ? "static" : "nonstatic") + " field found at offset "+ offset); + } + + FieldBlock getFieldFromName(String fieldName, boolean isStatic) { + FieldBlock block = getFieldFromNameOrNull(fieldName, isStatic); + if (block == null) { + throw new RuntimeException("No " + (isStatic ? "static" : "nonstatic") + " field found with name "+ fieldName); + } + return block; + } + + FieldBlock getFieldFromNameOrNull(String fieldName, boolean isStatic) { + Asserts.assertTrue(fieldName != null); + ArrayList fields = isStatic ? staticFields : nonStaticFields; + for (FieldBlock block : fields) { + if (block.name() == null) continue; + String n = block.name().substring(1, block.name().length() - 1); // in the log, name is surrounded by double quotes + if (fieldName.equals(n)) return block; + } + return null; + } + + } + + ArrayList layouts; + int oopSize; + + static String signatureToName(String sig) { + Asserts.assertTrue((sig.charAt(0) == 'L')); + Asserts.assertTrue((sig.charAt(sig.length() - 1) == ';')); + return sig.substring(1, sig.length() - 1); + } + + private FieldLayoutAnalyzer() { + layouts = new ArrayList(); + } + + public static FieldLayoutAnalyzer createFieldLayoutAnalyzer(LogOutput lo) { + FieldLayoutAnalyzer fla = new FieldLayoutAnalyzer(); + fla.generate(lo); + return fla; + } + + ClassLayout getClassLayout(String name) { + for(ClassLayout layout : layouts) { + if (layout.name.equals(name)) return layout; + } + return null; + } + + ClassLayout getClassLayoutFromName(String name) { + for(ClassLayout layout : layouts) { + String sub = layout.name.substring(0, layout.name.indexOf('@')); + if (name.equals(sub)) return layout; + } + return null; + } + + void checkOffsetOnFields(ArrayList fields) { + HashMap map = new HashMap(); + for (FieldBlock fb : fields) { + Asserts.assertFalse(map.containsKey(fb.offset()), "Duplicate offset at " + fb.offset()); + map.put(fb.offset(), fb); + } + } + + void checkOffsets() { + for (ClassLayout layout : layouts) { + try { + checkOffsetOnFields(layout.staticFields); + checkOffsetOnFields(layout.nonStaticFields); + } catch(Throwable t) { + System.out.println("Unexpection exception when checking offsets in class " + layout.name); + throw t; + } + } + } + + void checkNoOverlapOnFields(ArrayList fields) { + for (int i = 0; i < fields.size() - 1; i++) { + FieldBlock f0 = fields.get(i); + FieldBlock f1 = fields.get(i + 1); + if (f0.offset + f0.size < f1.offset) { + throw new RuntimeException("Hole issue found at offset " + f1.offset); + } else if (f0.offset + f0.size > f1.offset) { + throw new RuntimeException("Overlap issue found at offset " + f1.offset); + } + } + } + + void checkNoOverlap() { + for (ClassLayout layout : layouts) { + try { + checkNoOverlapOnFields(layout.staticFields); + checkNoOverlapOnFields(layout.nonStaticFields); + } catch(Throwable t) { + System.out.println("Unexpection exception when checking for overlaps/holes in class " + layout.name); + throw t; + } + } + } + + void checkSizeAndAlignmentForField(FieldBlock block) { + Asserts.assertTrue(block.size() > 0); + if (block.type == BlockType.RESERVED) { + Asserts.assertTrue(block.alignment == -1); + return; + } + if (block.type == BlockType.EMPTY || block.type == BlockType.PADDING + || block.type == BlockType.NULL_MARKER) { + Asserts.assertTrue(block.alignment == 1, "alignment = " + block.alignment); + return; + } + + switch(block.signature()) { + case "Z" : + case "B" : Asserts.assertTrue(block.size() == 1); + Asserts.assertTrue(block.alignment() == 1); + break; + case "S" : + case "C" : Asserts.assertTrue(block.size() == 2); + Asserts.assertTrue(block.alignment() == 2); + break; + case "F" : + case "I" : Asserts.assertTrue(block.size() == 4); + Asserts.assertTrue(block.alignment() == 4); + break; + case "J" : + case "D" : Asserts.assertTrue(block.size() == 8); + Asserts.assertTrue(block.alignment() == 8); + break; + default: { + if (block.signature().startsWith("[")) { + Asserts.assertEquals(oopSize, block.size()); + } else if (block.signature().startsWith("L")) { + if (block.type == BlockType.INHERITED) { + // Skip for now, will be verified when checking the class declaring the field + } else if (block.type == BlockType.REGULAR) { + Asserts.assertEquals(oopSize, block.size()); + } else { + Asserts.assertEquals(BlockType.FLAT, block.type); + ClassLayout fcl = getClassLayout(block.fieldClass); + Asserts.assertNotNull(fcl); + Asserts.assertEquals(block.size(), fcl.getSize(block.layoutKind)); + Asserts.assertEquals(block.alignment(), fcl.getAlignment(block.layoutKind)); + } + } else { + throw new RuntimeException("Unknown signature type: " + block.signature); + } + } + Asserts.assertTrue(block.offset % block.alignment == 0); + } + } + + void checkSizeAndAlignment() { + for (ClassLayout layout : layouts) { + try { + for (FieldBlock block : layout.staticFields) { + checkSizeAndAlignmentForField(block); + } + } catch(Throwable t) { + System.out.println("Unexpected exception when checking size and alignment in static fields of class " + layout.name); + throw t; + } + try { + for (FieldBlock block : layout.nonStaticFields) { + checkSizeAndAlignmentForField(block); + } + } catch(Throwable t) { + System.out.println("Unexpected exception when checking size and alignment in non-static fields of class " + layout.name); + throw t; + } + } + } + + // Verify that fields marked as INHERITED are declared in a super class + void checkInheritedFields() { + for (ClassLayout layout : layouts) { + try { + // Preparing the list of ClassLayout of super classes + ArrayList supers = new ArrayList(); + String className = layout.superName; + while (className != null) { + ClassLayout cl = getClassLayout(className); + supers.add(cl); + className = cl.superName; + } + for (FieldBlock field : layout.nonStaticFields) { + if (field.type == BlockType.INHERITED) { + int i = 0; + boolean found = false; + FieldBlock b = null; + while(i < supers.size() && !found) { + b = supers.get(i).getFieldAtOffset(field.offset, false); + if (b.type != BlockType.INHERITED) found = true; + i++; + } + String location = new String(" at " + layout.name + " offset " + field.offset()); + Asserts.assertTrue(found, "No declaration found for an inherited field " + location); + Asserts.assertNotEquals(field.type, BlockType.EMPTY, location); + Asserts.assertEquals(field.size, b.size, location); + Asserts.assertEquals(field.alignment, b.alignment, location ); + Asserts.assertEquals(field.name(), b.name(), location); + Asserts.assertEquals(field.signature(), b.signature(), location); + } + } + } catch(Throwable t) { + System.out.println("Unexpexted exception when checking inherited fields in class " + layout.name); + } + } + } + + static class Node { + ClassLayout classLayout; + Node superClass; + ArrayList subClasses = new ArrayList(); + } + + // Verify that all fields declared in a class are present in all subclass + void checkSubClasses() { + // Generating the class inheritance graph + HashMap nodes = new HashMap(); + for (ClassLayout layout : layouts) { + try { + if (layout.name.contains("$$Lambda@0")) continue; // Skipping lambda classes + Node current = nodes.get(layout.name); + if (current == null) { + current = new Node(); + nodes.put(layout.name, current); + } + if (current.classLayout == null) { + current.classLayout = layout; + } else { + Asserts.assertEQ(current.classLayout, layout); + } + if (layout.superName != null) { + Node superNode = nodes.get(layout.superName); + if (superNode == null) { + superNode = new Node(); + superNode.subClasses.add(current); + nodes.put(layout.superName, superNode); + } + superNode.subClasses.add(current); + } + } catch(Throwable t) { + System.out.println("Unexpected exception when generating list of sub-classes of class " + layout.name); + throw t; + } + } + // Field verification + for (Node node : nodes.values()) { + ClassLayout layout = node.classLayout; + for (FieldBlock block : layout.nonStaticFields) { + if (block.offset() == 0) continue; // Skip object header + if (block.type() == BlockType.EMPTY) continue; // Empty spaces can be used by subclasses + if (block.type() == BlockType.PADDING) continue; // PADDING should have a finer inspection, preserved for @Contended and other imperative padding, and relaxed for abstract value conservative padding + if (block.type() == BlockType.NULL_MARKER) continue; + // A special case for PADDING might be needed too => must NOT be used in subclasses + for (Node subnode : node.subClasses) { + try { + checkFieldInClass(block, subnode); + } catch(Throwable t) { + System.out.println("Unexpected exception when checking subclass " + subnode.classLayout.name + " of class " + layout.name); + throw t; + } + } + } + } + } + + void checkFieldInClass(FieldBlock block, Node node) { + FieldBlock b = node.classLayout.getFieldAtOffset(block.offset, false); + Asserts.assertTrue(b.type == BlockType.INHERITED); + Asserts.assertEquals(b.signature(), block.signature()); + Asserts.assertEquals(b.name(), block.name()); + Asserts.assertEquals(b.size(), block.size()); + Asserts.assertTrue(b.alignment() == block.alignment()); + for (Node subnode : node.subClasses) { + checkFieldInClass(block, subnode); + } + } + + void checkNullMarkers() { + for (ClassLayout layout : layouts) { + try { + BlockType last_type = BlockType.RESERVED; + boolean has_empty_slot = false; + for (FieldBlock block : layout.nonStaticFields) { + last_type = block.type; + if (block.type() == BlockType.NULL_MARKER) { + Asserts.assertTrue(layout.hasNullMarker()); + Asserts.assertTrue(layout.hasNullableLayout()); + Asserts.assertEQ(block.offset(), layout.nullMarkerOffset); + } + if (block.type() == BlockType.EMPTY) has_empty_slot = true; + } + // null marker should not be added at the end of the layout if there's an empty slot + Asserts.assertTrue(last_type != BlockType.NULL_MARKER || has_empty_slot == false, + "Problem detected in layout of class " + layout.name); + // static layout => must not have NULL_MARKERS because static fields are never flat + for (FieldBlock block : layout.staticFields) { + Asserts.assertNotEquals(block.type(), BlockType.NULL_MARKER); + Asserts.assertNotEquals(block.type(), BlockType.FLAT); + } + } catch(Throwable t) { + System.out.println("Unexpected exception while checking null markers in class " + layout.name); + throw t; + } + } + } + + void check() { + checkOffsets(); + checkNoOverlap(); + checkSizeAndAlignment(); + checkInheritedFields(); + checkSubClasses(); + checkNullMarkers(); + } + + private void generate(LogOutput lo) { + try { + while (lo.hasMoreLines()) { + if (lo.getCurrentLine().startsWith("Heap oop size = ")) { + String[] oopSizeLine = lo.getCurrentLine().split("\\s+"); + oopSize = Integer.parseInt(oopSizeLine[4]); + Asserts.assertTrue(oopSize == 4 || oopSize == 8); + } + if (lo.getCurrentLine().startsWith("Layout of class")) { + ClassLayout cl = ClassLayout.parseClassLayout(lo); + layouts.add(cl); + } else { + lo.moveToNextLine(); // skipping line + } + } + Asserts.assertTrue(oopSize != 0); + } catch (Throwable t) { + System.out.println("Error while processing line: " + lo.getCurrentLine()); + throw t; + } + } + } diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/NullMarkersTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/NullMarkersTest.java new file mode 100644 index 00000000000..28c0cdb5764 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/NullMarkersTest.java @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /* + * @test id=NullMarker32 + * @ignore + * @requires vm.bits == 32 + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java NullMarkersTest.java + * @run main/othervm NullMarkersTest 0 + */ + +/* + * @test id=NullMarker64CompressedOops + * @ignore + * @requires vm.bits == 64 + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java NullMarkersTest.java + * @run main/othervm NullMarkersTest 1 + */ + +/* + * @test id=NullMarker64NoCompressedOops + * @ignore + * @requires vm.bits == 64 + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java NullMarkersTest.java + * @run main/othervm NullMarkersTest 2 + */ + +/* + * @test id=NullMarker64NoCompressedOopsNoCompressedKlassPointers + * @ignore + * @requires vm.bits == 64 + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java NullMarkersTest.java + * @run main/othervm NullMarkersTest 3 + */ + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import jdk.test.lib.Asserts; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class NullMarkersTest { + + + static class TestRunner { + public static void main(String[] args) throws Exception { + Class testClass = Class.forName("NullMarkersTest"); + Asserts.assertNotNull(testClass); + Method[] testMethods = testClass.getMethods(); + for (Method test : testMethods) { + if (test.getName().startsWith("test_")) { + Asserts.assertTrue(Modifier.isStatic(test.getModifiers())); + Asserts.assertTrue(test.getReturnType().equals(Void.TYPE)); + System.out.println("Running " + test.getName()); + test.invoke(null); + } + } + } + } + + static value class Value0 { + int i = 0; + } + + static class Container0 { + Value0 val; + } + + // Simple test with a single nullable flattenable field + static public void test_0() { + Container0 c = new Container0(); + } + + static public void check_0(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("NullMarkersTest$Container0"); + FieldLayoutAnalyzer.FieldBlock f = cl.getFieldFromName("val", false); + Asserts.assertTrue(f.isFlat()); + Asserts.assertTrue(f.hasNullMarker()); + } + + static value class Value1 { + short s = 0; + } + + static class Container1 { + Value1 val0; + Value1 val1; + @Strict + @NullRestricted + Value1 val2 = new Value1(); + } + + static public void test_1() { + Container1 c = new Container1(); + } + + static public void check_1(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("NullMarkersTest$Container1"); + FieldLayoutAnalyzer.FieldBlock f0 = cl.getFieldFromName("val0", false); + Asserts.assertTrue(f0.isFlat()); + Asserts.assertTrue(f0.hasNullMarker()); + FieldLayoutAnalyzer.FieldBlock f1 = cl.getFieldFromName("val1", false); + Asserts.assertTrue(f1.isFlat()); + Asserts.assertTrue(f1.hasNullMarker()); + FieldLayoutAnalyzer.FieldBlock f2 = cl.getFieldFromName("val2", false); + Asserts.assertTrue(f2.isFlat()); + Asserts.assertFalse(f2.hasNullMarker()); + } + + static value class Value2 { + long l = 0; + } + + static class Container2a { + Value2 vala; + } + + static class Container2b extends Container2a { + Value2 valb; + } + + static public void test_2() { + Container2b c = new Container2b(); + } + + static public void check_2(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cla = fla.getClassLayoutFromName("NullMarkersTest$Container2a"); + FieldLayoutAnalyzer.FieldBlock fa = cla.getFieldFromName("vala", false); + Asserts.assertTrue(fa.isFlat()); + Asserts.assertTrue(fa.hasNullMarker()); + FieldLayoutAnalyzer.ClassLayout clb = fla.getClassLayoutFromName("NullMarkersTest$Container2b"); + FieldLayoutAnalyzer.FieldBlock fb = clb.getFieldFromName("valb", false); + Asserts.assertTrue(fb.isFlat()); + Asserts.assertTrue(fb.hasNullMarker()); + } + + static value class Value3 { + double d = 0.0d; + } + + static class Container3a { + @Strict + @NullRestricted + Value3 val0 = new Value3(); + Value3 val1; + } + + static class Container3b extends Container3a { + Value3 val2; + @Strict + @NullRestricted + Value3 val3 = new Value3(); + } + + static class Container3c extends Container3b { + Value3 val4; + Value3 val5; + } + + static public void test_3() { + Container3c c = new Container3c(); + } + + static public void check_3(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cla = fla.getClassLayoutFromName("NullMarkersTest$Container3a"); + FieldLayoutAnalyzer.FieldBlock f0 = cla.getFieldFromName("val0", false); + Asserts.assertTrue(f0.isFlat()); + Asserts.assertFalse(f0.hasNullMarker()); + FieldLayoutAnalyzer.FieldBlock f1 = cla.getFieldFromName("val1", false); + Asserts.assertTrue(f1.isFlat()); + Asserts.assertTrue(f1.hasNullMarker()); + FieldLayoutAnalyzer.ClassLayout clb = fla.getClassLayoutFromName("NullMarkersTest$Container3b"); + FieldLayoutAnalyzer.FieldBlock f2 = clb.getFieldFromName("val2", false); + Asserts.assertTrue(f2.isFlat()); + Asserts.assertTrue(f2.hasNullMarker()); + FieldLayoutAnalyzer.FieldBlock f3 = clb.getFieldFromName("val3", false); + Asserts.assertTrue(f3.isFlat()); + Asserts.assertFalse(f3.hasNullMarker()); + FieldLayoutAnalyzer.ClassLayout clc = fla.getClassLayoutFromName("NullMarkersTest$Container3c"); + FieldLayoutAnalyzer.FieldBlock f4 = clc.getFieldFromName("val4", false); + Asserts.assertTrue(f4.isFlat()); + Asserts.assertTrue(f4.hasNullMarker()); + FieldLayoutAnalyzer.FieldBlock f5 = clc.getFieldFromName("val5", false); + Asserts.assertTrue(f5.isFlat()); + Asserts.assertTrue(f5.hasNullMarker()); + } + + @LooselyConsistentValue + static value class Value4 { + int i = 0; + byte b = 0; + } + + static class Container4 { + Value4 val0; + Value4 val1; + } + + static public void test_4() { + Container4 c = new Container4(); + } + + static void check_4(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("NullMarkersTest$Container4"); + FieldLayoutAnalyzer.FieldBlock f0 = cl.getFieldFromName("val0", false); + Asserts.assertTrue(f0.isFlat()); + Asserts.assertTrue(f0.hasNullMarker()); + FieldLayoutAnalyzer.FieldBlock f1 = cl.getFieldFromName("val1", false); + Asserts.assertTrue(f1.isFlat()); + Asserts.assertTrue(f1.hasNullMarker()); + } + + @LooselyConsistentValue + static value class Value5a { + short s = 0; + byte b = 0; + } + + @LooselyConsistentValue + static value class Value5b { + @Strict + @NullRestricted + Value5a val0 = new Value5a(); + @Strict + @NullRestricted + Value5a val1 = new Value5a(); + } + + static class Container5 { + Value5a vala; + Value5b valb0; + Value5b valb1; + } + + static public void test_5() { + Container5 c = new Container5(); + } + + static void check_5(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("NullMarkersTest$Container5"); + FieldLayoutAnalyzer.FieldBlock fa = cl.getFieldFromName("vala", false); + Asserts.assertTrue(fa.isFlat()); + Asserts.assertTrue(fa.hasNullMarker()); + FieldLayoutAnalyzer.FieldBlock fb0 = cl.getFieldFromName("valb0", false); + Asserts.assertTrue(fb0.isFlat()); + Asserts.assertTrue(fb0.hasNullMarker()); + FieldLayoutAnalyzer.FieldBlock fb1 = cl.getFieldFromName("valb1", false); + Asserts.assertTrue(fb1.isFlat()); + Asserts.assertTrue(fb1.hasNullMarker()); + } + + static ProcessBuilder exec(String compressedOopsArg, String compressedKlassPointersArg, String... args) throws Exception { + List argsList = new ArrayList<>(); + Collections.addAll(argsList, "--enable-preview"); + Collections.addAll(argsList, "-XX:+UnlockDiagnosticVMOptions"); + Collections.addAll(argsList, "-XX:+PrintFieldLayout"); + Collections.addAll(argsList, "-Xshare:off"); + if (compressedOopsArg != null) { + Collections.addAll(argsList, compressedOopsArg); + } + if (compressedKlassPointersArg != null) { + Collections.addAll(argsList, compressedKlassPointersArg); + } + Collections.addAll(argsList, "-Xmx256m"); + Collections.addAll(argsList, "-XX:+UseNullableValueFlattening"); + Collections.addAll(argsList, "-cp", System.getProperty("java.class.path") + System.getProperty("path.separator") + "."); + Collections.addAll(argsList, args); + return ProcessTools.createTestJavaProcessBuilder(argsList); + } + + public static void main(String[] args) throws Exception { + String compressedOopsArg; + String compressedKlassPointersArg; + + switch(args[0]) { + case "0": compressedOopsArg = null; + compressedKlassPointersArg = null; + break; + case "1": compressedOopsArg = "-XX:+UseCompressedOops"; + compressedKlassPointersArg = "-XX:+UseCompressedClassPointers"; + break; + case "2": compressedOopsArg = "-XX:-UseCompressedOops"; + compressedKlassPointersArg = "-XX:+UseCompressedClassPointers"; + break; + case "3": compressedOopsArg = "-XX:-UseCompressedOops"; + compressedKlassPointersArg = "-XX:-UseCompressedClassPointers"; + break; + default: throw new RuntimeException("Unrecognized configuration"); + } + + // Generate test classes + NullMarkersTest fat = new NullMarkersTest(); + + // Execute the test runner in charge of loading all test classes + ProcessBuilder pb = exec(compressedOopsArg, compressedKlassPointersArg, "NullMarkersTest$TestRunner"); + OutputAnalyzer out = new OutputAnalyzer(pb.start()); + + if (out.getExitValue() != 0) { + System.out.print(out.getOutput()); + } + Asserts.assertEquals(out.getExitValue(), 0, "Something went wrong while running the tests"); + + // Get and parse the test output + FieldLayoutAnalyzer.LogOutput lo = new FieldLayoutAnalyzer.LogOutput(out.asLines()); + FieldLayoutAnalyzer fla = FieldLayoutAnalyzer.createFieldLayoutAnalyzer(lo); + + // Running tests verification method (check that tests produced the right configuration) + Class testClass = NullMarkersTest.class; + Method[] testMethods = testClass.getMethods(); + for (Method test : testMethods) { + if (test.getName().startsWith("check_")) { + Asserts.assertTrue(Modifier.isStatic(test.getModifiers())); + Asserts.assertTrue(test.getReturnType().equals(Void.TYPE)); + test.invoke(null, fla); + } + } + + // Verify that all layouts are correct + fla.check(); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/TestLayoutFlags.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/TestLayoutFlags.java new file mode 100644 index 00000000000..bee417834de --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/TestLayoutFlags.java @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /* + * @test id=TestLayoutFlags_0 + * @library /test/lib + * @requires vm.flagless + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java TestLayoutFlags.java + * @run main/othervm TestLayoutFlags 0 + */ + + /* + * @test id=TestLayoutFlags_1 + * @library /test/lib + * @requires vm.flagless + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java TestLayoutFlags.java + * @run main/othervm TestLayoutFlags 1 + */ + + /* @test id=TestLayoutFlags_2 + * @library /test/lib + * @requires vm.flagless + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java TestLayoutFlags.java + * @run main/othervm TestLayoutFlags 2 + */ + + /* @test id=TestLayoutFlags_3 + * @library /test/lib + * @requires vm.flagless + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java TestLayoutFlags.java + * @run main/othervm TestLayoutFlags 3 + */ + +/* @test id=TestLayoutFlags_4 + * @library /test/lib + * @requires vm.flagless + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java TestLayoutFlags.java + * @run main/othervm TestLayoutFlags 4 + */ + +/* @test id=TestLayoutFlags_5 + * @library /test/lib + * @requires vm.flagless + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java TestLayoutFlags.java + * @run main/othervm TestLayoutFlags 5 + */ + +/* @test id=TestLayoutFlags_6 + * @library /test/lib + * @requires vm.flagless + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java TestLayoutFlags.java + * @run main/othervm TestLayoutFlags 6 + */ + +/* @test id=TestLayoutFlags_7 + * @library /test/lib + * @requires vm.flagless + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java TestLayoutFlags.java + * @run main/othervm TestLayoutFlags 7 + */ + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import jdk.test.lib.Asserts; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class TestLayoutFlags { + + static class TestRunner { + public static void main(String[] args) throws Exception { + Class testClass = Class.forName("TestLayoutFlags"); + Asserts.assertNotNull(testClass); + Method[] testMethods = testClass.getMethods(); + for (Method test : testMethods) { + if (test.getName().startsWith("test_")) { + Asserts.assertTrue(Modifier.isStatic(test.getModifiers())); + Asserts.assertTrue(test.getReturnType().equals(Void.TYPE)); + System.out.println("Running " + test.getName()); + test.invoke(null); + } + } + } + } + + @LooselyConsistentValue + static value class Value0 { + byte b0 = 0; + byte b1 = 0; + } + + static class Container0 { + Value0 val0 = new Value0(); + } + + static public void test_0() { + Container0 c = new Container0(); + } + + static public void check_0(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("TestLayoutFlags$Container0"); + FieldLayoutAnalyzer.FieldBlock f0 = cl.getFieldFromName("val0", false); + if (useNullableAtomicFlat) { + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NULLABLE_FLAT, f0.layoutKind()); + } else { + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_FLAT, f0.layoutKind()); + } + } + + static class Container1 { + @Strict + @NullRestricted + volatile Value0 val0 = new Value0(); + } + + static public void test_1() { + Container1 c = new Container1(); + } + + static public void check_1(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("TestLayoutFlags$Container1"); + FieldLayoutAnalyzer.FieldBlock f0 = cl.getFieldFromName("val0", false); + // volatile fields are never flattened + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_FLAT, f0.layoutKind()); + } + + static class Container2 { + @Strict + @NullRestricted + Value0 val0 = new Value0(); + } + + static public void test_2() { + Container2 c = new Container2(); + } + + static public void check_2(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("TestLayoutFlags$Container2"); + FieldLayoutAnalyzer.FieldBlock f0 = cl.getFieldFromName("val0", false); + if (useNonAtomicFlat) { + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_ATOMIC_FLAT, f0.layoutKind()); + } else { + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_FLAT, f0.layoutKind()); + } + } + + static ProcessBuilder exec(String... args) throws Exception { + List argsList = new ArrayList<>(); + Collections.addAll(argsList, "--enable-preview"); + Collections.addAll(argsList, "-Xint"); + Collections.addAll(argsList, "-XX:+UnlockDiagnosticVMOptions"); + Collections.addAll(argsList, "-XX:+PrintFieldLayout"); + Collections.addAll(argsList, "-Xshare:off"); + Collections.addAll(argsList, "-Xmx256m"); + Collections.addAll(argsList, useNonAtomicFlat ? "-XX:+UseNonAtomicValueFlattening" : "-XX:-UseNonAtomicValueFlattening"); + Collections.addAll(argsList, useAtomicFlat ? "-XX:+UseAtomicValueFlattening" : "-XX:-UseAtomicValueFlattening"); + Collections.addAll(argsList, useNullableAtomicFlat ? "-XX:+UseNullableValueFlattening" : "-XX:-UseNullableValueFlattening"); + Collections.addAll(argsList, "-cp", System.getProperty("java.class.path") + System.getProperty("path.separator") + "."); + Collections.addAll(argsList, args); + return ProcessTools.createTestJavaProcessBuilder(argsList); + } + + static boolean useNonAtomicFlat; + static boolean useAtomicFlat; + static boolean useNullableAtomicFlat; + + public static void main(String[] args) throws Exception { + + switch(args[0]) { + case "0": useNonAtomicFlat = false; + useAtomicFlat = false; + useNullableAtomicFlat = false; + break; + case "1": useNonAtomicFlat = false; + useAtomicFlat = true; + useNullableAtomicFlat = true; + break; + case "2": useNonAtomicFlat = false; + useAtomicFlat = false; + useNullableAtomicFlat = true; + break; + case "3": useNonAtomicFlat = false; + useAtomicFlat = true; + useNullableAtomicFlat = false; + break; + case "4": useNonAtomicFlat = true; + useAtomicFlat = false; + useNullableAtomicFlat = false; + break; + case "5": useNonAtomicFlat = true; + useAtomicFlat = true; + useNullableAtomicFlat = true; + break; + case "6": useNonAtomicFlat = true; + useAtomicFlat = false; + useNullableAtomicFlat = true; + break; + case "7": useNonAtomicFlat = true; + useAtomicFlat = true; + useNullableAtomicFlat = false; + break; + default: throw new RuntimeException("Unrecognized configuration"); + } + + // Generate test classes + TestLayoutFlags vct = new TestLayoutFlags(); + + // Execute the test runner in charge of loading all test classes + ProcessBuilder pb = exec("TestLayoutFlags$TestRunner"); + OutputAnalyzer out = new OutputAnalyzer(pb.start()); + + if (out.getExitValue() != 0) { + System.out.print(out.getOutput()); + } + Asserts.assertEquals(out.getExitValue(), 0, "Something went wrong while running the tests"); + + // To help during test development + System.out.print(out.getOutput()); + + // Get and parse the test output + FieldLayoutAnalyzer.LogOutput lo = new FieldLayoutAnalyzer.LogOutput(out.asLines()); + FieldLayoutAnalyzer fla = FieldLayoutAnalyzer.createFieldLayoutAnalyzer(lo); + + // Running tests verification method (check that tests produced the right configuration) + Class testClass = TestLayoutFlags.class; + Method[] testMethods = testClass.getMethods(); + for (Method test : testMethods) { + if (test.getName().startsWith("check_")) { + Asserts.assertTrue(Modifier.isStatic(test.getModifiers())); + Asserts.assertTrue(test.getReturnType().equals(Void.TYPE)); + test.invoke(null, fla); + } + } + + // Verify that all layouts are correct + fla.check(); + } +} \ No newline at end of file diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/ValueCompositionTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/ValueCompositionTest.java new file mode 100644 index 00000000000..7a9d9d79bae --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/ValueCompositionTest.java @@ -0,0 +1,387 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /* + * @test id=ValueCompositionTest_no_atomic_flat_and_no_nullable_flat + * @library /test/lib + * @requires vm.flagless + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java ValueCompositionTest.java + * @run main/othervm ValueCompositionTest 0 + */ + + /* + * @test id=ValueCompositionTest_atomic_flat_and_nullable_flat + * @library /test/lib + * @requires vm.flagless + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java ValueCompositionTest.java + * @run main/othervm ValueCompositionTest 1 + */ + + /* @test id=ValueCompositionTest_no_atomic_flat_and_nullable_flat + * @library /test/lib + * @requires vm.flagless + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java ValueCompositionTest.java + * @run main/othervm ValueCompositionTest 2 + */ + + /* @test id=ValueCompositionTest_atomic_flat_and_no_nullable_flat + * @library /test/lib + * @requires vm.flagless + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java ValueCompositionTest.java + * @run main/othervm ValueCompositionTest 3 + */ + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import jdk.test.lib.Asserts; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class ValueCompositionTest { + + static class TestRunner { + public static void main(String[] args) throws Exception { + Class testClass = Class.forName("ValueCompositionTest"); + Asserts.assertNotNull(testClass); + Method[] testMethods = testClass.getMethods(); + for (Method test : testMethods) { + if (test.getName().startsWith("test_")) { + Asserts.assertTrue(Modifier.isStatic(test.getModifiers())); + Asserts.assertTrue(test.getReturnType().equals(Void.TYPE)); + System.out.println("Running " + test.getName()); + test.invoke(null); + } + } + } + } + + static value class Value0 { + byte b0 = 0; + byte b1 = 0; + } + + static class Container0 { + @Strict + @NullRestricted + Value0 val0 = new Value0(); + Value0 val1 = new Value0(); + } + + static public void test_0() { + Container0 c = new Container0(); + } + + static public void check_0(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("ValueCompositionTest$Container0"); + FieldLayoutAnalyzer.FieldBlock f0 = cl.getFieldFromName("val0", false); + if (useAtomicFlat) { + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.ATOMIC_FLAT, f0.layoutKind()); + } else { + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_FLAT, f0.layoutKind()); + } + FieldLayoutAnalyzer.FieldBlock f1 = cl.getFieldFromName("val1", false); + if (useNullableAtomicFlat) { + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NULLABLE_FLAT, f1.layoutKind()); + } else { + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_FLAT, f1.layoutKind()); + } + } + + static value class Container1 { + @Strict + @NullRestricted + Value0 val0 = new Value0(); + Value0 val1 = new Value0(); + } + + static public void test_1() { + Container1 c = new Container1(); + } + + static public void check_1(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("ValueCompositionTest$Container1"); + FieldLayoutAnalyzer.FieldBlock f = cl.getFieldFromName("val0", false); + if (useAtomicFlat) { + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.ATOMIC_FLAT, f.layoutKind()); + } else { + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_FLAT, f.layoutKind()); + } + FieldLayoutAnalyzer.FieldBlock f1 = cl.getFieldFromName("val1", false); + if (useNullableAtomicFlat) { + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NULLABLE_FLAT, f1.layoutKind()); + } else { + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_FLAT, f1.layoutKind()); + } + } + + @LooselyConsistentValue + static value class Container2 { + @Strict + @NullRestricted + Value0 val0 = new Value0(); + Value0 val1 = new Value0(); + } + + static public void test_2() { + Container2 c = new Container2(); + } + + // An atomic value should not be flattened in a non-atomic value + static public void check_2(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("ValueCompositionTest$Container2"); + FieldLayoutAnalyzer.FieldBlock f = cl.getFieldFromName("val0", false); + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_FLAT, f.layoutKind()); + FieldLayoutAnalyzer.FieldBlock f1 = cl.getFieldFromName("val1", false); + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_FLAT, f1.layoutKind()); + } + + @LooselyConsistentValue + static value class Value1 { + byte b0 = 0; + byte b1 = 0; + } + + static class Container3 { + @Strict + @NullRestricted + Value1 val0 = new Value1(); + Value1 val1 = new Value1(); + } + + static public void test_3() { + Container3 c = new Container3(); + } + + static public void check_3(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("ValueCompositionTest$Container3"); + FieldLayoutAnalyzer.FieldBlock f0 = cl.getFieldFromName("val0", false); + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_ATOMIC_FLAT, f0.layoutKind()); + FieldLayoutAnalyzer.FieldBlock f1 = cl.getFieldFromName("val1", false); + if (useNullableAtomicFlat) { + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NULLABLE_FLAT, f1.layoutKind()); + } else { + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_FLAT, f1.layoutKind()); + } + } + + static value class Container4 { + @Strict + @NullRestricted + Value1 val0 = new Value1(); + Value1 val1 = new Value1(); + } + + static public void test_4() { + Container4 c = new Container4(); + } + + static public void check_4(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("ValueCompositionTest$Container4"); + FieldLayoutAnalyzer.FieldBlock f0 = cl.getFieldFromName("val0", false); + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_ATOMIC_FLAT, f0.layoutKind()); + FieldLayoutAnalyzer.FieldBlock f1 = cl.getFieldFromName("val1", false); + if (useNullableAtomicFlat) { + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NULLABLE_FLAT, f1.layoutKind()); + } else { + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_FLAT, f1.layoutKind()); + } + } + + @LooselyConsistentValue + static value class Container5 { + @Strict + @NullRestricted + Value1 val0 = new Value1(); + Value1 val1 = new Value1(); + } + + static public void test_5() { + Container5 c = new Container5(); + } + + static public void check_5(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("ValueCompositionTest$Container5"); + FieldLayoutAnalyzer.FieldBlock f0 = cl.getFieldFromName("val0", false); + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_ATOMIC_FLAT, f0.layoutKind()); + FieldLayoutAnalyzer.FieldBlock f1 = cl.getFieldFromName("val1", false); + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_FLAT, f1.layoutKind()); + } + + static value class Value2 { + byte b0 = 0; + } + + static class Container6 { + @Strict + @NullRestricted + Value2 val0 = new Value2(); + Value2 val1 = new Value2(); + } + + static public void test_6() { + Container6 c = new Container6(); + } + + static public void check_6(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("ValueCompositionTest$Container6"); + FieldLayoutAnalyzer.FieldBlock f0 = cl.getFieldFromName("val0", false); + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_ATOMIC_FLAT, f0.layoutKind()); + FieldLayoutAnalyzer.FieldBlock f1 = cl.getFieldFromName("val1", false); + if (useNullableAtomicFlat) { + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NULLABLE_FLAT, f1.layoutKind()); + } else { + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_FLAT, f1.layoutKind()); + } + } + + static value class Container7 { + @Strict + @NullRestricted + Value2 val0 = new Value2(); + Value2 val1 = new Value2(); + } + + static public void test_7() { + Container7 c = new Container7(); + } + + static public void check_7(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("ValueCompositionTest$Container7"); + FieldLayoutAnalyzer.FieldBlock f0 = cl.getFieldFromName("val0", false); + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_ATOMIC_FLAT, f0.layoutKind()); + FieldLayoutAnalyzer.FieldBlock f1 = cl.getFieldFromName("val1", false); + if (useNullableAtomicFlat) { + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NULLABLE_FLAT, f1.layoutKind()); + } else { + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_FLAT, f1.layoutKind()); + } + } + + @LooselyConsistentValue + static value class Container8 { + @Strict + @NullRestricted + Value2 val0 = new Value2(); + Value2 val1 = new Value2(); + } + + static public void test_8() { + Container8 c = new Container8(); + } + + static public void check_8(FieldLayoutAnalyzer fla) { + FieldLayoutAnalyzer.ClassLayout cl = fla.getClassLayoutFromName("ValueCompositionTest$Container8"); + FieldLayoutAnalyzer.FieldBlock f0 = cl.getFieldFromName("val0", false); + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_ATOMIC_FLAT, f0.layoutKind()); + FieldLayoutAnalyzer.FieldBlock f1 = cl.getFieldFromName("val1", false); + Asserts.assertEquals(FieldLayoutAnalyzer.LayoutKind.NON_FLAT, f1.layoutKind()); + } + + + static ProcessBuilder exec(String... args) throws Exception { + List argsList = new ArrayList<>(); + Collections.addAll(argsList, "--enable-preview"); + Collections.addAll(argsList, "-Xint"); + Collections.addAll(argsList, "-XX:+UnlockDiagnosticVMOptions"); + Collections.addAll(argsList, "-XX:+PrintFieldLayout"); + Collections.addAll(argsList, "-Xshare:off"); + Collections.addAll(argsList, "-Xmx256m"); + Collections.addAll(argsList, useAtomicFlat ? "-XX:+UseAtomicValueFlattening" : "-XX:-UseAtomicValueFlattening"); + Collections.addAll(argsList, useNullableAtomicFlat ? "-XX:+UseNullableValueFlattening" : "-XX:-UseNullableValueFlattening"); + Collections.addAll(argsList, "-cp", System.getProperty("java.class.path") + System.getProperty("path.separator") + "."); + Collections.addAll(argsList, args); + return ProcessTools.createTestJavaProcessBuilder(argsList); + } + + static boolean useAtomicFlat; + static boolean useNullableAtomicFlat; + + public static void main(String[] args) throws Exception { + + switch(args[0]) { + case "0": useAtomicFlat = false; + useNullableAtomicFlat = false; + break; + case "1": useAtomicFlat = true; + useNullableAtomicFlat = true; + break; + case "2": useAtomicFlat = false; + useNullableAtomicFlat = true; + break; + case "3": useAtomicFlat = true; + useNullableAtomicFlat = false; + break; + default: throw new RuntimeException("Unrecognized configuration"); + } + + // Generate test classes + ValueCompositionTest vct = new ValueCompositionTest(); + + // Execute the test runner in charge of loading all test classes + ProcessBuilder pb = exec("ValueCompositionTest$TestRunner"); + OutputAnalyzer out = new OutputAnalyzer(pb.start()); + + if (out.getExitValue() != 0) { + System.out.print(out.getOutput()); + } + Asserts.assertEquals(out.getExitValue(), 0, "Something went wrong while running the tests"); + + // To help during test development + System.out.print(out.getOutput()); + + // Get and parse the test output + FieldLayoutAnalyzer.LogOutput lo = new FieldLayoutAnalyzer.LogOutput(out.asLines()); + FieldLayoutAnalyzer fla = FieldLayoutAnalyzer.createFieldLayoutAnalyzer(lo); + + // Running tests verification method (check that tests produced the right configuration) + Class testClass = ValueCompositionTest.class; + Method[] testMethods = testClass.getMethods(); + for (Method test : testMethods) { + if (test.getName().startsWith("check_")) { + Asserts.assertTrue(Modifier.isStatic(test.getModifiers())); + Asserts.assertTrue(test.getReturnType().equals(Void.TYPE)); + test.invoke(null, fla); + } + } + + // Verify that all layouts are correct + fla.check(); + } + +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/ValueFieldInheritanceTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/ValueFieldInheritanceTest.java new file mode 100644 index 00000000000..c3d29e86500 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/field_layout/ValueFieldInheritanceTest.java @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /* + * @test id=32bits + * @requires vm.bits == 32 + * @library /test/lib + * @requires vm.flagless + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java ValueFieldInheritanceTest.java + * @run main/othervm ValueFieldInheritanceTest 0 + */ + +/* + * @test id=64bitsCompressedOops + * @requires vm.bits == 64 + * @library /test/lib + * @requires vm.flagless + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java ValueFieldInheritanceTest.java + * @run main/othervm ValueFieldInheritanceTest 1 + */ + +/* + * @test id=64bitsNoCompressedOops + * @requires vm.bits == 64 + * @library /test/lib + * @requires vm.flagless + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java ValueFieldInheritanceTest.java + * @run main/othervm ValueFieldInheritanceTest 2 + */ + +/* + * @test id=64bitsNoCompressedOopsNoCompressKlassPointers + * @requires vm.bits == 64 + * @library /test/lib + * @requires vm.flagless + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @compile FieldLayoutAnalyzer.java ValueFieldInheritanceTest.java + * @run main/othervm ValueFieldInheritanceTest 3 + */ + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import jdk.test.lib.Asserts; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + + +public class ValueFieldInheritanceTest { + + static abstract value class AbstractNoField { }; + + static abstract value class AbstractOneField { byte b = 0; } + + static abstract value class SubAbstractOne extends AbstractNoField { } + + static abstract value class SubAbstractTwo extends AbstractOneField { } + + static abstract value class SubAbstractThree extends AbstractNoField { int i = 0; } + + static abstract value class SubAbstractFour extends AbstractOneField { int i = 0; } + + static class Identity0 extends AbstractNoField { } + + static class Identity1 extends AbstractNoField { short s; } + + static class Identity2 extends SubAbstractOne { } + + static class Identity3 extends SubAbstractOne { char c; } + + static class Identity4 extends SubAbstractTwo { } + + static class Identity5 extends SubAbstractTwo { int i; } + + static class Identity6 extends SubAbstractThree { } + + static class Identity7 extends SubAbstractThree { int i; } + + static class Identity8 extends SubAbstractFour { } + + static class Identity9 extends SubAbstractFour { int i; } + + static class ConcreteValue0 extends AbstractNoField { } + + static class ConcreteValue1 extends AbstractNoField { short s; } + + static class ConcreteValue2 extends SubAbstractOne { } + + static class ConcreteValue3 extends SubAbstractOne { char c; } + + static class ConcreteValue4 extends SubAbstractTwo { } + + static class ConcreteValue5 extends SubAbstractTwo { int i; } + + static class ConcreteValue6 extends SubAbstractThree { } + + static class ConcreteValue7 extends SubAbstractThree { int i; } + + static class ConcreteValue8 extends SubAbstractFour { } + + static class ConcreteValue9 extends SubAbstractFour { int i; } + + static public void test_0() { + var i0 = new Identity0(); + var i1 = new Identity1(); + var i2 = new Identity2(); + var i3 = new Identity3(); + var i4 = new Identity4(); + var i5 = new Identity5(); + var i6 = new Identity6(); + var i7 = new Identity7(); + var i8 = new Identity8(); + var i9 = new Identity9(); + var c0 = new ConcreteValue0(); + var c1 = new ConcreteValue1(); + var c2 = new ConcreteValue2(); + var c3 = new ConcreteValue3(); + var c4 = new ConcreteValue4(); + var c5 = new ConcreteValue5(); + var c6 = new ConcreteValue6(); + var c7 = new ConcreteValue7(); + var c8 = new ConcreteValue8(); + var c9 = new ConcreteValue9(); + } + + static class TestRunner { + public static void main(String[] args) throws Exception { + Class testClass = Class.forName("ValueFieldInheritanceTest"); + Asserts.assertNotNull(testClass); + Method[] testMethods = testClass.getMethods(); + for (Method test : testMethods) { + if (test.getName().startsWith("test_")) { + Asserts.assertTrue(Modifier.isStatic(test.getModifiers())); + Asserts.assertTrue(test.getReturnType().equals(Void.TYPE)); + System.out.println("Running " + test.getName()); + test.invoke(null); + } + } + } + } + + static ProcessBuilder exec(String compressedOopsArg, String compressedKlassPointersArg, String... args) throws Exception { + List argsList = new ArrayList<>(); + Collections.addAll(argsList, "--enable-preview"); + Collections.addAll(argsList, "-XX:+UnlockDiagnosticVMOptions"); + Collections.addAll(argsList, "-XX:+PrintFieldLayout"); + Collections.addAll(argsList, "-Xshare:off"); + if (compressedOopsArg != null) { + Collections.addAll(argsList, compressedOopsArg); + } + if (compressedKlassPointersArg != null) { + Collections.addAll(argsList, compressedKlassPointersArg); + } + Collections.addAll(argsList, "-Xmx256m"); + Collections.addAll(argsList, "-cp", System.getProperty("java.class.path") + System.getProperty("path.separator") + "."); + Collections.addAll(argsList, args); + return ProcessTools.createTestJavaProcessBuilder(argsList); + } + + public static void main(String[] args) throws Exception { + String compressedOopsArg; + String compressedKlassPointersArg; + + switch(args[0]) { + case "0": compressedOopsArg = null; + compressedKlassPointersArg = null; + break; + case "1": compressedOopsArg = "-XX:+UseCompressedOops"; + compressedKlassPointersArg = "-XX:+UseCompressedClassPointers"; + break; + case "2": compressedOopsArg = "-XX:-UseCompressedOops"; + compressedKlassPointersArg = "-XX:+UseCompressedClassPointers"; + break; + case "3": compressedOopsArg = "-XX:-UseCompressedOops"; + compressedKlassPointersArg = "-XX:-UseCompressedClassPointers"; + break; + default: throw new RuntimeException("Unrecognized configuration"); + } + + // Generate test classes + // NullMarkersTest fat = new NullMarkersTest(); + + // Execute the test runner in charge of loading all test classes + ProcessBuilder pb = exec(compressedOopsArg, compressedKlassPointersArg, "ValueFieldInheritanceTest$TestRunner"); + OutputAnalyzer out = new OutputAnalyzer(pb.start()); + + if (out.getExitValue() != 0) { + System.out.print(out.getOutput()); + } + Asserts.assertEquals(out.getExitValue(), 0, "Something went wrong while running the tests"); + + // Get and parse the test output + FieldLayoutAnalyzer.LogOutput lo = new FieldLayoutAnalyzer.LogOutput(out.asLines()); + FieldLayoutAnalyzer fla = FieldLayoutAnalyzer.createFieldLayoutAnalyzer(lo); + + // Running tests verification method (check that tests produced the right configuration) + Class testClass = ValueFieldInheritanceTest.class; + Method[] testMethods = testClass.getMethods(); + for (Method test : testMethods) { + if (test.getName().startsWith("check_")) { + Asserts.assertTrue(Modifier.isStatic(test.getModifiers())); + Asserts.assertTrue(test.getReturnType().equals(Void.TYPE)); + test.invoke(null, fla); + } + } + + // Verify that all layouts are correct + fla.check(); + } + +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/libInlineWithJni.c b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/libInlineWithJni.c new file mode 100644 index 00000000000..3e6655bdbbe --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/libInlineWithJni.c @@ -0,0 +1,74 @@ +/* + * 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 + * 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 + +JNIEXPORT void JNICALL +Java_runtime_valhalla_inlinetypes_InlineWithJni_doJniMonitorEnter(JNIEnv *env, jobject obj) { + int ret = (*env)->MonitorEnter(env, obj); + jclass class = (*env)->GetObjectClass(env, obj); + jfieldID fieldId = (*env)->GetStaticFieldID(env, class, "returnValue", "I"); + (*env)->SetStaticIntField(env, class, fieldId, ret); +} + +JNIEXPORT void JNICALL +Java_runtime_valhalla_inlinetypes_InlineWithJni_doJniMonitorExit(JNIEnv *env, jobject obj) { + (*env)->MonitorExit(env, obj); +} + +JNIEXPORT jobject JNICALL Java_runtime_valhalla_inlinetypes_InlineWithJni_readInstanceField(JNIEnv *env, + jclass k, jobject obj, jstring name, jstring signature) { + jclass class = (*env)->GetObjectClass(env, obj); + jboolean copy; + const char* name_string = (*env)->GetStringUTFChars(env, name, ©); + const char *signature_string = (*env)->GetStringUTFChars(env, signature, ©); + jfieldID fieldId = (*env)->GetFieldID(env, class, name_string, signature_string); + jobject ret = (*env)->GetObjectField(env, obj, fieldId); + (*env)->ReleaseStringUTFChars(env, name, name_string); + (*env)->ReleaseStringUTFChars(env, signature, signature_string); + return ret; +} + +JNIEXPORT void JNICALL Java_runtime_valhalla_inlinetypes_InlineWithJni_writeInstanceField(JNIEnv *env, + jclass k, jobject obj, jstring name, jstring signature, jobject value) +{ + jclass class = (*env)->GetObjectClass(env, obj); + jboolean copy; + const char *name_string = (*env)->GetStringUTFChars(env, name, ©); + const char *signature_string = (*env)->GetStringUTFChars(env, signature, ©); + jfieldID fieldId = (*env)->GetFieldID(env, class, name_string, signature_string); + (*env)->SetObjectField(env, obj, fieldId, value); + (*env)->ReleaseStringUTFChars(env, name, name_string); + (*env)->ReleaseStringUTFChars(env, signature, signature_string); + return; +} + +JNIEXPORT jobject JNICALL Java_runtime_valhalla_inlinetypes_InlineWithJni_readArrayElement(JNIEnv *env, + jclass k, jarray array, int index) { + return (*env)->GetObjectArrayElement(env, array, index); +} + +JNIEXPORT void JNICALL Java_runtime_valhalla_inlinetypes_InlineWithJni_writeArrayElement(JNIEnv *env, + jclass k, jarray array, int index, jobject value) { + (*env)->SetObjectArrayElement(env, array, index, value); +} \ No newline at end of file diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/libJNIIsSameObject.c b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/libJNIIsSameObject.c new file mode 100644 index 00000000000..50215fad6a4 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/libJNIIsSameObject.c @@ -0,0 +1,32 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include +#include +#include + +JNIEXPORT jboolean JNICALL +Java_TestJNIIsSameObject_isSameObject(JNIEnv* env, jclass klass, jobject obj0, jobject obj1) { + return (*env)->IsSameObject(env, obj0, obj1); +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/BadChild.jasm b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/BadChild.jasm new file mode 100644 index 00000000000..41b8a365423 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/BadChild.jasm @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* +class BadChild extends Parent { + @Strict int x; + @Strict int y; + + BadChild() { + y = 1; + super(); // FAIL: Strict field x must be set before this call + x = 1; + } +} +*/ + +identity class BadChild extends Parent version 70:65535 +{ + @-jdk/internal/vm/annotation/Strict { } + strict Field x:I; + @-jdk/internal/vm/annotation/Strict { } + strict Field y:I; + + Method "":"()V" + stack 4 locals 1 + { + aload_0; + aload_0; + iconst_1; + dup_x1; + putfield Field y:"I"; + aload_0; + invokespecial Method Parent."":"()V"; // FAIL: Strict field x must be set before this call + putfield Field x:"I"; + return; + } + + Method get_x:"()I" + stack 1 locals 1 + { + aload_0; + getfield Field x:"I"; + ireturn; + } + + Method get_y:"()I" + stack 1 locals 1 + { + aload_0; + getfield Field y:"I"; + ireturn; + } +} // end Class BadChild diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/BadChild1.jasm b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/BadChild1.jasm new file mode 100644 index 00000000000..315995b08ee --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/BadChild1.jasm @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* +class BadChild extends Parent { + @Strict int x; + @Strict int y; + + BadChild() { + x = 1; + super(); // FAIL: Strict field y must be set before this call + y = 1; + } +} +*/ + +identity class BadChild1 extends Parent version 70:65535 +{ + @-jdk/internal/vm/annotation/Strict { } + strict Field x:I; + @-jdk/internal/vm/annotation/Strict { } + strict Field y:I; + + Method "":"()V" + stack 4 locals 1 + { + aload_0; + aload_0; + iconst_1; + dup_x1; + putfield Field x:"I"; + aload_0; + invokespecial Method Parent."":"()V"; // FAIL: Strict field y must be set before this call + putfield Field y:"I"; + return; + } + + Method get_x:"()I" + stack 1 locals 1 + { + aload_0; + getfield Field x:"I"; + ireturn; + } + + Method get_y:"()I" + stack 1 locals 1 + { + aload_0; + getfield Field y:"I"; + ireturn; + } +} // end Class BadChild1 diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/Bnoinit_BAD.jasm b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/Bnoinit_BAD.jasm new file mode 100644 index 00000000000..05c0bbda719 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/Bnoinit_BAD.jasm @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* +class Bnoinit_BAD { + @Strict static String F1__STRICT; + @Strict static int F2__STRICT; + static {} // Strict statics not initialized +} +*/ + +identity class Bnoinit_BAD version 70:65535 +{ + @-jdk/internal/vm/annotation/Strict { } + strict static Field F1__STRICT:"Ljava/lang/String;"; + @-jdk/internal/vm/annotation/Strict { } + strict static Field F2__STRICT:I; + + public Method "":"()V" + stack 1 locals 1 + { + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } + + static Method "":"()V" + stack 0 locals 0 + { + return; + } + +} // end Class Bnoinit_BAD diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/Brbefore_BAD.jasm b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/Brbefore_BAD.jasm new file mode 100644 index 00000000000..aa1157b106f --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/Brbefore_BAD.jasm @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* +class Brbefore_BAD { + @Strict static String F1__STRICT; + @Strict static int F2__STRICT; + static { + int x = F2__STRICT; // Read before write + F1__STRICT = "hello"; + F2__STRICT = 42; + } +} +*/ + +identity class Brbefore_BAD version 70:65535 +{ + @-jdk/internal/vm/annotation/Strict { } + strict static Field F1__STRICT:"Ljava/lang/String;"; + @-jdk/internal/vm/annotation/Strict { } + strict static Field F2__STRICT:I; + + public Method "":"()V" + stack 1 locals 1 + { + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } + + static Method "":"()V" + stack 1 locals 1 + { + getstatic Field F2__STRICT:"I"; + istore_0; + ldc String "hello"; + putstatic Field F1__STRICT:"Ljava/lang/String;"; + bipush 42; + putstatic Field F2__STRICT:"I"; + return; + } +} // end Class Brbefore_BAD diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/ControlFlowChildBad.jasm b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/ControlFlowChildBad.jasm new file mode 100644 index 00000000000..e007f587507 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/ControlFlowChildBad.jasm @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* +class ControlFlowChild extends Parent { + @Strict int x; + @Strict int y; + + ControlFlowChild(boolean a, boolean b) { + if (a) { // FAIL: Strict field x never set on this path + if (b) { + y = 1; + } else { + y = 2; + } + } else { + x = y = 3; + } + super(); // + } +} +*/ + +identity class ControlFlowChildBad extends Parent version 70:65535 +{ + @-jdk/internal/vm/annotation/Strict { } + strict Field x:I; + @-jdk/internal/vm/annotation/Strict { } + strict Field y:I; + + Method "":"(ZZ)V" + stack 4 locals 3 + { + iload_1; + ifeq L29; + aload_0; + iconst_1; + putfield Field y:"I"; + iload_2; + ifeq L21; + aload_0; + iconst_1; + putfield Field y:"I"; + goto L39; + L21: stack_frame_type early_larval; + unset_fields y:"I"; + frame_type same; + aload_0; + iconst_2; + putfield Field y:"I"; + goto L39; // FAIL: Strict field x never set on this path + L29: stack_frame_type early_larval; + unset_fields x:"I", + y:"I"; + frame_type same; + aload_0; + aload_0; + iconst_3; + dup_x1; + putfield Field y:"I"; + putfield Field x:"I"; + L39: stack_frame_type early_larval; + unset_fields; + frame_type same; + aload_0; + invokespecial Method Parent."":"()V"; + return; + } + + Method get_x:"()I" + stack 1 locals 1 + { + aload_0; + getfield Field x:"I"; + ireturn; + } + + Method get_y:"()I" + stack 1 locals 1 + { + aload_0; + getfield Field y:"I"; + ireturn; + } +} // end Class ControlFlowChildBad diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/Creflbefore_BAD.jasm b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/Creflbefore_BAD.jasm new file mode 100644 index 00000000000..4240d243334 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/Creflbefore_BAD.jasm @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* +class Creflbefore_BAD { + @Strict static String F1__STRICT; + @Strict static int F2__STRICT; + static { + Field FIELD_F1 = findField(Creflbefore_BAD.class, "F1__STRICT"); + Field FIELD_F2 = findField(Creflbefore_BAD.class, "F2__STRICT"); + + int x = (int) getstaticReflective(FIELD_F2); // Read before write + System.out.println("Early read of F2=" + x); + + putstaticReflective(FIELD_F1, "hello"); + putstaticReflective(FIELD_F2, 42); + } +} +*/ +identity class Creflbefore_BAD version 70:65535 +{ + @-jdk/internal/vm/annotation/Strict { } + strict static Field F1__STRICT:"Ljava/lang/String;"; + @-jdk/internal/vm/annotation/Strict { } + strict static Field F2__STRICT:I; + + Method "":"(LStrictStaticFieldsTest;)V" + stack 2 locals 2 + 0: #{ #0 final mandated } + { + aload_1; + dup; + invokestatic Method java/util/Objects.requireNonNull:"(Ljava/lang/Object;)Ljava/lang/Object;"; + pop; + pop; + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } + + static Method "":"()V" + stack 2 locals 3 + { + ldc class Creflbefore_BAD; + ldc String "F1__STRICT"; + invokestatic Method StrictStaticFieldsTest.findField:"(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/reflect/Field;"; + astore_0; + ldc class Creflbefore_BAD; + ldc String "F2__STRICT"; + invokestatic Method StrictStaticFieldsTest.findField:"(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/reflect/Field;"; + astore_1; + aload_1; + invokestatic Method StrictStaticFieldsTest.getstaticReflective:"(Ljava/lang/reflect/Field;)Ljava/lang/Object;"; + checkcast class java/lang/Integer; + invokevirtual Method java/lang/Integer.intValue:"()I"; + istore_2; + getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; + iload_2; + invokedynamic InvokeDynamic REF_invokeStatic:Method java/lang/invoke/StringConcatFactory.makeConcatWithConstants: + "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;": + makeConcatWithConstants:"(I)Ljava/lang/String;" { + String "Early read of F2=\u0001" + }; + invokevirtual Method java/io/PrintStream.println:"(Ljava/lang/String;)V"; + aload_0; + ldc String "hello"; + invokestatic Method StrictStaticFieldsTest.putstaticReflective:"(Ljava/lang/reflect/Field;Ljava/lang/Object;)V"; + aload_1; + bipush 42; + invokestatic Method java/lang/Integer.valueOf:"(I)Ljava/lang/Integer;"; + invokestatic Method StrictStaticFieldsTest.putstaticReflective:"(Ljava/lang/reflect/Field;Ljava/lang/Object;)V"; + return; + } + + BootstrapMethod REF_invokeStatic:java/lang/invoke/StringConcatFactory.makeConcatWithConstants: + "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;"; + { + String "(early read of F2=\u0001)" + } +} // end Class Creflbefore_BAD diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/Cwreflective_OK.jasm b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/Cwreflective_OK.jasm new file mode 100644 index 00000000000..9192be87885 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/Cwreflective_OK.jasm @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* +class Cwreflective_OK { + @Strict static String F1__STRICT; + @Strict static int F2__STRICT; + static { + Field FIELD_F1 = findField(Cwreflective_OK.class, "F1__STRICT"); + Field FIELD_F2 = findField(Cwreflective_OK.class, "F2__STRICT"); + + putstaticReflective(FIELD_F1, "hello"); + putstaticReflective(FIELD_F2, 42); + } +} +*/ + +identity class Cwreflective_OK version 70:65535 +{ + @-jdk/internal/vm/annotation/Strict { } + strict static Field F1__STRICT:"Ljava/lang/String;"; + @-jdk/internal/vm/annotation/Strict { } + strict static Field F2__STRICT:I; + + Method "":"(LStrictStaticFieldsTest;)V" + stack 2 locals 2 + 0: #{ #0 final mandated } + { + aload_1; + dup; + invokestatic Method java/util/Objects.requireNonNull:"(Ljava/lang/Object;)Ljava/lang/Object;"; + pop; + pop; + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } + + static Method "":"()V" + stack 2 locals 2 + { + ldc class Cwreflective_OK; + ldc String "F1__STRICT"; + invokestatic Method StrictStaticFieldsTest.findField:"(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/reflect/Field;"; + astore_0; + ldc class Cwreflective_OK; + ldc String "F2__STRICT"; + invokestatic Method StrictStaticFieldsTest.findField:"(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/reflect/Field;"; + astore_1; + aload_0; + ldc String "hello"; + invokestatic Method StrictStaticFieldsTest.putstaticReflective:"(Ljava/lang/reflect/Field;Ljava/lang/Object;)V"; + aload_1; + bipush 42; + invokestatic Method java/lang/Integer.valueOf:"(I)Ljava/lang/Integer;"; + invokestatic Method StrictStaticFieldsTest.putstaticReflective:"(Ljava/lang/reflect/Field;Ljava/lang/Object;)V"; + return; + } +} // end Class Cwreflective_OK diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/EndsInEarlyLarval.jcod b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/EndsInEarlyLarval.jcod new file mode 100644 index 00000000000..38eaa18958b --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/EndsInEarlyLarval.jcod @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* +class EndsInEarlyLarval extends Parent { + @Strict + int x; + @Strict + int y; + + EndsInEarlyLarval(boolean a, boolean b) { + if (a) { + x = 1; + if (b) { + y = 1; + } else { + y = 2; + } + } else { + x = y = 3; + } + super(); + } +} +*/ + +class EndsInEarlyLarval { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Field #2 #3; // #1 + Class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "EndsInEarlyLarval"; // #4 + Utf8 "x"; // #5 + Utf8 "I"; // #6 + Field #2 #8; // #7 + NameAndType #9 #6; // #8 + Utf8 "y"; // #9 + Method #11 #12; // #10 + Class #13; // #11 + NameAndType #14 #15; // #12 + Utf8 "Parent"; // #13 + Utf8 ""; // #14 + Utf8 "()V"; // #15 + Method #2 #17; // #16 + NameAndType #18 #19; // #17 + Utf8 "get_x"; // #18 + Utf8 "()I"; // #19 + Method #2 #21; // #20 + NameAndType #22 #19; // #21 + Utf8 "get_y"; // #22 + Method #11 #24; // #23 + NameAndType #25 #26; // #24 + Utf8 "toString"; // #25 + Utf8 "()Ljava/lang/String;"; // #26 + InvokeDynamic 0s #28; // #27 + NameAndType #29 #30; // #28 + Utf8 "makeConcatWithConstants"; // #29 + Utf8 "(IILjava/lang/String;)Ljava/lang/String;"; // #30 + Utf8 "RuntimeInvisibleAnnotations"; // #31 + Utf8 "Ljdk/internal/vm/annotation/Strict;"; // #32 + Utf8 "(ZZ)V"; // #33 + Utf8 "Code"; // #34 + Utf8 "LineNumberTable"; // #35 + Utf8 "StackMapTable"; // #36 + Utf8 "SourceFile"; // #37 + Utf8 "StrictInstanceFieldsTest.java"; // #38 + Utf8 "BootstrapMethods"; // #39 + String #41; // #40 + Utf8 "x: \u0001\ny: \u0001\n\u0001"; // #41 + MethodHandle 6b #43; // #42 + Method #44 #45; // #43 + Class #46; // #44 + NameAndType #29 #47; // #45 + Utf8 "java/lang/invoke/StringConcatFactory"; // #46 + Utf8 "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;"; // #47 + Utf8 "InnerClasses"; // #48 + Class #50; // #49 + Utf8 "java/lang/invoke/MethodHandles$Lookup"; // #50 + Class #52; // #51 + Utf8 "java/lang/invoke/MethodHandles"; // #52 + Utf8 "Lookup"; // #53 + } + + 0x0020; // access + #2; // this_cpx + #11; // super_cpx + + [] { // Interfaces + } // end of Interfaces + + [] { // Fields + { // field + 0x0800; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#31) { // RuntimeInvisibleAnnotations + [] { // annotations + { // annotation + #32; + [] { // element_value_pairs + } // element_value_pairs + } // annotation + } + } // end of RuntimeInvisibleAnnotations + } // end of Attributes + } + ; + { // field + 0x0800; // access + #9; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#31) { // RuntimeInvisibleAnnotations + [] { // annotations + { // annotation + #32; + [] { // element_value_pairs + } // element_value_pairs + } // annotation + } + } // end of RuntimeInvisibleAnnotations + } // end of Attributes + } + } // end of Fields + + [] { // Methods + { // method + 0x0000; // access + #14; // name_index + #33; // descriptor_index + [] { // Attributes + Attr(#34) { // Code + 4; // max_stack + 3; // max_locals + Bytes[]{ + 0x1B 0x99 0x00 0x1C 0x2A 0x04 0xB5 0x00 0x01 0x1C 0x99 0x00; + 0x0B 0x2A 0x04 0xB5 0x00 0x07 0xA7 0x00 0x15 0x2A 0x05 0xB5; + 0x00 0x07 0xA7 0x00 0x0D 0x2A 0x2A 0x06 0x5A 0xB5 0x00 0x07; + 0xB5 0x00 0x01 0x2A 0xB7 0x00 0x0A 0xB1; + } + [] { // Traps + } // end of Traps + [] { // Attributes + Attr(#35) { // LineNumberTable + [] { // line_number_table + 0 121; + 4 122; + 9 123; + 13 124; + 21 126; + 29 129; + 39 131; + 43 132; + } + } // end of LineNumberTable + ; + Attr(#36) { // StackMapTable + [] { // + 246b, []{#8}, { // early_larval_frame + 21b; // same + }; + 246b, []{#3; #8}, { // early_larval_frame + 7b; // same_frame + }; + 246b, []{}, { // FAIL: early_larval_frame with no base frame + }; + } + } // end of StackMapTable + } // end of Attributes + } // end of Code + } // end of Attributes + } + ; + { // method + 0x0000; // access + #18; // name_index + #19; // descriptor_index + [] { // Attributes + Attr(#34) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2A 0xB4 0x00 0x01 0xAC; + } + [] { // Traps + } // end of Traps + [] { // Attributes + Attr(#35) { // LineNumberTable + [] { // line_number_table + 0 134; + } + } // end of LineNumberTable + } // end of Attributes + } // end of Code + } // end of Attributes + } + ; + { // method + 0x0000; // access + #22; // name_index + #19; // descriptor_index + [] { // Attributes + Attr(#34) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2A 0xB4 0x00 0x07 0xAC; + } + [] { // Traps + } // end of Traps + [] { // Attributes + Attr(#35) { // LineNumberTable + [] { // line_number_table + 0 135; + } + } // end of LineNumberTable + } // end of Attributes + } // end of Code + } // end of Attributes + } + ; + { // method + 0x0001; // access + #25; // name_index + #26; // descriptor_index + [] { // Attributes + Attr(#34) { // Code + 3; // max_stack + 1; // max_locals + Bytes[]{ + 0x2A 0xB6 0x00 0x10 0x2A 0xB6 0x00 0x14 0x2A 0xB7 0x00 0x17; + 0xBA 0x00 0x1B 0x00 0x00 0xB0; + } + [] { // Traps + } // end of Traps + [] { // Attributes + Attr(#35) { // LineNumberTable + [] { // line_number_table + 0 139; + } + } // end of LineNumberTable + } // end of Attributes + } // end of Code + } // end of Attributes + } + } // end of Methods + + [] { // Attributes + Attr(#37) { // SourceFile + #38; + } // end of SourceFile + ; + Attr(#39) { // BootstrapMethods + [] { // bootstrap_methods + { // bootstrap_method + #42; // bootstrap_method_ref + [] { // bootstrap_arguments + #40; + } // bootstrap_arguments + } // bootstrap_method + } + } // end of BootstrapMethods + ; + Attr(#48) { // InnerClasses + [] { // classes + #49 #51 #53 57; + } + } // end of InnerClasses + } // end of Attributes +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/InvalidIndexInEarlyLarval.jcod b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/InvalidIndexInEarlyLarval.jcod new file mode 100644 index 00000000000..65f25a006d1 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/InvalidIndexInEarlyLarval.jcod @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* +class InvalidIndexInEarlyLarval extends Parent { + @Strict + int x; + @Strict + int y; + + InvalidIndexInEarlyLarval(boolean a, boolean b) { + if (a) { + x = 1; + if (b) { + y = 1; + } else { + y = 2; + } + } else { + x = y = 3; + } + super(); + } +} +*/ + +class InvalidIndexInEarlyLarval { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Field #2 #3; // #1 + Class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "InvalidIndexInEarlyLarval"; // #4 + Utf8 "x"; // #5 + Utf8 "I"; // #6 + Field #2 #8; // #7 + NameAndType #9 #6; // #8 + Utf8 "y"; // #9 + Method #11 #12; // #10 + Class #13; // #11 + NameAndType #14 #15; // #12 + Utf8 "Parent"; // #13 + Utf8 ""; // #14 + Utf8 "()V"; // #15 + Method #2 #17; // #16 + NameAndType #18 #19; // #17 + Utf8 "get_x"; // #18 + Utf8 "()I"; // #19 + Method #2 #21; // #20 + NameAndType #22 #19; // #21 + Utf8 "get_y"; // #22 + Method #11 #24; // #23 + NameAndType #25 #26; // #24 + Utf8 "toString"; // #25 + Utf8 "()Ljava/lang/String;"; // #26 + InvokeDynamic 0s #28; // #27 + NameAndType #29 #30; // #28 + Utf8 "makeConcatWithConstants"; // #29 + Utf8 "(IILjava/lang/String;)Ljava/lang/String;"; // #30 + Utf8 "RuntimeInvisibleAnnotations"; // #31 + Utf8 "Ljdk/internal/vm/annotation/Strict;"; // #32 + Utf8 "(ZZ)V"; // #33 + Utf8 "Code"; // #34 + Utf8 "LineNumberTable"; // #35 + Utf8 "StackMapTable"; // #36 + Utf8 "SourceFile"; // #37 + Utf8 "StrictInstanceFieldsTest.java"; // #38 + Utf8 "BootstrapMethods"; // #39 + String #41; // #40 + Utf8 "x: \u0001\ny: \u0001\n\u0001"; // #41 + MethodHandle 6b #43; // #42 + Method #44 #45; // #43 + Class #46; // #44 + NameAndType #29 #47; // #45 + Utf8 "java/lang/invoke/StringConcatFactory"; // #46 + Utf8 "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;"; // #47 + Utf8 "InnerClasses"; // #48 + Class #50; // #49 + Utf8 "java/lang/invoke/MethodHandles$Lookup"; // #50 + Class #52; // #51 + Utf8 "java/lang/invoke/MethodHandles"; // #52 + Utf8 "Lookup"; // #53 + } + + 0x0020; // access + #2; // this_cpx + #11; // super_cpx + + [] { // Interfaces + } // end of Interfaces + + [] { // Fields + { // field + 0x0800; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#31) { // RuntimeInvisibleAnnotations + [] { // annotations + { // annotation + #32; + [] { // element_value_pairs + } // element_value_pairs + } // annotation + } + } // end of RuntimeInvisibleAnnotations + } // end of Attributes + } + ; + { // field + 0x0800; // access + #9; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#31) { // RuntimeInvisibleAnnotations + [] { // annotations + { // annotation + #32; + [] { // element_value_pairs + } // element_value_pairs + } // annotation + } + } // end of RuntimeInvisibleAnnotations + } // end of Attributes + } + } // end of Fields + + [] { // Methods + { // method + 0x0000; // access + #14; // name_index + #33; // descriptor_index + [] { // Attributes + Attr(#34) { // Code + 4; // max_stack + 3; // max_locals + Bytes[]{ + 0x1B 0x99 0x00 0x1C 0x2A 0x04 0xB5 0x00 0x01 0x1C 0x99 0x00; + 0x0B 0x2A 0x04 0xB5 0x00 0x07 0xA7 0x00 0x15 0x2A 0x05 0xB5; + 0x00 0x07 0xA7 0x00 0x0D 0x2A 0x2A 0x06 0x5A 0xB5 0x00 0x07; + 0xB5 0x00 0x01 0x2A 0xB7 0x00 0x0A 0xB1; + } + [] { // Traps + } // end of Traps + [] { // Attributes + Attr(#35) { // LineNumberTable + [] { // line_number_table + 0 121; + 4 122; + 9 123; + 13 124; + 21 126; + 29 129; + 39 131; + 43 132; + } + } // end of LineNumberTable + ; + Attr(#36) { // StackMapTable + [] { // + 246b, []{#8; #5}, { // FAIL: early_larval_frame contains cp index that isn't NameAndType + 21b; // same + }; + 246b, []{#3; #8}, { // early_larval_frame + 7b; // same_frame + }; + 246b, []{}, { // early_larval_frame + 9b; // same_frame + }; + } + } // end of StackMapTable + } // end of Attributes + } // end of Code + } // end of Attributes + } + ; + { // method + 0x0000; // access + #18; // name_index + #19; // descriptor_index + [] { // Attributes + Attr(#34) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2A 0xB4 0x00 0x01 0xAC; + } + [] { // Traps + } // end of Traps + [] { // Attributes + Attr(#35) { // LineNumberTable + [] { // line_number_table + 0 134; + } + } // end of LineNumberTable + } // end of Attributes + } // end of Code + } // end of Attributes + } + ; + { // method + 0x0000; // access + #22; // name_index + #19; // descriptor_index + [] { // Attributes + Attr(#34) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2A 0xB4 0x00 0x07 0xAC; + } + [] { // Traps + } // end of Traps + [] { // Attributes + Attr(#35) { // LineNumberTable + [] { // line_number_table + 0 135; + } + } // end of LineNumberTable + } // end of Attributes + } // end of Code + } // end of Attributes + } + ; + { // method + 0x0001; // access + #25; // name_index + #26; // descriptor_index + [] { // Attributes + Attr(#34) { // Code + 3; // max_stack + 1; // max_locals + Bytes[]{ + 0x2A 0xB6 0x00 0x10 0x2A 0xB6 0x00 0x14 0x2A 0xB7 0x00 0x17; + 0xBA 0x00 0x1B 0x00 0x00 0xB0; + } + [] { // Traps + } // end of Traps + [] { // Attributes + Attr(#35) { // LineNumberTable + [] { // line_number_table + 0 139; + } + } // end of LineNumberTable + } // end of Attributes + } // end of Code + } // end of Attributes + } + } // end of Methods + + [] { // Attributes + Attr(#37) { // SourceFile + #38; + } // end of SourceFile + ; + Attr(#39) { // BootstrapMethods + [] { // bootstrap_methods + { // bootstrap_method + #42; // bootstrap_method_ref + [] { // bootstrap_arguments + #40; + } // bootstrap_arguments + } // bootstrap_method + } + } // end of BootstrapMethods + ; + Attr(#48) { // InnerClasses + [] { // classes + #49 #51 #53 57; + } + } // end of InnerClasses + } // end of Attributes +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/NestedEarlyLarval.jcod b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/NestedEarlyLarval.jcod new file mode 100644 index 00000000000..305f0c89e1b --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/NestedEarlyLarval.jcod @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +class NestedEarlyLarval { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Field #2 #3; // #1 + Class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "NestedEarlyLarval"; // #4 + Utf8 "x"; // #5 + Utf8 "I"; // #6 + Field #2 #8; // #7 + NameAndType #9 #6; // #8 + Utf8 "y"; // #9 + Method #11 #12; // #10 + Class #13; // #11 + NameAndType #14 #15; // #12 + Utf8 "Parent"; // #13 + Utf8 ""; // #14 + Utf8 "()V"; // #15 + Method #2 #17; // #16 + NameAndType #18 #19; // #17 + Utf8 "get_x"; // #18 + Utf8 "()I"; // #19 + Method #2 #21; // #20 + NameAndType #22 #19; // #21 + Utf8 "get_y"; // #22 + Method #11 #24; // #23 + NameAndType #25 #26; // #24 + Utf8 "toString"; // #25 + Utf8 "()Ljava/lang/String;"; // #26 + InvokeDynamic 0s #28; // #27 + NameAndType #29 #30; // #28 + Utf8 "makeConcatWithConstants"; // #29 + Utf8 "(IILjava/lang/String;)Ljava/lang/String;"; // #30 + Utf8 "RuntimeInvisibleAnnotations"; // #31 + Utf8 "Ljdk/internal/vm/annotation/Strict;"; // #32 + Utf8 "(ZZ)V"; // #33 + Utf8 "Code"; // #34 + Utf8 "LineNumberTable"; // #35 + Utf8 "StackMapTable"; // #36 + Utf8 "SourceFile"; // #37 + Utf8 "StrictInstanceFieldsTest.java"; // #38 + Utf8 "BootstrapMethods"; // #39 + String #41; // #40 + Utf8 "x: \u0001\ny: \u0001\n\u0001"; // #41 + MethodHandle 6b #43; // #42 + Method #44 #45; // #43 + Class #46; // #44 + NameAndType #29 #47; // #45 + Utf8 "java/lang/invoke/StringConcatFactory"; // #46 + Utf8 "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;"; // #47 + Utf8 "InnerClasses"; // #48 + Class #50; // #49 + Utf8 "java/lang/invoke/MethodHandles$Lookup"; // #50 + Class #52; // #51 + Utf8 "java/lang/invoke/MethodHandles"; // #52 + Utf8 "Lookup"; // #53 + } + + 0x0020; // access + #2; // this_cpx + #11; // super_cpx + + [] { // Interfaces + } // end of Interfaces + + [] { // Fields + { // field + 0x0800; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#31) { // RuntimeInvisibleAnnotations + [] { // annotations + { // annotation + #32; + [] { // element_value_pairs + } // element_value_pairs + } // annotation + } + } // end of RuntimeInvisibleAnnotations + } // end of Attributes + } + ; + { // field + 0x0800; // access + #9; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#31) { // RuntimeInvisibleAnnotations + [] { // annotations + { // annotation + #32; + [] { // element_value_pairs + } // element_value_pairs + } // annotation + } + } // end of RuntimeInvisibleAnnotations + } // end of Attributes + } + } // end of Fields + + [] { // Methods + { // method + 0x0000; // access + #14; // name_index + #33; // descriptor_index + [] { // Attributes + Attr(#34) { // Code + 4; // max_stack + 3; // max_locals + Bytes[]{ + 0x1B 0x99 0x00 0x1C 0x2A 0x04 0xB5 0x00 0x01 0x1C 0x99 0x00; + 0x0B 0x2A 0x04 0xB5 0x00 0x07 0xA7 0x00 0x15 0x2A 0x05 0xB5; + 0x00 0x07 0xA7 0x00 0x0D 0x2A 0x2A 0x06 0x5A 0xB5 0x00 0x07; + 0xB5 0x00 0x01 0x2A 0xB7 0x00 0x0A 0xB1; + } + [] { // Traps + } // end of Traps + [] { // Attributes + Attr(#35) { // LineNumberTable + [] { // line_number_table + 0 121; + 4 122; + 9 123; + 13 124; + 21 126; + 29 129; + 39 131; + 43 132; + } + } // end of LineNumberTable + ; + Attr(#36) { // StackMapTable + [] { // + 246b, []{#8}, { // early_larval_frame + 246b; // early_larval_frame, illegal + }; + 246b, []{#3; #8}, { // early_larval_frame + 7b; // same_frame + }; + 246b, []{}, { // early_larval_frame + 9b; // same_frame + }; + } + } // end of StackMapTable + } // end of Attributes + } // end of Code + } // end of Attributes + } + ; + { // method + 0x0000; // access + #18; // name_index + #19; // descriptor_index + [] { // Attributes + Attr(#34) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2A 0xB4 0x00 0x01 0xAC; + } + [] { // Traps + } // end of Traps + [] { // Attributes + Attr(#35) { // LineNumberTable + [] { // line_number_table + 0 134; + } + } // end of LineNumberTable + } // end of Attributes + } // end of Code + } // end of Attributes + } + ; + { // method + 0x0000; // access + #22; // name_index + #19; // descriptor_index + [] { // Attributes + Attr(#34) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2A 0xB4 0x00 0x07 0xAC; + } + [] { // Traps + } // end of Traps + [] { // Attributes + Attr(#35) { // LineNumberTable + [] { // line_number_table + 0 135; + } + } // end of LineNumberTable + } // end of Attributes + } // end of Code + } // end of Attributes + } + ; + { // method + 0x0001; // access + #25; // name_index + #26; // descriptor_index + [] { // Attributes + Attr(#34) { // Code + 3; // max_stack + 1; // max_locals + Bytes[]{ + 0x2A 0xB6 0x00 0x10 0x2A 0xB6 0x00 0x14 0x2A 0xB7 0x00 0x17; + 0xBA 0x00 0x1B 0x00 0x00 0xB0; + } + [] { // Traps + } // end of Traps + [] { // Attributes + Attr(#35) { // LineNumberTable + [] { // line_number_table + 0 139; + } + } // end of LineNumberTable + } // end of Attributes + } // end of Code + } // end of Attributes + } + } // end of Methods + + [] { // Attributes + Attr(#37) { // SourceFile + #38; + } // end of SourceFile + ; + Attr(#39) { // BootstrapMethods + [] { // bootstrap_methods + { // bootstrap_method + #42; // bootstrap_method_ref + [] { // bootstrap_arguments + #40; + } // bootstrap_arguments + } // bootstrap_method + } + } // end of BootstrapMethods + ; + Attr(#48) { // InnerClasses + [] { // classes + #49 #51 #53 57; + } + } // end of InnerClasses + } // end of Attributes +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/RedefineStrictFieldsTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/RedefineStrictFieldsTest.java new file mode 100644 index 00000000000..eaf4f955487 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/RedefineStrictFieldsTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Redefinition tests that larval_frame stackmaps are adjusted + * @enablePreview + * @library /test/lib + * @run main RedefineClassHelper + * @modules java.base/jdk.internal.vm.annotation + * @compile -XDnoLocalProxyVars StrictFieldsOld.java StrictFieldsNew.java + * @run main/othervm -Xverify:remote -javaagent:redefineagent.jar RedefineStrictFieldsTest + */ + +import java.io.InputStream; +import static jdk.test.lib.Asserts.assertTrue; + +public class RedefineStrictFieldsTest { + + // All of this should be moved to RedefineClassHelper + private static byte[] getBytecodes(Class thisClass, String name) throws Exception { + InputStream is = thisClass.getClassLoader().getResourceAsStream(name + ".class"); + byte[] buf = is.readAllBytes(); + System.out.println("sizeof(" + name + ".class) == " + buf.length); + return buf; + } + + private static int getStringIndex(String needle, byte[] buf) { + return getStringIndex(needle, buf, 0); + } + + private static int getStringIndex(String needle, byte[] buf, int offset) { + outer: + for (int i = offset; i < buf.length - offset - needle.length(); i++) { + for (int j = 0; j < needle.length(); j++) { + if (buf[i + j] != (byte)needle.charAt(j)) continue outer; + } + return i; + } + return 0; + } + + private static void replaceString(byte[] buf, String name, int index) { + for (int i = index; i < index + name.length(); i++) { + buf[i] = (byte)name.charAt(i - index); + } + } + + private static byte[] replaceAllStrings(String oldString, String newString) throws Exception { + byte [] buf = getBytecodes(RedefineStrictFieldsTest.class, oldString); + assertTrue(oldString.length() == newString.length(), "must have same length"); + int index = -1; + while ((index = getStringIndex(oldString, buf, index + 1)) != 0) { + replaceString(buf, newString, index); + } + return buf; + } + + // This should fail because x and y are no longer strict. + static String newClassBytes = "class StrictFieldsOld { " + + "int x; int y; " + + "StrictFieldsOld(boolean a, boolean b) { }" + + "public void foo() { System.out.println(x + y );}}"; + + public static void main(java.lang.String[] unused) throws Exception { + + StrictFieldsOld old = new StrictFieldsOld(true, false); + old.foo(); + + // Rename class "StrictFieldsNew" to "StrictFieldsOld" so we can redefine it. + byte [] buf = replaceAllStrings("StrictFieldsNew", "StrictFieldsOld"); + // Now redefine the original version. + // If the stackmaps aren't rewritten to point to new constant pool indices, this should get a VerifyError + // which RedefineClasses eats and makes into an InternalError. Either way, this test will fail. + RedefineClassHelper.redefineClass(StrictFieldsOld.class, buf); + StrictFieldsOld newOld = new StrictFieldsOld(true, false); + newOld.foo(); // should call the new foo + + // Redefine class without strict fields. Should get a redefinition error. + try { + RedefineClassHelper.redefineClass(StrictFieldsOld.class, newClassBytes); + StrictFieldsOld shouldThrow = new StrictFieldsOld(false, false); + shouldThrow.foo(); // Should not be called. + throw new RuntimeException("Redefinition should have failed"); + } catch (java.lang.UnsupportedOperationException uoe) { + String msg = uoe.getMessage(); + assertTrue(msg.contains("class redefinition failed: attempted to change the schema (add/remove fields)"), "FAILED"); + } + + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictFields.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictFields.java new file mode 100644 index 00000000000..a19405b0eb2 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictFields.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @enablePreview + * @compile strictFields.jasm + * @run main/othervm -Xverify:remote StrictFields + */ + +public class StrictFields { + public static void main(String[] args) throws Throwable { + + // First load a class where strict should be ignored + Class c = Class.forName("StrictIgnore"); + + // Now load a well-formed strict-using value class + c = Class.forName("StrictBase"); + + // Now a bad class + try { + c = Class.forName("PostInitStrict"); + throw new Error("VerifyError was not thrown as expected!"); + } catch (VerifyError ve) { + // Once strict non-final is possible, expect "Illegal use of putfield on a strict field" + if (!ve.getMessage().startsWith("All strict final fields must be initialized before super()")) { + throw new Error("Wrong VerifyError thrown", ve); + } else { + System.out.println("Expected VerifyError was thrown"); + } + } + + // Now a bad class that tries to write to a super class's strict field + // in the preinit phase + try { + c = Class.forName("BadStrictSubPreInit"); + throw new Error("VerifyError was not thrown as expected!"); + } catch (VerifyError ve) { + if (!ve.getMessage().startsWith("Bad type on operand stack")) { + throw new Error("Wrong VerifyError thrown", ve); + } else { + System.out.println("Expected VerifyError was thrown"); + } + } + + // Now a bad class that tries to write to a super class's strict field + // in the post phase. This is not a verification error but we test it + // here for completeness.Expected exception: + // java.lang.IllegalAccessError: Update to non-static final field + // BadStrictSubPostInit.x attempted from a different class + // (BadStrictSubPostInit) than the field's declaring class + try { + c = Class.forName("BadStrictSubPostInit"); + Object o = c.newInstance(); + throw new Error("IllegalAccessErrorError was not thrown as expected!"); + } catch (IllegalAccessError iae) { + if (!iae.getMessage().startsWith("Update to non-static final field")) { + throw new Error("Wrong IllegalAccessError thrown", iae); + } else { + System.out.println("Expected IllegalAccessError was thrown"); + } + } + + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictFieldsNew.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictFieldsNew.java new file mode 100644 index 00000000000..28aabe88f6e --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictFieldsNew.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import jdk.internal.vm.annotation.Strict; + +class StrictFieldsNew { + + @Strict + int x; + @Strict + int y; + + StrictFieldsNew(boolean a, boolean b) { + System.out.println("Calling new constructor with " + a + " " + b); + if (a) { + x = 4; + if (b) { + y = 4; + } else { + y = 5; + } + } else { + x = y = 7; + } + super(); + } + + public void foo() { + System.out.println("Hello new fool " + x + " " + y ); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictFieldsNotSubset.jcod b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictFieldsNotSubset.jcod new file mode 100644 index 00000000000..c5c42e5bc24 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictFieldsNotSubset.jcod @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* +class StrictFieldsNotSubset extends Parent { + @Strict + int x; + @Strict + int y; + + StrictFieldsNotSubset(boolean a, boolean b) { + if (a) { + x = 1; + if (b) { + y = 1; + } else { + y = 2; + } + } else { + x = y = 3; + } + super(); + } +} +*/ + +class StrictFieldsNotSubset { + 0xCAFEBABE; + 65535; // minor version + 70; // version + [] { // Constant Pool + ; // first element is empty + Field #2 #3; // #1 + Class #4; // #2 + NameAndType #5 #6; // #3 + Utf8 "StrictFieldsNotSubset"; // #4 + Utf8 "x"; // #5 + Utf8 "I"; // #6 + Field #2 #8; // #7 + NameAndType #9 #6; // #8 + Utf8 "y"; // #9 + Method #11 #12; // #10 + Class #13; // #11 + NameAndType #14 #15; // #12 + Utf8 "Parent"; // #13 + Utf8 ""; // #14 + Utf8 "()V"; // #15 + Method #2 #17; // #16 + NameAndType #18 #19; // #17 + Utf8 "get_x"; // #18 + Utf8 "()I"; // #19 + Method #2 #21; // #20 + NameAndType #22 #19; // #21 + Utf8 "get_y"; // #22 + Method #11 #24; // #23 + NameAndType #25 #26; // #24 + Utf8 "toString"; // #25 + Utf8 "()Ljava/lang/String;"; // #26 + InvokeDynamic 0s #28; // #27 + NameAndType #29 #30; // #28 + Utf8 "makeConcatWithConstants"; // #29 + Utf8 "(IILjava/lang/String;)Ljava/lang/String;"; // #30 + Utf8 "RuntimeInvisibleAnnotations"; // #31 + Utf8 "Ljdk/internal/vm/annotation/Strict;"; // #32 + Utf8 "(ZZ)V"; // #33 + Utf8 "Code"; // #34 + Utf8 "LineNumberTable"; // #35 + Utf8 "StackMapTable"; // #36 + Utf8 "SourceFile"; // #37 + Utf8 "StrictInstanceFieldsTest.java"; // #38 + Utf8 "BootstrapMethods"; // #39 + String #41; // #40 + Utf8 "x: \u0001\ny: \u0001\n\u0001"; // #41 + MethodHandle 6b #43; // #42 + Method #44 #45; // #43 + Class #46; // #44 + NameAndType #29 #47; // #45 + Utf8 "java/lang/invoke/StringConcatFactory"; // #46 + Utf8 "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;"; // #47 + Utf8 "InnerClasses"; // #48 + Class #50; // #49 + Utf8 "java/lang/invoke/MethodHandles$Lookup"; // #50 + Class #52; // #51 + Utf8 "java/lang/invoke/MethodHandles"; // #52 + Utf8 "Lookup"; // #53 + } + + 0x0020; // access + #2; // this_cpx + #11; // super_cpx + + [] { // Interfaces + } // end of Interfaces + + [] { // Fields + { // field + 0x0800; // access + #5; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#31) { // RuntimeInvisibleAnnotations + [] { // annotations + { // annotation + #32; + [] { // element_value_pairs + } // element_value_pairs + } // annotation + } + } // end of RuntimeInvisibleAnnotations + } // end of Attributes + } + ; + { // field + 0x0800; // access + #9; // name_index + #6; // descriptor_index + [] { // Attributes + Attr(#31) { // RuntimeInvisibleAnnotations + [] { // annotations + { // annotation + #32; + [] { // element_value_pairs + } // element_value_pairs + } // annotation + } + } // end of RuntimeInvisibleAnnotations + } // end of Attributes + } + } // end of Fields + + [] { // Methods + { // method + 0x0000; // access + #14; // name_index + #33; // descriptor_index + [] { // Attributes + Attr(#34) { // Code + 4; // max_stack + 3; // max_locals + Bytes[]{ + 0x1B 0x99 0x00 0x1C 0x2A 0x04 0xB5 0x00 0x01 0x1C 0x99 0x00; + 0x0B 0x2A 0x04 0xB5 0x00 0x07 0xA7 0x00 0x15 0x2A 0x05 0xB5; + 0x00 0x07 0xA7 0x00 0x0D 0x2A 0x2A 0x06 0x5A 0xB5 0x00 0x07; + 0xB5 0x00 0x01 0x2A 0xB7 0x00 0x0A 0xB1; + } + [] { // Traps + } // end of Traps + [] { // Attributes + Attr(#35) { // LineNumberTable + [] { // line_number_table + 0 121; + 4 122; + 9 123; + 13 124; + 21 126; + 29 129; + 39 131; + 43 132; + } + } // end of LineNumberTable + ; + Attr(#36) { // StackMapTable + [] { // + 246b, []{#8; #12}, { // FAIL: early_larval_frame contains NameAndType not present in original list of strict fields + 21b; // same + }; + 246b, []{#3; #8}, { // early_larval_frame + 7b; // same_frame + }; + 246b, []{}, { // early_larval_frame + 9b; // same_frame + }; + } + } // end of StackMapTable + } // end of Attributes + } // end of Code + } // end of Attributes + } + ; + { // method + 0x0000; // access + #18; // name_index + #19; // descriptor_index + [] { // Attributes + Attr(#34) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2A 0xB4 0x00 0x01 0xAC; + } + [] { // Traps + } // end of Traps + [] { // Attributes + Attr(#35) { // LineNumberTable + [] { // line_number_table + 0 134; + } + } // end of LineNumberTable + } // end of Attributes + } // end of Code + } // end of Attributes + } + ; + { // method + 0x0000; // access + #22; // name_index + #19; // descriptor_index + [] { // Attributes + Attr(#34) { // Code + 1; // max_stack + 1; // max_locals + Bytes[]{ + 0x2A 0xB4 0x00 0x07 0xAC; + } + [] { // Traps + } // end of Traps + [] { // Attributes + Attr(#35) { // LineNumberTable + [] { // line_number_table + 0 135; + } + } // end of LineNumberTable + } // end of Attributes + } // end of Code + } // end of Attributes + } + ; + { // method + 0x0001; // access + #25; // name_index + #26; // descriptor_index + [] { // Attributes + Attr(#34) { // Code + 3; // max_stack + 1; // max_locals + Bytes[]{ + 0x2A 0xB6 0x00 0x10 0x2A 0xB6 0x00 0x14 0x2A 0xB7 0x00 0x17; + 0xBA 0x00 0x1B 0x00 0x00 0xB0; + } + [] { // Traps + } // end of Traps + [] { // Attributes + Attr(#35) { // LineNumberTable + [] { // line_number_table + 0 139; + } + } // end of LineNumberTable + } // end of Attributes + } // end of Code + } // end of Attributes + } + } // end of Methods + + [] { // Attributes + Attr(#37) { // SourceFile + #38; + } // end of SourceFile + ; + Attr(#39) { // BootstrapMethods + [] { // bootstrap_methods + { // bootstrap_method + #42; // bootstrap_method_ref + [] { // bootstrap_arguments + #40; + } // bootstrap_arguments + } // bootstrap_method + } + } // end of BootstrapMethods + ; + Attr(#48) { // InnerClasses + [] { // classes + #49 #51 #53 57; + } + } // end of InnerClasses + } // end of Attributes +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictFieldsOld.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictFieldsOld.java new file mode 100644 index 00000000000..04b4fb15f20 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictFieldsOld.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import jdk.internal.vm.annotation.Strict; + +class StrictFieldsOld { + @Strict + int x; + @Strict + int y; + + StrictFieldsOld(boolean a, boolean b) { + if (a) { + x = 1; + if (b) { + y = 1; + } else { + y = 2; + } + } else { + x = y = 3; + } + super(); + } + + public void foo() { + System.out.println("Hello old fool " + x + " " + y ); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictInstanceFieldsTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictInstanceFieldsTest.java new file mode 100644 index 00000000000..54a76de5b10 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictInstanceFieldsTest.java @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @enablePreview + * @modules java.base/jdk.internal.vm.annotation + * @compile BadChild.jasm + * BadChild1.jasm + * ControlFlowChildBad.jasm + * TryCatchChildBad.jasm + * NestedEarlyLarval.jcod + * EndsInEarlyLarval.jcod + * StrictFieldsNotSubset.jcod + * InvalidIndexInEarlyLarval.jcod + * @compile -XDnoLocalProxyVars StrictInstanceFieldsTest.java + * @run main/othervm -Xlog:verification StrictInstanceFieldsTest + */ + +import java.lang.reflect.Field; +import jdk.internal.vm.annotation.Strict; + +public class StrictInstanceFieldsTest { + public static void main(String[] args) { + + // -------------- + // POSITIVE TESTS + // -------------- + + // Base case + Child c = new Child(); + System.out.println(c); + + // Constructor with control flow + ControlFlowChild c1 = new ControlFlowChild(true, true); + System.out.println(c1); + + // Constructor with try-catch-finally + TryCatchChild c2 = new TryCatchChild(); + System.out.println(c2); + + // Constructor with switch case + SwitchCaseChild c3 = new SwitchCaseChild(2); + System.out.println(c3); + + // Constructor with strict field assignment in conditional + AssignedInConditionalChild c4 = new AssignedInConditionalChild(); + System.out.println(c4); + + // Constructor with nested constructor calls + NestedConstructorChild c5 = new NestedConstructorChild(); + System.out.println(c5); + + // Final stirct fields defined in constructor + FinalChild fc = new FinalChild(); + System.out.println(fc); + + // -------------- + // NEGATIVE TESTS + // -------------- + + // Field not initialized before super call + try { + BadChild child = new BadChild(); + System.out.println(child); + throw new RuntimeException("Should fail verification"); + } catch (java.lang.VerifyError e) { + if (!e.getMessage().contains("All strict final fields must be initialized before super()")) { + throw new RuntimeException("wrong exception: " + e.getMessage()); + } + e.printStackTrace(); + } + + // Field not initialized before super call + try { + BadChild1 child = new BadChild1(); + System.out.println(child); + throw new RuntimeException("Should fail verification"); + } catch (java.lang.VerifyError e) { + if (!e.getMessage().contains("All strict final fields must be initialized before super()")) { + throw new RuntimeException("wrong exception: " + e.getMessage()); + } + e.printStackTrace(); + } + + // Constructor with control flow but field is not initialized + try { + ControlFlowChildBad child = new ControlFlowChildBad(true, false); + System.out.println(child); + throw new RuntimeException("Should fail verification"); + } catch (java.lang.VerifyError e) { + if (!e.getMessage().contains("Inconsistent stackmap frames at branch target")) { + throw new RuntimeException("wrong exception: " + e.getMessage()); + } + e.printStackTrace(); + } + + // Constructor with try-catch but field is not initialized + try { + TryCatchChildBad child = new TryCatchChildBad(); + System.out.println(child); + throw new RuntimeException("Should fail verification"); + } catch (java.lang.VerifyError e) { + if (!e.getMessage().contains("Inconsistent stackmap frames at branch target")) { + throw new RuntimeException("wrong exception: " + e.getMessage()); + } + e.printStackTrace(); + } + + // Early_Larval frame contains another early_larval instead of a base frame + try { + NestedEarlyLarval child = new NestedEarlyLarval(true, false); + System.out.println(child); + throw new RuntimeException("Should fail verification"); + } catch (java.lang.VerifyError e) { + if (!e.getMessage().contains("Early larval frame must be followed by a base frame")) { + throw new RuntimeException("wrong exception: " + e.getMessage()); + } + e.printStackTrace(); + } + + // Stack map table ends in early_larval frame without base frame + try { + EndsInEarlyLarval child = new EndsInEarlyLarval(true, false); + System.out.println(child); + throw new RuntimeException("Should fail verification"); + } catch (java.lang.VerifyError e) { + if (!e.getMessage().contains("Early larval frame must be followed by a base frame")) { + throw new RuntimeException("wrong exception: " + e.getMessage()); + } + e.printStackTrace(); + } + + // Early_larval frame includes a strict field not preset in the original set of unset fields + try { + StrictFieldsNotSubset child = new StrictFieldsNotSubset(true, false); + System.out.println(child); + throw new RuntimeException("Should fail verification"); + } catch (java.lang.VerifyError e) { + if (!e.getMessage().contains("Strict fields not a subset of initial strict instance fields")) { + throw new RuntimeException("wrong exception: " + e.getMessage()); + } + e.printStackTrace(); + } + + // Early_larval frame includes a constant pool index that doesn't point to a NameAndType + try { + InvalidIndexInEarlyLarval child = new InvalidIndexInEarlyLarval(true, false); + System.out.println(child); + throw new RuntimeException("Should fail verification"); + } catch (java.lang.VerifyError e) { + if (!e.getMessage().contains("Invalid constant pool index in early larval frame")) { + throw new RuntimeException("wrong exception: " + e.getMessage()); + } + e.printStackTrace(); + } + + System.out.println("Passed"); + } +} + +class Parent { + int z; + + Parent() { + z = 0; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + Field[] fields = this.getClass().getDeclaredFields(); + + for (Field f : fields) { + try { + sb.append(f.getName() + ": " + f.get(this) + "\n"); + } catch (IllegalAccessException ex) { + throw new RuntimeException(ex); + } + } + return sb.toString(); + } +} + +class Child extends Parent { + + @Strict + int x; + @Strict + int y; + + Child() { + x = y = 1; + super(); + } +} + +class ControlFlowChild extends Parent { + + @Strict + int x; + @Strict + int y; + + ControlFlowChild(boolean a, boolean b) { + if (a) { + x = 1; + if (b) { + y = 1; + } else { + y = 2; + } + } else { + x = y = 3; + } + super(); + } +} + +class TryCatchChild extends Parent { + + @Strict + int x; + @Strict + int y; + + TryCatchChild() { + try { + x = 0; + int[] a = new int[1]; + System.out.println(a[2]); + } catch (java.lang.ArrayIndexOutOfBoundsException e) { + y = 0; + } finally { + x = y = 1; + } + super(); + } +} + +class AssignedInConditionalChild extends Parent { + + @Strict + final int x; + @Strict + final int y; + + AssignedInConditionalChild() { + if ((x=1) == 1) { + y = 1; + } else { + y = 2; + } + super(); + } +} + +class SwitchCaseChild extends Parent { + + @Strict + final int x; + @Strict + final int y; + + SwitchCaseChild(int n) { + switch(n) { + case 0: + x = y = 0; + break; + case 1: + x = y = 1; + break; + case 2: + x = y = 2; + break; + default: + x = y = 100; + break; + } + super(); + } +} + +class NestedConstructorChild extends Parent { + + @Strict + final int x; + @Strict + final int y; + + NestedConstructorChild(boolean a, boolean b) { + if (a) { + x = 1; + if (b) { + y = 1; + } else { + y = 2; + } + } else { + x = y = 3; + } + super(); + } + + NestedConstructorChild() { + this(true, true); + } +} + +class FinalChild extends Parent { + + @Strict + final int x; + @Strict + final int y; + + FinalChild() { + x = y = 1; + super(); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictStaticFieldsTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictStaticFieldsTest.java new file mode 100644 index 00000000000..231e4763f4d --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictStaticFieldsTest.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8349945 + * @summary Tracking of strict static fields + * @enablePreview + * @compile Bnoinit_BAD.jasm + * Brbefore_BAD.jasm + * Cwreflective_OK.jasm + * Creflbefore_BAD.jasm + * WriteAfterReadRefl.jasm + * @compile --add-exports=java.base/jdk.internal.vm.annotation=ALL-UNNAMED StrictStaticFieldsTest.java + * @run main/othervm -XX:+UnlockDiagnosticVMOptions StrictStaticFieldsTest + */ + +import java.lang.reflect.*; +import jdk.internal.vm.annotation.Strict; + +public class StrictStaticFieldsTest { + public static void main(String[] args) throws Exception { + // -------------- + // POSITIVE TESTS + // -------------- + + // Base Case + printStatics(Aregular_OK.class); + + // Strict statics initialized to null and zero + printStatics(Anulls_OK.class); + + // Assign strict static twice + printStatics(Arepeat_OK.class); + + // Read and write initialized strict static + printStatics(Aupdate_OK.class); + + // Reflectively set static fields + printStaticsReflective(Cwreflective_OK.class); + + // -------------- + // NEGATIVE TESTS + // -------------- + + // Strict statics not initialized + try { + printStatics(Bnoinit_BAD.class); + throw new RuntimeException("Should throw"); + } catch(ExceptionInInitializerError ex) { + Throwable e = (ex.getCause() != null) ? ex.getCause() : ex; + if (!e.getMessage().contains("unset after initialization")) { + throw new RuntimeException("wrong exception: " + e.getMessage()); + } + e.printStackTrace(); + } + + // Read before write + try { + printStatics(Brbefore_BAD.class); + throw new RuntimeException("Should throw"); + } catch(ExceptionInInitializerError ex) { + Throwable e = (ex.getCause() != null) ? ex.getCause() : ex; + if (!e.getMessage().contains("is unset before first read")) { + throw new RuntimeException("wrong exception: " + e.getMessage()); + } + e.printStackTrace(); + } + + // Reflective read before write + try { + printStaticsReflective(Creflbefore_BAD.class); + throw new RuntimeException("Should throw"); + } catch(ExceptionInInitializerError ex) { + Throwable e = (ex.getCause() != null) ? ex.getCause() : ex; + if (!e.getMessage().contains("is unset before first read")) { + throw new RuntimeException("wrong exception: " + e.getMessage()); + } + e.printStackTrace(); + } + + // Reflective write after read + try { + printStaticsReflective(WriteAfterReadRefl.class); + throw new RuntimeException("Should throw"); + } catch(ExceptionInInitializerError ex) { + Throwable e = (ex.getCause() != null) ? ex.getCause() : ex; + if (!e.getMessage().contains("set after read")) { + throw new RuntimeException("wrong exception: " + e.getMessage()); + } + e.printStackTrace(); + } + + System.out.println("Passed"); + } + + static void printStatics(Class cls) throws Exception { + Field f1 = cls.getDeclaredField("F1__STRICT"); + Field f2 = cls.getDeclaredField("F2__STRICT"); + System.out.println(f1.get(null)); + System.out.println(f2.get(null)); + } + + // Methods for reflective access + static void printStaticsReflective(Class cls) throws Exception { + Field FIELD_F1 = findField(cls, "F1__STRICT"); + Field FIELD_F2 = findField(cls, "F2__STRICT"); + + String f1 = (String)getstaticReflective(FIELD_F1); + int f2 = (int)getstaticReflective(FIELD_F2); + + System.out.println(f1); + System.out.println(f2); + } + + static void putstaticReflective(Field f, Object x) { + try { + f.set(null, x); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException(ex); + } + } + + static Object getstaticReflective(Field f) { + try { + return f.get(null); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException(ex); + } + } + + static Field findField(Class cls, String name) { + try { + return cls.getDeclaredField(name); + } catch (ReflectiveOperationException ex) { + throw new RuntimeException(ex); + } + } +} + +class Aregular_OK { + @Strict static final String F1__STRICT = "hello"; + @Strict static final int F2__STRICT = 42; + } + +class Anulls_OK { + @Strict static String F1__STRICT = null; + @Strict static int F2__STRICT = 0; +} + +class Arepeat_OK { + @Strict static String F1__STRICT = "hello"; + @Strict static int F2__STRICT = 42; + static { + System.out.print("(making second putstatic)"); + F2__STRICT = 43; + } +} + +class Aupdate_OK { + @Strict static String F1__STRICT = "hello"; + @Strict static int F2__STRICT = 42; + static { + System.out.println("(making getstatic and second putstatic)"); + F2__STRICT++; + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictStaticTests.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictStaticTests.java new file mode 100644 index 00000000000..f3d5726d234 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/StrictStaticTests.java @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8349945 + * @summary test tracking of strict static fields + * @enablePreview + * @run main/othervm StrictStaticTests + * + * @test id=C1only + * @run main/othervm -XX:TieredStopAtLevel=2 -Xcomp -Xbatch StrictStaticTests + * + * @test id=C2only + * @run main/othervm -XX:-TieredCompilation -Xcomp -Xbatch StrictStaticTests + */ + +import java.io.File; +import java.lang.classfile.*; +import java.lang.constant.*; +import java.lang.reflect.*; +import java.lang.invoke.*; +import java.nio.file.Files; + +import static java.lang.invoke.MethodHandles.*; +import static java.lang.invoke.MethodType.*; +import static java.lang.constant.ConstantDescs.*; + +public class StrictStaticTests { + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + private static final String THIS_PACKAGE = LOOKUP.lookupClass().getPackageName(); + + static Class buildClass(String className, + Class staticType, + int writeCount, // how many putstatics? (0=>error) + byte readFlag, // read before (-1) or after (1)? + int extraCount, // how many extra strict statics? + boolean finals // make them all finals? + ) throws Exception { + ClassDesc cn = ClassDesc.of(className); + ClassDesc CD_Integer = Integer.class.describeConstable().orElseThrow(); + String VO_NAME = "valueOf"; + MethodTypeDesc VO_TYPE = MethodTypeDesc.of(CD_Integer, CD_int); + String SS_NAME = "SS"; + ClassDesc SS_TYPE = staticType.describeConstable().orElseThrow(); + String XS_NAME = "EXTRAS"; + ClassDesc XS_TYPE = CD_boolean; + String PS_NAME = "PLAIN"; + ClassDesc PS_TYPE = CD_byte; + + boolean prim = staticType.isPrimitive(); + boolean pop2 = staticType == double.class; + final ConstantDesc SS_INIT, SS_INIT_2; + if (!prim) { + SS_INIT = "foo"; + SS_INIT_2 = null; + } else if (!pop2) { + SS_INIT = 1; + SS_INIT_2 = 0; + } else { + SS_INIT = 3.14; + SS_INIT_2 = 0.0; + } + int mods = (ClassFile.ACC_STATIC | ClassFile.ACC_STRICT | + (finals ? ClassFile.ACC_FINAL : 0)); + byte[] classBytes = ClassFile.of().build(cn, clb -> { + clb.withFlags(ClassFile.ACC_FINAL); + clb.withVersion(ClassFile.latestMajorVersion(), ClassFile.PREVIEW_MINOR_VERSION); + clb.withField(SS_NAME, SS_TYPE, mods); + for (int i = 0; i < extraCount; i++) { + clb.withField(XS_NAME+i, XS_TYPE, mods); + clb.withField(PS_NAME+i, PS_TYPE, mods & ~ClassFile.ACC_STRICT); + } + clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ClassFile.ACC_STATIC, cob -> { + // always store into the extra strict static(s) + for (int i = 0; i < extraCount/2; i++) { + cob.loadConstant(i&1); + cob.putstatic(cn, XS_NAME+i, XS_TYPE); + } + if (readFlag < 0) { + // perform an early read, which must fail + cob.getstatic(cn, SS_NAME, SS_TYPE); + if (pop2) cob.pop2(); else cob.pop(); + } + // perform any writes on the test field + ConstantDesc initializer = SS_INIT; + for (int i = 0; i < writeCount; i++) { + if (i == 1 && readFlag > 0) { + // do an extra read after the first write + if (readFlag > 0) { + cob.getstatic(cn, SS_NAME, SS_TYPE); + if (pop2) cob.pop2(); else cob.pop(); + } + } + if (i > 0) initializer = SS_INIT_2; + cob.loadConstant(initializer); + cob.putstatic(cn, SS_NAME, SS_TYPE); + // if we write zero times we must fail + } + if (readFlag > 0) { + // do more extra reads + cob.getstatic(cn, SS_NAME, SS_TYPE); + if (prim) { + if (pop2) cob.pop2(); else cob.pop(); + } else { + cob.loadConstant(initializer); + var L_skip = cob.newLabel(); + cob.if_acmpeq(L_skip); + cob.loadConstant(null); + cob.athrow(); // NPE! + cob.labelBinding(L_skip); + } + } + // finish storing into the extra strict static(s) + for (int i = extraCount/2; i < extraCount; i++) { + cob.loadConstant(i&1); + cob.putstatic(cn, XS_NAME+i, XS_TYPE); + } + cob.return_(); + }); + }); + File c = new File(className + ".class"); + c.createNewFile(); + Files.write(c.toPath(), classBytes); + + var vererrs = ClassFile.of().verify(classBytes); + if (vererrs != null && !vererrs.isEmpty()) { + System.out.println(vererrs); + var cm = ClassFile.of().parse(classBytes); + System.out.println(cm.toDebugString()); + throw new AssertionError(); + } + ++COUNT; + try { + return LOOKUP.defineHiddenClass(classBytes, false).lookupClass(); + } catch (IllegalAccessException ex) { + throw new RuntimeException(ex); + } + } + + static int COUNT = 0; + + private static final Class STATIC_TYPES[] = { + Object.class, + String.class, + int.class, + boolean.class, + double.class + }; + + static boolean isSSFailure(Throwable ex) { + return ex instanceof ExceptionInInitializerError && + ex.getCause() instanceof IllegalStateException; + } + static void testPositives() throws Exception { + testPositives(false); + testPositives(true); + } + static void testPositives(boolean finals) throws Exception { + for (var staticType : STATIC_TYPES) { + for (int writeCount = 1; writeCount <= 3; writeCount++) { + for (byte readFlag = 0; readFlag <= 1; readFlag++) { + if (writeCount > readFlag) + continue; + for (int extraCount = 0; extraCount <= 3; extraCount++) { + if (extraCount > 0 && staticType != String.class) continue; + var cn = String.format("Positive_T%s%s_W%d%s%s", + staticType.getSimpleName(), + (finals ? "_SSFinal" : ""), + writeCount, + (readFlag > 0 ? "_Rafter" : + readFlag < 0 ? "_Rbefore" : ""), + (extraCount > 0 ? "_E"+extraCount : "")); + var cls = buildClass(cn, staticType, writeCount, readFlag, extraCount, finals); + try { + LOOKUP.ensureInitialized(cls); + if (VERBOSE) + System.out.printf("ok: %s: no throw\n", cn); + } catch (Throwable ex) { + reportThrow(false, ex, cn); + } + } + } + } + } + } + static void testFailedWrites() throws Exception { + testFailedWrites(false); + testFailedWrites(true); + } + static void testFailedWrites(boolean finals) throws Exception { + for (var staticType : STATIC_TYPES) { + for (int writeCount = 0; writeCount <= 2; writeCount++) { + for (byte readFlag = 0; readFlag <= 1; readFlag++) { + if (readFlag > 0 || writeCount > 0) { + if (!finals || writeCount < 2) + continue; + if (readFlag <= 0) + continue; // Mode 2 fails only with R between 2W: W-R-W + } + for (int extraCount = 0; extraCount <= 3; extraCount++) { + if (extraCount > 0 && staticType != String.class) continue; + var cn = String.format("BadWrite_T%s%s_W%d%s%s", + staticType.getSimpleName(), + (finals ? "_SSFinal" : ""), + writeCount, + (readFlag > 0 ? "_Rafter" : + readFlag < 0 ? "_Rbefore" : ""), + (extraCount > 0 ? "_E"+extraCount : "")); + var cls = buildClass(cn, staticType, writeCount, readFlag, extraCount, finals); + try { + LOOKUP.ensureInitialized(cls); + throw new RuntimeException(cn); + } catch (Throwable ex) { + reportThrow(isSSFailure(ex), ex, cn); + } + } + } + } + } + } + static void testFailedReads() throws Exception { + testFailedReads(false); + testFailedReads(true); + } + static void testFailedReads(boolean finals) throws Exception { + for (var staticType : STATIC_TYPES) { + for (int writeCount = 0; writeCount <= 1; writeCount++) { + for (byte readFlag = -1; readFlag <= -1; readFlag++) { + for (int extraCount = 0; extraCount <= 3; extraCount++) { + if (extraCount > 0 && staticType != String.class) continue; + var cn = String.format("BadRead_T%s%s_W%d_%s%s", + staticType.getSimpleName(), + (finals ? "_SSFinal" : ""), + writeCount, + (readFlag > 0 ? "_Rafter" : + readFlag < 0 ? "_Rbefore" : ""), + (extraCount > 0 ? "_E"+extraCount : "")); + var cls = buildClass(cn, staticType, writeCount, readFlag, extraCount, finals); + try { + LOOKUP.ensureInitialized(cls); + throw new RuntimeException(cn); + } catch (Throwable ex) { + reportThrow(isSSFailure(ex), ex, cn); + } + } + } + } + } + } + + static boolean VERBOSE = true; + + private static void reportThrow(boolean ok, Throwable ex, String cn) { + if (!ok) throw new RuntimeException(ex); + if (VERBOSE) { + if (ex instanceof ExceptionInInitializerError && ex.getCause() != null) + ex = ex.getCause(); + String exs = ex.toString(); + exs = exs.replaceFirst("^[^ ]*IllegalStateException: ", "ISE: "); + exs = exs.replaceFirst(" strictStatic[.][^ ]+/0x[^ ]+", "..."); + System.out.printf("%s: %s: %s\n", ok ? "ok" : "FAIL", cn, exs); + } + } + + + public static void main(String... av) throws Exception { + testPositives(); + testFailedWrites(); + testFailedReads(); + System.out.println("tested " + COUNT + " classes"); + System.out.println("Passed"); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/TryCatchChildBad.jasm b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/TryCatchChildBad.jasm new file mode 100644 index 00000000000..7bc46fdf631 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/TryCatchChildBad.jasm @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* +class TryCatchChild extends Parent { + @Strict int x; + @Strict int y; + + TryCatchChild() { + try { + x = 0; + int[] a = new int[1]; + System.out.println(a[2]); + } catch (java.lang.ArrayIndexOutOfBoundsException e) { + y = 0; + } + super(); + } +} +*/ + +identity class TryCatchChildBad extends Parent version 70:65535 +{ + @-jdk/internal/vm/annotation/Strict { } + strict Field x:I; + @-jdk/internal/vm/annotation/Strict { } + strict Field y:I; + + Method "":"()V" + stack 4 locals 2 + { + try T0, T1; + aload_0; + iconst_0; + putfield Field x:"I"; + iconst_1; + newarray int; + astore_1; + getstatic Field java/lang/System.out:"Ljava/io/PrintStream;"; + aload_1; + iconst_2; + iaload; + invokevirtual Method java/io/PrintStream.println:"(I)V"; + endtry T0,T1; + goto L63; + catch T0 java/lang/ArrayIndexOutOfBoundsException; + try T2; + stack_frame_type stack1; + stack_map class java/lang/ArrayIndexOutOfBoundsException; + astore_1; + aload_0; + iconst_0; + putfield Field y:"I"; + endtry T2; + goto L63; + catch T1 #0; + catch T2 #0; + stack_frame_type stack1; + stack_map class java/lang/Throwable; + astore_2; + aload_2; + athrow; + L63: stack_frame_type early_larval; + unset_fields; + frame_type same; + aload_0; + invokespecial Method Parent."":"()V"; + return; + } +} // end Class TryCatchChildBad diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitThisAcmp.jasm b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitThisAcmp.jasm new file mode 100644 index 00000000000..d56a9c5a1bd --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitThisAcmp.jasm @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +class UninitThisAcmp version 69:0 +{ + public Method "":"()V" + stack 2 locals 2 + { + new class java/lang/Object; + dup; + invokespecial Method java/lang/Object."":"()V"; + astore_1; + aload_0; + aload_1; + if_acmpne L14; + nop; + L14: stack_frame_type append; + locals_map class java/lang/Object; + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } +} // end Class UninitThisAcmp diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitThisAcmpOld.jasm b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitThisAcmpOld.jasm new file mode 100644 index 00000000000..582ed5285bc --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitThisAcmpOld.jasm @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +class UninitThisAcmpOld version 49:0 +{ + public Method "":"()V" + stack 2 locals 2 + { + new class java/lang/Object; + dup; + invokespecial Method java/lang/Object."":"()V"; + astore_1; + aload_0; + aload_1; + if_acmpne L14; + nop; + L14: invokespecial Method java/lang/Object."":"()V"; + return; + } +} // end Class UninitThisAcmpOld diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitThisIfNull.jasm b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitThisIfNull.jasm new file mode 100644 index 00000000000..9b31e357d86 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitThisIfNull.jasm @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +class UninitThisIfNull version 69:0 +{ + public Method "":"()V" + stack 2 locals 2 + { + new class java/lang/Object; + dup; + invokespecial Method java/lang/Object."":"()V"; + astore_1; + aload_0; + ifnonnull L14; + nop; + L14: stack_frame_type append; + locals_map class java/lang/Object; + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } +} // end Class UninitThisIfNull diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitThisIfNullOld.jasm b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitThisIfNullOld.jasm new file mode 100644 index 00000000000..9391f0c23d0 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitThisIfNullOld.jasm @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +class UninitThisIfNullOld version 49:0 +{ + public Method "":"()V" + stack 2 locals 2 + { + new class java/lang/Object; + dup; + invokespecial Method java/lang/Object."":"()V"; + astore_1; + aload_0; + ifnonnull L14; + nop; + L14: aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } +} // end Class UninitThisIfNullOld diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitializedAcmp.jasm b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitializedAcmp.jasm new file mode 100644 index 00000000000..c4e070afcfb --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitializedAcmp.jasm @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +class UninitializedAcmp version 69:0 +{ + Method "":"()V" + stack 5 locals 1 + { + aload_0; + invokespecial Method java/lang/Object."":"()V"; + aload_0; + L1: new class java/lang/Object; + dup; + dup; + dup; + if_acmpne L18; + nop; + L18: stack_frame_type full; + locals_map class UninitializedAcmp; + stack_map class UninitializedAcmp, at L1, at L1; + invokespecial Method java/lang/Object."":"()V"; + return; + } +} // end Class UninitializedAcmp diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitializedAcmpOld.jasm b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitializedAcmpOld.jasm new file mode 100644 index 00000000000..ce93c00a678 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitializedAcmpOld.jasm @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +class UninitializedAcmpOld version 49:0 +{ + Method "":"()V" + stack 5 locals 1 + { + aload_0; + invokespecial Method java/lang/Object."":"()V"; + aload_0; + L1: new class java/lang/Object; + dup; + dup; + dup; + if_acmpne L18; + nop; + L18: invokespecial Method java/lang/Object."":"()V"; + return; + } +} // end Class UninitializedAcmpOld diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitializedIfNull.jasm b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitializedIfNull.jasm new file mode 100644 index 00000000000..1465c52cdd9 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitializedIfNull.jasm @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +class UninitializedIfNull version 69:0 +{ + Method "":"()V" + stack 3 locals 1 + { + aload_0; + invokespecial Method java/lang/Object."":"()V"; + L1: new class java/lang/Object; + dup; + dup; + ifnonnull L18; + nop; + L18: stack_frame_type full; + locals_map class UninitializedIfNull; + stack_map at L1, at L1; + invokespecial Method java/lang/Object."":"()V"; + return; + } +} // end Class UninitializedIfNull diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitializedIfNullOld.jasm b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitializedIfNullOld.jasm new file mode 100644 index 00000000000..605ce466cc5 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitializedIfNullOld.jasm @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +class UninitializedIfNullOld version 49:0 +{ + Method "":"()V" + stack 3 locals 1 + { + aload_0; + invokespecial Method java/lang/Object."":"()V"; + L1: new class java/lang/Object; + dup; + dup; + ifnonnull L18; + nop; + L18: invokespecial Method java/lang/Object."":"()V"; + return; + } +} // end Class UninitializedIfNullOld diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitializedThisVerificationTest.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitializedThisVerificationTest.java new file mode 100644 index 00000000000..85690298575 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/UninitializedThisVerificationTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8366743 + * @summary Test spec rules for uninitialized + * @compile UninitThisAcmp.jasm UninitThisIfNull.jasm + * UninitializedIfNull.jasm UninitializedAcmp.jasm + * UninitThisAcmpOld.jasm UninitThisIfNullOld.jasm + * UninitializedAcmpOld.jasm UninitializedIfNullOld.jasm + * @run main/othervm -Xlog:verification UninitializedThisVerificationTest + */ + +public class UninitializedThisVerificationTest { + + public static void main(String[] args) throws Exception { + String[] testNames = { "UninitThisAcmp", "UninitThisIfNull", + "UninitializedAcmp", "UninitializedIfNull", + "UninitThisAcmpOld", "UninitThisIfNullOld", + "UninitializedAcmpOld", "UninitializedIfNullOld" }; + int fails = 0; + for (String test : testNames) { + System.out.println("Testing " + test); + try { + Class c = Class.forName(test); + System.out.println("Failed"); + fails++; + } catch (java.lang.VerifyError e) { + System.out.println("Passed"); + } + } + + if (fails > 0) { + throw new RuntimeException("Failed: Expected VerifyError in " + fails + " tests"); + } + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/VTAssignability.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/VTAssignability.java new file mode 100644 index 00000000000..192f5ef9b11 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/VTAssignability.java @@ -0,0 +1,87 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Test basic verifier assignability of inline types. + * @enablePreview + * @compile VTAssignability.java + * @run main/othervm -Xverify:remote VTAssignability + */ + +// Test that an inline type is assignable to itself, to java.lang.Object, +// to an abstract super type and to an interface, +// +interface II { } + +abstract value class AbstractValue { } + +public value class VTAssignability extends AbstractValue implements II { + final int x; + final int y; + + public VTAssignability(int x, int y) { + this.x = x; + this.y = y; + } + + public int getX() { return x; } + public int getY() { return y; } + + public boolean isSameVTAssignability(VTAssignability that) { + return this.getX() == that.getX() && this.getY() == that.getY(); + } + + public boolean equals(Object o) { + if(o instanceof VTAssignability) { + return ((VTAssignability)o).x == x && ((VTAssignability)o).y == y; + } else { + return false; + } + } + + public void takesAbstractSuper(AbstractValue val) { + System.out.println("Test passes for abstract super"); + } + + public void takesInterface(II i) { + System.out.println("Test passes for interfaces"); + } + + public static void main(String[] args) { + VTAssignability a = new VTAssignability(3, 4); + VTAssignability b = new VTAssignability(2, 4); + + // Test assignability of an inline type to itself. + boolean res = a.isSameVTAssignability(b); + + // Test assignability of an inline type to java.lang.Object. + res = b.equals(a); + + // Test assignability of an inline type to an abstract super type. + a.takesAbstractSuper(b); + + // Test assignability of an inline type to an interface. + a.takesInterface(b); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/VTMonitor.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/VTMonitor.java new file mode 100644 index 00000000000..5a5026e2f53 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/VTMonitor.java @@ -0,0 +1,52 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8227373 + * @summary Test that verifier allows monitor operations on inline types. + * @enablePreview + * @compile VTMonitor.java + * @run main/othervm -Xverify:remote VTMonitor + */ + +public value final class VTMonitor { + final int x; + final int y; + + public VTMonitor(int x, int y) { + this.x = x; + this.y = y; + } + + public static void main(String[] args) { + Object a = new VTMonitor(3, 4); + try { + synchronized(a) { + throw new RuntimeException("Synchronization on inline type should fail"); + } + } catch (java.lang.IdentityException e) { + // Expected + } + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/VerifierInlineTypes.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/VerifierInlineTypes.java new file mode 100644 index 00000000000..122d2f4a336 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/VerifierInlineTypes.java @@ -0,0 +1,105 @@ +/* + * 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 + * 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. + * + */ +/* + * @ignore Fix JDK-8328423 + * @test + * @bug 8223028 + * @summary test that the right exceptions get thrown for bad inline type + * class files. + * @enablePreview + * @compile verifierTests.jcod + * @run main/othervm -verify VerifierInlineTypes + */ + +public class VerifierInlineTypes { + + public static void runTestVerifyError(String test_name, String message) throws Exception { + System.out.println("Testing: " + test_name); + try { + Class newClass = Class.forName(test_name); + throw new RuntimeException("Expected VerifyError exception not thrown"); + } catch (java.lang.VerifyError e) { + if (!e.getMessage().contains(message)) { + throw new RuntimeException("Wrong VerifyError: " + e.getMessage()); + } + } + } + + public static void runTestFormatError(String test_name, String message) throws Exception { + System.out.println("Testing: " + test_name); + try { + Class newClass = Class.forName(test_name); + throw new RuntimeException("Expected ClassFormatError exception not thrown"); + } catch (java.lang.ClassFormatError e) { + if (!e.getMessage().contains(message)) { + throw new RuntimeException("Wrong ClassFormatError: " + e.getMessage()); + } + } + } + + public static void runTestNoError(String test_name) throws Exception { + System.out.println("Testing: " + test_name); + Class newClass = Class.forName(test_name); + } + + public static void main(String[] args) throws Exception { + + // Test that a aconst_init opcode with an out of bounds cp index causes a VerifyError. + runTestVerifyError("defValBadCP", "Illegal constant pool index"); + + // Test that ClassFormatError is thrown for a class file, with major version 54, that + // contains a aconst_init opcode. + runTestFormatError("defValBadMajorVersion", "aconst_init not supported by this class file version"); + + // Test VerifyError is thrown if a aconst_init's cp entry is not a class. + runTestVerifyError("defValWrongCPType", "Illegal type at constant pool entry"); + + // Test that a withfield opcode with an out of bounds cp index causes a VerifyError. + runTestVerifyError("wthFldBadCP", "Illegal constant pool index"); + + // Test that VerifyError is thrown if the first operand on the stack is not assignable + // to withfield's field. + runTestVerifyError("wthFldBadFldVal", "Bad type on operand stack"); + + // Test that VerifyError is thrown if the second operand on the stack is a primitive. + runTestVerifyError("wthFldBadFldRef", "Bad type on operand stack"); + + // Test that ClassFormatError is thrown for a class file, with major version 54, that + // contains a withfield opcode. + runTestFormatError("wthFldBadMajorVersion", "withfield not supported by this class file version"); + + // Test VerifyError is thrown if a withfields's cp entry is not a field. + runTestVerifyError("wthFldWrongCPType", "Illegal type at constant pool entry"); + + // Test VerifyError is thrown if a aconst_init's cp entry is not an inline type. + runTestVerifyError("defValueObj", "Illegal type at constant pool entry 4"); + + // Test that the verifier doesn't require that a withfield bytecode has a Q type operand. + Class newClass = Class.forName("withfieldL"); + + // Test that null is not assignable to an inline type. + runTestVerifyError("NoNullVT", + "Type null (current frame, stack[1]) is not assignable to 'QNoNullVT;'"); + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/WriteAfterReadRefl.jasm b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/WriteAfterReadRefl.jasm new file mode 100644 index 00000000000..167fe1c9d2e --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/WriteAfterReadRefl.jasm @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* +class WriteAfterReadRefl { + @Strict static final String F1__STRICT; + @Strict static final int F2__STRICT; + static { + // Bytecode write + F1__STRICT = "foo"; + F2__STRICT = 42; + + + // Reflective read + Field FIELD_F2 = findField(WriteAfterReadRefl.class, "F2__STRICT"); + int x = (int) getstaticReflective(FIELD_F2); + + // Bytecode write after read (FAIL) + F2__STRICT = 43; + } + } +*/ + +super class WriteAfterReadRefl version 70:65535 +{ + @-jdk/internal/vm/annotation/Strict { } + strict static final Field F1__STRICT:"Ljava/lang/String;"; + @-jdk/internal/vm/annotation/Strict { } + strict static final Field F2__STRICT:I; + + Method "":"(LStrictStaticFieldsTest;)V" + stack 2 locals 2 + 0: #{ #0 final mandated } + { + aload_1; + dup; + invokestatic Method java/util/Objects.requireNonNull:"(Ljava/lang/Object;)Ljava/lang/Object;"; + pop; + pop; + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } + + static Method "":"()V" + stack 2 locals 2 + { + ldc String "foo"; + putstatic Field F1__STRICT:"Ljava/lang/String;"; + bipush 42; + putstatic Field F2__STRICT:"I"; + ldc class WriteAfterReadRefl; + ldc String "F2__STRICT"; + invokestatic Method StrictStaticFieldsTest.findField:"(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/reflect/Field;"; + astore_0; + aload_0; + invokestatic Method StrictStaticFieldsTest.getstaticReflective:"(Ljava/lang/reflect/Field;)Ljava/lang/Object;"; + checkcast class java/lang/Integer; + invokevirtual Method java/lang/Integer.intValue:"()I"; + istore_1; + bipush 43; + putstatic Field F2__STRICT:"I"; + return; + } +} // end Class WriteAfterReadRefl diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/strictFields.jasm b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/strictFields.jasm new file mode 100644 index 00000000000..5c005705d13 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/strictFields.jasm @@ -0,0 +1,101 @@ +/* + * 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. + */ + +// Defines the classes used by StrictFields.java to check verification +// of strict fields. + +// StrictIgnore is an earlier classfile version for which strict must be ignored +// so we can write to it multiple times in the constructor. +public class StrictIgnore version 66:0 { + final strict Field x:I; + + public Method "":"()V" stack 2 { + aload_0; + iconst_1; + putfield x:I; + aload_0; + invokespecial Method java/lang/Object."":"()V"; + aload_0; + iconst_1; + putfield x:I; + return; + } +} + +// StrictBase is a well formed value class with a strict field, that can +// be used for subclassing. +public abstract class StrictBase version 70:65535 { + protected final strict Field x:I; + + public Method "":"()V" stack 2 { + aload_0; + iconst_1; + putfield x:I; + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } +} + +// PostInitStrict is a bad value class that writes to a strict field after the +// super constructor call. +public final class PostInitStrict version 70:65535 { + final strict Field y:I; + + public Method "":"()V" stack 2 { + aload_0; + invokespecial Method java/lang/Object."":"()V"; + aload_0; + iconst_1; + putfield y:I; + return; + } +} + +// BadStrictSubPreInit is a bad value class that tries to write to an inherited +// strict field while acting on UninitializedThis. +public final class BadStrictSubPreInit extends StrictBase version 70:65535 { + + public Method "":"()V" stack 2 { + aload_0; + iconst_1; + putfield x:I; + aload_0; + invokespecial Method StrictBase."":"()V"; + return; + } +} + +// BadStrictSubPostInit is a bad value class that tries to write to an inherited +// strict field in "regular" code. +public final class BadStrictSubPostInit extends StrictBase version 70:65535 { + + public Method "":"()V" stack 2 { + aload_0; + invokespecial Method StrictBase."":"()V"; + aload_0; + iconst_1; + putfield x:I; + return; + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/verifierTests.jcod b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/verifierTests.jcod new file mode 100644 index 00000000000..55e054f9915 --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/verifier/verifierTests.jcod @@ -0,0 +1,1608 @@ +/* + * 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 + * 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. + * + */ + +// Many of the jcod classes in this file were derived from this Java inline type: +// +// final inline class Value { +// static final Value VT = makeValue(0x01234567); +// final int int_v; +// Value() { +// int_v = 1; +// } +// static Value makeValue(int x) { +// Value v = Value.default; +// v = __WithField(v.int_v, x); +// return v; +// } +// } +// +// The changes for each test were made to the bytecodes for method makeValue(int x). +// Its bytecodes are: +// +// static Value makeValue(int); descriptor: (I)LValue; flags: (0x0008) ACC_STATIC +// Code: +// stack=2, locals=2, args_size=1 +// 0: aconst_init #3 // class Value +// 3: astore_1 +// 4: aload_1 +// 5: iload_0 +// 6: withfield #2 // Field int_v:I +// 9: astore_1 +// 10: aload_1 +// 11: areturn + + +// The constant pool index of the aconst_init opcode (0xCB) in the Code +// attribute was changed to 0x93. Since this index is outside the range of +// the constant pool, a VerifyError exception should get thrown. +// +class defValBadCP { + 0xCAFEBABE; + 0; // minor version + 70; // version + [27] { // Constant Pool + ; // first element is empty + Method #7 #21; // #1 at 0x0A + Field #3 #22; // #2 at 0x0F + class #23; // #3 at 0x14 + int 0x01234567; // #4 at 0x17 + Method #3 #24; // #5 at 0x1C + Field #3 #25; // #6 at 0x21 + class #26; // #7 at 0x26 + Utf8 "VT"; // #8 at 0x29 + Utf8 "LdefValBadCP;"; // #9 at 0x2E + Utf8 "int_v"; // #10 at 0x38 + Utf8 "I"; // #11 at 0x40 + Utf8 ""; // #12 at 0x44 + Utf8 "()V"; // #13 at 0x4D + Utf8 "Code"; // #14 at 0x53 + Utf8 "LineNumberTable"; // #15 at 0x5A + Utf8 "makeValue"; // #16 at 0x6C + Utf8 "(I)LdefValBadCP;"; // #17 at 0x78 + Utf8 ""; // #18 at 0x85 + Utf8 "SourceFile"; // #19 at 0x90 + Utf8 "defValBadCP.java"; // #20 at 0x9D + NameAndType #12 #13; // #21 at 0xAA + NameAndType #10 #11; // #22 at 0xAF + Utf8 "defValBadCP"; // #23 at 0xB4 + NameAndType #16 #17; // #24 at 0xBC + NameAndType #8 #9; // #25 at 0xC1 + Utf8 "java/lang/Object"; // #26 at 0xC6 + } // Constant Pool + + 0x0850; // access [ ACC_VALUE ACC_PRIMITIVE ACC_FINAL ] + #3;// this_cpx + #7;// super_cpx + + [0] { // Interfaces + } // Interfaces + + [2] { // fields + { // Member at 0xE3 + 0x0018; // access + #8; // name_cpx + #9; // sig_cpx + [0] { // Attributes + } // Attributes + } // Member + ; + { // Member at 0xEB + 0x0010; // access + #10; // name_cpx + #11; // sig_cpx + [0] { // Attributes + } // Attributes + } // Member + } // fields + + [1] { // methods + { // Member at 0x012D + 0x0008; // access + #16; // name_cpx + #17; // sig_cpx + [1] { // Attributes + Attr(#14, 44) { // Code at 0x0135 + 2; // max_stack + 2; // max_locals + Bytes[12]{ + 0xCB00934C2B1ACC00; // Changed CP index from 3 to 0x93 for opcode 0xCB (aconst_init) + 0x024C2BB0; // so that the index is outside of the range of the constant pool. + }; + [0] { // Traps + } // end Traps + [1] { // Attributes + Attr(#15, 14) { // LineNumberTable at 0x0153 + [3] { // LineNumberTable + 0 8; // at 0x015F + 4 9; // at 0x0163 + 10 10; // at 0x0167 + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } // Member + } // methods + + [1] { // Attributes + Attr(#19, 2) { // SourceFile at 0x0198 + #20; + } // end SourceFile + } // Attributes +} // end class defValBadCP + +/////////////////////////////////////////////////////////// + +// The class's major version was changed to 54. Since this class has a +// aconst_init opcode (0xCB), this should cause a ClassFormatError +// exception to get thrown. +// +class defValBadMajorVersion { + 0xCAFEBABE; + 0; // minor version + 54; // version + [27] { // Constant Pool + ; // first element is empty + Method #7 #21; // #1 at 0x0A + Field #3 #22; // #2 at 0x0F + class #23; // #3 at 0x14 + int 0x01234567; // #4 at 0x17 + Method #3 #24; // #5 at 0x1C + Field #3 #25; // #6 at 0x21 + class #26; // #7 at 0x26 + Utf8 "VT"; // #8 at 0x29 + Utf8 "LdefValBadMajorVersion;"; // #9 at 0x2E + Utf8 "int_v"; // #10 at 0x38 + Utf8 "I"; // #11 at 0x40 + Utf8 ""; // #12 at 0x44 + Utf8 "()V"; // #13 at 0x4D + Utf8 "Code"; // #14 at 0x53 + Utf8 "LineNumberTable"; // #15 at 0x5A + Utf8 "makeValue"; // #16 at 0x6C + Utf8 "(I)LdefValBadMajorVersion;"; // #17 at 0x78 + Utf8 ""; // #18 at 0x85 + Utf8 "SourceFile"; // #19 at 0x90 + Utf8 "defValBadMajorVersion.java"; // #20 at 0x9D + NameAndType #12 #13; // #21 at 0xAA + NameAndType #10 #11; // #22 at 0xAF + Utf8 "defValBadMajorVersion"; // #23 at 0xB4 + NameAndType #16 #17; // #24 at 0xBC + NameAndType #8 #9; // #25 at 0xC1 + Utf8 "java/lang/Object"; // #26 at 0xC6 + } // Constant Pool + + 0x0850; // access [ ACC_VALUE ACC_PRIMITIVE ACC_FINAL ] + #3;// this_cpx + #7;// super_cpx + + [0] { // Interfaces + } // Interfaces + + [2] { // fields + { // Member at 0xE3 + 0x0018; // access + #8; // name_cpx + #9; // sig_cpx + [0] { // Attributes + } // Attributes + } // Member + ; + { // Member at 0xEB + 0x0010; // access + #10; // name_cpx + #11; // sig_cpx + [0] { // Attributes + } // Attributes + } // Member + } // fields + + [1] { // methods + { // Member at 0x012D + 0x0008; // access + #16; // name_cpx + #17; // sig_cpx + [1] { // Attributes + Attr(#14, 44) { // Code at 0x0135 + 2; // max_stack + 2; // max_locals + Bytes[12]{ + 0xCB00034C2B1ACC00; + 0x024C2BB0; + }; + [0] { // Traps + } // end Traps + [1] { // Attributes + Attr(#15, 14) { // LineNumberTable at 0x0153 + [3] { // LineNumberTable + 0 8; // at 0x015F + 4 9; // at 0x0163 + 10 10; // at 0x0167 + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } // Member + } // methods + + [1] { // Attributes + Attr(#19, 2) { // SourceFile at 0x0198 + #20; + } // end SourceFile + } // Attributes +} // end class defValBadMajorVersion + +/////////////////////////////////////////////////////////// + +// The constant pool index of a aconst_init opcode (0xCB) in the Code +// attribute was changed to 2. Since this index now points to a Field +// entry instead of a Class entry, a VerifyError exception should get thrown. +// +class defValWrongCPType { + 0xCAFEBABE; + 0; // minor version + 70; // version + [27] { // Constant Pool + ; // first element is empty + Method #7 #21; // #1 at 0x0A + Field #3 #22; // #2 at 0x0F + class #23; // #3 at 0x14 + int 0x01234567; // #4 at 0x17 + Method #3 #24; // #5 at 0x1C + Field #3 #25; // #6 at 0x21 + class #26; // #7 at 0x26 + Utf8 "VT"; // #8 at 0x29 + Utf8 "LdefValWrongCPType;"; // #9 at 0x2E + Utf8 "int_v"; // #10 at 0x38 + Utf8 "I"; // #11 at 0x40 + Utf8 ""; // #12 at 0x44 + Utf8 "()V"; // #13 at 0x4D + Utf8 "Code"; // #14 at 0x53 + Utf8 "LineNumberTable"; // #15 at 0x5A + Utf8 "makeValue"; // #16 at 0x6C + Utf8 "(I)LdefValWrongCPType;"; // #17 at 0x78 + Utf8 ""; // #18 at 0x85 + Utf8 "SourceFile"; // #19 at 0x90 + Utf8 "defValWrongCPType.java"; // #20 at 0x9D + NameAndType #12 #13; // #21 at 0xAA + NameAndType #10 #11; // #22 at 0xAF + Utf8 "defValWrongCPType"; // #23 at 0xB4 + NameAndType #16 #17; // #24 at 0xBC + NameAndType #8 #9; // #25 at 0xC1 + Utf8 "java/lang/Object"; // #26 at 0xC6 + } // Constant Pool + + 0x0850; // access [ ACC_VALUE ACC_PRIMITIVE ACC_FINAL ] + #3;// this_cpx + #7;// super_cpx + + [0] { // Interfaces + } // Interfaces + + [2] { // fields + { // Member at 0xE3 + 0x0018; // access + #8; // name_cpx + #9; // sig_cpx + [0] { // Attributes + } // Attributes + } // Member + ; + { // Member at 0xEB + 0x0010; // access + #10; // name_cpx + #11; // sig_cpx + [0] { // Attributes + } // Attributes + } // Member + } // fields + + [1] { // methods + { // Member at 0x012D + 0x0008; // access + #16; // name_cpx + #17; // sig_cpx + [1] { // Attributes + Attr(#14, 44) { // Code at 0x0135 + 2; // max_stack + 2; // max_locals + Bytes[12]{ + 0xCB00024C2B1ACC00; // Changed CP index from 3 to 2 for opcode 0xCB (aconst_init) + 0x024C2BB0; // so that the cp index no longer points to a cp Class entry. + }; + [0] { // Traps + } // end Traps + [1] { // Attributes + Attr(#15, 14) { // LineNumberTable at 0x0153 + [3] { // LineNumberTable + 0 8; // at 0x015F + 4 9; // at 0x0163 + 10 10; // at 0x0167 + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } // Member + } // methods + + [1] { // Attributes + Attr(#19, 2) { // SourceFile at 0x0198 + #20; + } // end SourceFile + } // Attributes +} // end class defValWrongCPType + +/////////////////////////////////////////////////////////// + +// The constant pool index of the withfield opcode (0xCC) in the Code +// attribute was changed to 0x82. Since this index is outside the range of +// the constant pool, a VerifyError exception should get thrown. +// +class wthFldBadCP { + 0xCAFEBABE; + 0; // minor version + 70; // version + [20] { // Constant Pool + ; // first element is empty + Method #4 #17; // #1 at 0x0A + Field #3 #18; // #2 at 0x0F + class #12; // #3 at 0x14 + class #19; // #4 at 0x17 + Utf8 "int_v"; // #5 at 0x1A + Utf8 "I"; // #6 at 0x22 + Utf8 ""; // #7 at 0x26 + Utf8 "()V"; // #8 at 0x2F + Utf8 "Code"; // #9 at 0x35 + Utf8 "LineNumberTable"; // #10 at 0x3C + Utf8 "makewthFldBadCP"; // #11 at 0x4E + Utf8 "wthFldBadCP"; // #12 at 0x60 + Utf8 "ValueTypes"; // #13 at 0x6E + Utf8 "(I)LwthFldBadCP;"; // #14 at 0x7B + Utf8 "SourceFile"; // #15 at 0x8E + Utf8 "wthFldBadCP.java"; // #16 at 0x9B + NameAndType #7 #8; // #17 at 0xAE + NameAndType #5 #6; // #18 at 0xB3 + Utf8 "java/lang/Object"; // #19 at 0xB8 + } // Constant Pool + + 0x0850; // access [ ACC_VALUE ACC_PRIMITIVE ACC_FINAL ] + #3;// this_cpx + #4;// super_cpx + + [0] { // Interfaces + } // Interfaces + + [1] { // fields + { // Member at 0xD5 + 0x0010; // access + #5; // name_cpx + #6; // sig_cpx + [0] { // Attributes + } // Attributes + } // Member + } // fields + + [1] { // methods + { // Member at 0x0117 + 0x0008; // access + #11; // name_cpx + #14; // sig_cpx + [1] { // Attributes + Attr(#9, 44) { // Code at 0x011F + 2; // max_stack + 2; // max_locals + Bytes[12]{ + 0xCB00034C2B1ACC00; // Changed CP index from 2 to 0x82 for opcode 0xCC (withfield) + 0x824C2BB0; // so that the index is outside of the range of the constant pool. + }; + [0] { // Traps + } // end Traps + [1] { // Attributes + Attr(#10, 14) { // LineNumberTable at 0x013D + [3] { // LineNumberTable + 0 8; // at 0x0149 + 4 9; // at 0x014D + 10 10; // at 0x0151 + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } // Member + } // methods + + [2] { // Attributes + Attr(#15, 2) { // SourceFile at 0x0153 + #16; + } // end SourceFile + ; + Attr(#13, 4) { // ValueTypes at 0x015B + 0x00010003; + } // end ValueTypes + } // Attributes +} // end class wthFldBadCP + +/////////////////////////////////////////////////////////// + +// The opcode at bytecode position 5 in the Code array was changed to aload_1 +// (0x2B). This should cause a VerifyError because now the first operand on the +// stack for the withfield opcode (0xCC at bytecode position 6) does not match +// the type (int) of the field being assigned to. +// +class wthFldBadFldVal { + 0xCAFEBABE; + 0; // minor version + 70; // version + [20] { // Constant Pool + ; // first element is empty + Method #4 #17; // #1 at 0x0A + Field #3 #18; // #2 at 0x0F + class #12; // #3 at 0x14 + class #19; // #4 at 0x17 + Utf8 "int_v"; // #5 at 0x1A + Utf8 "I"; // #6 at 0x22 + Utf8 ""; // #7 at 0x26 + Utf8 "()V"; // #8 at 0x2F + Utf8 "Code"; // #9 at 0x35 + Utf8 "LineNumberTable"; // #10 at 0x3C + Utf8 "makewthFldBadFldVal"; // #11 at 0x4E + Utf8 "wthFldBadFldVal"; // #12 at 0x60 + Utf8 "ValueTypes"; // #13 at 0x6E + Utf8 "(I)LwthFldBadFldVal;"; // #14 at 0x7B + Utf8 "SourceFile"; // #15 at 0x8E + Utf8 "wthFldBadFldVal.java"; // #16 at 0x9B + NameAndType #7 #8; // #17 at 0xAE + NameAndType #5 #6; // #18 at 0xB3 + Utf8 "java/lang/Object"; // #19 at 0xB8 + } // Constant Pool + + 0x0850; // access [ ACC_VALUE ACC_PRIMITIVE ACC_FINAL ] + #3;// this_cpx + #4;// super_cpx + + [0] { // Interfaces + } // Interfaces + + [1] { // fields + { // Member at 0xD5 + 0x0010; // access + #5; // name_cpx + #6; // sig_cpx + [0] { // Attributes + } // Attributes + } // Member + } // fields + + [1] { // methods + { // Member at 0x0117 + 0x0008; // access + #11; // name_cpx + #14; // sig_cpx + [1] { // Attributes + Attr(#9, 44) { // Code at 0x011F + 2; // max_stack + 2; // max_locals + Bytes[12]{ + 0xCB00034C2B2BCC00; // Changed opcode at bytecode 5 from iload_0 to aload_1 + 0x024C2BB0; + }; + [0] { // Traps + } // end Traps + [1] { // Attributes + Attr(#10, 14) { // LineNumberTable at 0x013D + [3] { // LineNumberTable + 0 8; // at 0x0149 + 4 9; // at 0x014D + 10 10; // at 0x0151 + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } // Member + } // methods + + [2] { // Attributes + Attr(#15, 2) { // SourceFile at 0x0153 + #16; + } // end SourceFile + ; + Attr(#13, 4) { // ValueTypes at 0x015B + 0x00010003; + } // end ValueTypes + } // Attributes +} // end class wthFldBadFldVal + +/////////////////////////////////////////////////////////// + +// The opcode at bytecode position 4 in the Code array was changed to iload_1 +// (0x1A). This should cause a VerifyError because the second operand on the stack +// for the withfield opcode (0xCC at bytecode position 6) must be a reference. +// +class wthFldBadFldRef { + 0xCAFEBABE; + 0; // minor version + 70; // version + [20] { // Constant Pool + ; // first element is empty + Method #4 #17; // #1 at 0x0A + Field #3 #18; // #2 at 0x0F + class #12; // #3 at 0x14 + class #19; // #4 at 0x17 + Utf8 "int_v"; // #5 at 0x1A + Utf8 "I"; // #6 at 0x22 + Utf8 ""; // #7 at 0x26 + Utf8 "()V"; // #8 at 0x2F + Utf8 "Code"; // #9 at 0x35 + Utf8 "LineNumberTable"; // #10 at 0x3C + Utf8 "makewthFldBadFldRef"; // #11 at 0x4E + Utf8 "wthFldBadFldRef"; // #12 at 0x60 + Utf8 "ValueTypes"; // #13 at 0x6E + Utf8 "(I)LwthFldBadFldRef;"; // #14 at 0x7B + Utf8 "SourceFile"; // #15 at 0x8E + Utf8 "wthFldBadFldRef.java"; // #16 at 0x9B + NameAndType #7 #8; // #17 at 0xAE + NameAndType #5 #6; // #18 at 0xB3 + Utf8 "java/lang/Object"; // #19 at 0xB8 + } // Constant Pool + + 0x0850; // access [ ACC_VALUE ACC_PRIMITIVE ACC_FINAL ] + #3;// this_cpx + #4;// super_cpx + + [0] { // Interfaces + } // Interfaces + + [1] { // fields + { // Member at 0xD5 + 0x0010; // access + #5; // name_cpx + #6; // sig_cpx + [0] { // Attributes + } // Attributes + } // Member + } // fields + + [1] { // methods + { // Member at 0x0117 + 0x0008; // access + #11; // name_cpx + #14; // sig_cpx + [1] { // Attributes + Attr(#9, 44) { // Code at 0x011F + 2; // max_stack + 2; // max_locals + Bytes[12]{ + 0xCB00034C1A1ACC00; // Changed opcode at bytecode 4 from aload_1 to iload_0 + 0x024C2BB0; + }; + [0] { // Traps + } // end Traps + [1] { // Attributes + Attr(#10, 14) { // LineNumberTable at 0x013D + [3] { // LineNumberTable + 0 8; // at 0x0149 + 4 9; // at 0x014D + 10 10; // at 0x0151 + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } // Member + } // methods + + [2] { // Attributes + Attr(#15, 2) { // SourceFile at 0x0153 + #16; + } // end SourceFile + ; + Attr(#13, 4) { // ValueTypes at 0x015B + 0x00010003; + } // end ValueTypes + } // Attributes +} // end class wthFldBadFldRef + +/////////////////////////////////////////////////////////// + +// The class's major version was changed to 54 and the first opcode in the Code +// attribute was changed to a withfield (0xCC).. Since withfield opcodes are not +// allowed in classes with major version 54, this should cause a ClassFormatError +// exception to get thrown. +// +class wthFldBadMajorVersion { + 0xCAFEBABE; + 0; // minor version + 54; // version + [27] { // Constant Pool + ; // first element is empty + Method #7 #21; // #1 at 0x0A + Field #3 #22; // #2 at 0x0F + class #23; // #3 at 0x14 + int 0x01234567; // #4 at 0x17 + Method #3 #24; // #5 at 0x1C + Field #3 #25; // #6 at 0x21 + class #26; // #7 at 0x26 + Utf8 "VT"; // #8 at 0x29 + Utf8 "LwthFldBadMajorVersion;"; // #9 at 0x2E + Utf8 "int_v"; // #10 at 0x38 + Utf8 "I"; // #11 at 0x40 + Utf8 ""; // #12 at 0x44 + Utf8 "()V"; // #13 at 0x4D + Utf8 "Code"; // #14 at 0x53 + Utf8 "LineNumberTable"; // #15 at 0x5A + Utf8 "makeValue"; // #16 at 0x6C + Utf8 "(I)LwthFldBadMajorVersion;"; // #17 at 0x78 + Utf8 ""; // #18 at 0x85 + Utf8 "SourceFile"; // #19 at 0x90 + Utf8 "wthFldBadMajorVersion.java"; // #20 at 0x9D + NameAndType #12 #13; // #21 at 0xAA + NameAndType #10 #11; // #22 at 0xAF + Utf8 "wthFldBadMajorVersion"; // #23 at 0xB4 + NameAndType #16 #17; // #24 at 0xBC + NameAndType #8 #9; // #25 at 0xC1 + Utf8 "java/lang/Object"; // #26 at 0xC6 + } // Constant Pool + + 0x0850; // access [ ACC_VALUE ACC_PRIMITIVE ACC_FINAL ] + #3;// this_cpx + #7;// super_cpx + + [0] { // Interfaces + } // Interfaces + + [2] { // fields + { // Member at 0xE3 + 0x0018; // access + #8; // name_cpx + #9; // sig_cpx + [0] { // Attributes + } // Attributes + } // Member + ; + { // Member at 0xEB + 0x0010; // access + #10; // name_cpx + #11; // sig_cpx + [0] { // Attributes + } // Attributes + } // Member + } // fields + + [1] { // methods + { // Member at 0x012D + 0x0008; // access + #16; // name_cpx + #17; // sig_cpx + [1] { // Attributes + Attr(#14, 44) { // Code at 0x0135 + 2; // max_stack + 2; // max_locals + Bytes[12]{ + 0xCC00034C2B1ACC00; // Changed the first opcode to 0xCC (withfield) in order to + 0x024C2BB0; // test withfield opcode with an illegal major version. + }; + [0] { // Traps + } // end Traps + [1] { // Attributes + Attr(#15, 14) { // LineNumberTable at 0x0153 + [3] { // LineNumberTable + 0 8; // at 0x015F + 4 9; // at 0x0163 + 10 10; // at 0x0167 + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } // Member + } // methods + + [1] { // Attributes + Attr(#19, 2) { // SourceFile at 0x0198 + #20; + } // end SourceFile + } // Attributes +} // end class wthFldBadMajorVersion + +/////////////////////////////////////////////////////////// + +// The constant pool index of a withfield opcode (0xCC) in the Code +// attribute was changed to 1. Since this index now points to a Method +// entry instead of a Field entry, a VerifyError exception should get thrown. +// +class wthFldWrongCPType { + 0xCAFEBABE; + 0; // minor version + 70; // version + [20] { // Constant Pool + ; // first element is empty + Method #4 #17; // #1 at 0x0A + Field #3 #18; // #2 at 0x0F + class #12; // #3 at 0x14 + class #19; // #4 at 0x17 + Utf8 "int_v"; // #5 at 0x1A + Utf8 "I"; // #6 at 0x22 + Utf8 ""; // #7 at 0x26 + Utf8 "()V"; // #8 at 0x2F + Utf8 "Code"; // #9 at 0x35 + Utf8 "LineNumberTable"; // #10 at 0x3C + Utf8 "makewthFldWrongCPType"; // #11 at 0x4E + Utf8 "wthFldWrongCPType"; // #12 at 0x60 + Utf8 "ValueTypes"; // #13 at 0x6E + Utf8 "(I)LwthFldWrongCPType;"; // #14 at 0x7B + Utf8 "SourceFile"; // #15 at 0x8E + Utf8 "wthFldWrongCPType.java"; // #16 at 0x9B + NameAndType #7 #8; // #17 at 0xAE + NameAndType #5 #6; // #18 at 0xB3 + Utf8 "java/lang/Object"; // #19 at 0xB8 + } // Constant Pool + + 0x0850; // access [ ACC_VALUE ACC_PRIMITIVE ACC_FINAL ] + #3;// this_cpx + #4;// super_cpx + + [0] { // Interfaces + } // Interfaces + + [1] { // fields + { // Member at 0xD5 + 0x0010; // access + #5; // name_cpx + #6; // sig_cpx + [0] { // Attributes + } // Attributes + } // Member + } // fields + + [1] { // methods + { // Member at 0x0117 + 0x0008; // access + #11; // name_cpx + #14; // sig_cpx + [1] { // Attributes + Attr(#9, 44) { // Code at 0x011F + 2; // max_stack + 2; // max_locals + Bytes[12]{ + 0xCB00034C2B1ACC00; // Changed CP index from 2 to 1 for opcode 0xCC (withfield) + 0x014C2BB0; // so that the cp index no longer points to a cp Field entry. + }; + [0] { // Traps + } // end Traps + [1] { // Attributes + Attr(#10, 14) { // LineNumberTable at 0x013D + [3] { // LineNumberTable + 0 8; // at 0x0149 + 4 9; // at 0x014D + 10 10; // at 0x0151 + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } // Member + } // methods + + [2] { // Attributes + Attr(#15, 2) { // SourceFile at 0x0153 + #16; + } // end SourceFile + ; + Attr(#13, 4) { // ValueTypes at 0x015B + 0x00010003; + } // end ValueTypes + } // Attributes +} // end class wthFldWrongCPType + +/////////////////////////////////////////////////////////// + +// The cp entry for the aconst_init opcode was changed to a reference that +// is not an inline type. +// This should cause a VerifyError because the cp entry for opcode aconst_init +// must be an inline type. +// +class defValueObj { + 0xCAFEBABE; + 0; // minor version + 70; // version + [46] { // Constant Pool + ; // first element is empty + class #23; // #1 at 0x0A + Field #1 #24; // #2 at 0x0D + InvokeDynamic 0s #27; // #3 at 0x12 + InvokeDynamic 0s #28; // #4 at 0x17 + InvokeDynamic 0s #29; // #5 at 0x1C + class #30; // #6 at 0x21 + Utf8 "int_v"; // #7 at 0x24 + Utf8 "I"; // #8 at 0x2C + Utf8 "makedefValueObj"; // #9 at 0x30 + Utf8 "(I)QdefValueObj;"; // #10 at 0x41 + Utf8 "Code"; // #11 at 0x53 + Utf8 "LineNumberTable"; // #12 at 0x5A + Utf8 "hashCode"; // #13 at 0x6C + Utf8 "()I"; // #14 at 0x77 + Utf8 "equals"; // #15 at 0x7D + Utf8 "(Ljava/lang/Object;)Z"; // #16 at 0x86 + Utf8 "toString"; // #17 at 0x9E + Utf8 "()Ljava/lang/String;"; // #18 at 0xA9 + Utf8 ""; // #19 at 0xC0 + Utf8 "()QdefValueObj;"; // #20 at 0xC9 + Utf8 "SourceFile"; // #21 at 0xDA + Utf8 "defValueObj.java"; // #22 at 0xE7 + Utf8 "defValueObj"; // #23 at 0xF9 + NameAndType #7 #8; // #24 at 0x0106 + Utf8 "BootstrapMethods"; // #25 at 0x010B + MethodHandle 6b #31; // #26 at 0x011E + NameAndType #13 #32; // #27 at 0x0122 + NameAndType #15 #33; // #28 at 0x0127 + NameAndType #17 #34; // #29 at 0x012C + Utf8 "java/lang/Object"; // #30 at 0x0131 + Method #35 #36; // #31 at 0x0144 + Utf8 "(QdefValueObj;)I"; // #32 at 0x0149 + Utf8 "(QdefValueObj;Ljava/lang/Object;)Z"; // #33 at 0x015B + Utf8 "(QdefValueObj;)Ljava/lang/String;"; // #34 at 0x017F + class #37; // #35 at 0x01A2 + NameAndType #38 #42; // #36 at 0x01A5 + Utf8 "java/lang/invoke/ValueBootstrapMethods"; // #37 at 0x01AA + Utf8 "makeBootstrapMethod"; // #38 at 0x01D3 + class #44; // #39 at 0x01E9 + Utf8 "Lookup"; // #40 at 0x01EC + Utf8 "InnerClasses"; // #41 at 0x01F5 + Utf8 "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;"; // #42 at 0x0204 + class #45; // #43 at 0x027A + Utf8 "java/lang/invoke/MethodHandles$Lookup"; // #44 at 0x027D + Utf8 "java/lang/invoke/MethodHandles"; // #45 at 0x02A5 + } // Constant Pool + + 0x0850; // access [ ACC_VALUE ACC_PRIMITIVE ACC_FINAL ] + #1;// this_cpx + #6;// super_cpx + + [0] { // Interfaces + } // Interfaces + + [1] { // fields + { // Member at 0x02D0 + 0x0010; // access + #7; // name_cpx + #8; // sig_cpx + [0] { // Attributes + } // Attributes + } // Member + } // fields + + [5] { // methods + { // Member at 0x02DA + 0x0008; // access + #9; // name_cpx + #10; // sig_cpx + [1] { // Attributes + Attr(#11, 45) { // Code at 0x02E2 + 2; // max_stack + 2; // max_locals + Bytes[13]{ + 0xCB00044C1A2B5FCC; // Changed aconst_init's cp index at byte 3 from 3 to 4. + 0x00024C2BB0; + }; + [0] { // Traps + } // end Traps + [1] { // Attributes + Attr(#12, 14) { // LineNumberTable at 0x0301 + [3] { // LineNumberTable + 0 8; // at 0x030D + 4 9; // at 0x0311 + 11 10; // at 0x0315 + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } // Member + ; + { // Member at 0x0315 + 0x0011; // access + #13; // name_cpx + #14; // sig_cpx + [1] { // Attributes + Attr(#11, 31) { // Code at 0x031D + 1; // max_stack + 1; // max_locals + Bytes[7]{ + 0x2ABA00030000AC; + }; + [0] { // Traps + } // end Traps + [1] { // Attributes + Attr(#12, 6) { // LineNumberTable at 0x0336 + [1] { // LineNumberTable + 0 1; // at 0x0342 + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } // Member + ; + { // Member at 0x0342 + 0x0011; // access + #15; // name_cpx + #16; // sig_cpx + [1] { // Attributes + Attr(#11, 32) { // Code at 0x034A + 2; // max_stack + 2; // max_locals + Bytes[8]{ + 0x2A2BBA00040000AC; + }; + [0] { // Traps + } // end Traps + [1] { // Attributes + Attr(#12, 6) { // LineNumberTable at 0x0364 + [1] { // LineNumberTable + 0 1; // at 0x0370 + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } // Member + ; + { // Member at 0x0370 + 0x0011; // access + #17; // name_cpx + #18; // sig_cpx + [1] { // Attributes + Attr(#11, 31) { // Code at 0x0378 + 1; // max_stack + 1; // max_locals + Bytes[7]{ + 0x2ABA00050000B0; + }; + [0] { // Traps + } // end Traps + [1] { // Attributes + Attr(#12, 6) { // LineNumberTable at 0x0391 + [1] { // LineNumberTable + 0 1; // at 0x039D + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } // Member + ; + { // Member at 0x039D + 0x0008; // access + #19; // name_cpx + #20; // sig_cpx + [1] { // Attributes + Attr(#11, 45) { // Code at 0x03A5 + 2; // max_stack + 1; // max_locals + Bytes[13]{ + 0xCB00014B042A5FCC; + 0x00024B2AB0; + }; + [0] { // Traps + } // end Traps + [1] { // Attributes + Attr(#12, 14) { // LineNumberTable at 0x03C4 + [3] { // LineNumberTable + 0 4; // at 0x03D0 + 4 5; // at 0x03D4 + 11 6; // at 0x03D8 + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } // Member + } // methods + + [3] { // Attributes + Attr(#21, 2) { // SourceFile at 0x03DA + #22; + } // end SourceFile + ; + Attr(#41, 10) { // InnerClasses at 0x03E2 + [1] { // InnerClasses + #39 #43 #40 25; // at 0x03F2 + } + } // end InnerClasses + ; + Attr(#25, 6) { // BootstrapMethods at 0x03F2 + [1] { // bootstrap_methods + { // bootstrap_method + #26; // bootstrap_method_ref + [0] { // bootstrap_arguments + } // bootstrap_arguments + } // bootstrap_method + } + } // end BootstrapMethods + } // Attributes +} // end class defValueObj + +/////////////////////////////////////////////////////////// + +// This class has a withfield opcode with a non-Q type operand. +class withfieldL { + 0xCAFEBABE; + 0; // minor version + 70; // version + [28] { // Constant Pool + ; // first element is empty + class #2; // #1 at 0x0A + Utf8 "withfieldL"; // #2 at 0x0D + class #2; // #3 at 0x17 + Field #1 #5; // #4 at 0x1A + NameAndType #6 #7; // #5 at 0x1F + Utf8 "x"; // #6 at 0x24 + Utf8 "I"; // #7 at 0x28 + Field #1 #9; // #8 at 0x2C + NameAndType #10 #7; // #9 at 0x31 + Utf8 "y"; // #10 at 0x36 + class #12; // #11 at 0x3A + Utf8 "QwithfieldL;"; // #12 at 0x3D + class #14; // #13 at 0x49 + Utf8 "java/lang/Object"; // #14 at 0x4C + Utf8 "makePoint"; // #15 at 0x5F + Utf8 "(II)QwithfieldL;"; // #16 at 0x6B + Utf8 "Code"; // #17 at 0x7B + Utf8 "LineNumberTable"; // #18 at 0x82 + Utf8 ""; // #19 at 0x94 + Utf8 "()QwithfieldL;"; // #20 at 0x9D + Utf8 "SourceFile"; // #21 at 0xAB + Utf8 "X.java"; // #22 at 0xB8 + Utf8 "NestHost"; // #23 at 0xC1 + class #25; // #24 at 0xCC + Utf8 "X"; // #25 at 0xCF + Utf8 "InnerClasses"; // #26 at 0xD3 + Utf8 "Point"; // #27 at 0xE2 + } // Constant Pool + + 0x0850; // access [ ACC_VALUE ACC_PRIMITIVE ACC_FINAL ] + #1;// this_cpx + #13;// super_cpx + + [0] { // Interfaces + } // Interfaces + + [2] { // Fields + { // field at 0xF4 + 0x0010; // access + #6; // name_index : x + #7; // descriptor_index : I + [0] { // Attributes + } // Attributes + } + ; + { // field at 0xFC + 0x0010; // access + #10; // name_index : y + #7; // descriptor_index : I + [0] { // Attributes + } // Attributes + } + } // Fields + + [2] { // Methods + { // method at 0x0106 + 0x0008; // access + #15; // name_index : makePoint + #16; // descriptor_index : (II)QwithfieldL; + [1] { // Attributes + Attr(#17, 62) { // Code at 0x010E + 2; // max_stack + 3; // max_locals + Bytes[26]{ + 0xCB0001C000034D1A; + 0x2C5FCC00044D1B2C; + 0x5FCC00084D2CC000; + 0x0BB0; + } + [0] { // Traps + } // end Traps + [1] { // Attributes + Attr(#18, 18) { // LineNumberTable at 0x013A + [4] { // line_number_table + 0 4; // at 0x0146 + 7 5; // at 0x014A + 14 6; // at 0x014E + 21 7; // at 0x0152 + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + ; + { // method at 0x0152 + 0x000A; // access + #19; // name_index : + #20; // descriptor_index : ()QwithfieldL; + [1] { // Attributes + Attr(#17, 55) { // Code at 0x015A + 2; // max_stack + 1; // max_locals + Bytes[23]{ + 0xCB00014B032A5FCC; + 0x0008594BB400082A; + 0x5FCC00044B2AB0; + } + [0] { // Traps + } // end Traps + [1] { // Attributes + Attr(#18, 14) { // LineNumberTable at 0x0183 + [3] { // line_number_table + 0 9; // at 0x018F + 4 10; // at 0x0193 + 21 11; // at 0x0197 + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } + } // Methods + + [3] { // Attributes + Attr(#21, 2) { // SourceFile at 0x0199 + #22; + } // end SourceFile + ; + Attr(#23, 2) { // NestHost at 0x01A1 + #24; // X at 0x01A9 + } // end NestHost + ; + Attr(#26, 10) { // InnerClasses at 0x01A9 + [1] { // classes + #1 #24 #27 280; // at 0x01B9 + } + } // end InnerClasses + } // Attributes +} // end class withfieldL + +/////////////////////////////////////////////////////////// + +// Test that a VerifyError exception is thrown when trying to pass a null +// when the formal parameter is an inline type. +// +// // Java program emulating the jcod contents. +// public inline final class NoNullVT { +// final int x; +// final int y; +// +// private NoNullVT() { +// x = 0; +// y = 0; +// } +// +// public int getX() { return x; } +// public int getY() { return y; } +// +// public boolean isSameNoNullVT(NoNullVT that) { +// return this.getX() == that.getX() && this.getY() == that.getY(); +// } +// +// public boolean equals(Object o) { +// if(o instanceof NoNullVT) { +// return ((NoNullVT)o).x == x && ((NoNullVT)o).y == y; +// } else { +// return false; +// } +// } +// +// public static NoNullVT createNoNullVT(int x, int y) { +// NoNullVT p = NoNullVT.default; +// p = __WithField(p.x, x); +// p = __WithField(p.y, y); +// return p; +// } +// +// public static void main(String[] args) { +// String str = null; +// NoNullVT a = createNoNullVT(3, 4); +// NoNullVT b = createNoNullVT(2, 4); +// boolean res = a.isSameNoNullVT(null); // Should throw VerifyError +// } +// } + +class NoNullVT { + 0xCAFEBABE; + 0; // minor version + 70; // version + [63] { // Constant Pool + ; // first element is empty + class #36; // #1 at 0x0A + Field #1 #37; // #2 at 0x0D + Field #1 #38; // #3 at 0x12 + Method #1 #39; // #4 at 0x17 + Method #1 #40; // #5 at 0x1C + class #41; // #6 at 0x21 + Method #1 #42; // #7 at 0x24 + Method #1 #43; // #8 at 0x29 + InvokeDynamic 0s #46; // #9 at 0x2E + InvokeDynamic 0s #47; // #10 at 0x33 + class #48; // #11 at 0x38 + Utf8 "x"; // #12 at 0x3B + Utf8 "I"; // #13 at 0x3F + Utf8 "y"; // #14 at 0x43 + Utf8 "getX"; // #15 at 0x47 + Utf8 "()I"; // #16 at 0x4E + Utf8 "Code"; // #17 at 0x54 + Utf8 "LineNumberTable"; // #18 at 0x5B + Utf8 "getY"; // #19 at 0x6D + Utf8 "isSameNoNullVT"; // #20 at 0x74 + Utf8 "(QNoNullVT;)Z"; // #21 at 0x85 + Utf8 "StackMapTable"; // #22 at 0x95 + Utf8 "equals"; // #23 at 0xA5 + Utf8 "(Ljava/lang/Object;)Z"; // #24 at 0xAE + Utf8 "createNoNullVT"; // #25 at 0xC6 + Utf8 "(II)QNoNullVT;"; // #26 at 0xD7 + Utf8 "main"; // #27 at 0xE8 + Utf8 "([Ljava/lang/String;)V"; // #28 at 0xEF + Utf8 "hashCode"; // #29 at 0x0108 + Utf8 "toString"; // #30 at 0x0113 + Utf8 "()Ljava/lang/String;"; // #31 at 0x011E + Utf8 ""; // #32 at 0x0135 + Utf8 "()QNoNullVT;"; // #33 at 0x013E + Utf8 "SourceFile"; // #34 at 0x014D + Utf8 "NoNullVT.java"; // #35 at 0x015A + Utf8 "NoNullVT"; // #36 at 0x016A + NameAndType #12 #13; // #37 at 0x0175 + NameAndType #14 #13; // #38 at 0x017A + NameAndType #15 #16; // #39 at 0x017F + NameAndType #19 #16; // #40 at 0x0184 + Utf8 "QNoNullVT;"; // #41 at 0x0189 + NameAndType #25 #26; // #42 at 0x0196 + NameAndType #20 #21; // #43 at 0x019B + Utf8 "BootstrapMethods"; // #44 at 0x01A0 + MethodHandle 6b #49; // #45 at 0x01B3 + NameAndType #29 #50; // #46 at 0x01B7 + NameAndType #30 #51; // #47 at 0x01BC + Utf8 "java/lang/Object"; // #48 at 0x01C1 + Method #52 #53; // #49 at 0x01D4 + Utf8 "(QNoNullVT;)I"; // #50 at 0x01D9 + Utf8 "(QNoNullVT;)Ljava/lang/String;"; // #51 at 0x01E9 + class #54; // #52 at 0x020A + NameAndType #55 #59; // #53 at 0x020D + Utf8 "java/lang/invoke/ValueBootstrapMethods"; // #54 at 0x0212 + Utf8 "makeBootstrapMethod"; // #55 at 0x023B + class #61; // #56 at 0x0251 + Utf8 "Lookup"; // #57 at 0x0254 + Utf8 "InnerClasses"; // #58 at 0x025D + Utf8 "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;"; // #59 at 0x026C + class #62; // #60 at 0x02E2 + Utf8 "java/lang/invoke/MethodHandles$Lookup"; // #61 at 0x02E5 + Utf8 "java/lang/invoke/MethodHandles"; // #62 at 0x030D + } // Constant Pool + + 0x0851; // access [ ACC_VALUE ACC_PRIMITIVE ACC_PUBLIC ACC_FINAL ] + #1;// this_cpx + #11;// super_cpx + + [0] { // Interfaces + } // Interfaces + + [2] { // fields + { // Member at 0x0338 + 0x0010; // access + #12; // name_cpx + #13; // sig_cpx + [0] { // Attributes + } // Attributes + } // Member + ; + { // Member at 0x0340 + 0x0010; // access + #14; // name_cpx + #13; // sig_cpx + [0] { // Attributes + } // Attributes + } // Member + } // fields + + [9] { // methods + { // Member at 0x034A + 0x0001; // access + #15; // name_cpx + #16; // sig_cpx + [1] { // Attributes + Attr(#17, 29) { // Code at 0x0352 + 1; // max_stack + 1; // max_locals + Bytes[5]{ + 0x2AB40002AC; + }; + [0] { // Traps + } // end Traps + [1] { // Attributes + Attr(#18, 6) { // LineNumberTable at 0x0369 + [1] { // LineNumberTable + 0 10; // at 0x0375 + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } // Member + ; + { // Member at 0x0375 + 0x0001; // access + #19; // name_cpx + #16; // sig_cpx + [1] { // Attributes + Attr(#17, 29) { // Code at 0x037D + 1; // max_stack + 1; // max_locals + Bytes[5]{ + 0x2AB40003AC; + }; + [0] { // Traps + } // end Traps + [1] { // Attributes + Attr(#18, 6) { // LineNumberTable at 0x0394 + [1] { // LineNumberTable + 0 11; // at 0x03A0 + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } // Member + ; + { // Member at 0x03A0 + 0x0001; // access + #20; // name_cpx + #21; // sig_cpx + [1] { // Attributes + Attr(#17, 63) { // Code at 0x03A8 + 2; // max_stack + 2; // max_locals + Bytes[28]{ + 0x2AB600042BB60004; + 0xA000122AB600052B; + 0xB60005A0000704A7; + 0x000403AC; + }; + [0] { // Traps + } // end Traps + [2] { // Attributes + Attr(#18, 6) { // LineNumberTable at 0x03D6 + [1] { // LineNumberTable + 0 14; // at 0x03E2 + } + } // end LineNumberTable + ; + Attr(#22, 5) { // StackMapTable at 0x03E2 + [2] { // + 26b; // same_frame + 64b, [1]z{1b}; // same_locals_1_stack_item_frame + } + } // end StackMapTable + } // Attributes + } // end Code + } // Attributes + } // Member + ; + { // Member at 0x03ED + 0x0001; // access + #23; // name_cpx + #24; // sig_cpx + [1] { // Attributes + Attr(#17, 87) { // Code at 0x03F5 + 2; // max_stack + 2; // max_locals + Bytes[43]{ + 0x2BC100019900252B; + 0xC00006B400022AB4; + 0x0002A000152BC000; + 0x06B400032AB40003; + 0xA0000704A7000403; + 0xAC03AC; + }; + [0] { // Traps + } // end Traps + [2] { // Attributes + Attr(#18, 14) { // LineNumberTable at 0x0432 + [3] { // LineNumberTable + 0 18; // at 0x043E + 7 19; // at 0x0442 + 41 21; // at 0x0446 + } + } // end LineNumberTable + ; + Attr(#22, 6) { // StackMapTable at 0x0446 + [3] { // + 39b; // same_frame + 64b, [1]z{1b}; // same_locals_1_stack_item_frame + 0b; // same_frame + } + } // end StackMapTable + } // Attributes + } // end Code + } // Attributes + } // Member + ; + { // Member at 0x0452 + 0x0009; // access + #25; // name_cpx + #26; // sig_cpx + [1] { // Attributes + Attr(#17, 56) { // Code at 0x045A + 2; // max_stack + 3; // max_locals + Bytes[20]{ + 0xCB00014D1A2C5FCC; + 0x00024D1B2C5FCC00; + 0x034D2CB0; + }; + [0] { // Traps + } // end Traps + [1] { // Attributes + Attr(#18, 18) { // LineNumberTable at 0x0480 + [4] { // LineNumberTable + 0 26; // at 0x048C + 4 27; // at 0x0490 + 11 28; // at 0x0494 + 18 29; // at 0x0498 + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } // Member + ; + { // Member at 0x0498 + 0x0009; // access + #27; // name_cpx + #28; // sig_cpx + [1] { // Attributes + Attr(#17, 62) { // Code at 0x04A0 + 2; // max_stack + 5; // max_locals + Bytes[22]{ + 0x014C0607B800074D; + 0x0507B800074E2C2B; // Change last nibble from C to B to load null + 0xB600083604B1; + }; + [0] { // Traps + } // end Traps + [1] { // Attributes + Attr(#18, 22) { // LineNumberTable at 0x04C8 + [5] { // LineNumberTable + 0 33; // at 0x04D4 + 2 34; // at 0x04D8 + 8 35; // at 0x04DC + 14 36; // at 0x04E0 + 21 37; // at 0x04E4 + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } // Member + ; + { // Member at 0x04E4 + 0x0011; // access + #29; // name_cpx + #16; // sig_cpx + [1] { // Attributes + Attr(#17, 31) { // Code at 0x04EC + 1; // max_stack + 1; // max_locals + Bytes[7]{ + 0x2ABA00090000AC; + }; + [0] { // Traps + } // end Traps + [1] { // Attributes + Attr(#18, 6) { // LineNumberTable at 0x0505 + [1] { // LineNumberTable + 0 1; // at 0x0511 + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } // Member + ; + { // Member at 0x0511 + 0x0011; // access + #30; // name_cpx + #31; // sig_cpx + [1] { // Attributes + Attr(#17, 31) { // Code at 0x0519 + 1; // max_stack + 1; // max_locals + Bytes[7]{ + 0x2ABA000A0000B0; + }; + [0] { // Traps + } // end Traps + [1] { // Attributes + Attr(#18, 6) { // LineNumberTable at 0x0532 + [1] { // LineNumberTable + 0 1; // at 0x053E + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } // Member + ; + { // Member at 0x053E + 0x000A; // access + #32; // name_cpx + #33; // sig_cpx + [1] { // Attributes + Attr(#17, 56) { // Code at 0x0546 + 2; // max_stack + 1; // max_locals + Bytes[20]{ + 0xCB00014B032A5FCC; + 0x00024B032A5FCC00; + 0x034B2AB0; + }; + [0] { // Traps + } // end Traps + [1] { // Attributes + Attr(#18, 18) { // LineNumberTable at 0x056C + [4] { // LineNumberTable + 0 5; // at 0x0578 + 4 6; // at 0x057C + 11 7; // at 0x0580 + 18 8; // at 0x0584 + } + } // end LineNumberTable + } // Attributes + } // end Code + } // Attributes + } // Member + } // methods + + [3] { // Attributes + Attr(#34, 2) { // SourceFile at 0x0586 + #35; + } // end SourceFile + ; + Attr(#58, 10) { // InnerClasses at 0x058E + [1] { // InnerClasses + #56 #60 #57 25; // at 0x059E + } + } // end InnerClasses + ; + Attr(#44, 6) { // BootstrapMethods at 0x059E + [1] { // bootstrap_methods + { // bootstrap_method + #45; // bootstrap_method_ref + [0] { // bootstrap_arguments + } // bootstrap_arguments + } // bootstrap_method + } + } // end BootstrapMethods + } // Attributes +} // end class NoNullVT diff --git a/test/hotspot/jtreg/runtime/verifier/CFLH/TestVerify.java b/test/hotspot/jtreg/runtime/verifier/CFLH/TestVerify.java index 214751863ea..ece28344751 100644 --- a/test/hotspot/jtreg/runtime/verifier/CFLH/TestVerify.java +++ b/test/hotspot/jtreg/runtime/verifier/CFLH/TestVerify.java @@ -39,7 +39,6 @@ */ import java.lang.invoke.MethodHandles; -import java.time.Duration; import java.lang.classfile.ClassFile; import java.lang.classfile.ClassTransform; import java.lang.classfile.MethodTransform; @@ -61,7 +60,7 @@ public class TestVerify { - private static final String CLASS_TO_BREAK = "java.time.Duration"; + private static final String CLASS_TO_BREAK = "java.util.Date"; private static final String INTERNAL_CLASS_TO_BREAK = CLASS_TO_BREAK.replace('.', '/'); private static final boolean DEBUG = false; @@ -91,7 +90,7 @@ public byte[] transform(Module module, ClassLoader loader, String className, Cla } builder.with(element); }); - var classTransform = ClassTransform.transformingMethods(mm -> mm.methodName().stringValue().equals("getSeconds"), methodTransform); + var classTransform = ClassTransform.transformingMethods(mm -> mm.methodName().stringValue().equals("parse"), methodTransform); byte[] bytes; try { @@ -164,7 +163,8 @@ public static void main(String argv[]) throws Exception { } else { // Load the class instrumented with CFLH for the VerifyError. inst.addTransformer(new BadTransformer()); - System.out.println("1 hour is " + Duration.ofHours(1).getSeconds() + " seconds"); + Class cls = Class.forName(CLASS_TO_BREAK); + System.out.println("class loaded" + cls); } throw new RuntimeException("Failed: Did not throw VerifyError"); } catch (VerifyError e) { diff --git a/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java b/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java index 0010c5bdd78..6e491f5cdb8 100644 --- a/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java +++ b/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java @@ -55,7 +55,7 @@ private static void checkAndVerify(OutputAnalyzer dcmdOut, LingeredApp app, File dcmdOut.shouldHaveExitValue(0); dcmdOut.shouldContain("Heap dump file created"); OutputAnalyzer appOut = new OutputAnalyzer(app.getProcessStdout()); - appOut.shouldContain("[heapdump]"); + appOut.shouldMatch("\\[heapdump *\\]"); String opts = Arrays.asList(Utils.getTestJavaOpts()).toString(); if (opts.contains("-XX:+UseSerialGC") || opts.contains("-XX:+UseEpsilonGC")) { System.out.println("UseSerialGC detected."); diff --git a/test/hotspot/jtreg/serviceability/jvmti/HiddenClass/libHiddenClassSigTest.cpp b/test/hotspot/jtreg/serviceability/jvmti/HiddenClass/libHiddenClassSigTest.cpp index e25587d2ac3..e7f04d44f49 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/HiddenClass/libHiddenClassSigTest.cpp +++ b/test/hotspot/jtreg/serviceability/jvmti/HiddenClass/libHiddenClassSigTest.cpp @@ -29,6 +29,7 @@ extern "C" { static const char* EXP_INTERF_SIG = "LP/Q/HCInterf;"; static const char* SIG_START = "LP/Q/HiddenClassSig"; +static const char* IDENTITYOBJECT_IF = "Ljava/lang/IdentityObject;"; static const size_t SIG_START_LEN = strlen(SIG_START); static const int ACC_INTERFACE = 0x0200; // Interface class modifiers bit @@ -196,20 +197,26 @@ check_hidden_class_impl_interf(jvmtiEnv* jvmti, JNIEnv* jni, jclass klass) { jclass* interfaces = nullptr; jvmtiError err; - // check that hidden class implements just one interface + // check that hidden class implements just one interface (or two if IdentityObject has been injected) err = jvmti->GetImplementedInterfaces(klass, &count, &interfaces); CHECK_JVMTI_ERROR(jni, err, "check_hidden_class_impl_interf: Error in JVMTI GetImplementedInterfaces"); - if (count != 1) { - LOG1("check_hidden_class_impl_interf: FAIL: implemented interfaces count: %d, expected to be 1\n", count); + if (count != 1 && count != 2) { + LOG1("check_hidden_class_impl_interf: FAIL: implemented interfaces count: %d, expected to be in [1-2] range\n", count); failed = true; return; } - // get interface signature - err = jvmti->GetClassSignature(interfaces[0], &sig, nullptr); - CHECK_JVMTI_ERROR(jni, err, "check_hidden_class_impl_interf: Error in JVMTI GetClassSignature for implemented interface"); + bool found = false; + for (int i = 0; i < count; i++) { + // get interface signature + err = jvmti->GetClassSignature(interfaces[i], &sig, nullptr); + CHECK_JVMTI_ERROR(jni, err, "check_hidden_class_impl_interf: Error in JVMTI GetClassSignature for implemented interface"); + // check the interface signature is matching the expected + if (strcmp(sig, EXP_INTERF_SIG) == 0) { + found = true; + } + } - // check the interface signature is matching the expected - if (strcmp(sig, EXP_INTERF_SIG) != 0) { + if (!found) { LOG2("check_hidden_class_impl_interf: FAIL: implemented interface signature: %s, expected to be: %s\n", sig, EXP_INTERF_SIG); failed = true; diff --git a/test/hotspot/jtreg/serviceability/jvmti/RedefineClasses/RedefineLeakThrowableValue.java b/test/hotspot/jtreg/serviceability/jvmti/RedefineClasses/RedefineLeakThrowableValue.java new file mode 100644 index 00000000000..291ce971f54 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/RedefineClasses/RedefineLeakThrowableValue.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8336829 + * @library /test/lib + * @summary Test that redefinition of a value class containing Throwable refs does not leak constant pool + * @enablePreview + * @modules java.base/jdk.internal.misc + * @modules java.instrument + * java.compiler + * @run main RedefineClassHelper + * @run main/othervm/timeout=6000 --enable-preview -Xlog:class+load,gc+metaspace+freelist+oom:rt.log -javaagent:redefineagent.jar -XX:MetaspaceSize=23m -XX:MaxMetaspaceSize=23m RedefineLeakThrowableValue + */ + +import jdk.test.lib.compiler.InMemoryJavaCompiler; + +value class Tester { + void test() { + try { + int i = 42; + } catch (Throwable t) { + t.printStackTrace(); + } + } +} + +public class RedefineLeakThrowableValue { + + static final String NEW_TESTER = + "value class Tester {" + + " void test() {" + + " try {" + + " int i = 42;" + + " } catch (Throwable t) {" + + " t.printStackTrace();" + + " }" + + " }" + + "}"; + + + public static void main(String argv[]) throws Exception { + String java_version = System.getProperty("java.specification.version"); + // Load InMemoryJavaCompiler outside the redefinition loop to prevent random lambdas that load and fill up metaspace. + byte[] bytecodes = InMemoryJavaCompiler.compile("Tester", NEW_TESTER, + "-source", java_version, "--enable-preview", + "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED"); + for (int i = 0; i < 700; i++) { + RedefineClassHelper.redefineClass(Tester.class, bytecodes); + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/valhalla/FieldAccessModify/FieldAccessModify.java b/test/hotspot/jtreg/serviceability/jvmti/valhalla/FieldAccessModify/FieldAccessModify.java new file mode 100644 index 00000000000..cb108fe2fb0 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/valhalla/FieldAccessModify/FieldAccessModify.java @@ -0,0 +1,218 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @summary Tests that all FieldAccess and FieldModification notifications + are generated for value classes. + * @requires vm.jvmti + * @enablePreview + * @run main/othervm/native -agentlib:FieldAccessModify -XX:+EnableValhalla FieldAccessModify + */ + +import java.lang.reflect.Field; +import java.util.Arrays; + +public class FieldAccessModify { + + private static final String agentLib = "FieldAccessModify"; + + private static value class ValueClass { + public int valueClass_fld1; + public int valueClass_fld2; + + public ValueClass(int v1, int v2) { valueClass_fld1 = v1; valueClass_fld2 = v2; } + + public String toString() { + return "ValueClass { fld1=" + valueClass_fld1 + ", fld2=" + valueClass_fld2 + "}"; + } + + } + + private static class InstanceHolder { + public final ValueClass instanceHolder_fld1; + + public InstanceHolder(int v) { + instanceHolder_fld1 = new ValueClass(v, v + 100); + } + + public String toString() { + return "InstanceHolder { fld1 is " + instanceHolder_fld1 + "}"; + } + } + + private static value class ValueHolder { + public ValueClass valueHolder_fld1; + + public ValueHolder(int v) { + valueHolder_fld1 = new ValueClass(v, v + 200); + } + + public String toString() { + return "ValueHolder { fld1 is " + valueHolder_fld1 + "}"; + } + } + + private static class TestHolder { + public ValueClass valueObj = new ValueClass(1, 1); + public InstanceHolder instanceHolderObj = new InstanceHolder(1); + public ValueHolder valueHolderObj = new ValueHolder(1); + } + + public static void main(String[] args) throws Exception { + try { + System.loadLibrary(agentLib); + } catch (UnsatisfiedLinkError ex) { + System.err.println("Failed to load " + agentLib + " lib"); + System.err.println("java.library.path: " + System.getProperty("java.library.path")); + throw ex; + } + + // create objects for access testing before setting watchers + TestHolder testHolder = new TestHolder(); + + if (!initWatchers(ValueClass.class, ValueClass.class.getDeclaredField("valueClass_fld1"))) { + throw new RuntimeException("Watchers initializations error (valueClass_fld1)"); + } + if (!initWatchers(ValueClass.class, ValueClass.class.getDeclaredField("valueClass_fld2"))) { + throw new RuntimeException("Watchers initializations error (valueClass_fld2)"); + } + if (!initWatchers(InstanceHolder.class, InstanceHolder.class.getDeclaredField("instanceHolder_fld1"))) { + throw new RuntimeException("Watchers initializations error (instanceHolder_fld1)"); + } + if (!initWatchers(ValueHolder.class, ValueHolder.class.getDeclaredField("valueHolder_fld1"))) { + throw new RuntimeException("Watchers initializations error (valueHolder_fld1)"); + } + + test("ValueClass (access)", () -> { + testHolder.valueObj.toString(); // should access both valueClass_fld1 and valueClass_fld2 + }, new TestResult() { + public boolean valueClass_fld1_access; + public boolean valueClass_fld2_access; + }); + + test("InstanceHolder (access)", () -> { + testHolder.instanceHolderObj.toString(); + }, new TestResult() { + public boolean instanceHolder_fld1_access; + // ValueClass fields should be accessed too + public boolean valueClass_fld1_access; + public boolean valueClass_fld2_access; + }); + + test("ValueHolder (access)", () -> { + testHolder.valueHolderObj.toString(); + }, new TestResult() { + public boolean valueHolder_fld1_access; + // ValueClass fields should be accessed too + public boolean valueClass_fld1_access; + public boolean valueClass_fld2_access; + }); + + test("ValueClass (modify)", () -> { + ValueClass obj = new ValueClass(1, 1); + }, new TestResult() { + public boolean valueClass_fld1_modify; + public boolean valueClass_fld2_modify; + }); + + test("InstanceHolder (modify)", () -> { + InstanceHolder obj = new InstanceHolder(10); + }, new TestResult() { + public boolean instanceHolder_fld1_modify; + // ValueClass fields should be modified too + boolean valueClass_fld1_modify; + public boolean valueClass_fld2_modify; + }); + + test("ValueHolder (modify)", () -> { + ValueHolder obj = new ValueHolder(11); + }, new TestResult() { + public boolean valueHolder_fld1_modify; + // ValueClass fields should be modified too + public boolean valueClass_fld1_modify; + public boolean valueClass_fld2_modify; + }); + + } + + private static void log(String msg) { + System.out.println(msg); + System.out.flush(); + } + + private static void assertTrue(boolean value) { + if (!value) { + throw new RuntimeException("assert"); + } + } + + // For every access/modify notification native part tries to locate + // boolean "_access"/"_modify" field and sets it to true + private static class TestResult { + + // verify that all fields are set to true + public void verify() { + Arrays.stream(this.getClass().getDeclaredFields()).forEach(f -> verify(f)); + } + + private void verify(Field f) { + try { + if (!f.getBoolean(this)) { + throw new RuntimeException(f.getName() + " notification is missed"); + } + log(" - " + f.getName() + ": OK"); + } catch (IllegalAccessException ex) { + throw new RuntimeException(ex); + } + } + } + + @FunctionalInterface + private interface TestAction { + void apply(); + } + + private static void test(String descr, TestAction action, TestResult result) throws Exception { + log(descr + ": starting"); + if (!startTest(result)) { + throw new RuntimeException("startTest failed"); + } + action.apply(); + // wait some time to ensure all posted events are handled + Thread.sleep(500); + + stopTest(); + + // check the results + result.verify(); + + log(descr + ": OK"); + log(""); + } + + private static native boolean initWatchers(Class cls, Field field); + private static native boolean startTest(TestResult results); + private static native void stopTest(); + +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/valhalla/FieldAccessModify/libFieldAccessModify.cpp b/test/hotspot/jtreg/serviceability/jvmti/valhalla/FieldAccessModify/libFieldAccessModify.cpp new file mode 100644 index 00000000000..cc07e5697d9 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/valhalla/FieldAccessModify/libFieldAccessModify.cpp @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include +#include +#include "jvmti.h" +#include "jni.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +static jvmtiEnv *jvmti = nullptr; + +// valid while a test is executed +static jobject testResultObject = nullptr; +static jclass testResultClass = nullptr; +// we log object values handling FieldModification event and this cause FieldAccess events are triggered. +// The flag to disable FieldAccess handling. +static bool disableAccessEvent = false; + +static void reportError(const char *msg, int err) { + printf("%s, error: %d\n", msg, err); +} + +static void printJValue(const char *prefix, JNIEnv *jni_env, char signature_type, jvalue value) { + // print new_value to ensure the value is valid + // use String.valueOf(...) to get string representation + /* + Z boolean + B byte + C char + S short + I int + J long + F float + D double + L fully-qualified-class ; fully-qualified-class + [ type type[] + */ + char signature[64] = {}; + if (signature_type == 'Q' || signature_type == 'L') { + snprintf(signature, sizeof(signature), "(Ljava/lang/Object;)Ljava/lang/String;"); + } else { + snprintf(signature, sizeof(signature), "(%c)Ljava/lang/String;", signature_type); + } + + jclass clsString = jni_env->FindClass("java/lang/String"); + jmethodID mid = jni_env->GetStaticMethodID(clsString, "valueOf", signature); + jstring objJStr = (jstring)jni_env->CallStaticObjectMethodA(clsString, mid, &value); + + const char* objStr = "UNKNOWN"; + if (objJStr != nullptr) { + objStr = jni_env->GetStringUTFChars(objJStr, nullptr); + } + + printf(" %s is: '%s'\n", prefix, objStr); + fflush(0); + + if (objJStr != nullptr) { + jni_env->ReleaseStringUTFChars(objJStr, objStr); + } +} + + +// logs the notification and updates currentTestResult +static void handleNotification(jvmtiEnv *jvmti, JNIEnv *jni_env, + jmethodID method, + jobject object, + jfieldID field, + jclass field_klass, + bool modified, + jlocation location) +{ + jvmtiError err; + char *name = nullptr; + char *signature = nullptr; + char *mname = nullptr; + char *mgensig = nullptr; + jclass methodClass = nullptr; + char *csig = nullptr; + + if (testResultObject == nullptr) { + // we are out of test + return; + } + + err = jvmti->GetFieldName(field_klass, field, &name, &signature, nullptr); + if (err != JVMTI_ERROR_NONE) { + reportError("GetFieldName failed", err); + return; + } + + err = jvmti->GetMethodName(method, &mname, nullptr, &mgensig); + if (err != JVMTI_ERROR_NONE) { + reportError("GetMethodName failed", err); + return; + } + + err = jvmti->GetMethodDeclaringClass(method, &methodClass); + if (err != JVMTI_ERROR_NONE) { + reportError("GetMethodDeclaringClass failed", err); + return; + } + + err = jvmti->GetClassSignature(methodClass, &csig, nullptr); + if (err != JVMTI_ERROR_NONE) { + reportError("GetClassSignature failed", err); + return; + } + + printf(" \"class: %s method: %s%s\" %s field: \"%s\" (type '%s'), location: %d\n", + csig, mname, mgensig, modified ? "modified" : "accessed", name, signature, (int)location); + + // For FieldModification event print current value. + // Note: this will cause FieldAccess event. + if (modified) { + jvalue curValue = {}; + switch (signature[0]) { + case 'L': + case 'Q': + curValue.l = jni_env->GetObjectField(object, field); break; + case 'Z': // boolean + curValue.z = jni_env->GetBooleanField(object, field); break; + case 'B': // byte + curValue.b = jni_env->GetByteField(object, field); break; + case 'C': // char + curValue.c = jni_env->GetCharField(object, field); break; + case 'S': // short + curValue.s = jni_env->GetShortField(object, field); break; + case 'I': // int + curValue.i = jni_env->GetIntField(object, field); break; + case 'J': // long + curValue.j = jni_env->GetLongField(object, field); break; + case 'F': // float + curValue.f = jni_env->GetFloatField(object, field); break; + case 'D': // double + curValue.d = jni_env->GetDoubleField(object, field); break; + default: + printf("ERROR: unexpected signature: %s\n", signature); + return; + } + printJValue("current value: ", jni_env, signature[0], curValue); + } + + // set TestResult + if (testResultObject != nullptr && testResultClass != nullptr) { + jfieldID fieldID; + // field names in TestResult are "_access"/"_modify" + char *fieldName = (char *)malloc(strlen(name) + 16); + strcpy(fieldName, name); + strcat(fieldName, modified ? "_modify" : "_access"); + + fieldID = jni_env->GetFieldID(testResultClass, fieldName, "Z"); + if (fieldID != nullptr) { + jni_env->SetBooleanField(testResultObject, fieldID, JNI_TRUE); + } else { + // the field is not interesting for the test + } + // clear any possible exception + jni_env->ExceptionClear(); + + free(fieldName); + } + + jvmti->Deallocate((unsigned char*)csig); + jvmti->Deallocate((unsigned char*)mname); + jvmti->Deallocate((unsigned char*)mgensig); + jvmti->Deallocate((unsigned char*)name); + jvmti->Deallocate((unsigned char*)signature); +} + +static void JNICALL +onFieldAccess(jvmtiEnv *jvmti_env, + JNIEnv* jni_env, + jthread thread, + jmethodID method, + jlocation location, + jclass field_klass, + jobject object, + jfieldID field) +{ + if (disableAccessEvent) { + return; + } + handleNotification(jvmti_env, jni_env, method, object, field, field_klass, false, location); +} + +static void JNICALL +onFieldModification(jvmtiEnv *jvmti_env, + JNIEnv* jni_env, + jthread thread, + jmethodID method, + jlocation location, + jclass field_klass, + jobject object, + jfieldID field, + char signature_type, + jvalue new_value) +{ + disableAccessEvent = true; + + handleNotification(jvmti_env, jni_env, method, object, field, field_klass, true, location); + + printJValue("new value", jni_env, signature_type, new_value); + + disableAccessEvent = false; +} + + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) +{ + jvmtiError err; + jvmtiCapabilities caps = {}; + jvmtiEventCallbacks callbacks = {}; + jint res = jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_1); + if (res != JNI_OK || jvmti == nullptr) { + reportError("GetEnv failed", res); + return JNI_ERR; + } + + caps.can_generate_field_modification_events = 1; + caps.can_generate_field_access_events = 1; + caps.can_tag_objects = 1; + err = jvmti->AddCapabilities(&caps); + if (err != JVMTI_ERROR_NONE) { + reportError("Failed to set capabilities", err); + return JNI_ERR; + } + + callbacks.FieldModification = &onFieldModification; + callbacks.FieldAccess = &onFieldAccess; + + err = jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks)); + if (err != JVMTI_ERROR_NONE) { + reportError("Failed to set event callbacks", err); + return JNI_ERR; + } + + err = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_FIELD_ACCESS, nullptr); + if (err != JVMTI_ERROR_NONE) { + reportError("Failed to set access notifications", err); + return JNI_ERR; + } + + err = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_FIELD_MODIFICATION, nullptr); + if (err != JVMTI_ERROR_NONE) { + reportError("Failed to set modification notifications", err); + return JNI_ERR; + } + setbuf(stdout, nullptr); + return JNI_OK; +} + + +JNIEXPORT jboolean JNICALL +Java_FieldAccessModify_initWatchers(JNIEnv *env, jclass thisClass, jclass cls, jobject field) +{ + jfieldID fieldId; + jvmtiError err; + + if (jvmti == nullptr) { + reportError("jvmti is nullptr", 0); + return JNI_FALSE; + } + + fieldId = env->FromReflectedField(field); + + err = jvmti->SetFieldModificationWatch(cls, fieldId); + if (err != JVMTI_ERROR_NONE) { + reportError("SetFieldModificationWatch failed", err); + return JNI_FALSE; + } + + err = jvmti->SetFieldAccessWatch(cls, fieldId); + if (err != JVMTI_ERROR_NONE) { + reportError("SetFieldAccessWatch failed", err); + return JNI_FALSE; + } + + return JNI_TRUE; +} + + +JNIEXPORT jboolean JNICALL +Java_FieldAccessModify_startTest(JNIEnv *env, jclass thisClass, jobject testResults) +{ + testResultObject = env->NewGlobalRef(testResults); + testResultClass = (jclass)env->NewGlobalRef(env->GetObjectClass(testResultObject)); + + return JNI_TRUE; +} + +JNIEXPORT void JNICALL +Java_FieldAccessModify_stopTest(JNIEnv *env, jclass thisClass) +{ + if (testResultObject != nullptr) { + env->DeleteGlobalRef(testResultObject); + testResultObject = nullptr; + } + if (testResultClass != nullptr) { + env->DeleteGlobalRef(testResultClass); + testResultClass = nullptr; + } +} + + +#ifdef __cplusplus +} +#endif + diff --git a/test/hotspot/jtreg/serviceability/jvmti/valhalla/ForceEarlyReturn/ValueForceEarlyReturn.java b/test/hotspot/jtreg/serviceability/jvmti/valhalla/ForceEarlyReturn/ValueForceEarlyReturn.java new file mode 100644 index 00000000000..48d7f80cf7c --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/valhalla/ForceEarlyReturn/ValueForceEarlyReturn.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @summary Sanity test for ForceEarlyReturn with value classes. + * @requires vm.jvmti + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @run main/othervm/native -agentlib:ValueForceEarlyReturn -XX:+EnableValhalla ValueForceEarlyReturn + */ + +import java.util.Objects; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +public class ValueForceEarlyReturn { + + private static final String agentLib = "ValueForceEarlyReturn"; + + @LooselyConsistentValue + private static value class ValueClass { + public int f1; + public int f2; + + public ValueClass(int v1, int v2) { f1 = v1; f2 = v2; } + } + + private static value class ValueHolder { + public ValueClass f1; + @Strict + @NullRestricted + public ValueClass f2; + + public static ValueClass s1 = new ValueClass(0, 1); + + public ValueHolder(int v) { + f1 = new ValueClass(v, v + 100); + f2 = new ValueClass(v + 1, v + 200); + } + } + + public static void main(String[] args) throws Exception { + try { + System.loadLibrary(agentLib); + } catch (UnsatisfiedLinkError ex) { + System.err.println("Failed to load " + agentLib + " lib"); + System.err.println("java.library.path: " + System.getProperty("java.library.path")); + throw ex; + } + + ValueClass testObj1 = new ValueClass(4, 5); + ValueHolder testObj2 = new ValueHolder(6); + + testForceEarlyReturn(testObj1); + testForceEarlyReturn(testObj2); + } + + private static void testForceEarlyReturn(Object retObject) throws Exception { + String className = retObject.getClass().getName(); + log(">> Testing ForceEarlyReturn for " + className); + + TestTask task = new TestTask(); + Thread thread = new Thread(task, "testThread"); + thread.start(); + + task.ensureReady(); + + nSuspendThread(thread); + nForceEarlyReturn(thread, retObject); + nResumeThread(thread); + + task.finish(); + thread.join(); + + Object result = task.getResult(); + + if (!Objects.equals(result, retObject)) { + throw new RuntimeException("ERROR: unexpected result (" + result + ", expected " + retObject + ")"); + } + log("<< Testing " + className + " - OK"); + log(""); + } + + private static class TestTask implements Runnable { + + private volatile boolean ready = false; + private volatile boolean doLoop = true; + private volatile Object result = null; + + private static void sleep(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + throw new RuntimeException("Interruption in TestTask.sleep: " + e); + } + } + + public void ensureReady() { + while (!ready) { + sleep(1); + } + } + + public void finish() { + doLoop = false; + } + + public Object getResult() { + return result; + } + + public void run() { + result = meth(); + } + + // Method is busy in a while loop. + private Object meth() { + ready = true; + while (doLoop) { + } + return null; + } + } + + private static void log(String msg) { + System.out.println(msg); + } + + static native void nSuspendThread(Thread thread); + static native void nResumeThread(Thread thread); + static native void nForceEarlyReturn(Thread thread, Object obj); + +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/valhalla/ForceEarlyReturn/libValueForceEarlyReturn.cpp b/test/hotspot/jtreg/serviceability/jvmti/valhalla/ForceEarlyReturn/libValueForceEarlyReturn.cpp new file mode 100644 index 00000000000..cd0022544fd --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/valhalla/ForceEarlyReturn/libValueForceEarlyReturn.cpp @@ -0,0 +1,76 @@ +/* + * 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 +#include +#include "jvmti.h" +#include "jni.h" +#include "jvmti_common.hpp" + +#ifdef __cplusplus +extern "C" { +#endif + +static jvmtiEnv *jvmti = nullptr; + + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { + jint res = jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_1); + if (res != JNI_OK || jvmti == nullptr) { + LOG("GetEnv failed, res = %d", (int)res); + return JNI_ERR; + } + + jvmtiCapabilities caps; + memset(&caps, 0, sizeof(caps)); + caps.can_suspend = 1; + caps.can_force_early_return = 1; + jvmtiError err = jvmti->AddCapabilities(&caps); + if (err != JVMTI_ERROR_NONE) { + LOG("AddCapabilities failed: %s (%d)\n", TranslateError(err), err); + return JNI_ERR; + } + + return JNI_OK; +} + +JNIEXPORT void JNICALL +Java_ValueForceEarlyReturn_nSuspendThread(JNIEnv *jni, jclass thisClass, jthread thread) { + suspend_thread(jvmti, jni, thread); +} + +JNIEXPORT void JNICALL +Java_ValueForceEarlyReturn_nResumeThread(JNIEnv *jni, jclass thisClass, jthread thread) { + resume_thread(jvmti, jni, thread); +} + +JNIEXPORT void JNICALL +Java_ValueForceEarlyReturn_nForceEarlyReturn(JNIEnv *jni, jclass thisClass, jthread thread, jobject obj) { + check_jvmti_error(jvmti->ForceEarlyReturnObject(thread, obj), "ForceEarlyReturnObject"); +} + +#ifdef __cplusplus +} +#endif + diff --git a/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetClassFields/ValueGetClassFields.java b/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetClassFields/ValueGetClassFields.java new file mode 100644 index 00000000000..727fdd8af4c --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetClassFields/ValueGetClassFields.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @summary Sanity test for GetClassFields with value classes. + * @requires vm.jvmti + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @run main/othervm/native -agentlib:ValueGetClassFields -XX:+EnableValhalla ValueGetClassFields + */ + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; + +public class ValueGetClassFields { + + private static final String agentLib = "ValueGetClassFields"; + + @LooselyConsistentValue + private static value class ValueClass { + public int f1; + public int f2; + + public ValueClass(int v1, int v2) { f1 = v1; f2 = v2; } + } + + private static value class ValueHolder { + public ValueClass f1; + @NullRestricted + public ValueClass f2; + + public static ValueClass s1 = new ValueClass(0, 1); + + public ValueHolder(int v) { + f1 = new ValueClass(v, v + 100); + f2 = new ValueClass(v + 1, v + 200); + } + } + + public static void main(String[] args) throws Exception { + try { + System.loadLibrary(agentLib); + } catch (UnsatisfiedLinkError ex) { + System.err.println("Failed to load " + agentLib + " lib"); + System.err.println("java.library.path: " + System.getProperty("java.library.path")); + throw ex; + } + + testGetClassFields(ValueClass.class, 2); + testGetClassFields(ValueHolder.class, 3); + } + + private static void testGetClassFields(Class cls, int fieldNum) throws Exception { + String className = cls.getName(); + // Ensure the class is prepared. + cls = Class.forName(className); + log(">> Testing GetClassFields for " + className); + if (!nTestGetClassFields(cls, fieldNum)) { + throw new RuntimeException("ERROR: " + className); + } + log("<< Testing " + className + " - OK"); + log(""); + } + + private static void log(String msg) { + System.out.println(msg); + } + + private static native boolean nTestGetClassFields(Class cls, int fieldNum); +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetClassFields/libValueGetClassFields.cpp b/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetClassFields/libValueGetClassFields.cpp new file mode 100644 index 00000000000..79e1cd513a0 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetClassFields/libValueGetClassFields.cpp @@ -0,0 +1,78 @@ +/* + * 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 +#include +#include "jvmti.h" +#include "jni.h" +#include "jvmti_common.hpp" + +#ifdef __cplusplus +extern "C" { +#endif + +static jvmtiEnv *jvmti = nullptr; + + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { + jint res = jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_1); + if (res != JNI_OK || jvmti == nullptr) { + LOG("GetEnv failed, res = %d", (int)res); + return JNI_ERR; + } + return JNI_OK; +} + +JNIEXPORT jboolean JNICALL +Java_ValueGetClassFields_nTestGetClassFields(JNIEnv *jni, jclass thisClass, jclass cls, jint fieldNum) { + bool result = true; + jint field_count; + jfieldID* fields = nullptr; + check_jvmti_error(jvmti->GetClassFields(cls, &field_count, &fields), "GetClassFields"); + + if (field_count != fieldNum) { + LOG("ERROR: GetClassFields returned unexpected field count: %d (expected %d)\n", (int)field_count, (int)fieldNum); + result = false; + } else { + // Use GetFieldName to verify correctness of the returned fields. + for (jint i = 0; i < field_count; i++) { + char *name = nullptr; + char *signature = nullptr; + + check_jvmti_error(jvmti->GetFieldName(cls, fields[i], &name, &signature, nullptr), "GetFieldName"); + + LOG(" - field %s, sig = %s\n", name, signature); + jvmti->Deallocate((unsigned char *)name); + jvmti->Deallocate((unsigned char *)signature); + } + } + + jvmti->Deallocate((unsigned char *)fields); + return result ? JNI_TRUE : JNI_FALSE; +} + +#ifdef __cplusplus +} +#endif + diff --git a/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetObjectHashCode/ValueGetObjectHashCodeTest.java b/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetObjectHashCode/ValueGetObjectHashCodeTest.java new file mode 100644 index 00000000000..d55d84c72bf --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetObjectHashCode/ValueGetObjectHashCodeTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8348743 + * @summary Tests ValueGetObjectHashCodeTest functionality for value objects. + * @requires vm.jvmti + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @run main/othervm/native -agentlib:ValueGetObjectHashCodeTest + * -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlineLayout + * ValueGetObjectHashCodeTest + */ + +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +public class ValueGetObjectHashCodeTest { + + private static value class ValueClass { + public int f1; + public ValueClass(int v1) { f1 = v1; } + public String toString() { + return "value(" + String.valueOf(f1) + ")"; + } + } + + private static value class ValueHolder { + public ValueClass f1; + @Strict + @NullRestricted + public ValueClass f2; + + public ValueHolder(int v1, int v2) { + f1 = new ValueClass(v1); + f2 = new ValueClass(v2); + } + public String toString() { + return "holder{" + f1 + ", " + f2 + "}"; + } + } + + private static native int getHash0(Object object); + + private static int getHash(Object object) { + int hash = getHash0(object); + System.out.println("hash (" + object + "): " + hash); + return hash; + } + + public static void main(String[] args) { + System.loadLibrary("ValueGetObjectHashCodeTest"); + ValueClass v1 = new ValueClass(8); + ValueHolder h1 = new ValueHolder(8, 8); + + // expect the same hash code for equal value objects + int hash1 = getHash(v1); + int hash2 = getHash(h1.f1); + int hash3 = getHash(h1.f2); + + if (hash1 != hash2 || hash2 != hash3) { + throw new RuntimeException("Hash should be equal"); + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetObjectHashCode/libValueGetObjectHashCodeTest.cpp b/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetObjectHashCode/libValueGetObjectHashCodeTest.cpp new file mode 100644 index 00000000000..a3dfe70abc5 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetObjectHashCode/libValueGetObjectHashCodeTest.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include +#include + +namespace { +jvmtiEnv *jvmti = nullptr; + +void checkJvmti(int code, const char* message) { + if (code != JVMTI_ERROR_NONE) { + printf("Error %s: %d\n", message, code); + abort(); + } +} + +} + +extern "C" JNIEXPORT jint JNICALL Java_ValueGetObjectHashCodeTest_getHash0(JNIEnv* jni_env, jclass clazz, jobject object) { + jint hash = 0; + jvmtiError err = jvmti->GetObjectHashCode(object, &hash); + checkJvmti(err, "GetObjectHashCode failed"); + return hash; +} + +extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { + if (vm->GetEnv(reinterpret_cast(&jvmti), JVMTI_VERSION) != JNI_OK || !jvmti) { + printf("Could not initialize JVMTI\n"); + abort(); + } + return JVMTI_ERROR_NONE; +} + diff --git a/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetObjectMonitorUsage/ValueGetObjectMonitorUsage.java b/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetObjectMonitorUsage/ValueGetObjectMonitorUsage.java new file mode 100644 index 00000000000..91be5e20872 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetObjectMonitorUsage/ValueGetObjectMonitorUsage.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @summary Sanity test for GetObjectMonitorUsage with value classes. + * @requires vm.jvmti + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @run main/othervm/native -agentlib:ValueGetObjectMonitorUsage -XX:+EnableValhalla ValueGetObjectMonitorUsage + */ + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; + +public class ValueGetObjectMonitorUsage { + + private static final String agentLib = "ValueGetObjectMonitorUsage"; + + @LooselyConsistentValue + private static value class ValueClass { + public int f1; + public int f2; + + public ValueClass(int v1, int v2) { f1 = v1; f2 = v2; } + } + + private static value class ValueHolder { + public ValueClass f1; + @NullRestricted + public ValueClass f2; + + public static ValueClass s1 = new ValueClass(0, 1); + + public ValueHolder(int v) { + f1 = new ValueClass(v, v + 100); + f2 = new ValueClass(v + 1, v + 200); + } + } + + public static void main(String[] args) throws Exception { + try { + System.loadLibrary(agentLib); + } catch (UnsatisfiedLinkError ex) { + System.err.println("Failed to load " + agentLib + " lib"); + System.err.println("java.library.path: " + System.getProperty("java.library.path")); + throw ex; + } + + ValueClass testObj1 = new ValueClass(4, 5); + ValueHolder testObj2 = new ValueHolder(6); + + testGetObjectMonitorUsage(testObj1); + testGetObjectMonitorUsage(testObj2); + } + + private static void testGetObjectMonitorUsage(Object testObj) { + String className = testObj.getClass().getName(); + log(">> Testing GetObjectMonitorUsage for " + className); + if (!nTestGetObjectMonitorUsage(testObj)) { + throw new RuntimeException("ERROR: " + className); + } + log("<< Testing " + className + " - OK"); + log(""); + } + + private static void log(String msg) { + System.out.println(msg); + } + + private static native boolean nTestGetObjectMonitorUsage(Object obj); +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetObjectMonitorUsage/libValueGetObjectMonitorUsage.cpp b/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetObjectMonitorUsage/libValueGetObjectMonitorUsage.cpp new file mode 100644 index 00000000000..74c66aa802a --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetObjectMonitorUsage/libValueGetObjectMonitorUsage.cpp @@ -0,0 +1,90 @@ +/* + * 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 +#include +#include "jvmti.h" +#include "jni.h" +#include "jvmti_common.hpp" + +#ifdef __cplusplus +extern "C" { +#endif + +static jvmtiEnv *jvmti = nullptr; + + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { + jint res = jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_1); + if (res != JNI_OK || jvmti == nullptr) { + LOG("GetEnv failed, res = %d", (int)res); + return JNI_ERR; + } + + jvmtiCapabilities caps; + memset(&caps, 0, sizeof(caps)); + caps.can_get_monitor_info = 1; + jvmtiError err = jvmti->AddCapabilities(&caps); + if (err != JVMTI_ERROR_NONE) { + LOG("AddCapabilities failed: %s (%d)\n", TranslateError(err), err); + return JNI_ERR; + } + + return JNI_OK; +} + +JNIEXPORT jboolean JNICALL +Java_ValueGetObjectMonitorUsage_nTestGetObjectMonitorUsage(JNIEnv *jni, jclass thisClass, jobject obj) { + bool result = true; + jvmtiMonitorUsage info; + memset(&info, 0, sizeof(info)); + check_jvmti_error(jvmti->GetObjectMonitorUsage(obj, &info), "GetObjectMonitorUsage"); + + if (info.owner != nullptr) { + LOG("ERROR: owner is not nullptr\n"); + result = false; + } + if (info.entry_count != 0) { + LOG("ERROR: entry_count is non-zero: %d\n", (int)info.entry_count); + result = false; + } + if (info.waiter_count != 0) { + LOG("ERROR: waiter_count is no-zero: %d\n", (int)info.waiter_count); + result = false; + } + if (info.notify_waiter_count != 0) { + LOG("ERROR: notify_waiter_count is no-zero: %d\n", (int)info.notify_waiter_count); + result = false; + } + + jvmti->Deallocate((unsigned char *)info.waiters); + jvmti->Deallocate((unsigned char *)info.notify_waiters); + + return result ? JNI_TRUE : JNI_FALSE; +} + +#ifdef __cplusplus +} +#endif + diff --git a/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetObjectSize/ValueGetObjectSize.java b/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetObjectSize/ValueGetObjectSize.java new file mode 100644 index 00000000000..356cf3db097 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetObjectSize/ValueGetObjectSize.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @summary Sanity test for GetObjectSize with values classes. + * @requires vm.jvmti + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @run main/othervm/native -agentlib:ValueGetObjectSize -XX:+EnableValhalla ValueGetObjectSize + */ + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; + +public class ValueGetObjectSize { + + private static final String agentLib = "ValueGetObjectSize"; + + @LooselyConsistentValue + private static value class ValueClass { + public int f1; + public int f2; + + public ValueClass(int v1, int v2) { f1 = v1; f2 = v2; } + } + + private static value class ValueHolder { + public ValueClass f1; + @NullRestricted + public ValueClass f2; + + public static ValueClass s1 = new ValueClass(0, 1); + + public ValueHolder(int v) { + f1 = new ValueClass(v, v + 100); + f2 = new ValueClass(v + 1, v + 200); + } + } + + public static void main(String[] args) throws Exception { + try { + System.loadLibrary(agentLib); + } catch (UnsatisfiedLinkError ex) { + System.err.println("Failed to load " + agentLib + " lib"); + System.err.println("java.library.path: " + System.getProperty("java.library.path")); + throw ex; + } + + ValueClass testObj1 = new ValueClass(4, 5); + ValueHolder testObj2 = new ValueHolder(6); + + testGetObjectSize(testObj1); + testGetObjectSize(testObj2); + } + + private static void testGetObjectSize(Object testObj) { + String className = testObj.getClass().getName(); + log(">> Testing GetObjectSize for " + className); + if (!nTestGetObjectSize(testObj)) { + throw new RuntimeException("ERROR: " + className); + } + log("<< Testing " + className + " - OK"); + log(""); + } + + private static void log(String msg) { + System.out.println(msg); + } + + private static native boolean nTestGetObjectSize(Object obj); +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetObjectSize/libValueGetObjectSize.cpp b/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetObjectSize/libValueGetObjectSize.cpp new file mode 100644 index 00000000000..bfd056c9bfe --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetObjectSize/libValueGetObjectSize.cpp @@ -0,0 +1,60 @@ +/* + * 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 +#include +#include "jvmti.h" +#include "jni.h" +#include "jvmti_common.hpp" + +#ifdef __cplusplus +extern "C" { +#endif + +static jvmtiEnv *jvmti = nullptr; + + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { + jint res = jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_1); + if (res != JNI_OK || jvmti == nullptr) { + LOG("GetEnv failed, res = %d", (int)res); + return JNI_ERR; + } + return JNI_OK; +} + +JNIEXPORT jboolean JNICALL +Java_ValueGetObjectSize_nTestGetObjectSize(JNIEnv *jni, jclass thisClass, jobject obj) { + jlong size = 0; + check_jvmti_error(jvmti->GetObjectSize(obj, &size), "GetObjectSize"); + + LOG(" GetObjectSize returned %d\n", (int)size); + + return JNI_TRUE; +} + +#ifdef __cplusplus +} +#endif + diff --git a/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetSetLocal/ValueGetSetLocal.java b/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetSetLocal/ValueGetSetLocal.java new file mode 100644 index 00000000000..299b65ae498 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetSetLocal/ValueGetSetLocal.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @summary Sanity tests for GetLocalObject/SetLocalObject/GetLocalInstance with value classes. + * @requires vm.jvmti + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @run main/othervm/native -agentlib:ValueGetSetLocal -XX:+EnableValhalla ValueGetSetLocal + */ + +import java.util.Objects; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; + +public class ValueGetSetLocal { + + private static final String agentLib = "ValueGetSetLocal"; + + @LooselyConsistentValue + private static value class ValueClass { + public int f1; + public int f2; + + public ValueClass(int v1, int v2) { f1 = v1; f2 = v2; } + } + + private static value class ValueHolder { + public ValueClass f1; + @NullRestricted + public ValueClass f2; + + public static ValueClass s1 = new ValueClass(0, 1); + + public ValueHolder(int v) { + f1 = new ValueClass(v, v + 100); + f2 = new ValueClass(v + 1, v + 200); + } + + // slot 0 is "this" + public void meth(ValueClass obj1, // slot 1 + ValueHolder obj2) { // slot 2 + Object obj3 = obj2; // slot 3 + // SetLocalObject can only set locals for top frame of virtual threads. + boolean testSetLocal = !Thread.currentThread().isVirtual(); + if (!nTestLocals(Thread.currentThread(), testSetLocal)) { + throw new RuntimeException("ERROR: nTestLocals failed"); + } + // nTestLocals sets obj3 = obj1 + if (testSetLocal && !Objects.equals(obj3, obj1)) { + throw new RuntimeException("ERROR: obj3 != obj1" + " (obj3 = " + obj3 + ")"); + } + } + } + + public static void main(String[] args) throws Exception { + try { + System.loadLibrary(agentLib); + } catch (UnsatisfiedLinkError ex) { + System.err.println("Failed to load " + agentLib + " lib"); + System.err.println("java.library.path: " + System.getProperty("java.library.path")); + throw ex; + } + + ValueClass testObj1 = new ValueClass(7, 8); + ValueHolder testObj2 = new ValueHolder(9); + testObj2.meth(testObj1, testObj2); + } + + private static native boolean nTestLocals(Thread thread, boolean testSetLocal); +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetSetLocal/libValueGetSetLocal.cpp b/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetSetLocal/libValueGetSetLocal.cpp new file mode 100644 index 00000000000..e37500fafe3 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/valhalla/GetSetLocal/libValueGetSetLocal.cpp @@ -0,0 +1,129 @@ +/* + * 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 +#include +#include "jvmti.h" +#include "jni.h" +#include "jvmti_common.hpp" + +#ifdef __cplusplus +extern "C" { +#endif + +static jvmtiEnv *jvmti = nullptr; + + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { + jint res = jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_1); + if (res != JNI_OK || jvmti == nullptr) { + LOG("GetEnv failed, res = %d", (int)res); + return JNI_ERR; + } + + jvmtiCapabilities caps; + memset(&caps, 0, sizeof(caps)); + caps.can_access_local_variables = 1; + jvmtiError err = jvmti->AddCapabilities(&caps); + if (err != JVMTI_ERROR_NONE) { + LOG("AddCapabilities failed: %s (%d)\n", TranslateError(err), err); + return JNI_ERR; + } + + return JNI_OK; +} + +static void log_value(JNIEnv *jni, jobject value) { + jclass cls = jni->GetObjectClass(value); + if (cls == nullptr) { + LOG("ERROR: value class is nullptr\n"); + return; + } + + char* sig = nullptr; + check_jvmti_error(jvmti->GetClassSignature(cls, &sig, nullptr), "GetClassSignature"); + + LOG(" - the value class: %s\n", sig); + jvmti->Deallocate((unsigned char *)sig); +} + +static jobject get_local(JNIEnv *jni, jthread thread, jint depth, jint slot) { + LOG("GetLocalObject for slot %d...\n", (int)slot); + jobject value = nullptr; + check_jvmti_error(jvmti->GetLocalObject(thread, depth, slot, &value), "GetLocalObject"); + + log_value(jni, value); + + return value; +} + +static void set_local(jthread thread, jint depth, jint slot, jobject value) { + LOG("SetLocalObject for slot %d...\n", (int)slot); + check_jvmti_error(jvmti->SetLocalObject(thread, depth, slot, value), "SetLocalObject"); +} + +static jobject get_this(JNIEnv *jni, jthread thread, jint depth) { + LOG("GetLocalInstance...\n"); + jobject value = nullptr; + check_jvmti_error(jvmti->GetLocalInstance(thread, depth, &value), "GetLocalInstance"); + + log_value(jni, value); + + return value; +} + +JNIEXPORT jboolean JNICALL +Java_ValueGetSetLocal_nTestLocals(JNIEnv *jni, jclass thisClass, jthread thread, jboolean testSetLocal) { + bool result = true; + const jint depth = 1; + + jobject obj0 = get_local(jni, thread, depth, 0); + jobject obj1 = get_local(jni, thread, depth, 1); + jobject obj2 = get_local(jni, thread, depth, 2); + jobject obj3 = get_local(jni, thread, depth, 3); + jobject obj_this = get_this(jni, thread, depth); + + // obj0 is expected to be equal "this" + if (!jni->IsSameObject(obj0, obj_this)) { + LOG("ERROR: obj0 != obj_this\n"); + result = false; + } + // obj3 is expected to be equal obj2 + if (!jni->IsSameObject(obj3, obj2)) { + LOG("ERROR: obj3 != obj2\n"); + result = false; + } + + if (testSetLocal) { + // set obj3 = obj1 + set_local(thread, depth, 3, obj1); + } + + return result ? JNI_TRUE : JNI_FALSE; +} + +#ifdef __cplusplus +} +#endif + diff --git a/test/hotspot/jtreg/serviceability/jvmti/valhalla/HeapDump/HeapDump.java b/test/hotspot/jtreg/serviceability/jvmti/valhalla/HeapDump/HeapDump.java new file mode 100644 index 00000000000..16dc9c0574f --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/valhalla/HeapDump/HeapDump.java @@ -0,0 +1,509 @@ +/* + * 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 + * 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. + */ + +/** + * @ignore Fix JDK-8328468 + * @test + * @modules java.base/jdk.internal.value + * @library /test/lib + * @requires vm.jvmti + * @enablePreview + * @compile HeapDump.java + * @run main/othervm HeapDump + */ + +import java.io.File; +import java.lang.ref.Reference; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.concurrent.TimeUnit; +import jdk.internal.value.PrimitiveClass; +import jdk.test.lib.apps.LingeredApp; +import jdk.test.lib.JDKToolLauncher; +import jdk.test.lib.hprof.model.HackJavaValue; +import jdk.test.lib.hprof.model.InlinedJavaObject; +import jdk.test.lib.hprof.model.JavaByte; +import jdk.test.lib.hprof.model.JavaClass; +import jdk.test.lib.hprof.model.JavaField; +import jdk.test.lib.hprof.model.JavaHeapObject; +import jdk.test.lib.hprof.model.JavaObject; +import jdk.test.lib.hprof.model.JavaObjectArray; +import jdk.test.lib.hprof.model.JavaStatic; +import jdk.test.lib.hprof.model.JavaThing; +import jdk.test.lib.hprof.model.JavaValueArray; +import jdk.test.lib.hprof.model.Snapshot; +import jdk.test.lib.process.ProcessTools; + +import java.util.Enumeration; + +import jdk.test.lib.hprof.parser.Reader; + + +class TestClass { + + public static primitive class MyPrimitive0 { + public byte prim0_fld1; + public MyPrimitive0(int fld1) { prim0_fld1 = (byte)fld1; } + } + public static primitive class MyPrimitive { + public MyPrimitive0 prim_prim0; + public byte prim_fld1; + public int prim_fld2; + + public MyPrimitive(int p0, int fld1, int fld2) { + prim_fld1 = (byte)fld1; prim_fld2 = fld2; prim_prim0 = new MyPrimitive0(p0); + } + } + + public static primitive class PrimitiveHolder { + // offset of the inlined hld_flatObj is the same as offset of inlined PrimitiveHolder + public MyPrimitive hld_flatObj; + + public PrimitiveHolder(int n) { + hld_flatObj = new MyPrimitive((byte)n, n+1, n+2); + } + } + + // primitive class with reference + public static primitive class MyPrimitiveRef { + public byte ref_fld1; + public int ref_fld2; + public String ref_strObj; + + public MyPrimitiveRef(int v1, int v2) { + ref_fld1 = (byte)v1; + ref_fld2 = v2; + ref_strObj = "#" + String.valueOf(v2); } + } + + public static primitive class PrimitiveHolderRef { + public MyPrimitiveRef[] flatArr = new MyPrimitiveRef[5]; + public MyPrimitiveRef flatObj; + public String ref_str; + public PrimitiveHolderRef(int n) { + ref_str = String.valueOf(n); + flatObj = new MyPrimitiveRef(n, n + 10); + for (int i = 0; i < flatArr.length; i++) { + flatArr[i] = new MyPrimitiveRef(i + n + 1, i + n + 11); + } + } + } + + public MyPrimitive[] main_flatArr = new MyPrimitive[3]; + public MyPrimitive main_flatObj = new MyPrimitive(10, 15, 9); + public MyPrimitiveRef main_flatObjRef = new MyPrimitiveRef(11, 144); + public MyPrimitiveRef[] flatArrRef = new MyPrimitiveRef[4]; + public String main_strObj = "targ.strObj"; + + public Object main_nullObj; + + public final PrimitiveHolder main_primHolder = new PrimitiveHolder(16); + // array of compound primitive objects + public final PrimitiveHolderRef[] primHolderArr = new PrimitiveHolderRef[4]; + + + // static inlined fields + public static MyPrimitive main_flatObjStatic = new MyPrimitive(13, 241, 24); + public static MyPrimitiveRef[] flatArrRefStatic = new MyPrimitiveRef[6]; + static { + for (int i = 0; i < flatArrRefStatic.length; i++) { + flatArrRefStatic[i] = new MyPrimitiveRef(i + 200, i + 225); + } + } + + public TestClass() { + for (int i = 0; i < main_flatArr.length; i++) { + main_flatArr[i] = new MyPrimitive(i + 10, i + 110, i + 35); + } + for (int i = 0; i < flatArrRef.length; i++) { + flatArrRef[i] = new MyPrimitiveRef(i + 100, i + 120); + } + for (int i = 0; i < primHolderArr.length; i++) { + primHolderArr[i] = new PrimitiveHolderRef(20 + i); + } + } +} + +class HeapDumpTarg extends LingeredApp { + + public static void main(String[] args) { + TestClass testObj = new TestClass(); + LingeredApp.main(args); + Reference.reachabilityFence(testObj); + } + +} + +public class HeapDump { + public static void main(String[] args) throws Throwable { + LingeredApp theApp = null; + String hprogFile = new File(System.getProperty("test.classes") + "/Myheapdump.hprof").getAbsolutePath(); + try { + theApp = new HeapDumpTarg(); + + // -XX:+PrintInlineLayout is debug-only arg + LingeredApp.startApp(theApp, "--enable-preview"/*, "-XX:+PrintInlineLayout"*/); + + // jcmd GC.heap_dump + JDKToolLauncher launcher = JDKToolLauncher + .createUsingTestJDK("jcmd") + .addToolArg(Long.toString(theApp.getPid())) + .addToolArg("GC.heap_dump") + .addToolArg(hprogFile); + Process jcmd = ProcessTools.startProcess("jcmd", new ProcessBuilder(launcher.getCommand())); + // If something goes wrong with heap dumping most likely we'll get crash of the target VM + while (!jcmd.waitFor(5, TimeUnit.SECONDS)) { + if (!theApp.getProcess().isAlive()) { + log("ERROR: target VM died, killing jcmd..."); + jcmd.destroyForcibly(); + throw new Exception("Target VM died"); + } + } + + if (jcmd.exitValue() != 0) { + throw new Exception("Jcmd exited with code " + jcmd.exitValue()); + } + } finally { + LingeredApp.stopApp(theApp); + } + + // test object to compare + TestClass testObj = new TestClass(); + + log("Reading " + hprogFile + "..."); + try (Snapshot snapshot = Reader.readFile(hprogFile,true, 0)) { + log("Snapshot read, resolving..."); + snapshot.resolve(true); + log("Snapshot resolved."); + + JavaObject dumpObj = findObject(snapshot, testObj.getClass().getName()); + + log(""); + print(dumpObj); + + log(""); + log("Verifying object " + testObj.getClass().getName() + " (dumped object " + dumpObj + ")"); + compareObjectFields(" ", testObj, dumpObj); + } + } + + private static JavaObject findObject(Snapshot snapshot, String className) throws Exception { + log("looking for " + className + "..."); + JavaClass jClass = snapshot.findClass(className); + if (jClass == null) { + throw new Exception("'" + className + "' not found"); + } + Enumeration objects = jClass.getInstances(false); + if (!objects.hasMoreElements()) { + throw new Exception("No '" + className + "' instances found"); + } + JavaHeapObject heapObj = objects.nextElement(); + if (objects.hasMoreElements()) { + throw new Exception("More than 1 instances of '" + className + "' found"); + } + if (!(heapObj instanceof JavaObject)) { + throw new Exception("'" + className + "' instance is not JavaObject (" + heapObj.getClass() + ")"); + } + return (JavaObject)heapObj; + } + + // description of the current object for logging and error reporting + private static String objDescr; + private static boolean errorReported = false; + + private static void compareObjects(String logPrefix, Object testObj, JavaThing dumpObj) throws Exception { + if (testObj == null) { + if (!isNullValue(dumpObj)) { + throw new Exception("null expected, but dumped object is " + dumpObj); + } + log(logPrefix + objDescr + ": null"); + } else if (dumpObj instanceof JavaObject obj) { + // special handling for Strings + if (testObj instanceof String testStr) { + objDescr += " (String, " + obj.getIdString() + ")"; + if (!obj.getClazz().isString()) { + throw new Exception("value (" + obj + ")" + + " is not String (" + obj.getClazz() + ")"); + } + String dumpStr = getStringValue(obj); + if (!testStr.equals(dumpStr)) { + throw new Exception("different values:" + + " expected \"" + testStr + "\", actual \"" + dumpStr + "\""); + } + log(logPrefix + objDescr + ": \"" + testStr + "\" ( == \"" + dumpStr + "\")"); + } else { + // other Object + log(logPrefix + objDescr + ": Object " + obj); + if (isTestClass(obj.getClazz().getName())) { + compareObjectFields(logPrefix + " ", testObj, obj); + } + } + } else { + throw new Exception("Object expected, but the value (" + dumpObj + ")" + + " is not JavaObject (" + dumpObj.getClass() + ")"); + } + } + + private static void compareObjectFields(String logPrefix, Object testObj, JavaObject dumpObj) throws Exception { + Field[] fields = testObj.getClass().getDeclaredFields(); + for (Field testField : fields) { + boolean isStatic = Modifier.isStatic(testField.getModifiers()); + testField.setAccessible(true); + objDescr = "- " + (isStatic ? "(static) " : "") + + (PrimitiveClass.isPrimitiveClass(testField.getType()) ? "(primitive) " : "") + + testField.getName() + " ('" + testField.getType().descriptorString() + "')"; + try { + Object testValue = testField.get(testObj); + + JavaField dumpField = getField(dumpObj, testField.getName(), isStatic); + JavaThing dumpValue = isStatic + ? dumpObj.getClazz().getStaticField(dumpField.getName()) + : dumpObj.getField(dumpField.getName()); + + objDescr += ", dump signature '" + dumpField.getSignature() + "'"; + + compareType(testField, dumpField); + + if (testValue == null) { + if (!isNullValue(dumpValue)) { + throw new Exception("null expected, but dumped object is " + dumpValue); + } + log(logPrefix + objDescr + ": null"); + } else { + switch (testField.getType().descriptorString().charAt(0)) { + case 'L': + case 'Q': + compareObjects(logPrefix, testValue, dumpValue); + break; + case '[': + int testLength = Array.getLength(testValue); + objDescr += " (Array of '" + testField.getType().getComponentType() + "'" + + ", length = " + testLength + ", " + dumpValue + ")"; + if (dumpValue instanceof JavaValueArray arr) { + // array of primitive type + char testElementType = testField.getType().getComponentType().descriptorString().charAt(0); + if ((char)arr.getElementType() != testElementType) { + throw new Exception("wrong element type: '" + (char)arr.getElementType() + "'"); + } + int dumpLength = arr.getLength(); + if (dumpLength != testLength) { + throw new Exception("wrong array size: " + dumpLength); + } + JavaThing[] dumpElements = arr.getElements(); + log(logPrefix + objDescr); + for (int j = 0; j < testLength; j++) { + Object elementValue = Array.get(testValue, j); + objDescr = "[" + j + "]"; + if (arr.isFlatArray()) { + compareObjects(logPrefix + " ", elementValue, dumpElements[j]); + } else { + comparePrimitiveValues(elementValue, dumpElements[j]); + log(logPrefix + " [" + j + "]: " + elementValue + " ( == " + dumpElements[j] + ")"); + } + } + } else if (dumpValue instanceof JavaObjectArray arr) { + int dumpLength = arr.getLength(); + if (dumpLength != testLength) { + throw new Exception("wrong array size: " + dumpLength); + } + JavaThing[] dumpElements = arr.getElements(); + log(logPrefix + objDescr); + for (int j = 0; j < testLength; j++) { + Object elementValue = Array.get(testValue, j); + objDescr = "[" + j + "]"; + compareObjects(logPrefix + " ", elementValue, dumpElements[j]); + } + } else { + throw new Exception("Array expected, but the value (" + dumpValue + ")" + + " is neither JavaValueArray nor JavaObjectArray" + + " (" + dumpValue.getClass() + ")"); + } + break; + default: + comparePrimitiveValues(testValue, dumpValue); + log(logPrefix + objDescr + ": " + testValue + " ( == " + dumpValue + ")"); + break; + } + } + } catch (Exception ex) { + if (!errorReported) { + log(logPrefix + objDescr + ": ERROR - " + ex.getMessage()); + errorReported = true; + } + throw ex; + } + } + } + + private static JavaField getField(JavaObject obj, String fieldName, boolean isStatic) throws Exception { + if (isStatic) { + JavaStatic[] statics = obj.getClazz().getStatics(); + for (JavaStatic st: statics) { + if (st.getField().getName().equals(fieldName)) { + return st.getField(); + } + } + } else { + JavaField[] fields = obj.getClazz().getFields(); + for (JavaField field : fields) { + if (fieldName.equals(field.getName())) { + return field; + } + } + } + throw new Exception("field '" + fieldName + "' not found"); + } + + private static void compareType(Field field, JavaField dumpField) throws Exception { + String sig = field.getType().descriptorString(); + char type = sig.charAt(0); + if (type == '[' || type == 'Q') { + type = 'L'; + } + char dumpType = dumpField.getSignature().charAt(0); + if (dumpType == 'Q') { + dumpType = 'L'; + } + if (dumpType != type) { + throw new Exception("type mismatch:" + + " expected '" + type + "' (" + sig + ")" + + ", found '" + dumpField.getSignature().charAt(0) + "' (" + dumpField.getSignature() + ")"); + } + } + + private static void comparePrimitiveValues(Object testValue, JavaThing dumpValue) throws Exception { + // JavaByte.toString() returns hex + String testStr = testValue instanceof Byte byteValue + ? (new JavaByte(byteValue)).toString() + : String.valueOf(testValue); + String dumpStr = dumpValue.toString(); + if (!testStr.equals(dumpStr)) { + throw new Exception("Wrong value: expected " + testStr + ", actual " + dumpStr); + } + } + + private static boolean isNullValue(JavaThing value) { + return value == null + // dumped value is HackJavaValue with string representation "" + || (value instanceof HackJavaValue && "".equals(value.toString())); + } + + private static String getStringValue(JavaObject value) { + JavaThing valueObj = value.getField("value"); + if (valueObj instanceof JavaValueArray valueArr) { + try { + if (valueArr.getElementType() == 'B') { + Field valueField = JavaByte.class.getDeclaredField("value"); + valueField.setAccessible(true); + JavaThing[] things = valueArr.getElements(); + byte[] bytes = new byte[things.length]; + for (int i = 0; i < things.length; i++) { + bytes[i] = valueField.getByte(things[i]); + } + return new String(bytes); + } + } catch (Exception ignored) { + } + return valueArr.valueString(); + } else { + return null; + } + } + + private static void print(JavaObject dumpObject) { + log("Dumped object " + dumpObject + ":"); + print("", dumpObject); + } + + private static void print(String prefix, JavaObject dumpObject) { + JavaClass clazz = dumpObject.getClazz(); + // print only test classes + if (!isTestClass(clazz.getName())) { + return; + } + + JavaField[] fields = clazz.getFields(); + for (JavaField field : fields) { + printFieldValue(prefix, field, false, dumpObject.getField(field.getName())); + } + + JavaStatic[] statics = clazz.getStatics(); + for (JavaStatic st: statics) { + printFieldValue(prefix, st.getField(), true, st.getValue()); + } + } + + private static void printFieldValue(String prefix, JavaField field, boolean isStatic, JavaThing value) { + String logPrefix = prefix + "- " + (isStatic ? "(static) " : "") + + field.getName() + " ('" + field.getSignature() + "'): "; + if (isNullValue(value)) { + log(logPrefix + "null"); + } else { + if (value instanceof JavaObject obj) { + logPrefix += "(class '" + obj.getClazz().getName() + "'): "; + if (obj.getClazz().isString()) { + String dumpStr = getStringValue(obj); + log(logPrefix + "\"" + dumpStr + "\""); + } else { + log(logPrefix + (value instanceof InlinedJavaObject ? "inlined " : "") + "object " + obj); + print(prefix + " ", obj); + } + } else if (value instanceof JavaObjectArray arr) { + log(logPrefix + " array " + arr + " length: " + arr.getLength()); + JavaThing[] values = arr.getValues(); + for (int v = 0; v < values.length; v++) { + log(prefix + " [" + v + "]: " + values[v]); + if (values[v] instanceof JavaObject obj) { + print(prefix + " ", obj); + } + } + } else if (value instanceof JavaValueArray arr) { // array of primitive type or flat array + if (arr.isFlatArray()) { + log(logPrefix + " flat array " + arr + " length: " + arr.getLength()); + JavaThing[] values = arr.getElements(); + for (int v = 0; v < values.length; v++) { + log(prefix + " [" + v + "]: " + values[v]); + if (values[v] instanceof JavaObject obj) { + print(prefix + " ", obj); + } + } + } else { + log(logPrefix + "(array of '" + (char)arr.getElementType() + "')" + ": " + arr.valueString()); + } + } else { + log(logPrefix + "(" + value.getClass() + ")" + value.toString()); + } + } + } + + private static boolean isTestClass(String className) { + return className.startsWith("TestClass"); + } + + private static void log(String msg) { + System.out.println(msg); + System.out.flush(); + } + +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/valhalla/Heapwalking/ValueHeapwalkingTest.java b/test/hotspot/jtreg/serviceability/jvmti/valhalla/Heapwalking/ValueHeapwalkingTest.java new file mode 100644 index 00000000000..7ed13ad1951 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/valhalla/Heapwalking/ValueHeapwalkingTest.java @@ -0,0 +1,351 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Tests heapwalking API (FollowReferences, IterateThroughHeap, GetObjectsWithTags) for value objects. + * @requires vm.jvmti + * @modules java.base/jdk.internal.vm.annotation java.base/jdk.internal.value + * @enablePreview + * @run main/othervm/native -agentlib:ValueHeapwalkingTest + * -XX:+UnlockDiagnosticVMOptions + * -XX:+UseArrayFlattening + * -XX:+UseFieldFlattening + * -XX:+UseAtomicValueFlattening + * -XX:+UseNullableValueFlattening + * -XX:+PrintInlineLayout + * -XX:+PrintFlatArrayLayout + * -Xlog:jvmti+table + * ValueHeapwalkingTest + */ + +import java.lang.ref.Reference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import java.lang.reflect.Field; + +public class ValueHeapwalkingTest { + + static value class Value { + int v; + Value() { + this(0); + } + Value(int v) { + this.v = v; + } + } + + // flat object has flat field (address of the field is the same as address of the object) + static value class Value2 { + @Strict + @NullRestricted + public Value v1; + @Strict + @NullRestricted + public Value v2; + Value2() { + this(0); + } + Value2(int i) { + this.v1 = new Value(i); + this.v2 = new Value(i+1); + super(); + } + } + + static value class ValueHolder { + public Value v1; + @Strict + @NullRestricted + public Value v2; + public Value v_null; + + public Value2 v2_1; + @Strict + @NullRestricted + public Value2 v2_2; + + public Value[] v_arr; + public Value2[] v2_arr; + + public ValueHolder(int seed) throws Exception { + v1 = new Value(seed); + v2 = new Value(seed + 1); + v_null = null; + + v2_1 = new Value2(seed + 6); + v2_2 = new Value2(seed + 8); + + v_arr = createValueArray(seed); + v2_arr = createValue2Array(seed); + } + } + + static Value[] createValueArray(int seed) throws Exception { + Value[] arr = (Value[])ValueClass.newNullableAtomicArray(Value.class, 5); + for (int i = 0; i < arr.length; i++) { + arr[i] = i == 2 ? null : new Value(seed + 10 + i); + } + return arr; + } + + static Value2[] createValue2Array(int seed) throws Exception { + Value2[] arr = (Value2[])ValueClass.newNullRestrictedNonAtomicArray(Value2.class, 5, Value2.class.newInstance()); + for (int i = 0; i < arr.length; i++) { + arr[i] = new Value2(seed + 20 + i * 2); + } + return arr; + } + + static final int TAG_VALUE_CLASS = 1; + static final int TAG_VALUE2_CLASS = 2; + static final int TAG_HOLDER_CLASS = 3; + static final int TAG_VALUE_ARRAY = 4; + static final int TAG_VALUE2_ARRAY = 5; + + static final int TAG_MIN = TAG_VALUE_CLASS; + static final int TAG_MAX = TAG_VALUE2_ARRAY; + + static String tagStr(int tag) { + String suffix = " (tag " + tag + ")"; + switch (tag) { + case TAG_VALUE_CLASS: return "Value class" + suffix; + case TAG_VALUE2_CLASS: return "Value2 class" + suffix; + case TAG_HOLDER_CLASS: return "ValueHolder class" + suffix; + case TAG_VALUE_ARRAY: return "Value[] object" + suffix; + case TAG_VALUE2_ARRAY: return "Value2[] object" + suffix; + } + return "Unknown" + suffix; + } + + static native void setTag(Object object, long tag); + static native long getTag(Object object); + + static native void reset(); + + static native void followReferences(); + + static native void iterateThroughHeap(); + + static native int count(int classTag); + static native int refCount(int fromTag, int toTag); + static native int primitiveFieldCount(int tag); + + static native long getMaxTag(); + + static native int getObjectWithTags(long minTag, long maxTag, Object[] objects, long[] tags); + + + // counts non-null elements in the array + static int nonNullCount(T[] array) { + return (int)Arrays.stream(array).filter(e -> e != null).count(); + } + + static void verifyMinCount(int classTag, int minCount) { + int count = count(classTag); + String msg = tagStr(classTag) + " count: " + count + ", min expected: " + minCount; + if (count < minCount) { + throw new RuntimeException(msg); + } + System.out.println(msg); + } + + static void verifyRefCount(int tagFrom, int tagTo, int expectedCount) { + int count = refCount(tagFrom, tagTo); + String msg = "Ref.count from " + tagStr(tagFrom) + " to " + tagStr(tagTo) + ": " + + count + ", expected: " + expectedCount; + if (count != expectedCount) { + throw new RuntimeException(msg); + } + System.out.println(msg); + } + + static void verifyPrimitiveFieldCount(int classTag, int expectedCount) { + int count = primitiveFieldCount(classTag); + String msg = "Primitive field count from " + tagStr(classTag) + ": " + + count + ", expected: " + expectedCount; + if (count != expectedCount) { + throw new RuntimeException(msg); + } + System.out.println(msg); + } + + + static void printObject(Object obj) { + printObject("", obj); + } + + static void printObject(String prefix, Object obj) { + if (obj == null) { + System.out.println(prefix + "null"); + return; + } + + Class clazz = obj.getClass(); + System.out.println(prefix + "Object (class " + clazz.getName() + ", tag = " + getTag(obj) + ", class tag = " + getTag(clazz)); + + if (clazz.isArray()) { + Class elementType = clazz.getComponentType(); + int length = java.lang.reflect.Array.getLength(obj); + System.out.println(prefix + "Array of " + elementType + ", length = " + length + " ["); + for (int i = 0; i < length; i++) { + Object element = java.lang.reflect.Array.get(obj, i); + if (elementType.isPrimitive()) { + if (i == 0) { + System.out.print(prefix + " "); + } else { + System.out.print(", "); + } + System.out.print(prefix + "(" + i + "):" + element); + } else { + System.out.println(prefix + "(" + i + "):" + "NOT primitive"); + printObject(prefix + " ", element); + } + } + System.out.println(prefix + "]"); + } else { + while (clazz != null && clazz != Object.class) { + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + try { + field.setAccessible(true); + Object value = field.get(obj); + Class fieldType = field.getType(); + System.out.println(prefix + "- " + field.getName() + " (" + fieldType + ") = " + value); + + if (!fieldType.isPrimitive()) { + printObject(prefix + " ", value); + } + } catch (IllegalAccessException | java.lang.reflect.InaccessibleObjectException e) { + System.err.println(" Error accessing field " + field.getName() + ": " + e.getMessage()); + } + } + clazz = clazz.getSuperclass(); + } + } + } + + public static void main(String[] args) throws Exception { + System.loadLibrary("ValueHeapwalkingTest"); + ValueHolder holder = new ValueHolder(10); + + setTag(Value.class, TAG_VALUE_CLASS); + setTag(Value2.class, TAG_VALUE2_CLASS); + setTag(ValueHolder.class, TAG_HOLDER_CLASS); + setTag(holder.v_arr, TAG_VALUE_ARRAY); + setTag(holder.v2_arr, TAG_VALUE2_ARRAY); + + reset(); + System.out.println(">>iterateThroughHeap"); + iterateThroughHeap(); + System.out.println("<>followReferences"); + followReferences(); + System.out.println("<>followReferences (2)"); + followReferences(); + System.out.println("<>getObjectWithTags, maxTag = " + maxTag); + int count = getObjectWithTags(1, maxTag, objects, tags); + System.out.println("getObjectWithTags returned " + count); + for (int i = 0; i < count; i++) { + System.out.println(" [" + i + "] " + objects[i] + ", tag = " + tags[i]); + if (objects[i] == null || tags[i] == 0) { + throw new RuntimeException("unexpected object"); + } + } + int expectedTaggedCount = 5 // TAG_VALUE_CLASS/TAG_VALUE2_CLASS/TAG_HOLDER_CLASS/TAG_VALUE_ARRAY/TAG_VALUE2_ARRAY + + reachableValue2Count + reachableValueCount; + if (count != expectedTaggedCount) { + throw new RuntimeException("unexpected getObjectWithTags result: " + count + + ", expected " + expectedTaggedCount); + } + + Reference.reachabilityFence(holder); + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/valhalla/Heapwalking/libValueHeapwalkingTest.cpp b/test/hotspot/jtreg/serviceability/jvmti/valhalla/Heapwalking/libValueHeapwalkingTest.cpp new file mode 100644 index 00000000000..eb0f08165dd --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/valhalla/Heapwalking/libValueHeapwalkingTest.cpp @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include "jvmti_common.hpp" + +static jvmtiEnv *jvmti = nullptr; + +extern "C" JNIEXPORT void JNICALL +Java_ValueHeapwalkingTest_setTag(JNIEnv* jni_env, jclass clazz, jobject object, jlong tag) { + jvmtiError err = jvmti->SetTag(object, tag); + check_jvmti_error(err, "could not set tag"); +} + +extern "C" JNIEXPORT jlong JNICALL +Java_ValueHeapwalkingTest_getTag(JNIEnv* jni_env, jclass clazz, jobject object) { + jlong tag; + check_jvmti_error(jvmti->GetTag(object, &tag), "could not get tag"); + return tag; +} + +const int TAG_VALUE_CLASS = 1; +const int TAG_VALUE2_CLASS = 2; +const int TAG_HOLDER_CLASS = 3; +const int TAG_VALUE_ARRAY = 4; +const int TAG_VALUE3_ARRAY = 5; +const int MAX_TAG = 5; +const int START_TAG = 10; // start value for tagging objects + +static const char* tag_str(jlong tag) { + switch (tag) { + case 0: return "None"; + case TAG_VALUE_CLASS: return "Value class"; + case TAG_VALUE2_CLASS: return "Value2 class"; + case TAG_HOLDER_CLASS: return "ValueHolder class"; + case TAG_VALUE_ARRAY: return "Value[] object"; + case TAG_VALUE3_ARRAY: return "Value2[] object"; + } + return "Unknown"; +} + +struct Callback_Data { + // Updated by heap_iteration_callback. + jint counters[MAX_TAG + 1]; + // Updated by heap_reference_callback. + jint ref_counters[MAX_TAG + 1][MAX_TAG + 1]; + // Updated by primitive_field_callback. + jint primitive_counters[MAX_TAG + 1]; + jlong tag_counter; +}; + +static Callback_Data callbackData; + +extern "C" JNIEXPORT void JNICALL +Java_ValueHeapwalkingTest_reset(JNIEnv* jni_env, jclass clazz) { + memset(&callbackData, 0, sizeof(callbackData)); + callbackData.tag_counter = START_TAG; +} + +extern "C" JNIEXPORT jint JNICALL +Java_ValueHeapwalkingTest_count(JNIEnv* jni_env, jclass clazz, jint tag) { + return callbackData.counters[tag]; +} + +extern "C" JNIEXPORT jint JNICALL +Java_ValueHeapwalkingTest_refCount(JNIEnv* jni_env, jclass clazz, jint fromTag, jint toTag) { + return callbackData.ref_counters[fromTag][toTag]; +} + +extern "C" JNIEXPORT jint JNICALL +Java_ValueHeapwalkingTest_primitiveFieldCount(JNIEnv* jni_env, jclass clazz, jint tag) { + return callbackData.primitive_counters[tag]; +} + +extern "C" JNIEXPORT jlong JNICALL +Java_ValueHeapwalkingTest_getMaxTag(JNIEnv* jni_env, jclass clazz) { + return callbackData.tag_counter; +} + +static jlong safe_deref(jlong* ref) { + return ref == nullptr ? 0 : *ref; +} + +static jint JNICALL +heap_iteration_callback(jlong class_tag, + jlong size, + jlong* tag_ptr, + jint length, + void* user_data) { + Callback_Data* data = (Callback_Data*)user_data; + + if (class_tag != 0 && class_tag <= MAX_TAG) { + data->counters[class_tag]++; + printf("heap_iteration_callback: class_tag = %d (%s), tag = %d (%s), length = %d\n", + (int)class_tag, tag_str(class_tag), (int)*tag_ptr, tag_str(*tag_ptr), length); + fflush(nullptr); + } + return 0; +} + +static jint JNICALL +heap_reference_callback(jvmtiHeapReferenceKind reference_kind, + const jvmtiHeapReferenceInfo* reference_info, + jlong class_tag, + jlong referrer_class_tag, + jlong size, + jlong* tag_ptr, + jlong* referrer_tag_ptr, + jint length, + void* user_data) { + Callback_Data* data = (Callback_Data*)user_data; + + jlong tag = class_tag; + if (tag == 0 && *tag_ptr != 0 && *tag_ptr <= MAX_TAG) { + tag = *tag_ptr; + } + jlong referrer_tag = referrer_class_tag; + if (referrer_tag == 0 && safe_deref(referrer_tag_ptr) != 0 && safe_deref(referrer_tag_ptr) <= MAX_TAG) { + referrer_tag = *referrer_tag_ptr; + } + + if (tag != 0 && referrer_tag != 0) { + // For testing we count only JVMTI_HEAP_REFERENCE_FIELD and JVMTI_HEAP_REFERENCE_ARRAY_ELEMENT references. + if (reference_kind == JVMTI_HEAP_REFERENCE_FIELD || reference_kind == JVMTI_HEAP_REFERENCE_ARRAY_ELEMENT) { + data->ref_counters[referrer_tag][tag]++; + } + + jlong cur_tag = *tag_ptr; + char new_tag_str[64] = {}; + if (*tag_ptr == 0) { // i.e. class_tag != 0, but the object is untagged + *tag_ptr = ++data->tag_counter; + snprintf(new_tag_str, sizeof(new_tag_str), ", set tag to %d", (int)*tag_ptr); + } + printf("heap_reference_callback: kind = %d, class_tag = %d (%s), tag = %d (%s), referrer_tag = %d (%s) %s\n", + (int)reference_kind, (int)class_tag, tag_str(class_tag), (int)cur_tag, tag_str(*tag_ptr), + (int)referrer_tag, tag_str(referrer_tag), new_tag_str); + fflush(nullptr); + } + + return JVMTI_VISIT_OBJECTS; +} + +static jint JNICALL +primitive_field_callback(jvmtiHeapReferenceKind kind, + const jvmtiHeapReferenceInfo* info, + jlong object_class_tag, + jlong* object_tag_ptr, + jvalue value, + jvmtiPrimitiveType value_type, + void* user_data) { + Callback_Data* data = (Callback_Data*)user_data; + if (object_class_tag != 0) { + char value_str[64] = {}; + switch (value_type) { + case JVMTI_PRIMITIVE_TYPE_BOOLEAN: snprintf(value_str, sizeof(value_str), "(boolean) %s", value.z ? "true" : "false"); break; + case JVMTI_PRIMITIVE_TYPE_BYTE: snprintf(value_str, sizeof(value_str), "(byte) %d", value.b); break; + case JVMTI_PRIMITIVE_TYPE_CHAR: snprintf(value_str, sizeof(value_str), "(char) %c", value.c); break; + case JVMTI_PRIMITIVE_TYPE_SHORT: snprintf(value_str, sizeof(value_str), "(short): %d", value.s); break; + case JVMTI_PRIMITIVE_TYPE_INT: snprintf(value_str, sizeof(value_str), "(int): %d", value.i); break; + case JVMTI_PRIMITIVE_TYPE_LONG: snprintf(value_str, sizeof(value_str), "(long): %lld", (long long)value.j); break; + case JVMTI_PRIMITIVE_TYPE_FLOAT: snprintf(value_str, sizeof(value_str), "(float): %f", value.f); break; + case JVMTI_PRIMITIVE_TYPE_DOUBLE: snprintf(value_str, sizeof(value_str), "(double): %f", value.d); break; + default: snprintf(value_str, sizeof(value_str), "invalid_type %d (%c)", (int)value_type, (char)value_type); + } + + if (object_class_tag != 0 && object_class_tag <= MAX_TAG) { + data->primitive_counters[object_class_tag]++; + if (*object_tag_ptr != 0) { + *object_tag_ptr = *object_tag_ptr; + } + } + + printf("primitive_field_callback: kind = %d, class_tag = %d (%s), tag = %d (%s), value = %s\n", + (int)kind, (int)object_class_tag, tag_str(object_class_tag), + (int)*object_tag_ptr, tag_str(*object_tag_ptr), value_str); + fflush(nullptr); + } + return 0; +} + +static jint JNICALL +array_primitive_value_callback(jlong class_tag, + jlong size, + jlong* tag_ptr, + jint element_count, + jvmtiPrimitiveType element_type, + const void* elements, + void* user_data) { + Callback_Data* data = (Callback_Data*)user_data; + if (class_tag != 0 || *tag_ptr != 0) { + printf("array_primitive_value_callback: class_tag = %d (%s), tag = %d (%s), element_count = %d, element_type = %c\n", + (int)class_tag, tag_str(class_tag), (int)*tag_ptr, tag_str(*tag_ptr), element_count, (char)element_type); + fflush(nullptr); + } + return 0; +} + +static jint JNICALL +string_primitive_value_callback(jlong class_tag, + jlong size, + jlong* tag_ptr, + const jchar* value, + jint value_length, + void* user_data) { + Callback_Data* data = (Callback_Data*)user_data; + if (class_tag != 0 || *tag_ptr != 0) { + jchar value_copy[1024] = {}; // fills with 0 + if (value_length > 1023) { + value_length = 1023; + } + memcpy(value_copy, value, value_length * sizeof(jchar)); + printf("string_primitive_value_callback: class_tag = %d (%s), tag = %d (%s), value=\"%ls\"\n", + (int)class_tag, tag_str(class_tag), (int)*tag_ptr, tag_str(*tag_ptr), (wchar_t*)value_copy); + fflush(nullptr); + } + return 0; +} + +extern "C" JNIEXPORT void JNICALL +Java_ValueHeapwalkingTest_followReferences(JNIEnv* jni_env, jclass clazz) { + jvmtiHeapCallbacks callbacks = {}; + callbacks.heap_iteration_callback = heap_iteration_callback; + callbacks.heap_reference_callback = heap_reference_callback; + callbacks.primitive_field_callback = primitive_field_callback; + callbacks.array_primitive_value_callback = array_primitive_value_callback; + callbacks.string_primitive_value_callback = string_primitive_value_callback; + + jvmtiError err = jvmti->FollowReferences(0 /* filter nothing */, + nullptr /* no class filter */, + nullptr /* no initial object, follow roots */, + &callbacks, + &callbackData); + check_jvmti_error(err, "FollowReferences failed"); +} + +extern "C" JNIEXPORT void JNICALL +Java_ValueHeapwalkingTest_iterateThroughHeap(JNIEnv* jni_env, jclass clazz) { + jvmtiHeapCallbacks callbacks = {}; + callbacks.heap_iteration_callback = heap_iteration_callback; + callbacks.heap_reference_callback = heap_reference_callback; + callbacks.primitive_field_callback = primitive_field_callback; + callbacks.array_primitive_value_callback = array_primitive_value_callback; + callbacks.string_primitive_value_callback = string_primitive_value_callback; + + jvmtiError err = jvmti->IterateThroughHeap(0 /* filter nothing */, + nullptr /* no class filter */, + &callbacks, + &callbackData); + check_jvmti_error(err, "IterateThroughHeap failed"); +} + +extern "C" JNIEXPORT jint JNICALL +Java_ValueHeapwalkingTest_getObjectWithTags(JNIEnv* jni_env, jclass clazz, jlong minTag, jlong maxTag, jobjectArray objects, jlongArray tags) { + jsize len = jni_env->GetArrayLength(objects); + + jint tag_count = (jint)(maxTag - minTag + 1); + jlong* scan_tags = nullptr; + check_jvmti_error(jvmti->Allocate(tag_count * sizeof(jlong), (unsigned char**)&scan_tags), + "Allocate failed"); + + for (jlong i = 0; i < tag_count; i++) { + scan_tags[i] = i + minTag; + } + + jint count = 0; + jobject* object_result = nullptr; + jlong* tag_result = nullptr; + + check_jvmti_error(jvmti->GetObjectsWithTags(tag_count, scan_tags, &count, &object_result, &tag_result), + "GetObjectsWithTags failed"); + + if (count > len) { + printf("GetObjectsWithTags returned too many entries: %d (object length is %d)\n", count, (int)len); + fflush(nullptr); + abort(); + } + + for (jint i = 0; i < count; i++) { + jni_env->SetObjectArrayElement(objects, i, object_result[i]); + } + jni_env->SetLongArrayRegion(tags, 0, count, tag_result); + + jvmti->Deallocate((unsigned char*)scan_tags); + jvmti->Deallocate((unsigned char*)object_result); + jvmti->Deallocate((unsigned char*)tag_result); + + return count; +} + +extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { + if (vm->GetEnv(reinterpret_cast(&jvmti), JVMTI_VERSION) != JNI_OK || !jvmti) { + LOG("Could not initialize JVMTI\n"); + abort(); + } + jvmtiCapabilities capabilities; + memset(&capabilities, 0, sizeof(capabilities)); + capabilities.can_tag_objects = 1; + check_jvmti_error(jvmti->AddCapabilities(&capabilities), "adding capabilities"); + return JVMTI_ERROR_NONE; +} + diff --git a/test/hotspot/jtreg/serviceability/jvmti/valhalla/SetTag/ValueTagMapTest.java b/test/hotspot/jtreg/serviceability/jvmti/valhalla/SetTag/ValueTagMapTest.java new file mode 100644 index 00000000000..3eda536ca4d --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/valhalla/SetTag/ValueTagMapTest.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Tests SetTag/GetTag functionality for value objects. + * @requires vm.jvmti + * @modules java.base/jdk.internal.vm.annotation + * @enablePreview + * @run main/othervm/native -agentlib:ValueTagMapTest + * -XX:+UnlockDiagnosticVMOptions + * -XX:+PrintInlineLayout + * -XX:+PrintFlatArrayLayout + * -Xlog:jvmti+table + * ValueTagMapTest + */ + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +public class ValueTagMapTest { + + private static value class ValueClass { + public int f; + public ValueClass(int v) { f = v + 1; } + public String toString() { + return String.valueOf(f); + } + } + + private static value class ValueHolder { + @Strict + @NullRestricted + public ValueClass f0; + + public ValueHolder(int v) { + f0 = new ValueClass(v); + } + public String toString() { + return "holder{" + f0 + "}"; + } + } + + private static value class ValueHolder2 { + public ValueHolder f1; + @Strict + @NullRestricted + public ValueHolder f2; + + public ValueHolder2(int v, int v2) { + f1 = new ValueHolder(v); + f2 = new ValueHolder(v2); + } + public ValueHolder2(ValueHolder h1, int v2) { + f1 = h1; + f2 = new ValueHolder(v2); + } + public String toString() { + return "holder2{" + f1 + ", " + f2 + "}"; + } + } + + + private static native void setTag0(Object object, long tag); + private static void setTag(Object object, long tag) { + setTag0(object, tag); + } + private static native long getTag0(Object object); + private static long getTag(Object object) { + long tag = getTag0(object); + if (tag == 0) { + throw new RuntimeException("Zero tag for object " + object); + } + return tag; + } + + private static void testGetTag(Object o1, Object o2) { + long tag1 = getTag(o1); + long tag2 = getTag(o2); + if (o1 == o2) { + if (tag1 != tag2) { + throw new RuntimeException("different tags for equal objects: " + + o1 + " (tag " + tag1 + "), " + + o2 + " (tag " + tag2 + ")"); + } + } else { + if (tag1 == tag2) { + throw new RuntimeException("equal tags for different objects: " + + o1 + " (tag " + tag1 + "), " + + o2 + " (tag " + tag2 + ")"); + } + } + } + + public static void main(String[] args) { + System.loadLibrary("ValueTagMapTest"); + List items = new ArrayList<>(); + + for (int i = 0; i < 20; i++) { + items.add(new ValueHolder2(i % 4, i % 8)); + } + + long startTime = System.nanoTime(); + long tag = 1; + for (ValueHolder2 item : items) { + setTag(item, tag++); + setTag(item.f1, tag++); + setTag(item.f2, tag++); + } + + for (ValueHolder2 item: items) { + long tag0 = getTag(item); + long tag1 = getTag(item.f1); + long tag2 = getTag(item.f2); + System.out.println("getTag (" + item + "): " + tag0 + ", f1: " + tag1 + ", f2:" + tag2); + } + + startTime = System.nanoTime(); + for (ValueHolder2 item1: items) { + for (ValueHolder2 item2 : items) { + testGetTag(item1, item2); + testGetTag(item1.f1, item2.f1); + testGetTag(item1.f2, item2.f2); + testGetTag(item1.f1, item2.f2); + testGetTag(item1.f2, item2.f1); + } + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/valhalla/SetTag/libValueTagMapTest.cpp b/test/hotspot/jtreg/serviceability/jvmti/valhalla/SetTag/libValueTagMapTest.cpp new file mode 100644 index 00000000000..4996c6e8a50 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/valhalla/SetTag/libValueTagMapTest.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include "jvmti_common.hpp" + +namespace { + +jvmtiEnv *jvmti = nullptr; + +} + +extern "C" JNIEXPORT void JNICALL Java_ValueTagMapTest_setTag0(JNIEnv* jni_env, jclass clazz, jobject object, jlong tag) { + jvmtiError err = jvmti->SetTag(object, tag); + check_jvmti_error(err, "could not set tag"); +} + +extern "C" JNIEXPORT jlong JNICALL Java_ValueTagMapTest_getTag0(JNIEnv* jni_env, jclass clazz, jobject object) { + jlong tag; + check_jvmti_error(jvmti->GetTag(object, &tag), "could not get tag"); + return tag; +} + +extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) { + if (vm->GetEnv(reinterpret_cast(&jvmti), JVMTI_VERSION) != JNI_OK || !jvmti) { + LOG("Could not initialize JVMTI\n"); + abort(); + } + jvmtiCapabilities capabilities; + memset(&capabilities, 0, sizeof(capabilities)); + capabilities.can_tag_objects = 1; + check_jvmti_error(jvmti->AddCapabilities(&capabilities), "adding capabilities"); + return JVMTI_ERROR_NONE; +} + diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/ToggleNotifyJvmtiTest/ToggleNotifyJvmtiTest.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/ToggleNotifyJvmtiTest/ToggleNotifyJvmtiTest.java index 0b003d90276..8332b06cadb 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/vthread/ToggleNotifyJvmtiTest/ToggleNotifyJvmtiTest.java +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/ToggleNotifyJvmtiTest/ToggleNotifyJvmtiTest.java @@ -26,7 +26,6 @@ * @summary Verifies JVMTI works for agents loaded into running VM * @requires vm.jvmti * @requires vm.continuations - * @enablePreview * @library /test/lib /test/hotspot/jtreg * @build jdk.test.whitebox.WhiteBox * diff --git a/test/hotspot/jtreg/serviceability/sa/ClhsdbDumpclass.java b/test/hotspot/jtreg/serviceability/sa/ClhsdbDumpclass.java index 47a5357bab4..63cc65e03cc 100644 --- a/test/hotspot/jtreg/serviceability/sa/ClhsdbDumpclass.java +++ b/test/hotspot/jtreg/serviceability/sa/ClhsdbDumpclass.java @@ -88,7 +88,7 @@ public static void main(String[] args) throws Exception { System.out.println(out.getStdout()); System.err.println(out.getStderr()); out.shouldHaveExitValue(0); - out.shouldMatch("public class " + APP_DOT_CLASSNAME); + out.shouldMatch("public identity class " + APP_DOT_CLASSNAME); // StackMapTable might not be generated for a class // containing only methods with sequential control flows. // But the class used here (LingeredApp) is not such a case. diff --git a/test/hotspot/jtreg/serviceability/tmtools/jstack/WaitNotifyThreadTest.java b/test/hotspot/jtreg/serviceability/tmtools/jstack/WaitNotifyThreadTest.java index 67ff821ee07..32ecca97820 100644 --- a/test/hotspot/jtreg/serviceability/tmtools/jstack/WaitNotifyThreadTest.java +++ b/test/hotspot/jtreg/serviceability/tmtools/jstack/WaitNotifyThreadTest.java @@ -38,7 +38,7 @@ public class WaitNotifyThreadTest { - private Object monitor = new Object(); + private Object monitor = new Object();; private final String OBJECT = "a java.lang.Object"; private final String OBJECT_WAIT = "java.lang.Object.wait0"; private final String RUN_METHOD = "WaitNotifyThreadTest$WaitThread.run"; diff --git a/test/hotspot/jtreg/testlibrary/asm/org/objectweb/asm/Opcodes.java b/test/hotspot/jtreg/testlibrary/asm/org/objectweb/asm/Opcodes.java index da68deacf90..97b128aa7dd 100644 --- a/test/hotspot/jtreg/testlibrary/asm/org/objectweb/asm/Opcodes.java +++ b/test/hotspot/jtreg/testlibrary/asm/org/objectweb/asm/Opcodes.java @@ -337,6 +337,7 @@ public interface Opcodes { int ACC_STATIC = 0x0008; // field, method int ACC_FINAL = 0x0010; // class, field, method, parameter int ACC_SUPER = 0x0020; // class + int ACC_IDENTITY = 0x0020; // class int ACC_SYNCHRONIZED = 0x0020; // method int ACC_OPEN = 0x0020; // module int ACC_TRANSITIVE = 0x0020; // module requires diff --git a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestIRMatching.java b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestIRMatching.java index c7f8badf83b..a1d1253776c 100644 --- a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestIRMatching.java +++ b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestIRMatching.java @@ -774,8 +774,17 @@ public void good5() { } @Test - @IR(counts = {IRNode.STORE_OF_FIELD, "myClassEmpty", "1", IRNode.STORE_OF_CLASS, "GoodCount", "1", - IRNode.STORE_OF_CLASS, "/GoodCount", "1", IRNode.STORE_OF_CLASS, "MyClassEmpty", "0"}, + @IR(counts = { + IRNode.STORE_OF_FIELD, "myClassEmpty", "1", + IRNode.STORE_OF_CLASS, "oodCount", "0", + IRNode.STORE_OF_CLASS, "GoodCount", "1", + IRNode.STORE_OF_CLASS, "/GoodCount", "1", + IRNode.STORE_OF_CLASS, "tests/GoodCount", "1", + IRNode.STORE_OF_CLASS, "/tests/GoodCount", "1", + IRNode.STORE_OF_CLASS, "ir_framework/tests/GoodCount", "1", + IRNode.STORE_OF_CLASS, "/ir_framework/tests/GoodCount", "0", + IRNode.STORE_OF_CLASS, "MyClassEmpty", "0" + }, failOn = {IRNode.STORE_OF_CLASS, "MyClassEmpty"}) public void good6() { myClassEmpty = new MyClassEmpty(); diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jdi/Accessible/modifiers/modifiers001.java b/test/hotspot/jtreg/vmTestbase/nsk/jdi/Accessible/modifiers/modifiers001.java index 8fe0a939871..a0e69c1f041 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jdi/Accessible/modifiers/modifiers001.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jdi/Accessible/modifiers/modifiers001.java @@ -188,9 +188,6 @@ private int runThis (String argv[], PrintStream out) { String s_type = classes_for_check[i][2]; String s_modifiers = classes_for_check[i][1]; int got_modifiers = refType.modifiers(); - // Class.getModifiers() will never return ACC_SUPER - // but Accessible.modifers() can, so ignore this bit - got_modifiers &= ~0x20; // 0x20 == ACC_SUPER logHandler.display(""); if ( got_modifiers != expected_modifiers ) { logHandler.complain("##> modifiers001: UNEXPECTED modifiers() method result (" diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jdwp/ReferenceType/Interfaces/interfaces001.java b/test/hotspot/jtreg/vmTestbase/nsk/jdwp/ReferenceType/Interfaces/interfaces001.java index d30d63b272b..a5d1d43613f 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jdwp/ReferenceType/Interfaces/interfaces001.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jdwp/ReferenceType/Interfaces/interfaces001.java @@ -142,7 +142,7 @@ public int runIt(String argv[], PrintStream out) { if (interfaceID != interfaceIDs[i]) { log.complain("Unexpected interface ID for interface #" + i + " in the reply packet: " + interfaceID - + " (expected: " + interfaceIDs[i] + ")"); + + " (expected: " + interfaceIDs[i] + ")"); success = false; } diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/GetClassModifiers/getclmdf006.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/GetClassModifiers/getclmdf006.java index 3019f6c3be6..945ed6dd0ef 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/GetClassModifiers/getclmdf006.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/GetClassModifiers/getclmdf006.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/GetObjectMonitorUsage/objmonusage007.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/GetObjectMonitorUsage/objmonusage007.java new file mode 100644 index 00000000000..c87542ddf70 --- /dev/null +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/GetObjectMonitorUsage/objmonusage007.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +/* + * @test + * + * @summary + * VM Testbase keywords: [quick, jpda, jvmti, noras] + * VM Testbase readme: + * DESCRIPTION + * The test exercises JVMTI function GetObjectMonitorUsage. + * COMMENTS + * This test checks that GetObjectMonitorUsage works with inline types and always + * returns information consistent with a never locked monitor + * + * @enablePreview + * @library /vmTestbase + * /test/lib + * @run driver jdk.test.lib.FileInstaller . . + * @run main/othervm/native -agentlib:objmonusage007 nsk.jvmti.GetObjectMonitorUsage.objmonusage007 + */ +package nsk.jvmti.GetObjectMonitorUsage; + +import java.io.PrintStream; + +public class objmonusage007 { + final static int JCK_STATUS_BASE = 95; + final static int NUMBER_OF_THREADS = 32; + + static { + try { + System.loadLibrary("objmonusage007"); + } catch (UnsatisfiedLinkError err) { + System.err.println("Could not load objmonusage7 library"); + System.err.println("java.library.path:" + + System.getProperty("java.library.path")); + throw err; + } + } + + native static int getResult(); + native static void check(Object o, Thread owner, int ec, int wc); + + static value class LocalValue { + int i = 0; + } + + public static void main(String argv[]) { + argv = nsk.share.jvmti.JVMTITest.commonInit(argv); + + System.exit(run(argv, System.out) + JCK_STATUS_BASE); + } + + public static int run(String argv[], PrintStream out) { + LocalValue lv = new LocalValue(); + objmonusage007.check(lv, null, 0, 0); + + return getResult(); + } +} + diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/GetObjectMonitorUsage/objmonusage007/libobjmonusage007.cpp b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/GetObjectMonitorUsage/objmonusage007/libobjmonusage007.cpp new file mode 100644 index 00000000000..d8a4bea897b --- /dev/null +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/GetObjectMonitorUsage/objmonusage007/libobjmonusage007.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "native_thread.cpp" +#include "nsk_tools.cpp" +#include "jni_tools.cpp" +#include "jvmti_tools.cpp" +#include "agent_tools.cpp" +#include "jvmti_FollowRefObjects.cpp" +#include "Injector.cpp" +#include "JVMTITools.cpp" +#include "agent_common.cpp" +#include "objmonusage007.cpp" diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/GetObjectMonitorUsage/objmonusage007/objmonusage007.cpp b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/GetObjectMonitorUsage/objmonusage007/objmonusage007.cpp new file mode 100644 index 00000000000..5dcce16ba0c --- /dev/null +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/GetObjectMonitorUsage/objmonusage007/objmonusage007.cpp @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include +#include "jvmti.h" +#include "agent_common.hpp" +#include "JVMTITools.hpp" + +extern "C" { + + +#define PASSED 0 +#define STATUS_FAILED 2 + +static jvmtiEnv *jvmti = nullptr; +static jvmtiCapabilities caps; +static jint result = PASSED; +static jboolean printdump = JNI_FALSE; + +#ifdef STATIC_BUILD +JNIEXPORT jint JNICALL Agent_OnLoad_objmonusage007(JavaVM *jvm, char *options, void *reserved) { + return Agent_Initialize(jvm, options, reserved); +} +JNIEXPORT jint JNICALL Agent_OnAttach_objmonusage007(JavaVM *jvm, char *options, void *reserved) { + return Agent_Initialize(jvm, options, reserved); +} +JNIEXPORT jint JNI_OnLoad_objmonusage007(JavaVM *jvm, char *options, void *reserved) { + return JNI_VERSION_1_8; +} +#endif +jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved) { + jint res; + jvmtiError err; + + if (options != nullptr && strcmp(options, "printdump") == 0) { + printdump = JNI_TRUE; + } + + res = jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_1); + if (res != JNI_OK || jvmti == nullptr) { + printf("Wrong result of a valid call to GetEnv !\n"); + return JNI_ERR; + } + + err = jvmti->GetPotentialCapabilities(&caps); + if (err != JVMTI_ERROR_NONE) { + printf("(GetPotentialCapabilities) unexpected error: %s (%d)\n", + TranslateError(err), err); + return JNI_ERR; + } + + err = jvmti->AddCapabilities(&caps); + if (err != JVMTI_ERROR_NONE) { + printf("(AddCapabilities) unexpected error: %s (%d)\n", + TranslateError(err), err); + return JNI_ERR; + } + + err = jvmti->GetCapabilities(&caps); + if (err != JVMTI_ERROR_NONE) { + printf("(GetCapabilities) unexpected error: %s (%d)\n", + TranslateError(err), err); + return JNI_ERR; + } + + if (!caps.can_get_monitor_info) { + printf("Warning: GetObjectMonitorUsage is not implemented\n"); + } + + return JNI_OK; +} + +JNIEXPORT void JNICALL +Java_nsk_jvmti_GetObjectMonitorUsage_objmonusage007_check(JNIEnv *env, + jclass cls, jobject obj, jthread owner, + jint entryCount, jint waiterCount) { + jvmtiError err; + jvmtiMonitorUsage inf; + jvmtiThreadInfo tinf; + int j; + + if (result == STATUS_FAILED) { + return; + } + + err = jvmti->GetObjectMonitorUsage(obj, &inf); + if (err == JVMTI_ERROR_MUST_POSSESS_CAPABILITY && + !caps.can_get_monitor_info) { + /* Ok, it's expected */ + return; + } else if (err != JVMTI_ERROR_NONE) { + printf("(GetMonitorInfo) unexpected error: %s (%d)\n", + TranslateError(err), err); + result = STATUS_FAILED; + return; + } + + if (printdump == JNI_TRUE) { + if (inf.owner == nullptr) { + printf(">>> owner: none (0x0)\n"); + } else { + jvmti->GetThreadInfo(inf.owner, &tinf); + printf(">>> owner: %s (0x%p)\n", + tinf.name, inf.owner); + } + printf(">>> entry_count: %d\n", inf.entry_count); + printf(">>> waiter_count: %d\n", inf.waiter_count); + if (inf.waiter_count > 0) { + printf(">>> waiters:\n"); + for (j = 0; j < inf.waiter_count; j++) { + jvmti->GetThreadInfo(inf.waiters[j], &tinf); + printf(">>> %2d: %s (0x%p)\n", + j, tinf.name, inf.waiters[j]); + } + } + printf(">>> notify_waiter_count: %d\n", inf.notify_waiter_count); + if (inf.notify_waiter_count > 0) { + printf(">>> notify_waiters:\n"); + for (j = 0; j < inf.notify_waiter_count; j++) { + jvmti->GetThreadInfo(inf.notify_waiters[j], &tinf); + printf(">>> %2d: %s (0x%p)\n", + j, tinf.name, inf.notify_waiters[j]); + } + } + } + + if (!env->IsSameObject(owner, inf.owner)) { + jvmti->GetThreadInfo(inf.owner, &tinf); + printf(" unexpected owner: %s (0x%p)\n", tinf.name, inf.owner); + result = STATUS_FAILED; + } + + if (inf.entry_count != entryCount) { + printf(" entry_count expected: %d, actually: %d\n", + entryCount, inf.entry_count); + result = STATUS_FAILED; + } + + if (inf.waiter_count != waiterCount) { + printf(" waiter_count expected: %d, actually: %d\n", + waiterCount, inf.waiter_count); + result = STATUS_FAILED; + } +} + +JNIEXPORT jint JNICALL +Java_nsk_jvmti_GetObjectMonitorUsage_objmonusage007_getResult(JNIEnv *env, jclass cls) { + return result; +} + +} diff --git a/test/jaxp/TEST.ROOT b/test/jaxp/TEST.ROOT index 6398c399f0a..7a457d87b69 100644 --- a/test/jaxp/TEST.ROOT +++ b/test/jaxp/TEST.ROOT @@ -23,7 +23,7 @@ modules=java.xml groups=TEST.groups # Minimum jtreg version -requiredVersion=7.5.2+1 +requiredVersion=7.5.1+1 # Path to libraries in the topmost test directory. This is needed so @library # does not need ../../ notation to reach them diff --git a/test/jdk/ProblemList-Virtual.txt b/test/jdk/ProblemList-Virtual.txt index d7692b578cd..8cdd5983497 100644 --- a/test/jdk/ProblemList-Virtual.txt +++ b/test/jdk/ProblemList-Virtual.txt @@ -69,3 +69,7 @@ java/util/Properties/StoreReproducibilityTest.java 0000000 generic-all java/util/Properties/StoreReproducibilityTest.java 0000000 generic-all javax/management/ImplementationVersion/ImplVersionTest.java 0000000 generic-all javax/management/remote/mandatory/version/ImplVersionTest.java 0000000 generic-all + +# Valhalla +java/lang/Thread/virtual/stress/PingPong.java 8314996 macosx-all +java/lang/Thread/virtual/stress/Skynet.java 8342977 generic-all diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index 58fb2c51397..5423321fd87 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -535,6 +535,11 @@ java/lang/invoke/LFCaching/LFGarbageCollectedTest.java 8078602 generic- java/lang/invoke/lambda/LambdaFileEncodingSerialization.java 8249079 linux-all java/lang/invoke/RicochetTest.java 8251969 generic-all +java/lang/ModuleLayer/LayerControllerTest.java 8337048 generic-all +java/lang/ModuleLayer/BasicLayerTest.java 8337048 generic-all + +java/lang/Thread/virtual/stress/Skynet.java#default 8342977 generic-all + ############################################################################ # jdk_instrument @@ -552,6 +557,7 @@ java/io/IO/IO.java 8337935 linux-pp ############################################################################ # jdk_management +com/sun/management/HotSpotDiagnosticMXBean/DumpThreadsWithEliminatedLock.java 8360599 generic-all com/sun/management/OperatingSystemMXBean/GetProcessCpuLoad.java 8030957 aix-all com/sun/management/OperatingSystemMXBean/GetSystemCpuLoad.java 8030957 aix-all @@ -577,6 +583,8 @@ javax/management/remote/mandatory/connection/BrokenConnectionTest.java 8262312 l # jdk_net +java/net/CookieHandler/B6644726.java 8365811 generic-all + java/net/DatagramSocket/DatagramSocketExample.java 8308807 aix-ppc64 java/net/DatagramSocket/DatagramSocketMulticasting.java 8308807 aix-ppc64 @@ -730,6 +738,10 @@ com/sun/jdi/RepStep.java 8043571 generic- com/sun/jdi/InvokeHangTest.java 8218463 linux-all +com/sun/jdi/cds/CDSBreakpointTest.java 8304168 generic-all +com/sun/jdi/cds/CDSDeleteAllBkptsTest.java 8304168 generic-all +com/sun/jdi/cds/CDSFieldWatchpoints.java 8304168 generic-all + ############################################################################ # jdk_time @@ -738,6 +750,7 @@ com/sun/jdi/InvokeHangTest.java 8218463 linux-al # jdk_util java/util/zip/CloseInflaterDeflaterTest.java 8339216 linux-s390x +java/util/logging/LoggingDeadlock2.java 8368801 generic-all ############################################################################ @@ -828,8 +841,23 @@ java/awt/Menu/MenuVisibilityTest.java 8161110 macosx-all java/awt/Modal/NativeDialogToFrontBackTest.java 7188049 windows-all,linux-all java/awt/Cursor/CursorDragTest/ListDragCursor.java 7177297 macosx-all + ############################################################################ # jdk_since_checks tools/sincechecker/modules/jdk.management.jfr/JdkManagementJfrCheckSince.java 8354921 generic-all + +############################################################################ + +# valhalla +java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessValue.java 8367346 generic-all + +jdk/classfile/AccessFlagsTest.java 8366270 generic-all +jdk/internal/misc/Unsafe/AddressComputationContractTest.java 8368933 generic-all +jdk/jfr/event/runtime/TestClassLoaderStatsEvent.java 8366820 generic-all + +sun/tools/jhsdb/BasicLauncherTest.java 8366806 generic-all +sun/tools/jhsdb/HeapDumpTest.java 8366806 generic-all +sun/tools/jhsdb/HeapDumpTestWithActiveProcess.java 8366806 generic-all +sun/tools/jhsdb/JShellHeapDumpTest.java 8366806 generic-all diff --git a/test/jdk/TEST.ROOT b/test/jdk/TEST.ROOT index 0fa78bebc3f..5805c77a526 100644 --- a/test/jdk/TEST.ROOT +++ b/test/jdk/TEST.ROOT @@ -121,7 +121,7 @@ requires.properties= \ jdk.static # Minimum jtreg version -requiredVersion=7.5.2+1 +requiredVersion=7.5.1+1 # Path to libraries in the topmost test directory. This is needed so @library # does not need ../../ notation to reach them diff --git a/test/jdk/TEST.groups b/test/jdk/TEST.groups index 8bb270561dc..4193e680a26 100644 --- a/test/jdk/TEST.groups +++ b/test/jdk/TEST.groups @@ -138,7 +138,15 @@ jdk_lang = \ jdk/internal/math \ jdk/internal/vm \ jdk/modules \ - jni/nullCaller + jni/nullCaller \ + valhalla + +# valhalla lworld tests +jdk_valhalla = \ + java/lang/invoke \ + valhalla \ + java/lang/instrument/valhalla + # All of the java.util package jdk_util = \ diff --git a/test/jdk/com/sun/jdi/TestScaffold.java b/test/jdk/com/sun/jdi/TestScaffold.java index 92779738998..bb5dd410d20 100644 --- a/test/jdk/com/sun/jdi/TestScaffold.java +++ b/test/jdk/com/sun/jdi/TestScaffold.java @@ -558,9 +558,10 @@ private ArgInfo parseArgs(String args[]) { // and set property 'test.thread.factory' so test could use DebuggeeWrapper.isVirtual() method String testThreadFactoryName = DebuggeeWrapper.getTestThreadFactoryName(); if (testThreadFactoryName != null && !argInfo.targetAppCommandLine.isEmpty()) { - argInfo.targetVMArgs += "-D" + DebuggeeWrapper.PROPERTY_NAME + "=" + testThreadFactoryName; + argInfo.targetVMArgs += "-D" + DebuggeeWrapper.PROPERTY_NAME + "=" + testThreadFactoryName + " "; argInfo.targetAppCommandLine = DebuggeeWrapper.class.getName() + ' ' + argInfo.targetAppCommandLine; - } else if ("true".equals(System.getProperty("test.enable.preview"))) { + } + if ("true".equals(System.getProperty("test.enable.preview"))) { // the test specified @enablePreview. argInfo.targetVMArgs += "--enable-preview "; } diff --git a/test/jdk/com/sun/jdi/valhalla/FieldWatchpointsTest.java b/test/jdk/com/sun/jdi/valhalla/FieldWatchpointsTest.java new file mode 100644 index 00000000000..2cacbda7b63 --- /dev/null +++ b/test/jdk/com/sun/jdi/valhalla/FieldWatchpointsTest.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @summary Sanity test for AccessWatchpoint/ModificationWatchpoint and InstanceFilter with value objects + * + * @library .. + * @enablePreview + * @run main/othervm FieldWatchpointsTest + * -XX:+UseArrayFlattening -XX:+UseFieldFlattening -XX:+UseAtomicValueFlattening -XX:+UseNullableValueFlattening + * -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlineLayout -XX:+PrintFlatArrayLayout + */ +import com.sun.jdi.Field; +import com.sun.jdi.ClassType; +import com.sun.jdi.Method; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.Value; +import com.sun.jdi.event.AccessWatchpointEvent; +import com.sun.jdi.event.BreakpointEvent; +import com.sun.jdi.event.ModificationWatchpointEvent; +import com.sun.jdi.event.WatchpointEvent; +import com.sun.jdi.request.WatchpointRequest; +import java.util.ArrayList; +import java.util.List; + +class FieldWatchpointsTarg { + static value class Value { + int v; + Value() { + this(0); + } + Value(int v) { + this.v = v; + } + public int getValue() { + return v; + } + } + + static Value staticField = new Value(1); + + public static void main(String[] args) { + System.out.println(">>Targ.main"); + Value obj = new Value(2); // modify + System.out.println("obj value = " + obj.v); // access + System.out.println("staticField value = " + staticField.v); // access + System.out.println("< testCases = new ArrayList<>(); + try { + BreakpointEvent bpe = startToMain("FieldWatchpointsTarg"); + ThreadReference mainThread = bpe.thread(); + ClassType testClass = (ClassType)bpe.location().declaringType(); + Field staticValueField = testClass.fieldByName("staticField"); + ClassType valueClass = (ClassType)staticValueField.type(); + ObjectReference staticFieldValue = (ObjectReference)testClass.getValue(staticValueField); + + Field watchField = valueClass.fieldByName("v"); + + WatchpointRequest request = eventRequestManager().createModificationWatchpointRequest(watchField); + testCases.add(new TestCase("modify", 1, request)); // obj ctor + + request = eventRequestManager().createAccessWatchpointRequest(watchField); + testCases.add(new TestCase("access", 2, request)); // staticField, obj + + request = eventRequestManager().createAccessWatchpointRequest(watchField); + request.addInstanceFilter(staticFieldValue); + testCases.add(new TestCase("access+instanceFilter", 1, request)); // only staticField + } finally { + listenUntilVMDisconnect(); + } + + for (TestCase test: testCases) { + String msg = "Testcase: " + test.name + + ", count = " + test.count + + ", expectedCount = " + test.expectedCount; + System.out.println(msg); + if (test.count != test.expectedCount) { + throw new RuntimeException("FAILED " + msg); + } + } + } + + class TestCase { + String name; + int count; + int expectedCount; + TestCase(String name, int expectedCount, WatchpointRequest request) { + this.name = name; + this.expectedCount = expectedCount; + request.putProperty("testcase", this); + request.enable(); + } + + static void watchpoint(WatchpointEvent event) { + TestCase test = (TestCase)event.request().getProperty("testcase"); + test.count++; + } + } +} diff --git a/test/jdk/com/sun/jdi/valhalla/ValueArrayReferenceTest.java b/test/jdk/com/sun/jdi/valhalla/ValueArrayReferenceTest.java new file mode 100644 index 00000000000..d497e9730dc --- /dev/null +++ b/test/jdk/com/sun/jdi/valhalla/ValueArrayReferenceTest.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @summary Sanity test for ArrayReference (getValue/setValue) with flat arrays + * + * @modules java.base/jdk.internal.value + * @library .. + * @enablePreview + * @run main/othervm ValueArrayReferenceTest + * --add-modules java.base --add-exports java.base/jdk.internal.value=ALL-UNNAMED + * -XX:+UseArrayFlattening -XX:+UseFieldFlattening -XX:+UseAtomicValueFlattening -XX:+UseNullableValueFlattening + * -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlineLayout -XX:+PrintFlatArrayLayout + */ +import com.sun.jdi.ArrayReference; +import com.sun.jdi.Field; +import com.sun.jdi.ReferenceType; +import com.sun.jdi.Value; +import com.sun.jdi.event.BreakpointEvent; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import jdk.internal.value.ValueClass; + +class ValueArrayReferenceTarg { + static value class Value { + int v; + Value() { + this(0); + } + Value(int v) { + this.v = v; + } + public int getValue() { + return v; + } + } + + static int ARRAY_SIZE = 5; + static void initArray(Value[] arr) { + for (int i = 0; i < arr.length; i++) { + arr[i] = new Value(i); + } + } + + static Value[] testRegularArray; + static Value[] testNullableAtomicArray; + static Value[] testNonNullNonAtomicArray; + static Value[] testNonNullAtomicArray; + + static Value otherValue = new Value(25); + + static { + try { + testRegularArray = new Value[ARRAY_SIZE]; + initArray(testRegularArray); + + testNullableAtomicArray = (Value[])ValueClass.newNullableAtomicArray(Value.class, ARRAY_SIZE); + initArray(testNullableAtomicArray); + + testNonNullNonAtomicArray = (Value[])ValueClass.newNullRestrictedNonAtomicArray(Value.class, ARRAY_SIZE, Value.class.newInstance()); + initArray(testNonNullNonAtomicArray); + + testNonNullAtomicArray = (Value[])ValueClass.newNullRestrictedAtomicArray(Value.class, ARRAY_SIZE, Value.class.newInstance()); + initArray(testNonNullAtomicArray); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + public static void main(String[] args) { + System.out.println("Hello and goodbye from main"); + } +} + +public class ValueArrayReferenceTest extends TestScaffold { + + ValueArrayReferenceTest (String args[]) { + super(args); + } + + public static void main(String[] args) throws Exception { + new ValueArrayReferenceTest(args).startTests(); + } + + String arrayToString(ArrayReference array) { + List values = array.getValues(); + // Mirror.toString reports object type and reference id, + // it should be enough to see if objects are different. + return values.stream() + .map(String::valueOf) + .collect(Collectors.joining(", ", "[", "]")); + } + + Value getFieldValue(ReferenceType cls, String fieldName) { + System.out.println("Getting value from " + fieldName); + Value value = cls.getValue(cls.fieldByName(fieldName)); + System.out.println(" - " + value); + return value; + } + + ArrayReference getArrayFromField(ReferenceType cls, Field field) throws Exception { + System.out.println("Getting array from " + field.name()); + ArrayReference array = (ArrayReference)cls.getValue(field); + System.out.println(" - " + array); + System.out.println(" " + arrayToString(array)); + return array; + } + + boolean arraysEquals(ArrayReference arr1, ArrayReference arr2) throws Exception { + // Compare string representation of the array (contains object type and id for each element). + String s1 = arrayToString(arr1); + String s2 = arrayToString(arr2); + return s1.equals(s2); + } + + void fillArrayWithOtherValue(ArrayReference arr, Value value) throws Exception { + for (int i = 0; i < arr.length(); i++) { + arr.setValue(i, value); + } + } + + void verifyArraysEqual(List arrays) throws Exception { + for (ArrayReference arr1: arrays) { + for (ArrayReference arr2: arrays) { + if (!arraysEquals(arr1, arr2)) { + System.out.println("Arrays are different (1):" + + "\n - " + arrayToString(arr1) + + "\n - " + arrayToString(arr2)); + throw new RuntimeException("Arrays are different"); + } + } + } + } + + protected void runTests() throws Exception { + try { + BreakpointEvent bpe = startToMain("ValueArrayReferenceTarg"); + ReferenceType cls = bpe.location().declaringType(); + + // Get all arrays. + List arrays = new ArrayList<>(); + List fields = cls.allFields(); + for (Field field: fields) { + if (field.name().startsWith("test")) { + arrays.add(getArrayFromField(cls, field)); + } + } + + // Ensure elements in all arrays are equal. + verifyArraysEqual(arrays); + + // Update arrays. + Value otherValue = getFieldValue(cls, "otherValue"); + for (ArrayReference arr: arrays) { + fillArrayWithOtherValue(arr, otherValue); + System.out.println("Array after update:"); + System.out.println(" " + arrayToString(arr)); + } + + // Ensure elements in all arrays are equal. + verifyArraysEqual(arrays); + } finally { + // Resume the target until end. + listenUntilVMDisconnect(); + } + } +} diff --git a/test/jdk/com/sun/jdi/valhalla/ValueClassTypeTest.java b/test/jdk/com/sun/jdi/valhalla/ValueClassTypeTest.java new file mode 100644 index 00000000000..9a8ccbc8d4a --- /dev/null +++ b/test/jdk/com/sun/jdi/valhalla/ValueClassTypeTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @summary Sanity test for ClassType (getValue, setValue, newInstance) with value objects + * + * @library .. + * @enablePreview + * @run main/othervm ValueClassTypeTest + * -XX:+UseArrayFlattening -XX:+UseFieldFlattening -XX:+UseAtomicValueFlattening -XX:+UseNullableValueFlattening + * -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlineLayout -XX:+PrintFlatArrayLayout + */ +import com.sun.jdi.Field; +import com.sun.jdi.ClassType; +import com.sun.jdi.Method; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.ThreadReference; +import com.sun.jdi.Value; +import com.sun.jdi.event.BreakpointEvent; +import java.util.List; + +class ValueClassTypeTarg { + static value class Value { + static Value staticField = new Value(1); + + int v; + Value() { + this(0); + } + Value(int v) { + this.v = v; + } + public int getValue() { + return v; + } + } + + static Value staticField = new Value(2); + + public static void main(String[] args) { + System.out.println("Hello and goodbye from main"); + } +} + +public class ValueClassTypeTest extends TestScaffold { + + ValueClassTypeTest (String args[]) { + super(args); + } + + public static void main(String[] args) throws Exception { + new ValueClassTypeTest(args).startTests(); + } + + boolean equals(ObjectReference obj1, ObjectReference obj2) throws Exception { + return obj1.equals(obj2); + } + + void assertEqual(ObjectReference obj1, ObjectReference obj2) throws Exception { + if (!equals(obj1, obj2)) { + throw new RuntimeException("Values are not equal: " + obj1 + " and " + obj2); + } + } + + void assertNotEqual(ObjectReference obj1, ObjectReference obj2) throws Exception { + if (equals(obj1, obj2)) { + throw new RuntimeException("Values are equal: " + obj1 + " and " + obj2); + } + } + + protected void runTests() throws Exception { + try { + BreakpointEvent bpe = startToMain("ValueClassTypeTarg"); + ThreadReference mainThread = bpe.thread(); + ClassType testClass = (ClassType)bpe.location().declaringType(); + Field testField = testClass.fieldByName("staticField"); + ObjectReference value1 = (ObjectReference)testClass.getValue(testField); + + ClassType valueClass = (ClassType)value1.type(); + Field valueField = valueClass.fieldByName("staticField"); + ObjectReference value2 = (ObjectReference)valueClass.getValue(valueField); + + Method valueCtor = valueClass.concreteMethodByName("", "(I)V"); + + ObjectReference newValue1 = valueClass.newInstance(mainThread, valueCtor, List.of(vm().mirrorOf(10)), 0); + ObjectReference newValue2 = valueClass.newInstance(mainThread, valueCtor, List.of(vm().mirrorOf(11)), 0); + // sanity check for enableCollection/disableCollection + newValue1.disableCollection(); + newValue2.disableCollection(); + + testClass.setValue(testField, newValue1); + valueClass.setValue(valueField, newValue2); + + ObjectReference updatedValue1 = (ObjectReference)testClass.getValue(testField); + ObjectReference updatedValue2 = (ObjectReference)valueClass.getValue(valueField); + + assertEqual(updatedValue1, newValue1); + assertNotEqual(value1, newValue1); + assertEqual(updatedValue2, newValue2); + assertNotEqual(value2, newValue2); + + // sanity check for enableCollection/disableCollection + newValue1.enableCollection(); + newValue2.enableCollection(); + } finally { + // Resume the target until end + listenUntilVMDisconnect(); + } + } +} diff --git a/test/jdk/java/io/Serializable/records/BasicRecordSer.java b/test/jdk/java/io/Serializable/records/BasicRecordSer.java index 94a79e85b38..dc6a0496094 100644 --- a/test/jdk/java/io/Serializable/records/BasicRecordSer.java +++ b/test/jdk/java/io/Serializable/records/BasicRecordSer.java @@ -23,9 +23,10 @@ /* * @test - * @bug 8246774 + * @bug 8246774 8326879 * @summary Basic test that serializes and deserializes a number of records * @run testng BasicRecordSer + * @run testng/othervm --enable-preview BasicRecordSer */ import java.io.ByteArrayInputStream; diff --git a/test/jdk/java/io/Serializable/records/RecordClassTest.java b/test/jdk/java/io/Serializable/records/RecordClassTest.java index 401512056e6..e8cd1c0494f 100644 --- a/test/jdk/java/io/Serializable/records/RecordClassTest.java +++ b/test/jdk/java/io/Serializable/records/RecordClassTest.java @@ -23,9 +23,10 @@ /* * @test - * @bug 8246774 + * @bug 8246774 8326879 * @summary Basic tests for serializing and deserializing record classes * @run testng RecordClassTest + * @run testng/othervm --enable-preview RecordClassTest */ import java.io.ByteArrayInputStream; diff --git a/test/jdk/java/io/Serializable/valueObjects/SerializeAllValueClasses.java b/test/jdk/java/io/Serializable/valueObjects/SerializeAllValueClasses.java new file mode 100644 index 00000000000..7a7b3d826e1 --- /dev/null +++ b/test/jdk/java/io/Serializable/valueObjects/SerializeAllValueClasses.java @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @modules java.base/jdk.internal java.base/jdk.internal.misc + * @run junit/othervm --enable-preview SerializeAllValueClasses + * @run junit/othervm SerializeAllValueClasses + */ + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.time.ZonedDateTime; +import java.time.chrono.HijrahDate; +import java.time.chrono.JapaneseDate; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalUnit; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import jdk.internal.misc.PreviewFeatures; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Scans all classes in the JDK for those recognized as value classes + * or with the annotation jdk.internal.misc.ValueBasedClass. + * + * Scanning is done over the jrt: filesystem. Classes are matched using the + * following criteria: + * + * - serializable + * - is a public or protected class + * - has public or protected constructor + * + * This returns a list of class', which is convenient for the caller. + */ + +public class SerializeAllValueClasses { + // Cache of instances of known classes suitable as arguments to constructors + // or factory methods. + private static final Map, Object> argumentForType = initInstances(); + + private static Map, Object> initInstances() { + Map, Object> map = new HashMap<>(); + map.put(Integer.class, 12); map.put(int.class, 12); + map.put(Short.class, (short)3); map.put(short.class, (short)3); + map.put(Byte.class, (byte)4); map.put(byte.class, (byte)4); + map.put(Long.class, 5L); map.put(long.class, 5L); + map.put(Character.class, 'C'); map.put(char.class, 'C'); + map.put(Float.class, 1.0f); map.put(float.class, 1.0f); + map.put(Double.class, 2.0d); map.put(double.class, 2.0d); + map.put(Duration.class, Duration.ofHours(1)); + map.put(TemporalUnit.class, ChronoUnit.SECONDS); + map.put(LocalTime.class, LocalTime.of(12, 1)); + map.put(LocalDate.class, LocalDate.of(2024, 1, 1)); + map.put(LocalDateTime.class, LocalDateTime.of(2024, 2, 1, 12, 2)); + map.put(TemporalAccessor.class, ZonedDateTime.now()); + map.put(ZonedDateTime.class, ZonedDateTime.now()); + map.put(Clock.class, Clock.systemUTC()); + map.put(Month.class, Month.JANUARY); + map.put(Instant.class, Instant.now()); + map.put(JapaneseDate.class, JapaneseDate.now()); + map.put(HijrahDate.class, HijrahDate.now()); + return map; + } + + + // Stream the value classes to the test + private static Stream classProvider() throws IOException, URISyntaxException { + return findAll().stream().map(c -> Arguments.of(c)); + } + + @Test + void info() { + var info = (PreviewFeatures.isEnabled()) ? " Checking preview classes declared as `value class`" : + " Checking identity classes with annotation `jdk.internal.ValueBased.class`"; + System.err.println(info); + } + + @ParameterizedTest + @MethodSource("classProvider") + void testValueClass(Class clazz) { + boolean atLeastOne = false; + + Object expected = argumentForType.get(clazz); + if (expected != null) { + serializeDeserialize(expected); + atLeastOne = true; + } + var cons = clazz.getConstructors(); + for (Constructor c : cons) { + Object[] args = makeArgs(c.getParameterTypes(), clazz); + if (args != null) { + try { + expected = c.newInstance(args); + serializeDeserialize(expected); + atLeastOne = true; + break; // one is enough + } catch (InvocationTargetException | InstantiationException | + IllegalAccessException e) { + // Ignore + System.err.printf(""" + Ignoring constructor: %s + Generated arguments are invalid: %s + %s + """, + c, Arrays.toString(args), e.getCause()); + } + } + } + + // Scan for suitable factory methods + for (Method m : clazz.getMethods()) { + if (Modifier.isStatic(m.getModifiers()) && + m.getReturnType().equals(clazz)) { + // static method returning itself + Object[] args = makeArgs(m.getParameterTypes(), clazz); + if (args != null) { + try { + expected = m.invoke(null, args); + serializeDeserialize(expected); + atLeastOne = true; + break; // one is enough + } catch (IllegalAccessException | InvocationTargetException e) { + // Ignore + System.err.printf(""" + Ignoring factory: %s + Generated arguments are invalid: %s + %s + """, + m, Arrays.toString(args), e.getCause()); + } + } + } + } + assertTrue(atLeastOne, "No constructor or factory found for " + clazz); + } + + /** + * {@return an array of instances matching the parameter types, or null} + * + * @param paramTypes an array of parameter types + * @param forClazz the owner class for which the parameters are being generated + */ + private Object[] makeArgs(Class[] paramTypes, Class forClazz) { + Object[] args = Arrays.stream(paramTypes) + .map(t -> makeArg(t, forClazz)) + .toArray(); + for (Object arg : args) { + if (arg == null) + return null; + } + return args; + } + + /** + * {@return an instance of the class, or null if not available} + * String values are customized by the requesting owner. + * For example, "true" is returned as a value when requested for "Boolean". + * @param paramType the parameter type + * @param forClass the owner class + */ + private static Object makeArg(Class paramType, Class forClass) { + return (paramType == String.class || paramType == CharSequence.class) + ? makeStringArg(forClass) + : argumentForType.get(paramType); + } + + /** + * {@return a string representation of an instance of class, or null} + * Mostly special cased for core value classes. + * @param forClass a Class + */ + private static String makeStringArg(Class forClass) { + if (forClass == Integer.class || forClass == int.class || + forClass == Byte.class || forClass == byte.class || + forClass == Short.class || forClass == short.class || + forClass == Long.class || forClass == long.class) { + return "0"; + } else if (forClass == Boolean.class || forClass == boolean.class) { + return "true"; + } else if (forClass == Float.class || forClass == float.class || + forClass == Double.class || forClass == double.class) { + return "1.0"; + } else if (forClass == Duration.class) { + return "PT4H"; + } else if (forClass == LocalDate.class) { + return LocalDate.of(2024, 1, 1).toString(); + } else if (forClass == LocalDateTime.class) { + return LocalDateTime.of(2024, 1, 1, 12, 1).toString(); + } else if (forClass == LocalTime.class) { + return LocalTime.of(12, 1).toString(); + } else if (forClass == Instant.class) { + return Instant.ofEpochSecond(5_000_000, 1000).toString(); + } else { + return null; + } + } + + static final ClassLoader LOADER = SerializeAllValueClasses.class.getClassLoader(); + + private static Optional> findClass(String name) { + try { + Class clazz = Class.forName(name, false, LOADER); + return Optional.of(clazz); + } catch (ClassNotFoundException | ExceptionInInitializerError | + NoClassDefFoundError | IllegalAccessError ex) { + return Optional.empty(); + } + } + + private static boolean isClass(Class clazz) { + return !(clazz.isEnum() || clazz.isInterface()); + } + + private static boolean isNonAbstract(Class clazz) { + return (clazz.getModifiers() & Modifier.ABSTRACT) == 0; + } + + private static boolean isPublicOrProtected(Class clazz) { + return (clazz.getModifiers() & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0; + } + + @SuppressWarnings("preview") + private static boolean isValueClass(Class clazz) { + if (PreviewFeatures.isEnabled()) + return clazz.isValue(); + var a = clazz.getAnnotation(jdk.internal.ValueBased.class); + return a != null; + } + + /** + * Scans classes in the JDK and returns matching classes. + * + * @return list of matching class + * @throws IOException if an unexpected exception occurs + * @throws URISyntaxException if an unexpected exception occurs + */ + public static List> findAll() throws IOException, URISyntaxException { + FileSystem fs = FileSystems.getFileSystem(new URI("jrt:/")); + Path dir = fs.getPath("/modules"); + try (final Stream paths = Files.walk(dir)) { + // each path is in the form: /modules////.../name.class + return paths.filter((path) -> path.getNameCount() > 2) + .map((path) -> path.subpath(2, path.getNameCount())) + .map(Path::toString) + .filter((name) -> name.endsWith(".class")) + .map((name) -> name.replaceFirst("\\.class$", "")) + .filter((name) -> !name.equals("module-info")) + .map((name) -> name.replaceAll("/", ".")) + .flatMap((java.lang.String name) -> findClass(name).stream()) + .filter(Serializable.class::isAssignableFrom) + .filter(SerializeAllValueClasses::isClass) + .filter(SerializeAllValueClasses::isNonAbstract) + .filter((klass) -> !klass.isSealed()) + .filter(SerializeAllValueClasses::isValueClass) + .filter(SerializeAllValueClasses::isPublicOrProtected) + .collect(Collectors.toList()); + } + } + + private void serializeDeserialize(Object expected) { + try { + Object actual = deserialize(serialize(expected)); + assertEquals(expected, actual, "round trip compare fail"); + } catch (IOException | ClassNotFoundException e) { + fail("serialize/Deserialize", e); + } + } + + /** + * Serialize an object into byte array. + */ + private static byte[] serialize(Object obj) throws IOException { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + try (ObjectOutputStream out = new ObjectOutputStream(bs)) { + out.writeObject(obj); + } + return bs.toByteArray(); + } + + /** + * Deserialize an object from byte array using the requested classloader. + */ + private static Object deserialize(byte[] ba) throws IOException, ClassNotFoundException { + try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(ba))) { + return in.readObject(); + } + } + +} diff --git a/test/jdk/java/io/Serializable/valueObjects/SerializedObjectCombo.java b/test/jdk/java/io/Serializable/valueObjects/SerializedObjectCombo.java new file mode 100644 index 00000000000..c706535fba1 --- /dev/null +++ b/test/jdk/java/io/Serializable/valueObjects/SerializedObjectCombo.java @@ -0,0 +1,1373 @@ +/* + * 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. + */ + +import combo.ComboInstance; +import combo.ComboParameter; +import combo.ComboTask; +import combo.ComboTestHelper; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InvalidClassException; +import java.io.NotSerializableException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; +import java.io.OptionalDataException; +import java.io.Reader; +import java.io.UncheckedIOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiFunction; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.tools.Diagnostic; +import javax.tools.JavaFileObject; + +import static jdk.test.lib.Asserts.assertEquals; +import static jdk.test.lib.Asserts.assertTrue; +import static jdk.test.lib.Asserts.assertFalse; + +import jdk.test.lib.hexdump.HexPrinter; +import jdk.test.lib.hexdump.ObjectStreamPrinter; + +/* + * @test + * @summary Deserialization Combo tests + * @library /test/langtools/tools/javac/lib /test/lib . + * @modules jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.code + * jdk.compiler/com.sun.tools.javac.comp + * jdk.compiler/com.sun.tools.javac.file + * jdk.compiler/com.sun.tools.javac.main + * jdk.compiler/com.sun.tools.javac.tree + * jdk.compiler/com.sun.tools.javac.util + * @build combo.ComboTestHelper SerializedObjectCombo + * @run main/othervm --enable-preview SerializedObjectCombo + */ + + +public final class SerializedObjectCombo extends ComboInstance { + private static final Map LOADER_FOR_PATH = new ConcurrentHashMap<>(); + private static final ParamSet KIND_SET = new ParamSet("KIND", + SerializationKind.values()); + private static final ParamSet FIELD_SET = new ParamSet("FIELD", + 2, ArgumentValue.BASIC_VALUES); + private static final ParamSet CLASSACCESS_SET = new ParamSet("CLASSACCESS", + new ClassAccessKind[]{ClassAccessKind.PUBLIC}); + private static final ParamSet SPECIAL_WRITE_METHODS_SET = new ParamSet("SPECIAL_WRITE_METHODS", + WriteObjectFragments.values()); + private static final ParamSet SPECIAL_READ_METHODS_SET = new ParamSet("SPECIAL_READ_METHODS", + ReadObjectFragments.values()); + private static final ParamSet EXTERNALIZABLE_METHODS_SET = new ParamSet("EXTERNALIZABLE_METHODS", + ExternalizableMethodFragments.values()); + private static final ParamSet OBJECT_CONSTRUCTOR_SET = new ParamSet("OBJECT_CONSTRUCTOR", + ObjectConstructorFragment.ANNOTATED_OBJECT_CONSTRUCTOR_FRAGMENT, ObjectConstructorFragment.NONE); + private static final ParamSet VALUE_SET = new ParamSet("VALUE", + ValueKind.values()); + private static final ParamSet TESTNAME_EXTENDS_SET = new ParamSet("TESTNAME_EXTENDS", + TestNameExtendsFragments.NONE, TestNameExtendsFragments.TESTNAME_EXTENDS_FRAGMENT); + private static final ParamSet TOP_ABSTRACT_SET = new ParamSet("TOP_FRAGMENTS", + TopFragments.values()); + /** + * The base template to generate all test classes. + * Each substitutable fragment is defined by an Enum of the alternatives. + * Giving each a name and an array of ComboParameters with the expansion value. + */ + private static final String TEST_SOURCE_TEMPLATE = """ + import java.io.*; + import java.util.*; + import jdk.internal.value.DeserializeConstructor; + import jdk.internal.MigratedValueClass; + + #{TOP_FRAGMENTS} + + @MigratedValueClass + #{CLASSACCESS} #{VALUE} class #{TESTNAME} #{TESTNAME_EXTENDS} #{KIND.IMPLEMENTS} { + #{FIELD[0]} f1; + #{FIELD[1]} f2; + #{FIELD_ADDITIONS} + #{CLASSACCESS} #{TESTNAME}() { + f1 = #{FIELD[0].RANDOM}; + f2 = #{FIELD[1].RANDOM}; + #{FIELD_CONSTRUCTOR_ADDITIONS} + } + #{OBJECT_CONSTRUCTOR} + @Override public boolean equals(Object obj) { + if (obj instanceof #{TESTNAME} other) { + if (#{FIELD[0]}.class.isPrimitive()) { + if (f1 != other.f1) return false; + } else { + if (!Objects.equals(f1, other.f1)) return false; + } + if (#{FIELD[1]}.class.isPrimitive()) { + if (f2 != other.f2) return false; + } else { + if (!Objects.equals(f2, other.f2)) return false; + } + return true; + } + return false; + } + @Override public String toString() { + return "f1: " + String.valueOf(f1) + + ", f2: " + String.valueOf(f2) + #{FIELD_TOSTRING_ADDITIONS}; + } + #{KIND.SPECIAL_METHODS} + private static final long serialVersionUID = 1L; + } + """; + + // The unique number to qualify interface names, unique across multiple runs + private static int uniqueId = 0; + // Compilation errors prevent execution; set/cleared by checkCompile + private ComboTask.Result compilationResult = null; + // The current set of parameters for the file being compiled and tested + private final Set currParams = new HashSet<>(); + + private static List focusKeys = null; + + private enum CommandOption { + SHOW_SOURCE("--show-source", "show source files"), + VERBOSE("--verbose", "show extra information"), + SHOW_SERIAL_STREAM("--show-serial", "show and format the serialized stream"), + EVERYTHING("--everything", "run all tests"), + TRACE("--trace", "set TRACE system property of ObjectInputStream (temp)"), + MAX_COMBOS("--max-combo", "maximum number of values for each parameter", CommandOption::parseInt), + NO_PRE_FILTER("--no-pre-filter", "disable pre-filter checks"), + SELFTEST("--self-test", "run some self tests and exit"), + ; + private final String option; + private final String usage; + private final BiFunction parseArg; + private Optional value; + CommandOption(String option, String usage, BiFunction parseArg) { + this.option = option; + this.usage = usage; + this.parseArg = parseArg; + this.value = Optional.empty(); + } + CommandOption(String option, String usage) { + this(option, usage, null); + } + + /** + * Evaluate and parse an array of command line args + * @param args array of strings + * @return true if parsing succeeded + */ + static boolean parseOptions(String[] args) { + boolean unknownArg = false; + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + Optional knownOpt = Arrays.stream(CommandOption.values()) + .filter(o -> o.option.equals(arg)) + .findFirst(); + if (knownOpt.isEmpty()) { // Not a recognized option + if (arg.startsWith("-")) { + System.err.println("Unrecognized option: " + arg); + unknownArg = true; + } else { + // Take the remaining non-option args as selectors of keys to be run + String[] keys = Arrays.copyOfRange(args, i, args.length); + focusKeys = List.of(keys); + } + } else { + CommandOption option = knownOpt.get(); + if (option.parseArg == null) { + option.setValue(true); + } else { + i++; + if (i >= args.length || args[i].startsWith("--")) { + System.err.println("Missing argument for " + option.option); + continue; + } + option.parseArg.apply(option, args[i]); + } + } + } + return !unknownArg; + } + static void showUsage() { + System.out.println(""" + Usage: + """); + Arrays.stream(CommandOption.values()).forEach(o -> System.out.printf(" %-15s: %s\n", o.option, o.usage)); + } + boolean present() { + return value != null && value.isPresent(); + } + void setValue(Object o) { + value = Optional.ofNullable(o); + } + private static boolean parseInt(CommandOption option, String arg) { + try { + int count = Integer.parseInt(arg); + option.setValue(count); + } catch (NumberFormatException nfe) { + System.out.println("--max-combo argument not a number: " + arg); + } + return true; + } + // Get the int value from the option, defaulting if not valid or present + private int getInt(int otherMax) { + Object obj = value == null ? otherMax : value.orElseGet(() -> otherMax); + return (obj instanceof Integer i) ? i : otherMax; + } + } + + private static URLClassLoader getLoaderFor(Path path) { + return LOADER_FOR_PATH.computeIfAbsent(path, + p -> { + try { + // new URLClassLoader for path + Files.createDirectories(p); + URL[] urls = {p.toUri().toURL()}; + return new URLClassLoader(p.toString(), urls, null); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + }); + } + + // Map an array of strings to an array of ComboParameter.Constants. + @SuppressWarnings("unchecked") + private static ComboParameter.Constant[] paramsForStrings(String... strings) { + return Arrays.stream(strings) + .map(ComboParameter.Constant::new).toArray(ComboParameter.Constant[]::new); + } + + /** + * Main to generate combinations and run the tests. + * + * @param args may contain "--verbose" to show source of every file + * @throws Exception In case of failure + */ + public static void main(String... args) throws Exception { + if (!CommandOption.parseOptions(args)) { + CommandOption.showUsage(); + System.exit(1); + } + + Arrays.stream(CommandOption.values()) + .filter(o -> o.present()) + .forEach( o1 -> System.out.printf(" %15s: %s\n", o1.option, o1.value + )); + + if (CommandOption.SELFTEST.present()) { + selftest(); + return; + } + + // Sets of all possible ComboParameters (substitutions) + Set allParams = Set.of( + VALUE_SET, + KIND_SET, + TOP_ABSTRACT_SET, + OBJECT_CONSTRUCTOR_SET, + TESTNAME_EXTENDS_SET, + CLASSACCESS_SET, + SPECIAL_READ_METHODS_SET, + SPECIAL_WRITE_METHODS_SET, + EXTERNALIZABLE_METHODS_SET, + FIELD_SET + ); + + // Test variations of all code shapes + var helper = new ComboTestHelper(); + int maxCombos = CommandOption.MAX_COMBOS.getInt(2); + + Set subSet = CommandOption.EVERYTHING.present() ? allParams + : computeSubset(allParams, focusKeys, maxCombos); + withDimensions(helper, subSet); + if (CommandOption.VERBOSE.present()) { + System.out.println("Keys; maximum combinations: " + maxCombos); + subSet.stream() + .sorted((p, q) -> String.CASE_INSENSITIVE_ORDER.compare(p.key(), q.key())) + .forEach(p -> System.out.println(" " + p.key + ": " + Arrays.toString(p.params))); + } + helper.withFilter(SerializedObjectCombo::filter) + .withFailMode(ComboTestHelper.FailMode.FAIL_FAST) + .run(SerializedObjectCombo::new); + } + + private static void withDimensions(ComboTestHelper helper, Set subSet) { + subSet.forEach(p -> { + if (p.count() == 1) + helper.withDimension(p.key(), SerializedObjectCombo::saveParameter, p.params()); + else + helper.withArrayDimension(p.key(), SerializedObjectCombo::saveParameter, p.count(), p.params()); + }); + } + + // Return a subset of ParamSets with the non-focused ParamSet's truncated to a max number of values + private static Set computeSubset(Set allParams, List focusKeys, int maxKeys) { + if (focusKeys == null || focusKeys.isEmpty()) + return allParams; + Set r = allParams.stream().map(p -> + (focusKeys.contains(p.key())) ? p + : new ParamSet(p.key, p.count(), Arrays.copyOfRange(p.params(), 0, Math.min(p.params().length, maxKeys)))) + .collect(Collectors.toUnmodifiableSet()); + return r; + } + + /** + * Print the source files to System out + * + * @param task the compilation task + */ + static void showSources(ComboTask task) { + task.getSources() + .forEach(fo -> { + System.out.println("Source: " + fo.getName()); + System.out.println(getSource(fo)); + }); + } + + /** + * Return the contents of the source file + * + * @param fo a file object + * @return the contents of the source file + */ + static String getSource(JavaFileObject fo) { + try (Reader reader = fo.openReader(true)) { + char[] buf = new char[100000]; + var len = reader.read(buf); + return new String(buf, 0, len); + } catch (IOException ioe) { + return "IOException: " + fo.getName() + ", ex: " + ioe.getMessage(); + } + } + + /** + * Dump the serial stream. + * + * @param bytes the bytes of the stream + */ + private static void showSerialStream(byte[] bytes) { + HexPrinter.simple().dest(System.out).formatter(ObjectStreamPrinter.formatter()).format(bytes); + } + + /** + * Serialize an object into byte array. + */ + private static byte[] serialize(Object obj) throws IOException { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + try (ObjectOutputStream out = new ObjectOutputStream(bs)) { + out.writeObject(obj); + } + return bs.toByteArray(); + } + + /** + * Deserialize an object from byte array using the requested classloader. + */ + private static Object deserialize(byte[] ba, ClassLoader loader) throws IOException, ClassNotFoundException { + try (ObjectInputStream in = new LoaderObjectInputStream(new ByteArrayInputStream(ba), loader)) { + return in.readObject(); + } + } + + + @Override + public int id() { + return ++uniqueId; + } + + private void fail(String msg, Throwable thrown) { + super.fail(msg); + thrown.printStackTrace(System.out); + } + + /** + * Save a parameter. + * + * @param param a ComboParameter + */ + private void saveParameter(ComboParameter param) { + saveParameter(param, 0); + } + + /** + * Save an indexed parameter. + * + * @param param a ComboParameter + * @param index unused + */ + private void saveParameter(ComboParameter param, int index) { + currParams.add(param); + } + + /** + * Filter out needless tests (mostly with more variations of arguments than needed). + * Usually, these are compile time failures, or code shapes that cannot succeed. + * + * @return true to run the test, false if not + */ + boolean filter() { + if (!CommandOption.NO_PRE_FILTER.present()) { + for (CodeShape shape : CodeShape.values()) { + if (shape.test(currParams)) { + if (CommandOption.VERBOSE.present()) { + System.out.println("IGNORING: " + shape); + } + return false; + } + } + } + if (CommandOption.VERBOSE.present()) { + System.out.println("TESTING: "); + showParams(); + } + return true; + } + + /** + * Generate the source files from the parameters and test a single combination. + * Two versions are compiled into different directories and separate class loaders. + * They differ only with the addition of a field to the generated class. + * Then each class is serialized and deserialized by the other class, + * testing simple evolution in the process. + * + * @throws IOException catch all IOException + */ + @Override + public void doWork() throws IOException { + String cp = System.getProperty("test.classes"); + String className = "Class_" + this.id(); + + // new URLClassLoader for path + final Path firstPath = Path.of(cp, "1st"); + URLClassLoader firstLoader = getLoaderFor(firstPath); + final Path secondPath = Path.of(cp, "2nd"); + URLClassLoader secondLoader = getLoaderFor(secondPath); + + // Create a map of additional constants that are resolved without the combo overhead. + final Map> params = new HashMap<>(); + params.put("TESTNAME", new ComboParameter.Constant<>(className)); + params.put("SPECIAL_METHODS_SERIALIZABLE", new ComboParameter.Constant<>("#{SPECIAL_READ_METHODS} #{SPECIAL_WRITE_METHODS}")); + params.put("SPECIAL_METHODS_EXTERNALIZABLE", new ComboParameter.Constant<>("#{EXTERNALIZABLE_METHODS}")); + params.put("FIELD_ADDITIONS", new ComboParameter.Constant<>("")); + params.put("FIELD_CONSTRUCTOR_ADDITIONS", new ComboParameter.Constant<>("")); + params.put("FIELD_TOSTRING_ADDITIONS", new ComboParameter.Constant<>("")); + + final ComboTask firstTask = generateAndCompile(firstPath, className, params); + + if (firstTask == null) { + return; // Skip execution, errors already reported + } + + if (CommandOption.EVERYTHING.present()) { + params.put("FIELD_ADDITIONS", new ComboParameter.Constant<>("int fExtra;")); + params.put("FIELD_CONSTRUCTOR_ADDITIONS", new ComboParameter.Constant<>("this.fExtra = 99;")); + params.put("FIELD_TOSTRING_ADDITIONS", new ComboParameter.Constant<>("+ \", fExtra: String.valueOf(fExtra)\"")); + final ComboTask secondTask = generateAndCompile(secondPath, className, params); + if (secondTask == null) { + return; // Skip execution, errors already reported + } + + doTestWork(className, firstTask, firstLoader, secondLoader); + doTestWork(className, secondTask, secondLoader, firstLoader); + } else { + doTestWork(className, firstTask, firstLoader, firstLoader); + } + } + + /** + * Test that two versions of the class can be serialized using one version and deserialized + * by the other version. + * The two classes have the same name and have been compiled into different classloaders. + * The original and result objects are compared using .equals if there is only 1 classloader. + * If the classloaders are different the `toString()` output for each object is compared loosely. + * (One must be the prefix of the other) + * + * @param className the class name + * @param task the task context (for source and parameters to report failures) + * @param firstLoader the first classloader + * @param secondLoader the second classloader + */ + private void doTestWork(String className, ComboTask task, ClassLoader firstLoader, ClassLoader secondLoader) { + byte[] bytes = null; + try { + Class tc = Class.forName(className, true, firstLoader); + Object testObj = tc.getDeclaredConstructor().newInstance(); + bytes = serialize(testObj); + if (CommandOption.VERBOSE.present()) { + System.out.println("Testing: " + task.getSources()); + if (CommandOption.SHOW_SOURCE.present()) { + showParams(); + showSources(task); + } + if (CommandOption.SHOW_SERIAL_STREAM.present()) { + showSerialStream(bytes); + } + } + + if (CodeShape.BAD_SO_CONSTRUCTOR.test(currParams)) { + // should have thrown ICE due to mismatch between value class and missing constructor + System.out.println(CodeShape.BAD_SO_CONSTRUCTOR.explain(currParams)); + fail(CodeShape.BAD_SO_CONSTRUCTOR.explain(currParams)); + } + + Object actual = deserialize(bytes, secondLoader); + if (testObj.getClass().getClassLoader().equals(actual.getClass().getClassLoader())) { + assertEquals(testObj, actual, "Round-trip comparison fail using .equals"); + } else { + // The instances are from different classloaders and can't be compared directly + final String s1 = testObj.toString(); + final String s2 = actual.toString(); + assertTrue(s1.startsWith(s2) || s2.startsWith(s1), + "Round-trip comparison fail using toString(): s1: " + s1 + ", s2: " + s2); + } + } catch (InvalidClassException ice) { + for (CodeShape shape : CodeShape.values()){ + if (ice.equals(shape.exception)) { + if (shape.test(currParams)) { + if (CommandOption.VERBOSE.present()) { + System.out.println("OK: " + shape.explain(currParams)); + } else { + // unexpected ICE + ice.printStackTrace(System.out); + showParams(); + showSources(task); + if (bytes != null) + showSerialStream(bytes); + fail(ice.getMessage()); + } + } + } + } + } catch (EOFException | OptionalDataException eof) { + // Ignore if conditions of the source invite EOF + if (0 == CodeShape.shapesThrowing(EOFException.class).peek(s -> { + // Ignore: Serialized Object to reads custom data but none written + if (CommandOption.VERBOSE.present()) { + System.out.println("OK: " + s.explain(currParams)); + } + }).count()) { + eof.printStackTrace(System.out); + showParams(); + showSources(task); + showSerialStream(bytes); + fail(eof.getMessage(), eof); + } + } catch (ClassFormatError cfe) { + System.out.println(cfe.toString()); + } catch (NotSerializableException nse) { + if (CodeShape.BAD_EXT_VALUE.test(currParams)) { + // Expected Value class that is Externalizable w/o writeReplace + } else { + // unexpected NSE + nse.printStackTrace(System.out); + showParams(); + showSources(task); + fail(nse.getMessage(), nse); + } + } catch (Throwable ex) { + ex.printStackTrace(System.out); + showParams(); + showSources(task); + fail(ex.getMessage()); + } + } + + // Side effect of error is compilationResult.hasErrors() > 0 + private ComboTask generateAndCompile(Path path, String className, Map> params) { + ComboTask task = newCompilationTask() + .withSourceFromTemplate(className, + TEST_SOURCE_TEMPLATE, + r -> params.computeIfAbsent(r, s -> new ComboParameter.Constant<>("UNKNOWN_" + s))) + .withOption("-d") + .withOption(path.toString()) + .withOption("--enable-preview") + .withOption("--add-modules") + .withOption("java.base") + .withOption("--add-exports") + .withOption("java.base/jdk.internal=ALL-UNNAMED") + .withOption("--add-exports") + .withOption("java.base/jdk.internal.value=ALL-UNNAMED") + .withOption("--source") + .withOption(Integer.toString(Runtime.version().feature())); + ; + task.generate(this::checkCompile); + if (compilationResult.hasErrors()) { + boolean match = false; + for (CodeShape shape : CodeShape.values()){ + if (CompileException.class.equals(shape.exception)) { + if (shape.test(currParams)) { + // shape matches known error + if (!uniqueParams.contains(shape)) { + System.out.println("// Unique: " + shape); + uniqueParams.add(shape); + } + match = true; + } + } + } + if (match) + return null; + // Unexpected compilation error + showDiags(compilationResult); + showSources(task); + showParams(); + fail("Compilation failure"); + } + return task; + } + + private static Set uniqueParams = new HashSet<>(); + + private String paramToString(ComboParameter param) { + String name = param.getClass().getName(); + return name.substring(name.indexOf('$') + 1) + "::" + + param + ": " + truncate(param.expand(null), 60); + } + + private void showParams() { + currParams.stream() + .sorted((p, q) -> String.CASE_INSENSITIVE_ORDER.compare(paramToString(p), paramToString(q))) + .forEach(p -> System.out.println(" " + paramToString(p))); + } + + private void showParams(ComboParameter... params) { + for (ComboParameter param : params) { + System.out.println(">>> " + paramToString(param) + ", present: " + + currParams.contains(param)); + } + } + + private static String truncate(String s, int maxLen) { + int nl = s.indexOf("\n"); + if (nl >= 0) + maxLen = nl; + if (maxLen < s.length()) { + return s.substring(0, maxLen).concat("..."); + } else { + return s; + } + } + + /** + * Report any compilation errors. + * + * @param res the result + */ + void checkCompile(ComboTask.Result res) { + compilationResult = res; + } + + void showDiags(ComboTask.Result res) { + res.diagnosticsForKind(Diagnostic.Kind.ERROR).forEach(SerializedObjectCombo::showDiag); + res.diagnosticsForKind(Diagnostic.Kind.WARNING).forEach(SerializedObjectCombo::showDiag); + } + + static void showDiag(Diagnostic diag) { + System.out.println(diag.getKind() + ": " + diag.getMessage(Locale.ROOT)); + System.out.println("File: " + diag.getSource() + + " line: " + diag.getLineNumber() + ", col: " + diag.getColumnNumber()); + } + + private static class CodeShapePredicateOp implements Predicate { + private final Predicate first; + private final Predicate other; + private final String op; + + CodeShapePredicateOp(Predicate first, Predicate other, String op) { + if ("OR" != op && "AND" != op && "NOT" != op) + throw new IllegalArgumentException("unknown op: " + op); + this.first = first; + this.other = other; + this.op = op; + } + + @Override + public boolean test(T comboParameters) { + return switch (op) { + case "NOT" -> !first.test(comboParameters); + case "OR" -> first.test(comboParameters) || other.test(comboParameters); + case "AND" -> first.test(comboParameters) && other.test(comboParameters); + default -> throw new IllegalArgumentException("unknown op: " + op); + }; + } + @Override + public Predicate and(Predicate other) { + return new CodeShapePredicateOp(this, other,"AND"); + } + + + @Override + public Predicate negate() { + return new CodeShapePredicateOp(this, null,"NOT"); + } + + @Override + public Predicate or(Predicate other) { + return new CodeShapePredicateOp(this, other,"OR"); + } + public String toString() { + return switch (op) { + case "NOT" -> op + " " + first; + case "OR" -> "(" + first + " " + op + " " + other + ")"; + case "AND" -> "(" + first + " " + op + " " + other + ")"; + default -> throw new IllegalArgumentException("unknown op: " + op); + }; + } + } + + interface CodeShapePredicate extends Predicate> { + @Override + default boolean test(Set comboParameters) { + return comboParameters.contains(this); + } + + @Override + default Predicate> and(Predicate> other) { + return new CodeShapePredicateOp(this, other,"AND"); + } + + + @Override + default Predicate> negate() { + return new CodeShapePredicateOp(this, null,"NOT"); + } + + @Override + default Predicate> or(Predicate> other) { + return new CodeShapePredicateOp(this, other,"OR"); + } + } + + /** + * A set of code shapes that are interesting, usually indicating an error + * compile time, or runtime based on the shape of the code and the dependencies between + * the code fragments. + * The descriptive text may be easier to understand than the boolean expression of the fragments. + * They can also be to filter out test cases that would not succeed. + * Or can be used after a successful deserialization to check + * if an exception should have been thrown. + */ + private enum CodeShape implements Predicate> { + BAD_SO_CONSTRUCTOR("Value class does not have a constructor annotated with DeserializeConstructor", + InvalidClassException.class, + ValueKind.VALUE, + ObjectConstructorFragment.ANNOTATED_OBJECT_CONSTRUCTOR_FRAGMENT.negate() + ), + BAD_EXT_VALUE("Externalizable can not be a value class", + CompileException.class, + SerializationKind.EXTERNALIZABLE, + ValueKind.VALUE), + BAD_EXT_METHODS("Externalizable methods but not Externalizable", + CompileException.class, + ExternalizableMethodFragments.EXTERNALIZABLE_METHODS, + SerializationKind.EXTERNALIZABLE.negate()), + BAD_EXT_NO_METHODS("Externalizable but no implementation of readExternal or writeExternal", + CompileException.class, + SerializationKind.EXTERNALIZABLE, + ExternalizableMethodFragments.EXTERNALIZABLE_METHODS.negate()), + BAD_VALUE_NON_ABSTRACT_SUPER("Can't inherit from non-abstract super or abstract super with fields", + CompileException.class, + ValueKind.VALUE, + TestNameExtendsFragments.TESTNAME_EXTENDS_FRAGMENT, + TopFragments.ABSTRACT_NO_FIELDS.negate()), + BAD_MISSING_SUPER("Extends TOP_ without TOP_ superclass", + CompileException.class, + TestNameExtendsFragments.TESTNAME_EXTENDS_FRAGMENT, + TopFragments.NONE), + BAD_READ_CUSTOM_METHODS("Custom read fragment but no custom write fragment", + EOFException.class, + ReadObjectFragments.READ_OBJECT_FIELDS_CUSTOM_FRAGMENT + .or(ReadObjectFragments.READ_OBJECT_DEFAULT_CUSTOM_FRAGMENT), + WriteObjectFragments.WRITE_OBJECT_FIELDS_CUSTOM_FRAGMENT + .or(WriteObjectFragments.WRITE_OBJECT_DEFAULT_CUSTOM_FRAGMENT).negate() + ), + BAD_RW_CUSTOM_METHODS("Custom write fragment but no custom read fragment", + null, + WriteObjectFragments.WRITE_OBJECT_FIELDS_CUSTOM_FRAGMENT + .or(WriteObjectFragments.WRITE_OBJECT_DEFAULT_CUSTOM_FRAGMENT), + ReadObjectFragments.READ_OBJECT_FIELDS_CUSTOM_FRAGMENT + .or(ReadObjectFragments.READ_OBJECT_DEFAULT_CUSTOM_FRAGMENT).negate()), + BAD_VALUE_READOBJECT_METHODS("readObjectXXX(OIS) methods incompatible with Value class", + CompileException.class, + ReadObjectFragments.READ_OBJECT_FIELDS_FRAGMENT + .or(ReadObjectFragments.READ_OBJECT_DEFAULT_FRAGMENT) + .or(ReadObjectFragments.READ_OBJECT_FIELDS_CUSTOM_FRAGMENT) + .or(ReadObjectFragments.READ_OBJECT_DEFAULT_CUSTOM_FRAGMENT), + ValueKind.VALUE), + ; + + private final String description; + private final Class exception; + private final List>> predicates; + CodeShape(String desc, Class exception, Predicate>... predicates) { + this.description = desc; + this.exception = exception; + this.predicates = List.of(predicates); + } + + // Return a stream of CodeShapes throwing the exception + static Stream shapesThrowing(Class exception) { + return Arrays.stream(values()).filter(s -> exception.equals(s.exception)); + + } + + /** + * {@return true if all of the predicates are true in the set of ComboParameters} + * @param comboParameters a set of ComboParameters + */ + @Override + public boolean test(Set comboParameters) { + for (Predicate> p : predicates) { + if (!p.test(comboParameters)) + return false; + } + return true; + } + + /** + * {@return a string describing the predicate in relation to a set of parameters} + * @param comboParameters a set of active ComboParameters. + */ + public String explain(Set comboParameters) { + StringBuffer sbTrue = new StringBuffer(); + StringBuffer sbFalse = new StringBuffer(); + for (Predicate> p : predicates) { + ((p.test(comboParameters)) ? sbTrue : sbFalse) + .append(p).append(", "); + } + return description + "\n" +"Missing: " + sbFalse + "\nTrue: " + sbTrue; + } + public String toString() { + return super.toString() + "::" + description + ", params: " + predicates; + } + } + + /** + * TopAbstract Fragments + */ + enum TopFragments implements ComboParameter, CodeShapePredicate { + NONE(""), + ABSTRACT_NO_FIELDS(""" + @MigratedValueClass + abstract #{VALUE} class TOP_#{TESTNAME} implements Serializable { + #{CLASSACCESS} TOP_#{TESTNAME}() {} + } + """), + ABSTRACT_ONE_FIELD(""" + @MigratedValueClass + abstract #{VALUE} class TOP_#{TESTNAME} implements Serializable { + private int t1; + #{CLASSACCESS} TOP_#{TESTNAME}() { + t1 = 1; + } + } + """), + NO_FIELDS(""" + @MigratedValueClass + #{VALUE} class TOP_#{TESTNAME} implements Serializable { + #{CLASSACCESS} TOP_#{TESTNAME}() {} + } + """), + ONE_FIELD(""" + @MigratedValueClass + #{VALUE} class TOP_#{TESTNAME} implements Serializable { + private int t1; + #{CLASSACCESS} TOP_#{TESTNAME}() { + t1 = 1; + } + } + """), + ; + + private final String template; + + TopFragments(String template) { + this.template = template; + } + + @Override + public String expand(String optParameter) { + return template; + } + } + + /** + * TopAbstract Fragments + */ + enum TestNameExtendsFragments implements ComboParameter, CodeShapePredicate { + NONE(""), + TESTNAME_EXTENDS_FRAGMENT("extends TOP_#{TESTNAME}"), + ; + + private final String template; + + TestNameExtendsFragments(String template) { + this.template = template; + } + + @Override + public String expand(String optParameter) { + return template; + } + } + + /** + * SerializedObjectCustom Fragments + */ + enum SerializedObjectCustomFragments implements ComboParameter, CodeShapePredicate { + NONE(""), + ; + + private final String template; + + SerializedObjectCustomFragments(String template) { + this.template = template; + } + + @Override + public String expand(String optParameter) { + return template; + } + } + + /** + * ExternalizableMethod Fragments + */ + enum ExternalizableMethodFragments implements ComboParameter, CodeShapePredicate { + NONE(""), + EXTERNALIZABLE_METHODS(""" + public void writeExternal(ObjectOutput oos) throws IOException { + oos.write#{FIELD[0].READFIELD}(f1); + oos.write#{FIELD[1].READFIELD}(f2); + } + + public void readExternal(ObjectInput ois) throws IOException, ClassNotFoundException { + f1 = (#{FIELD[0]})ois.read#{FIELD[0].READFIELD}(); + f2 = (#{FIELD[1]})ois.read#{FIELD[1].READFIELD}(); + } + """), + ; + + private final String template; + + ExternalizableMethodFragments(String template) { + this.template = template; + } + + @Override + public String expand(String optParameter) { + return template; + } + } + + /** + * ObjectConstructorFragment Fragments + */ + enum ObjectConstructorFragment implements ComboParameter, CodeShapePredicate { + NONE(""), + ANNOTATED_OBJECT_CONSTRUCTOR_FRAGMENT(""" + @DeserializeConstructor + #{CLASSACCESS} #{TESTNAME}(#{FIELD[0]} f1, #{FIELD[1]} f2) { + this.f1 = f1; + this.f2 = f2; + #{FIELD_CONSTRUCTOR_ADDITIONS} + } + + @DeserializeConstructor + #{CLASSACCESS} #{TESTNAME}(#{FIELD[0]} f1, #{FIELD[1]} f2, int fExtra) { + this.f1 = f1; + this.f2 = f2; + #{FIELD_CONSTRUCTOR_ADDITIONS} + } + """), + + ; + + private final String template; + + ObjectConstructorFragment(String template) { + this.template = template; + } + + @Override + public String expand(String optParameter) { + return template; + } + } + + /** + * WriteObject templates + */ + enum WriteObjectFragments implements ComboParameter, CodeShapePredicate { + NONE(""), + WRITE_OBJECT_DEFAULT_FRAGMENT(""" + private void writeObject(ObjectOutputStream oos) throws IOException { + oos.defaultWriteObject(); + } + """), + WRITE_OBJECT_FIELDS_FRAGMENT(""" + private void writeObject(ObjectOutputStream oos) throws IOException { + ObjectOutputStream.PutField fields = oos.putFields(); + fields.put("f1", f1); + fields.put("f2", f2); + oos.writeFields(); + } + """), + WRITE_OBJECT_DEFAULT_CUSTOM_FRAGMENT(""" + private void writeObject(ObjectOutputStream oos) throws IOException { + oos.defaultWriteObject(); + // Write custom data + oos.write#{FIELD[0].READFIELD}(#{FIELD[0].DEFAULT}); + oos.write#{FIELD[1].READFIELD}(#{FIELD[1].DEFAULT}); + } + """), + WRITE_OBJECT_FIELDS_CUSTOM_FRAGMENT(""" + private void writeObject(ObjectOutputStream oos) throws IOException { + ObjectOutputStream.PutField fields = oos.putFields(); + fields.put("f1", f1); + fields.put("f2", f2); + oos.writeFields(); + // Write custom data + oos.write#{FIELD[0].READFIELD}(#{FIELD[0].DEFAULT}); + oos.write#{FIELD[1].READFIELD}(#{FIELD[1].DEFAULT}); + } + """), + ; + + private final String template; + + WriteObjectFragments(String template) { + this.template = template; + } + + @Override + public String expand(String optParameter) { + return template; + } + } + + /** + * ReadObject templates + */ + enum ReadObjectFragments implements ComboParameter, CodeShapePredicate { + NONE(""), + READ_OBJECT_DEFAULT_FRAGMENT(""" + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + ois.defaultReadObject(); + } + """), + READ_OBJECT_FIELDS_FRAGMENT(""" + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + ObjectInputStream.GetField fields = ois.readFields(); + this.f1 = (#{FIELD[0]})fields.get("f1", #{FIELD[0].DEFAULT}); + this.f2 = (#{FIELD[1]})fields.get("f2", #{FIELD[1].DEFAULT}); + } + """), + READ_OBJECT_DEFAULT_CUSTOM_FRAGMENT(""" + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + ois.defaultReadObject(); + // Read custom data + #{FIELD[0]} d1 = (#{FIELD[0]})ois.read#{FIELD[0].READFIELD}(); + #{FIELD[1]} d2 = (#{FIELD[1]})ois.read#{FIELD[1].READFIELD}(); + assert Objects.equals(#{FIELD[0].DEFAULT}, d1) : "reading custom data1, actual: " + d1; + assert Objects.equals(#{FIELD[1].DEFAULT}, d2) : "reading custom data2, actual: " + d2; + } + """), + READ_OBJECT_FIELDS_CUSTOM_FRAGMENT(""" + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + ObjectInputStream.GetField fields = ois.readFields(); + this.f1 = (#{FIELD[0]})fields.get("f1", #{FIELD[0].DEFAULT}); + this.f2 = (#{FIELD[1]})fields.get("f2", #{FIELD[1].DEFAULT}); + // Read custom data + #{FIELD[0]} d1 = (#{FIELD[0]})ois.read#{FIELD[0].READFIELD}(); + #{FIELD[1]} d2 = (#{FIELD[1]})ois.read#{FIELD[1].READFIELD}(); + assert Objects.equals(#{FIELD[0].DEFAULT}, d1) : "reading custom data1, actual: " + d1; + assert Objects.equals(#{FIELD[1].DEFAULT}, d2) : "reading custom data2, actual: " + d2; + } + """), + ; + + private final String template; + + ReadObjectFragments(String template) { + this.template = template; + } + + @Override + public String expand(String optParameter) { + return template; + } + } + + /** + * Value and Identity kinds. + */ + enum ValueKind implements ComboParameter, CodeShapePredicate { + VALUE("value"), + IDENTITY(""), + ; + + private final String template; + + ValueKind(String template) { + this.template = template; + } + + @Override + public String expand(String optParameter) { + return template; + } + } + + enum SerializationKind implements ComboParameter, CodeShapePredicate { + SERIALIZABLE("SER", "implements Serializable"), + EXTERNALIZABLE("EXT", "implements Externalizable"), + ; + + private final String key; + private final String declaration; + + SerializationKind(String key, String declaration) { + this.key = key; + this.declaration = declaration; + } + + public String expand(String optParameter) { + return switch (optParameter) { + case null -> key; + case "IMPLEMENTS" -> declaration; + default -> + "#{" + optParameter + "_" + this + "}"; // everything ELSE turn into requested key with suffix + }; + } + } + + /** + * Class Access kinds. + */ + enum ClassAccessKind implements ComboParameter, CodeShapePredicate { + PUBLIC("public"), + PACKAGE(""), + ; + + private final String classAccessTemplate; + + ClassAccessKind(String classAccessTemplate) { + this.classAccessTemplate = classAccessTemplate; + } + + @Override + public String expand(String optParameter) { + return classAccessTemplate; + } + } + + /** + * Type of arguments to insert in method signatures + */ + enum ArgumentValue implements ComboParameter, CodeShapePredicate { + BOOLEAN("boolean", true), + BYTE("byte", (byte) 127), + CHAR("char", 'Z'), + SHORT("short", (short) 0x7fff), + INT("int", 0x7fffffff), + LONG("long", 0x7fffffffffffffffL), + FLOAT("float", 1.0F), + DOUBLE("double", 1.0d), + STRING("String", "xyz"); + + static final ArgumentValue[] BASIC_VALUES = {INT, STRING}; + + private final String argumentsValueTemplate; + private final Object value; + + ArgumentValue(String argumentsValueTemplate, Object value) { + this.argumentsValueTemplate = argumentsValueTemplate; + this.value = value; + } + + @Override + public String expand(String optParameter) { + return switch (optParameter) { + case null -> argumentsValueTemplate; + case "TITLECASE" -> Character.toTitleCase(argumentsValueTemplate.charAt(0)) + + argumentsValueTemplate.substring(1); + case "DEFAULT" -> switch (this) { + case BOOLEAN -> "false"; + case BYTE -> "(byte)-1"; + case CHAR -> "'" + "!" + "'"; + case SHORT -> "(short)-1"; + case INT -> "-1"; + case LONG -> "-1L"; + case FLOAT -> "-1.0f"; + case DOUBLE -> "-1.0d"; + case STRING -> '"' + "n/a" + '"'; + }; + case "READFIELD" -> switch (this) { + case BOOLEAN -> "Boolean"; + case BYTE -> "Byte"; + case CHAR -> "Char"; + case SHORT -> "Short"; + case INT -> "Int"; + case LONG -> "Long"; + case FLOAT -> "Float"; + case DOUBLE -> "Double"; + case STRING -> "Object"; + }; + case "RANDOM" -> switch (this) { // or can be Random + case BOOLEAN -> Boolean.toString(!(boolean) value); + case BYTE -> "(byte)" + value + 1; + case CHAR -> "'" + value + "'"; + case SHORT -> "(short)" + value + 1; + case INT -> "-2"; + case LONG -> "-2L"; + case FLOAT -> (1.0f + (float) value) + "f"; + case DOUBLE -> (1.0d + (float) value) + "d"; + case STRING -> "\"" + value + "!\""; + }; + default -> switch (this) { + case BOOLEAN -> value.toString(); + case BYTE -> "(byte)" + value; + case CHAR -> "'" + value + "'"; + case SHORT -> "(short)" + value; + case INT -> "-1"; + case LONG -> "-1L"; + case FLOAT -> value + "f"; + case DOUBLE -> value + "d"; + case STRING -> '"' + (String) value + '"'; + }; + }; + } + } + + /** + * Set of Parameters to fill in template. + * + * @param key the key + * @param params the ComboParameters (one or more) + */ + record ParamSet(String key, int count, ComboParameter... params) { + /** + * Set of parameter strings for fill in template. + * The strings are mapped to CompboParameter.Constants. + * + * @param key the key + * @param strings varargs strings + */ + ParamSet(String key, String... strings) { + this(key, 1, paramsForStrings(strings)); + } + + /** + * Set of parameter strings for fill in template. + * The strings are mapped to CompboParameter.Constants. + * + * @param key the key + * @param strings varargs strings + */ + ParamSet(String key, int count, String... strings) { + this(key, count, paramsForStrings(strings)); + } + + /** + * Set of parameters for fill in template. + * The strings are mapped to CompboParameter.Constants. + * + * @param key the key + * @param params varargs strings + */ + ParamSet(String key, ComboParameter... params) { + this(key, 1, params); + } + } + + /** + * Marks conditions that should match compile time errors + */ + static class CompileException extends RuntimeException { + CompileException(String msg) { + super(msg); + } + } + + /** + * Custom ObjectInputStream to be resolve classes from a specific class loader. + */ + private static class LoaderObjectInputStream extends ObjectInputStream { + private final ClassLoader loader; + + public LoaderObjectInputStream(InputStream in, ClassLoader loader) throws IOException { + super(in); + this.loader = loader; + } + + /** + * Override resolveClass to be resolve classes from the specified loader. + * + * @param desc an instance of class {@code ObjectStreamClass} + * @return the class + * @throws ClassNotFoundException if the class is not found + */ + @Override + protected Class resolveClass(ObjectStreamClass desc) throws ClassNotFoundException { + String name = desc.getName(); + try { + return Class.forName(name, false, loader); + } catch (ClassNotFoundException ex) { + Class cl = Class.forPrimitiveName(name); + if (cl != null) { + return cl; + } else { + throw ex; + } + } + } + } + + private abstract class MyCompilationTask extends ComboInstance { + + } + private static void selftest() { + Set params = Set.of(ValueKind.VALUE, SerializationKind.EXTERNALIZABLE); + assertTrue(ValueKind.VALUE.test(params), "VALUE"); + assertTrue(SerializationKind.EXTERNALIZABLE.test(params), "SerializationKind.EXTERNALIZABLE"); + assertFalse(CodeShape.BAD_EXT_VALUE.test(params)); + } +} diff --git a/test/jdk/java/io/Serializable/valueObjects/SimpleValueGraphs.java b/test/jdk/java/io/Serializable/valueObjects/SimpleValueGraphs.java new file mode 100644 index 00000000000..dba4a6ebbdb --- /dev/null +++ b/test/jdk/java/io/Serializable/valueObjects/SimpleValueGraphs.java @@ -0,0 +1,403 @@ +/* + * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @library /test/lib + * @summary Serialize and deserialize value objects + * @enablePreview + * @modules java.base/jdk.internal + * @modules java.base/jdk.internal.value + * @run testng/othervm SimpleValueGraphs + */ + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectInputStream; +import java.io.ObjectOutput; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.io.InvalidClassException; + +import java.util.Arrays; +import java.util.function.BiFunction; +import java.util.Objects; + +import java.nio.charset.StandardCharsets; + +import jdk.internal.value.DeserializeConstructor; +import jdk.internal.MigratedValueClass; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import jdk.test.lib.hexdump.HexPrinter; +import jdk.test.lib.hexdump.ObjectStreamPrinter; + +@Test +public class SimpleValueGraphs implements Serializable { + + private static boolean DEBUG = true; + + private static SimpleValue foo1 = new SimpleValue("One", 1); + private static SimpleValue foo2 = new SimpleValue("Two", 2); + + @DataProvider(name = "ValueObjects") + public Object[][] valueObjects() { + return new Object[][] { + {new SimpleValue(new SimpleValue(1))}, + {new SimpleValue(new SimpleValue(2), 3)}, + {new SimpleValue[] {foo1, foo1, foo2, foo1, foo2 }}, + }; + } + + @Test(enabled = true, dataProvider = "ValueObjects") + public void roundTrip(Object expected) throws Exception { + byte[] bytes = serialize(expected); + + if (DEBUG) + HexPrinter.simple().dest(System.out).formatter(ObjectStreamPrinter.formatter()).format(bytes); + + Object actual = deserialize(bytes); + System.out.println("actual: " + actual.toString()); + Assert.assertEquals(actual, expected, "Mismatch" + expected.getClass()); + } + + private static Tree treeI = Tree.makeTree(3, (l, r) -> new TreeI((TreeI)l, (TreeI)l)); + private static Tree treeV = Tree.makeTree(3, (l, r) -> new TreeV((TreeV)l, (TreeV)l)); + + // Create a tree of identity objects with a cycle; it will serialize ok, but when deserialized as + // a value class the cycle is broken by replacing the back ref with null. + private static Tree treeCycle(boolean cycle) { + TreeI tree = (TreeI)Tree.makeTree(3, (l, r) -> new TreeI((TreeI)l, (TreeI)l)); + tree.setLeft(cycle ? tree : null); // force a cycle or null + return tree; + } + + @DataProvider(name = "CompatibleChanges") + public Object[][] migrationObjects() { + return new Object[][] { + {treeI, "TreeI", "TreeV", treeV}, // Serialize as an identity class, deserialize as Value class + {treeCycle(true), "TreeI", "TreeV", treeCycle(false)}, + // TBD: add cases for serializing TreeV and converting to TreeI: + // Waiting for: + // JDK-8293321: [lworld] PrimitiveObjectMethods.substitutableInvoker StackOverFlowError + }; + } + + /** + * Test serializing an object graph, and deserialize with a modification of the serialized form. + * The modifications to the stream change the class name being deserialized. + * The cases include serializing an identity class and deserialize the corresponding + * value class. + * + * @param origObj an object to serialize + * @param origName a string in the serialized stream to replace + * @param replName a string to replace the original string + * @param expectedObject the expected object (graph) or an exception if it should fail + * @throws Exception some unexpected exception may be thrown and cause the test to fail + */ + @Test(enabled = true, dataProvider = "CompatibleChanges") + public void treeVTest(Object origObj, String origName, String replName, Object expectedObject) throws Exception { + byte[] bytes = serialize(origObj); + if (DEBUG) { + System.out.println("Original serialized " + origObj.getClass().getName()); + HexPrinter.simple().dest(System.out).formatter(ObjectStreamPrinter.formatter()).format(bytes); + } + + // Modify the serialized bytes to change a class name from the serialized name + // to a different class. The replacement name must be the same length as the original name. + byte[] replBytes = patchBytes(bytes, origName, replName); + if (DEBUG) { + System.out.println("Modified serialized " + origObj.getClass().getName()); + HexPrinter.simple().dest(System.out).formatter(ObjectStreamPrinter.formatter()).format(replBytes); + } + try { + Object actual = deserialize(replBytes); + if (expectedObject instanceof Exception ex) { + Assert.fail("Unexpected: " + expectedObject); + } + // Compare the shape of the actual and expected trees + Assert.assertEquals(actual.toString(), expectedObject.toString(), + "Resulting object not equals: " + actual.getClass().getName()); + + } catch (Exception ex) { + ex.printStackTrace(); + Assert.assertEquals(ex.getClass(), expectedObject.getClass(), ex.toString()); + Assert.assertEquals(ex.getMessage(), ((Exception)expectedObject).getMessage(), ex.toString()); + } + } + + /** + * Serialize an object and return the serialized bytes. + * + * @param expected an object to serialize + * @return a byte array containing the serialized object + */ + private static byte[] serialize(Object expected) throws IOException { + try (ByteArrayOutputStream bout = new ByteArrayOutputStream(); + ObjectOutputStream oout = new ObjectOutputStream(bout)) { + oout.writeObject(expected); + oout.flush(); + return bout.toByteArray(); + } + } + + /** + * Deserialize an object from the byte array. + * @param bytes a byte array + * @return an Object read from the byte array + */ + private static Object deserialize(byte[] bytes) throws IOException, ClassNotFoundException { + try (ByteArrayInputStream bin = new ByteArrayInputStream(bytes); + ObjectInputStream oin = new ObjectInputStream(bin)) { + return oin.readObject(); + } + } + + /** + * Replace every occurrence of the string in the byte array with the replacement. + * The strings are US_ASCII only. + * @param bytes a byte array + * @param orig a string, converted to bytes using US_ASCII, originally exists in the bytes + * @param repl a string, converted to byted using US_ASCII, to replace the original bytes + * @return a new byte array that has been patched + */ + private byte[] patchBytes(byte[] bytes, String orig, String repl) { + byte[] alt = patchBytes(bytes, + orig.getBytes(StandardCharsets.US_ASCII), + repl.getBytes(StandardCharsets.US_ASCII)); + return alt; + } + + /** + * Replace every occurrence of the original bytes in the byte array with the replacement bytes. + * @param bytes a byte array + * @param orig a byte array containing existing bytes in the byte array + * @param repl a byte array to replace the original bytes + * @return a copy of the bytes array with each occurence of the orig bytes with the replacement bytes + */ + static byte[] patchBytes(byte[] bytes, byte[] orig, byte[] repl) { + if (orig.length != repl.length && orig.length > 0) + throw new IllegalArgumentException("orig bytes and replacement must be same length"); + byte[] result = Arrays.copyOf(bytes, bytes.length); + for (int i = 0; i < result.length - orig.length; i++) { + if (Arrays.equals(result, i, i + orig.length, orig, 0, orig.length)) { + for (int j = 0; j < orig.length; j++) { + result[i + j] = repl[j]; + } + i = i + orig.length - 1; // continue replacing after this occurrence + } + } + return result; + } + + public static class SimpleValue implements Serializable { + private static final long serialVersionUID = 1L; + + int i; + Serializable obj; + + public SimpleValue(Serializable o) { + this.obj = o; + this.i = 0; + } + SimpleValue(int i) { + this.i = i; + } + + public SimpleValue(Serializable o, int i) { + this.obj = o; + this.i = i; + } + + public boolean equals(Object obj) { + if (obj instanceof SimpleValue simpleValue) { + return (i == simpleValue.i && Objects.equals(this.obj, simpleValue.obj)); + } + return false; + } + + public int hashCode() { + return i; + } + + public String toString() { + return "SimpleValue{" + "i=" + i + ", obj=" + obj + '}'; + } + } + + interface Tree { + static Tree makeTree(int depth, BiFunction genNode) { + if (depth <= 0) return null; + Tree left = makeTree(depth - 1, genNode); + Tree right = makeTree(depth - 1, genNode); + Tree t = genNode.apply(left, right); + return t; + } + + Tree left(); + Tree right(); + } + static class TreeI implements Tree, Serializable { + + private static final long serialVersionUID = 2L; + private TreeI left; + private TreeI right; + + TreeI(TreeI left, TreeI right) { + this.left = left; + this.right = right; + } + + public TreeI left() { + return left; + } + public TreeI right() { + return right; + } + + public void setLeft(TreeI left) { + this.left = left; + } + public void setRight(TreeI right) { + this.right = right; + } + + public boolean equals(Object other) { + if (other instanceof TreeV tree) { + boolean leftEq = (this.left == null && tree.left == null) || + left.equals(tree.left); + boolean rightEq = (this.right == null && tree.right == null) || + right.equals(tree.right); + return leftEq == rightEq; + } + return false; + } + public String toString() { + return toString(5); + } + public String toString(int depth) { + if (depth <= 0) + return "!"; + String l = (left != null) ? left.toString(depth - 1) : Character.toString(126); + String r = (right != null) ? right.toString(depth - 1) : Character.toString(126); + return "(" + l + r + ")"; + } + } + + @MigratedValueClass + static value class TreeV implements Tree, Serializable { + + private static final long serialVersionUID = 2L; + private TreeV left; + private TreeV right; + + @DeserializeConstructor + TreeV(TreeV left, TreeV right) { + this.left = left; + this.right = right; + } + + public TreeV left() { + return left; + } + public TreeV right() { + return right; + } + + public boolean equals(Object other) { + // avoid ==, is substutible check causes stack overflow. + if (other instanceof TreeV tree) { + return compRef(this.left, tree.left) && compRef(this.right, tree.right); + } + return false; + } + + // Compare references but don't use ==; isSubstitutable may recurse + private static boolean compRef(Object o1, Object o2) { + if (o1 == null && o2 == null) + return true; + if (o1 != null && o2 != null) + return o1.equals(o2); + return false; + + } + public String toString() { + return toString(10); + } + public String toString(int depth) { + if (depth <= 0) + return "!"; + String l = (left != null) ? left.toString(depth - 1) : Character.toString(126); + String r = (right != null) ? right.toString(depth - 1) : Character.toString(126); + return "(" + l + r + ")"; + } + } + + @Test + void testExternalizableNotSer() { + var obj = new ValueExt(); + var ex = Assert.expectThrows(InvalidClassException.class, () -> serialize(obj)); + Assert.assertEquals(ex.getMessage(), + "SimpleValueGraphs$ValueExt; Externalizable not valid for value class"); + } + + @Test + void testExternalizableNotDeser() throws IOException { + var obj = new IdentExt(); + byte[] bytes = serialize(obj); + byte[] newBytes = patchBytes(bytes, "IdentExt", "ValueExt"); + var ex = Assert.expectThrows(InvalidClassException.class, () -> deserialize(newBytes)); + Assert.assertTrue(ex.getMessage().contains("Externalizable not valid for value class")); + } + + // Exception trying to serialize + // Exception trying to deserialize + + static class IdentExt implements Externalizable { + public void writeExternal(ObjectOutput is) { + + } + public void readExternal(ObjectInput is) { + + } + private static final long serialVersionUID = 3L; + } + + // Not Desrializable or Deserializable, no writeable fields + static value class ValueExt implements Externalizable { + public void writeExternal(ObjectOutput is) { + + } + public void readExternal(ObjectInput is) { + + } + private static final long serialVersionUID = 3L; + + } +} diff --git a/test/jdk/java/io/Serializable/valueObjects/ValueSerializationTest.java b/test/jdk/java/io/Serializable/valueObjects/ValueSerializationTest.java new file mode 100644 index 00000000000..f6ea5f2d847 --- /dev/null +++ b/test/jdk/java/io/Serializable/valueObjects/ValueSerializationTest.java @@ -0,0 +1,277 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Test serialization of value classes + * @enablePreview + * @modules java.base/jdk.internal java.base/jdk.internal.value + * @compile ValueSerializationTest.java + * @run testng/othervm ValueSerializationTest + */ + +import static java.io.ObjectStreamConstants.*; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.Externalizable; +import java.io.IOException; +import java.io.InvalidClassException; +import java.io.InvalidObjectException; +import java.io.NotSerializableException; +import java.io.ObjectInput; +import java.io.ObjectInputStream; +import java.io.ObjectOutput; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; +import java.io.ObjectStreamException; +import java.io.Serial; +import java.io.Serializable; + +import jdk.internal.MigratedValueClass; +import jdk.internal.value.DeserializeConstructor; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertThrows; + +public class ValueSerializationTest { + + static final Class NSE = NotSerializableException.class; + private static final Class ICE = InvalidClassException.class; + + @DataProvider(name = "doesNotImplementSerializable") + public Object[][] doesNotImplementSerializable() { + return new Object[][] { + new Object[] { new NonSerializablePoint(10, 100), NSE}, + new Object[] { new NonSerializablePointNoCons(10, 100), ICE}, + // an array of Points + new Object[] { new NonSerializablePoint[] {new NonSerializablePoint(1, 5)}, NSE}, + new Object[] { new Object[] {new NonSerializablePoint(3, 7)}, NSE}, + new Object[] { new ExternalizablePoint(12, 102), ICE}, + new Object[] { new ExternalizablePoint[] { + new ExternalizablePoint(3, 7), + new ExternalizablePoint(2, 8) }, ICE}, + new Object[] { new Object[] { + new ExternalizablePoint(13, 17), + new ExternalizablePoint(14, 18) }, ICE}, + }; + } + + // value class that DOES NOT implement Serializable should throw ICE + @Test(dataProvider = "doesNotImplementSerializable") + public void doesNotImplementSerializable(Object obj, Class expectedException) { + assertThrows(expectedException, () -> serialize(obj)); + } + + /* Non-Serializable point. */ + public static value class NonSerializablePoint { + public int x; + public int y; + + public NonSerializablePoint(int x, int y) { + this.x = x; + this.y = y; + } + @Override public String toString() { + return "[NonSerializablePoint x=" + x + " y=" + y + "]"; + } + } + + /* Non-Serializable point, because it does not have an @DeserializeConstructor constructor. */ + public static value class NonSerializablePointNoCons implements Serializable { + public int x; + public int y; + + // Note: Must NOT have @DeserializeConstructor annotation + public NonSerializablePointNoCons(int x, int y) { + this.x = x; + this.y = y; + } + @Override public String toString() { + return "[NonSerializablePointNoCons x=" + x + " y=" + y + "]"; + } + } + + /* An Externalizable Point is not Serializable, readExternal cannot modify fields */ + static value class ExternalizablePoint implements Externalizable { + public int x; + public int y; + public ExternalizablePoint() {this.x = 0; this.y = 0;} + ExternalizablePoint(int x, int y) { this.x = x; this.y = y; } + @Override public void readExternal(ObjectInput in) { } + @Override public void writeExternal(ObjectOutput out) { } + @Override public String toString() { + return "[ExternalizablePoint x=" + x + " y=" + y + "]"; } + } + + @DataProvider(name = "ImplementSerializable") + public Object[][] implementSerializable() { + return new Object[][]{ + new Object[]{new SerializablePoint(11, 101)}, + new Object[]{new SerializablePoint[]{ + new SerializablePoint(1, 5), + new SerializablePoint(2, 6)}}, + new Object[]{new Object[]{ + new SerializablePoint(3, 7), + new SerializablePoint(4, 8)}}, + new Object[]{new SerializableFoo(45)}, + new Object[]{new SerializableFoo[]{new SerializableFoo(46)}}, + new Object[]{new ExternalizableFoo("hello")}, + new Object[]{new ExternalizableFoo[]{new ExternalizableFoo("there")}}, + }; + } + + // value class that DOES implement Serializable is supported + @Test(dataProvider = "ImplementSerializable") + public void implementSerializable(Object obj) throws IOException, ClassNotFoundException { + byte[] bytes = serialize(obj); + Object actual = deserialize(bytes); + if (obj.getClass().isArray()) + assertEquals((Object[])obj, (Object[])actual); + else + assertEquals(obj, actual); + } + + /* A Serializable value class Point */ + @MigratedValueClass + static value class SerializablePoint implements Serializable { + public int x; + public int y; + @DeserializeConstructor + private SerializablePoint(int x, int y) { this.x = x; this.y = y; } + + @Override public String toString() { + return "[SerializablePoint x=" + x + " y=" + y + "]"; + } + } + + /* A Serializable Foo, with a serial proxy */ + static value class SerializableFoo implements Serializable { + public int x; + @DeserializeConstructor + SerializableFoo(int x) { this.x = x; } + + @Serial Object writeReplace() throws ObjectStreamException { + return new SerialFooProxy(x); + } + @Serial private void readObject(ObjectInputStream s) throws InvalidObjectException { + throw new InvalidObjectException("Proxy required"); + } + private record SerialFooProxy(int x) implements Serializable { + @Serial Object readResolve() throws ObjectStreamException { + return new SerializableFoo(x); + } + } + } + + /* An Externalizable Foo, with a serial proxy */ + static value class ExternalizableFoo implements Externalizable { + public String s; + ExternalizableFoo(String s) { this.s = s; } + public boolean equals(Object other) { + if (other instanceof ExternalizableFoo foo) { + return s.equals(foo.s); + } else { + return false; + } + } + @Serial Object writeReplace() throws ObjectStreamException { + return new SerialFooProxy(s); + } + private record SerialFooProxy(String s) implements Serializable { + @Serial Object readResolve() throws ObjectStreamException { + return new ExternalizableFoo(s); + } + } + @Override public void readExternal(ObjectInput in) { } + @Override public void writeExternal(ObjectOutput out) { } + } + + // Generate a byte stream containing a reference to the named class with the SVID and flags. + private static byte[] byteStreamFor(String className, long uid, byte flags) + throws Exception + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(baos); + dos.writeShort(STREAM_MAGIC); + dos.writeShort(STREAM_VERSION); + dos.writeByte(TC_OBJECT); + dos.writeByte(TC_CLASSDESC); + dos.writeUTF(className); + dos.writeLong(uid); + dos.writeByte(flags); + dos.writeShort(0); // number of fields + dos.writeByte(TC_ENDBLOCKDATA); // no annotations + dos.writeByte(TC_NULL); // no superclasses + dos.close(); + return baos.toByteArray(); + } + + @DataProvider(name = "classes") + public Object[][] classes() { + return new Object[][] { + new Object[] { ExternalizableFoo.class, SC_EXTERNALIZABLE, ICE }, + new Object[] { ExternalizableFoo.class, SC_SERIALIZABLE, ICE }, + new Object[] { SerializablePoint.class, SC_EXTERNALIZABLE, ICE }, + new Object[] { SerializablePoint.class, SC_SERIALIZABLE, null }, + }; + } + + // value class read directly from a byte stream + // a byte stream is generated containing a reference to the class with the flags and SVID. + // Reading the class from the stream verifies the exceptions thrown if there is a mismatch + // between the stream and the local class. + @Test(dataProvider = "classes") + public void deserialize(Class cls, byte flags, Class expected) throws Exception { + var clsDesc = ObjectStreamClass.lookup(cls); + long uid = clsDesc == null ? 0L : clsDesc.getSerialVersionUID(); + byte[] serialBytes = byteStreamFor(cls.getName(), uid, flags); + try { + deserialize(serialBytes); + Assert.assertNull(expected, "Expected exception"); + } catch (IOException ioe) { + Assert.assertEquals(ioe.getClass(), expected); + } + } + + static byte[] serialize(T obj) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(obj); + oos.close(); + return baos.toByteArray(); + } + + @SuppressWarnings("unchecked") + static T deserialize(byte[] streamBytes) + throws IOException, ClassNotFoundException + { + ByteArrayInputStream bais = new ByteArrayInputStream(streamBytes); + ObjectInputStream ois = new ObjectInputStream(bais); + return (T) ois.readObject(); + } +} diff --git a/test/jdk/java/lang/Class/GenericStringTest.java b/test/jdk/java/lang/Class/GenericStringTest.java index 71f39f60228..73a3ffed5ad 100644 --- a/test/jdk/java/lang/Class/GenericStringTest.java +++ b/test/jdk/java/lang/Class/GenericStringTest.java @@ -25,6 +25,9 @@ * @test * @bug 6298888 6992705 8161500 6304578 8322878 * @summary Check Class.toGenericString() + * @enablePreview + * @compile GenericStringTest.java + * @run main/othervm GenericStringTest */ import java.lang.reflect.*; @@ -86,6 +89,7 @@ public static void main(String... args) throws ReflectiveOperationException { LocalMap.class, AnEnum.class, AnotherEnum.class, + AValueClass.class, SealedRootClass.class, SealedRootClass.ChildA.class, @@ -156,6 +160,9 @@ enum AnotherEnum { BAR{}; } +@ExpectedGenericString("final value class AValueClass") +value class AValueClass {} + // Test cases for sealed/non-sealed _class_ hierarchy. @ExpectedGenericString("sealed class SealedRootClass") sealed class SealedRootClass diff --git a/test/jdk/java/lang/Class/getModifiers/TestPrimitiveAndArrayModifiers.java b/test/jdk/java/lang/Class/getModifiers/TestPrimitiveAndArrayModifiers.java index 5efa0b3f0d8..099b8521cdd 100644 --- a/test/jdk/java/lang/Class/getModifiers/TestPrimitiveAndArrayModifiers.java +++ b/test/jdk/java/lang/Class/getModifiers/TestPrimitiveAndArrayModifiers.java @@ -21,13 +21,18 @@ * questions. */ +import jdk.internal.misc.PreviewFeatures; + import java.lang.reflect.Modifier; import java.lang.annotation.*; /* * @test * @bug 8296743 + * @modules java.base/jdk.internal.misc * @summary Verify array classes and primitives have expected modifiers + * @run main/othervm TestPrimitiveAndArrayModifiers + * @run main/othervm --enable-preview TestPrimitiveAndArrayModifiers */ @ExpectedModifiers(Modifier.PUBLIC | Modifier.FINAL | Modifier.ABSTRACT) public class TestPrimitiveAndArrayModifiers { @@ -67,11 +72,15 @@ private static void testArrays() { for(var testCase : testCases) { int expectedModifiers = testCase.getAnnotation(ExpectedModifiers.class).value(); + if (PreviewFeatures.isEnabled()) { + // All arrays under preview also have IDENTITY + expectedModifiers |= Modifier.IDENTITY; + } Class arrayClass = testCase.arrayType(); int actualModifiers = arrayClass.getModifiers(); if (expectedModifiers != actualModifiers) { throw new RuntimeException("Expected " + Modifier.toString(expectedModifiers) + - "on " + testCase.getCanonicalName() + + " on " + testCase.getCanonicalName() + ", but got " + Modifier.toString(actualModifiers)); } } diff --git a/test/jdk/java/lang/Class/getSimpleName/GetSimpleNameTest.java b/test/jdk/java/lang/Class/getSimpleName/GetSimpleNameTest.java index 2f48f2b1216..ce7e10a49e5 100644 --- a/test/jdk/java/lang/Class/getSimpleName/GetSimpleNameTest.java +++ b/test/jdk/java/lang/Class/getSimpleName/GetSimpleNameTest.java @@ -23,6 +23,7 @@ /* @test * @bug 8057919 + * @enablePreview * @summary Class.getSimpleName() should work for non-JLS compliant class names */ @@ -166,7 +167,7 @@ byte[] getNestedClasses(boolean isInner) { var name = (isInner ? innerName : outerName); return ClassFile.of().build(name, clb -> { clb.withSuperclass(CD_Object); - clb.withFlags(AccessFlag.PUBLIC, AccessFlag.SUPER); + clb.withFlags(AccessFlag.PUBLIC, AccessFlag.IDENTITY); clb.with(InnerClassesAttribute.of( InnerClassInfo.of(innerName, Optional.of(outerName), @@ -179,7 +180,7 @@ byte[] getInnerClasses(boolean isInner) { var name = (isInner ? innerName : outerName); return ClassFile.of().build(name, clb -> { clb.withSuperclass(CD_Object); - clb.withFlags(AccessFlag.PUBLIC, AccessFlag.SUPER); + clb.withFlags(AccessFlag.PUBLIC, AccessFlag.IDENTITY); clb.with(InnerClassesAttribute.of( InnerClassInfo.of(innerName, Optional.of(outerName), @@ -193,7 +194,7 @@ byte[] getLocalClasses(boolean isInner) { var name = (isInner ? innerName : outerName); return ClassFile.of().build(name, clb -> { clb.withSuperclass(CD_Object); - clb.withFlags(AccessFlag.PUBLIC, AccessFlag.SUPER); + clb.withFlags(AccessFlag.PUBLIC, AccessFlag.IDENTITY); clb.with(InnerClassesAttribute.of( InnerClassInfo.of(innerName, Optional.empty(), @@ -208,7 +209,7 @@ byte[] getAnonymousClasses(boolean isInner) { var name = (isInner ? innerName : outerName); return ClassFile.of().build(name, clb -> { clb.withSuperclass(CD_Object); - clb.withFlags(AccessFlag.PUBLIC, AccessFlag.SUPER); + clb.withFlags(AccessFlag.PUBLIC, AccessFlag.IDENTITY); clb.with(InnerClassesAttribute.of( InnerClassInfo.of(innerName, Optional.empty(), diff --git a/test/jdk/java/lang/ModuleTests/AnnotationsTest.java b/test/jdk/java/lang/ModuleTests/AnnotationsTest.java index 5ccdecd8734..44cbbdca321 100644 --- a/test/jdk/java/lang/ModuleTests/AnnotationsTest.java +++ b/test/jdk/java/lang/ModuleTests/AnnotationsTest.java @@ -51,7 +51,6 @@ * @test * @modules java.base/jdk.internal.module * @library /test/lib - * @build jdk.test.lib.util.ModuleInfoWriter * @run testng AnnotationsTest * @summary Basic test of annotations on modules */ diff --git a/test/jdk/java/lang/ProcessBuilder/Basic.java b/test/jdk/java/lang/ProcessBuilder/Basic.java index c06ecbdb871..49a9bd621e7 100644 --- a/test/jdk/java/lang/ProcessBuilder/Basic.java +++ b/test/jdk/java/lang/ProcessBuilder/Basic.java @@ -28,7 +28,7 @@ * 6464154 6523983 6206031 4960438 6631352 6631966 6850957 6850958 * 4947220 7018606 7034570 4244896 5049299 8003488 8054494 8058464 * 8067796 8224905 8263729 8265173 8272600 8231297 8282219 8285517 - * 8352533 + * 8352533 8368192 * @key intermittent * @summary Basic tests for Process and Environment Variable code * @modules java.base/java.lang:open @@ -777,30 +777,29 @@ private static boolean matches(String str, String regex) { return Pattern.compile(regex).matcher(str).find(); } - private static String matchAndExtract(String str, String regex) { - Matcher matcher = Pattern.compile(regex).matcher(str); - if (matcher.find()) { - return matcher.group(); - } else { - return ""; - } + // Return the string with the matching regex removed + private static String matchAndRemove(String str, String regex) { + return Pattern.compile(regex) + .matcher(str) + .replaceAll(""); } /* Only used for Mac OS X -- - * Mac OS X (may) add the variable __CF_USER_TEXT_ENCODING to an empty - * environment. The environment variable JAVA_MAIN_CLASS_ may also - * be set in Mac OS X. - * Remove them both from the list of env variables + * Mac OS X (may) add the variables: __CF_USER_TEXT_ENCODING, JAVA_MAIN_CLASS_, + * and TMPDIR. + * Remove them from the list of env variables */ private static String removeMacExpectedVars(String vars) { // Check for __CF_USER_TEXT_ENCODING - String cleanedVars = vars.replace("__CF_USER_TEXT_ENCODING=" - +cfUserTextEncoding+",",""); + String cleanedVars = matchAndRemove(vars, + "__CF_USER_TEXT_ENCODING=" + cfUserTextEncoding + ","); // Check for JAVA_MAIN_CLASS_ - String javaMainClassStr - = matchAndExtract(cleanedVars, - "JAVA_MAIN_CLASS_\\d+=Basic.JavaChild,"); - return cleanedVars.replace(javaMainClassStr,""); + cleanedVars = matchAndRemove(cleanedVars, + "JAVA_MAIN_CLASS_\\d+=Basic.JavaChild,"); + // Check and remove TMPDIR + cleanedVars = matchAndRemove(cleanedVars, + "TMPDIR=[^,]*,"); + return cleanedVars; } /* Only used for AIX -- diff --git a/test/jdk/java/lang/instrument/RetransformApp.java b/test/jdk/java/lang/instrument/RetransformApp.java index a026952b29f..f6db7ebe7ae 100644 --- a/test/jdk/java/lang/instrument/RetransformApp.java +++ b/test/jdk/java/lang/instrument/RetransformApp.java @@ -36,6 +36,7 @@ * * @modules java.instrument * @library /test/lib + * @build jdk.test.lib.process.ProcessTools * @build RetransformAgent asmlib.Instrumentor * @run driver/timeout=240 RetransformApp roleDriver * @comment The test uses a higher timeout to prevent test timeouts noted in JDK-6528548 diff --git a/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/Host/Host.java b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/Host/Host.java new file mode 100644 index 00000000000..db63360a8fa --- /dev/null +++ b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/Host/Host.java @@ -0,0 +1,31 @@ +/* + * 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. + */ + +public value class Host { + public static String getID() { return "Host/Host.java";} + public int m() { + return 1; // original class + } + public Host(int A, long B, char C) { + } +} diff --git a/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/Host/redef/Host.java b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/Host/redef/Host.java new file mode 100644 index 00000000000..427052398a0 --- /dev/null +++ b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/Host/redef/Host.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. + * + * 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. + */ + +public value class Host { + public static String getID() { return "Host/redef/Host.java";} + public int m() { + return 2; // redefined class + } + public Host(int A, long B, char C) { + } + +} diff --git a/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostA/Host.java b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostA/Host.java new file mode 100644 index 00000000000..823289c6360 --- /dev/null +++ b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostA/Host.java @@ -0,0 +1,33 @@ +/* + * 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. + */ + +public value class Host { + int A; + public static String getID() { return "HostA/Host.java";} + public int m() { + return 1; // original class + } + public Host(int A, long B, char C) { + this.A = A; + } +} diff --git a/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostA/redef/Host.java b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostA/redef/Host.java new file mode 100644 index 00000000000..a5c81e23c7e --- /dev/null +++ b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostA/redef/Host.java @@ -0,0 +1,33 @@ +/* + * 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. + */ + +public value class Host { + int A; + public static String getID() { return "HostA/redef/Host.java"; } + public int m() { + return 2; // redefined class + } + public Host(int A, long B, char C) { + this.A = A; + } +} diff --git a/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostAB/Host.java b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostAB/Host.java new file mode 100644 index 00000000000..6bfbfe20930 --- /dev/null +++ b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostAB/Host.java @@ -0,0 +1,35 @@ +/* + * 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. + */ + +public value class Host { + int A; + long B; + public static String getID() { return "HostAB/Host.java";} + public int m() { + return 1; // original class + } + public Host(int A, long B, char C) { + this.A = A; + this.B = B; + } +} diff --git a/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostAB/redef/Host.java b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostAB/redef/Host.java new file mode 100644 index 00000000000..a40ad4f4e91 --- /dev/null +++ b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostAB/redef/Host.java @@ -0,0 +1,35 @@ +/* + * 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. + */ + +public value class Host { + int A; + long B; + public static String getID() { return "HostAB/redef/Host.java"; } + public int m() { + return 2; // redefined class + } + public Host(int A, long B, char C) { + this.A = A; + this.B = B; + } +} diff --git a/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostABC/redef/Host.java b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostABC/redef/Host.java new file mode 100644 index 00000000000..3d232a851ed --- /dev/null +++ b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostABC/redef/Host.java @@ -0,0 +1,37 @@ +/* + * 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. + */ + +public value class Host { + int A; + long B; + char C; + public static String getID() { return "HostABC/redef/Host.java"; } + public int m() { + return 2; // redefined class + } + public Host(int A, long B, char C) { + this.A = A; + this.B = B; + this.C = C; + } +} diff --git a/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostAC/redef/Host.java b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostAC/redef/Host.java new file mode 100644 index 00000000000..63fea1bb106 --- /dev/null +++ b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostAC/redef/Host.java @@ -0,0 +1,35 @@ +/* + * 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. + */ + +public value class Host { + int A; + char C; + public static String getID() { return "HostAC/redef/Host.java"; } + public int m() { + return 2; // redefined class + } + public Host(int A, long B, char C) { + this.A = A; + this.C = C; + } +} diff --git a/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostB/redef/Host.java b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostB/redef/Host.java new file mode 100644 index 00000000000..af14d6d9e6d --- /dev/null +++ b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostB/redef/Host.java @@ -0,0 +1,33 @@ +/* + * 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. + */ + +public value class Host { + long B; + public static String getID() { return "HostB/redef/Host.java"; } + public int m() { + return 2; // redefined class + } + public Host(int A, long B, char C) { + this.B = B; + } +} diff --git a/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostBA/redef/Host.java b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostBA/redef/Host.java new file mode 100644 index 00000000000..bda7a69a18c --- /dev/null +++ b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostBA/redef/Host.java @@ -0,0 +1,35 @@ +/* + * 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. + */ + +public value class Host { + long B; + int A; + public static String getID() { return "HostBA/redef/Host.java"; } + public int m() { + return 2; // redefined class + } + public Host(int A, long B, char C) { + this.B = B; + this.A = A; + } +} diff --git a/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostI/Host.java b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostI/Host.java new file mode 100644 index 00000000000..1623664dca3 --- /dev/null +++ b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostI/Host.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. + * + * 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. + */ + +public class Host { // this is instance (not value) class + public static String getID() { return "HostI/Host.java";} + public int m() { + return 1; // original class + } + public Host(int A, long B, char C) { + } + +} diff --git a/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostI/redef/Host.java b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostI/redef/Host.java new file mode 100644 index 00000000000..40942b9408f --- /dev/null +++ b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/HostI/redef/Host.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. + * + * 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. + */ + +public class Host { // this is instance (not value) class + public static String getID() { return "Host/redef/Host.java";} + public int m() { + return 2; // redefined class + } + public Host(int A, long B, char C) { + } + +} diff --git a/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/RedefineValueClass.java b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/RedefineValueClass.java new file mode 100644 index 00000000000..73702e4610f --- /dev/null +++ b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/RedefineValueClass.java @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Test for value class redefinition + * @comment The code is based on test/jdk/java/lang/instrument/RedefineNestmateAttr + * @comment modified for value classes. + * + * @library /test/lib + * @modules java.compiler + * java.instrument + * @enablePreview + * @run main RedefineClassHelper + * @compile Host/Host.java + * @run main/othervm -javaagent:redefineagent.jar -Xlog:redefine+class*=trace RedefineValueClass Host + * @compile HostA/Host.java + * @run main/othervm -javaagent:redefineagent.jar -Xlog:redefine+class*=trace RedefineValueClass HostA + * @compile HostAB/Host.java + * @run main/othervm -javaagent:redefineagent.jar -Xlog:redefine+class*=trace RedefineValueClass HostAB + * @compile HostI/Host.java + * @run main/othervm -javaagent:redefineagent.jar -Xlog:redefine+class*=trace RedefineValueClass HostI + */ + +/* Test Description + +The basic test class is called Host. +Each variant of the class is defined in source code in its own directory i.e. + +Host/Host.java defines zero fields +Class HostA/Host.java has field "int A" +Class HostAB/Host.java has fields "int A" and "long B" (in that order) +Class HostI/Host.java is an instance class with zero fields +etc. + +Each Host class has the form: + + public value class Host { + // fields here + public static String getID() { return "/Host.java"; } + + public int m() { + return 1; // original class + } + + public Host(int A, long B, char C) { + ... + } + } + +The only exception is class in HostI dir which is instance class. + +Under each directory is a directory "redef" with a modified version of the Host +class that changes the ID to e.g. Host/redef/Host.java, and the method m() +returns 2. This allows us to check we have the redefined class loaded. + +Using Host' to represent the redefined version we test different redefinition combinations. + +We can only directly load one class Host per classloader, so to run all the +groups we either need to use new classloaders, or we reinvoke the test +requesting a different primary directory. We chose the latter using +multiple @run tags. So we preceed as follows: + + @compile Host/Host.java + @run RedefineValueClass Host + @compile HostA/Host.java - replaces previous Host.class + @run RedefineValueClass HostA + @compile HostAB/Host.java - replaces previous Host.class + @run RedefineValueClass HostAB +etc. + +Within the test we directly compile redefined versions of the classes, +using CompilerUtil, and then read the .class file directly as a byte[] +to use with the RedefineClassHelper. +*/ + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import jdk.test.lib.compiler.CompilerUtils; +import static jdk.test.lib.Asserts.assertTrue; + +public class RedefineValueClass { + + static final Path SRC = Paths.get(System.getProperty("test.src")); + static final Path DEST = Paths.get(System.getProperty("test.classes")); + + public static void main(String[] args) throws Throwable { + String origin = args[0]; + System.out.println("Testing original Host class from " + origin); + + // Make sure the Host class loaded directly is an original version + // and from the expected location. Use a ctor common to all Host + // classes. + Host h = new Host(3, 4, 'a'); + assertTrue(h.m() == 1); + assertTrue(Host.getID().startsWith(origin + "/")); + + String[] badTransforms = null; // directories of bad classes + String[] goodTransforms = null; // directories of good classes + + switch (origin) { + case "Host": + badTransforms = new String[] { + "HostI", // value class to instance class + "HostA" // add field + }; + goodTransforms = new String[] { + origin + }; + break; + + case "HostA": + badTransforms = new String[] { + "Host", // remove field + "HostAB", // add field + "HostB" // change field + }; + goodTransforms = new String[] { + origin + }; + break; + + case "HostAB": + badTransforms = new String[] { + "HostA", // remove field + "HostABC", // add field + "HostAC", // change fields + "HostBA" // reorder fields + }; + goodTransforms = new String[] { + origin, + }; + break; + + case "HostI": // instance class + badTransforms = new String[] { + "Host", // instance class to value class + }; + break; + + default: + throw new RuntimeException("Unknown test directory: " + origin); + } + + // Compile and check bad transformations + checkBadTransforms(Host.class, badTransforms); + + // Compile and check good transformations + if (goodTransforms != null) { + checkGoodTransforms(Host.class, goodTransforms); + } + } + + static void checkGoodTransforms(Class c, String[] dirs) throws Throwable { + for (String dir : dirs) { + dir += "/redef"; + System.out.println("Trying good retransform from " + dir); + byte[] buf = bytesForHostClass(dir); + RedefineClassHelper.redefineClass(c, buf); + + // Test redefintion worked + Host h = new Host(3, 4, 'a'); + assertTrue(h.m() == 2); + System.out.println("Redefined ID: " + Host.getID()); + assertTrue(Host.getID().startsWith(dir)); + } + } + + static void checkBadTransforms(Class c, String[] dirs) throws Throwable { + for (String dir : dirs) { + dir += "/redef"; + System.out.println("Trying bad retransform from " + dir); + byte[] buf = bytesForHostClass(dir); + try { + RedefineClassHelper.redefineClass(c, buf); + throw new RuntimeException("Retransformation from directory " + dir + + " succeeded unexpectedly"); + } + catch (UnsupportedOperationException uoe) { + System.out.println("Got expected UnsupportedOperationException " + uoe); + } + } + } + + static byte[] bytesForHostClass(String dir) throws Throwable { + compile(dir); + Path clsfile = DEST.resolve(dir).resolve("Host.class"); + System.out.println("Reading bytes from " + clsfile); + return Files.readAllBytes(clsfile); + } + + static void compile(String dir) throws Throwable { + Path src = SRC.resolve(dir); + Path dst = DEST.resolve(dir); + System.out.println("Compiling from: " + src + "\n" + + " to: " + dst); + CompilerUtils.compile(src, dst, + false /* don't recurse */, + "--enable-preview", + "--source", String.valueOf(Runtime.version().feature())); + } +} diff --git a/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/_HostACB/redef/Host.java b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/_HostACB/redef/Host.java new file mode 100644 index 00000000000..b0a90c9b945 --- /dev/null +++ b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/_HostACB/redef/Host.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * 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. + */ + +public record Host(int A, char C, long B) { + public static String getID() { return "HostACB/redef/Host.java"; } + public int m() { + return 2; // redefined class + } + public Host(int A, long B, char C) { + this(A, C, B); + } +} diff --git a/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/_HostBAC/redef/Host.java b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/_HostBAC/redef/Host.java new file mode 100644 index 00000000000..7d84e8697c2 --- /dev/null +++ b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/_HostBAC/redef/Host.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * 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. + */ + +public record Host(long B, int A, char C) { + public static String getID() { return "HostBAC/redef/Host.java"; } + public int m() { + return 2; // redefined class + } + public Host(int A, long B, char C) { + this(B, A, C); + } +} diff --git a/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/_HostBCA/redef/Host.java b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/_HostBCA/redef/Host.java new file mode 100644 index 00000000000..f06856de07f --- /dev/null +++ b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/_HostBCA/redef/Host.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * 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. + */ + +public record Host(long B, char C, int A) { + public static String getID() { return "HostBCA/redef/Host.java"; } + public int m() { + return 2; // redefined class + } + public Host(int A, long B, char C) { + this(B, C, A); + } +} diff --git a/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/_HostCAB/redef/Host.java b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/_HostCAB/redef/Host.java new file mode 100644 index 00000000000..4898a7612ba --- /dev/null +++ b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/_HostCAB/redef/Host.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * 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. + */ + +public record Host(char C, int A, long B) { + public static String getID() { return "HostCAB/redef/Host.java"; } + public int m() { + return 2; // redefined class + } + public Host(int A, long B, char C) { + this(C, A, B); + } +} diff --git a/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/_HostCBA/redef/Host.java b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/_HostCBA/redef/Host.java new file mode 100644 index 00000000000..9f9fb307115 --- /dev/null +++ b/test/jdk/java/lang/instrument/valhalla/RedefineValueClass/_HostCBA/redef/Host.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * 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. + */ + +public record Host(char C, long B, int A) { + public static String getID() { return "HostCBA/redef/Host.java"; } + public int m() { + return 2; // redefined class + } + public Host(int A, long B, char C) { + this(C, B, A); + } +} diff --git a/test/jdk/java/lang/instrument/valhalla/RetransformValueClass.java b/test/jdk/java/lang/instrument/valhalla/RetransformValueClass.java new file mode 100644 index 00000000000..f96509320d4 --- /dev/null +++ b/test/jdk/java/lang/instrument/valhalla/RetransformValueClass.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Test for value class retransformation + * + * @library /test/lib + * @modules java.instrument + * @enablePreview + * + * @run main RetransformValueClass buildAgent + * + * @run main/othervm -javaagent:testAgent.jar RetransformValueClass + */ + +import java.lang.instrument.Instrumentation; +import java.lang.instrument.ClassFileTransformer; +import java.security.ProtectionDomain; + +import jdk.test.lib.helpers.ClassFileInstaller; + +/* + * The test verifies Instrumentation.retransformClasses() (and JVMTI function RetransformClasses) + * works with value classes (i.e. JVMTI JvmtiClassFileReconstituter correctly restores class bytes). + */ + +value class ValueClass { + public int f1; + public String f2; + + public ValueClass(int v1, String v2) { + f1 = v1; + f2 = v2; + } +} + +public class RetransformValueClass { + + public static void main (String[] args) throws Exception { + if (args.length == 1 && "buildAgent".equals(args[0])) { + buildAgent(); + } else { + runTest(); + } + } + + static void buildAgent() throws Exception { + String manifest = "Premain-Class: RetransformValueClass\nCan-Redefine-Classes: true\nCan-Retransform-Classes: true\n"; + ClassFileInstaller.writeJar("testAgent.jar", ClassFileInstaller.Manifest.fromString(manifest), "RetransformValueClass"); + } + + // agent implementation + static Instrumentation instrumentation; + public static void premain(String agentArgs, Instrumentation inst) { + instrumentation = inst; + } + + // test implementation + static final Class targetClass = ValueClass.class; + static final String targetClassName = targetClass.getName(); + static boolean transformToOriginalClassbytes = false; + + static void runTest() throws Exception { + instrumentation.addTransformer(new Transformer(), true); + + instrumentation.retransformClasses(targetClass); + + transformToOriginalClassbytes = true; + instrumentation.retransformClasses(targetClass); + } + + + static class Transformer implements ClassFileTransformer { + public Transformer() { + } + + public byte[] transform(ClassLoader loader, String className, + Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { + + if (className.equals(targetClassName)) { + log("Transformer sees '" + className + "' of " + classfileBuffer.length + " bytes."); + if (transformToOriginalClassbytes) { + return classfileBuffer; + } else { + return null; + } + } + return null; + } + } + + static void log(Object o) { + System.out.println(String.valueOf(o)); + } +} diff --git a/test/jdk/java/lang/invoke/DefineClassTest.java b/test/jdk/java/lang/invoke/DefineClassTest.java index f712c945e59..09a68ef71bc 100644 --- a/test/jdk/java/lang/invoke/DefineClassTest.java +++ b/test/jdk/java/lang/invoke/DefineClassTest.java @@ -22,6 +22,7 @@ */ /* @test + * @enablePreview * @modules java.base/java.lang:open * @run testng/othervm test.DefineClassTest * @summary Basic test for java.lang.invoke.MethodHandles.Lookup.defineClass @@ -258,7 +259,7 @@ public void testModuleInfo() throws Exception { */ byte[] generateClass(String className) { return ClassFile.of().build(ClassDesc.of(className), clb -> { - clb.withFlags(AccessFlag.PUBLIC, AccessFlag.SUPER); + clb.withFlags(AccessFlag.PUBLIC, AccessFlag.IDENTITY); clb.withSuperclass(CD_Object); clb.withMethodBody(INIT_NAME, MTD_void, PUBLIC, cob -> { cob.aload(0); @@ -300,7 +301,7 @@ byte[] generateClassWithInitializer(String className, String targetMethod) throws Exception { return ClassFile.of().build(ClassDesc.of(className), clb -> { - clb.withFlags(AccessFlag.PUBLIC, AccessFlag.SUPER); + clb.withFlags(AccessFlag.PUBLIC, AccessFlag.IDENTITY); clb.withSuperclass(CD_Object); clb.withMethodBody(INIT_NAME, MTD_void, ACC_PUBLIC, cob -> { cob.aload(0); @@ -319,7 +320,7 @@ byte[] generateClassWithInitializer(String className, */ byte[] generateNonLinkableClass(String className) { return ClassFile.of().build(ClassDesc.of(className), clb -> { - clb.withFlags(AccessFlag.PUBLIC, AccessFlag.SUPER); + clb.withFlags(AccessFlag.PUBLIC, AccessFlag.IDENTITY); clb.withSuperclass(CD_MissingSuperClass); clb.withMethodBody(INIT_NAME, MTD_void, ACC_PUBLIC, cob -> { cob.aload(0); diff --git a/test/jdk/java/lang/invoke/MethodHandleProxies/WrapperHiddenClassTest.java b/test/jdk/java/lang/invoke/MethodHandleProxies/WrapperHiddenClassTest.java index 81b832860c2..de563e58c65 100644 --- a/test/jdk/java/lang/invoke/MethodHandleProxies/WrapperHiddenClassTest.java +++ b/test/jdk/java/lang/invoke/MethodHandleProxies/WrapperHiddenClassTest.java @@ -40,12 +40,16 @@ import static java.lang.invoke.MethodHandleProxies.*; import static java.lang.invoke.MethodType.methodType; import static java.lang.classfile.ClassFile.*; + +import jdk.internal.misc.PreviewFeatures; + import static org.junit.jupiter.api.Assertions.*; /* * @test * @bug 6983726 * @library /test/lib + * @modules java.base/jdk.internal.misc * @summary Tests on implementation hidden classes spinned by MethodHandleProxies * @build WrapperHiddenClassTest Client jdk.test.lib.util.ForceGC * @run junit WrapperHiddenClassTest @@ -85,7 +89,7 @@ private Comparator createHostileInstance() throws Throwable { var cf = ClassFile.of(); var bytes = cf.build(CD_HostileWrapper, clb -> { clb.withSuperclass(CD_Object); - clb.withFlags(ACC_FINAL | ACC_SYNTHETIC); + clb.withFlags((PreviewFeatures.isEnabled() ? ACC_IDENTITY : 0) | ACC_FINAL | ACC_SYNTHETIC); clb.withInterfaceSymbols(CD_Comparator); // static and instance fields diff --git a/test/jdk/java/lang/invoke/MethodHandles/TestRecursive.java b/test/jdk/java/lang/invoke/MethodHandles/TestRecursive.java new file mode 100644 index 00000000000..9e27fbbc3ef --- /dev/null +++ b/test/jdk/java/lang/invoke/MethodHandles/TestRecursive.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @modules java.base/java.lang.runtime:open + * @run testng TestRecursive + */ + +import org.testng.annotations.Test; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Method; + +import static java.lang.invoke.MethodHandles.lookup; +import static java.lang.invoke.MethodType.methodType; +import static org.testng.Assert.assertEquals; + +public class TestRecursive { + static MethodHandle recursive(MethodHandle base, MethodHandle... moreBases) throws ReflectiveOperationException { + // temporarily place this API in an internal class + Class c = Class.forName("java.lang.runtime.ValueObjectMethods"); + Method m = c.getDeclaredMethod("recursive", MethodHandle.class, MethodHandle[].class); + m.setAccessible(true); + return (MethodHandle) m.invoke(null, base, moreBases); + } + + static class Snippet1 { + // classic recursive implementation of the factorial function + static int base(MethodHandle recur, int k) throws Throwable { + if (k <= 1) return 1; + return k * (int) recur.invokeExact(k - 1); + } + static void doTest() throws Throwable { + var MT_base = methodType(int.class, MethodHandle.class, int.class); + var MH_base = lookup().findStatic(Snippet1.class, "base", MT_base); + // assume MH_base is a handle to the above method + MethodHandle recur = recursive(MH_base); + assertEquals(120, (int) recur.invoke(5)); + } + } + + @Test + public void testSingleRecursion() throws Throwable { + Snippet1.doTest(); + } + + static class DoubleRecursion { + static long entryPoint(MethodHandle entryPoint, + MethodHandle factorialOdd, + MethodHandle factorialEven, + long k) throws Throwable { + if ((k & 1) == 0) + return (long) factorialEven.invokeExact(k, "even0", 2.2f); + else + return (long) factorialOdd.invokeExact(k, "odd0"); + } + static long factorialOdd(MethodHandle entryPoint, + MethodHandle factorialOdd, + MethodHandle factorialEven, + long k, + // change up the signature: + String ignore) throws Throwable { + assertEquals(k & 1, 1); + if (k < 3) return 1; + return k * (long) factorialEven.invokeExact(k - 1, "even1", 3.3f); + } + static long factorialEven(MethodHandle entryPoint, + MethodHandle factorialOdd, + MethodHandle factorialEven, + long k, + // change up the signature again: + String ignore, float ig2) throws Throwable { + assertEquals(k & 1, 0); + if (k < 2) return 1; + return k * (long) factorialOdd.invokeExact(k - 1, "odd1"); + } + static void doTest() throws Throwable { + var mt = methodType(long.class, + MethodHandle.class, + MethodHandle.class, + MethodHandle.class, + long.class); + var MH_entryPoint = lookup().findStatic(DoubleRecursion.class, + "entryPoint", mt); + mt = mt.appendParameterTypes(String.class); + var MH_factorialOdd = lookup().findStatic(DoubleRecursion.class, + "factorialOdd", mt); + mt = mt.appendParameterTypes(float.class); + var MH_factorialEven = lookup().findStatic(DoubleRecursion.class, + "factorialEven", mt); + MethodHandle recur = recursive(MH_entryPoint, + MH_factorialOdd, + MH_factorialEven); + long fact = 1; + for (long k = 0; k < 20; k++) { + assertEquals(fact, (long) recur.invoke(k)); + fact *= k+1; + } + } + } + + @Test + public void testDoubleRecursion() throws Throwable { + DoubleRecursion.doTest(); + } +} + diff --git a/test/jdk/java/lang/invoke/MethodHandles/classData/ClassDataTest.java b/test/jdk/java/lang/invoke/MethodHandles/classData/ClassDataTest.java index 5880761f925..fdda19d8c01 100644 --- a/test/jdk/java/lang/invoke/MethodHandles/classData/ClassDataTest.java +++ b/test/jdk/java/lang/invoke/MethodHandles/classData/ClassDataTest.java @@ -501,5 +501,3 @@ private void assertClassData(Lookup lookup, Object o, Object value) throws Refle assertEquals(value, v); } } - - diff --git a/test/jdk/java/lang/invoke/TEST.properties b/test/jdk/java/lang/invoke/TEST.properties deleted file mode 100644 index 908b451f8ea..00000000000 --- a/test/jdk/java/lang/invoke/TEST.properties +++ /dev/null @@ -1,2 +0,0 @@ -# disabled till JDK-8219408 is fixed -allowSmartActionArgs=false diff --git a/test/jdk/java/lang/invoke/VarHandles/Value.java b/test/jdk/java/lang/invoke/VarHandles/Value.java new file mode 100644 index 00000000000..25cc95bbb27 --- /dev/null +++ b/test/jdk/java/lang/invoke/VarHandles/Value.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +value class Value { + int v; + public Value(int v) { + this.v = v; + } + + public static Value getInstance(int v) { + return new Value(v); + } +} diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleBaseByteArrayTest.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleBaseByteArrayTest.java index f09ba518e48..59852da71b7 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleBaseByteArrayTest.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleBaseByteArrayTest.java @@ -194,6 +194,10 @@ static class VarHandleSourceAccessTestCase extends AccessTestCase { VarHandle get() { return vh; } + + public String toString() { + return super.toString() + ", vh:" + vh; + } } static class MethodHandleAccessTestCase extends AccessTestCase { @@ -464,6 +468,10 @@ static class MethodHandleAccessTestCase extends AccessTestCase { Handles get() throws Exception { return new Handles(vh, f); } + + public String toString() { + return super.toString() + ", vh:" + vh + ", f: " + f; + } } static void testTypes(VarHandle vh) { diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessBoolean.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessBoolean.java index a9cd5a61655..b22cfb37ca8 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessBoolean.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessBoolean.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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @run testng/othervm -Diters=10 -Xint VarHandleTestAccessBoolean diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessByte.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessByte.java index 5dc4bf2d1d5..556eedbbe4e 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessByte.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessByte.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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @run testng/othervm -Diters=10 -Xint VarHandleTestAccessByte diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessChar.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessChar.java index bafde057167..4004e81e93c 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessChar.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessChar.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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @run testng/othervm -Diters=10 -Xint VarHandleTestAccessChar diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessDouble.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessDouble.java index 0edb196076c..1e511db74f3 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessDouble.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessDouble.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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @run testng/othervm -Diters=10 -Xint VarHandleTestAccessDouble diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessFloat.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessFloat.java index c77a8d68008..44911ea7ff2 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessFloat.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessFloat.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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @run testng/othervm -Diters=10 -Xint VarHandleTestAccessFloat diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessInt.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessInt.java index d5dfaa4e1c6..54ce20a55db 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessInt.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessInt.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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @run testng/othervm -Diters=10 -Xint VarHandleTestAccessInt diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessLong.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessLong.java index e40256a8bd5..7db36791f8c 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessLong.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessLong.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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @run testng/othervm -Diters=10 -Xint VarHandleTestAccessLong diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessShort.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessShort.java index b9daf4880a0..93cd0d2d373 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessShort.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessShort.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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @run testng/othervm -Diters=10 -Xint VarHandleTestAccessShort diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessString.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessString.java index 8bc30739660..c0093fb0142 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessString.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessString.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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @run testng/othervm -Diters=10 -Xint VarHandleTestAccessString diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessValue.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessValue.java new file mode 100644 index 00000000000..a38c89e1491 --- /dev/null +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestAccessValue.java @@ -0,0 +1,1402 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// -- This file was mechanically generated: Do not edit! -- // + +/* + * @test + * @enablePreview + * @modules java.base/jdk.internal.vm.annotation + * @run testng/othervm -Diters=10 -Xint VarHandleTestAccessValue + * + * @comment Set CompileThresholdScaling to 0.1 so that the warmup loop sets to 2000 iterations + * to hit compilation thresholds + * + * @run testng/othervm -Diters=2000 -XX:CompileThresholdScaling=0.1 -XX:TieredStopAtLevel=1 VarHandleTestAccessValue + * @run testng/othervm -Diters=2000 -XX:CompileThresholdScaling=0.1 VarHandleTestAccessValue + * @run testng/othervm -Diters=2000 -XX:CompileThresholdScaling=0.1 -XX:-TieredCompilation VarHandleTestAccessValue + */ + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.testng.Assert.*; + +public class VarHandleTestAccessValue extends VarHandleBaseTest { + static final Value static_final_v = Value.getInstance(10); + + static Value static_v; + + final Value final_v = Value.getInstance(10); + + Value v; + + static final Value static_final_v2 = Value.getInstance(10); + + static Value static_v2; + + final Value final_v2 = Value.getInstance(10); + + Value v2; + + VarHandle vhFinalField; + + VarHandle vhField; + + VarHandle vhStaticField; + + VarHandle vhStaticFinalField; + + VarHandle vhArray; + + VarHandle vhArrayObject; + + VarHandle[] allocate(boolean same) { + List vhs = new ArrayList<>(); + + String postfix = same ? "" : "2"; + VarHandle vh; + try { + vh = MethodHandles.lookup().findVarHandle( + VarHandleTestAccessValue.class, "final_v" + postfix, Value.class); + vhs.add(vh); + + vh = MethodHandles.lookup().findVarHandle( + VarHandleTestAccessValue.class, "v" + postfix, Value.class); + vhs.add(vh); + + vh = MethodHandles.lookup().findStaticVarHandle( + VarHandleTestAccessValue.class, "static_final_v" + postfix, Value.class); + vhs.add(vh); + + vh = MethodHandles.lookup().findStaticVarHandle( + VarHandleTestAccessValue.class, "static_v" + postfix, Value.class); + vhs.add(vh); + + if (same) { + vh = MethodHandles.arrayElementVarHandle(Value[].class); + } + else { + vh = MethodHandles.arrayElementVarHandle(String[].class); + } + vhs.add(vh); + } catch (Exception e) { + throw new InternalError(e); + } + return vhs.toArray(new VarHandle[0]); + } + + @BeforeClass + public void setup() throws Exception { + vhFinalField = MethodHandles.lookup().findVarHandle( + VarHandleTestAccessValue.class, "final_v", Value.class); + + vhField = MethodHandles.lookup().findVarHandle( + VarHandleTestAccessValue.class, "v", Value.class); + + vhStaticFinalField = MethodHandles.lookup().findStaticVarHandle( + VarHandleTestAccessValue.class, "static_final_v", Value.class); + + vhStaticField = MethodHandles.lookup().findStaticVarHandle( + VarHandleTestAccessValue.class, "static_v", Value.class); + + vhArray = MethodHandles.arrayElementVarHandle(Value[].class); + vhArrayObject = MethodHandles.arrayElementVarHandle(Object[].class); + } + + + @DataProvider + public Object[][] varHandlesProvider() throws Exception { + List vhs = new ArrayList<>(); + vhs.add(vhField); + vhs.add(vhStaticField); + vhs.add(vhArray); + + return vhs.stream().map(tc -> new Object[]{tc}).toArray(Object[][]::new); + } + + @Test + public void testEquals() { + VarHandle[] vhs1 = allocate(true); + VarHandle[] vhs2 = allocate(true); + + for (int i = 0; i < vhs1.length; i++) { + for (int j = 0; j < vhs1.length; j++) { + if (i != j) { + assertNotEquals(vhs1[i], vhs1[j]); + assertNotEquals(vhs1[i], vhs2[j]); + } + } + } + + VarHandle[] vhs3 = allocate(false); + for (int i = 0; i < vhs1.length; i++) { + assertNotEquals(vhs1[i], vhs3[i]); + } + } + + @Test(dataProvider = "varHandlesProvider") + public void testIsAccessModeSupported(VarHandle vh) { + assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.GET)); + assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.SET)); + assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.GET_VOLATILE)); + assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.SET_VOLATILE)); + assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.GET_ACQUIRE)); + assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.SET_RELEASE)); + assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.GET_OPAQUE)); + assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.SET_OPAQUE)); + + assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.COMPARE_AND_SET)); + assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.COMPARE_AND_EXCHANGE)); + assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.COMPARE_AND_EXCHANGE_ACQUIRE)); + assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.COMPARE_AND_EXCHANGE_RELEASE)); + assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.WEAK_COMPARE_AND_SET_PLAIN)); + assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.WEAK_COMPARE_AND_SET)); + assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.WEAK_COMPARE_AND_SET_ACQUIRE)); + assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.WEAK_COMPARE_AND_SET_RELEASE)); + assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.GET_AND_SET)); + assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.GET_AND_SET_ACQUIRE)); + assertTrue(vh.isAccessModeSupported(VarHandle.AccessMode.GET_AND_SET_RELEASE)); + + assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.GET_AND_ADD)); + assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.GET_AND_ADD_ACQUIRE)); + assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.GET_AND_ADD_RELEASE)); + + assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.GET_AND_BITWISE_OR)); + assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.GET_AND_BITWISE_OR_ACQUIRE)); + assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.GET_AND_BITWISE_OR_RELEASE)); + assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.GET_AND_BITWISE_AND)); + assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.GET_AND_BITWISE_AND_ACQUIRE)); + assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.GET_AND_BITWISE_AND_RELEASE)); + assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.GET_AND_BITWISE_XOR)); + assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.GET_AND_BITWISE_XOR_ACQUIRE)); + assertFalse(vh.isAccessModeSupported(VarHandle.AccessMode.GET_AND_BITWISE_XOR_RELEASE)); + } + + + @DataProvider + public Object[][] typesProvider() throws Exception { + List types = new ArrayList<>(); + types.add(new Object[] {vhField, Arrays.asList(VarHandleTestAccessValue.class)}); + types.add(new Object[] {vhStaticField, Arrays.asList()}); + types.add(new Object[] {vhArray, Arrays.asList(Value[].class, int.class)}); + + return types.stream().toArray(Object[][]::new); + } + + @Test(dataProvider = "typesProvider") + public void testTypes(VarHandle vh, List> pts) { + assertEquals(vh.varType(), Value.class); + + assertEquals(vh.coordinateTypes(), pts); + + testTypes(vh); + } + + + @Test + public void testLookupInstanceToStatic() { + checkIAE("Lookup of static final field to instance final field", () -> { + MethodHandles.lookup().findStaticVarHandle( + VarHandleTestAccessValue.class, "final_v", Value.class); + }); + + checkIAE("Lookup of static field to instance field", () -> { + MethodHandles.lookup().findStaticVarHandle( + VarHandleTestAccessValue.class, "v", Value.class); + }); + } + + @Test + public void testLookupStaticToInstance() { + checkIAE("Lookup of instance final field to static final field", () -> { + MethodHandles.lookup().findVarHandle( + VarHandleTestAccessValue.class, "static_final_v", Value.class); + }); + + checkIAE("Lookup of instance field to static field", () -> { + vhStaticField = MethodHandles.lookup().findVarHandle( + VarHandleTestAccessValue.class, "static_v", Value.class); + }); + } + + + @DataProvider + public Object[][] accessTestCaseProvider() throws Exception { + List> cases = new ArrayList<>(); + + cases.add(new VarHandleAccessTestCase("Instance final field", + vhFinalField, vh -> testInstanceFinalField(this, vh))); + cases.add(new VarHandleAccessTestCase("Instance final field unsupported", + vhFinalField, vh -> testInstanceFinalFieldUnsupported(this, vh), + false)); + + cases.add(new VarHandleAccessTestCase("Static final field", + vhStaticFinalField, VarHandleTestAccessValue::testStaticFinalField)); + cases.add(new VarHandleAccessTestCase("Static final field unsupported", + vhStaticFinalField, VarHandleTestAccessValue::testStaticFinalFieldUnsupported, + false)); + + cases.add(new VarHandleAccessTestCase("Instance field", + vhField, vh -> testInstanceField(this, vh))); + cases.add(new VarHandleAccessTestCase("Instance field unsupported", + vhField, vh -> testInstanceFieldUnsupported(this, vh), + false)); + + cases.add(new VarHandleAccessTestCase("Static field", + vhStaticField, VarHandleTestAccessValue::testStaticField)); + cases.add(new VarHandleAccessTestCase("Static field unsupported", + vhStaticField, VarHandleTestAccessValue::testStaticFieldUnsupported, + false)); + + cases.add(new VarHandleAccessTestCase("Array", + vhArray, VarHandleTestAccessValue::testArray)); + cases.add(new VarHandleAccessTestCase("Array Object[]", + vhArrayObject, VarHandleTestAccessValue::testArray)); + cases.add(new VarHandleAccessTestCase("Array unsupported", + vhArray, VarHandleTestAccessValue::testArrayUnsupported, + false)); + cases.add(new VarHandleAccessTestCase("Array index out of bounds", + vhArray, VarHandleTestAccessValue::testArrayIndexOutOfBounds, + false)); + cases.add(new VarHandleAccessTestCase("Array store exception", + vhArrayObject, VarHandleTestAccessValue::testArrayStoreException, + false)); + // Work around issue with jtreg summary reporting which truncates + // the String result of Object.toString to 30 characters, hence + // the first dummy argument + return cases.stream().map(tc -> new Object[]{tc.toString(), tc}).toArray(Object[][]::new); + } + + @Test(dataProvider = "accessTestCaseProvider") + public void testAccess(String desc, AccessTestCase atc) throws Throwable { + T t = atc.get(); + int iters = atc.requiresLoop() ? ITERS : 1; + for (int c = 0; c < iters; c++) { + atc.testAccess(t); + } + } + + static void testInstanceFinalField(VarHandleTestAccessValue recv, VarHandle vh) { + // Plain + { + Value x = (Value) vh.get(recv); + assertEquals(x, Value.getInstance(10), "get Value value"); + } + + + // Volatile + { + Value x = (Value) vh.getVolatile(recv); + assertEquals(x, Value.getInstance(10), "getVolatile Value value"); + } + + // Lazy + { + Value x = (Value) vh.getAcquire(recv); + assertEquals(x, Value.getInstance(10), "getRelease Value value"); + } + + // Opaque + { + Value x = (Value) vh.getOpaque(recv); + assertEquals(x, Value.getInstance(10), "getOpaque Value value"); + } + } + + static void testInstanceFinalFieldUnsupported(VarHandleTestAccessValue recv, VarHandle vh) { + checkUOE(() -> { + vh.set(recv, Value.getInstance(20)); + }); + + checkUOE(() -> { + vh.setVolatile(recv, Value.getInstance(20)); + }); + + checkUOE(() -> { + vh.setRelease(recv, Value.getInstance(20)); + }); + + checkUOE(() -> { + vh.setOpaque(recv, Value.getInstance(20)); + }); + + + checkUOE(() -> { + Value o = (Value) vh.getAndAdd(recv, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndAddAcquire(recv, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndAddRelease(recv, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseOr(recv, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseOrAcquire(recv, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseOrRelease(recv, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseAnd(recv, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseAndAcquire(recv, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseAndRelease(recv, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseXor(recv, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseXorAcquire(recv, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseXorRelease(recv, Value.getInstance(10)); + }); + } + + + static void testStaticFinalField(VarHandle vh) { + // Plain + { + Value x = (Value) vh.get(); + assertEquals(x, Value.getInstance(10), "get Value value"); + } + + + // Volatile + { + Value x = (Value) vh.getVolatile(); + assertEquals(x, Value.getInstance(10), "getVolatile Value value"); + } + + // Lazy + { + Value x = (Value) vh.getAcquire(); + assertEquals(x, Value.getInstance(10), "getRelease Value value"); + } + + // Opaque + { + Value x = (Value) vh.getOpaque(); + assertEquals(x, Value.getInstance(10), "getOpaque Value value"); + } + } + + static void testStaticFinalFieldUnsupported(VarHandle vh) { + checkUOE(() -> { + vh.set(Value.getInstance(20)); + }); + + checkUOE(() -> { + vh.setVolatile(Value.getInstance(20)); + }); + + checkUOE(() -> { + vh.setRelease(Value.getInstance(20)); + }); + + checkUOE(() -> { + vh.setOpaque(Value.getInstance(20)); + }); + + + checkUOE(() -> { + Value o = (Value) vh.getAndAdd(Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndAddAcquire(Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndAddRelease(Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseOr(Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseOrAcquire(Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseOrRelease(Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseAnd(Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseAndAcquire(Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseAndRelease(Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseXor(Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseXorAcquire(Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseXorRelease(Value.getInstance(10)); + }); + } + + + static void testInstanceField(VarHandleTestAccessValue recv, VarHandle vh) { + // Plain + { + vh.set(recv, Value.getInstance(10)); + Value x = (Value) vh.get(recv); + assertEquals(x, Value.getInstance(10), "set Value value"); + } + + + // Volatile + { + vh.setVolatile(recv, Value.getInstance(20)); + Value x = (Value) vh.getVolatile(recv); + assertEquals(x, Value.getInstance(20), "setVolatile Value value"); + } + + // Lazy + { + vh.setRelease(recv, Value.getInstance(10)); + Value x = (Value) vh.getAcquire(recv); + assertEquals(x, Value.getInstance(10), "setRelease Value value"); + } + + // Opaque + { + vh.setOpaque(recv, Value.getInstance(20)); + Value x = (Value) vh.getOpaque(recv); + assertEquals(x, Value.getInstance(20), "setOpaque Value value"); + } + + vh.set(recv, Value.getInstance(10)); + + // Compare + { + boolean r = vh.compareAndSet(recv, Value.getInstance(10), Value.getInstance(20)); + assertEquals(r, true, "success compareAndSet Value"); + Value x = (Value) vh.get(recv); + assertEquals(x, Value.getInstance(20), "success compareAndSet Value value"); + } + + { + boolean r = vh.compareAndSet(recv, Value.getInstance(10), Value.getInstance(30)); + assertEquals(r, false, "failing compareAndSet Value"); + Value x = (Value) vh.get(recv); + assertEquals(x, Value.getInstance(20), "failing compareAndSet Value value"); + } + + { + Value r = (Value) vh.compareAndExchange(recv, Value.getInstance(20), Value.getInstance(10)); + assertEquals(r, Value.getInstance(20), "success compareAndExchange Value"); + Value x = (Value) vh.get(recv); + assertEquals(x, Value.getInstance(10), "success compareAndExchange Value value"); + } + + { + Value r = (Value) vh.compareAndExchange(recv, Value.getInstance(20), Value.getInstance(30)); + assertEquals(r, Value.getInstance(10), "failing compareAndExchange Value"); + Value x = (Value) vh.get(recv); + assertEquals(x, Value.getInstance(10), "failing compareAndExchange Value value"); + } + + { + Value r = (Value) vh.compareAndExchangeAcquire(recv, Value.getInstance(10), Value.getInstance(20)); + assertEquals(r, Value.getInstance(10), "success compareAndExchangeAcquire Value"); + Value x = (Value) vh.get(recv); + assertEquals(x, Value.getInstance(20), "success compareAndExchangeAcquire Value value"); + } + + { + Value r = (Value) vh.compareAndExchangeAcquire(recv, Value.getInstance(10), Value.getInstance(30)); + assertEquals(r, Value.getInstance(20), "failing compareAndExchangeAcquire Value"); + Value x = (Value) vh.get(recv); + assertEquals(x, Value.getInstance(20), "failing compareAndExchangeAcquire Value value"); + } + + { + Value r = (Value) vh.compareAndExchangeRelease(recv, Value.getInstance(20), Value.getInstance(10)); + assertEquals(r, Value.getInstance(20), "success compareAndExchangeRelease Value"); + Value x = (Value) vh.get(recv); + assertEquals(x, Value.getInstance(10), "success compareAndExchangeRelease Value value"); + } + + { + Value r = (Value) vh.compareAndExchangeRelease(recv, Value.getInstance(20), Value.getInstance(30)); + assertEquals(r, Value.getInstance(10), "failing compareAndExchangeRelease Value"); + Value x = (Value) vh.get(recv); + assertEquals(x, Value.getInstance(10), "failing compareAndExchangeRelease Value value"); + } + + { + boolean success = false; + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = vh.weakCompareAndSetPlain(recv, Value.getInstance(10), Value.getInstance(20)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSetPlain Value"); + Value x = (Value) vh.get(recv); + assertEquals(x, Value.getInstance(20), "success weakCompareAndSetPlain Value value"); + } + + { + boolean success = vh.weakCompareAndSetPlain(recv, Value.getInstance(10), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSetPlain Value"); + Value x = (Value) vh.get(recv); + assertEquals(x, Value.getInstance(20), "failing weakCompareAndSetPlain Value value"); + } + + { + boolean success = false; + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = vh.weakCompareAndSetAcquire(recv, Value.getInstance(20), Value.getInstance(10)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSetAcquire Value"); + Value x = (Value) vh.get(recv); + assertEquals(x, Value.getInstance(10), "success weakCompareAndSetAcquire Value"); + } + + { + boolean success = vh.weakCompareAndSetAcquire(recv, Value.getInstance(20), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSetAcquire Value"); + Value x = (Value) vh.get(recv); + assertEquals(x, Value.getInstance(10), "failing weakCompareAndSetAcquire Value value"); + } + + { + boolean success = false; + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = vh.weakCompareAndSetRelease(recv, Value.getInstance(10), Value.getInstance(20)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSetRelease Value"); + Value x = (Value) vh.get(recv); + assertEquals(x, Value.getInstance(20), "success weakCompareAndSetRelease Value"); + } + + { + boolean success = vh.weakCompareAndSetRelease(recv, Value.getInstance(10), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSetRelease Value"); + Value x = (Value) vh.get(recv); + assertEquals(x, Value.getInstance(20), "failing weakCompareAndSetRelease Value value"); + } + + { + boolean success = false; + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = vh.weakCompareAndSet(recv, Value.getInstance(20), Value.getInstance(10)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSet Value"); + Value x = (Value) vh.get(recv); + assertEquals(x, Value.getInstance(10), "success weakCompareAndSet Value value"); + } + + { + boolean success = vh.weakCompareAndSet(recv, Value.getInstance(20), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSet Value"); + Value x = (Value) vh.get(recv); + assertEquals(x, Value.getInstance(10), "failing weakCompareAndSet Value value"); + } + + // Compare set and get + { + vh.set(recv, Value.getInstance(10)); + + Value o = (Value) vh.getAndSet(recv, Value.getInstance(20)); + assertEquals(o, Value.getInstance(10), "getAndSet Value"); + Value x = (Value) vh.get(recv); + assertEquals(x, Value.getInstance(20), "getAndSet Value value"); + } + + { + vh.set(recv, Value.getInstance(10)); + + Value o = (Value) vh.getAndSetAcquire(recv, Value.getInstance(20)); + assertEquals(o, Value.getInstance(10), "getAndSetAcquire Value"); + Value x = (Value) vh.get(recv); + assertEquals(x, Value.getInstance(20), "getAndSetAcquire Value value"); + } + + { + vh.set(recv, Value.getInstance(10)); + + Value o = (Value) vh.getAndSetRelease(recv, Value.getInstance(20)); + assertEquals(o, Value.getInstance(10), "getAndSetRelease Value"); + Value x = (Value) vh.get(recv); + assertEquals(x, Value.getInstance(20), "getAndSetRelease Value value"); + } + + + } + + static void testInstanceFieldUnsupported(VarHandleTestAccessValue recv, VarHandle vh) { + + checkUOE(() -> { + Value o = (Value) vh.getAndAdd(recv, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndAddAcquire(recv, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndAddRelease(recv, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseOr(recv, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseOrAcquire(recv, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseOrRelease(recv, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseAnd(recv, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseAndAcquire(recv, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseAndRelease(recv, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseXor(recv, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseXorAcquire(recv, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseXorRelease(recv, Value.getInstance(10)); + }); + } + + + static void testStaticField(VarHandle vh) { + // Plain + { + vh.set(Value.getInstance(10)); + Value x = (Value) vh.get(); + assertEquals(x, Value.getInstance(10), "set Value value"); + } + + + // Volatile + { + vh.setVolatile(Value.getInstance(20)); + Value x = (Value) vh.getVolatile(); + assertEquals(x, Value.getInstance(20), "setVolatile Value value"); + } + + // Lazy + { + vh.setRelease(Value.getInstance(10)); + Value x = (Value) vh.getAcquire(); + assertEquals(x, Value.getInstance(10), "setRelease Value value"); + } + + // Opaque + { + vh.setOpaque(Value.getInstance(20)); + Value x = (Value) vh.getOpaque(); + assertEquals(x, Value.getInstance(20), "setOpaque Value value"); + } + + vh.set(Value.getInstance(10)); + + // Compare + { + boolean r = vh.compareAndSet(Value.getInstance(10), Value.getInstance(20)); + assertEquals(r, true, "success compareAndSet Value"); + Value x = (Value) vh.get(); + assertEquals(x, Value.getInstance(20), "success compareAndSet Value value"); + } + + { + boolean r = vh.compareAndSet(Value.getInstance(10), Value.getInstance(30)); + assertEquals(r, false, "failing compareAndSet Value"); + Value x = (Value) vh.get(); + assertEquals(x, Value.getInstance(20), "failing compareAndSet Value value"); + } + + { + Value r = (Value) vh.compareAndExchange(Value.getInstance(20), Value.getInstance(10)); + assertEquals(r, Value.getInstance(20), "success compareAndExchange Value"); + Value x = (Value) vh.get(); + assertEquals(x, Value.getInstance(10), "success compareAndExchange Value value"); + } + + { + Value r = (Value) vh.compareAndExchange(Value.getInstance(20), Value.getInstance(30)); + assertEquals(r, Value.getInstance(10), "failing compareAndExchange Value"); + Value x = (Value) vh.get(); + assertEquals(x, Value.getInstance(10), "failing compareAndExchange Value value"); + } + + { + Value r = (Value) vh.compareAndExchangeAcquire(Value.getInstance(10), Value.getInstance(20)); + assertEquals(r, Value.getInstance(10), "success compareAndExchangeAcquire Value"); + Value x = (Value) vh.get(); + assertEquals(x, Value.getInstance(20), "success compareAndExchangeAcquire Value value"); + } + + { + Value r = (Value) vh.compareAndExchangeAcquire(Value.getInstance(10), Value.getInstance(30)); + assertEquals(r, Value.getInstance(20), "failing compareAndExchangeAcquire Value"); + Value x = (Value) vh.get(); + assertEquals(x, Value.getInstance(20), "failing compareAndExchangeAcquire Value value"); + } + + { + Value r = (Value) vh.compareAndExchangeRelease(Value.getInstance(20), Value.getInstance(10)); + assertEquals(r, Value.getInstance(20), "success compareAndExchangeRelease Value"); + Value x = (Value) vh.get(); + assertEquals(x, Value.getInstance(10), "success compareAndExchangeRelease Value value"); + } + + { + Value r = (Value) vh.compareAndExchangeRelease(Value.getInstance(20), Value.getInstance(30)); + assertEquals(r, Value.getInstance(10), "failing compareAndExchangeRelease Value"); + Value x = (Value) vh.get(); + assertEquals(x, Value.getInstance(10), "failing compareAndExchangeRelease Value value"); + } + + { + boolean success = false; + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = vh.weakCompareAndSetPlain(Value.getInstance(10), Value.getInstance(20)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSetPlain Value"); + Value x = (Value) vh.get(); + assertEquals(x, Value.getInstance(20), "success weakCompareAndSetPlain Value value"); + } + + { + boolean success = vh.weakCompareAndSetPlain(Value.getInstance(10), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSetPlain Value"); + Value x = (Value) vh.get(); + assertEquals(x, Value.getInstance(20), "failing weakCompareAndSetPlain Value value"); + } + + { + boolean success = false; + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = vh.weakCompareAndSetAcquire(Value.getInstance(20), Value.getInstance(10)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSetAcquire Value"); + Value x = (Value) vh.get(); + assertEquals(x, Value.getInstance(10), "success weakCompareAndSetAcquire Value"); + } + + { + boolean success = vh.weakCompareAndSetAcquire(Value.getInstance(20), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSetAcquire Value"); + Value x = (Value) vh.get(); + assertEquals(x, Value.getInstance(10), "failing weakCompareAndSetAcquire Value value"); + } + + { + boolean success = false; + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = vh.weakCompareAndSetRelease(Value.getInstance(10), Value.getInstance(20)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSetRelease Value"); + Value x = (Value) vh.get(); + assertEquals(x, Value.getInstance(20), "success weakCompareAndSetRelease Value"); + } + + { + boolean success = vh.weakCompareAndSetRelease(Value.getInstance(10), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSetRelease Value"); + Value x = (Value) vh.get(); + assertEquals(x, Value.getInstance(20), "failing weakCompareAndSetRelease Value value"); + } + + { + boolean success = false; + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = vh.weakCompareAndSet(Value.getInstance(20), Value.getInstance(10)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSet Value"); + Value x = (Value) vh.get(); + assertEquals(x, Value.getInstance(10), "success weakCompareAndSet Value"); + } + + { + boolean success = vh.weakCompareAndSet(Value.getInstance(20), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSet Value"); + Value x = (Value) vh.get(); + assertEquals(x, Value.getInstance(10), "failing weakCompareAndSet Value value"); + } + + // Compare set and get + { + vh.set(Value.getInstance(10)); + + Value o = (Value) vh.getAndSet(Value.getInstance(20)); + assertEquals(o, Value.getInstance(10), "getAndSet Value"); + Value x = (Value) vh.get(); + assertEquals(x, Value.getInstance(20), "getAndSet Value value"); + } + + { + vh.set(Value.getInstance(10)); + + Value o = (Value) vh.getAndSetAcquire(Value.getInstance(20)); + assertEquals(o, Value.getInstance(10), "getAndSetAcquire Value"); + Value x = (Value) vh.get(); + assertEquals(x, Value.getInstance(20), "getAndSetAcquire Value value"); + } + + { + vh.set(Value.getInstance(10)); + + Value o = (Value) vh.getAndSetRelease(Value.getInstance(20)); + assertEquals(o, Value.getInstance(10), "getAndSetRelease Value"); + Value x = (Value) vh.get(); + assertEquals(x, Value.getInstance(20), "getAndSetRelease Value value"); + } + + + } + + static void testStaticFieldUnsupported(VarHandle vh) { + + checkUOE(() -> { + Value o = (Value) vh.getAndAdd(Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndAddAcquire(Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndAddRelease(Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseOr(Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseOrAcquire(Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseOrRelease(Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseAnd(Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseAndAcquire(Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseAndRelease(Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseXor(Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseXorAcquire(Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseXorRelease(Value.getInstance(10)); + }); + } + + + static void testArray(VarHandle vh) { + Value[] array = new Value[10]; + + for (int i = 0; i < array.length; i++) { + // Plain + { + vh.set(array, i, Value.getInstance(10)); + Value x = (Value) vh.get(array, i); + assertEquals(x, Value.getInstance(10), "get Value value"); + } + + + // Volatile + { + vh.setVolatile(array, i, Value.getInstance(20)); + Value x = (Value) vh.getVolatile(array, i); + assertEquals(x, Value.getInstance(20), "setVolatile Value value"); + } + + // Lazy + { + vh.setRelease(array, i, Value.getInstance(10)); + Value x = (Value) vh.getAcquire(array, i); + assertEquals(x, Value.getInstance(10), "setRelease Value value"); + } + + // Opaque + { + vh.setOpaque(array, i, Value.getInstance(20)); + Value x = (Value) vh.getOpaque(array, i); + assertEquals(x, Value.getInstance(20), "setOpaque Value value"); + } + + vh.set(array, i, Value.getInstance(10)); + + // Compare + { + boolean r = vh.compareAndSet(array, i, Value.getInstance(10), Value.getInstance(20)); + assertEquals(r, true, "success compareAndSet Value"); + Value x = (Value) vh.get(array, i); + assertEquals(x, Value.getInstance(20), "success compareAndSet Value value"); + } + + { + boolean r = vh.compareAndSet(array, i, Value.getInstance(10), Value.getInstance(30)); + assertEquals(r, false, "failing compareAndSet Value"); + Value x = (Value) vh.get(array, i); + assertEquals(x, Value.getInstance(20), "failing compareAndSet Value value"); + } + + { + Value r = (Value) vh.compareAndExchange(array, i, Value.getInstance(20), Value.getInstance(10)); + assertEquals(r, Value.getInstance(20), "success compareAndExchange Value"); + Value x = (Value) vh.get(array, i); + assertEquals(x, Value.getInstance(10), "success compareAndExchange Value value"); + } + + { + Value r = (Value) vh.compareAndExchange(array, i, Value.getInstance(20), Value.getInstance(30)); + assertEquals(r, Value.getInstance(10), "failing compareAndExchange Value"); + Value x = (Value) vh.get(array, i); + assertEquals(x, Value.getInstance(10), "failing compareAndExchange Value value"); + } + + { + Value r = (Value) vh.compareAndExchangeAcquire(array, i, Value.getInstance(10), Value.getInstance(20)); + assertEquals(r, Value.getInstance(10), "success compareAndExchangeAcquire Value"); + Value x = (Value) vh.get(array, i); + assertEquals(x, Value.getInstance(20), "success compareAndExchangeAcquire Value value"); + } + + { + Value r = (Value) vh.compareAndExchangeAcquire(array, i, Value.getInstance(10), Value.getInstance(30)); + assertEquals(r, Value.getInstance(20), "failing compareAndExchangeAcquire Value"); + Value x = (Value) vh.get(array, i); + assertEquals(x, Value.getInstance(20), "failing compareAndExchangeAcquire Value value"); + } + + { + Value r = (Value) vh.compareAndExchangeRelease(array, i, Value.getInstance(20), Value.getInstance(10)); + assertEquals(r, Value.getInstance(20), "success compareAndExchangeRelease Value"); + Value x = (Value) vh.get(array, i); + assertEquals(x, Value.getInstance(10), "success compareAndExchangeRelease Value value"); + } + + { + Value r = (Value) vh.compareAndExchangeRelease(array, i, Value.getInstance(20), Value.getInstance(30)); + assertEquals(r, Value.getInstance(10), "failing compareAndExchangeRelease Value"); + Value x = (Value) vh.get(array, i); + assertEquals(x, Value.getInstance(10), "failing compareAndExchangeRelease Value value"); + } + + { + boolean success = false; + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = vh.weakCompareAndSetPlain(array, i, Value.getInstance(10), Value.getInstance(20)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSetPlain Value"); + Value x = (Value) vh.get(array, i); + assertEquals(x, Value.getInstance(20), "success weakCompareAndSetPlain Value value"); + } + + { + boolean success = vh.weakCompareAndSetPlain(array, i, Value.getInstance(10), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSetPlain Value"); + Value x = (Value) vh.get(array, i); + assertEquals(x, Value.getInstance(20), "failing weakCompareAndSetPlain Value value"); + } + + { + boolean success = false; + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = vh.weakCompareAndSetAcquire(array, i, Value.getInstance(20), Value.getInstance(10)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSetAcquire Value"); + Value x = (Value) vh.get(array, i); + assertEquals(x, Value.getInstance(10), "success weakCompareAndSetAcquire Value"); + } + + { + boolean success = vh.weakCompareAndSetAcquire(array, i, Value.getInstance(20), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSetAcquire Value"); + Value x = (Value) vh.get(array, i); + assertEquals(x, Value.getInstance(10), "failing weakCompareAndSetAcquire Value value"); + } + + { + boolean success = false; + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = vh.weakCompareAndSetRelease(array, i, Value.getInstance(10), Value.getInstance(20)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSetRelease Value"); + Value x = (Value) vh.get(array, i); + assertEquals(x, Value.getInstance(20), "success weakCompareAndSetRelease Value"); + } + + { + boolean success = vh.weakCompareAndSetRelease(array, i, Value.getInstance(10), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSetRelease Value"); + Value x = (Value) vh.get(array, i); + assertEquals(x, Value.getInstance(20), "failing weakCompareAndSetRelease Value value"); + } + + { + boolean success = false; + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = vh.weakCompareAndSet(array, i, Value.getInstance(20), Value.getInstance(10)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSet Value"); + Value x = (Value) vh.get(array, i); + assertEquals(x, Value.getInstance(10), "success weakCompareAndSet Value"); + } + + { + boolean success = vh.weakCompareAndSet(array, i, Value.getInstance(20), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSet Value"); + Value x = (Value) vh.get(array, i); + assertEquals(x, Value.getInstance(10), "failing weakCompareAndSet Value value"); + } + + // Compare set and get + { + vh.set(array, i, Value.getInstance(10)); + + Value o = (Value) vh.getAndSet(array, i, Value.getInstance(20)); + assertEquals(o, Value.getInstance(10), "getAndSet Value"); + Value x = (Value) vh.get(array, i); + assertEquals(x, Value.getInstance(20), "getAndSet Value value"); + } + + { + vh.set(array, i, Value.getInstance(10)); + + Value o = (Value) vh.getAndSetAcquire(array, i, Value.getInstance(20)); + assertEquals(o, Value.getInstance(10), "getAndSetAcquire Value"); + Value x = (Value) vh.get(array, i); + assertEquals(x, Value.getInstance(20), "getAndSetAcquire Value value"); + } + + { + vh.set(array, i, Value.getInstance(10)); + + Value o = (Value) vh.getAndSetRelease(array, i, Value.getInstance(20)); + assertEquals(o, Value.getInstance(10), "getAndSetRelease Value"); + Value x = (Value) vh.get(array, i); + assertEquals(x, Value.getInstance(20), "getAndSetRelease Value value"); + } + + + } + } + + static void testArrayUnsupported(VarHandle vh) { + Value[] array = new Value[10]; + + int i = 0; + + checkUOE(() -> { + Value o = (Value) vh.getAndAdd(array, i, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndAddAcquire(array, i, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndAddRelease(array, i, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseOr(array, i, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseOrAcquire(array, i, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseOrRelease(array, i, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseAnd(array, i, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseAndAcquire(array, i, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseAndRelease(array, i, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseXor(array, i, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseXorAcquire(array, i, Value.getInstance(10)); + }); + + checkUOE(() -> { + Value o = (Value) vh.getAndBitwiseXorRelease(array, i, Value.getInstance(10)); + }); + } + + static void testArrayIndexOutOfBounds(VarHandle vh) throws Throwable { + Value[] array = new Value[10]; + + for (int i : new int[]{-1, Integer.MIN_VALUE, 10, 11, Integer.MAX_VALUE}) { + final int ci = i; + + checkAIOOBE(() -> { + Value x = (Value) vh.get(array, ci); + }); + + checkAIOOBE(() -> { + vh.set(array, ci, Value.getInstance(10)); + }); + + checkAIOOBE(() -> { + Value x = (Value) vh.getVolatile(array, ci); + }); + + checkAIOOBE(() -> { + vh.setVolatile(array, ci, Value.getInstance(10)); + }); + + checkAIOOBE(() -> { + Value x = (Value) vh.getAcquire(array, ci); + }); + + checkAIOOBE(() -> { + vh.setRelease(array, ci, Value.getInstance(10)); + }); + + checkAIOOBE(() -> { + Value x = (Value) vh.getOpaque(array, ci); + }); + + checkAIOOBE(() -> { + vh.setOpaque(array, ci, Value.getInstance(10)); + }); + + checkAIOOBE(() -> { + boolean r = vh.compareAndSet(array, ci, Value.getInstance(10), Value.getInstance(20)); + }); + + checkAIOOBE(() -> { + Value r = (Value) vh.compareAndExchange(array, ci, Value.getInstance(20), Value.getInstance(10)); + }); + + checkAIOOBE(() -> { + Value r = (Value) vh.compareAndExchangeAcquire(array, ci, Value.getInstance(20), Value.getInstance(10)); + }); + + checkAIOOBE(() -> { + Value r = (Value) vh.compareAndExchangeRelease(array, ci, Value.getInstance(20), Value.getInstance(10)); + }); + + checkAIOOBE(() -> { + boolean r = vh.weakCompareAndSetPlain(array, ci, Value.getInstance(10), Value.getInstance(20)); + }); + + checkAIOOBE(() -> { + boolean r = vh.weakCompareAndSet(array, ci, Value.getInstance(10), Value.getInstance(20)); + }); + + checkAIOOBE(() -> { + boolean r = vh.weakCompareAndSetAcquire(array, ci, Value.getInstance(10), Value.getInstance(20)); + }); + + checkAIOOBE(() -> { + boolean r = vh.weakCompareAndSetRelease(array, ci, Value.getInstance(10), Value.getInstance(20)); + }); + + checkAIOOBE(() -> { + Value o = (Value) vh.getAndSet(array, ci, Value.getInstance(10)); + }); + + checkAIOOBE(() -> { + Value o = (Value) vh.getAndSetAcquire(array, ci, Value.getInstance(10)); + }); + + checkAIOOBE(() -> { + Value o = (Value) vh.getAndSetRelease(array, ci, Value.getInstance(10)); + }); + + + } + } + + static void testArrayStoreException(VarHandle vh) throws Throwable { + Object[] array = new Value[10]; + Arrays.fill(array, Value.getInstance(10)); + Object value = new Object(); + + // Set + checkASE(() -> { + vh.set(array, 0, value); + }); + + // SetVolatile + checkASE(() -> { + vh.setVolatile(array, 0, value); + }); + + // SetOpaque + checkASE(() -> { + vh.setOpaque(array, 0, value); + }); + + // SetRelease + checkASE(() -> { + vh.setRelease(array, 0, value); + }); + + // CompareAndSet + checkASE(() -> { // receiver reference class + boolean r = vh.compareAndSet(array, 0, Value.getInstance(10), value); + }); + + // WeakCompareAndSet + checkASE(() -> { // receiver reference class + boolean r = vh.weakCompareAndSetPlain(array, 0, Value.getInstance(10), value); + }); + + // WeakCompareAndSetVolatile + checkASE(() -> { // receiver reference class + boolean r = vh.weakCompareAndSet(array, 0, Value.getInstance(10), value); + }); + + // WeakCompareAndSetAcquire + checkASE(() -> { // receiver reference class + boolean r = vh.weakCompareAndSetAcquire(array, 0, Value.getInstance(10), value); + }); + + // WeakCompareAndSetRelease + checkASE(() -> { // receiver reference class + boolean r = vh.weakCompareAndSetRelease(array, 0, Value.getInstance(10), value); + }); + + // CompareAndExchange + checkASE(() -> { // receiver reference class + Value x = (Value) vh.compareAndExchange(array, 0, Value.getInstance(10), value); + }); + + // CompareAndExchangeAcquire + checkASE(() -> { // receiver reference class + Value x = (Value) vh.compareAndExchangeAcquire(array, 0, Value.getInstance(10), value); + }); + + // CompareAndExchangeRelease + checkASE(() -> { // receiver reference class + Value x = (Value) vh.compareAndExchangeRelease(array, 0, Value.getInstance(10), value); + }); + + // GetAndSet + checkASE(() -> { // receiver reference class + Value x = (Value) vh.getAndSet(array, 0, value); + }); + + // GetAndSetAcquire + checkASE(() -> { // receiver reference class + Value x = (Value) vh.getAndSetAcquire(array, 0, value); + }); + + // GetAndSetRelease + checkASE(() -> { // receiver reference class + Value x = (Value) vh.getAndSetRelease(array, 0, value); + }); + } +} + diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessBoolean.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessBoolean.java index 14d5c05d3b8..ef026dbf484 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessBoolean.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessBoolean.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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @comment Set CompileThresholdScaling to 0.1 so that the warmup loop sets to 2000 iterations diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessByte.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessByte.java index 5b3eafba63b..d6aded72594 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessByte.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessByte.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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @comment Set CompileThresholdScaling to 0.1 so that the warmup loop sets to 2000 iterations diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessChar.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessChar.java index d191b507366..2c622d50b47 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessChar.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessChar.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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @comment Set CompileThresholdScaling to 0.1 so that the warmup loop sets to 2000 iterations diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessDouble.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessDouble.java index fe0455d2375..e040a497d19 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessDouble.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessDouble.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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @comment Set CompileThresholdScaling to 0.1 so that the warmup loop sets to 2000 iterations diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessFloat.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessFloat.java index 256e4723eec..17e0e358225 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessFloat.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessFloat.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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @comment Set CompileThresholdScaling to 0.1 so that the warmup loop sets to 2000 iterations diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessInt.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessInt.java index d8558e0b070..476bcfb3de9 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessInt.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessInt.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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @comment Set CompileThresholdScaling to 0.1 so that the warmup loop sets to 2000 iterations diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessLong.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessLong.java index 0e73063395d..4431c7fbdd0 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessLong.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessLong.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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @comment Set CompileThresholdScaling to 0.1 so that the warmup loop sets to 2000 iterations diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessShort.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessShort.java index 8fb23d7dc55..43cc435bd9f 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessShort.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessShort.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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @comment Set CompileThresholdScaling to 0.1 so that the warmup loop sets to 2000 iterations diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessString.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessString.java index 8ccede760c9..2565cc4fc8f 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessString.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessString.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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @comment Set CompileThresholdScaling to 0.1 so that the warmup loop sets to 2000 iterations diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessValue.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessValue.java new file mode 100644 index 00000000000..40748d1b193 --- /dev/null +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodHandleAccessValue.java @@ -0,0 +1,793 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// -- This file was mechanically generated: Do not edit! -- // + +/* + * @test + * @enablePreview + * @modules java.base/jdk.internal.vm.annotation + * @comment Set CompileThresholdScaling to 0.1 so that the warmup loop sets to 2000 iterations + * to hit compilation thresholds + * @run testng/othervm -Diters=2000 -XX:CompileThresholdScaling=0.1 VarHandleTestMethodHandleAccessValue + */ + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.testng.Assert.*; + +public class VarHandleTestMethodHandleAccessValue extends VarHandleBaseTest { + static final Value static_final_v = Value.getInstance(10); + + static Value static_v; + + final Value final_v = Value.getInstance(10); + + Value v; + + VarHandle vhFinalField; + + VarHandle vhField; + + VarHandle vhStaticField; + + VarHandle vhStaticFinalField; + + VarHandle vhArray; + + @BeforeClass + public void setup() throws Exception { + vhFinalField = MethodHandles.lookup().findVarHandle( + VarHandleTestMethodHandleAccessValue.class, "final_v", Value.class); + + vhField = MethodHandles.lookup().findVarHandle( + VarHandleTestMethodHandleAccessValue.class, "v", Value.class); + + vhStaticFinalField = MethodHandles.lookup().findStaticVarHandle( + VarHandleTestMethodHandleAccessValue.class, "static_final_v", Value.class); + + vhStaticField = MethodHandles.lookup().findStaticVarHandle( + VarHandleTestMethodHandleAccessValue.class, "static_v", Value.class); + + vhArray = MethodHandles.arrayElementVarHandle(Value[].class); + } + + + @DataProvider + public Object[][] accessTestCaseProvider() throws Exception { + List> cases = new ArrayList<>(); + + for (VarHandleToMethodHandle f : VarHandleToMethodHandle.values()) { + cases.add(new MethodHandleAccessTestCase("Instance field", + vhField, f, hs -> testInstanceField(this, hs))); + cases.add(new MethodHandleAccessTestCase("Instance field unsupported", + vhField, f, hs -> testInstanceFieldUnsupported(this, hs), + false)); + + cases.add(new MethodHandleAccessTestCase("Static field", + vhStaticField, f, VarHandleTestMethodHandleAccessValue::testStaticField)); + cases.add(new MethodHandleAccessTestCase("Static field unsupported", + vhStaticField, f, VarHandleTestMethodHandleAccessValue::testStaticFieldUnsupported, + false)); + + cases.add(new MethodHandleAccessTestCase("Array", + vhArray, f, VarHandleTestMethodHandleAccessValue::testArray)); + cases.add(new MethodHandleAccessTestCase("Array unsupported", + vhArray, f, VarHandleTestMethodHandleAccessValue::testArrayUnsupported, + false)); + cases.add(new MethodHandleAccessTestCase("Array index out of bounds", + vhArray, f, VarHandleTestMethodHandleAccessValue::testArrayIndexOutOfBounds, + false)); + } + + // Work around issue with jtreg summary reporting which truncates + // the String result of Object.toString to 30 characters, hence + // the first dummy argument + return cases.stream().map(tc -> new Object[]{tc.toString(), tc}).toArray(Object[][]::new); + } + + @Test(dataProvider = "accessTestCaseProvider") + public void testAccess(String desc, AccessTestCase atc) throws Throwable { + T t = atc.get(); + int iters = atc.requiresLoop() ? ITERS : 1; + for (int c = 0; c < iters; c++) { + atc.testAccess(t); + } + } + + + static void testInstanceField(VarHandleTestMethodHandleAccessValue recv, Handles hs) throws Throwable { + // Plain + { + hs.get(TestAccessMode.SET).invokeExact(recv, Value.getInstance(10)); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(recv); + assertEquals(x, Value.getInstance(10), "set Value value"); + } + + + // Volatile + { + hs.get(TestAccessMode.SET_VOLATILE).invokeExact(recv, Value.getInstance(20)); + Value x = (Value) hs.get(TestAccessMode.GET_VOLATILE).invokeExact(recv); + assertEquals(x, Value.getInstance(20), "setVolatile Value value"); + } + + // Lazy + { + hs.get(TestAccessMode.SET_RELEASE).invokeExact(recv, Value.getInstance(10)); + Value x = (Value) hs.get(TestAccessMode.GET_ACQUIRE).invokeExact(recv); + assertEquals(x, Value.getInstance(10), "setRelease Value value"); + } + + // Opaque + { + hs.get(TestAccessMode.SET_OPAQUE).invokeExact(recv, Value.getInstance(20)); + Value x = (Value) hs.get(TestAccessMode.GET_OPAQUE).invokeExact(recv); + assertEquals(x, Value.getInstance(20), "setOpaque Value value"); + } + + hs.get(TestAccessMode.SET).invokeExact(recv, Value.getInstance(10)); + + // Compare + { + boolean r = (boolean) hs.get(TestAccessMode.COMPARE_AND_SET).invokeExact(recv, Value.getInstance(10), Value.getInstance(20)); + assertEquals(r, true, "success compareAndSet Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(recv); + assertEquals(x, Value.getInstance(20), "success compareAndSet Value value"); + } + + { + boolean r = (boolean) hs.get(TestAccessMode.COMPARE_AND_SET).invokeExact(recv, Value.getInstance(10), Value.getInstance(30)); + assertEquals(r, false, "failing compareAndSet Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(recv); + assertEquals(x, Value.getInstance(20), "failing compareAndSet Value value"); + } + + { + Value r = (Value) hs.get(TestAccessMode.COMPARE_AND_EXCHANGE).invokeExact(recv, Value.getInstance(20), Value.getInstance(10)); + assertEquals(r, Value.getInstance(20), "success compareAndExchange Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(recv); + assertEquals(x, Value.getInstance(10), "success compareAndExchange Value value"); + } + + { + Value r = (Value) hs.get(TestAccessMode.COMPARE_AND_EXCHANGE).invokeExact(recv, Value.getInstance(20), Value.getInstance(30)); + assertEquals(r, Value.getInstance(10), "failing compareAndExchange Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(recv); + assertEquals(x, Value.getInstance(10), "failing compareAndExchange Value value"); + } + + { + Value r = (Value) hs.get(TestAccessMode.COMPARE_AND_EXCHANGE_ACQUIRE).invokeExact(recv, Value.getInstance(10), Value.getInstance(20)); + assertEquals(r, Value.getInstance(10), "success compareAndExchangeAcquire Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(recv); + assertEquals(x, Value.getInstance(20), "success compareAndExchangeAcquire Value value"); + } + + { + Value r = (Value) hs.get(TestAccessMode.COMPARE_AND_EXCHANGE_ACQUIRE).invokeExact(recv, Value.getInstance(10), Value.getInstance(30)); + assertEquals(r, Value.getInstance(20), "failing compareAndExchangeAcquire Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(recv); + assertEquals(x, Value.getInstance(20), "failing compareAndExchangeAcquire Value value"); + } + + { + Value r = (Value) hs.get(TestAccessMode.COMPARE_AND_EXCHANGE_RELEASE).invokeExact(recv, Value.getInstance(20), Value.getInstance(10)); + assertEquals(r, Value.getInstance(20), "success compareAndExchangeRelease Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(recv); + assertEquals(x, Value.getInstance(10), "success compareAndExchangeRelease Value value"); + } + + { + Value r = (Value) hs.get(TestAccessMode.COMPARE_AND_EXCHANGE_RELEASE).invokeExact(recv, Value.getInstance(20), Value.getInstance(30)); + assertEquals(r, Value.getInstance(10), "failing compareAndExchangeRelease Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(recv); + assertEquals(x, Value.getInstance(10), "failing compareAndExchangeRelease Value value"); + } + + { + MethodHandle mh = hs.get(TestAccessMode.WEAK_COMPARE_AND_SET_PLAIN); + boolean success = false; + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = (boolean) mh.invokeExact(recv, Value.getInstance(10), Value.getInstance(20)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSetPlain Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(recv); + assertEquals(x, Value.getInstance(20), "success weakCompareAndSetPlain Value value"); + } + + { + boolean success = (boolean) hs.get(TestAccessMode.WEAK_COMPARE_AND_SET_PLAIN).invokeExact(recv, Value.getInstance(10), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSetPlain Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(recv); + assertEquals(x, Value.getInstance(20), "failing weakCompareAndSetPlain Value value"); + } + + { + MethodHandle mh = hs.get(TestAccessMode.WEAK_COMPARE_AND_SET_ACQUIRE); + boolean success = false; + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = (boolean) mh.invokeExact(recv, Value.getInstance(20), Value.getInstance(10)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSetAcquire Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(recv); + assertEquals(x, Value.getInstance(10), "success weakCompareAndSetAcquire Value"); + } + + { + boolean success = (boolean) hs.get(TestAccessMode.WEAK_COMPARE_AND_SET_ACQUIRE).invokeExact(recv, Value.getInstance(20), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSetAcquire Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(recv); + assertEquals(x, Value.getInstance(10), "failing weakCompareAndSetAcquire Value value"); + } + + { + MethodHandle mh = hs.get(TestAccessMode.WEAK_COMPARE_AND_SET_RELEASE); + boolean success = false; + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = (boolean) mh.invokeExact(recv, Value.getInstance(10), Value.getInstance(20)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSetRelease Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(recv); + assertEquals(x, Value.getInstance(20), "success weakCompareAndSetRelease Value"); + } + + { + boolean success = (boolean) hs.get(TestAccessMode.WEAK_COMPARE_AND_SET_RELEASE).invokeExact(recv, Value.getInstance(10), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSetRelease Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(recv); + assertEquals(x, Value.getInstance(20), "failing weakCompareAndSetRelease Value value"); + } + + { + boolean success = false; + MethodHandle mh = hs.get(TestAccessMode.WEAK_COMPARE_AND_SET); + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = (boolean) mh.invokeExact(recv, Value.getInstance(20), Value.getInstance(10)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSet Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(recv); + assertEquals(x, Value.getInstance(10), "success weakCompareAndSet Value"); + } + + { + boolean success = (boolean) hs.get(TestAccessMode.WEAK_COMPARE_AND_SET).invokeExact(recv, Value.getInstance(20), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSet Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(recv); + assertEquals(x, Value.getInstance(10), "failing weakCompareAndSet Value value"); + } + + // Compare set and get + { + Value o = (Value) hs.get(TestAccessMode.GET_AND_SET).invokeExact(recv, Value.getInstance(20)); + assertEquals(o, Value.getInstance(10), "getAndSet Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(recv); + assertEquals(x, Value.getInstance(20), "getAndSet Value value"); + } + + + } + + static void testInstanceFieldUnsupported(VarHandleTestMethodHandleAccessValue recv, Handles hs) throws Throwable { + + for (TestAccessMode am : testAccessModesOfType(TestAccessType.GET_AND_ADD)) { + checkUOE(am, () -> { + Value r = (Value) hs.get(am).invokeExact(recv, Value.getInstance(10)); + }); + } + + for (TestAccessMode am : testAccessModesOfType(TestAccessType.GET_AND_BITWISE)) { + checkUOE(am, () -> { + Value r = (Value) hs.get(am).invokeExact(recv, Value.getInstance(10)); + }); + } + } + + + static void testStaticField(Handles hs) throws Throwable { + // Plain + { + hs.get(TestAccessMode.SET).invokeExact(Value.getInstance(10)); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(); + assertEquals(x, Value.getInstance(10), "set Value value"); + } + + + // Volatile + { + hs.get(TestAccessMode.SET_VOLATILE).invokeExact(Value.getInstance(20)); + Value x = (Value) hs.get(TestAccessMode.GET_VOLATILE).invokeExact(); + assertEquals(x, Value.getInstance(20), "setVolatile Value value"); + } + + // Lazy + { + hs.get(TestAccessMode.SET_RELEASE).invokeExact(Value.getInstance(10)); + Value x = (Value) hs.get(TestAccessMode.GET_ACQUIRE).invokeExact(); + assertEquals(x, Value.getInstance(10), "setRelease Value value"); + } + + // Opaque + { + hs.get(TestAccessMode.SET_OPAQUE).invokeExact(Value.getInstance(20)); + Value x = (Value) hs.get(TestAccessMode.GET_OPAQUE).invokeExact(); + assertEquals(x, Value.getInstance(20), "setOpaque Value value"); + } + + hs.get(TestAccessMode.SET).invokeExact(Value.getInstance(10)); + + // Compare + { + boolean r = (boolean) hs.get(TestAccessMode.COMPARE_AND_SET).invokeExact(Value.getInstance(10), Value.getInstance(20)); + assertEquals(r, true, "success compareAndSet Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(); + assertEquals(x, Value.getInstance(20), "success compareAndSet Value value"); + } + + { + boolean r = (boolean) hs.get(TestAccessMode.COMPARE_AND_SET).invokeExact(Value.getInstance(10), Value.getInstance(30)); + assertEquals(r, false, "failing compareAndSet Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(); + assertEquals(x, Value.getInstance(20), "failing compareAndSet Value value"); + } + + { + Value r = (Value) hs.get(TestAccessMode.COMPARE_AND_EXCHANGE).invokeExact(Value.getInstance(20), Value.getInstance(10)); + assertEquals(r, Value.getInstance(20), "success compareAndExchange Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(); + assertEquals(x, Value.getInstance(10), "success compareAndExchange Value value"); + } + + { + Value r = (Value) hs.get(TestAccessMode.COMPARE_AND_EXCHANGE).invokeExact(Value.getInstance(20), Value.getInstance(30)); + assertEquals(r, Value.getInstance(10), "failing compareAndExchange Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(); + assertEquals(x, Value.getInstance(10), "failing compareAndExchange Value value"); + } + + { + Value r = (Value) hs.get(TestAccessMode.COMPARE_AND_EXCHANGE_ACQUIRE).invokeExact(Value.getInstance(10), Value.getInstance(20)); + assertEquals(r, Value.getInstance(10), "success compareAndExchangeAcquire Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(); + assertEquals(x, Value.getInstance(20), "success compareAndExchangeAcquire Value value"); + } + + { + Value r = (Value) hs.get(TestAccessMode.COMPARE_AND_EXCHANGE_ACQUIRE).invokeExact(Value.getInstance(10), Value.getInstance(30)); + assertEquals(r, Value.getInstance(20), "failing compareAndExchangeAcquire Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(); + assertEquals(x, Value.getInstance(20), "failing compareAndExchangeAcquire Value value"); + } + + { + Value r = (Value) hs.get(TestAccessMode.COMPARE_AND_EXCHANGE_RELEASE).invokeExact(Value.getInstance(20), Value.getInstance(10)); + assertEquals(r, Value.getInstance(20), "success compareAndExchangeRelease Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(); + assertEquals(x, Value.getInstance(10), "success compareAndExchangeRelease Value value"); + } + + { + Value r = (Value) hs.get(TestAccessMode.COMPARE_AND_EXCHANGE_RELEASE).invokeExact(Value.getInstance(20), Value.getInstance(30)); + assertEquals(r, Value.getInstance(10), "failing compareAndExchangeRelease Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(); + assertEquals(x, Value.getInstance(10), "failing compareAndExchangeRelease Value value"); + } + + { + MethodHandle mh = hs.get(TestAccessMode.WEAK_COMPARE_AND_SET_PLAIN); + boolean success = false; + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = (boolean) mh.invokeExact(Value.getInstance(10), Value.getInstance(20)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSetPlain Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(); + assertEquals(x, Value.getInstance(20), "success weakCompareAndSetPlain Value value"); + } + + { + boolean success = (boolean) hs.get(TestAccessMode.WEAK_COMPARE_AND_SET_PLAIN).invokeExact(Value.getInstance(10), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSetPlain Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(); + assertEquals(x, Value.getInstance(20), "failing weakCompareAndSetPlain Value value"); + } + + { + MethodHandle mh = hs.get(TestAccessMode.WEAK_COMPARE_AND_SET_ACQUIRE); + boolean success = false; + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = (boolean) mh.invokeExact(Value.getInstance(20), Value.getInstance(10)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSetAcquire Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(); + assertEquals(x, Value.getInstance(10), "success weakCompareAndSetAcquire Value"); + } + + { + MethodHandle mh = hs.get(TestAccessMode.WEAK_COMPARE_AND_SET_ACQUIRE); + boolean success = (boolean) mh.invokeExact(Value.getInstance(20), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSetAcquire Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(); + assertEquals(x, Value.getInstance(10), "failing weakCompareAndSetAcquire Value value"); + } + + { + MethodHandle mh = hs.get(TestAccessMode.WEAK_COMPARE_AND_SET_RELEASE); + boolean success = false; + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = (boolean) mh.invokeExact(Value.getInstance(10), Value.getInstance(20)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSetRelease Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(); + assertEquals(x, Value.getInstance(20), "success weakCompareAndSetRelease Value"); + } + + { + boolean success = (boolean) hs.get(TestAccessMode.WEAK_COMPARE_AND_SET_RELEASE).invokeExact(Value.getInstance(10), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSetRelease Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(); + assertEquals(x, Value.getInstance(20), "failing weakCompareAndSetRelease Value value"); + } + + { + MethodHandle mh = hs.get(TestAccessMode.WEAK_COMPARE_AND_SET); + boolean success = false; + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = (boolean) mh.invokeExact(Value.getInstance(20), Value.getInstance(10)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSet Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(); + assertEquals(x, Value.getInstance(10), "success weakCompareAndSet Value"); + } + + { + boolean success = (boolean) hs.get(TestAccessMode.WEAK_COMPARE_AND_SET).invokeExact(Value.getInstance(20), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSet Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(); + assertEquals(x, Value.getInstance(10), "failing weakCompareAndSetRe Value value"); + } + + // Compare set and get + { + hs.get(TestAccessMode.SET).invokeExact(Value.getInstance(10)); + + Value o = (Value) hs.get(TestAccessMode.GET_AND_SET).invokeExact(Value.getInstance(20)); + assertEquals(o, Value.getInstance(10), "getAndSet Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(); + assertEquals(x, Value.getInstance(20), "getAndSet Value value"); + } + + // Compare set and get + { + hs.get(TestAccessMode.SET).invokeExact(Value.getInstance(10)); + + Value o = (Value) hs.get(TestAccessMode.GET_AND_SET_ACQUIRE).invokeExact(Value.getInstance(20)); + assertEquals(o, Value.getInstance(10), "getAndSetAcquire Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(); + assertEquals(x, Value.getInstance(20), "getAndSetAcquire Value value"); + } + + // Compare set and get + { + hs.get(TestAccessMode.SET).invokeExact(Value.getInstance(10)); + + Value o = (Value) hs.get(TestAccessMode.GET_AND_SET_RELEASE).invokeExact(Value.getInstance(20)); + assertEquals(o, Value.getInstance(10), "getAndSetRelease Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(); + assertEquals(x, Value.getInstance(20), "getAndSetRelease Value value"); + } + + + } + + static void testStaticFieldUnsupported(Handles hs) throws Throwable { + + for (TestAccessMode am : testAccessModesOfType(TestAccessType.GET_AND_ADD)) { + checkUOE(am, () -> { + Value r = (Value) hs.get(am).invokeExact(Value.getInstance(10)); + }); + } + + for (TestAccessMode am : testAccessModesOfType(TestAccessType.GET_AND_BITWISE)) { + checkUOE(am, () -> { + Value r = (Value) hs.get(am).invokeExact(Value.getInstance(10)); + }); + } + } + + + static void testArray(Handles hs) throws Throwable { + Value[] array = new Value[10]; + + for (int i = 0; i < array.length; i++) { + // Plain + { + hs.get(TestAccessMode.SET).invokeExact(array, i, Value.getInstance(10)); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(array, i); + assertEquals(x, Value.getInstance(10), "get Value value"); + } + + + // Volatile + { + hs.get(TestAccessMode.SET_VOLATILE).invokeExact(array, i, Value.getInstance(20)); + Value x = (Value) hs.get(TestAccessMode.GET_VOLATILE).invokeExact(array, i); + assertEquals(x, Value.getInstance(20), "setVolatile Value value"); + } + + // Lazy + { + hs.get(TestAccessMode.SET_RELEASE).invokeExact(array, i, Value.getInstance(10)); + Value x = (Value) hs.get(TestAccessMode.GET_ACQUIRE).invokeExact(array, i); + assertEquals(x, Value.getInstance(10), "setRelease Value value"); + } + + // Opaque + { + hs.get(TestAccessMode.SET_OPAQUE).invokeExact(array, i, Value.getInstance(20)); + Value x = (Value) hs.get(TestAccessMode.GET_OPAQUE).invokeExact(array, i); + assertEquals(x, Value.getInstance(20), "setOpaque Value value"); + } + + hs.get(TestAccessMode.SET).invokeExact(array, i, Value.getInstance(10)); + + // Compare + { + boolean r = (boolean) hs.get(TestAccessMode.COMPARE_AND_SET).invokeExact(array, i, Value.getInstance(10), Value.getInstance(20)); + assertEquals(r, true, "success compareAndSet Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(array, i); + assertEquals(x, Value.getInstance(20), "success compareAndSet Value value"); + } + + { + boolean r = (boolean) hs.get(TestAccessMode.COMPARE_AND_SET).invokeExact(array, i, Value.getInstance(10), Value.getInstance(30)); + assertEquals(r, false, "failing compareAndSet Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(array, i); + assertEquals(x, Value.getInstance(20), "failing compareAndSet Value value"); + } + + { + Value r = (Value) hs.get(TestAccessMode.COMPARE_AND_EXCHANGE).invokeExact(array, i, Value.getInstance(20), Value.getInstance(10)); + assertEquals(r, Value.getInstance(20), "success compareAndExchange Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(array, i); + assertEquals(x, Value.getInstance(10), "success compareAndExchange Value value"); + } + + { + Value r = (Value) hs.get(TestAccessMode.COMPARE_AND_EXCHANGE).invokeExact(array, i, Value.getInstance(20), Value.getInstance(30)); + assertEquals(r, Value.getInstance(10), "failing compareAndExchange Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(array, i); + assertEquals(x, Value.getInstance(10), "failing compareAndExchange Value value"); + } + + { + Value r = (Value) hs.get(TestAccessMode.COMPARE_AND_EXCHANGE_ACQUIRE).invokeExact(array, i, Value.getInstance(10), Value.getInstance(20)); + assertEquals(r, Value.getInstance(10), "success compareAndExchangeAcquire Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(array, i); + assertEquals(x, Value.getInstance(20), "success compareAndExchangeAcquire Value value"); + } + + { + Value r = (Value) hs.get(TestAccessMode.COMPARE_AND_EXCHANGE_ACQUIRE).invokeExact(array, i, Value.getInstance(10), Value.getInstance(30)); + assertEquals(r, Value.getInstance(20), "failing compareAndExchangeAcquire Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(array, i); + assertEquals(x, Value.getInstance(20), "failing compareAndExchangeAcquire Value value"); + } + + { + Value r = (Value) hs.get(TestAccessMode.COMPARE_AND_EXCHANGE_RELEASE).invokeExact(array, i, Value.getInstance(20), Value.getInstance(10)); + assertEquals(r, Value.getInstance(20), "success compareAndExchangeRelease Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(array, i); + assertEquals(x, Value.getInstance(10), "success compareAndExchangeRelease Value value"); + } + + { + Value r = (Value) hs.get(TestAccessMode.COMPARE_AND_EXCHANGE_RELEASE).invokeExact(array, i, Value.getInstance(20), Value.getInstance(30)); + assertEquals(r, Value.getInstance(10), "failing compareAndExchangeRelease Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(array, i); + assertEquals(x, Value.getInstance(10), "failing compareAndExchangeRelease Value value"); + } + + { + MethodHandle mh = hs.get(TestAccessMode.WEAK_COMPARE_AND_SET_PLAIN); + boolean success = false; + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = (boolean) mh.invokeExact(array, i, Value.getInstance(10), Value.getInstance(20)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSetPlain Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(array, i); + assertEquals(x, Value.getInstance(20), "success weakCompareAndSetPlain Value value"); + } + + { + boolean success = (boolean) hs.get(TestAccessMode.WEAK_COMPARE_AND_SET_PLAIN).invokeExact(array, i, Value.getInstance(10), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSetPlain Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(array, i); + assertEquals(x, Value.getInstance(20), "failing weakCompareAndSetPlain Value value"); + } + + { + MethodHandle mh = hs.get(TestAccessMode.WEAK_COMPARE_AND_SET_ACQUIRE); + boolean success = false; + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = (boolean) mh.invokeExact(array, i, Value.getInstance(20), Value.getInstance(10)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSetAcquire Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(array, i); + assertEquals(x, Value.getInstance(10), "success weakCompareAndSetAcquire Value"); + } + + { + boolean success = (boolean) hs.get(TestAccessMode.WEAK_COMPARE_AND_SET_ACQUIRE).invokeExact(array, i, Value.getInstance(20), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSetAcquire Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(array, i); + assertEquals(x, Value.getInstance(10), "failing weakCompareAndSetAcquire Value value"); + } + + { + MethodHandle mh = hs.get(TestAccessMode.WEAK_COMPARE_AND_SET_RELEASE); + boolean success = false; + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = (boolean) mh.invokeExact(array, i, Value.getInstance(10), Value.getInstance(20)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSetRelease Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(array, i); + assertEquals(x, Value.getInstance(20), "success weakCompareAndSetRelease Value"); + } + + { + boolean success = (boolean) hs.get(TestAccessMode.WEAK_COMPARE_AND_SET_ACQUIRE).invokeExact(array, i, Value.getInstance(10), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSetAcquire Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(array, i); + assertEquals(x, Value.getInstance(20), "failing weakCompareAndSetAcquire Value value"); + } + + { + MethodHandle mh = hs.get(TestAccessMode.WEAK_COMPARE_AND_SET); + boolean success = false; + for (int c = 0; c < WEAK_ATTEMPTS && !success; c++) { + success = (boolean) mh.invokeExact(array, i, Value.getInstance(20), Value.getInstance(10)); + if (!success) weakDelay(); + } + assertEquals(success, true, "success weakCompareAndSet Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(array, i); + assertEquals(x, Value.getInstance(10), "success weakCompareAndSet Value"); + } + + { + boolean success = (boolean) hs.get(TestAccessMode.WEAK_COMPARE_AND_SET).invokeExact(array, i, Value.getInstance(20), Value.getInstance(30)); + assertEquals(success, false, "failing weakCompareAndSet Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(array, i); + assertEquals(x, Value.getInstance(10), "failing weakCompareAndSet Value value"); + } + + // Compare set and get + { + hs.get(TestAccessMode.SET).invokeExact(array, i, Value.getInstance(10)); + + Value o = (Value) hs.get(TestAccessMode.GET_AND_SET).invokeExact(array, i, Value.getInstance(20)); + assertEquals(o, Value.getInstance(10), "getAndSet Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(array, i); + assertEquals(x, Value.getInstance(20), "getAndSet Value value"); + } + + { + hs.get(TestAccessMode.SET).invokeExact(array, i, Value.getInstance(10)); + + Value o = (Value) hs.get(TestAccessMode.GET_AND_SET_ACQUIRE).invokeExact(array, i, Value.getInstance(20)); + assertEquals(o, Value.getInstance(10), "getAndSetAcquire Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(array, i); + assertEquals(x, Value.getInstance(20), "getAndSetAcquire Value value"); + } + + { + hs.get(TestAccessMode.SET).invokeExact(array, i, Value.getInstance(10)); + + Value o = (Value) hs.get(TestAccessMode.GET_AND_SET_RELEASE).invokeExact(array, i, Value.getInstance(20)); + assertEquals(o, Value.getInstance(10), "getAndSetRelease Value"); + Value x = (Value) hs.get(TestAccessMode.GET).invokeExact(array, i); + assertEquals(x, Value.getInstance(20), "getAndSetRelease Value value"); + } + + + } + } + + static void testArrayUnsupported(Handles hs) throws Throwable { + Value[] array = new Value[10]; + + final int i = 0; + + for (TestAccessMode am : testAccessModesOfType(TestAccessType.GET_AND_ADD)) { + checkUOE(am, () -> { + Value o = (Value) hs.get(am).invokeExact(array, i, Value.getInstance(10)); + }); + } + + for (TestAccessMode am : testAccessModesOfType(TestAccessType.GET_AND_BITWISE)) { + checkUOE(am, () -> { + Value o = (Value) hs.get(am).invokeExact(array, i, Value.getInstance(10)); + }); + } + } + + static void testArrayIndexOutOfBounds(Handles hs) throws Throwable { + Value[] array = new Value[10]; + + for (int i : new int[]{-1, Integer.MIN_VALUE, 10, 11, Integer.MAX_VALUE}) { + final int ci = i; + + for (TestAccessMode am : testAccessModesOfType(TestAccessType.GET)) { + checkAIOOBE(am, () -> { + Value x = (Value) hs.get(am).invokeExact(array, ci); + }); + } + + for (TestAccessMode am : testAccessModesOfType(TestAccessType.SET)) { + checkAIOOBE(am, () -> { + hs.get(am).invokeExact(array, ci, Value.getInstance(10)); + }); + } + + for (TestAccessMode am : testAccessModesOfType(TestAccessType.COMPARE_AND_SET)) { + checkAIOOBE(am, () -> { + boolean r = (boolean) hs.get(am).invokeExact(array, ci, Value.getInstance(10), Value.getInstance(20)); + }); + } + + for (TestAccessMode am : testAccessModesOfType(TestAccessType.COMPARE_AND_EXCHANGE)) { + checkAIOOBE(am, () -> { + Value r = (Value) hs.get(am).invokeExact(array, ci, Value.getInstance(20), Value.getInstance(10)); + }); + } + + for (TestAccessMode am : testAccessModesOfType(TestAccessType.GET_AND_SET)) { + checkAIOOBE(am, () -> { + Value o = (Value) hs.get(am).invokeExact(array, ci, Value.getInstance(10)); + }); + } + + + } + } +} + diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeBoolean.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeBoolean.java index eb6327d4c55..a512d198f5b 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeBoolean.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeBoolean.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, 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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @bug 8156486 diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeByte.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeByte.java index 08b834699af..caab6dc62f0 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeByte.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeByte.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, 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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @bug 8156486 diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeChar.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeChar.java index 25aa62825b7..f3b7f151484 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeChar.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeChar.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, 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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @bug 8156486 diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeDouble.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeDouble.java index c3f8d22435b..4032bd36e79 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeDouble.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeDouble.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, 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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @bug 8156486 diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeFloat.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeFloat.java index 0d05270b3ac..d3ddc977901 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeFloat.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeFloat.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, 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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @bug 8156486 diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeInt.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeInt.java index a431c0a889e..e1957ea3504 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeInt.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeInt.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, 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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @bug 8156486 diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeLong.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeLong.java index 53b9257d700..4c90fbec0f9 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeLong.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeLong.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, 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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @bug 8156486 diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeShort.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeShort.java index 10dd5832c9b..3cf4bfe13b0 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeShort.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeShort.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, 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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @bug 8156486 diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeString.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeString.java index d823a21d69f..73b296c356d 100644 --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeString.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeString.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, 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 @@ -21,6 +21,8 @@ * questions. */ +// -- This file was mechanically generated: Do not edit! -- // + /* * @test * @bug 8156486 diff --git a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeValue.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeValue.java new file mode 100644 index 00000000000..b7f1ca69373 --- /dev/null +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestMethodTypeValue.java @@ -0,0 +1,2062 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// -- This file was mechanically generated: Do not edit! -- // + +/* + * @test + * @bug 8156486 + * @enablePreview + * @modules java.base/jdk.internal.vm.annotation + * @run testng/othervm VarHandleTestMethodTypeValue + * @run testng/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_GUARDS=true -Djava.lang.invoke.VarHandle.VAR_HANDLE_IDENTITY_ADAPT=true VarHandleTestMethodTypeValue + * @run testng/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_GUARDS=false -Djava.lang.invoke.VarHandle.VAR_HANDLE_IDENTITY_ADAPT=false VarHandleTestMethodTypeValue + * @run testng/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_GUARDS=false -Djava.lang.invoke.VarHandle.VAR_HANDLE_IDENTITY_ADAPT=true VarHandleTestMethodTypeValue + */ + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.testng.Assert.*; + +import static java.lang.invoke.MethodType.*; + +public class VarHandleTestMethodTypeValue extends VarHandleBaseTest { + static final Value static_final_v = Value.getInstance(10); + + static Value static_v = Value.getInstance(10); + + final Value final_v = Value.getInstance(10); + + Value v = Value.getInstance(10); + + VarHandle vhFinalField; + + VarHandle vhField; + + VarHandle vhStaticField; + + VarHandle vhStaticFinalField; + + VarHandle vhArray; + + @BeforeClass + public void setup() throws Exception { + vhFinalField = MethodHandles.lookup().findVarHandle( + VarHandleTestMethodTypeValue.class, "final_v", Value.class); + + vhField = MethodHandles.lookup().findVarHandle( + VarHandleTestMethodTypeValue.class, "v", Value.class); + + vhStaticFinalField = MethodHandles.lookup().findStaticVarHandle( + VarHandleTestMethodTypeValue.class, "static_final_v", Value.class); + + vhStaticField = MethodHandles.lookup().findStaticVarHandle( + VarHandleTestMethodTypeValue.class, "static_v", Value.class); + + vhArray = MethodHandles.arrayElementVarHandle(Value[].class); + } + + @DataProvider + public Object[][] accessTestCaseProvider() throws Exception { + List> cases = new ArrayList<>(); + + cases.add(new VarHandleAccessTestCase("Instance field", + vhField, vh -> testInstanceFieldWrongMethodType(this, vh), + false)); + + cases.add(new VarHandleAccessTestCase("Static field", + vhStaticField, VarHandleTestMethodTypeValue::testStaticFieldWrongMethodType, + false)); + + cases.add(new VarHandleAccessTestCase("Array", + vhArray, VarHandleTestMethodTypeValue::testArrayWrongMethodType, + false)); + + for (VarHandleToMethodHandle f : VarHandleToMethodHandle.values()) { + cases.add(new MethodHandleAccessTestCase("Instance field", + vhField, f, hs -> testInstanceFieldWrongMethodType(this, hs), + false)); + + cases.add(new MethodHandleAccessTestCase("Static field", + vhStaticField, f, VarHandleTestMethodTypeValue::testStaticFieldWrongMethodType, + false)); + + cases.add(new MethodHandleAccessTestCase("Array", + vhArray, f, VarHandleTestMethodTypeValue::testArrayWrongMethodType, + false)); + } + // Work around issue with jtreg summary reporting which truncates + // the String result of Object.toString to 30 characters, hence + // the first dummy argument + return cases.stream().map(tc -> new Object[]{tc.toString(), tc}).toArray(Object[][]::new); + } + + @Test(dataProvider = "accessTestCaseProvider") + public void testAccess(String desc, AccessTestCase atc) throws Throwable { + T t = atc.get(); + int iters = atc.requiresLoop() ? ITERS : 1; + for (int c = 0; c < iters; c++) { + atc.testAccess(t); + } + } + + + static void testInstanceFieldWrongMethodType(VarHandleTestMethodTypeValue recv, VarHandle vh) throws Throwable { + // Get + // Incorrect argument types + checkNPE(() -> { // null receiver + Value x = (Value) vh.get(null); + }); + checkCCE(() -> { // receiver reference class + Value x = (Value) vh.get(Void.class); + }); + checkWMTE(() -> { // receiver primitive class + Value x = (Value) vh.get(0); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void x = (Void) vh.get(recv); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.get(recv); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.get(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.get(recv, Void.class); + }); + + + // Set + // Incorrect argument types + checkNPE(() -> { // null receiver + vh.set(null, Value.getInstance(10)); + }); + checkCCE(() -> { // receiver reference class + vh.set(Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // value reference class + vh.set(recv, Void.class); + }); + checkWMTE(() -> { // receiver primitive class + vh.set(0, Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + vh.set(); + }); + checkWMTE(() -> { // > + vh.set(recv, Value.getInstance(10), Void.class); + }); + + + // GetVolatile + // Incorrect argument types + checkNPE(() -> { // null receiver + Value x = (Value) vh.getVolatile(null); + }); + checkCCE(() -> { // receiver reference class + Value x = (Value) vh.getVolatile(Void.class); + }); + checkWMTE(() -> { // receiver primitive class + Value x = (Value) vh.getVolatile(0); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void x = (Void) vh.getVolatile(recv); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.getVolatile(recv); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.getVolatile(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.getVolatile(recv, Void.class); + }); + + + // SetVolatile + // Incorrect argument types + checkNPE(() -> { // null receiver + vh.setVolatile(null, Value.getInstance(10)); + }); + checkCCE(() -> { // receiver reference class + vh.setVolatile(Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // value reference class + vh.setVolatile(recv, Void.class); + }); + checkWMTE(() -> { // receiver primitive class + vh.setVolatile(0, Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + vh.setVolatile(); + }); + checkWMTE(() -> { // > + vh.setVolatile(recv, Value.getInstance(10), Void.class); + }); + + + // GetOpaque + // Incorrect argument types + checkNPE(() -> { // null receiver + Value x = (Value) vh.getOpaque(null); + }); + checkCCE(() -> { // receiver reference class + Value x = (Value) vh.getOpaque(Void.class); + }); + checkWMTE(() -> { // receiver primitive class + Value x = (Value) vh.getOpaque(0); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void x = (Void) vh.getOpaque(recv); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.getOpaque(recv); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.getOpaque(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.getOpaque(recv, Void.class); + }); + + + // SetOpaque + // Incorrect argument types + checkNPE(() -> { // null receiver + vh.setOpaque(null, Value.getInstance(10)); + }); + checkCCE(() -> { // receiver reference class + vh.setOpaque(Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // value reference class + vh.setOpaque(recv, Void.class); + }); + checkWMTE(() -> { // receiver primitive class + vh.setOpaque(0, Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + vh.setOpaque(); + }); + checkWMTE(() -> { // > + vh.setOpaque(recv, Value.getInstance(10), Void.class); + }); + + + // GetAcquire + // Incorrect argument types + checkNPE(() -> { // null receiver + Value x = (Value) vh.getAcquire(null); + }); + checkCCE(() -> { // receiver reference class + Value x = (Value) vh.getAcquire(Void.class); + }); + checkWMTE(() -> { // receiver primitive class + Value x = (Value) vh.getAcquire(0); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void x = (Void) vh.getAcquire(recv); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.getAcquire(recv); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.getAcquire(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.getAcquire(recv, Void.class); + }); + + + // SetRelease + // Incorrect argument types + checkNPE(() -> { // null receiver + vh.setRelease(null, Value.getInstance(10)); + }); + checkCCE(() -> { // receiver reference class + vh.setRelease(Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // value reference class + vh.setRelease(recv, Void.class); + }); + checkWMTE(() -> { // receiver primitive class + vh.setRelease(0, Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + vh.setRelease(); + }); + checkWMTE(() -> { // > + vh.setRelease(recv, Value.getInstance(10), Void.class); + }); + + + // CompareAndSet + // Incorrect argument types + checkNPE(() -> { // null receiver + boolean r = vh.compareAndSet(null, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // receiver reference class + boolean r = vh.compareAndSet(Void.class, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // expected reference class + boolean r = vh.compareAndSet(recv, Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + boolean r = vh.compareAndSet(recv, Value.getInstance(10), Void.class); + }); + checkWMTE(() -> { // receiver primitive class + boolean r = vh.compareAndSet(0, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + boolean r = vh.compareAndSet(); + }); + checkWMTE(() -> { // > + boolean r = vh.compareAndSet(recv, Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // WeakCompareAndSet + // Incorrect argument types + checkNPE(() -> { // null receiver + boolean r = vh.weakCompareAndSetPlain(null, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // receiver reference class + boolean r = vh.weakCompareAndSetPlain(Void.class, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // expected reference class + boolean r = vh.weakCompareAndSetPlain(recv, Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + boolean r = vh.weakCompareAndSetPlain(recv, Value.getInstance(10), Void.class); + }); + checkWMTE(() -> { // receiver primitive class + boolean r = vh.weakCompareAndSetPlain(0, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + boolean r = vh.weakCompareAndSetPlain(); + }); + checkWMTE(() -> { // > + boolean r = vh.weakCompareAndSetPlain(recv, Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // WeakCompareAndSetVolatile + // Incorrect argument types + checkNPE(() -> { // null receiver + boolean r = vh.weakCompareAndSet(null, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // receiver reference class + boolean r = vh.weakCompareAndSet(Void.class, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // expected reference class + boolean r = vh.weakCompareAndSet(recv, Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + boolean r = vh.weakCompareAndSet(recv, Value.getInstance(10), Void.class); + }); + checkWMTE(() -> { // receiver primitive class + boolean r = vh.weakCompareAndSet(0, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + boolean r = vh.weakCompareAndSet(); + }); + checkWMTE(() -> { // > + boolean r = vh.weakCompareAndSet(recv, Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // WeakCompareAndSetAcquire + // Incorrect argument types + checkNPE(() -> { // null receiver + boolean r = vh.weakCompareAndSetAcquire(null, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // receiver reference class + boolean r = vh.weakCompareAndSetAcquire(Void.class, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // expected reference class + boolean r = vh.weakCompareAndSetAcquire(recv, Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + boolean r = vh.weakCompareAndSetAcquire(recv, Value.getInstance(10), Void.class); + }); + checkWMTE(() -> { // receiver primitive class + boolean r = vh.weakCompareAndSetAcquire(0, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + boolean r = vh.weakCompareAndSetAcquire(); + }); + checkWMTE(() -> { // > + boolean r = vh.weakCompareAndSetAcquire(recv, Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // WeakCompareAndSetRelease + // Incorrect argument types + checkNPE(() -> { // null receiver + boolean r = vh.weakCompareAndSetRelease(null, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // receiver reference class + boolean r = vh.weakCompareAndSetRelease(Void.class, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // expected reference class + boolean r = vh.weakCompareAndSetRelease(recv, Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + boolean r = vh.weakCompareAndSetRelease(recv, Value.getInstance(10), Void.class); + }); + checkWMTE(() -> { // receiver primitive class + boolean r = vh.weakCompareAndSetRelease(0, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + boolean r = vh.weakCompareAndSetRelease(); + }); + checkWMTE(() -> { // > + boolean r = vh.weakCompareAndSetRelease(recv, Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // CompareAndExchange + // Incorrect argument types + checkNPE(() -> { // null receiver + Value x = (Value) vh.compareAndExchange(null, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // receiver reference class + Value x = (Value) vh.compareAndExchange(Void.class, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // expected reference class + Value x = (Value) vh.compareAndExchange(recv, Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + Value x = (Value) vh.compareAndExchange(recv, Value.getInstance(10), Void.class); + }); + checkWMTE(() -> { // reciever primitive class + Value x = (Value) vh.compareAndExchange(0, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void r = (Void) vh.compareAndExchange(recv, Value.getInstance(10), Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.compareAndExchange(recv, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.compareAndExchange(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.compareAndExchange(recv, Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // CompareAndExchangeAcquire + // Incorrect argument types + checkNPE(() -> { // null receiver + Value x = (Value) vh.compareAndExchangeAcquire(null, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // receiver reference class + Value x = (Value) vh.compareAndExchangeAcquire(Void.class, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // expected reference class + Value x = (Value) vh.compareAndExchangeAcquire(recv, Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + Value x = (Value) vh.compareAndExchangeAcquire(recv, Value.getInstance(10), Void.class); + }); + checkWMTE(() -> { // reciever primitive class + Value x = (Value) vh.compareAndExchangeAcquire(0, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void r = (Void) vh.compareAndExchangeAcquire(recv, Value.getInstance(10), Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.compareAndExchangeAcquire(recv, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.compareAndExchangeAcquire(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.compareAndExchangeAcquire(recv, Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // CompareAndExchangeRelease + // Incorrect argument types + checkNPE(() -> { // null receiver + Value x = (Value) vh.compareAndExchangeRelease(null, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // receiver reference class + Value x = (Value) vh.compareAndExchangeRelease(Void.class, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // expected reference class + Value x = (Value) vh.compareAndExchangeRelease(recv, Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + Value x = (Value) vh.compareAndExchangeRelease(recv, Value.getInstance(10), Void.class); + }); + checkWMTE(() -> { // reciever primitive class + Value x = (Value) vh.compareAndExchangeRelease(0, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void r = (Void) vh.compareAndExchangeRelease(recv, Value.getInstance(10), Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.compareAndExchangeRelease(recv, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.compareAndExchangeRelease(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.compareAndExchangeRelease(recv, Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // GetAndSet + // Incorrect argument types + checkNPE(() -> { // null receiver + Value x = (Value) vh.getAndSet(null, Value.getInstance(10)); + }); + checkCCE(() -> { // receiver reference class + Value x = (Value) vh.getAndSet(Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // value reference class + Value x = (Value) vh.getAndSet(recv, Void.class); + }); + checkWMTE(() -> { // reciever primitive class + Value x = (Value) vh.getAndSet(0, Value.getInstance(10)); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void r = (Void) vh.getAndSet(recv, Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.getAndSet(recv, Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.getAndSet(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.getAndSet(recv, Value.getInstance(10), Void.class); + }); + + // GetAndSetAcquire + // Incorrect argument types + checkNPE(() -> { // null receiver + Value x = (Value) vh.getAndSetAcquire(null, Value.getInstance(10)); + }); + checkCCE(() -> { // receiver reference class + Value x = (Value) vh.getAndSetAcquire(Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // value reference class + Value x = (Value) vh.getAndSetAcquire(recv, Void.class); + }); + checkWMTE(() -> { // reciever primitive class + Value x = (Value) vh.getAndSetAcquire(0, Value.getInstance(10)); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void r = (Void) vh.getAndSetAcquire(recv, Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.getAndSetAcquire(recv, Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.getAndSetAcquire(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.getAndSetAcquire(recv, Value.getInstance(10), Void.class); + }); + + // GetAndSetRelease + // Incorrect argument types + checkNPE(() -> { // null receiver + Value x = (Value) vh.getAndSetRelease(null, Value.getInstance(10)); + }); + checkCCE(() -> { // receiver reference class + Value x = (Value) vh.getAndSetRelease(Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // value reference class + Value x = (Value) vh.getAndSetRelease(recv, Void.class); + }); + checkWMTE(() -> { // reciever primitive class + Value x = (Value) vh.getAndSetRelease(0, Value.getInstance(10)); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void r = (Void) vh.getAndSetRelease(recv, Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.getAndSetRelease(recv, Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.getAndSetRelease(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.getAndSetRelease(recv, Value.getInstance(10), Void.class); + }); + + + } + + static void testInstanceFieldWrongMethodType(VarHandleTestMethodTypeValue recv, Handles hs) throws Throwable { + for (TestAccessMode am : testAccessModesOfType(TestAccessType.GET)) { + // Incorrect argument types + checkNPE(() -> { // null receiver + Value x = (Value) hs.get(am, methodType(Value.class, VarHandleTestMethodTypeValue.class)). + invokeExact((VarHandleTestMethodTypeValue) null); + }); + hs.checkWMTEOrCCE(() -> { // receiver reference class + Value x = (Value) hs.get(am, methodType(Value.class, Class.class)). + invokeExact(Void.class); + }); + checkWMTE(() -> { // receiver primitive class + Value x = (Value) hs.get(am, methodType(Value.class, int.class)). + invokeExact(0); + }); + // Incorrect return type + hs.checkWMTEOrCCE(() -> { // reference class + Void x = (Void) hs.get(am, methodType(Void.class, VarHandleTestMethodTypeValue.class)). + invokeExact(recv); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) hs.get(am, methodType(boolean.class, VarHandleTestMethodTypeValue.class)). + invokeExact(recv); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) hs.get(am, methodType(Value.class)). + invokeExact(); + }); + checkWMTE(() -> { // > + Value x = (Value) hs.get(am, methodType(Value.class, VarHandleTestMethodTypeValue.class, Class.class)). + invokeExact(recv, Void.class); + }); + } + + for (TestAccessMode am : testAccessModesOfType(TestAccessType.SET)) { + // Incorrect argument types + checkNPE(() -> { // null receiver + hs.get(am, methodType(void.class, VarHandleTestMethodTypeValue.class, Value.class)). + invokeExact((VarHandleTestMethodTypeValue) null, Value.getInstance(10)); + }); + hs.checkWMTEOrCCE(() -> { // receiver reference class + hs.get(am, methodType(void.class, Class.class, Value.class)). + invokeExact(Void.class, Value.getInstance(10)); + }); + hs.checkWMTEOrCCE(() -> { // value reference class + hs.get(am, methodType(void.class, VarHandleTestMethodTypeValue.class, Class.class)). + invokeExact(recv, Void.class); + }); + checkWMTE(() -> { // receiver primitive class + hs.get(am, methodType(void.class, int.class, Value.class)). + invokeExact(0, Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + hs.get(am, methodType(void.class)). + invokeExact(); + }); + checkWMTE(() -> { // > + hs.get(am, methodType(void.class, VarHandleTestMethodTypeValue.class, Value.class, Class.class)). + invokeExact(recv, Value.getInstance(10), Void.class); + }); + } + + for (TestAccessMode am : testAccessModesOfType(TestAccessType.COMPARE_AND_SET)) { + // Incorrect argument types + checkNPE(() -> { // null receiver + boolean r = (boolean) hs.get(am, methodType(boolean.class, VarHandleTestMethodTypeValue.class, Value.class, Value.class)). + invokeExact((VarHandleTestMethodTypeValue) null, Value.getInstance(10), Value.getInstance(10)); + }); + hs.checkWMTEOrCCE(() -> { // receiver reference class + boolean r = (boolean) hs.get(am, methodType(boolean.class, Class.class, Value.class, Value.class)). + invokeExact(Void.class, Value.getInstance(10), Value.getInstance(10)); + }); + hs.checkWMTEOrCCE(() -> { // expected reference class + boolean r = (boolean) hs.get(am, methodType(boolean.class, VarHandleTestMethodTypeValue.class, Class.class, Value.class)). + invokeExact(recv, Void.class, Value.getInstance(10)); + }); + hs.checkWMTEOrCCE(() -> { // actual reference class + boolean r = (boolean) hs.get(am, methodType(boolean.class, VarHandleTestMethodTypeValue.class, Value.class, Class.class)). + invokeExact(recv, Value.getInstance(10), Void.class); + }); + checkWMTE(() -> { // receiver primitive class + boolean r = (boolean) hs.get(am, methodType(boolean.class, int.class , Value.class, Value.class)). + invokeExact(0, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + boolean r = (boolean) hs.get(am, methodType(boolean.class)). + invokeExact(); + }); + checkWMTE(() -> { // > + boolean r = (boolean) hs.get(am, methodType(boolean.class, VarHandleTestMethodTypeValue.class, Value.class, Value.class, Class.class)). + invokeExact(recv, Value.getInstance(10), Value.getInstance(10), Void.class); + }); + } + + for (TestAccessMode am : testAccessModesOfType(TestAccessType.COMPARE_AND_EXCHANGE)) { + checkNPE(() -> { // null receiver + Value x = (Value) hs.get(am, methodType(Value.class, VarHandleTestMethodTypeValue.class, Value.class, Value.class)). + invokeExact((VarHandleTestMethodTypeValue) null, Value.getInstance(10), Value.getInstance(10)); + }); + hs.checkWMTEOrCCE(() -> { // receiver reference class + Value x = (Value) hs.get(am, methodType(Value.class, Class.class, Value.class, Value.class)). + invokeExact(Void.class, Value.getInstance(10), Value.getInstance(10)); + }); + hs.checkWMTEOrCCE(() -> { // expected reference class + Value x = (Value) hs.get(am, methodType(Value.class, VarHandleTestMethodTypeValue.class, Class.class, Value.class)). + invokeExact(recv, Void.class, Value.getInstance(10)); + }); + hs.checkWMTEOrCCE(() -> { // actual reference class + Value x = (Value) hs.get(am, methodType(Value.class, VarHandleTestMethodTypeValue.class, Value.class, Class.class)). + invokeExact(recv, Value.getInstance(10), Void.class); + }); + checkWMTE(() -> { // reciever primitive class + Value x = (Value) hs.get(am, methodType(Value.class, int.class , Value.class, Value.class)). + invokeExact(0, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect return type + hs.checkWMTEOrCCE(() -> { // reference class + Void r = (Void) hs.get(am, methodType(Void.class, VarHandleTestMethodTypeValue.class , Value.class, Value.class)). + invokeExact(recv, Value.getInstance(10), Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) hs.get(am, methodType(boolean.class, VarHandleTestMethodTypeValue.class , Value.class, Value.class)). + invokeExact(recv, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) hs.get(am, methodType(Value.class)). + invokeExact(); + }); + checkWMTE(() -> { // > + Value x = (Value) hs.get(am, methodType(Value.class, VarHandleTestMethodTypeValue.class, Value.class, Value.class, Class.class)). + invokeExact(recv, Value.getInstance(10), Value.getInstance(10), Void.class); + }); + } + + for (TestAccessMode am : testAccessModesOfType(TestAccessType.GET_AND_SET)) { + checkNPE(() -> { // null receiver + Value x = (Value) hs.get(am, methodType(Value.class, VarHandleTestMethodTypeValue.class, Value.class)). + invokeExact((VarHandleTestMethodTypeValue) null, Value.getInstance(10)); + }); + hs.checkWMTEOrCCE(() -> { // receiver reference class + Value x = (Value) hs.get(am, methodType(Value.class, Class.class, Value.class)). + invokeExact(Void.class, Value.getInstance(10)); + }); + hs.checkWMTEOrCCE(() -> { // value reference class + Value x = (Value) hs.get(am, methodType(Value.class, VarHandleTestMethodTypeValue.class, Class.class)). + invokeExact(recv, Void.class); + }); + checkWMTE(() -> { // reciever primitive class + Value x = (Value) hs.get(am, methodType(Value.class, int.class, Value.class)). + invokeExact(0, Value.getInstance(10)); + }); + // Incorrect return type + hs.checkWMTEOrCCE(() -> { // reference class + Void r = (Void) hs.get(am, methodType(Void.class, VarHandleTestMethodTypeValue.class, Value.class)). + invokeExact(recv, Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) hs.get(am, methodType(boolean.class, VarHandleTestMethodTypeValue.class, Value.class)). + invokeExact(recv, Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) hs.get(am, methodType(Value.class)). + invokeExact(); + }); + checkWMTE(() -> { // > + Value x = (Value) hs.get(am, methodType(Value.class, VarHandleTestMethodTypeValue.class, Value.class)). + invokeExact(recv, Value.getInstance(10), Void.class); + }); + } + + + } + + + static void testStaticFieldWrongMethodType(VarHandle vh) throws Throwable { + // Get + // Incorrect return type + checkCCE(() -> { // reference class + Void x = (Void) vh.get(); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.get(); + }); + // Incorrect arity + checkWMTE(() -> { // > + Value x = (Value) vh.get(Void.class); + }); + + + // Set + // Incorrect argument types + checkCCE(() -> { // value reference class + vh.set(Void.class); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + vh.set(); + }); + checkWMTE(() -> { // > + vh.set(Value.getInstance(10), Void.class); + }); + + + // GetVolatile + // Incorrect return type + checkCCE(() -> { // reference class + Void x = (Void) vh.getVolatile(); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.getVolatile(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.getVolatile(Void.class); + }); + + + // SetVolatile + // Incorrect argument types + checkCCE(() -> { // value reference class + vh.setVolatile(Void.class); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + vh.setVolatile(); + }); + checkWMTE(() -> { // > + vh.setVolatile(Value.getInstance(10), Void.class); + }); + + + // GetOpaque + // Incorrect return type + checkCCE(() -> { // reference class + Void x = (Void) vh.getOpaque(); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.getOpaque(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.getOpaque(Void.class); + }); + + + // SetOpaque + // Incorrect argument types + checkCCE(() -> { // value reference class + vh.setOpaque(Void.class); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + vh.setOpaque(); + }); + checkWMTE(() -> { // > + vh.setOpaque(Value.getInstance(10), Void.class); + }); + + + // GetAcquire + // Incorrect return type + checkCCE(() -> { // reference class + Void x = (Void) vh.getAcquire(); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.getAcquire(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.getAcquire(Void.class); + }); + + + // SetRelease + // Incorrect argument types + checkCCE(() -> { // value reference class + vh.setRelease(Void.class); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + vh.setRelease(); + }); + checkWMTE(() -> { // > + vh.setRelease(Value.getInstance(10), Void.class); + }); + + + // CompareAndSet + // Incorrect argument types + checkCCE(() -> { // expected reference class + boolean r = vh.compareAndSet(Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + boolean r = vh.compareAndSet(Value.getInstance(10), Void.class); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + boolean r = vh.compareAndSet(); + }); + checkWMTE(() -> { // > + boolean r = vh.compareAndSet(Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // WeakCompareAndSet + // Incorrect argument types + checkCCE(() -> { // expected reference class + boolean r = vh.weakCompareAndSetPlain(Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + boolean r = vh.weakCompareAndSetPlain(Value.getInstance(10), Void.class); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + boolean r = vh.weakCompareAndSetPlain(); + }); + checkWMTE(() -> { // > + boolean r = vh.weakCompareAndSetPlain(Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // WeakCompareAndSetVolatile + // Incorrect argument types + checkCCE(() -> { // expected reference class + boolean r = vh.weakCompareAndSet(Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + boolean r = vh.weakCompareAndSet(Value.getInstance(10), Void.class); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + boolean r = vh.weakCompareAndSet(); + }); + checkWMTE(() -> { // > + boolean r = vh.weakCompareAndSet(Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // WeakCompareAndSetAcquire + // Incorrect argument types + checkCCE(() -> { // expected reference class + boolean r = vh.weakCompareAndSetAcquire(Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + boolean r = vh.weakCompareAndSetAcquire(Value.getInstance(10), Void.class); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + boolean r = vh.weakCompareAndSetAcquire(); + }); + checkWMTE(() -> { // > + boolean r = vh.weakCompareAndSetAcquire(Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // WeakCompareAndSetRelease + // Incorrect argument types + checkCCE(() -> { // expected reference class + boolean r = vh.weakCompareAndSetRelease(Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + boolean r = vh.weakCompareAndSetRelease(Value.getInstance(10), Void.class); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + boolean r = vh.weakCompareAndSetRelease(); + }); + checkWMTE(() -> { // > + boolean r = vh.weakCompareAndSetRelease(Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // CompareAndExchange + // Incorrect argument types + checkCCE(() -> { // expected reference class + Value x = (Value) vh.compareAndExchange(Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + Value x = (Value) vh.compareAndExchange(Value.getInstance(10), Void.class); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void r = (Void) vh.compareAndExchange(Value.getInstance(10), Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.compareAndExchange(Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.compareAndExchange(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.compareAndExchange(Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // CompareAndExchangeAcquire + // Incorrect argument types + checkCCE(() -> { // expected reference class + Value x = (Value) vh.compareAndExchangeAcquire(Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + Value x = (Value) vh.compareAndExchangeAcquire(Value.getInstance(10), Void.class); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void r = (Void) vh.compareAndExchangeAcquire(Value.getInstance(10), Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.compareAndExchangeAcquire(Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.compareAndExchangeAcquire(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.compareAndExchangeAcquire(Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // CompareAndExchangeRelease + // Incorrect argument types + checkCCE(() -> { // expected reference class + Value x = (Value) vh.compareAndExchangeRelease(Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + Value x = (Value) vh.compareAndExchangeRelease(Value.getInstance(10), Void.class); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void r = (Void) vh.compareAndExchangeRelease(Value.getInstance(10), Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.compareAndExchangeRelease(Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.compareAndExchangeRelease(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.compareAndExchangeRelease(Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // GetAndSet + // Incorrect argument types + checkCCE(() -> { // value reference class + Value x = (Value) vh.getAndSet(Void.class); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void r = (Void) vh.getAndSet(Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.getAndSet(Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.getAndSet(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.getAndSet(Value.getInstance(10), Void.class); + }); + + + // GetAndSetAcquire + // Incorrect argument types + checkCCE(() -> { // value reference class + Value x = (Value) vh.getAndSetAcquire(Void.class); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void r = (Void) vh.getAndSetAcquire(Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.getAndSetAcquire(Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.getAndSetAcquire(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.getAndSetAcquire(Value.getInstance(10), Void.class); + }); + + + // GetAndSetRelease + // Incorrect argument types + checkCCE(() -> { // value reference class + Value x = (Value) vh.getAndSetRelease(Void.class); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void r = (Void) vh.getAndSetRelease(Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.getAndSetRelease(Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.getAndSetRelease(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.getAndSetRelease(Value.getInstance(10), Void.class); + }); + + + } + + static void testStaticFieldWrongMethodType(Handles hs) throws Throwable { + int i = 0; + + for (TestAccessMode am : testAccessModesOfType(TestAccessType.GET)) { + // Incorrect return type + hs.checkWMTEOrCCE(() -> { // reference class + Void x = (Void) hs.get(am, methodType(Void.class)). + invokeExact(); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) hs.get(am, methodType(boolean.class)). + invokeExact(); + }); + // Incorrect arity + checkWMTE(() -> { // > + Value x = (Value) hs.get(am, methodType(Class.class)). + invokeExact(Void.class); + }); + } + + for (TestAccessMode am : testAccessModesOfType(TestAccessType.SET)) { + hs.checkWMTEOrCCE(() -> { // value reference class + hs.get(am, methodType(void.class, Class.class)). + invokeExact(Void.class); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + hs.get(am, methodType(void.class)). + invokeExact(); + }); + checkWMTE(() -> { // > + hs.get(am, methodType(void.class, Value.class, Class.class)). + invokeExact(Value.getInstance(10), Void.class); + }); + } + for (TestAccessMode am : testAccessModesOfType(TestAccessType.COMPARE_AND_SET)) { + // Incorrect argument types + hs.checkWMTEOrCCE(() -> { // expected reference class + boolean r = (boolean) hs.get(am, methodType(boolean.class, Class.class, Value.class)). + invokeExact(Void.class, Value.getInstance(10)); + }); + hs.checkWMTEOrCCE(() -> { // actual reference class + boolean r = (boolean) hs.get(am, methodType(boolean.class, Value.class, Class.class)). + invokeExact(Value.getInstance(10), Void.class); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + boolean r = (boolean) hs.get(am, methodType(boolean.class)). + invokeExact(); + }); + checkWMTE(() -> { // > + boolean r = (boolean) hs.get(am, methodType(boolean.class, Value.class, Value.class, Class.class)). + invokeExact(Value.getInstance(10), Value.getInstance(10), Void.class); + }); + } + + for (TestAccessMode am : testAccessModesOfType(TestAccessType.COMPARE_AND_EXCHANGE)) { + // Incorrect argument types + hs.checkWMTEOrCCE(() -> { // expected reference class + Value x = (Value) hs.get(am, methodType(Value.class, Class.class, Value.class)). + invokeExact(Void.class, Value.getInstance(10)); + }); + hs.checkWMTEOrCCE(() -> { // actual reference class + Value x = (Value) hs.get(am, methodType(Value.class, Value.class, Class.class)). + invokeExact(Value.getInstance(10), Void.class); + }); + // Incorrect return type + hs.checkWMTEOrCCE(() -> { // reference class + Void r = (Void) hs.get(am, methodType(Void.class, Value.class, Value.class)). + invokeExact(Value.getInstance(10), Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) hs.get(am, methodType(boolean.class, Value.class, Value.class)). + invokeExact(Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) hs.get(am, methodType(Value.class)). + invokeExact(); + }); + checkWMTE(() -> { // > + Value x = (Value) hs.get(am, methodType(Value.class, Value.class, Value.class, Class.class)). + invokeExact(Value.getInstance(10), Value.getInstance(10), Void.class); + }); + } + + for (TestAccessMode am : testAccessModesOfType(TestAccessType.GET_AND_SET)) { + // Incorrect argument types + hs.checkWMTEOrCCE(() -> { // value reference class + Value x = (Value) hs.get(am, methodType(Value.class, Class.class)). + invokeExact(Void.class); + }); + // Incorrect return type + hs.checkWMTEOrCCE(() -> { // reference class + Void r = (Void) hs.get(am, methodType(Void.class, Value.class)). + invokeExact(Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) hs.get(am, methodType(boolean.class, Value.class)). + invokeExact(Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) hs.get(am, methodType(Value.class)). + invokeExact(); + }); + checkWMTE(() -> { // > + Value x = (Value) hs.get(am, methodType(Value.class, Value.class, Class.class)). + invokeExact(Value.getInstance(10), Void.class); + }); + } + + + } + + + static void testArrayWrongMethodType(VarHandle vh) throws Throwable { + Value[] array = new Value[10]; + Arrays.fill(array, Value.getInstance(10)); + + // Get + // Incorrect argument types + checkNPE(() -> { // null array + Value x = (Value) vh.get(null, 0); + }); + checkCCE(() -> { // array reference class + Value x = (Value) vh.get(Void.class, 0); + }); + checkWMTE(() -> { // array primitive class + Value x = (Value) vh.get(0, 0); + }); + checkWMTE(() -> { // index reference class + Value x = (Value) vh.get(array, Void.class); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void x = (Void) vh.get(array, 0); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.get(array, 0); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.get(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.get(array, 0, Void.class); + }); + + + // Set + // Incorrect argument types + checkNPE(() -> { // null array + vh.set(null, 0, Value.getInstance(10)); + }); + checkCCE(() -> { // array reference class + vh.set(Void.class, 0, Value.getInstance(10)); + }); + checkCCE(() -> { // value reference class + vh.set(array, 0, Void.class); + }); + checkWMTE(() -> { // receiver primitive class + vh.set(0, 0, Value.getInstance(10)); + }); + checkWMTE(() -> { // index reference class + vh.set(array, Void.class, Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + vh.set(); + }); + checkWMTE(() -> { // > + vh.set(array, 0, Value.getInstance(10), Void.class); + }); + + + // GetVolatile + // Incorrect argument types + checkNPE(() -> { // null array + Value x = (Value) vh.getVolatile(null, 0); + }); + checkCCE(() -> { // array reference class + Value x = (Value) vh.getVolatile(Void.class, 0); + }); + checkWMTE(() -> { // array primitive class + Value x = (Value) vh.getVolatile(0, 0); + }); + checkWMTE(() -> { // index reference class + Value x = (Value) vh.getVolatile(array, Void.class); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void x = (Void) vh.getVolatile(array, 0); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.getVolatile(array, 0); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.getVolatile(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.getVolatile(array, 0, Void.class); + }); + + + // SetVolatile + // Incorrect argument types + checkNPE(() -> { // null array + vh.setVolatile(null, 0, Value.getInstance(10)); + }); + checkCCE(() -> { // array reference class + vh.setVolatile(Void.class, 0, Value.getInstance(10)); + }); + checkCCE(() -> { // value reference class + vh.setVolatile(array, 0, Void.class); + }); + checkWMTE(() -> { // receiver primitive class + vh.setVolatile(0, 0, Value.getInstance(10)); + }); + checkWMTE(() -> { // index reference class + vh.setVolatile(array, Void.class, Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + vh.setVolatile(); + }); + checkWMTE(() -> { // > + vh.setVolatile(array, 0, Value.getInstance(10), Void.class); + }); + + + // GetOpaque + // Incorrect argument types + checkNPE(() -> { // null array + Value x = (Value) vh.getOpaque(null, 0); + }); + checkCCE(() -> { // array reference class + Value x = (Value) vh.getOpaque(Void.class, 0); + }); + checkWMTE(() -> { // array primitive class + Value x = (Value) vh.getOpaque(0, 0); + }); + checkWMTE(() -> { // index reference class + Value x = (Value) vh.getOpaque(array, Void.class); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void x = (Void) vh.getOpaque(array, 0); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.getOpaque(array, 0); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.getOpaque(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.getOpaque(array, 0, Void.class); + }); + + + // SetOpaque + // Incorrect argument types + checkNPE(() -> { // null array + vh.setOpaque(null, 0, Value.getInstance(10)); + }); + checkCCE(() -> { // array reference class + vh.setOpaque(Void.class, 0, Value.getInstance(10)); + }); + checkCCE(() -> { // value reference class + vh.setOpaque(array, 0, Void.class); + }); + checkWMTE(() -> { // receiver primitive class + vh.setOpaque(0, 0, Value.getInstance(10)); + }); + checkWMTE(() -> { // index reference class + vh.setOpaque(array, Void.class, Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + vh.setOpaque(); + }); + checkWMTE(() -> { // > + vh.setOpaque(array, 0, Value.getInstance(10), Void.class); + }); + + + // GetAcquire + // Incorrect argument types + checkNPE(() -> { // null array + Value x = (Value) vh.getAcquire(null, 0); + }); + checkCCE(() -> { // array reference class + Value x = (Value) vh.getAcquire(Void.class, 0); + }); + checkWMTE(() -> { // array primitive class + Value x = (Value) vh.getAcquire(0, 0); + }); + checkWMTE(() -> { // index reference class + Value x = (Value) vh.getAcquire(array, Void.class); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void x = (Void) vh.getAcquire(array, 0); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.getAcquire(array, 0); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.getAcquire(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.getAcquire(array, 0, Void.class); + }); + + + // SetRelease + // Incorrect argument types + checkNPE(() -> { // null array + vh.setRelease(null, 0, Value.getInstance(10)); + }); + checkCCE(() -> { // array reference class + vh.setRelease(Void.class, 0, Value.getInstance(10)); + }); + checkCCE(() -> { // value reference class + vh.setRelease(array, 0, Void.class); + }); + checkWMTE(() -> { // receiver primitive class + vh.setRelease(0, 0, Value.getInstance(10)); + }); + checkWMTE(() -> { // index reference class + vh.setRelease(array, Void.class, Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + vh.setRelease(); + }); + checkWMTE(() -> { // > + vh.setRelease(array, 0, Value.getInstance(10), Void.class); + }); + + + // CompareAndSet + // Incorrect argument types + checkNPE(() -> { // null receiver + boolean r = vh.compareAndSet(null, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // receiver reference class + boolean r = vh.compareAndSet(Void.class, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // expected reference class + boolean r = vh.compareAndSet(array, 0, Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + boolean r = vh.compareAndSet(array, 0, Value.getInstance(10), Void.class); + }); + checkWMTE(() -> { // receiver primitive class + boolean r = vh.compareAndSet(0, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkWMTE(() -> { // index reference class + boolean r = vh.compareAndSet(array, Void.class, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + boolean r = vh.compareAndSet(); + }); + checkWMTE(() -> { // > + boolean r = vh.compareAndSet(array, 0, Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // WeakCompareAndSet + // Incorrect argument types + checkNPE(() -> { // null receiver + boolean r = vh.weakCompareAndSetPlain(null, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // receiver reference class + boolean r = vh.weakCompareAndSetPlain(Void.class, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // expected reference class + boolean r = vh.weakCompareAndSetPlain(array, 0, Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + boolean r = vh.weakCompareAndSetPlain(array, 0, Value.getInstance(10), Void.class); + }); + checkWMTE(() -> { // receiver primitive class + boolean r = vh.weakCompareAndSetPlain(0, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkWMTE(() -> { // index reference class + boolean r = vh.weakCompareAndSetPlain(array, Void.class, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + boolean r = vh.weakCompareAndSetPlain(); + }); + checkWMTE(() -> { // > + boolean r = vh.weakCompareAndSetPlain(array, 0, Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // WeakCompareAndSetVolatile + // Incorrect argument types + checkNPE(() -> { // null receiver + boolean r = vh.weakCompareAndSet(null, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // receiver reference class + boolean r = vh.weakCompareAndSet(Void.class, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // expected reference class + boolean r = vh.weakCompareAndSet(array, 0, Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + boolean r = vh.weakCompareAndSet(array, 0, Value.getInstance(10), Void.class); + }); + checkWMTE(() -> { // receiver primitive class + boolean r = vh.weakCompareAndSet(0, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkWMTE(() -> { // index reference class + boolean r = vh.weakCompareAndSet(array, Void.class, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + boolean r = vh.weakCompareAndSet(); + }); + checkWMTE(() -> { // > + boolean r = vh.weakCompareAndSet(array, 0, Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // WeakCompareAndSetAcquire + // Incorrect argument types + checkNPE(() -> { // null receiver + boolean r = vh.weakCompareAndSetAcquire(null, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // receiver reference class + boolean r = vh.weakCompareAndSetAcquire(Void.class, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // expected reference class + boolean r = vh.weakCompareAndSetAcquire(array, 0, Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + boolean r = vh.weakCompareAndSetAcquire(array, 0, Value.getInstance(10), Void.class); + }); + checkWMTE(() -> { // receiver primitive class + boolean r = vh.weakCompareAndSetAcquire(0, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkWMTE(() -> { // index reference class + boolean r = vh.weakCompareAndSetAcquire(array, Void.class, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + boolean r = vh.weakCompareAndSetAcquire(); + }); + checkWMTE(() -> { // > + boolean r = vh.weakCompareAndSetAcquire(array, 0, Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // WeakCompareAndSetRelease + // Incorrect argument types + checkNPE(() -> { // null receiver + boolean r = vh.weakCompareAndSetRelease(null, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // receiver reference class + boolean r = vh.weakCompareAndSetRelease(Void.class, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // expected reference class + boolean r = vh.weakCompareAndSetRelease(array, 0, Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + boolean r = vh.weakCompareAndSetRelease(array, 0, Value.getInstance(10), Void.class); + }); + checkWMTE(() -> { // receiver primitive class + boolean r = vh.weakCompareAndSetRelease(0, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkWMTE(() -> { // index reference class + boolean r = vh.weakCompareAndSetRelease(array, Void.class, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + boolean r = vh.weakCompareAndSetRelease(); + }); + checkWMTE(() -> { // > + boolean r = vh.weakCompareAndSetRelease(array, 0, Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // CompareAndExchange + // Incorrect argument types + checkNPE(() -> { // null receiver + Value x = (Value) vh.compareAndExchange(null, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // array reference class + Value x = (Value) vh.compareAndExchange(Void.class, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // expected reference class + Value x = (Value) vh.compareAndExchange(array, 0, Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + Value x = (Value) vh.compareAndExchange(array, 0, Value.getInstance(10), Void.class); + }); + checkWMTE(() -> { // array primitive class + Value x = (Value) vh.compareAndExchange(0, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkWMTE(() -> { // index reference class + Value x = (Value) vh.compareAndExchange(array, Void.class, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void r = (Void) vh.compareAndExchange(array, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.compareAndExchange(array, 0, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.compareAndExchange(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.compareAndExchange(array, 0, Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // CompareAndExchangeAcquire + // Incorrect argument types + checkNPE(() -> { // null receiver + Value x = (Value) vh.compareAndExchangeAcquire(null, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // array reference class + Value x = (Value) vh.compareAndExchangeAcquire(Void.class, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // expected reference class + Value x = (Value) vh.compareAndExchangeAcquire(array, 0, Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + Value x = (Value) vh.compareAndExchangeAcquire(array, 0, Value.getInstance(10), Void.class); + }); + checkWMTE(() -> { // array primitive class + Value x = (Value) vh.compareAndExchangeAcquire(0, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkWMTE(() -> { // index reference class + Value x = (Value) vh.compareAndExchangeAcquire(array, Void.class, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void r = (Void) vh.compareAndExchangeAcquire(array, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.compareAndExchangeAcquire(array, 0, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.compareAndExchangeAcquire(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.compareAndExchangeAcquire(array, 0, Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // CompareAndExchangeRelease + // Incorrect argument types + checkNPE(() -> { // null receiver + Value x = (Value) vh.compareAndExchangeRelease(null, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // array reference class + Value x = (Value) vh.compareAndExchangeRelease(Void.class, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkCCE(() -> { // expected reference class + Value x = (Value) vh.compareAndExchangeRelease(array, 0, Void.class, Value.getInstance(10)); + }); + checkCCE(() -> { // actual reference class + Value x = (Value) vh.compareAndExchangeRelease(array, 0, Value.getInstance(10), Void.class); + }); + checkWMTE(() -> { // array primitive class + Value x = (Value) vh.compareAndExchangeRelease(0, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkWMTE(() -> { // index reference class + Value x = (Value) vh.compareAndExchangeRelease(array, Void.class, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void r = (Void) vh.compareAndExchangeRelease(array, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.compareAndExchangeRelease(array, 0, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.compareAndExchangeRelease(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.compareAndExchangeRelease(array, 0, Value.getInstance(10), Value.getInstance(10), Void.class); + }); + + + // GetAndSet + // Incorrect argument types + checkNPE(() -> { // null array + Value x = (Value) vh.getAndSet(null, 0, Value.getInstance(10)); + }); + checkCCE(() -> { // array reference class + Value x = (Value) vh.getAndSet(Void.class, 0, Value.getInstance(10)); + }); + checkCCE(() -> { // value reference class + Value x = (Value) vh.getAndSet(array, 0, Void.class); + }); + checkWMTE(() -> { // reciarrayever primitive class + Value x = (Value) vh.getAndSet(0, 0, Value.getInstance(10)); + }); + checkWMTE(() -> { // index reference class + Value x = (Value) vh.getAndSet(array, Void.class, Value.getInstance(10)); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void r = (Void) vh.getAndSet(array, 0, Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.getAndSet(array, 0, Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.getAndSet(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.getAndSet(array, 0, Value.getInstance(10), Void.class); + }); + + + // GetAndSetAcquire + // Incorrect argument types + checkNPE(() -> { // null array + Value x = (Value) vh.getAndSetAcquire(null, 0, Value.getInstance(10)); + }); + checkCCE(() -> { // array reference class + Value x = (Value) vh.getAndSetAcquire(Void.class, 0, Value.getInstance(10)); + }); + checkCCE(() -> { // value reference class + Value x = (Value) vh.getAndSetAcquire(array, 0, Void.class); + }); + checkWMTE(() -> { // reciarrayever primitive class + Value x = (Value) vh.getAndSetAcquire(0, 0, Value.getInstance(10)); + }); + checkWMTE(() -> { // index reference class + Value x = (Value) vh.getAndSetAcquire(array, Void.class, Value.getInstance(10)); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void r = (Void) vh.getAndSetAcquire(array, 0, Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.getAndSetAcquire(array, 0, Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.getAndSetAcquire(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.getAndSetAcquire(array, 0, Value.getInstance(10), Void.class); + }); + + + // GetAndSetRelease + // Incorrect argument types + checkNPE(() -> { // null array + Value x = (Value) vh.getAndSetRelease(null, 0, Value.getInstance(10)); + }); + checkCCE(() -> { // array reference class + Value x = (Value) vh.getAndSetRelease(Void.class, 0, Value.getInstance(10)); + }); + checkCCE(() -> { // value reference class + Value x = (Value) vh.getAndSetRelease(array, 0, Void.class); + }); + checkWMTE(() -> { // reciarrayever primitive class + Value x = (Value) vh.getAndSetRelease(0, 0, Value.getInstance(10)); + }); + checkWMTE(() -> { // index reference class + Value x = (Value) vh.getAndSetRelease(array, Void.class, Value.getInstance(10)); + }); + // Incorrect return type + checkCCE(() -> { // reference class + Void r = (Void) vh.getAndSetRelease(array, 0, Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) vh.getAndSetRelease(array, 0, Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) vh.getAndSetRelease(); + }); + checkWMTE(() -> { // > + Value x = (Value) vh.getAndSetRelease(array, 0, Value.getInstance(10), Void.class); + }); + + + } + + static void testArrayWrongMethodType(Handles hs) throws Throwable { + Value[] array = new Value[10]; + Arrays.fill(array, Value.getInstance(10)); + + for (TestAccessMode am : testAccessModesOfType(TestAccessType.GET)) { + // Incorrect argument types + checkNPE(() -> { // null array + Value x = (Value) hs.get(am, methodType(Value.class, Value[].class, int.class)). + invokeExact((Value[]) null, 0); + }); + hs.checkWMTEOrCCE(() -> { // array reference class + Value x = (Value) hs.get(am, methodType(Value.class, Class.class, int.class)). + invokeExact(Void.class, 0); + }); + checkWMTE(() -> { // array primitive class + Value x = (Value) hs.get(am, methodType(Value.class, int.class, int.class)). + invokeExact(0, 0); + }); + checkWMTE(() -> { // index reference class + Value x = (Value) hs.get(am, methodType(Value.class, Value[].class, Class.class)). + invokeExact(array, Void.class); + }); + // Incorrect return type + hs.checkWMTEOrCCE(() -> { // reference class + Void x = (Void) hs.get(am, methodType(Void.class, Value[].class, int.class)). + invokeExact(array, 0); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) hs.get(am, methodType(boolean.class, Value[].class, int.class)). + invokeExact(array, 0); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) hs.get(am, methodType(Value.class)). + invokeExact(); + }); + checkWMTE(() -> { // > + Value x = (Value) hs.get(am, methodType(Value.class, Value[].class, int.class, Class.class)). + invokeExact(array, 0, Void.class); + }); + } + + for (TestAccessMode am : testAccessModesOfType(TestAccessType.SET)) { + // Incorrect argument types + checkNPE(() -> { // null array + hs.get(am, methodType(void.class, Value[].class, int.class, Value.class)). + invokeExact((Value[]) null, 0, Value.getInstance(10)); + }); + hs.checkWMTEOrCCE(() -> { // array reference class + hs.get(am, methodType(void.class, Class.class, int.class, Value.class)). + invokeExact(Void.class, 0, Value.getInstance(10)); + }); + hs.checkWMTEOrCCE(() -> { // value reference class + hs.get(am, methodType(void.class, Value[].class, int.class, Class.class)). + invokeExact(array, 0, Void.class); + }); + checkWMTE(() -> { // receiver primitive class + hs.get(am, methodType(void.class, int.class, int.class, Value.class)). + invokeExact(0, 0, Value.getInstance(10)); + }); + checkWMTE(() -> { // index reference class + hs.get(am, methodType(void.class, Value[].class, Class.class, Value.class)). + invokeExact(array, Void.class, Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + hs.get(am, methodType(void.class)). + invokeExact(); + }); + checkWMTE(() -> { // > + hs.get(am, methodType(void.class, Value[].class, int.class, Class.class)). + invokeExact(array, 0, Value.getInstance(10), Void.class); + }); + } + for (TestAccessMode am : testAccessModesOfType(TestAccessType.COMPARE_AND_SET)) { + // Incorrect argument types + checkNPE(() -> { // null receiver + boolean r = (boolean) hs.get(am, methodType(boolean.class, Value[].class, int.class, Value.class, Value.class)). + invokeExact((Value[]) null, 0, Value.getInstance(10), Value.getInstance(10)); + }); + hs.checkWMTEOrCCE(() -> { // receiver reference class + boolean r = (boolean) hs.get(am, methodType(boolean.class, Class.class, int.class, Value.class, Value.class)). + invokeExact(Void.class, 0, Value.getInstance(10), Value.getInstance(10)); + }); + hs.checkWMTEOrCCE(() -> { // expected reference class + boolean r = (boolean) hs.get(am, methodType(boolean.class, Value[].class, int.class, Class.class, Value.class)). + invokeExact(array, 0, Void.class, Value.getInstance(10)); + }); + hs.checkWMTEOrCCE(() -> { // actual reference class + boolean r = (boolean) hs.get(am, methodType(boolean.class, Value[].class, int.class, Value.class, Class.class)). + invokeExact(array, 0, Value.getInstance(10), Void.class); + }); + checkWMTE(() -> { // receiver primitive class + boolean r = (boolean) hs.get(am, methodType(boolean.class, int.class, int.class, Value.class, Value.class)). + invokeExact(0, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkWMTE(() -> { // index reference class + boolean r = (boolean) hs.get(am, methodType(boolean.class, Value[].class, Class.class, Value.class, Value.class)). + invokeExact(array, Void.class, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + boolean r = (boolean) hs.get(am, methodType(boolean.class)). + invokeExact(); + }); + checkWMTE(() -> { // > + boolean r = (boolean) hs.get(am, methodType(boolean.class, Value[].class, int.class, Value.class, Value.class, Class.class)). + invokeExact(array, 0, Value.getInstance(10), Value.getInstance(10), Void.class); + }); + } + + for (TestAccessMode am : testAccessModesOfType(TestAccessType.COMPARE_AND_EXCHANGE)) { + // Incorrect argument types + checkNPE(() -> { // null receiver + Value x = (Value) hs.get(am, methodType(Value.class, Value[].class, int.class, Value.class, Value.class)). + invokeExact((Value[]) null, 0, Value.getInstance(10), Value.getInstance(10)); + }); + hs.checkWMTEOrCCE(() -> { // array reference class + Value x = (Value) hs.get(am, methodType(Value.class, Class.class, int.class, Value.class, Value.class)). + invokeExact(Void.class, 0, Value.getInstance(10), Value.getInstance(10)); + }); + hs.checkWMTEOrCCE(() -> { // expected reference class + Value x = (Value) hs.get(am, methodType(Value.class, Value[].class, int.class, Class.class, Value.class)). + invokeExact(array, 0, Void.class, Value.getInstance(10)); + }); + hs.checkWMTEOrCCE(() -> { // actual reference class + Value x = (Value) hs.get(am, methodType(Value.class, Value[].class, int.class, Value.class, Class.class)). + invokeExact(array, 0, Value.getInstance(10), Void.class); + }); + checkWMTE(() -> { // array primitive class + Value x = (Value) hs.get(am, methodType(Value.class, int.class, int.class, Value.class, Value.class)). + invokeExact(0, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkWMTE(() -> { // index reference class + Value x = (Value) hs.get(am, methodType(Value.class, Value[].class, Class.class, Value.class, Value.class)). + invokeExact(array, Void.class, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect return type + hs.checkWMTEOrCCE(() -> { // reference class + Void r = (Void) hs.get(am, methodType(Void.class, Value[].class, int.class, Value.class, Value.class)). + invokeExact(array, 0, Value.getInstance(10), Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) hs.get(am, methodType(boolean.class, Value[].class, int.class, Value.class, Value.class)). + invokeExact(array, 0, Value.getInstance(10), Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) hs.get(am, methodType(Value.class)). + invokeExact(); + }); + checkWMTE(() -> { // > + Value x = (Value) hs.get(am, methodType(Value.class, Value[].class, int.class, Value.class, Value.class, Class.class)). + invokeExact(array, 0, Value.getInstance(10), Value.getInstance(10), Void.class); + }); + } + + for (TestAccessMode am : testAccessModesOfType(TestAccessType.GET_AND_SET)) { + // Incorrect argument types + checkNPE(() -> { // null array + Value x = (Value) hs.get(am, methodType(Value.class, Value[].class, int.class, Value.class)). + invokeExact((Value[]) null, 0, Value.getInstance(10)); + }); + hs.checkWMTEOrCCE(() -> { // array reference class + Value x = (Value) hs.get(am, methodType(Value.class, Class.class, int.class, Value.class)). + invokeExact(Void.class, 0, Value.getInstance(10)); + }); + hs.checkWMTEOrCCE(() -> { // value reference class + Value x = (Value) hs.get(am, methodType(Value.class, Value[].class, int.class, Class.class)). + invokeExact(array, 0, Void.class); + }); + checkWMTE(() -> { // array primitive class + Value x = (Value) hs.get(am, methodType(Value.class, int.class, int.class, Value.class)). + invokeExact(0, 0, Value.getInstance(10)); + }); + checkWMTE(() -> { // index reference class + Value x = (Value) hs.get(am, methodType(Value.class, Value[].class, Class.class, Value.class)). + invokeExact(array, Void.class, Value.getInstance(10)); + }); + // Incorrect return type + hs.checkWMTEOrCCE(() -> { // reference class + Void r = (Void) hs.get(am, methodType(Void.class, Value[].class, int.class, Value.class)). + invokeExact(array, 0, Value.getInstance(10)); + }); + checkWMTE(() -> { // primitive class + boolean x = (boolean) hs.get(am, methodType(boolean.class, Value[].class, int.class, Value.class)). + invokeExact(array, 0, Value.getInstance(10)); + }); + // Incorrect arity + checkWMTE(() -> { // 0 + Value x = (Value) hs.get(am, methodType(Value.class)). + invokeExact(); + }); + checkWMTE(() -> { // > + Value x = (Value) hs.get(am, methodType(Value.class, Value[].class, int.class, Value.class, Class.class)). + invokeExact(array, 0, Value.getInstance(10), Void.class); + }); + } + + + } +} diff --git a/test/jdk/java/lang/invoke/VarHandles/X-VarHandleTestAccess.java.template b/test/jdk/java/lang/invoke/VarHandles/X-VarHandleTestAccess.java.template index f3c90c5e8cc..2d377c603ae 100644 --- a/test/jdk/java/lang/invoke/VarHandles/X-VarHandleTestAccess.java.template +++ b/test/jdk/java/lang/invoke/VarHandles/X-VarHandleTestAccess.java.template @@ -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 @@ -21,8 +21,14 @@ * questions. */ +#warn + /* * @test +#if[Value] + * @enablePreview + * @modules java.base/jdk.internal.vm.annotation +#end[Value] * @run testng/othervm -Diters=10 -Xint VarHandleTestAccess$Type$ * * @comment Set CompileThresholdScaling to 0.1 so that the warmup loop sets to 2000 iterations @@ -72,9 +78,9 @@ public class VarHandleTestAccess$Type$ extends VarHandleBaseTest { VarHandle vhArray; -#if[String] +#if[Object] VarHandle vhArrayObject; -#end[String] +#end[Object] VarHandle[] allocate(boolean same) { List vhs = new ArrayList<>(); @@ -130,9 +136,9 @@ public class VarHandleTestAccess$Type$ extends VarHandleBaseTest { VarHandleTestAccess$Type$.class, "static_v", $type$.class); vhArray = MethodHandles.arrayElementVarHandle($type$[].class); -#if[String] +#if[Object] vhArrayObject = MethodHandles.arrayElementVarHandle(Object[].class); -#end[String] +#end[Object] } @@ -314,21 +320,21 @@ public class VarHandleTestAccess$Type$ extends VarHandleBaseTest { cases.add(new VarHandleAccessTestCase("Array", vhArray, VarHandleTestAccess$Type$::testArray)); -#if[String] +#if[Object] cases.add(new VarHandleAccessTestCase("Array Object[]", vhArrayObject, VarHandleTestAccess$Type$::testArray)); -#end[String] +#end[Object] cases.add(new VarHandleAccessTestCase("Array unsupported", vhArray, VarHandleTestAccess$Type$::testArrayUnsupported, false)); cases.add(new VarHandleAccessTestCase("Array index out of bounds", vhArray, VarHandleTestAccess$Type$::testArrayIndexOutOfBounds, false)); -#if[String] +#if[Object] cases.add(new VarHandleAccessTestCase("Array store exception", vhArrayObject, VarHandleTestAccess$Type$::testArrayStoreException, false)); -#end[String] +#end[Object] // Work around issue with jtreg summary reporting which truncates // the String result of Object.toString to 30 characters, hence // the first dummy argument @@ -2003,7 +2009,7 @@ public class VarHandleTestAccess$Type$ extends VarHandleBaseTest { } } -#if[String] +#if[Object] static void testArrayStoreException(VarHandle vh) throws Throwable { Object[] array = new $type$[10]; Arrays.fill(array, $value1$); @@ -2084,6 +2090,6 @@ public class VarHandleTestAccess$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.getAndSetRelease(array, 0, value); }); } -#end[String] +#end[Object] } diff --git a/test/jdk/java/lang/invoke/VarHandles/X-VarHandleTestMethodHandleAccess.java.template b/test/jdk/java/lang/invoke/VarHandles/X-VarHandleTestMethodHandleAccess.java.template index 4a9fa7c4fe8..cfda0b0c795 100644 --- a/test/jdk/java/lang/invoke/VarHandles/X-VarHandleTestMethodHandleAccess.java.template +++ b/test/jdk/java/lang/invoke/VarHandles/X-VarHandleTestMethodHandleAccess.java.template @@ -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 @@ -21,8 +21,14 @@ * questions. */ +#warn + /* * @test +#if[Value] + * @enablePreview + * @modules java.base/jdk.internal.vm.annotation +#end[Value] * @comment Set CompileThresholdScaling to 0.1 so that the warmup loop sets to 2000 iterations * to hit compilation thresholds * @run testng/othervm -Diters=2000 -XX:CompileThresholdScaling=0.1 VarHandleTestMethodHandleAccess$Type$ diff --git a/test/jdk/java/lang/invoke/VarHandles/X-VarHandleTestMethodType.java.template b/test/jdk/java/lang/invoke/VarHandles/X-VarHandleTestMethodType.java.template index 85ecf9bb95c..4f7601c3c14 100644 --- a/test/jdk/java/lang/invoke/VarHandles/X-VarHandleTestMethodType.java.template +++ b/test/jdk/java/lang/invoke/VarHandles/X-VarHandleTestMethodType.java.template @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, 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 @@ -21,9 +21,15 @@ * questions. */ +#warn + /* * @test * @bug 8156486 +#if[Value] + * @enablePreview + * @modules java.base/jdk.internal.vm.annotation +#end[Value] * @run testng/othervm VarHandleTestMethodType$Type$ * @run testng/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_GUARDS=true -Djava.lang.invoke.VarHandle.VAR_HANDLE_IDENTITY_ADAPT=true VarHandleTestMethodType$Type$ * @run testng/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_GUARDS=false -Djava.lang.invoke.VarHandle.VAR_HANDLE_IDENTITY_ADAPT=false VarHandleTestMethodType$Type$ @@ -138,7 +144,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.get(0); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void x = (Void) vh.get(recv); }); checkWMTE(() -> { // primitive class @@ -161,7 +167,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class vh.set(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class vh.set(recv, Void.class); }); checkWMTE(() -> { // receiver primitive class @@ -188,7 +194,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.getVolatile(0); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void x = (Void) vh.getVolatile(recv); }); checkWMTE(() -> { // primitive class @@ -211,7 +217,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class vh.setVolatile(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class vh.setVolatile(recv, Void.class); }); checkWMTE(() -> { // receiver primitive class @@ -238,7 +244,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.getOpaque(0); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void x = (Void) vh.getOpaque(recv); }); checkWMTE(() -> { // primitive class @@ -261,7 +267,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class vh.setOpaque(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class vh.setOpaque(recv, Void.class); }); checkWMTE(() -> { // receiver primitive class @@ -288,7 +294,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.getAcquire(0); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void x = (Void) vh.getAcquire(recv); }); checkWMTE(() -> { // primitive class @@ -311,7 +317,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class vh.setRelease(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class vh.setRelease(recv, Void.class); }); checkWMTE(() -> { // receiver primitive class @@ -335,10 +341,10 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class boolean r = vh.compareAndSet(Void.class, $value1$, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class boolean r = vh.compareAndSet(recv, Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class boolean r = vh.compareAndSet(recv, $value1$, Void.class); }); checkWMTE(() -> { // receiver primitive class @@ -361,10 +367,10 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class boolean r = vh.weakCompareAndSetPlain(Void.class, $value1$, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class boolean r = vh.weakCompareAndSetPlain(recv, Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class boolean r = vh.weakCompareAndSetPlain(recv, $value1$, Void.class); }); checkWMTE(() -> { // receiver primitive class @@ -387,10 +393,10 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class boolean r = vh.weakCompareAndSet(Void.class, $value1$, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class boolean r = vh.weakCompareAndSet(recv, Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class boolean r = vh.weakCompareAndSet(recv, $value1$, Void.class); }); checkWMTE(() -> { // receiver primitive class @@ -413,10 +419,10 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class boolean r = vh.weakCompareAndSetAcquire(Void.class, $value1$, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class boolean r = vh.weakCompareAndSetAcquire(recv, Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class boolean r = vh.weakCompareAndSetAcquire(recv, $value1$, Void.class); }); checkWMTE(() -> { // receiver primitive class @@ -439,10 +445,10 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class boolean r = vh.weakCompareAndSetRelease(Void.class, $value1$, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class boolean r = vh.weakCompareAndSetRelease(recv, Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class boolean r = vh.weakCompareAndSetRelease(recv, $value1$, Void.class); }); checkWMTE(() -> { // receiver primitive class @@ -465,17 +471,17 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class $type$ x = ($type$) vh.compareAndExchange(Void.class, $value1$, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class $type$ x = ($type$) vh.compareAndExchange(recv, Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class $type$ x = ($type$) vh.compareAndExchange(recv, $value1$, Void.class); }); checkWMTE(() -> { // reciever primitive class $type$ x = ($type$) vh.compareAndExchange(0, $value1$, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.compareAndExchange(recv, $value1$, $value1$); }); checkWMTE(() -> { // primitive class @@ -498,17 +504,17 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class $type$ x = ($type$) vh.compareAndExchangeAcquire(Void.class, $value1$, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class $type$ x = ($type$) vh.compareAndExchangeAcquire(recv, Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class $type$ x = ($type$) vh.compareAndExchangeAcquire(recv, $value1$, Void.class); }); checkWMTE(() -> { // reciever primitive class $type$ x = ($type$) vh.compareAndExchangeAcquire(0, $value1$, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.compareAndExchangeAcquire(recv, $value1$, $value1$); }); checkWMTE(() -> { // primitive class @@ -531,17 +537,17 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class $type$ x = ($type$) vh.compareAndExchangeRelease(Void.class, $value1$, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class $type$ x = ($type$) vh.compareAndExchangeRelease(recv, Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class $type$ x = ($type$) vh.compareAndExchangeRelease(recv, $value1$, Void.class); }); checkWMTE(() -> { // reciever primitive class $type$ x = ($type$) vh.compareAndExchangeRelease(0, $value1$, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.compareAndExchangeRelease(recv, $value1$, $value1$); }); checkWMTE(() -> { // primitive class @@ -564,14 +570,14 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class $type$ x = ($type$) vh.getAndSet(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndSet(recv, Void.class); }); checkWMTE(() -> { // reciever primitive class $type$ x = ($type$) vh.getAndSet(0, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndSet(recv, $value1$); }); checkWMTE(() -> { // primitive class @@ -593,14 +599,14 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class $type$ x = ($type$) vh.getAndSetAcquire(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndSetAcquire(recv, Void.class); }); checkWMTE(() -> { // reciever primitive class $type$ x = ($type$) vh.getAndSetAcquire(0, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndSetAcquire(recv, $value1$); }); checkWMTE(() -> { // primitive class @@ -622,14 +628,14 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class $type$ x = ($type$) vh.getAndSetRelease(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndSetRelease(recv, Void.class); }); checkWMTE(() -> { // reciever primitive class $type$ x = ($type$) vh.getAndSetRelease(0, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndSetRelease(recv, $value1$); }); checkWMTE(() -> { // primitive class @@ -653,14 +659,14 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class $type$ x = ($type$) vh.getAndAdd(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndAdd(recv, Void.class); }); checkWMTE(() -> { // reciever primitive class $type$ x = ($type$) vh.getAndAdd(0, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndAdd(recv, $value1$); }); checkWMTE(() -> { // primitive class @@ -682,14 +688,14 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class $type$ x = ($type$) vh.getAndAddAcquire(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndAddAcquire(recv, Void.class); }); checkWMTE(() -> { // reciever primitive class $type$ x = ($type$) vh.getAndAddAcquire(0, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndAddAcquire(recv, $value1$); }); checkWMTE(() -> { // primitive class @@ -711,14 +717,14 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class $type$ x = ($type$) vh.getAndAddRelease(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndAddRelease(recv, Void.class); }); checkWMTE(() -> { // reciever primitive class $type$ x = ($type$) vh.getAndAddRelease(0, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndAddRelease(recv, $value1$); }); checkWMTE(() -> { // primitive class @@ -742,14 +748,14 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class $type$ x = ($type$) vh.getAndBitwiseOr(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseOr(recv, Void.class); }); checkWMTE(() -> { // reciever primitive class $type$ x = ($type$) vh.getAndBitwiseOr(0, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseOr(recv, $value1$); }); checkWMTE(() -> { // primitive class @@ -772,14 +778,14 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class $type$ x = ($type$) vh.getAndBitwiseOrAcquire(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseOrAcquire(recv, Void.class); }); checkWMTE(() -> { // reciever primitive class $type$ x = ($type$) vh.getAndBitwiseOrAcquire(0, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseOrAcquire(recv, $value1$); }); checkWMTE(() -> { // primitive class @@ -802,14 +808,14 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class $type$ x = ($type$) vh.getAndBitwiseOr(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseOr(recv, Void.class); }); checkWMTE(() -> { // reciever primitive class $type$ x = ($type$) vh.getAndBitwiseOr(0, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseOr(recv, $value1$); }); checkWMTE(() -> { // primitive class @@ -832,14 +838,14 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class $type$ x = ($type$) vh.getAndBitwiseAnd(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseAnd(recv, Void.class); }); checkWMTE(() -> { // reciever primitive class $type$ x = ($type$) vh.getAndBitwiseAnd(0, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseAnd(recv, $value1$); }); checkWMTE(() -> { // primitive class @@ -862,14 +868,14 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class $type$ x = ($type$) vh.getAndBitwiseAndAcquire(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseAndAcquire(recv, Void.class); }); checkWMTE(() -> { // reciever primitive class $type$ x = ($type$) vh.getAndBitwiseAndAcquire(0, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseAndAcquire(recv, $value1$); }); checkWMTE(() -> { // primitive class @@ -892,14 +898,14 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class $type$ x = ($type$) vh.getAndBitwiseAnd(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseAnd(recv, Void.class); }); checkWMTE(() -> { // reciever primitive class $type$ x = ($type$) vh.getAndBitwiseAnd(0, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseAnd(recv, $value1$); }); checkWMTE(() -> { // primitive class @@ -922,14 +928,14 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class $type$ x = ($type$) vh.getAndBitwiseXor(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseXor(recv, Void.class); }); checkWMTE(() -> { // reciever primitive class $type$ x = ($type$) vh.getAndBitwiseXor(0, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseXor(recv, $value1$); }); checkWMTE(() -> { // primitive class @@ -952,14 +958,14 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class $type$ x = ($type$) vh.getAndBitwiseXorAcquire(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseXorAcquire(recv, Void.class); }); checkWMTE(() -> { // reciever primitive class $type$ x = ($type$) vh.getAndBitwiseXorAcquire(0, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseXorAcquire(recv, $value1$); }); checkWMTE(() -> { // primitive class @@ -982,14 +988,14 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class $type$ x = ($type$) vh.getAndBitwiseXor(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseXor(recv, Void.class); }); checkWMTE(() -> { // reciever primitive class $type$ x = ($type$) vh.getAndBitwiseXor(0, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseXor(recv, $value1$); }); checkWMTE(() -> { // primitive class @@ -1021,7 +1027,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { invokeExact(0); }); // Incorrect return type - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class Void x = (Void) hs.get(am, methodType(Void.class, VarHandleTestMethodType$Type$.class)). invokeExact(recv); }); @@ -1050,7 +1056,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { hs.get(am, methodType(void.class, Class.class, $type$.class)). invokeExact(Void.class, $value1$); }); - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // value reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // value reference class hs.get(am, methodType(void.class, VarHandleTestMethodType$Type$.class, Class.class)). invokeExact(recv, Void.class); }); @@ -1080,11 +1086,11 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { boolean r = (boolean) hs.get(am, methodType(boolean.class, Class.class, $type$.class, $type$.class)). invokeExact(Void.class, $value1$, $value1$); }); - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // expected reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // expected reference class boolean r = (boolean) hs.get(am, methodType(boolean.class, VarHandleTestMethodType$Type$.class, Class.class, $type$.class)). invokeExact(recv, Void.class, $value1$); }); - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // actual reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // actual reference class boolean r = (boolean) hs.get(am, methodType(boolean.class, VarHandleTestMethodType$Type$.class, $type$.class, Class.class)). invokeExact(recv, $value1$, Void.class); }); @@ -1112,11 +1118,11 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) hs.get(am, methodType($type$.class, Class.class, $type$.class, $type$.class)). invokeExact(Void.class, $value1$, $value1$); }); - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // expected reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // expected reference class $type$ x = ($type$) hs.get(am, methodType($type$.class, VarHandleTestMethodType$Type$.class, Class.class, $type$.class)). invokeExact(recv, Void.class, $value1$); }); - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // actual reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // actual reference class $type$ x = ($type$) hs.get(am, methodType($type$.class, VarHandleTestMethodType$Type$.class, $type$.class, Class.class)). invokeExact(recv, $value1$, Void.class); }); @@ -1125,7 +1131,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { invokeExact(0, $value1$, $value1$); }); // Incorrect return type - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class Void r = (Void) hs.get(am, methodType(Void.class, VarHandleTestMethodType$Type$.class , $type$.class, $type$.class)). invokeExact(recv, $value1$, $value1$); }); @@ -1153,7 +1159,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) hs.get(am, methodType($type$.class, Class.class, $type$.class)). invokeExact(Void.class, $value1$); }); - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // value reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // value reference class $type$ x = ($type$) hs.get(am, methodType($type$.class, VarHandleTestMethodType$Type$.class, Class.class)). invokeExact(recv, Void.class); }); @@ -1162,7 +1168,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { invokeExact(0, $value1$); }); // Incorrect return type - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class Void r = (Void) hs.get(am, methodType(Void.class, VarHandleTestMethodType$Type$.class, $type$.class)). invokeExact(recv, $value1$); }); @@ -1192,7 +1198,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) hs.get(am, methodType($type$.class, Class.class, $type$.class)). invokeExact(Void.class, $value1$); }); - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // value reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // value reference class $type$ x = ($type$) hs.get(am, methodType($type$.class, VarHandleTestMethodType$Type$.class, Class.class)). invokeExact(recv, Void.class); }); @@ -1201,7 +1207,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { invokeExact(0, $value1$); }); // Incorrect return type - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class Void r = (Void) hs.get(am, methodType(Void.class, VarHandleTestMethodType$Type$.class, $type$.class)). invokeExact(recv, $value1$); }); @@ -1231,7 +1237,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) hs.get(am, methodType($type$.class, Class.class, $type$.class)). invokeExact(Void.class, $value1$); }); - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // value reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // value reference class $type$ x = ($type$) hs.get(am, methodType($type$.class, VarHandleTestMethodType$Type$.class, Class.class)). invokeExact(recv, Void.class); }); @@ -1240,7 +1246,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { invokeExact(0, $value1$); }); // Incorrect return type - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class Void r = (Void) hs.get(am, methodType(Void.class, VarHandleTestMethodType$Type$.class, $type$.class)). invokeExact(recv, $value1$); }); @@ -1265,7 +1271,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { static void testStaticFieldWrongMethodType(VarHandle vh) throws Throwable { // Get // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void x = (Void) vh.get(); }); checkWMTE(() -> { // primitive class @@ -1279,7 +1285,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // Set // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class vh.set(Void.class); }); // Incorrect arity @@ -1293,7 +1299,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // GetVolatile // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void x = (Void) vh.getVolatile(); }); checkWMTE(() -> { // primitive class @@ -1306,7 +1312,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // SetVolatile // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class vh.setVolatile(Void.class); }); // Incorrect arity @@ -1320,7 +1326,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // GetOpaque // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void x = (Void) vh.getOpaque(); }); checkWMTE(() -> { // primitive class @@ -1333,7 +1339,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // SetOpaque // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class vh.setOpaque(Void.class); }); // Incorrect arity @@ -1347,7 +1353,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // GetAcquire // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void x = (Void) vh.getAcquire(); }); checkWMTE(() -> { // primitive class @@ -1360,7 +1366,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // SetRelease // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class vh.setRelease(Void.class); }); // Incorrect arity @@ -1375,10 +1381,10 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { #if[CAS] // CompareAndSet // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class boolean r = vh.compareAndSet(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class boolean r = vh.compareAndSet($value1$, Void.class); }); // Incorrect arity @@ -1392,10 +1398,10 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // WeakCompareAndSet // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class boolean r = vh.weakCompareAndSetPlain(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class boolean r = vh.weakCompareAndSetPlain($value1$, Void.class); }); // Incorrect arity @@ -1409,10 +1415,10 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // WeakCompareAndSetVolatile // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class boolean r = vh.weakCompareAndSet(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class boolean r = vh.weakCompareAndSet($value1$, Void.class); }); // Incorrect arity @@ -1426,10 +1432,10 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // WeakCompareAndSetAcquire // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class boolean r = vh.weakCompareAndSetAcquire(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class boolean r = vh.weakCompareAndSetAcquire($value1$, Void.class); }); // Incorrect arity @@ -1443,10 +1449,10 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // WeakCompareAndSetRelease // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class boolean r = vh.weakCompareAndSetRelease(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class boolean r = vh.weakCompareAndSetRelease($value1$, Void.class); }); // Incorrect arity @@ -1460,14 +1466,14 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // CompareAndExchange // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class $type$ x = ($type$) vh.compareAndExchange(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class $type$ x = ($type$) vh.compareAndExchange($value1$, Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.compareAndExchange($value1$, $value1$); }); checkWMTE(() -> { // primitive class @@ -1484,14 +1490,14 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // CompareAndExchangeAcquire // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class $type$ x = ($type$) vh.compareAndExchangeAcquire(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class $type$ x = ($type$) vh.compareAndExchangeAcquire($value1$, Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.compareAndExchangeAcquire($value1$, $value1$); }); checkWMTE(() -> { // primitive class @@ -1508,14 +1514,14 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // CompareAndExchangeRelease // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class $type$ x = ($type$) vh.compareAndExchangeRelease(Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class $type$ x = ($type$) vh.compareAndExchangeRelease($value1$, Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.compareAndExchangeRelease($value1$, $value1$); }); checkWMTE(() -> { // primitive class @@ -1532,11 +1538,11 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // GetAndSet // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndSet(Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndSet($value1$); }); checkWMTE(() -> { // primitive class @@ -1553,11 +1559,11 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // GetAndSetAcquire // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndSetAcquire(Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndSetAcquire($value1$); }); checkWMTE(() -> { // primitive class @@ -1574,11 +1580,11 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // GetAndSetRelease // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndSetRelease(Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndSetRelease($value1$); }); checkWMTE(() -> { // primitive class @@ -1596,11 +1602,11 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { #if[AtomicAdd] // GetAndAdd // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndAdd(Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndAdd($value1$); }); checkWMTE(() -> { // primitive class @@ -1617,11 +1623,11 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // GetAndAddAcquire // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndAddAcquire(Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndAddAcquire($value1$); }); checkWMTE(() -> { // primitive class @@ -1638,11 +1644,11 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // GetAndAddRelease // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndAddRelease(Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndAddRelease($value1$); }); checkWMTE(() -> { // primitive class @@ -1660,11 +1666,11 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { #if[Bitwise] // GetAndBitwiseOr // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseOr(Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseOr($value1$); }); checkWMTE(() -> { // primitive class @@ -1681,11 +1687,11 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // GetAndBitwiseOrAcquire // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseOrAcquire(Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseOrAcquire($value1$); }); checkWMTE(() -> { // primitive class @@ -1702,11 +1708,11 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // GetAndBitwiseOrReleaseRelease // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseOrRelease(Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseOrRelease($value1$); }); checkWMTE(() -> { // primitive class @@ -1723,11 +1729,11 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // GetAndBitwiseAnd // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseAnd(Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseAnd($value1$); }); checkWMTE(() -> { // primitive class @@ -1744,11 +1750,11 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // GetAndBitwiseAndAcquire // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseAndAcquire(Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseAndAcquire($value1$); }); checkWMTE(() -> { // primitive class @@ -1765,11 +1771,11 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // GetAndBitwiseAndReleaseRelease // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseAndRelease(Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseAndRelease($value1$); }); checkWMTE(() -> { // primitive class @@ -1786,11 +1792,11 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // GetAndBitwiseXor // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseXor(Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseXor($value1$); }); checkWMTE(() -> { // primitive class @@ -1807,11 +1813,11 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // GetAndBitwiseXorAcquire // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseXorAcquire(Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseXorAcquire($value1$); }); checkWMTE(() -> { // primitive class @@ -1828,11 +1834,11 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { // GetAndBitwiseXorReleaseRelease // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseXorRelease(Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseXorRelease($value1$); }); checkWMTE(() -> { // primitive class @@ -1853,7 +1859,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { for (TestAccessMode am : testAccessModesOfType(TestAccessType.GET)) { // Incorrect return type - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class Void x = (Void) hs.get(am, methodType(Void.class)). invokeExact(); }); @@ -1869,7 +1875,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { } for (TestAccessMode am : testAccessModesOfType(TestAccessType.SET)) { - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // value reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // value reference class hs.get(am, methodType(void.class, Class.class)). invokeExact(Void.class); }); @@ -1886,11 +1892,11 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { #if[CAS] for (TestAccessMode am : testAccessModesOfType(TestAccessType.COMPARE_AND_SET)) { // Incorrect argument types - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // expected reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // expected reference class boolean r = (boolean) hs.get(am, methodType(boolean.class, Class.class, $type$.class)). invokeExact(Void.class, $value1$); }); - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // actual reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // actual reference class boolean r = (boolean) hs.get(am, methodType(boolean.class, $type$.class, Class.class)). invokeExact($value1$, Void.class); }); @@ -1907,16 +1913,16 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { for (TestAccessMode am : testAccessModesOfType(TestAccessType.COMPARE_AND_EXCHANGE)) { // Incorrect argument types - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // expected reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // expected reference class $type$ x = ($type$) hs.get(am, methodType($type$.class, Class.class, $type$.class)). invokeExact(Void.class, $value1$); }); - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // actual reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // actual reference class $type$ x = ($type$) hs.get(am, methodType($type$.class, $type$.class, Class.class)). invokeExact($value1$, Void.class); }); // Incorrect return type - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class Void r = (Void) hs.get(am, methodType(Void.class, $type$.class, $type$.class)). invokeExact($value1$, $value1$); }); @@ -1937,12 +1943,12 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { for (TestAccessMode am : testAccessModesOfType(TestAccessType.GET_AND_SET)) { // Incorrect argument types - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // value reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // value reference class $type$ x = ($type$) hs.get(am, methodType($type$.class, Class.class)). invokeExact(Void.class); }); // Incorrect return type - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class Void r = (Void) hs.get(am, methodType(Void.class, $type$.class)). invokeExact($value1$); }); @@ -1965,12 +1971,12 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { #if[AtomicAdd] for (TestAccessMode am : testAccessModesOfType(TestAccessType.GET_AND_ADD)) { // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) hs.get(am, methodType($type$.class, Class.class)). invokeExact(Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) hs.get(am, methodType(Void.class, $type$.class)). invokeExact($value1$); }); @@ -1993,12 +1999,12 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { #if[Bitwise] for (TestAccessMode am : testAccessModesOfType(TestAccessType.GET_AND_BITWISE)) { // Incorrect argument types - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) hs.get(am, methodType($type$.class, Class.class)). invokeExact(Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) hs.get(am, methodType(Void.class, $type$.class)). invokeExact($value1$); }); @@ -2039,7 +2045,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.get(array, Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void x = (Void) vh.get(array, 0); }); checkWMTE(() -> { // primitive class @@ -2062,7 +2068,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // array reference class vh.set(Void.class, 0, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class vh.set(array, 0, Void.class); }); checkWMTE(() -> { // receiver primitive class @@ -2095,7 +2101,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.getVolatile(array, Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void x = (Void) vh.getVolatile(array, 0); }); checkWMTE(() -> { // primitive class @@ -2118,7 +2124,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // array reference class vh.setVolatile(Void.class, 0, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class vh.setVolatile(array, 0, Void.class); }); checkWMTE(() -> { // receiver primitive class @@ -2151,7 +2157,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.getOpaque(array, Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void x = (Void) vh.getOpaque(array, 0); }); checkWMTE(() -> { // primitive class @@ -2174,7 +2180,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // array reference class vh.setOpaque(Void.class, 0, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class vh.setOpaque(array, 0, Void.class); }); checkWMTE(() -> { // receiver primitive class @@ -2207,7 +2213,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.getAcquire(array, Void.class); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void x = (Void) vh.getAcquire(array, 0); }); checkWMTE(() -> { // primitive class @@ -2230,7 +2236,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // array reference class vh.setRelease(Void.class, 0, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class vh.setRelease(array, 0, Void.class); }); checkWMTE(() -> { // receiver primitive class @@ -2257,10 +2263,10 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class boolean r = vh.compareAndSet(Void.class, 0, $value1$, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class boolean r = vh.compareAndSet(array, 0, Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class boolean r = vh.compareAndSet(array, 0, $value1$, Void.class); }); checkWMTE(() -> { // receiver primitive class @@ -2286,10 +2292,10 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class boolean r = vh.weakCompareAndSetPlain(Void.class, 0, $value1$, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class boolean r = vh.weakCompareAndSetPlain(array, 0, Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class boolean r = vh.weakCompareAndSetPlain(array, 0, $value1$, Void.class); }); checkWMTE(() -> { // receiver primitive class @@ -2315,10 +2321,10 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class boolean r = vh.weakCompareAndSet(Void.class, 0, $value1$, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class boolean r = vh.weakCompareAndSet(array, 0, Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class boolean r = vh.weakCompareAndSet(array, 0, $value1$, Void.class); }); checkWMTE(() -> { // receiver primitive class @@ -2344,10 +2350,10 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class boolean r = vh.weakCompareAndSetAcquire(Void.class, 0, $value1$, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class boolean r = vh.weakCompareAndSetAcquire(array, 0, Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class boolean r = vh.weakCompareAndSetAcquire(array, 0, $value1$, Void.class); }); checkWMTE(() -> { // receiver primitive class @@ -2373,10 +2379,10 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // receiver reference class boolean r = vh.weakCompareAndSetRelease(Void.class, 0, $value1$, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class boolean r = vh.weakCompareAndSetRelease(array, 0, Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class boolean r = vh.weakCompareAndSetRelease(array, 0, $value1$, Void.class); }); checkWMTE(() -> { // receiver primitive class @@ -2402,10 +2408,10 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // array reference class $type$ x = ($type$) vh.compareAndExchange(Void.class, 0, $value1$, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class $type$ x = ($type$) vh.compareAndExchange(array, 0, Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class $type$ x = ($type$) vh.compareAndExchange(array, 0, $value1$, Void.class); }); checkWMTE(() -> { // array primitive class @@ -2415,7 +2421,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.compareAndExchange(array, Void.class, $value1$, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.compareAndExchange(array, 0, $value1$, $value1$); }); checkWMTE(() -> { // primitive class @@ -2438,10 +2444,10 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // array reference class $type$ x = ($type$) vh.compareAndExchangeAcquire(Void.class, 0, $value1$, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class $type$ x = ($type$) vh.compareAndExchangeAcquire(array, 0, Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class $type$ x = ($type$) vh.compareAndExchangeAcquire(array, 0, $value1$, Void.class); }); checkWMTE(() -> { // array primitive class @@ -2451,7 +2457,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.compareAndExchangeAcquire(array, Void.class, $value1$, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.compareAndExchangeAcquire(array, 0, $value1$, $value1$); }); checkWMTE(() -> { // primitive class @@ -2474,10 +2480,10 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // array reference class $type$ x = ($type$) vh.compareAndExchangeRelease(Void.class, 0, $value1$, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // expected reference class + check{#if[Object]?CCE:WMTE}(() -> { // expected reference class $type$ x = ($type$) vh.compareAndExchangeRelease(array, 0, Void.class, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // actual reference class + check{#if[Object]?CCE:WMTE}(() -> { // actual reference class $type$ x = ($type$) vh.compareAndExchangeRelease(array, 0, $value1$, Void.class); }); checkWMTE(() -> { // array primitive class @@ -2487,7 +2493,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.compareAndExchangeRelease(array, Void.class, $value1$, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.compareAndExchangeRelease(array, 0, $value1$, $value1$); }); checkWMTE(() -> { // primitive class @@ -2510,7 +2516,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // array reference class $type$ x = ($type$) vh.getAndSet(Void.class, 0, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndSet(array, 0, Void.class); }); checkWMTE(() -> { // reciarrayever primitive class @@ -2520,7 +2526,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.getAndSet(array, Void.class, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndSet(array, 0, $value1$); }); checkWMTE(() -> { // primitive class @@ -2543,7 +2549,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // array reference class $type$ x = ($type$) vh.getAndSetAcquire(Void.class, 0, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndSetAcquire(array, 0, Void.class); }); checkWMTE(() -> { // reciarrayever primitive class @@ -2553,7 +2559,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.getAndSetAcquire(array, Void.class, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndSetAcquire(array, 0, $value1$); }); checkWMTE(() -> { // primitive class @@ -2576,7 +2582,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // array reference class $type$ x = ($type$) vh.getAndSetRelease(Void.class, 0, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndSetRelease(array, 0, Void.class); }); checkWMTE(() -> { // reciarrayever primitive class @@ -2586,7 +2592,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.getAndSetRelease(array, Void.class, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndSetRelease(array, 0, $value1$); }); checkWMTE(() -> { // primitive class @@ -2610,7 +2616,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // array reference class $type$ x = ($type$) vh.getAndAdd(Void.class, 0, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndAdd(array, 0, Void.class); }); checkWMTE(() -> { // array primitive class @@ -2620,7 +2626,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.getAndAdd(array, Void.class, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndAdd(array, 0, $value1$); }); checkWMTE(() -> { // primitive class @@ -2643,7 +2649,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // array reference class $type$ x = ($type$) vh.getAndAddAcquire(Void.class, 0, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndAddAcquire(array, 0, Void.class); }); checkWMTE(() -> { // array primitive class @@ -2653,7 +2659,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.getAndAddAcquire(array, Void.class, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndAddAcquire(array, 0, $value1$); }); checkWMTE(() -> { // primitive class @@ -2676,7 +2682,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // array reference class $type$ x = ($type$) vh.getAndAddRelease(Void.class, 0, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndAddRelease(array, 0, Void.class); }); checkWMTE(() -> { // array primitive class @@ -2686,7 +2692,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.getAndAddRelease(array, Void.class, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndAddRelease(array, 0, $value1$); }); checkWMTE(() -> { // primitive class @@ -2710,7 +2716,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // array reference class $type$ x = ($type$) vh.getAndBitwiseOr(Void.class, 0, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseOr(array, 0, Void.class); }); checkWMTE(() -> { // array primitive class @@ -2720,7 +2726,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.getAndBitwiseOr(array, Void.class, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseOr(array, 0, $value1$); }); checkWMTE(() -> { // primitive class @@ -2743,7 +2749,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // array reference class $type$ x = ($type$) vh.getAndBitwiseOrAcquire(Void.class, 0, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseOrAcquire(array, 0, Void.class); }); checkWMTE(() -> { // array primitive class @@ -2753,7 +2759,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.getAndBitwiseOrAcquire(array, Void.class, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseOrAcquire(array, 0, $value1$); }); checkWMTE(() -> { // primitive class @@ -2776,7 +2782,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // array reference class $type$ x = ($type$) vh.getAndBitwiseOrRelease(Void.class, 0, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseOrRelease(array, 0, Void.class); }); checkWMTE(() -> { // array primitive class @@ -2786,7 +2792,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.getAndBitwiseOrRelease(array, Void.class, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseOrRelease(array, 0, $value1$); }); checkWMTE(() -> { // primitive class @@ -2809,7 +2815,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // array reference class $type$ x = ($type$) vh.getAndBitwiseAnd(Void.class, 0, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseAnd(array, 0, Void.class); }); checkWMTE(() -> { // array primitive class @@ -2819,7 +2825,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.getAndBitwiseAnd(array, Void.class, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseAnd(array, 0, $value1$); }); checkWMTE(() -> { // primitive class @@ -2842,7 +2848,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // array reference class $type$ x = ($type$) vh.getAndBitwiseAndAcquire(Void.class, 0, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseAndAcquire(array, 0, Void.class); }); checkWMTE(() -> { // array primitive class @@ -2852,7 +2858,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.getAndBitwiseAndAcquire(array, Void.class, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseAndAcquire(array, 0, $value1$); }); checkWMTE(() -> { // primitive class @@ -2875,7 +2881,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // array reference class $type$ x = ($type$) vh.getAndBitwiseAndRelease(Void.class, 0, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseAndRelease(array, 0, Void.class); }); checkWMTE(() -> { // array primitive class @@ -2885,7 +2891,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.getAndBitwiseAndRelease(array, Void.class, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseAndRelease(array, 0, $value1$); }); checkWMTE(() -> { // primitive class @@ -2908,7 +2914,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // array reference class $type$ x = ($type$) vh.getAndBitwiseXor(Void.class, 0, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseXor(array, 0, Void.class); }); checkWMTE(() -> { // array primitive class @@ -2918,7 +2924,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.getAndBitwiseXor(array, Void.class, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseXor(array, 0, $value1$); }); checkWMTE(() -> { // primitive class @@ -2941,7 +2947,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // array reference class $type$ x = ($type$) vh.getAndBitwiseXorAcquire(Void.class, 0, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseXorAcquire(array, 0, Void.class); }); checkWMTE(() -> { // array primitive class @@ -2951,7 +2957,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.getAndBitwiseXorAcquire(array, Void.class, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseXorAcquire(array, 0, $value1$); }); checkWMTE(() -> { // primitive class @@ -2974,7 +2980,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { checkCCE(() -> { // array reference class $type$ x = ($type$) vh.getAndBitwiseXorRelease(Void.class, 0, $value1$); }); - check{#if[String]?CCE:WMTE}(() -> { // value reference class + check{#if[Object]?CCE:WMTE}(() -> { // value reference class $type$ x = ($type$) vh.getAndBitwiseXorRelease(array, 0, Void.class); }); checkWMTE(() -> { // array primitive class @@ -2984,7 +2990,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) vh.getAndBitwiseXorRelease(array, Void.class, $value1$); }); // Incorrect return type - check{#if[String]?CCE:WMTE}(() -> { // reference class + check{#if[Object]?CCE:WMTE}(() -> { // reference class Void r = (Void) vh.getAndBitwiseXorRelease(array, 0, $value1$); }); checkWMTE(() -> { // primitive class @@ -3023,7 +3029,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { invokeExact(array, Void.class); }); // Incorrect return type - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class Void x = (Void) hs.get(am, methodType(Void.class, $type$[].class, int.class)). invokeExact(array, 0); }); @@ -3052,7 +3058,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { hs.get(am, methodType(void.class, Class.class, int.class, $type$.class)). invokeExact(Void.class, 0, $value1$); }); - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // value reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // value reference class hs.get(am, methodType(void.class, $type$[].class, int.class, Class.class)). invokeExact(array, 0, Void.class); }); @@ -3085,11 +3091,11 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { boolean r = (boolean) hs.get(am, methodType(boolean.class, Class.class, int.class, $type$.class, $type$.class)). invokeExact(Void.class, 0, $value1$, $value1$); }); - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // expected reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // expected reference class boolean r = (boolean) hs.get(am, methodType(boolean.class, $type$[].class, int.class, Class.class, $type$.class)). invokeExact(array, 0, Void.class, $value1$); }); - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // actual reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // actual reference class boolean r = (boolean) hs.get(am, methodType(boolean.class, $type$[].class, int.class, $type$.class, Class.class)). invokeExact(array, 0, $value1$, Void.class); }); @@ -3122,11 +3128,11 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) hs.get(am, methodType($type$.class, Class.class, int.class, $type$.class, $type$.class)). invokeExact(Void.class, 0, $value1$, $value1$); }); - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // expected reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // expected reference class $type$ x = ($type$) hs.get(am, methodType($type$.class, $type$[].class, int.class, Class.class, $type$.class)). invokeExact(array, 0, Void.class, $value1$); }); - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // actual reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // actual reference class $type$ x = ($type$) hs.get(am, methodType($type$.class, $type$[].class, int.class, $type$.class, Class.class)). invokeExact(array, 0, $value1$, Void.class); }); @@ -3139,7 +3145,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { invokeExact(array, Void.class, $value1$, $value1$); }); // Incorrect return type - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class Void r = (Void) hs.get(am, methodType(Void.class, $type$[].class, int.class, $type$.class, $type$.class)). invokeExact(array, 0, $value1$, $value1$); }); @@ -3168,7 +3174,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) hs.get(am, methodType($type$.class, Class.class, int.class, $type$.class)). invokeExact(Void.class, 0, $value1$); }); - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // value reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // value reference class $type$ x = ($type$) hs.get(am, methodType($type$.class, $type$[].class, int.class, Class.class)). invokeExact(array, 0, Void.class); }); @@ -3181,7 +3187,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { invokeExact(array, Void.class, $value1$); }); // Incorrect return type - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class Void r = (Void) hs.get(am, methodType(Void.class, $type$[].class, int.class, $type$.class)). invokeExact(array, 0, $value1$); }); @@ -3212,7 +3218,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) hs.get(am, methodType($type$.class, Class.class, int.class, $type$.class)). invokeExact(Void.class, 0, $value1$); }); - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // value reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // value reference class $type$ x = ($type$) hs.get(am, methodType($type$.class, $type$[].class, int.class, Class.class)). invokeExact(array, 0, Void.class); }); @@ -3225,7 +3231,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { invokeExact(array, Void.class, $value1$); }); // Incorrect return type - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class Void r = (Void) hs.get(am, methodType(Void.class, $type$[].class, int.class, $type$.class)). invokeExact(array, 0, $value1$); }); @@ -3256,7 +3262,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { $type$ x = ($type$) hs.get(am, methodType($type$.class, Class.class, int.class, $type$.class)). invokeExact(Void.class, 0, $value1$); }); - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // value reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // value reference class $type$ x = ($type$) hs.get(am, methodType($type$.class, $type$[].class, int.class, Class.class)). invokeExact(array, 0, Void.class); }); @@ -3269,7 +3275,7 @@ public class VarHandleTestMethodType$Type$ extends VarHandleBaseTest { invokeExact(array, Void.class, $value1$); }); // Incorrect return type - {#if[String]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class + {#if[Object]?hs.checkWMTEOrCCE:checkWMTE}(() -> { // reference class Void r = (Void) hs.get(am, methodType(Void.class, $type$[].class, int.class, $type$.class)). invokeExact(array, 0, $value1$); }); diff --git a/test/jdk/java/lang/invoke/VarHandles/generate-vh-tests.sh b/test/jdk/java/lang/invoke/VarHandles/generate-vh-tests.sh index 391de865f81..1d860551aa8 100644 --- a/test/jdk/java/lang/invoke/VarHandles/generate-vh-tests.sh +++ b/test/jdk/java/lang/invoke/VarHandles/generate-vh-tests.sh @@ -9,7 +9,7 @@ SPP=build.tools.spp.Spp # desirable to generate code using ASM which will allow more flexibility # in the kinds of tests that are generated. -for type in boolean byte short char int long float double String +for type in boolean byte short char int long float double String Value do Type="$(tr '[:lower:]' '[:upper:]' <<< ${type:0:1})${type:1}" args="-K$type -Dtype=$type -DType=$Type" @@ -28,6 +28,17 @@ do ;; esac + # Object = objects of identity or value class + # Value = value class + case $type in + String) + args="$args -KObject" + ;; + Value) + args="$args -KObject -KValue" + ;; + esac + wrong_primitive_type=boolean case $type in @@ -77,6 +88,11 @@ do value2=\"bar\" value3=\"baz\" ;; + Value) + value1="Value.getInstance(10)" + value2="Value.getInstance(20)" + value3="Value.getInstance(30)" + ;; esac args="$args -Dvalue1=$value1 -Dvalue2=$value2 -Dvalue3=$value3 -Dwrong_primitive_type=$wrong_primitive_type" diff --git a/test/jdk/java/lang/invoke/callerSensitive/CallerSensitiveAccess.java b/test/jdk/java/lang/invoke/callerSensitive/CallerSensitiveAccess.java index a6359162629..f5bdf939691 100644 --- a/test/jdk/java/lang/invoke/callerSensitive/CallerSensitiveAccess.java +++ b/test/jdk/java/lang/invoke/callerSensitive/CallerSensitiveAccess.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021, 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 @@ -22,7 +22,7 @@ */ /* @test - * @bug 8196830 8235351 8257874 + * @bug 8196830 8235351 8257874 8327639 * @modules java.base/jdk.internal.reflect * @run testng/othervm CallerSensitiveAccess * @summary Check Lookup findVirtual, findStatic and unreflect behavior with @@ -50,22 +50,29 @@ import jdk.internal.reflect.CallerSensitive; +import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.NoInjection; import org.testng.annotations.Test; import static org.testng.Assert.*; public class CallerSensitiveAccess { + // Cache the list of Caller Sensitive Methods + private static List CALLER_SENSITIVE_METHODS; + + @BeforeClass + private void setupCallerSensitiveMethods() { + CALLER_SENSITIVE_METHODS = callerSensitiveMethods(Object.class.getModule()); + } /** * Caller sensitive methods in APIs exported by java.base. */ @DataProvider(name = "callerSensitiveMethods") static Object[][] callerSensitiveMethods() { - try (Stream stream = callerSensitiveMethods(Object.class.getModule())) { - return stream.map(m -> new Object[]{m, shortDescription(m)}) - .toArray(Object[][]::new); - } + return CALLER_SENSITIVE_METHODS.stream() + .map(m -> new Object[]{m, shortDescription(m)}) + .toArray(Object[][]::new); } /** @@ -101,13 +108,11 @@ public void testPublicLookupUnreflect(@NoInjection Method method, String desc) t */ @DataProvider(name = "accessibleCallerSensitiveMethods") static Object[][] accessibleCallerSensitiveMethods() { - try (Stream stream = callerSensitiveMethods(Object.class.getModule())) { - return stream + return CALLER_SENSITIVE_METHODS.stream() .filter(m -> Modifier.isPublic(m.getModifiers())) .map(m -> { m.setAccessible(true); return m; }) .map(m -> new Object[] { m, shortDescription(m) }) .toArray(Object[][]::new); - } } /** @@ -396,10 +401,11 @@ public void testInnerPrivateField() throws Throwable { // -- supporting methods -- /** - * Returns a stream of all caller sensitive methods on public classes in packages + * Returns a List of all caller sensitive methods on public classes in packages * exported by a named module. + * Returns a List instead of a stream so the ModuleReader can be closed before returning. */ - static Stream callerSensitiveMethods(Module module) { + static List callerSensitiveMethods(Module module) { assert module.isNamed(); ModuleReference mref = module.getLayer().configuration() .findModule(module.getName()) @@ -419,7 +425,7 @@ static Stream callerSensitiveMethods(Module module) { .filter(refc -> refc != null && Modifier.isPublic(refc.getModifiers())) .map(refc -> callerSensitiveMethods(refc)) - .flatMap(List::stream); + .flatMap(List::stream).toList(); } catch (IOException ioe) { throw new UncheckedIOException(ioe); } diff --git a/test/jdk/java/lang/invoke/common/test/java/lang/invoke/lib/InstructionHelper.java b/test/jdk/java/lang/invoke/common/test/java/lang/invoke/lib/InstructionHelper.java index 123cccd53e7..d14a168d329 100644 --- a/test/jdk/java/lang/invoke/common/test/java/lang/invoke/lib/InstructionHelper.java +++ b/test/jdk/java/lang/invoke/common/test/java/lang/invoke/lib/InstructionHelper.java @@ -25,15 +25,16 @@ import java.lang.classfile.ClassBuilder; import java.lang.classfile.ClassFile; +import java.lang.classfile.CodeBuilder; import java.lang.classfile.TypeKind; import java.lang.constant.*; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -import java.util.concurrent.atomic.AtomicInteger; - import static java.lang.invoke.MethodType.fromMethodDescriptorString; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; public class InstructionHelper { @@ -135,4 +136,27 @@ public static ClassDesc classDesc(Class c, String suffix) { String classDescStr = sb.insert(sb.length() - 1, suffix).toString(); return ClassDesc.ofDescriptor(classDescStr); } + + + public static MethodHandle buildMethodHandle(MethodHandles.Lookup l, String methodName, MethodType methodType, Consumer builder) { + ClassDesc genClassDesc = classDesc(l.lookupClass(), "$Code_" + COUNT.getAndIncrement()); + return buildMethodHandle(l, genClassDesc, methodName, methodType, builder); + } + + public static MethodHandle buildMethodHandle(MethodHandles.Lookup l, ClassDesc classDesc, String methodName, MethodType methodType, Consumer builder) { + try { + byte[] bytes = ClassFile.of().build(classDesc, classBuilder -> { + classBuilder.withMethod(methodName, + MethodTypeDesc.ofDescriptor(methodType.toMethodDescriptorString()), + ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC, + methodBuilder -> methodBuilder.withCode(builder)); + }); + Class clazz = l.defineClass(bytes); + return l.findStatic(clazz, methodName, methodType); + } catch (Throwable t) { + t.printStackTrace(); + throw new RuntimeException("Failed to buildMethodHandle: " + methodName + " type " + methodType); + } + } + } diff --git a/test/jdk/java/lang/invoke/condy/CondyNestedResolution.jcod b/test/jdk/java/lang/invoke/condy/CondyNestedResolution.jcod index ce1f1faa44a..5431bb2974e 100644 --- a/test/jdk/java/lang/invoke/condy/CondyNestedResolution.jcod +++ b/test/jdk/java/lang/invoke/condy/CondyNestedResolution.jcod @@ -187,7 +187,7 @@ class CondyNestedResolution { class #72; // #84 at 0x04C5 } // Constant Pool - 0x0000; // access [ ] + 0x0020; // access [ ] #84;// this_cpx #51;// super_cpx diff --git a/test/jdk/java/lang/invoke/condy/CondyNestedTest_Code.jcod b/test/jdk/java/lang/invoke/condy/CondyNestedTest_Code.jcod index a483551ab9a..5f82def31b9 100644 --- a/test/jdk/java/lang/invoke/condy/CondyNestedTest_Code.jcod +++ b/test/jdk/java/lang/invoke/condy/CondyNestedTest_Code.jcod @@ -94,7 +94,7 @@ class CondyNestedTest_Code { Utf8 "BootstrapMethods"; // #65 } // Constant Pool - 0x0000; // access + 0x0020; // access #17;// this_cpx #2;// super_cpx @@ -143,7 +143,7 @@ class CondyNestedTest_Code { } // end Traps [] { // Attributes Attr(#29) { // StackMapTable - [] { // + [] { // 252b, 17, []z{O,9}; // append_frame 1 9b; // same_frame 9b; // same_frame @@ -171,7 +171,7 @@ class CondyNestedTest_Code { } // end Traps [] { // Attributes Attr(#29) { // StackMapTable - [] { // + [] { // 22b; // same_frame } } // end StackMapTable diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/BasicTest.java b/test/jdk/java/lang/invoke/defineHiddenClass/BasicTest.java index 379ae765a6b..75ee9b26553 100644 --- a/test/jdk/java/lang/invoke/defineHiddenClass/BasicTest.java +++ b/test/jdk/java/lang/invoke/defineHiddenClass/BasicTest.java @@ -39,6 +39,7 @@ import java.lang.classfile.ClassFile; import java.lang.constant.ClassDesc; import java.lang.invoke.MethodHandles.Lookup; +import java.lang.reflect.AccessFlag; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; @@ -114,8 +115,8 @@ public void hiddenClass() throws Throwable { Class[] intfs = c.getInterfaces(); assertTrue(c.isHidden()); assertFalse(c.isPrimitive()); - assertTrue(intfs.length == 1); - assertTrue(intfs[0] == HiddenTest.class); + assertTrue(intfs.length == 1 || intfs.length == 2); + assertTrue(intfs[0] == HiddenTest.class || (intfs.length == 2 && intfs[1] == HiddenTest.class)); assertTrue(c.getCanonicalName() == null); String hcName = "HiddenClass"; @@ -260,9 +261,9 @@ public void defineHiddenClass(String name, boolean nestmate) throws Exception { @DataProvider(name = "emptyClasses") private Object[][] emptyClasses() { return new Object[][] { - new Object[] { "EmptyHiddenSynthetic", ACC_SYNTHETIC }, - new Object[] { "EmptyHiddenEnum", ACC_ENUM }, - new Object[] { "EmptyHiddenAbstractClass", ACC_ABSTRACT }, + new Object[] { "EmptyHiddenSynthetic", ACC_SYNTHETIC | ACC_IDENTITY }, + new Object[] { "EmptyHiddenEnum", ACC_ENUM | ACC_IDENTITY }, + new Object[] { "EmptyHiddenAbstractClass", ACC_ABSTRACT | ACC_IDENTITY }, new Object[] { "EmptyHiddenInterface", ACC_ABSTRACT|ACC_INTERFACE }, new Object[] { "EmptyHiddenAnnotation", ACC_ANNOTATION|ACC_ABSTRACT|ACC_INTERFACE }, }; @@ -278,23 +279,23 @@ private Object[][] emptyClasses() { */ @Test(dataProvider = "emptyClasses") public void emptyHiddenClass(String name, int accessFlags) throws Exception { - byte[] bytes = (accessFlags == ACC_ENUM) ? classBytes(name, CD_Enum, accessFlags) + byte[] bytes = (accessFlags == (ACC_ENUM | ACC_IDENTITY)) ? classBytes(name, CD_Enum, accessFlags) : classBytes(name, accessFlags); Class hc = lookup().defineHiddenClass(bytes, false).lookupClass(); switch (accessFlags) { - case ACC_SYNTHETIC: + case (ACC_SYNTHETIC | ACC_IDENTITY): assertTrue(hc.isSynthetic()); assertFalse(hc.isEnum()); assertFalse(hc.isAnnotation()); assertFalse(hc.isInterface()); break; - case ACC_ENUM: + case (ACC_ENUM | ACC_IDENTITY): assertFalse(hc.isSynthetic()); assertTrue(hc.isEnum()); assertFalse(hc.isAnnotation()); assertFalse(hc.isInterface()); break; - case ACC_ABSTRACT: + case ACC_ABSTRACT | ACC_IDENTITY: assertFalse(hc.isSynthetic()); assertFalse(hc.isEnum()); assertFalse(hc.isAnnotation()); @@ -316,7 +317,7 @@ public void emptyHiddenClass(String name, int accessFlags) throws Exception { throw new IllegalArgumentException("unexpected access flag: " + accessFlags); } assertTrue(hc.isHidden()); - assertTrue(hc.getModifiers() == (ACC_PUBLIC|accessFlags)); + assertEquals(hc.getModifiers(), (ACC_PUBLIC|accessFlags)); assertFalse(hc.isLocalClass()); assertFalse(hc.isMemberClass()); assertFalse(hc.isAnonymousClass()); diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/HiddenValueClass.java b/test/jdk/java/lang/invoke/defineHiddenClass/HiddenValueClass.java new file mode 100644 index 00000000000..d53e900ccae --- /dev/null +++ b/test/jdk/java/lang/invoke/defineHiddenClass/HiddenValueClass.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @library /test/lib + * @enablePreview + * @run testng/othervm HiddenValueClass + * @summary Test that a value class can be defined as a hidden class + */ + +import java.lang.invoke.MethodHandles; +import static java.lang.invoke.MethodType.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import jdk.test.lib.Utils; + +import org.testng.annotations.Test; + +public class HiddenValueClass { + private static final Path CLASSES_DIR = Paths.get(Utils.TEST_CLASSES); + + /* + * Defines ValueImpl as a hidden class. + */ + @Test + public void hiddenValueClass() throws Throwable { + byte[] bytes = Files.readAllBytes(CLASSES_DIR.resolve("ValueImpl.class")); + var lookup = MethodHandles.lookup().defineHiddenClass(bytes, false); + var mh = lookup.findConstructor(lookup.lookupClass(), methodType(void.class)) + .asType(methodType(Runnable.class)); + Runnable r = (Runnable)mh.invokeExact(); + r.run(); + } +} + +value class ValueImpl implements Runnable { + public void run() {} +} \ No newline at end of file diff --git a/test/jdk/java/lang/invoke/defineHiddenClass/StaticInvocableTest.java b/test/jdk/java/lang/invoke/defineHiddenClass/StaticInvocableTest.java index 1c95caa97d3..221a1d7d5f2 100644 --- a/test/jdk/java/lang/invoke/defineHiddenClass/StaticInvocableTest.java +++ b/test/jdk/java/lang/invoke/defineHiddenClass/StaticInvocableTest.java @@ -25,6 +25,7 @@ * @test * @bug 8266925 * @summary hidden class members can't be statically invocable + * @enablePreview * @modules java.base/jdk.internal.misc * @build java.base/* * @run testng StaticInvocableTest @@ -116,7 +117,7 @@ private static void test(String pkg) throws Throwable { public static byte[] dumpClass(String pkg) { return ClassFile.of().build(ClassDesc.of(pkg.replace('/', '.'), "MyClass"), clb -> { clb.withSuperclass(CD_Object); - clb.withFlags(AccessFlag.PUBLIC, AccessFlag.SUPER); + clb.withFlags(AccessFlag.PUBLIC, AccessFlag.IDENTITY); clb.withMethodBody(INIT_NAME, MTD_void, 0, cob -> { cob.aload(0); cob.invokespecial(CD_Object, INIT_NAME, MTD_void); diff --git a/test/jdk/java/lang/invoke/lookup/SpecialStatic.java b/test/jdk/java/lang/invoke/lookup/SpecialStatic.java index 31ded727d66..bbf4210ee5d 100644 --- a/test/jdk/java/lang/invoke/lookup/SpecialStatic.java +++ b/test/jdk/java/lang/invoke/lookup/SpecialStatic.java @@ -24,6 +24,7 @@ /* @test * @bug 8032400 * @summary JSR292: invokeSpecial: InternalError attempting to lookup a method + * @enablePreview * @compile -XDignore.symbol.file SpecialStatic.java * @run testng test.java.lang.invoke.lookup.SpecialStatic */ @@ -119,7 +120,7 @@ public void testFindSpecial() throws Throwable { public static byte[] dumpT1() { return ClassFile.of().build(CD_T1, clb -> { clb.withSuperclass(CD_Object); - clb.withFlags(AccessFlag.PUBLIC, AccessFlag.SUPER); + clb.withFlags(AccessFlag.PUBLIC, AccessFlag.IDENTITY); clb.withMethodBody(INIT_NAME, MTD_void, ACC_PUBLIC, cob -> { cob.aload(0); cob.invokespecial(CD_Object, INIT_NAME, MTD_void); @@ -135,7 +136,7 @@ public static byte[] dumpT1() { public static byte[] dumpT2() { return ClassFile.of().build(CD_T2, clb -> { clb.withSuperclass(CD_T1); - clb.withFlags(AccessFlag.PUBLIC, AccessFlag.SUPER); + clb.withFlags(AccessFlag.PUBLIC, AccessFlag.IDENTITY); clb.withMethodBody(INIT_NAME, MTD_void, ACC_PUBLIC, cob -> { cob.aload(0); cob.invokespecial(CD_T1, INIT_NAME, MTD_void); @@ -151,7 +152,7 @@ public static byte[] dumpT2() { public static byte[] dumpT3() { return ClassFile.of().build(CD_T3, clb -> { clb.withSuperclass(CD_T2); - clb.withFlags(AccessFlag.PUBLIC, AccessFlag.SUPER); + clb.withFlags(AccessFlag.PUBLIC, AccessFlag.IDENTITY); clb.withMethodBody(INIT_NAME, MTD_void, ACC_PUBLIC, cob -> { cob.aload(0); cob.invokespecial(CD_T2, INIT_NAME, MTD_void); diff --git a/test/jdk/java/lang/module/ClassFileVersionsTest.java b/test/jdk/java/lang/module/ClassFileVersionsTest.java index 308df36b81d..f7fc62d0fe3 100644 --- a/test/jdk/java/lang/module/ClassFileVersionsTest.java +++ b/test/jdk/java/lang/module/ClassFileVersionsTest.java @@ -25,7 +25,6 @@ * @test * @modules java.base/jdk.internal.module * @library /test/lib - * @build jdk.test.lib.util.ModuleInfoWriter * @run testng ClassFileVersionsTest * @summary Test parsing of module-info.class with different class file versions */ diff --git a/test/jdk/java/lang/module/ConfigurationTest.java b/test/jdk/java/lang/module/ConfigurationTest.java index b32d49ffb06..969c889e271 100644 --- a/test/jdk/java/lang/module/ConfigurationTest.java +++ b/test/jdk/java/lang/module/ConfigurationTest.java @@ -28,7 +28,6 @@ * java.base/jdk.internal.module * @library /test/lib * @build ConfigurationTest - * jdk.test.lib.util.ModuleInfoWriter * jdk.test.lib.util.ModuleUtils * @run junit ConfigurationTest * @summary Basic tests for java.lang.module.Configuration diff --git a/test/jdk/java/lang/module/ModuleDescriptorTest.java b/test/jdk/java/lang/module/ModuleDescriptorTest.java index fd7d32cc978..c1ee262b084 100644 --- a/test/jdk/java/lang/module/ModuleDescriptorTest.java +++ b/test/jdk/java/lang/module/ModuleDescriptorTest.java @@ -27,7 +27,6 @@ * @modules java.base/jdk.internal.access * java.base/jdk.internal.module * @library /test/lib - * @build jdk.test.lib.util.ModuleInfoWriter * @run testng ModuleDescriptorTest * @summary Basic test for java.lang.module.ModuleDescriptor and its builder */ diff --git a/test/jdk/java/lang/module/ModuleFinderTest.java b/test/jdk/java/lang/module/ModuleFinderTest.java index 1c663b1d717..2e2e6119293 100644 --- a/test/jdk/java/lang/module/ModuleFinderTest.java +++ b/test/jdk/java/lang/module/ModuleFinderTest.java @@ -25,7 +25,7 @@ * @test * @modules java.base/jdk.internal.module * @library /test/lib - * @build ModuleFinderTest jdk.test.lib.util.ModuleInfoWriter + * @build ModuleFinderTest * @run testng ModuleFinderTest * @summary Basic tests for java.lang.module.ModuleFinder */ @@ -826,4 +826,3 @@ static Path createModularJar(Path file, String mid, String ... entries) } } - diff --git a/test/jdk/java/lang/module/ModuleNamesTest.java b/test/jdk/java/lang/module/ModuleNamesTest.java index 49e730c06e8..3009db7fce3 100644 --- a/test/jdk/java/lang/module/ModuleNamesTest.java +++ b/test/jdk/java/lang/module/ModuleNamesTest.java @@ -26,7 +26,6 @@ * @modules java.base/jdk.internal.access * java.base/jdk.internal.module * @library /test/lib - * @build jdk.test.lib.util.ModuleInfoWriter * @run testng ModuleNamesTest * @summary Basic test of reading a module-info.class with module names that * are legal in class files but not legal in the Java Language diff --git a/test/jdk/java/lang/module/MultiReleaseJarTest.java b/test/jdk/java/lang/module/MultiReleaseJarTest.java index 1dec2ae49bf..ec2690cf3ab 100644 --- a/test/jdk/java/lang/module/MultiReleaseJarTest.java +++ b/test/jdk/java/lang/module/MultiReleaseJarTest.java @@ -27,7 +27,6 @@ * @library /test/lib * @build MultiReleaseJarTest * jdk.test.lib.util.JarUtils - * jdk.test.lib.util.ModuleInfoWriter * @run testng MultiReleaseJarTest * @run testng/othervm -Djdk.util.jar.enableMultiRelease=false MultiReleaseJarTest * @summary Basic test of modular JARs as multi-release JARs diff --git a/test/jdk/java/lang/ref/ReferenceEnqueuePending.java b/test/jdk/java/lang/ref/ReferenceEnqueuePending.java index 50bec341b40..b596925ae10 100644 --- a/test/jdk/java/lang/ref/ReferenceEnqueuePending.java +++ b/test/jdk/java/lang/ref/ReferenceEnqueuePending.java @@ -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 @@ -22,17 +22,21 @@ */ /* @test - * @bug 4243978 + * @bug 4243978 8336671 * @summary Test if Reference.enqueue() works properly with pending references */ import java.lang.ref.*; +import java.util.Arrays; public class ReferenceEnqueuePending { - static class NumberedWeakReference extends WeakReference { + + record Numbered(int number) {} + + static class NumberedWeakReference extends WeakReference { // Add an integer to identify the weak reference object. int number; - NumberedWeakReference(Integer referent, ReferenceQueue q, int i) { + NumberedWeakReference(Numbered referent, ReferenceQueue q, int i) { super(referent, q); number = i; } @@ -53,30 +57,30 @@ public static void main(String[] argv) throws Exception { // priority, so that they can race also on a uniprocessor. raisePriority(); - ReferenceQueue refQueue = new ReferenceQueue<>(); + ReferenceQueue refQueue = new ReferenceQueue<>(); // Our objective is to let the mutator enqueue // a Reference object that may already be in the // pending state because of having been identified // as weakly reachable at a previous garbage collection. - // To this end, we create many Reference objects, each with a - // a unique integer object as its referant. + // To this end, we create many Reference objects, each with + // a unique Numbered object as its referant. // We let the referents become eligible for collection, // while racing with the garbage collector which may // have pended some of these Reference objects. - // Finally we check that all of the Reference objects - // end up on the their queue. The test was originally + // Finally, we check that all of the Reference objects + // end up on their queue. The test was originally // submitted to show that such races could break the // pending list and/or the reference queue, because of sharing // the same link ("next") for maintaining both lists, thus // losing some of the Reference objects on either queue. - Integer obj = new Integer(0); + Numbered obj = new Numbered(0); NumberedWeakReference weaky = new NumberedWeakReference(obj, refQueue, 0); for (int i = 1; i < iterations; i++) { - // Create a new object, dropping the onlY strong reference to - // the previous Integer object. - obj = new Integer(i); + // Create a new object, dropping the only strong reference to + // the previous Numbered object. + obj = new Numbered(i); // Trigger gc each gc_trigger iterations. if ((i % gc_trigger) == 0) { forceGc(0); @@ -87,7 +91,7 @@ public static void main(String[] argv) throws Exception { } // Remember the Reference objects, for testing later. b[i - 1] = weaky; - // Get a new weaky for the Integer object just + // Get a new weaky for the Numbered object just // created, which may be explicitly enqueued in // our next trip around the loop. weaky = new NumberedWeakReference(obj, refQueue, i); @@ -112,7 +116,7 @@ public static void main(String[] argv) throws Exception { System.out.println("Test passed."); } - private static NumberedWeakReference waitForReference(ReferenceQueue queue) { + private static NumberedWeakReference waitForReference(ReferenceQueue queue) { try { return (NumberedWeakReference) queue.remove(30000); // 30sec } catch (InterruptedException ie) { @@ -120,7 +124,7 @@ private static NumberedWeakReference waitForReference(ReferenceQueue qu } } - private static void checkResult(ReferenceQueue queue, + private static void checkResult(ReferenceQueue queue, int expected) { if (debug) { System.out.println("Reading the queue"); @@ -149,7 +153,7 @@ private static void checkResult(ReferenceQueue queue, } // Sort the first "length" elements in array "a[]". - sort(length); + Arrays.sort(a, 0, length); boolean fail = (length != expected); for (int i = 0; i < length; i++) { @@ -191,23 +195,6 @@ private static void forceGc(long millis) throws InterruptedException { Thread.sleep(millis); } - // Bubble sort the first "length" elements in array "a". - private static void sort(int length) { - int hold; - if (debug) { - System.out.println("Sorting. Length=" + length); - } - for (int pass = 1; pass < length; pass++) { // passes over the array - for (int i = 0; i < length - pass; i++) { // a single pass - if (a[i] > a[i + 1]) { // then swap - hold = a[i]; - a[i] = a[i + 1]; - a[i + 1] = hold; - } - } // End of i loop - } // End of pass loop - } - // Raise thread priority so as to increase the // probability of the mutator succeeding in enqueueing // an object that is still in the pending state. diff --git a/test/jdk/java/lang/reflect/AccessFlag/BasicAccessFlagTest.java b/test/jdk/java/lang/reflect/AccessFlag/BasicAccessFlagTest.java index d2ee6a2434e..9ae64d1a8f8 100644 --- a/test/jdk/java/lang/reflect/AccessFlag/BasicAccessFlagTest.java +++ b/test/jdk/java/lang/reflect/AccessFlag/BasicAccessFlagTest.java @@ -22,9 +22,19 @@ */ /* - * @test - * @bug 8266670 8293626 8297271 + * @test id=Basic + * @bug 8266670 8281463 8293626 8297271 * @summary Basic tests of AccessFlag + * @modules java.base/jdk.internal.misc + * @run junit BasicAccessFlagTest + */ + +/* + * @test id=BasicPreview + * @bug 8266670 8281463 8293626 8297271 + * @summary Basic tests of AccessFlag + * @modules java.base/jdk.internal.misc + * @enablePreview * @run junit BasicAccessFlagTest */ @@ -39,6 +49,9 @@ import java.util.HashSet; import java.util.Set; +import jdk.internal.misc.PreviewFeatures; + + import org.junit.Test; import org.junit.jupiter.api.Assertions; @@ -111,6 +124,8 @@ public void testDisjoint() { Set locations = new HashSet<>(); for (var accessFlag : value) { + if (accessFlag.equals(AccessFlag.SUPER)) + continue; // SUPER is defined to overlap with IDENTITY for (var location : accessFlag.locations()) { boolean added = locations.add(location); if (!added) { @@ -141,7 +156,9 @@ public void testMaskToAccessFlagsPositive() { for (var location : accessFlag.locations()) { Set computedSet = AccessFlag.maskToAccessFlags(accessFlag.mask(), location); - if (!expectedSet.equals(computedSet)) { + if (!computedSet.containsAll(expectedSet)) { + System.out.println("expected: " + expectedSet); + System.out.println("computed: " + computedSet); throw new RuntimeException("Bad set computation on " + accessFlag + ", " + location); } diff --git a/test/jdk/java/lang/reflect/AccessFlag/ClassAccessFlagPreviewTest.java b/test/jdk/java/lang/reflect/AccessFlag/ClassAccessFlagPreviewTest.java new file mode 100644 index 00000000000..98453c965ab --- /dev/null +++ b/test/jdk/java/lang/reflect/AccessFlag/ClassAccessFlagPreviewTest.java @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8266670 8291734 8296743 8294866 + * @summary Test expected AccessFlag's on classes. + * @modules java.base/jdk.internal.misc + * @enablePreview false + * @run main ClassAccessFlagPreviewTest + * @enablePreview true + * @compile -XDforcePreview ClassAccessFlagPreviewTest.java + * @run main ClassAccessFlagPreviewTest + */ + +import java.lang.annotation.*; +import java.lang.reflect.*; +import java.util.*; + +import jdk.internal.misc.PreviewFeatures; + +/* + * Class access flags that can directly or indirectly declared in + * source include: + * public, private, protected, static, final, interface, abstract, + * annotation, enum. + * + * Additionally, the access flags super and synthetic cannot be + * explicitly applied. + * + * This test is written on top of the facilities of core reflection. + * + * Note that core reflection does not offer a supported mechanism to + * return the Class object created from a module-info.class + * file. Therefore, this test does not attempt to probe the setting of + * that access flag. + */ +@ExpectedClassFlags(value = "[PUBLIC, FINAL, SUPER]", + preview = "[PUBLIC, FINAL, IDENTITY]") +public final class ClassAccessFlagPreviewTest { + public static void main(String... args) { + // Top-level and auxiliary classes; i.e. non-inner classes + Class[] testClasses = { + ClassAccessFlagPreviewTest.class, + TestInterface.class, + TestFinalClass.class, + TestAbstractClass.class, + TestAbstractValueClass.class, + TestPrivateAbstractClass.class, + TestPrivateAbstractValueClass.class, + StaticTestInterface.class, + TestMarkerAnnotation.class, + ExpectedClassFlags.class, + TestOuterEnum.class + }; + checkClasses(testClasses); + + // Nested classes of ClassAccessFlagPreviewTest + checkClasses(ClassAccessFlagPreviewTest.class.getDeclaredClasses()); + + checkPrimitives(); + checkArrays(); + } + + private static void checkClasses(Class[] classes) { + for (var clazz : classes) { + checkClass(clazz); + } + } + + private static void checkClass(Class clazz) { + ExpectedClassFlags expected = + clazz.getAnnotation(ExpectedClassFlags.class); + if (expected != null) { + String actual = clazz.accessFlags().toString(); + String expectedFlags = (PreviewFeatures.isEnabled() && !expected.preview().isEmpty()) + ? expected.preview() : expected.value(); + if (!expectedFlags.equals(actual)) { + throw new RuntimeException("On " + clazz + + " expected " + expected + + " got " + actual); + } + } + } + + private static void checkPrimitives() { + final Class[] primitives = { + byte.class, + int.class, + long.class, + short.class, + char.class, + float.class, + double.class, + boolean.class, + void.class // same access flag rules + }; + + var expected = Set.of(AccessFlag.PUBLIC, + AccessFlag.FINAL, + AccessFlag.ABSTRACT); + + for(var primClass : primitives) { + var accessFlags = primClass.accessFlags(); + if (!accessFlags.equals(expected)) { + throw new RuntimeException("Unexpected flags on " + + primClass); + } + } + } + + private static boolean containsAny(Set input, + Set test) { + var copy = new HashSet<>(input); + return copy.removeAll(test); + } + + private static void checkArrays() { + Class[] accessClasses = { + PublicInterface.class, + ProtectedInterface.class, + PrivateInterface.class, + }; + + for (var accessClass : accessClasses) { + AccessFlag accessLevel; + var flags = accessClass.accessFlags(); + if (flags.contains(AccessFlag.PUBLIC)) + accessLevel = AccessFlag.PUBLIC; + else if (flags.contains(AccessFlag.PROTECTED)) + accessLevel = AccessFlag.PROTECTED; + else if (flags.contains(AccessFlag.PRIVATE)) + accessLevel = AccessFlag.PRIVATE; + else + accessLevel = null; + + var arrayClass = accessClass.arrayType(); + // Access modifier must match on the array type + if (accessLevel != null) { + if (!arrayClass.accessFlags().contains(accessLevel)) { + throw new RuntimeException("Mismatched access flags on " + + arrayClass); + } + } else { + if (containsAny(arrayClass.accessFlags(), + Set.of(AccessFlag.PUBLIC, + AccessFlag.PROTECTED, + AccessFlag.PRIVATE))) { + throw new RuntimeException("Unexpected access flags on " + + arrayClass); + } + } + // Verify IDENTITY, ABSTRACT, FINAL, and access mode + Set expected = new HashSet<>(4); + expected.add(AccessFlag.ABSTRACT); + expected.add(AccessFlag.FINAL); + expected.add(AccessFlag.IDENTITY); + if (accessLevel != null) + expected.add(accessLevel); + if (!expected.equals(arrayClass.accessFlags())) { + throw new RuntimeException("Unexpected access flags for array: " + accessClass + + ": actual: " + arrayClass.accessFlags() + + ", expected: " + expected); + } + } + + } + + // inner classes and interfaces; possible flags on INNER_CLASS + // locations: + // PUBLIC, PRIVATE, PROTECTED, STATIC, FINAL, INTERFACE, ABSTRACT, + // SYNTHETIC, ANNOTATION, ENUM. + // Include cases for classes with identity, value modifier, or no modifier. + + @ExpectedClassFlags("[PUBLIC, STATIC, INTERFACE, ABSTRACT]") + public interface PublicInterface {} + @ExpectedClassFlags("[PROTECTED, STATIC, INTERFACE, ABSTRACT]") + protected interface ProtectedInterface {} + @ExpectedClassFlags("[PRIVATE, STATIC, INTERFACE, ABSTRACT]") + private interface PrivateInterface {} + @ExpectedClassFlags("[STATIC, INTERFACE, ABSTRACT]") + /*package*/ interface PackageInterface {} + + @ExpectedClassFlags(value = "[FINAL]", + preview = "[FINAL, IDENTITY]") + /*package*/ final class TestFinalClass {} + + @ExpectedClassFlags(value = "[ABSTRACT]", + preview = "[IDENTITY, ABSTRACT]") + /*package*/ abstract class TestAbstractClass {} + + @ExpectedClassFlags(value = "[ABSTRACT]", + preview = "[ABSTRACT]") + /*package*/ abstract value class TestAbstractValueClass {} + + @ExpectedClassFlags("[STATIC, INTERFACE, ABSTRACT, ANNOTATION]") + /*package*/ @interface TestMarkerAnnotation {} + + @ExpectedClassFlags(value = "[PUBLIC, STATIC, FINAL, ENUM]", + preview = "[PUBLIC, STATIC, FINAL, IDENTITY, ENUM]") + public enum MetaSynVar { + QUUX; + } + + // Is there is at least one special enum constant, the enum class + // itself is implicitly abstract rather than final. + @ExpectedClassFlags(value = "[PROTECTED, STATIC, ABSTRACT, ENUM]", + preview = "[PROTECTED, STATIC, IDENTITY, ABSTRACT, ENUM]") + protected enum MetaSynVar2 { + WOMBAT{ + @Override + public int foo() {return 42;} + }; + public abstract int foo(); + } + + @ExpectedClassFlags(value = "[PRIVATE, ABSTRACT]", + preview = "[PRIVATE, IDENTITY, ABSTRACT]") + private abstract class TestPrivateAbstractClass {} + + @ExpectedClassFlags(value = "[PRIVATE, ABSTRACT]", + preview = "[PRIVATE, ABSTRACT]") + private abstract value class TestPrivateAbstractValueClass {} + + @ExpectedClassFlags("[STATIC, INTERFACE, ABSTRACT]") + interface StaticTestInterface {} +} + +@Retention(RetentionPolicy.RUNTIME) +@ExpectedClassFlags("[INTERFACE, ABSTRACT, ANNOTATION]") +@interface ExpectedClassFlags { + String value(); + String preview() default ""; +} + +@ExpectedClassFlags("[INTERFACE, ABSTRACT]") +interface TestInterface {} + + +@ExpectedClassFlags(value="[FINAL, SUPER, ENUM]", + preview="[FINAL, IDENTITY, ENUM]") +enum TestOuterEnum { + INSTANCE; +} diff --git a/test/jdk/java/lang/reflect/AccessFlag/ClassAccessFlagTest.java b/test/jdk/java/lang/reflect/AccessFlag/ClassAccessFlagTest.java index 1c80c29dc9b..d43b5c7b244 100644 --- a/test/jdk/java/lang/reflect/AccessFlag/ClassAccessFlagTest.java +++ b/test/jdk/java/lang/reflect/AccessFlag/ClassAccessFlagTest.java @@ -25,12 +25,21 @@ * @test * @bug 8266670 8291734 8296743 * @summary Test expected AccessFlag's on classes. + * @modules java.base/jdk.internal.misc + * @library /test/lib .. + * @enablePreview */ + +import jdk.internal.misc.PreviewFeatures; + import java.lang.annotation.*; import java.lang.reflect.*; import java.util.*; +import jtreg.SkippedException; + + import static java.lang.reflect.AccessFlag.*; /* @@ -52,6 +61,9 @@ @ExpectedClassFlags({PUBLIC, FINAL, SUPER}) public final class ClassAccessFlagTest { public static void main(String... args) { + if (PreviewFeatures.isEnabled()) { + throw new SkippedException("Preview mode not supported"); + } // Top-level and auxiliary classes; i.e. non-inner classes Class[] testClasses = { ClassAccessFlagTest.class, diff --git a/test/jdk/java/lang/reflect/AccessFlag/SuperAccessFlagTest.java b/test/jdk/java/lang/reflect/AccessFlag/SuperAccessFlagTest.java new file mode 100644 index 00000000000..e8fe3815938 --- /dev/null +++ b/test/jdk/java/lang/reflect/AccessFlag/SuperAccessFlagTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8301720 + * @summary Test expected value of SUPER AccessFlag for pre-ValueClass .class file + * @compile -source 20 -target 20 SuperAccessFlagTest.java + * @comment Cannot use --release 20 because the accessFlags() method is + * not found in release 20; therefore -source and -target are used instead. + * @run main SuperAccessFlagTest + */ + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/* + * Test expected value of ACC_SUPER access flag on an earlier release. + */ +@ExpectedClassFlags("[PUBLIC, SUPER]") +public class SuperAccessFlagTest { + public static void main(String... args) { + checkClass(SuperAccessFlagTest.class); + } + + private static void checkClass(Class clazz) { + ExpectedClassFlags expected = + clazz.getAnnotation(ExpectedClassFlags.class); + if (expected != null) { + String actual = clazz.accessFlags().toString(); + if (!expected.value().equals(actual)) { + throw new RuntimeException("On " + clazz + + " expected " + expected.value() + + " got " + actual); + } + } + } +} + +@Retention(RetentionPolicy.RUNTIME) +@ExpectedClassFlags("[INTERFACE, ABSTRACT, ANNOTATION]") +@interface ExpectedClassFlags { + String value(); +} diff --git a/test/jdk/java/lang/reflect/AccessFlag/VersionedLocationsTest.java b/test/jdk/java/lang/reflect/AccessFlag/VersionedLocationsTest.java index 29130cbec38..fc2f6fbf745 100644 --- a/test/jdk/java/lang/reflect/AccessFlag/VersionedLocationsTest.java +++ b/test/jdk/java/lang/reflect/AccessFlag/VersionedLocationsTest.java @@ -52,7 +52,7 @@ * PROTECTED step * STATIC step * FINAL two-step - * SUPER invariant + * SUPER step * OPEN step * TRANSITIVE step * SYNCHRONIZED invariant @@ -90,7 +90,7 @@ public static void main(String... args) throws Exception { */ private static void testInvariantAccessFlags() { Set invariantAccessFlags = - Set.of(SUPER, SYNCHRONIZED, VOLATILE, TRANSIENT, NATIVE); + Set.of(SYNCHRONIZED, VOLATILE, TRANSIENT, NATIVE); for(var accessFlag : invariantAccessFlags) { Set expected = accessFlag.locations(); @@ -118,6 +118,18 @@ private static void testStepFunctionAccessFlags() { removeInnerClass(STATIC.locations()), ClassFileFormatVersion.RELEASE_1), + new StepFunctionTC(SUPER, + Set.of(AccessFlag.Location.CLASS), + ClassFileFormatVersion.CURRENT_PREVIEW_FEATURES), + + new StepFunctionTC(IDENTITY, + Set.of(), + ClassFileFormatVersion.CURRENT_PREVIEW_FEATURES), + + new StepFunctionTC(STRICT_INIT, + Set.of(), + ClassFileFormatVersion.CURRENT_PREVIEW_FEATURES), + new StepFunctionTC(OPEN, Set.of(), ClassFileFormatVersion.RELEASE_9), @@ -191,7 +203,7 @@ private record StepFunctionTC(AccessFlag accessFlag, ClassFileFormatVersion transition) { public Set finalLocs() { - return accessFlag.locations(); + return accessFlag.locations(ClassFileFormatVersion.CURRENT_PREVIEW_FEATURES); } } @@ -294,7 +306,7 @@ private static void testFlagVersionConsistency() { for (var location : AccessFlag.Location.values()) { if (location.flags().contains(flag) != flag.locations().contains(location)) { throw new RuntimeException(String.format("AccessFlag and Location inconsistency:" + - "flag %s and location %s are inconsistent for the latest version")); + "flag %s and location %s are inconsistent for the latest version", flag, location)); } } } @@ -303,7 +315,7 @@ private static void testFlagVersionConsistency() { for (var location : AccessFlag.Location.values()) { if (location.flags(cffv).contains(flag) != flag.locations(cffv).contains(location)) { throw new RuntimeException(String.format("AccessFlag and Location inconsistency:" + - "flag %s and location %s are inconsistent for class file version %s")); + "flag %s and location %s are inconsistent for class file version %s", flag, location, cffv)); } } } diff --git a/test/jdk/java/lang/reflect/records/IsRecordTest.java b/test/jdk/java/lang/reflect/records/IsRecordTest.java index 4888e6d506e..0c4c7652275 100644 --- a/test/jdk/java/lang/reflect/records/IsRecordTest.java +++ b/test/jdk/java/lang/reflect/records/IsRecordTest.java @@ -23,10 +23,11 @@ /* * @test - * @bug 8255560 + * @bug 8255560 8326879 * @summary Class::isRecord should check that the current class is final and not abstract * @library /test/lib * @run testng/othervm IsRecordTest + * @run testng/othervm --enable-preview IsRecordTest */ import java.lang.classfile.ClassFile; diff --git a/test/jdk/java/lang/reflect/records/RecordReflectionTest.java b/test/jdk/java/lang/reflect/records/RecordReflectionTest.java index 0598e88f0c1..455d5e17305 100644 --- a/test/jdk/java/lang/reflect/records/RecordReflectionTest.java +++ b/test/jdk/java/lang/reflect/records/RecordReflectionTest.java @@ -23,11 +23,12 @@ /* * @test - * @bug 8235369 8235550 8247444 8320575 + * @bug 8235369 8235550 8247444 8326879 8320575 * @summary reflection test for records * @build R10 * @compile RecordReflectionTest.java * @run testng/othervm RecordReflectionTest + * @run testng/othervm --enable-preview RecordReflectionTest */ import java.lang.annotation.*; diff --git a/test/jdk/java/lang/reflect/valhalla/ValueClassesReflectionTest.java b/test/jdk/java/lang/reflect/valhalla/ValueClassesReflectionTest.java new file mode 100644 index 00000000000..0f43c3d13ca --- /dev/null +++ b/test/jdk/java/lang/reflect/valhalla/ValueClassesReflectionTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8334334 + * @summary reflection test for value classes + * @enablePreview + * @compile ValueClassesReflectionTest.java + * @run testng/othervm ValueClassesReflectionTest + */ + +import java.lang.annotation.*; +import java.lang.constant.ClassDesc; +import java.lang.reflect.*; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import org.testng.annotations.*; +import static org.testng.Assert.*; + +@Test +public class ValueClassesReflectionTest { + final static int numberOfFields = 2; + + value class ValueClass { + private int i = 0; + private String s = ""; + } + abstract value class AValueClass { + private int i = 0; + private String s = ""; + } + value record ValueRecord(int i, String s) {} + + class Inner {} + + @DataProvider(name = "valueClasses") + public Object[][] valueClassesData() { + return List.of( + ValueClass.class, + AValueClass.class, + ValueRecord.class + ).stream().map(c -> new Object[] {c}).toArray(Object[][]::new); + } + + @Test(dataProvider = "valueClasses") + public void testValueClasses(Class cls) { + assertTrue(cls.isValue()); + assertTrue(!cls.isIdentity()); + Set accessFlagSet = cls.accessFlags(); + assertTrue(!accessFlagSet.contains(AccessFlag.IDENTITY)); + } + + @DataProvider(name = "notValueClasses") + public Object[][] notSealedClassesData() { + return List.of( + Inner.class, + Object.class, + Void.class, Void[].class, + byte[].class, Byte[].class, + short[].class, Short[].class, + char[].class, Character[].class, + int[].class, Integer[].class, + long[].class, Long[].class, + float[].class, Float[].class, + double[].class, Double[].class, + boolean[].class, Boolean[].class, + String.class, String[].class + ).stream().map(c -> new Object[] {c}).toArray(Object[][]::new); + } + + @Test(dataProvider = "notValueClasses") + public void testNotValueClasses(Class cls) { + assertTrue(!cls.isValue(), " failing for class " + cls); + assertTrue(cls.isIdentity()); + } + + @Test(dataProvider = "valueClasses") + public void testValueClassReflection(Class valueClass) throws ReflectiveOperationException { + assertTrue(valueClass.isValue()); + Field[] fields = valueClass.getDeclaredFields(); + assertTrue(fields.length == numberOfFields); + for (Field field : fields) { + int mod = field.getModifiers(); + assertTrue((mod & Modifier.STRICT) != 0); + assertTrue((mod & Modifier.FINAL) != 0); + } + } +} diff --git a/test/jdk/java/lang/runtime/ObjectMethodsTest.java b/test/jdk/java/lang/runtime/ObjectMethodsTest.java index 62c8b034ed1..7309ab185c0 100644 --- a/test/jdk/java/lang/runtime/ObjectMethodsTest.java +++ b/test/jdk/java/lang/runtime/ObjectMethodsTest.java @@ -151,6 +151,13 @@ public void exceptions() { assertThrows(IAE, () -> ObjectMethods.bootstrap(LOOKUP, "hashCode", C.TO_STRING_DESC, C.class, "x;y", C.ACCESSORS)); assertThrows(IAE, () -> ObjectMethods.bootstrap(LOOKUP, "equals", C.HASHCODE_DESC, C.class, "x;y", C.ACCESSORS)); + assertThrows(IAE, () -> ObjectMethods.bootstrap(LOOKUP, "toString", methodType(String.class, this.getClass()), C.class, "x;y", C.ACCESSORS)); + assertThrows(IAE, () -> ObjectMethods.bootstrap(LOOKUP, "toString", C.TO_STRING_DESC, C.class, "x;y", + new MethodHandle[]{ + MethodHandles.lookup().findGetter(C.class, "x", int.class), + MethodHandles.lookup().findGetter(this.getClass(), "y", int.class), + })); + record NamePlusType(String mn, MethodType mt) {} List namePlusTypeList = List.of( new NamePlusType("toString", C.TO_STRING_DESC), @@ -169,6 +176,9 @@ record NamePlusType(String mn, MethodType mt) {} } } + // same field name and type as C::y + private int y; + // Based on the ObjectMethods internal implementation private static int hashCombiner(int x, int y) { return x*31 + y; diff --git a/test/jdk/java/security/Provider/SecurityProviderModularTest.java b/test/jdk/java/security/Provider/SecurityProviderModularTest.java index 32d45d514b6..bb060192c29 100644 --- a/test/jdk/java/security/Provider/SecurityProviderModularTest.java +++ b/test/jdk/java/security/Provider/SecurityProviderModularTest.java @@ -50,7 +50,6 @@ * @modules java.base/jdk.internal.module * @library /test/lib * @build jdk.test.lib.util.JarUtils - * jdk.test.lib.util.ModuleInfoWriter * TestProvider TestClient * @run main SecurityProviderModularTest CL true * @run main SecurityProviderModularTest CL false diff --git a/test/jdk/java/util/Collection/IteratorAtEnd.java b/test/jdk/java/util/Collection/IteratorAtEnd.java index 76c41afd8b3..79716e01679 100644 --- a/test/jdk/java/util/Collection/IteratorAtEnd.java +++ b/test/jdk/java/util/Collection/IteratorAtEnd.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2014, 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 @@ -23,7 +23,7 @@ /* * @test - * @bug 6529795 + * @bug 6529795 8336669 * @summary next() does not change iterator state if throws NoSuchElementException * @author Martin Buchholz */ @@ -99,7 +99,7 @@ static void testCollection(Collection c) { static void testMap(Map m) { try { for (int i = 0; i < 3*SIZE; i++) - m.put(i, i); + m.put("BASE-" + i, i); test(m.values()); test(m.keySet()); test(m.entrySet()); diff --git a/test/jdk/java/util/Collection/MOAT.java b/test/jdk/java/util/Collection/MOAT.java index e87b071d80a..ae6d172d6a8 100644 --- a/test/jdk/java/util/Collection/MOAT.java +++ b/test/jdk/java/util/Collection/MOAT.java @@ -26,12 +26,13 @@ * @bug 6207984 6272521 6192552 6269713 6197726 6260652 5073546 4137464 * 4155650 4216399 4294891 6282555 6318622 6355327 6383475 6420753 * 6431845 4802633 6570566 6570575 6570631 6570924 6691185 6691215 - * 4802647 7123424 8024709 8193128 8327858 + * 4802647 7123424 8024709 8193128 8327858 8346307 * @summary Run many tests on many Collection and Map implementations * @author Martin Buchholz * @modules java.base/java.util:open * @enablePreview * @run main MOAT + * @run main MOAT --enable-preview * @key randomness */ diff --git a/test/jdk/java/util/Collections/CheckedIdentityMap.java b/test/jdk/java/util/Collections/CheckedIdentityMap.java index d51f5b4fd0a..b0bc6478155 100644 --- a/test/jdk/java/util/Collections/CheckedIdentityMap.java +++ b/test/jdk/java/util/Collections/CheckedIdentityMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2013, 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 @@ -23,7 +23,7 @@ /* * @test - * @bug 6585904 + * @bug 6585904 8336669 * @run testng CheckedIdentityMap * @summary Checked collections with underlying maps with identity comparisons */ @@ -41,18 +41,18 @@ public class CheckedIdentityMap { @Test public void testHashCode() { - Map m1 = checkedMap( - new IdentityHashMap(), - Integer.class, Integer.class); - Map m2 = checkedMap( - new IdentityHashMap(), - Integer.class, Integer.class); - // NB: these are unique instances. Compare vs. Integer.valueOf(1) - m1.put(new Integer(1), new Integer(1)); - m2.put(new Integer(1), new Integer(1)); + Map m1 = checkedMap( + new IdentityHashMap<>(), + String.class, String.class); + Map m2 = checkedMap( + new IdentityHashMap<>(), + String.class, String.class); + // NB: these are unique instances. Compare vs. "A" + m1.put(new String("A"), new String("A")); + m2.put(new String("A"), new String("A")); - Map.Entry e1 = m1.entrySet().iterator().next(); - Map.Entry e2 = m2.entrySet().iterator().next(); + Map.Entry e1 = m1.entrySet().iterator().next(); + Map.Entry e2 = m2.entrySet().iterator().next(); assertNotEquals(e1, e2); assertEquals(e1.hashCode(), hashCode(e1)); diff --git a/test/jdk/java/util/Collections/CheckedListBash.java b/test/jdk/java/util/Collections/CheckedListBash.java index b65504bad45..1adfc0ddd4a 100644 --- a/test/jdk/java/util/Collections/CheckedListBash.java +++ b/test/jdk/java/util/Collections/CheckedListBash.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 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 @@ -23,7 +23,7 @@ /* * @test - * @bug 4904067 + * @bug 4904067 8336669 * @summary Unit test for Collections.checkedList * @author Josh Bloch * @key randomness @@ -109,7 +109,7 @@ public static void main(String[] args) { List s = newList(); for (int i=0; i { + @Override + public int compareTo(Int o) { + return Integer.compare(intValue, o.intValue); + } + } + public static void main(String[] args) { checkMap(false); checkMap(true); diff --git a/test/jdk/java/util/Map/Get.java b/test/jdk/java/util/Map/Get.java index f89e72aacff..80e2d80e70f 100644 --- a/test/jdk/java/util/Map/Get.java +++ b/test/jdk/java/util/Map/Get.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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,7 @@ /* * @test - * @bug 6306829 + * @bug 6306829 8336669 * @summary Verify assertions in get() javadocs * @author Martin Buchholz */ @@ -43,19 +43,27 @@ public class Get { + // An identity class holding an char (like non-Preview Character) + record Char(char c) implements Comparable { + @Override + public int compareTo(Char ch) { + return Character.compare(c, ch.c); + } + } + private static void realMain(String[] args) throws Throwable { - testMap(new Hashtable()); - testMap(new HashMap()); - testMap(new IdentityHashMap()); - testMap(new LinkedHashMap()); - testMap(new ConcurrentHashMap()); - testMap(new WeakHashMap()); - testMap(new TreeMap()); - testMap(new ConcurrentSkipListMap()); + testMap(new Hashtable()); + testMap(new HashMap()); + testMap(new IdentityHashMap()); + testMap(new LinkedHashMap()); + testMap(new ConcurrentHashMap()); + testMap(new WeakHashMap()); + testMap(new TreeMap()); + testMap(new ConcurrentSkipListMap()); } - private static void put(Map m, - Character key, Boolean value, + private static void put(Map m, + Char key, Boolean value, Boolean oldValue) { if (oldValue != null) { check("containsValue(oldValue)", m.containsValue(oldValue)); @@ -70,7 +78,7 @@ private static void put(Map m, check("!isEmpty", ! m.isEmpty()); } - private static void testMap(Map m) { + private static void testMap(Map m) { // We verify following assertions in get(Object) method javadocs boolean permitsNullKeys = (! (m instanceof ConcurrentMap || m instanceof Hashtable || @@ -80,10 +88,11 @@ private static void testMap(Map m) { boolean usesIdentity = m instanceof IdentityHashMap; System.err.println(m.getClass()); - put(m, 'A', true, null); - put(m, 'A', false, true); // Guaranteed identical by JLS - put(m, 'B', true, null); - put(m, new Character('A'), false, usesIdentity ? null : false); + Char aCh = new Char('A'); + put(m, aCh, true, null); + put(m, aCh, false, true); + put(m, new Char('B'), true, null); + put(m, new Char('A'), false, usesIdentity ? null : false); if (permitsNullKeys) { try { put(m, null, true, null); @@ -101,17 +110,18 @@ private static void testMap(Map m) { } if (permitsNullValues) { try { - put(m, 'C', null, null); - put(m, 'C', true, null); - put(m, 'C', null, true); + Char cCh = new Char('C'); + put(m, cCh, null, null); + put(m, cCh, true, null); + put(m, cCh, null, true); } catch (Throwable t) { unexpected(m.getClass().getName(), t); } } else { - try { m.put('A', null); fail(m.getClass().getName() + " did not reject null key"); } + try { m.put(new Char('A'), null); fail(m.getClass().getName() + " did not reject null key"); } catch (NullPointerException e) {} catch (Throwable t) { unexpected(m.getClass().getName(), t); } - try { m.put('C', null); fail(m.getClass().getName() + " did not reject null key"); } + try { m.put(new Char('C'), null); fail(m.getClass().getName() + " did not reject null key"); } catch (NullPointerException e) {} catch (Throwable t) { unexpected(m.getClass().getName(), t); } } diff --git a/test/jdk/java/util/Map/LockStep.java b/test/jdk/java/util/Map/LockStep.java index 0560a5ab695..e496ba3dd6b 100644 --- a/test/jdk/java/util/Map/LockStep.java +++ b/test/jdk/java/util/Map/LockStep.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 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,7 @@ /* * @test - * @bug 6612102 + * @bug 6612102 8336669 * @summary Test Map implementations for mutual compatibility * @key randomness */ @@ -49,6 +49,23 @@ * It would be good to add more "Lockstep-style" tests to this file. */ public class LockStep { + // An interned identity class holding an int (like non-Preview Integer) + // interned for IdentityHashMap + // identity for WeakHashMap + private record Int(int intValue) implements Comparable { + private static final Map interned = new HashMap<>(100); + + // Return a unique Ini for each int. + static Int intern(int intValue) { + return interned.computeIfAbsent(intValue, (i) -> new Int(i)); + } + + @Override + public int compareTo(Int o) { + return Integer.compare(intValue, o.intValue); + } + } + void mapsEqual(Map m1, Map m2) { equal(m1, m2); equal(m2, m1); @@ -112,15 +129,15 @@ void test(String[] args) throws Throwable { new TreeMap(), new ConcurrentHashMap(16), new ConcurrentSkipListMap(), - Collections.checkedMap(new HashMap(16), Integer.class, Integer.class), - Collections.checkedSortedMap(new TreeMap(), Integer.class, Integer.class), - Collections.checkedNavigableMap(new TreeMap(), Integer.class, Integer.class), + Collections.checkedMap(new HashMap(16), Int.class, Integer.class), + Collections.checkedSortedMap(new TreeMap(), Int.class, Integer.class), + Collections.checkedNavigableMap(new TreeMap(), Int.class, Integer.class), Collections.synchronizedMap(new HashMap(16)), Collections.synchronizedSortedMap(new TreeMap()), Collections.synchronizedNavigableMap(new TreeMap())); for (int j = 0; j < 10; j++) - put(maps, r.nextInt(100), r.nextInt(100)); + put(maps, Int.intern(r.nextInt(100)), r.nextInt(100)); removeLastTwo(maps); } } diff --git a/test/jdk/java/util/Map/ToArray.java b/test/jdk/java/util/Map/ToArray.java index f7a8de716cf..36757ee06e5 100644 --- a/test/jdk/java/util/Map/ToArray.java +++ b/test/jdk/java/util/Map/ToArray.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ /* * @test - * @bug 8008785 + * @bug 8008785 8336669 * @summary Ensure toArray() implementations return correct results. * @author Mike Duigou */ @@ -38,8 +38,12 @@ public class ToArray { */ private static final int TEST_SIZE = 5000; + static String genString(int i) { + return "BASE-" + HexFormat.of().toHexDigits(i); + } + private static void realMain(String[] args) throws Throwable { - Map[] maps = (Map[]) new Map[]{ + Map[] maps = (Map[]) new Map[]{ new HashMap<>(), new Hashtable<>(), new IdentityHashMap<>(), @@ -51,7 +55,7 @@ private static void realMain(String[] args) throws Throwable { }; // for each map type. - for (Map map : maps) { + for (Map map : maps) { try { testMap(map); } catch(Exception all) { @@ -60,19 +64,20 @@ private static void realMain(String[] args) throws Throwable { } } - private static final Integer[] KEYS = new Integer[TEST_SIZE]; + private static final String[] KEYS = new String[TEST_SIZE]; private static final Long[] VALUES = new Long[TEST_SIZE]; static { for (int each = 0; each < TEST_SIZE; each++) { - KEYS[each] = Integer.valueOf(each); + // BUG: WeakHashMap of value object! + KEYS[each] = genString(each); VALUES[each] = Long.valueOf(each + TEST_SIZE); } } - private static void testMap(Map map) { + private static void testMap(Map map) { System.out.println("Testing " + map.getClass()); System.out.flush(); @@ -98,9 +103,9 @@ private static void testMap(Map map) { } // check the entries - Map.Entry[] entries = map.entrySet().toArray(new Map.Entry[TEST_SIZE]); - Arrays.sort( entries,new Comparator>() { - public int compare(Map.Entry o1, Map.Entry o2) { + Map.Entry[] entries = map.entrySet().toArray(new Map.Entry[TEST_SIZE]); + Arrays.sort( entries,new Comparator>() { + public int compare(Map.Entry o1, Map.Entry o2) { return o1.getKey().compareTo(o2.getKey()); }}); diff --git a/test/jdk/java/util/ServiceLoader/BadProvidersTest.java b/test/jdk/java/util/ServiceLoader/BadProvidersTest.java index 8560126eb00..ce1942a952d 100644 --- a/test/jdk/java/util/ServiceLoader/BadProvidersTest.java +++ b/test/jdk/java/util/ServiceLoader/BadProvidersTest.java @@ -23,6 +23,7 @@ /** * @test + * @enablePreview * @library /test/lib * @modules jdk.compiler * @build jdk.test.lib.compiler.CompilerUtils @@ -215,7 +216,7 @@ public void testWithTwoFactoryMethods() throws Exception { var bytes = ClassFile.of().build(ClassDesc.of("p", "ProviderFactory"), clb -> { clb.withSuperclass(CD_Object); - clb.withFlags(AccessFlag.PUBLIC, AccessFlag.SUPER); + clb.withFlags(AccessFlag.PUBLIC, AccessFlag.IDENTITY); var providerFactory$1 = ClassDesc.of("p", "ProviderFactory$1"); diff --git a/test/jdk/java/util/Spliterator/SpliteratorCharacteristics.java b/test/jdk/java/util/Spliterator/SpliteratorCharacteristics.java index 3e50bfaaca9..fd9b61db74b 100644 --- a/test/jdk/java/util/Spliterator/SpliteratorCharacteristics.java +++ b/test/jdk/java/util/Spliterator/SpliteratorCharacteristics.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ /** * @test - * @bug 8020156 8020009 8022326 8012913 8024405 8024408 8071477 + * @bug 8020156 8020009 8022326 8012913 8024405 8024408 8071477 8336672 * @run testng SpliteratorCharacteristics */ @@ -111,7 +111,7 @@ public String toString() { } public void testSpliteratorFromCollection() { - List l = Arrays.asList(1, 2, 3, 4); + List l = Arrays.asList("A", "B", "C", "D"); { Spliterator s = Spliterators.spliterator(l, 0); @@ -291,7 +291,7 @@ public void testConcurrentSkipListMap() { } public void testConcurrentSkipListMapWithComparator() { - assertSortedMapCharacteristics(new ConcurrentSkipListMap<>(Comparator.reverseOrder()), + assertSortedMapCharacteristics(new ConcurrentSkipListMap<>(Comparator.reverseOrder()), Spliterator.CONCURRENT | Spliterator.NONNULL | Spliterator.DISTINCT | Spliterator.SORTED | Spliterator.ORDERED); @@ -315,11 +315,11 @@ public void testConcurrentSkipListSetWithComparator() { // - void assertMapCharacteristics(Map m, int keyCharacteristics) { + void assertMapCharacteristics(Map m, int keyCharacteristics) { assertMapCharacteristics(m, keyCharacteristics, 0); } - void assertMapCharacteristics(Map m, int keyCharacteristics, int notValueCharacteristics) { + void assertMapCharacteristics(Map m, int keyCharacteristics, int notValueCharacteristics) { initMap(m); assertCharacteristics(m.keySet(), keyCharacteristics); @@ -336,7 +336,7 @@ void assertMapCharacteristics(Map m, int keyCharacteristics, in } } - void assertSetCharacteristics(Set s, int keyCharacteristics) { + void assertSetCharacteristics(Set s, int keyCharacteristics) { initSet(s); assertCharacteristics(s, keyCharacteristics); @@ -346,10 +346,10 @@ void assertSetCharacteristics(Set s, int keyCharacteristics) { } } - void assertSortedMapCharacteristics(SortedMap m, int keyCharacteristics) { + void assertSortedMapCharacteristics(SortedMap m, int keyCharacteristics) { assertMapCharacteristics(m, keyCharacteristics, Spliterator.SORTED); - Set keys = m.keySet(); + Set keys = m.keySet(); if (m.comparator() != null) { assertNotNullComparator(keys); } @@ -362,7 +362,7 @@ void assertSortedMapCharacteristics(SortedMap m, int keyCharact assertNotNullComparator(m.entrySet()); } - void assertSortedSetCharacteristics(SortedSet s, int keyCharacteristics) { + void assertSortedSetCharacteristics(SortedSet s, int keyCharacteristics) { assertSetCharacteristics(s, keyCharacteristics); if (s.comparator() != null) { @@ -373,15 +373,15 @@ void assertSortedSetCharacteristics(SortedSet s, int keyCharacteristics } } - void initMap(Map m) { - m.put(1, "4"); - m.put(2, "3"); - m.put(3, "2"); - m.put(4, "1"); + void initMap(Map m) { + m.put("A", "4"); + m.put("B", "3"); + m.put("C", "2"); + m.put("D", "1"); } - void initSet(Set s) { - s.addAll(Arrays.asList(1, 2, 3, 4)); + void initSet(Set s) { + s.addAll(Arrays.asList("A", "B", "C", "D")); } void assertCharacteristics(Collection c, int expectedCharacteristics) { diff --git a/test/jdk/java/util/Spliterator/SpliteratorFailFastTest.java b/test/jdk/java/util/Spliterator/SpliteratorFailFastTest.java index 84d6ca9d6ab..9a181ad36f1 100644 --- a/test/jdk/java/util/Spliterator/SpliteratorFailFastTest.java +++ b/test/jdk/java/util/Spliterator/SpliteratorFailFastTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -47,7 +47,7 @@ /** * @test - * @bug 8148748 + * @bug 8148748 8336672 * @summary Spliterator fail-fast tests * @run testng SpliteratorFailFastTest */ @@ -64,8 +64,8 @@ public static Object[][] spliteratorDataProvider() { } List data = new ArrayList<>(); - SpliteratorDataBuilder db = - new SpliteratorDataBuilder<>(data, 5, Arrays.asList(1, 2, 3, 4)); + SpliteratorDataBuilder db = + new SpliteratorDataBuilder<>(data, "Z", Arrays.asList("A", "B", "C", "D")); // Collections @@ -84,7 +84,7 @@ public static Object[][] spliteratorDataProvider() { db.addCollection(TreeSet::new); db.addCollection(c -> { - Stack s = new Stack<>(); + Stack s = new Stack<>(); s.addAll(c); return s; }); @@ -106,7 +106,7 @@ public static Object[][] spliteratorDataProvider() { // This fails when run through jtreg but passes when run through // ant // db.addMap(IdentityHashMap::new); - + // BUG: Assumes identity db.addMap(WeakHashMap::new); // @@@ Descending maps etc diff --git a/test/jdk/java/util/Spliterator/SpliteratorLateBindingFailFastHelper.java b/test/jdk/java/util/Spliterator/SpliteratorLateBindingFailFastHelper.java index cc89340efd7..a89794eddfc 100644 --- a/test/jdk/java/util/Spliterator/SpliteratorLateBindingFailFastHelper.java +++ b/test/jdk/java/util/Spliterator/SpliteratorLateBindingFailFastHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 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 @@ -182,26 +182,26 @@ public void update() { } } - static class AbstractRandomAccessListImpl extends AbstractList implements RandomAccess { - List l; + static class AbstractRandomAccessListImpl extends AbstractList implements RandomAccess { + List l; - AbstractRandomAccessListImpl(Collection c) { + AbstractRandomAccessListImpl(Collection c) { this.l = new ArrayList<>(c); } @Override - public boolean add(Integer integer) { + public boolean add(T value) { modCount++; - return l.add(integer); + return l.add(value); } @Override - public Iterator iterator() { + public Iterator iterator() { return l.iterator(); } @Override - public Integer get(int index) { + public T get(int index) { return l.get(index); } @@ -217,7 +217,7 @@ public int size() { } @Override - public List subList(int fromIndex, int toIndex) { + public List subList(int fromIndex, int toIndex) { return l.subList(fromIndex, toIndex); } } diff --git a/test/jdk/java/util/Spliterator/SpliteratorLateBindingTest.java b/test/jdk/java/util/Spliterator/SpliteratorLateBindingTest.java index 42553cbe4e5..354abc5108c 100644 --- a/test/jdk/java/util/Spliterator/SpliteratorLateBindingTest.java +++ b/test/jdk/java/util/Spliterator/SpliteratorLateBindingTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -52,7 +52,7 @@ /** * @test - * @bug 8148748 8170155 + * @bug 8148748 8170155 8336672 * @summary Spliterator last-binding tests * @run testng SpliteratorLateBindingTest */ @@ -69,8 +69,8 @@ public static Object[][] sourceDataProvider() { } List data = new ArrayList<>(); - SpliteratorDataBuilder db = - new SpliteratorDataBuilder<>(data, 5, Arrays.asList(1, 2, 3, 4)); + SpliteratorDataBuilder db = + new SpliteratorDataBuilder<>(data, "Z", Arrays.asList("A", "B", "C", "D")); // Collections @@ -89,7 +89,7 @@ public static Object[][] sourceDataProvider() { db.addCollection(TreeSet::new); db.addCollection(c -> { - Stack s = new Stack<>(); + Stack s = new Stack<>(); s.addAll(c); return s; }); @@ -106,6 +106,7 @@ public static Object[][] sourceDataProvider() { db.addMap(IdentityHashMap::new); + // BUG: Assumes identity db.addMap(WeakHashMap::new); // @@@ Descending maps etc @@ -113,6 +114,7 @@ public static Object[][] sourceDataProvider() { // BitSet + // BUG: Assumes identity in WeakHashMap List bits = List.of(0, 1, 2); Function bitsSource = bs -> bs.stream().spliterator(); db.add("new BitSet.stream().spliterator() ADD", diff --git a/test/jdk/java/util/Spliterator/SpliteratorTraversingAndSplittingTest.java b/test/jdk/java/util/Spliterator/SpliteratorTraversingAndSplittingTest.java index ece63bfe813..cfb0c49b8da 100644 --- a/test/jdk/java/util/Spliterator/SpliteratorTraversingAndSplittingTest.java +++ b/test/jdk/java/util/Spliterator/SpliteratorTraversingAndSplittingTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,12 +25,16 @@ * @test * @summary Spliterator traversing and splitting tests * @library /lib/testlibrary/bootlib + * @modules java.base/jdk.internal.misc * @build java.base/java.util.SpliteratorOfIntDataBuilder * java.base/java.util.SpliteratorTestHelper * @run testng SpliteratorTraversingAndSplittingTest - * @bug 8020016 8071477 8072784 8169838 + * @run testng/othervm --enable-preview SpliteratorTraversingAndSplittingTest + * @bug 8020016 8071477 8072784 8169838 8336672 */ +import jdk.internal.misc.PreviewFeatures; + import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -628,16 +632,19 @@ else if (size == 1) { db.addMap(IdentityHashMap::new); - db.addMap(WeakHashMap::new); - - db.addMap(m -> { - // Create a Map ensuring that for large sizes - // buckets will be consist of 2 or more entries - WeakHashMap cm = new WeakHashMap<>(1, m.size() + 1); - for (Map.Entry e : m.entrySet()) - cm.put(e.getKey(), e.getValue()); - return cm; - }, "new java.util.WeakHashMap(1, size + 1)"); + if (!PreviewFeatures.isEnabled()) { + // With --enable-preview, WeakHashmap is not tested with Integer, a value class + db.addMap(WeakHashMap::new); + + db.addMap(m -> { + // Create a Map ensuring that for large sizes + // buckets will consist of 2 or more entries + WeakHashMap cm = new WeakHashMap<>(1, m.size() + 1); + for (Map.Entry e : m.entrySet()) + cm.put(e.getKey(), e.getValue()); + return cm; + }, "new java.util.WeakHashMap(1, size + 1)"); + } // @@@ Descending maps etc db.addMap(TreeMap::new); diff --git a/test/jdk/java/util/WeakHashMapValues.java b/test/jdk/java/util/WeakHashMapValues.java new file mode 100644 index 00000000000..a3acfb26627 --- /dev/null +++ b/test/jdk/java/util/WeakHashMapValues.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.WeakHashMap; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/* + * @test + * @summary Check WeakHashMap throws IdentityException when Value Objects are put + * @enablePreview + * @run junit WeakHashMapValues + */ +public class WeakHashMapValues { + + /* + * Check that any kind of put with a value class as a key throws IdentityException + */ + @Test + void checkThrowsIdentityException() { + WeakHashMap whm = new WeakHashMap<>(); + Object key = new Foo(1); + assertThrows(IdentityException.class, () -> whm.put(key, "1")); + assertThrows(IdentityException.class, () -> whm.putIfAbsent(key, "2")); + assertThrows(IdentityException.class, () -> whm.compute(key, (_, _) -> "3")); + assertThrows(IdentityException.class, () -> whm.computeIfAbsent(key, (_) -> "4")); + + HashMap hmap = new HashMap<>(); + hmap.put(key, "6"); + assertThrows(IdentityException.class, () -> whm.putAll(hmap)); + } + + /* + * Check that any kind of put with Integer as a value class as a key throws IdentityException + */ + @Test + void checkIntegerThrowsIdentityException() { + WeakHashMap whm = new WeakHashMap<>(); + Object key = 1; + assertThrows(IdentityException.class, () -> whm.put(key, "1")); + assertThrows(IdentityException.class, () -> whm.putIfAbsent(key, "2")); + assertThrows(IdentityException.class, () -> whm.compute(key, (_, _) -> "3")); + assertThrows(IdentityException.class, () -> whm.computeIfAbsent(key, (_) -> "4")); + + HashMap hmap = new HashMap<>(); + hmap.put(key, "6"); + assertThrows(IdentityException.class, () -> whm.putAll(hmap)); + + } + + /** + * Check that queries with a value object return false or null. + */ + @Test + void checkValueObjectGet() { + WeakHashMap whm = new WeakHashMap<>(); + Object key = "X"; + Object v = new Foo(1); + assertEquals(whm.get(v), null, "Get of value object should return null"); + assertEquals(whm.containsKey(v), false, "containsKey should return false"); + } + + /** + * Check WeakHashMap.putAll from a source map containing a value object as a key throws. + */ + @Test + void checkValueObjectPutAll() { + // src is mix of identity and value objects (Integer is value class with --enable-preview) + HashMap srcMap = new LinkedHashMap<>(); + srcMap.put("abc", "Vabc"); + srcMap.put(1, "V1"); + srcMap.put("xyz", "Vxyz"); + WeakHashMap whm = new WeakHashMap<>(); + assertThrows(IdentityException.class, () -> whm.putAll(srcMap)); + assertTrue(whm.containsKey("abc"), "Identity key should have been copied"); + assertFalse(whm.containsKey(1), "Value object key should not have been copied"); + assertEquals(1, whm.size(), "Result map size"); + } +} + +value class Foo { + int x; + Foo(int x) { + this.x = x; + } +} diff --git a/test/jdk/java/util/stream/test/org/openjdk/tests/java/util/stream/CollectionAndMapModifyStreamTest.java b/test/jdk/java/util/stream/test/org/openjdk/tests/java/util/stream/CollectionAndMapModifyStreamTest.java index 07911219065..a85cdc3585d 100644 --- a/test/jdk/java/util/stream/test/org/openjdk/tests/java/util/stream/CollectionAndMapModifyStreamTest.java +++ b/test/jdk/java/util/stream/test/org/openjdk/tests/java/util/stream/CollectionAndMapModifyStreamTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2016, 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 @@ -28,14 +28,13 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ConcurrentSkipListSet; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedTransferQueue; import java.util.concurrent.PriorityBlockingQueue; import java.util.function.Supplier; -import java.util.stream.LambdaTestHelpers; +import java.util.stream.IntStream; + import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -45,46 +44,53 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; +/* + * @test + * @summary Tests laziness of stream operations + * @bug 8336672 + */ + /** * Tests laziness of stream operations -- mutations to the source after the stream() but prior to terminal operations * are reflected in the stream contents. */ @Test public class CollectionAndMapModifyStreamTest { + // Well known Identity instances; needed for IdentityHashMap + static List CONTENT = IntStream.range(0, 10).mapToObj(i -> "BASE-" + i).toList(); @DataProvider(name = "collections") public Object[][] createCollections() { - List content = LambdaTestHelpers.countTo(10); - List> collections = new ArrayList<>(); - collections.add(new ArrayList<>(content)); - collections.add(new LinkedList<>(content)); - collections.add(new Vector<>(content)); + List> collections = new ArrayList<>(); + collections.add(new ArrayList<>(CONTENT)); + collections.add(new LinkedList<>(CONTENT)); + collections.add(new Vector<>(CONTENT)); - collections.add(new HashSet<>(content)); - collections.add(new LinkedHashSet<>(content)); - collections.add(new TreeSet<>(content)); + collections.add(new HashSet<>(CONTENT)); + collections.add(new LinkedHashSet<>(CONTENT)); + collections.add(new TreeSet<>(CONTENT)); - Stack stack = new Stack<>(); - stack.addAll(content); + Stack stack = new Stack<>(); + stack.addAll(CONTENT); collections.add(stack); - collections.add(new PriorityQueue<>(content)); - collections.add(new ArrayDeque<>(content)); + collections.add(new PriorityQueue<>(CONTENT)); + collections.add(new ArrayDeque<>(CONTENT)); // Concurrent collections - collections.add(new ConcurrentSkipListSet<>(content)); + collections.add(new ConcurrentSkipListSet<>(CONTENT)); - ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(content.size()); - for (Integer i : content) + ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(CONTENT.size()); + for (String i : CONTENT) arrayBlockingQueue.add(i); collections.add(arrayBlockingQueue); - collections.add(new PriorityBlockingQueue<>(content)); - collections.add(new LinkedBlockingQueue<>(content)); - collections.add(new LinkedTransferQueue<>(content)); - collections.add(new ConcurrentLinkedQueue<>(content)); - collections.add(new LinkedBlockingDeque<>(content)); - collections.add(new ConcurrentLinkedDeque<>(content)); + collections.add(new PriorityBlockingQueue<>(CONTENT)); + collections.add(new LinkedBlockingQueue<>(CONTENT)); + collections.add(new LinkedTransferQueue<>(CONTENT)); + collections.add(new ConcurrentLinkedQueue<>(CONTENT)); + collections.add(new LinkedBlockingDeque<>(CONTENT)); + collections.add(new ConcurrentLinkedDeque<>(CONTENT)); Object[][] params = new Object[collections.size()][]; for (int i = 0; i < collections.size(); i++) { @@ -95,22 +101,23 @@ public Object[][] createCollections() { } @Test(dataProvider = "collections") - public void testCollectionSizeRemove(String name, Collection c) { - assertTrue(c.remove(1)); - Stream s = c.stream(); - assertTrue(c.remove(2)); + public void testCollectionSizeRemove(String name, Collection c) { + assertTrue(c.remove(CONTENT.get(0))); + Stream s = c.stream(); + assertTrue(c.remove(CONTENT.get(1))); Object[] result = s.toArray(); assertEquals(result.length, c.size()); } @DataProvider(name = "maps") public Object[][] createMaps() { - Map content = new HashMap<>(); + Map content = new HashMap<>(); for (int i = 0; i < 10; i++) { - content.put(i, i); + var ix = CONTENT.get(i); + content.put(ix, ix); } - Map>> maps = new HashMap<>(); + Map>> maps = new HashMap<>(); maps.put(HashMap.class.getName(), () -> new HashMap<>(content)); maps.put(LinkedHashMap.class.getName(), () -> new LinkedHashMap<>(content)); @@ -132,34 +139,33 @@ public Object[][] createMaps() { Object[][] params = new Object[maps.size()][]; int i = 0; - for (Map.Entry>> e : maps.entrySet()) { + for (Map.Entry>> e : maps.entrySet()) { params[i++] = new Object[]{e.getKey(), e.getValue()}; - } return params; } @Test(dataProvider = "maps", groups = { "serialization-hostile" }) - public void testMapKeysSizeRemove(String name, Supplier> c) { + public void testMapKeysSizeRemove(String name, Supplier> c) { testCollectionSizeRemove(name + " key set", c.get().keySet()); } @Test(dataProvider = "maps", groups = { "serialization-hostile" }) - public void testMapValuesSizeRemove(String name, Supplier> c) { + public void testMapValuesSizeRemove(String name, Supplier> c) { testCollectionSizeRemove(name + " value set", c.get().values()); } @Test(dataProvider = "maps") - public void testMapEntriesSizeRemove(String name, Supplier> c) { + public void testMapEntriesSizeRemove(String name, Supplier> c) { testEntrySetSizeRemove(name + " entry set", c.get().entrySet()); } - private void testEntrySetSizeRemove(String name, Set> c) { - Map.Entry first = c.iterator().next(); + private void testEntrySetSizeRemove(String name, Set> c) { + Map.Entry first = c.iterator().next(); assertTrue(c.remove(first)); - Stream> s = c.stream(); - Map.Entry second = c.iterator().next(); + Stream> s = c.stream(); + Map.Entry second = c.iterator().next(); assertTrue(c.remove(second)); Object[] result = s.toArray(); assertEquals(result.length, c.size()); diff --git a/test/jdk/javax/naming/module/src/test/test/StoreObject.ldap b/test/jdk/javax/naming/module/src/test/test/StoreObject.ldap index af1a5d52f0a..4506313f218 100644 --- a/test/jdk/javax/naming/module/src/test/test/StoreObject.ldap +++ b/test/jdk/javax/naming/module/src/test/test/StoreObject.ldap @@ -661,4 +661,3 @@ 0000: 30 22 02 01 08 42 00 A0 1B 30 19 04 17 32 2E 31 0"...B...0...2.1 0010: 36 2E 38 34 30 2E 31 2E 31 31 33 37 33 30 2E 33 6.840.1.113730.3 0020: 2E 34 2E 32 .4.2 - diff --git a/test/jdk/javax/security/auth/login/modules/JaasModularClientTest.java b/test/jdk/javax/security/auth/login/modules/JaasModularClientTest.java index b3f3e9a2e86..07002519ddb 100644 --- a/test/jdk/javax/security/auth/login/modules/JaasModularClientTest.java +++ b/test/jdk/javax/security/auth/login/modules/JaasModularClientTest.java @@ -45,7 +45,7 @@ * @summary Test custom JAAS login module with all possible modular option. * @modules java.base/jdk.internal.module * @library /test/lib - * @build jdk.test.lib.util.JarUtils jdk.test.lib.util.ModuleInfoWriter + * @build jdk.test.lib.util.JarUtils * @build TestLoginModule JaasClient * @run main JaasModularClientTest false * @run main JaasModularClientTest true diff --git a/test/jdk/javax/security/auth/login/modules/JaasModularDefaultHandlerTest.java b/test/jdk/javax/security/auth/login/modules/JaasModularDefaultHandlerTest.java index 93c3240ffc3..3b1ea5c54d2 100644 --- a/test/jdk/javax/security/auth/login/modules/JaasModularDefaultHandlerTest.java +++ b/test/jdk/javax/security/auth/login/modules/JaasModularDefaultHandlerTest.java @@ -45,7 +45,7 @@ * @enablePreview * @modules java.base/jdk.internal.module * @library /test/lib - * @build jdk.test.lib.util.JarUtils jdk.test.lib.util.ModuleInfoWriter + * @build jdk.test.lib.util.JarUtils * @build TestCallbackHandler TestLoginModule JaasClientWithDefaultHandler * @run main JaasModularDefaultHandlerTest */ diff --git a/test/jdk/jdk/classfile/AdaptCodeTest.java b/test/jdk/jdk/classfile/AdaptCodeTest.java index 80c526c63c7..82967b9fbd0 100644 --- a/test/jdk/jdk/classfile/AdaptCodeTest.java +++ b/test/jdk/jdk/classfile/AdaptCodeTest.java @@ -24,6 +24,7 @@ /* * @test * @summary Testing ClassFile Code Adaptation. + * @enablePreview * @run junit AdaptCodeTest */ diff --git a/test/jdk/jdk/classfile/CorpusTest.java b/test/jdk/jdk/classfile/CorpusTest.java index 513005370e7..0d8c35e47df 100644 --- a/test/jdk/jdk/classfile/CorpusTest.java +++ b/test/jdk/jdk/classfile/CorpusTest.java @@ -25,6 +25,7 @@ * @test * @bug 8325485 * @summary Testing ClassFile on small Corpus. + * @enablePreview * @build helpers.* testdata.* * @run junit/othervm/timeout=480 -Djunit.jupiter.execution.parallel.enabled=true CorpusTest */ diff --git a/test/jdk/jdk/classfile/LvtTest.java b/test/jdk/jdk/classfile/LvtTest.java index d95a4fb7b29..5d3075a311f 100644 --- a/test/jdk/jdk/classfile/LvtTest.java +++ b/test/jdk/jdk/classfile/LvtTest.java @@ -24,6 +24,7 @@ /* * @test * @summary Testing ClassFile local variable table. + * @enablePreview * @compile -g testdata/Lvt.java * @run junit LvtTest */ diff --git a/test/jdk/jdk/classfile/StrictStackMapsTest.java b/test/jdk/jdk/classfile/StrictStackMapsTest.java new file mode 100644 index 00000000000..240230f8020 --- /dev/null +++ b/test/jdk/jdk/classfile/StrictStackMapsTest.java @@ -0,0 +1,402 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @enablePreview + * @library /test/lib + * @build jdk.test.lib.ByteCodeLoader + * @run junit/othervm -Xverify StrictStackMapsTest + */ + +import java.lang.classfile.AccessFlags; +import java.lang.classfile.Attributes; +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassTransform; +import java.lang.classfile.attribute.StackMapFrameInfo; +import java.lang.classfile.attribute.StackMapTableAttribute; +import java.lang.classfile.constantpool.ConstantPoolBuilder; +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import jdk.test.lib.ByteCodeLoader; +import org.junit.jupiter.api.Test; + +import static java.lang.classfile.ClassFile.*; +import static java.lang.constant.ConstantDescs.*; +import static org.junit.jupiter.api.Assertions.*; + +class StrictStackMapsTest { + @Test + void basicBranchTest() throws Throwable { + var className = "Test"; + var classDesc = ClassDesc.of(className); + var classBytes = ClassFile.of().build(classDesc, clb -> clb + .withVersion(latestMajorVersion(), PREVIEW_MINOR_VERSION) + .withFlags(ACC_PUBLIC | ACC_IDENTITY) + .withField("fs", CD_int, ACC_STRICT) + .withField("fsf", CD_int, ACC_STRICT | ACC_FINAL) + .withMethodBody(INIT_NAME, MTD_void, 0, cob -> cob + .aload(0) + .iconst_5() + .putfield(classDesc, "fs", CD_int) + .aload(0) + .iconst_0() + .ifThenElse(thb -> thb + .iconst_3() + .putfield(classDesc, "fsf", CD_int), elb -> elb + .iconst_2() + .putfield(classDesc, "fsf", CD_int)) + .aload(0) + .invokespecial(CD_Object, INIT_NAME, MTD_void) + .return_())); + runtimeVerify(className, classBytes); + var classModel = ClassFile.of().parse(classBytes); + var ctorModel = classModel.methods().getFirst(); + var stackMaps = ctorModel.code().orElseThrow().findAttribute(Attributes.stackMapTable()).orElseThrow(); + assertEquals(2, stackMaps.entries().size(), "if -> else, then -> end"); + var elseFrame = stackMaps.entries().get(0); + assertEquals(246, elseFrame.frameType(), "if -> else"); + assertEquals(List.of(ConstantPoolBuilder.of().nameAndTypeEntry("fsf", CD_int)), elseFrame.unsetFields()); + var mergedFrame = stackMaps.entries().get(1); + assertEquals(246, mergedFrame.frameType(), "then -> merge"); + assertEquals(List.of(), mergedFrame.unsetFields()); + } + + @Test + void noEarlyFrameInOldTest() { + var className = "Test"; + var classDesc = ClassDesc.of(className); + var classBytes = ClassFile.of().build(classDesc, clb -> clb + .withVersion(JAVA_26_VERSION, 0) + .withFlags(ACC_PUBLIC | ACC_SUPER) + .withField("fs", CD_int, ACC_STRICT) // spurious meaningless flags in 70.0 + .withField("fsf", CD_int, ACC_STRICT | ACC_FINAL) + .withMethodBody(INIT_NAME, MTD_void, 0, cob -> cob + .aload(0) + .iconst_5() + .putfield(classDesc, "fs", CD_int) + .aload(0) + .iconst_0() + .ifThenElse(thb -> thb + .iconst_3() + .putfield(classDesc, "fsf", CD_int), elb -> elb + .iconst_2() + .putfield(classDesc, "fsf", CD_int)) + .aload(0) + .invokespecial(CD_Object, INIT_NAME, MTD_void) + .return_())); + runtimeVerify(className, classBytes); + var classModel = ClassFile.of().parse(classBytes); + var ctorModel = classModel.methods().getFirst(); + var stackMaps = ctorModel.code().orElseThrow().findAttribute(Attributes.stackMapTable()).orElseThrow(); + assertEquals(2, stackMaps.entries().size(), "if -> else, then -> end"); + var elseFrame = stackMaps.entries().get(0); + assertNotEquals(246, elseFrame.frameType(), "if -> else"); + assertEquals(List.of(), elseFrame.unsetFields()); + var mergedFrame = stackMaps.entries().get(1); + assertNotEquals(246, mergedFrame.frameType(), "then -> merge"); + assertEquals(List.of(), mergedFrame.unsetFields()); + } + + @Test + void skipUnnecessaryUnsetFramesTest() throws Throwable { + var className = "Test"; + var classDesc = ClassDesc.of(className); + var classBytes = ClassFile.of().build(classDesc, clb -> clb + .withVersion(latestMajorVersion(), PREVIEW_MINOR_VERSION) + .withFlags(ACC_PUBLIC | ACC_IDENTITY) + .withField("fPlain", CD_char, ACC_PRIVATE) + .withField("fs", CD_int, ACC_STRICT) + .withField("fsf", CD_int, ACC_STRICT | ACC_FINAL) + .withMethodBody(INIT_NAME, MTD_void, 0, cob -> cob + .aload(0) + .iconst_5() + .putfield(classDesc, "fs", CD_int) + .aload(0) + .iconst_0() + .ifThenElse(thb -> thb + .iconst_3() + .putfield(classDesc, "fPlain", CD_int), elb -> elb + .iconst_2() + .putfield(classDesc, "fPlain", CD_int)) + .aload(0) + .iconst_5() + .putfield(classDesc, "fsf", CD_int) + .aload(0) + .invokespecial(CD_Object, INIT_NAME, MTD_void) + .return_())); + // runtimeVerify(className, classBytes); // TODO VM fix branching + var classModel = ClassFile.of().parse(classBytes); + var ctorModel = classModel.methods().getFirst(); + var stackMaps = ctorModel.code().orElseThrow().findAttribute(Attributes.stackMapTable()).orElseThrow(); + assertEquals(2, stackMaps.entries().size(), "if -> else, then -> end"); + var elseFrame = stackMaps.entries().get(0); + assertEquals(246, elseFrame.frameType(), "if -> else"); + assertEquals(List.of(ConstantPoolBuilder.of().nameAndTypeEntry("fsf", CD_int)), elseFrame.unsetFields()); + var mergedFrame = stackMaps.entries().get(1); + assertNotEquals(246, mergedFrame.frameType(), "then -> end, no redundant larval"); + assertEquals(elseFrame.unsetFields(), mergedFrame.unsetFields(), "larval carries over in parsing"); + } + + // Also tests no larval_frame after ctor call + @Test + void clearUnsetAfterThisConstructorCallTest() throws Throwable { + var className = "Test"; + var classDesc = ClassDesc.of(className); + var fullArgsCtorDesc = MethodTypeDesc.of(CD_void, CD_int, CD_int); + var classBytes = ClassFile.of().build(classDesc, clb -> clb + .withVersion(latestMajorVersion(), PREVIEW_MINOR_VERSION) + .withFlags(ACC_PUBLIC | ACC_IDENTITY) + .withField("fPlain", CD_int, ACC_PRIVATE) + .withField("fs", CD_int, ACC_STRICT) + .withField("fsf", CD_int, ACC_STRICT | ACC_FINAL) + // record-style ctor + .withMethodBody(INIT_NAME, fullArgsCtorDesc, 0, cob -> cob + .aload(0) + .iload(1) + .putfield(classDesc, "fs", CD_int) + .aload(0) + .iload(2) + .putfield(classDesc, "fsf", CD_int) + .aload(0) + .invokespecial(CD_Object, INIT_NAME, MTD_void) + .return_()) + // delegates to the other ctor + .withMethodBody(INIT_NAME, MTD_void, 0, cob -> cob + .aload(0) + .iconst_5() + .iconst_0() + .invokespecial(classDesc, INIT_NAME, fullArgsCtorDesc) + .aload(0) + .iconst_1() + .ifThenElse(thb -> thb + .iconst_3() + .putfield(classDesc, "fPlain", CD_int), elb -> elb + .iconst_2() + .putfield(classDesc, "fPlain", CD_int)) + .return_())); + runtimeVerify(className, classBytes); + var classModel = ClassFile.of().parse(classBytes); + var delegatingCtorModel = classModel.methods().stream() + .filter(m -> m.methodType().equalsString(MTD_void.descriptorString())) + .findFirst().orElseThrow(); + var stackMaps = delegatingCtorModel.code().orElseThrow().findAttribute(Attributes.stackMapTable()).orElseThrow(); + assertEquals(2, stackMaps.entries().size(), "if -> else, then -> merge"); + var elseFrame = stackMaps.entries().get(0); + assertNotEquals(246, elseFrame.frameType(), "if -> else, no uninitializedThis, no larval frame needed to clear unset"); + assertEquals(List.of(), elseFrame.unsetFields(), "cleared by constructor call"); + var mergeFrame = stackMaps.entries().get(1); + assertNotEquals(246, mergeFrame.frameType(), "then -> merge"); + } + + @Test + void allowMultiAssignTest() throws Throwable { + var className = "Test"; + var classDesc = ClassDesc.of(className); + var classBytes = ClassFile.of().build(classDesc, clb -> clb + .withVersion(latestMajorVersion(), PREVIEW_MINOR_VERSION) + .withFlags(ACC_PUBLIC | ACC_IDENTITY) + .withField("fs", CD_int, ACC_STRICT) + .withField("fsf", CD_int, ACC_STRICT | ACC_FINAL) + .withMethodBody(INIT_NAME, MTD_void, 0, cob -> cob + .aload(0) + .iconst_1() + .ifThenElse(thb -> thb + .iconst_3() + .putfield(classDesc, "fs", CD_int), elb -> elb + // frame 0 + .iconst_2() + .putfield(classDesc, "fsf", CD_int)) + // frame 1 + .aload(0) + .iconst_5() + .putfield(classDesc, "fs", CD_int) + .aload(0) + .loadConstant(12) + .putfield(classDesc, "fsf", CD_int) + .aload(0) + .invokespecial(CD_Object, INIT_NAME, MTD_void) + .return_())); + runtimeVerify(className, classBytes); + var classModel = ClassFile.of().parse(classBytes); + var ctorModel = classModel.methods().getFirst(); + var stackMaps = ctorModel.code().orElseThrow().findAttribute(Attributes.stackMapTable()).orElseThrow(); + assertEquals(2, stackMaps.entries().size(), () -> stackMaps.entries().toString()); + var elseFrame = stackMaps.entries().get(0); + var mergeFrame = stackMaps.entries().get(1); + assertNotEquals(246, elseFrame.frameType(), "if -> else, no redundant larval frames"); + assertNotEquals(246, mergeFrame.frameType(), "then -> merge, no redundant larval frames"); + var cpb = ConstantPoolBuilder.of(); + assertEquals(Set.of(cpb.nameAndTypeEntry("fsf", CD_int), cpb.nameAndTypeEntry("fs", CD_int)), + Set.copyOf(elseFrame.unsetFields()), "retains initial unsets"); + assertEquals(elseFrame.unsetFields(), mergeFrame.unsetFields(), "no unset change"); + } + + @Test + void failOnUnsetNotClearTest() throws Throwable { + var className = "Test"; + var classDesc = ClassDesc.of(className); + assertThrows(IllegalArgumentException.class, () -> ClassFile.of().build(classDesc, clb -> clb + .withVersion(latestMajorVersion(), PREVIEW_MINOR_VERSION) + .withFlags(ACC_PUBLIC | ACC_IDENTITY) + .withField("fs0", CD_int, ACC_STRICT) + .withField("fs1", CD_int, ACC_STRICT | ACC_FINAL) + .withMethodBody(INIT_NAME, MTD_void, 0, cob -> cob + .aload(0) + .iconst_0() + .ifThenElse(thb -> thb + .iconst_3() + .putfield(classDesc, "fs0", CD_int), elb -> elb + .iconst_2() + .putfield(classDesc, "fs1", CD_int)) + .aload(0) + .invokespecial(CD_Object, INIT_NAME, MTD_void) // unset not clear here + .return_()))); + } + + // Ensures stack maps are updated when fields are transformed to be strict + @Test + void basicTransformToStrictTest() throws Throwable { + var className = "Test"; + var classDesc = ClassDesc.of(className); + // this class has no strict + var classBytes = ClassFile.of().build(classDesc, clb -> clb + .withVersion(latestMajorVersion(), PREVIEW_MINOR_VERSION) + .withFlags(ACC_PUBLIC | ACC_IDENTITY) + .withField("fs", CD_int, 0) + .withField("fsf", CD_int, ACC_FINAL) + .withMethodBody(INIT_NAME, MTD_void, 0, cob -> cob + .aload(0) + .iconst_5() + .putfield(classDesc, "fs", CD_int) + .aload(0) + .iconst_0() + .ifThenElse(thb -> thb + .iconst_3() + .putfield(classDesc, "fsf", CD_int), elb -> elb + .iconst_2() + .putfield(classDesc, "fsf", CD_int)) + .aload(0) + .invokespecial(CD_Object, INIT_NAME, MTD_void) + .return_())); + + classBytes = ClassFile.of().transformClass(ClassFile.of().parse(classBytes), ClassTransform.transformingFields((fb, fe) -> { + if (fe instanceof AccessFlags acc) { + fb.withFlags(acc.flagsMask() | ACC_STRICT); + } else { + fb.with(fe); + } + })); + + runtimeVerify(className, classBytes); + var classModel = ClassFile.of().parse(classBytes); + var ctorModel = classModel.methods().getFirst(); + var stackMaps = ctorModel.code().orElseThrow().findAttribute(Attributes.stackMapTable()).orElseThrow(); + assertEquals(2, stackMaps.entries().size(), "if -> else, then -> merge"); + var elseFrame = stackMaps.entries().get(0); + assertEquals(246, elseFrame.frameType(), "if -> else"); + assertEquals(List.of(ConstantPoolBuilder.of().nameAndTypeEntry("fsf", CD_int)), elseFrame.unsetFields()); + var mergedFrame = stackMaps.entries().get(1); + assertEquals(246, mergedFrame.frameType(), "then -> merge"); + assertEquals(List.of(), mergedFrame.unsetFields()); + } + + @Test + void explicitWriteFramesTest() throws Throwable { + var className = "Test"; + var classDesc = ClassDesc.of(className); + var classBytes = ClassFile.of(StackMapsOption.DROP_STACK_MAPS).build(classDesc, clb -> clb + .withVersion(latestMajorVersion(), PREVIEW_MINOR_VERSION) + .withFlags(ACC_PUBLIC | ACC_IDENTITY) + .withField("fs", CD_int, ACC_STRICT) + .withField("fsf", CD_int, ACC_STRICT | ACC_FINAL) + .withMethodBody(INIT_NAME, MTD_void, 0, cob -> { + var frames = new ArrayList(); + cob.aload(0) + .iconst_0() + .ifThenElse(thb -> thb + .iconst_3() + .putfield(classDesc, "fsf", CD_int), elb -> { + // jump to else - fs, fsf unset + frames.add(StackMapFrameInfo.of(elb.startLabel(), + List.of(StackMapFrameInfo.SimpleVerificationTypeInfo.UNINITIALIZED_THIS), + List.of(), + List.of(elb.constantPool().nameAndTypeEntry("fs", CD_int), + elb.constantPool().nameAndTypeEntry("fsf", CD_int)))); + elb.iconst_2() + .putfield(classDesc, "fsf", CD_int); + }); + // merge - fs unset + frames.add(StackMapFrameInfo.of(cob.newBoundLabel(), + List.of(StackMapFrameInfo.SimpleVerificationTypeInfo.UNINITIALIZED_THIS), + List.of(), + List.of(cob.constantPool().nameAndTypeEntry("fs", CD_int)))); + cob.aload(0) + .iconst_5() + .putfield(classDesc, "fs", CD_int) + .aload(0) + .invokespecial(CD_Object, INIT_NAME, MTD_void) + .iconst_1() + .ifThen(thb -> thb.iconst_3().pop()); + // post larval - no uninitializedThis, empty unsets + frames.add(StackMapFrameInfo.of(cob.newBoundLabel(), + List.of(StackMapFrameInfo.ObjectVerificationTypeInfo.of(classDesc)), + List.of())); + cob.return_() + .with(StackMapTableAttribute.of(frames)); + })); + // runtimeVerify(className, classBytes); // TODO VM fix + var classModel = ClassFile.of().parse(classBytes); + var ctorModel = classModel.methods().getFirst(); + var stackMaps = ctorModel.code().orElseThrow().findAttribute(Attributes.stackMapTable()).orElseThrow(); + assertEquals(3, stackMaps.entries().size(), "if -> else, then -> end, post larval"); + var elseFrame = stackMaps.entries().get(0); + assertEquals(List.of(StackMapFrameInfo.SimpleVerificationTypeInfo.UNINITIALIZED_THIS), elseFrame.locals()); + // frame type for else may or may not be 246... no unset field changes but may have reorders + assertEquals(2, elseFrame.unsetFields().size(), "if -> else"); + var cpb = ConstantPoolBuilder.of(); + assertEquals(Set.of(cpb.nameAndTypeEntry("fs", CD_int), cpb.nameAndTypeEntry("fsf", CD_int)), + Set.copyOf(elseFrame.unsetFields())); + var mergedFrame = stackMaps.entries().get(1); + assertEquals(List.of(StackMapFrameInfo.SimpleVerificationTypeInfo.UNINITIALIZED_THIS), mergedFrame.locals()); + assertEquals(246, mergedFrame.frameType(), "then -> merge"); + assertEquals(List.of(cpb.nameAndTypeEntry("fs", CD_int)), mergedFrame.unsetFields()); + var postLarvalFrame = stackMaps.entries().get(2); + assertNotEquals(246, postLarvalFrame.frameType(), "postLarval"); // no larval frame here + assertEquals(List.of(StackMapFrameInfo.ObjectVerificationTypeInfo.of(classDesc)), postLarvalFrame.locals()); + assertEquals(List.of(), postLarvalFrame.unsetFields()); + } + + private static void runtimeVerify(String className, byte[] classBytes) { + var clazz = assertDoesNotThrow(() -> ByteCodeLoader.load(className, classBytes)); + var lookup = assertDoesNotThrow(() -> MethodHandles.privateLookupIn(clazz, MethodHandles.lookup())); + assertDoesNotThrow(() -> lookup.ensureInitialized(clazz)); // forces verification + var errors = ClassFile.of().verify(classBytes); + assertEquals(List.of(), errors, "Errors detected"); + } +} diff --git a/test/jdk/jdk/classfile/TransformTests.java b/test/jdk/jdk/classfile/TransformTests.java index b78da8b4311..c2a961933d7 100644 --- a/test/jdk/jdk/classfile/TransformTests.java +++ b/test/jdk/jdk/classfile/TransformTests.java @@ -25,6 +25,7 @@ * @test * @bug 8335935 8336588 * @summary Testing ClassFile transformations. + * @enablePreview * @run junit TransformTests */ diff --git a/test/jdk/jdk/classfile/VerifierSelfTest.java b/test/jdk/jdk/classfile/VerifierSelfTest.java index 7d1dae6f519..aefbf0e7356 100644 --- a/test/jdk/jdk/classfile/VerifierSelfTest.java +++ b/test/jdk/jdk/classfile/VerifierSelfTest.java @@ -26,6 +26,7 @@ * @summary Testing ClassFile Verifier. * @bug 8333812 8361526 * @run junit VerifierSelfTest + * @run junit/othervm --enable-preview VerifierSelfTest */ import java.io.IOException; import java.lang.classfile.constantpool.PoolEntry; @@ -37,12 +38,14 @@ import java.lang.constant.MethodTypeDesc; import java.lang.invoke.MethodHandleInfo; +import java.lang.invoke.MethodHandles; import java.net.URI; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -59,6 +62,9 @@ import jdk.internal.classfile.impl.DirectClassBuilder; import jdk.internal.classfile.impl.UnboundAttribute; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import static org.junit.jupiter.api.Assertions.*; @@ -211,7 +217,6 @@ void testParserVerification() { cob.iconst_0() .ifThen(CodeBuilder::nop) .return_() - .with(new CloneAttribute(StackMapTableAttribute.of(List.of()))) .with(new CloneAttribute(CharacterRangeTableAttribute.of(List.of()))) .with(new CloneAttribute(LineNumberTableAttribute.of(List.of()))) .with(new CloneAttribute(LocalVariableTableAttribute.of(List.of()))) @@ -331,12 +336,10 @@ Duplicate method name <> with signature ()V in class ParserVerificationTestClass Wrong Signature attribute length in method ParserVerificationTestClass::m() Wrong Synthetic attribute length in method ParserVerificationTestClass::m() Code attribute in native or abstract method ParserVerificationTestClass::m() - Wrong StackMapTable attribute length in Code attribute for method ParserVerificationTestClass::m() Wrong CharacterRangeTable attribute length in Code attribute for method ParserVerificationTestClass::m() Wrong LineNumberTable attribute length in Code attribute for method ParserVerificationTestClass::m() Wrong LocalVariableTable attribute length in Code attribute for method ParserVerificationTestClass::m() Wrong LocalVariableTypeTable attribute length in Code attribute for method ParserVerificationTestClass::m() - Multiple StackMapTable attributes in Code attribute for method ParserVerificationTestClass::m() Multiple Signature attributes in Record component c of class ParserVerificationTestClass Wrong Signature attribute length in Record component c of class ParserVerificationTestClass Multiple RuntimeVisibleAnnotations attributes in Record component c of class ParserVerificationTestClass @@ -440,4 +443,69 @@ void testInvokeSpecialInterfacePatch() { assertTrue(errors.getFirst().getMessage().contains("interface method to invoke is not in a direct superinterface"), errors.getFirst().getMessage()); } } + + enum ComparisonInstruction { + IF_ACMPEQ(Opcode.IF_ACMPEQ, 2), + IF_ACMPNE(Opcode.IF_ACMPNE, 2), + IFNONNULL(Opcode.IFNONNULL, 1), + IFNULL(Opcode.IFNULL, 1); + final Opcode opcode; + final int argCount; + ComparisonInstruction(Opcode opcode, int argCount) { + this.opcode = opcode; + this.argCount = argCount; + } + } + + enum UninitializeKind { + UNINITIALIZED, UNINITIALIZED_THIS + } + + @ParameterizedTest + @MethodSource("uninitializedInBytecodeClasses") + public void testUninitializedInComparisons(ComparisonInstruction inst, UninitializeKind kind) throws Throwable { + var bytes = ClassFile.of(ClassFile.StackMapsOption.DROP_STACK_MAPS).build(ClassDesc.of("Test"), clb -> clb + .withMethodBody(INIT_NAME, MTD_void, 0, cob -> { + StackMapFrameInfo.VerificationTypeInfo uninitializeInfo; + if (kind == UninitializeKind.UNINITIALIZED) { + uninitializeInfo = StackMapFrameInfo.UninitializedVerificationTypeInfo.of(cob.newBoundLabel()); + cob.new_(CD_Object); + } else { + uninitializeInfo = StackMapFrameInfo.SimpleVerificationTypeInfo.UNINITIALIZED_THIS; + cob.aload(0); + } + + // Stack: uninitializeInfo + for (int i = 0; i < inst.argCount; i++) { + cob.dup(); + } + var dest = cob.newLabel(); + cob.branch(inst.opcode, dest) + .nop() + .labelBinding(dest) + .with(StackMapTableAttribute.of(List.of(StackMapFrameInfo.of(dest, + List.of(StackMapFrameInfo.SimpleVerificationTypeInfo.UNINITIALIZED_THIS), + List.of(uninitializeInfo))))) + .invokespecial(CD_Object, INIT_NAME, MTD_void); + if (kind == UninitializeKind.UNINITIALIZED) { + // still need to call super constructor + cob.aload(0) + .invokespecial(CD_Object, INIT_NAME, MTD_void); + } + cob.return_(); + })); + var errors = ClassFile.of().verify(bytes); + assertNotEquals(List.of(), errors, () -> errors + " : " + ClassFile.of().parse(bytes).toDebugString()); + var lookup = MethodHandles.lookup(); + assertThrows(VerifyError.class, () -> lookup.defineHiddenClass(bytes, true)); // force JVM verification + } + + public static Stream uninitializedInBytecodeClasses() { + return Arrays.stream(ComparisonInstruction.values()) + .mapMulti((inst, sink) -> { + for (var kind : UninitializeKind.values()) { + sink.accept(Arguments.of(inst, kind)); + } + }); + } } diff --git a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java index b66bd8f1c00..b8e90a1b464 100644 --- a/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java +++ b/test/jdk/jdk/classfile/helpers/RebuildingTransformation.java @@ -105,6 +105,7 @@ static byte[] transform(ClassModel clm) { ici.outerClass().map(ClassEntry::asSymbol), ici.innerName().map(Utf8Entry::stringValue), ici.flagsMask())).toArray(InnerClassInfo[]::new))); + case LoadableDescriptorsAttribute a -> clb.with(LoadableDescriptorsAttribute.of(a.loadableDescriptors())); case ModuleAttribute a -> clb.with(ModuleAttribute.of(a.moduleName().asSymbol(), mob -> { mob.moduleFlags(a.moduleFlagsMask()); a.moduleVersion().ifPresent(v -> mob.moduleVersion(v.stringValue())); diff --git a/test/jdk/jdk/internal/jimage/ImageReaderTest.java b/test/jdk/jdk/internal/jimage/ImageReaderTest.java index 8db9a3768d4..de52ed1503d 100644 --- a/test/jdk/jdk/internal/jimage/ImageReaderTest.java +++ b/test/jdk/jdk/internal/jimage/ImageReaderTest.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; import tests.Helper; import tests.JImageGenerator; @@ -43,9 +44,11 @@ import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.assumeTrue; @@ -122,6 +125,65 @@ public void testModuleResources() throws IOException { } } + @ParameterizedTest + @CsvSource(delimiter = ':', value = { + "modfoo:com/foo/Alpha.class", + "modbar:com/bar/One.class", + }) + public void testResource_present(String modName, String resPath) throws IOException { + try (ImageReader reader = ImageReader.open(image)) { + assertNotNull(reader.findResourceNode(modName, resPath)); + assertTrue(reader.containsResource(modName, resPath)); + + String canonicalNodeName = "/modules/" + modName + "/" + resPath; + Node node = reader.findNode(canonicalNodeName); + assertTrue(node != null && node.isResource()); + } + } + + @ParameterizedTest + @CsvSource(delimiter = ':', value = { + // Absolute resource names are not allowed. + "modfoo:/com/bar/One.class", + // Resource in wrong module. + "modfoo:com/bar/One.class", + "modbar:com/foo/Alpha.class", + // Directories are not returned. + "modfoo:com/foo", + "modbar:com/bar", + // JImage entries exist for these, but they are not resources. + "modules:modfoo/com/foo/Alpha.class", + "packages:com.foo/modfoo", + // Empty module names/paths do not find resources. + "'':modfoo/com/foo/Alpha.class", + "modfoo:''"}) + public void testResource_absent(String modName, String resPath) throws IOException { + try (ImageReader reader = ImageReader.open(image)) { + assertNull(reader.findResourceNode(modName, resPath)); + assertFalse(reader.containsResource(modName, resPath)); + + // Non-existent resources names should either not be found, + // or (in the case of directory nodes) not be resources. + String canonicalNodeName = "/modules/" + modName + "/" + resPath; + Node node = reader.findNode(canonicalNodeName); + assertTrue(node == null || !node.isResource()); + } + } + + @ParameterizedTest + @CsvSource(delimiter = ':', value = { + // Don't permit module names to contain paths. + "modfoo/com/bar:One.class", + "modfoo/com:bar/One.class", + "modules/modfoo/com:foo/Alpha.class", + }) + public void testResource_invalid(String modName, String resPath) throws IOException { + try (ImageReader reader = ImageReader.open(image)) { + assertThrows(IllegalArgumentException.class, () -> reader.containsResource(modName, resPath)); + assertThrows(IllegalArgumentException.class, () -> reader.findResourceNode(modName, resPath)); + } + } + @Test public void testPackageDirectories() throws IOException { try (ImageReader reader = ImageReader.open(image)) { diff --git a/test/jdk/jdk/internal/jrtfs/whitebox/ExplodedImageTestDriver.java b/test/jdk/jdk/internal/jrtfs/whitebox/ExplodedImageTestDriver.java new file mode 100644 index 00000000000..884024454d4 --- /dev/null +++ b/test/jdk/jdk/internal/jrtfs/whitebox/ExplodedImageTestDriver.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Whitebox tests for ExplodedImage to ensure compatibility with ImageReader. + * @modules java.base/jdk.internal.jrtfs java.base/jdk.internal.jimage + * @run junit/othervm java.base/jdk.internal.jrtfs.ExplodedImageTest + */ +public class ExplodedImageTestDriver {} \ No newline at end of file diff --git a/test/jdk/jdk/internal/jrtfs/whitebox/TEST.properties b/test/jdk/jdk/internal/jrtfs/whitebox/TEST.properties new file mode 100644 index 00000000000..6e60bee4991 --- /dev/null +++ b/test/jdk/jdk/internal/jrtfs/whitebox/TEST.properties @@ -0,0 +1,4 @@ +modules = \ + java.base/jdk.internal.jimage \ + java.base/jdk.internal.jrtfs +bootclasspath.dirs=. diff --git a/test/jdk/jdk/internal/jrtfs/whitebox/java.base/jdk/internal/jrtfs/ExplodedImageTest.java b/test/jdk/jdk/internal/jrtfs/whitebox/java.base/jdk/internal/jrtfs/ExplodedImageTest.java new file mode 100644 index 00000000000..c63e163467b --- /dev/null +++ b/test/jdk/jdk/internal/jrtfs/whitebox/java.base/jdk/internal/jrtfs/ExplodedImageTest.java @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.jrtfs; + +import jdk.internal.jimage.ImageReader; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.DirectoryStream; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests an {@link ExplodedImage} view of a class-file hierarchy. + * + *

    For simplicity and performance, only a subset of the JRT files are copied + * to disk for testing. + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class ExplodedImageTest { + + private Path modulesRoot; + private SystemImage explodedImage; + private String pathSeparator; + + @BeforeAll + public void createTestDirectory(@TempDir Path modulesRoot) throws IOException { + this.modulesRoot = modulesRoot; + this.pathSeparator = modulesRoot.getFileSystem().getSeparator(); + // Copy only a useful subset of files for testing. Use at least two + // modules with "overlapping" packages to test /package links better. + unpackModulesDirectoriesFromJrtFileSystem(modulesRoot, + "java.base/java/util", + "java.base/java/util/zip", + "java.logging/java/util/logging"); + this.explodedImage = new ExplodedImage(modulesRoot); + } + + /** Unpacks a list of "/modules/..." directories non-recursively into the specified root directory. */ + private static void unpackModulesDirectoriesFromJrtFileSystem(Path modulesRoot, String... dirNames) + throws IOException { + FileSystem jrtfs = FileSystems.getFileSystem(URI.create("jrt:/")); + List srcDirs = Arrays.stream(dirNames).map(s -> "/modules/" + s).map(jrtfs::getPath).toList(); + for (Path srcDir : srcDirs) { + // Skip-1 to remove "modules" segment (not part of the file system path). + Path dstDir = StreamSupport.stream(srcDir.spliterator(), false) + .skip(1) + .reduce(modulesRoot, (path, segment) -> path.resolve(segment.toString())); + Files.createDirectories(dstDir); + try (DirectoryStream files = Files.newDirectoryStream(srcDir)) { + for (Path srcFile : files) { + Files.copy(srcFile, dstDir.resolve(srcFile.getFileName().toString())); + } + } + } + } + + @Test + public void topLevelNodes() throws IOException { + ImageReader.Node root = explodedImage.findNode("/"); + ImageReader.Node modules = explodedImage.findNode("/modules"); + ImageReader.Node packages = explodedImage.findNode("/packages"); + assertEquals( + Set.of(modules.getName(), packages.getName()), + root.getChildNames().collect(Collectors.toSet())); + } + + @ParameterizedTest + @ValueSource(strings = { + "/modules/java.base/java/util/List.class", + "/modules/java.base/java/util/zip/ZipEntry.class", + "/modules/java.logging/java/util/logging/Logger.class"}) + public void basicLookupResource(String expectedResourceName) throws IOException { + ImageReader.Node node = assertResourceNode(expectedResourceName); + + Path fsRelPath = getRelativePath(expectedResourceName); + assertArrayEquals( + Files.readAllBytes(modulesRoot.resolve(fsRelPath)), + explodedImage.getResource(node)); + } + + @ParameterizedTest + @ValueSource(strings = { + "/modules/java.base", + "/modules/java.logging", + "/modules/java.base/java", + "/modules/java.base/java/util", + "/modules/java.logging/java/util", + }) + public void basicLookupDirectory(String expectedDirectoryName) throws IOException { + ImageReader.Node node = assertDirectoryNode(expectedDirectoryName); + + Path fsRelPath = getRelativePath(expectedDirectoryName); + List fsChildBaseNames; + try (DirectoryStream paths = Files.newDirectoryStream(modulesRoot.resolve(fsRelPath))) { + fsChildBaseNames = StreamSupport.stream(paths.spliterator(), false) + .map(Path::getFileName) + .map(Path::toString) + .toList(); + } + List nodeChildBaseNames = node.getChildNames() + .map(s -> s.substring(node.getName().length() + 1)) + .toList(); + assertEquals(fsChildBaseNames, nodeChildBaseNames, "expected same child names"); + } + + @ParameterizedTest + @ValueSource(strings = { + "/packages/java/java.base", + "/packages/java/java.logging", + "/packages/java.util/java.base", + "/packages/java.util/java.logging", + "/packages/java.util.zip/java.base"}) + public void basicLookupPackageLinks(String expectedLinkName) throws IOException { + ImageReader.Node node = assertLinkNode(expectedLinkName); + ImageReader.Node resolved = node.resolveLink(); + assertSame(explodedImage.findNode(resolved.getName()), resolved); + String moduleName = expectedLinkName.substring(expectedLinkName.lastIndexOf('/') + 1); + assertEquals("/modules/" + moduleName, resolved.getName()); + } + + @ParameterizedTest + @ValueSource(strings = { + "/packages/java", + "/packages/java.util", + "/packages/java.util.zip"}) + public void packageDirectories(String expectedDirectoryName) throws IOException { + ImageReader.Node node = assertDirectoryNode(expectedDirectoryName); + assertTrue(node.getChildNames().findFirst().isPresent(), + "Package directories should not be empty: " + node); + } + + @ParameterizedTest + @ValueSource(strings = { + "", + ".", + "/.", + "modules", + "packages", + "/modules/", + "/modules/xxxx", + "/modules/java.base/java/lang/Xxxx.class", + "/packages/", + "/packages/xxxx", + "/packages/java.xxxx", + "/packages/java.util.", + // Mismatched module. + "/packages/java.util.logging/java.base", + "/packages/java.util.zip/java.logging", + // Links are not resolved as they are fetched (old/broken behaviour). + "/packages/java.util/java.base/java/util/Vector.class", + }) + public void invalidNames(String invalidName) throws IOException { + assertNull(explodedImage.findNode(invalidName), "No node expected for: " + invalidName); + } + + private ImageReader.Node assertResourceNode(String name) throws IOException { + ImageReader.Node node = explodedImage.findNode(name); + assertNotNull(node); + assertEquals(name, node.getName(), "expected node name: " + name); + assertTrue(node.isResource(), "expected a resource: " + node); + assertFalse(node.isDirectory(), "resources are not directories: " + node); + assertFalse(node.isLink(), "resources are not links: " + node); + return node; + } + + private ImageReader.Node assertDirectoryNode(String name) throws IOException { + ImageReader.Node node = explodedImage.findNode(name); + assertNotNull(node); + assertEquals(name, node.getName(), "expected node name: " + name); + assertTrue(node.isDirectory(), "expected a directory: " + node); + assertFalse(node.isResource(), "directories are not resources: " + node); + assertFalse(node.isLink(), "directories are not links: " + node); + return node; + } + + private ImageReader.Node assertLinkNode(String name) throws IOException { + ImageReader.Node node = explodedImage.findNode(name); + assertNotNull(node); + assertEquals(name, node.getName(), "expected node name: " + name); + assertTrue(node.isLink(), "expected a link: " + node); + assertFalse(node.isResource(), "links are not resources: " + node); + assertFalse(node.isDirectory(), "links are not directories: " + node); + return node; + } + + private Path getRelativePath(String name) { + return Path.of(name.substring("/modules/".length()).replace("/", pathSeparator)); + } +} diff --git a/test/jdk/jdk/internal/util/ReferencedKeyTest.java b/test/jdk/jdk/internal/util/ReferencedKeyTest.java index c68908a8d5a..fdf1bcf7315 100644 --- a/test/jdk/jdk/internal/util/ReferencedKeyTest.java +++ b/test/jdk/jdk/internal/util/ReferencedKeyTest.java @@ -23,7 +23,7 @@ /* * @test - * @bug 8285932 8310913 8336390 8338060 + * @bug 8285932 8310913 8336390 8338060 8338252 * @summary Test features provided by the ReferencedKeyMap/ReferencedKeySet classes. * @modules java.base/jdk.internal.util * @compile --patch-module java.base=${test.src} ReferencedKeyTest.java diff --git a/test/jdk/jdk/internal/vm/Continuation/Fuzz.java b/test/jdk/jdk/internal/vm/Continuation/Fuzz.java index 8d522cc83e1..2acc1ba44f1 100644 --- a/test/jdk/jdk/internal/vm/Continuation/Fuzz.java +++ b/test/jdk/jdk/internal/vm/Continuation/Fuzz.java @@ -30,6 +30,7 @@ * @requires vm.opt.TieredCompilation == null | vm.opt.TieredCompilation == true * @modules java.base java.base/jdk.internal.vm.annotation java.base/jdk.internal.vm * @library /test/lib + * @enablePreview * @build java.base/java.lang.StackWalkerHelper * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox @@ -47,6 +48,7 @@ * @requires vm.opt.TieredCompilation == null | vm.opt.TieredCompilation == true * @modules java.base java.base/jdk.internal.vm.annotation java.base/jdk.internal.vm * @library /test/lib + * @enablePreview * @build java.base/java.lang.StackWalkerHelper * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox @@ -83,6 +85,7 @@ public class Fuzz implements Runnable { static final boolean FILE = true; static final boolean RANDOM = true; static final boolean VERBOSE = false; + static final Random RAND = Utils.getRandomInstance(); static int COMPILATION_TIMEOUT = (int)(5_000 * Utils.TIMEOUT_FACTOR); // ms @@ -109,14 +112,14 @@ public static void main(String[] args) { static void runTests() { if (FILE) testFile("fuzz.dat"); - if (RANDOM) testRandom(System.currentTimeMillis(), 50); + if (RANDOM) testRandom(RAND.nextLong(), 50); } //////////////// enum Op { - CALL_I_INT, CALL_I_DBL, CALL_I_MANY, - CALL_C_INT, CALL_C_DBL, CALL_C_MANY, + CALL_I_INT, CALL_I_DBL, CALL_I_MANY, CALL_I_VAL, + CALL_C_INT, CALL_C_DBL, CALL_C_MANY, CALL_C_VAL, CALL_I_CTCH, CALL_C_CTCH, CALL_I_PIN, CALL_C_PIN, MH_I_INT, MH_C_INT, MH_I_MANY, MH_C_MANY, @@ -279,7 +282,7 @@ int test() { private static int COMPILE_LEVEL; static final int WARMUP_ITERS = 15_000; - static final Op[] WARMUP_TRACE = {Op.MH_C_INT, Op.MH_C_MANY, Op.REF_C_INT, Op.REF_C_MANY, Op.CALL_C_INT}; + static final Op[] WARMUP_TRACE = {Op.MH_C_INT, Op.MH_C_MANY, Op.REF_C_INT, Op.REF_C_MANY, Op.CALL_C_INT, Op.CALL_C_VAL}; static void warmup() { final long start = time(); @@ -599,6 +602,118 @@ static String sfToString(Object f) { return f instanceof StackFrame ? StackWalkerHelper.frameToString((StackFrame)f) : Objects.toString(f); } + //// Value Classes + + static abstract value class BaseValue { + public abstract int res(); + }; + + static value class SmallValue extends BaseValue { + int x1; + int x2; + + public SmallValue(int i) { + x1 = i; + x2 = i; + } + + public int res() { + return x1 + x2; + } + }; + + static value class LargeValue extends BaseValue { + int x1; + int x2; + int x3; + int x4; + int x5; + int x6; + int x7; + + public LargeValue(int i) { + x1 = i; + x2 = i; + x3 = i; + x4 = i; + x5 = i; + x6 = i; + x7 = i; + } + + public int res() { + return x1 + x2 + x3 + x4 + x5 + x6 + x7; + } + }; + + static value class OopsValue extends BaseValue { + Object x1; + Object x2; + Object x3; + Object x4; + Object x5; + int x6; + + public OopsValue(int i) { + x1 = new Object(); + x2 = new Object(); + x3 = new Object(); + x4 = new Object(); + x5 = new Object(); + x6 = i; + } + + public int res() { + return x6; + } + }; + + public static value class DoubleValue extends BaseValue { + double d1; + double d2; + double d3; + double d4; + double d5; + double d6; + double d7; + + public DoubleValue(double d) { + d1 = d; + d2 = d + 1; + d3 = d + 2; + d4 = d + 3; + d5 = d + 4; + d6 = d + 4; + d7 = d + 4; + } + + public int res() { + return (int)(d1 + d2 + d3 + d4 + d5 + d6 + d7); + } + }; + + static value class MixedValue extends BaseValue { + byte x1; + short x2; + int x3; + long x4; + double x5; + boolean x6; + + public MixedValue(int i) { + x1 = (byte)i; + x2 = (short)i; + x3 = i; + x4 = i; + x5 = i; + x6 = (i % 2) == 0; + } + + public int res() { + return (int)x1 + (int)x2 + (int)x3 + (int)x4 + (int)x5 + (x6 ? 1 : 0); + } + }; + //// Static Helpers static void rethrow(Throwable t) { @@ -634,6 +749,8 @@ static void time(long startNanos, String message) { static final Class[] run_sig = new Class[]{}; static final Class[] int_sig = new Class[]{int.class, int.class}; static final Class[] dbl_sig = new Class[]{int.class, double.class}; + static final Class[] val_sig = new Class[]{int.class, SmallValue.class, + LargeValue.class, OopsValue.class, DoubleValue.class, MixedValue.class}; static final Class[] mny_sig = new Class[]{int.class, int.class, double.class, long.class, float.class, Object.class, int.class, double.class, long.class, float.class, Object.class, @@ -659,6 +776,8 @@ static void time(long startNanos, String message) { method.put(Op.CALL_C_INT, Fuzz.class.getDeclaredMethod("com_int", int_sig)); method.put(Op.CALL_I_DBL, Fuzz.class.getDeclaredMethod("int_dbl", dbl_sig)); method.put(Op.CALL_C_DBL, Fuzz.class.getDeclaredMethod("com_dbl", dbl_sig)); + method.put(Op.CALL_I_VAL, Fuzz.class.getDeclaredMethod("int_val", val_sig)); + method.put(Op.CALL_C_VAL, Fuzz.class.getDeclaredMethod("com_val", val_sig)); method.put(Op.CALL_I_MANY, Fuzz.class.getDeclaredMethod("int_mny", mny_sig)); method.put(Op.CALL_C_MANY, Fuzz.class.getDeclaredMethod("com_mny", mny_sig)); method.put(Op.CALL_I_PIN, Fuzz.class.getDeclaredMethod("int_pin", int_sig)); @@ -703,6 +822,11 @@ public void run() { long l1 = (long)res, l2 = (long)res, l3 = (long)res, l4 = (long)res; float f1 = (float)res, f2 = (float)res, f3 = (float)res, f4 = (float)res; Object o1 = res, o2 = res, o3 = res, o4 = res; + SmallValue sv = new SmallValue(res); + LargeValue lv = new LargeValue(res); + OopsValue ov = new OopsValue(res); + DoubleValue dv = new DoubleValue((double)res); + MixedValue mv = new MixedValue(res); for (int c = 1, index0 = index; c > 0; c--, maybeResetIndex(index0)) { // index0 is the index to which we return when we loop switch (next(c)) { @@ -714,6 +838,8 @@ public void run() { case CALL_C_INT -> res += com_int(depth+1, (int)res); case CALL_I_DBL -> res += (int)int_dbl(depth+1, res); case CALL_C_DBL -> res += (int)com_dbl(depth+1, res); + case CALL_I_VAL -> res += int_val(depth+1, sv, lv, ov, dv, mv).res(); + case CALL_C_VAL -> res += com_val(depth+1, sv, lv, ov, dv, mv).res(); case CALL_I_PIN -> res += int_pin(depth+1, (int)res); case CALL_C_PIN -> res += com_pin(depth+1, (int)res); case CALL_I_MANY -> res += int_mny(depth+1, x1, d1, l1, f1, o1, x2, d2, l2, f2, o2, x3, d3, l3, f3, o3, x4, d4, l4, f4, o4); @@ -740,6 +866,11 @@ int int_int(final int depth, int x) { long l1 = (long)res, l2 = (long)res, l3 = (long)res, l4 = (long)res; float f1 = (float)res, f2 = (float)res, f3 = (float)res, f4 = (float)res; Object o1 = res, o2 = res, o3 = res, o4 = res; + SmallValue sv = new SmallValue(x); + LargeValue lv = new LargeValue(x); + OopsValue ov = new OopsValue(x); + DoubleValue dv = new DoubleValue((double)x); + MixedValue mv = new MixedValue(x); for (int c = 1, index0 = index; c > 0; c--, maybeResetIndex(index0)) { // index0 is the index to which we return when we loop switch (next(c)) { @@ -751,6 +882,8 @@ int int_int(final int depth, int x) { case CALL_C_INT -> res += com_int(depth+1, (int)res); case CALL_I_DBL -> res += (int)int_dbl(depth+1, res); case CALL_C_DBL -> res += (int)com_dbl(depth+1, res); + case CALL_I_VAL -> res += int_val(depth+1, sv, lv, ov, dv, mv).res(); + case CALL_C_VAL -> res += com_val(depth+1, sv, lv, ov, dv, mv).res(); case CALL_I_PIN -> res += int_pin(depth+1, (int)res); case CALL_C_PIN -> res += com_pin(depth+1, (int)res); case CALL_I_MANY -> res += int_mny(depth+1, x1, d1, l1, f1, o1, x2, d2, l2, f2, o2, x3, d3, l3, f3, o3, x4, d4, l4, f4, o4); @@ -777,6 +910,11 @@ int com_int(final int depth, int x) { long l1 = (long)res, l2 = (long)res, l3 = (long)res, l4 = (long)res; float f1 = (float)res, f2 = (float)res, f3 = (float)res, f4 = (float)res; Object o1 = res, o2 = res, o3 = res, o4 = res; + SmallValue sv = new SmallValue(x); + LargeValue lv = new LargeValue(x); + OopsValue ov = new OopsValue(x); + DoubleValue dv = new DoubleValue((double)x); + MixedValue mv = new MixedValue(x); for (int c = 1, index0 = index; c > 0; c--, maybeResetIndex(index0)) { // index0 is the index to which we return when we loop switch (next(c)) { @@ -788,6 +926,8 @@ int com_int(final int depth, int x) { case CALL_C_INT -> res += com_int(depth+1, (int)res); case CALL_I_DBL -> res += (int)int_dbl(depth+1, res); case CALL_C_DBL -> res += (int)com_dbl(depth+1, res); + case CALL_I_VAL -> res += int_val(depth+1, sv, lv, ov, dv, mv).res(); + case CALL_C_VAL -> res += com_val(depth+1, sv, lv, ov, dv, mv).res(); case CALL_I_PIN -> res += int_pin(depth+1, (int)res); case CALL_C_PIN -> res += com_pin(depth+1, (int)res); case CALL_I_MANY -> res += int_mny(depth+1, x1, d1, l1, f1, o1, x2, d2, l2, f2, o2, x3, d3, l3, f3, o3, x4, d4, l4, f4, o4); @@ -814,6 +954,11 @@ int com_int(final int depth, int x) { long l1 = (long)res, l2 = (long)res, l3 = (long)res, l4 = (long)res; float f1 = (float)res, f2 = (float)res, f3 = (float)res, f4 = (float)res; Object o1 = res, o2 = res, o3 = res, o4 = res; + SmallValue sv = new SmallValue(x1); + LargeValue lv = new LargeValue(x1); + OopsValue ov = new OopsValue(x1); + DoubleValue dv = new DoubleValue((double)x1); + MixedValue mv = new MixedValue(x1); for (int c = 1, index0 = index; c > 0; c--, maybeResetIndex(index0)) { // index0 is the index to which we return when we loop switch (next(c)) { @@ -825,6 +970,8 @@ int com_int(final int depth, int x) { case CALL_C_INT -> res += com_int(depth+1, (int)res); case CALL_I_DBL -> res += (int)int_dbl(depth+1, res); case CALL_C_DBL -> res += (int)com_dbl(depth+1, res); + case CALL_I_VAL -> res += int_val(depth+1, sv, lv, ov, dv, mv).res(); + case CALL_C_VAL -> res += com_val(depth+1, sv, lv, ov, dv, mv).res(); case CALL_I_PIN -> res += int_pin(depth+1, (int)res); case CALL_C_PIN -> res += com_pin(depth+1, (int)res); case CALL_I_MANY -> res += int_mny(depth+1, x1, d1, l1, f1, o1, x2, d2, l2, f2, o2, x3, d3, l3, f3, o3, x4, d4, l4, f4, o4); @@ -851,6 +998,11 @@ int com_int(final int depth, int x) { long l1 = (long)res, l2 = (long)res, l3 = (long)res, l4 = (long)res; float f1 = (float)res, f2 = (float)res, f3 = (float)res, f4 = (float)res; Object o1 = res, o2 = res, o3 = res, o4 = res; + SmallValue sv = new SmallValue(x1); + LargeValue lv = new LargeValue(x1); + OopsValue ov = new OopsValue(x1); + DoubleValue dv = new DoubleValue((double)x1); + MixedValue mv = new MixedValue(x1); for (int c = 1, index0 = index; c > 0; c--, maybeResetIndex(index0)) { // index0 is the index to which we return when we loop switch (next(c)) { @@ -862,6 +1014,8 @@ int com_int(final int depth, int x) { case CALL_C_INT -> res += com_int(depth+1, (int)res); case CALL_I_DBL -> res += (int)int_dbl(depth+1, res); case CALL_C_DBL -> res += (int)com_dbl(depth+1, res); + case CALL_I_VAL -> res += int_val(depth+1, sv, lv, ov, dv, mv).res(); + case CALL_C_VAL -> res += com_val(depth+1, sv, lv, ov, dv, mv).res(); case CALL_I_PIN -> res += int_pin(depth+1, (int)res); case CALL_C_PIN -> res += com_pin(depth+1, (int)res); case CALL_I_MANY -> res += int_mny(depth+1, x1, d1, l1, f1, o1, x2, d2, l2, f2, o2, x3, d3, l3, f3, o3, x4, d4, l4, f4, o4); @@ -879,6 +1033,110 @@ int com_int(final int depth, int x) { return log(res); } + @DontInline + BaseValue int_val(final int depth, SmallValue x1, LargeValue x2, OopsValue x3, DoubleValue x4, MixedValue x5) { + int res = x1.res(); + + int x11 = (int)res, x12 = (int)res, x13 = (int)res, x14 = (int)res; + double d1 = (double)res, d2 = (double)res, d3 = (double)res, d4 = (double)res; + long l1 = (long)res, l2 = (long)res, l3 = (long)res, l4 = (long)res; + float f1 = (float)res, f2 = (float)res, f3 = (float)res, f4 = (float)res; + Object o1 = res, o2 = res, o3 = res, o4 = res; + SmallValue sv = new SmallValue(res); + LargeValue lv = new LargeValue(res); + OopsValue ov = new OopsValue(res); + DoubleValue dv = new DoubleValue((double)res); + MixedValue mv = new MixedValue(res); + + for (int c = 1, index0 = index; c > 0; c--, maybeResetIndex(index0)) { // index0 is the index to which we return when we loop + switch (next(c)) { + case THROW -> throwException(); + case LOOP -> { c += 2; index0 = index; } + case YIELD -> { preYield(); boolean y = Continuation.yield(SCOPE); postYield(y); c++; } + case DONE -> { break; } + case CALL_I_INT -> res += int_int(depth+1, (int)res); + case CALL_C_INT -> res += com_int(depth+1, (int)res); + case CALL_I_DBL -> res += (int)int_dbl(depth+1, res); + case CALL_C_DBL -> res += (int)com_dbl(depth+1, res); + case CALL_I_VAL -> res += int_val(depth+1, sv, lv, ov, dv, mv).res(); + case CALL_C_VAL -> res += com_val(depth+1, sv, lv, ov, dv, mv).res(); + case CALL_I_PIN -> res += int_pin(depth+1, (int)res); + case CALL_C_PIN -> res += com_pin(depth+1, (int)res); + case CALL_I_MANY -> res += int_mny(depth+1, x11, d1, l1, f1, o1, x12, d2, l2, f2, o2, x13, d3, l3, f3, o3, x14, d4, l4, f4, o4); + case CALL_C_MANY -> res += com_mny(depth+1, x11, d1, l1, f1, o1, x12, d2, l2, f2, o2, x13, d3, l3, f3, o3, x14, d4, l4, f4, o4); + case CALL_I_CTCH -> {try { res += int_int(depth+1, (int)res); } catch (FuzzException e) {}} + case CALL_C_CTCH -> {try { res += com_int(depth+1, (int)res); } catch (FuzzException e) {}} + case MH_I_INT, MH_C_INT -> {try { res += (int)handle(current()).invokeExact(this, depth+1, (int)res); } catch (Throwable e) { rethrow(e); }} + case MH_I_MANY, MH_C_MANY -> {try { res += (int)handle(current()).invokeExact(this, depth+1, x11, d1, l1, f1, o1, x12, d2, l2, f2, o2, x13, d3, l3, f3, o3, x14, d4, l4, f4, o4); } catch (Throwable e) { rethrow(e); }} + case REF_I_INT, REF_C_INT -> {try { res += (int)method(current()).invoke(this, depth+1, (int)res); } catch (InvocationTargetException e) { rethrow(e.getCause()); } catch (IllegalAccessException e) { assert false; }} + case REF_I_MANY, REF_C_MANY -> {try { res += (int)method(current()).invoke(this, depth+1, x11, d1, l1, f1, o1, x12, d2, l2, f2, o2, x13, d3, l3, f3, o3, x14, d4, l4, f4, o4); } catch (InvocationTargetException e) { rethrow(e.getCause()); } catch (IllegalAccessException e) { assert false; }} + default -> throw new AssertionError("Unknown op: " + current()); + } + } + + int positiveRes = (res == Integer.MIN_VALUE) ? Integer.MAX_VALUE : Math.abs(res); + switch (positiveRes % 5) { + case 0 -> { return log(new SmallValue(res)); } + case 1 -> { return log(new LargeValue(res)); } + case 2 -> { return log(new OopsValue(res)); } + case 3 -> { return log(new DoubleValue((double)res)); } + case 4 -> { return log(new MixedValue(res)); } + default -> throw new AssertionError("Invalid case"); + } + } + + @DontInline + BaseValue com_val(final int depth, SmallValue x1, LargeValue x2, OopsValue x3, DoubleValue x4, MixedValue x5) { + int res = x1.res(); + + int x11 = (int)res, x12 = (int)res, x13 = (int)res, x14 = (int)res; + double d1 = (double)res, d2 = (double)res, d3 = (double)res, d4 = (double)res; + long l1 = (long)res, l2 = (long)res, l3 = (long)res, l4 = (long)res; + float f1 = (float)res, f2 = (float)res, f3 = (float)res, f4 = (float)res; + Object o1 = res, o2 = res, o3 = res, o4 = res; + SmallValue sv = new SmallValue(res); + LargeValue lv = new LargeValue(res); + OopsValue ov = new OopsValue(res); + DoubleValue dv = new DoubleValue((double)res); + MixedValue mv = new MixedValue(res); + + for (int c = 1, index0 = index; c > 0; c--, maybeResetIndex(index0)) { // index0 is the index to which we return when we loop + switch (next(c)) { + case THROW -> throwException(); + case LOOP -> { c += 2; index0 = index; } + case YIELD -> { preYield(); boolean y = Continuation.yield(SCOPE); postYield(y); c++; } + case DONE -> { break; } + case CALL_I_INT -> res += int_int(depth+1, (int)res); + case CALL_C_INT -> res += com_int(depth+1, (int)res); + case CALL_I_DBL -> res += (int)int_dbl(depth+1, res); + case CALL_C_DBL -> res += (int)com_dbl(depth+1, res); + case CALL_I_VAL -> res += int_val(depth+1, sv, lv, ov, dv, mv).res(); + case CALL_C_VAL -> res += com_val(depth+1, sv, lv, ov, dv, mv).res(); + case CALL_I_PIN -> res += int_pin(depth+1, (int)res); + case CALL_C_PIN -> res += com_pin(depth+1, (int)res); + case CALL_I_MANY -> res += int_mny(depth+1, x11, d1, l1, f1, o1, x12, d2, l2, f2, o2, x13, d3, l3, f3, o3, x14, d4, l4, f4, o4); + case CALL_C_MANY -> res += com_mny(depth+1, x11, d1, l1, f1, o1, x12, d2, l2, f2, o2, x13, d3, l3, f3, o3, x14, d4, l4, f4, o4); + case CALL_I_CTCH -> {try { res += int_int(depth+1, (int)res); } catch (FuzzException e) {}} + case CALL_C_CTCH -> {try { res += com_int(depth+1, (int)res); } catch (FuzzException e) {}} + case MH_I_INT, MH_C_INT -> {try { res += (int)handle(current()).invokeExact(this, depth+1, (int)res); } catch (Throwable e) { rethrow(e); }} + case MH_I_MANY, MH_C_MANY -> {try { res += (int)handle(current()).invokeExact(this, depth+1, x11, d1, l1, f1, o1, x12, d2, l2, f2, o2, x13, d3, l3, f3, o3, x14, d4, l4, f4, o4); } catch (Throwable e) { rethrow(e); }} + case REF_I_INT, REF_C_INT -> {try { res += (int)method(current()).invoke(this, depth+1, (int)res); } catch (InvocationTargetException e) { rethrow(e.getCause()); } catch (IllegalAccessException e) { assert false; }} + case REF_I_MANY, REF_C_MANY -> {try { res += (int)method(current()).invoke(this, depth+1, x11, d1, l1, f1, o1, x12, d2, l2, f2, o2, x13, d3, l3, f3, o3, x14, d4, l4, f4, o4); } catch (InvocationTargetException e) { rethrow(e.getCause()); } catch (IllegalAccessException e) { assert false; }} + default -> throw new AssertionError("Unknown op: " + current()); + } + } + + int positiveRes = (res == Integer.MIN_VALUE) ? Integer.MAX_VALUE : Math.abs(res); + switch (positiveRes % 5) { + case 0 -> { return log(new SmallValue(res)); } + case 1 -> { return log(new LargeValue(res)); } + case 2 -> { return log(new OopsValue(res)); } + case 3 -> { return log(new DoubleValue((double)res)); } + case 4 -> { return log(new MixedValue(res)); } + default -> throw new AssertionError("Invalid case"); + } + } + @DontInline int int_pin(final int depth, int x) { int res = x; @@ -888,6 +1146,11 @@ int int_pin(final int depth, int x) { long l1 = (long)res, l2 = (long)res, l3 = (long)res, l4 = (long)res; float f1 = (float)res, f2 = (float)res, f3 = (float)res, f4 = (float)res; Object o1 = res, o2 = res, o3 = res, o4 = res; + SmallValue sv = new SmallValue(x); + LargeValue lv = new LargeValue(x); + OopsValue ov = new OopsValue(x); + DoubleValue dv = new DoubleValue((double)x); + MixedValue mv = new MixedValue(x); synchronized (this) { @@ -901,6 +1164,8 @@ int int_pin(final int depth, int x) { case CALL_C_INT -> res += com_int(depth+1, (int)res); case CALL_I_DBL -> res += (int)int_dbl(depth+1, res); case CALL_C_DBL -> res += (int)com_dbl(depth+1, res); + case CALL_I_VAL -> res += int_val(depth+1, sv, lv, ov, dv, mv).res(); + case CALL_C_VAL -> res += com_val(depth+1, sv, lv, ov, dv, mv).res(); case CALL_I_PIN -> res += int_pin(depth+1, (int)res); case CALL_C_PIN -> res += com_pin(depth+1, (int)res); case CALL_I_MANY -> res += int_mny(depth+1, x1, d1, l1, f1, o1, x2, d2, l2, f2, o2, x3, d3, l3, f3, o3, x4, d4, l4, f4, o4); @@ -929,6 +1194,11 @@ int com_pin(final int depth, int x) { long l1 = (long)res, l2 = (long)res, l3 = (long)res, l4 = (long)res; float f1 = (float)res, f2 = (float)res, f3 = (float)res, f4 = (float)res; Object o1 = res, o2 = res, o3 = res, o4 = res; + SmallValue sv = new SmallValue(x); + LargeValue lv = new LargeValue(x); + OopsValue ov = new OopsValue(x); + DoubleValue dv = new DoubleValue((double)x); + MixedValue mv = new MixedValue(x); synchronized (this) { @@ -942,6 +1212,8 @@ int com_pin(final int depth, int x) { case CALL_C_INT -> res += com_int(depth+1, (int)res); case CALL_I_DBL -> res += (int)int_dbl(depth+1, res); case CALL_C_DBL -> res += (int)com_dbl(depth+1, res); + case CALL_I_VAL -> res += int_val(depth+1, sv, lv, ov, dv, mv).res(); + case CALL_C_VAL -> res += com_val(depth+1, sv, lv, ov, dv, mv).res(); case CALL_I_PIN -> res += int_pin(depth+1, (int)res); case CALL_C_PIN -> res += com_pin(depth+1, (int)res); case CALL_I_MANY -> res += int_mny(depth+1, x1, d1, l1, f1, o1, x2, d2, l2, f2, o2, x3, d3, l3, f3, o3, x4, d4, l4, f4, o4); @@ -969,6 +1241,11 @@ int int_mny(int depth, int x4, double d4, long l4, float f4, Object o4) { double res = x1 + d2 + f3 + l4 + (double)(o4 instanceof Double ? (Double)o4 : (Integer)o4); + SmallValue sv = new SmallValue(x1); + LargeValue lv = new LargeValue(x1); + OopsValue ov = new OopsValue(x1); + DoubleValue dv = new DoubleValue((double)x1); + MixedValue mv = new MixedValue(x1); for (int c = 1, index0 = index; c > 0; c--, maybeResetIndex(index0)) { // index0 is the index to which we return when we loop switch (next(c)) { @@ -980,6 +1257,8 @@ int int_mny(int depth, case CALL_C_INT -> res += com_int(depth+1, (int)res); case CALL_I_DBL -> res += (int)int_dbl(depth+1, res); case CALL_C_DBL -> res += (int)com_dbl(depth+1, res); + case CALL_I_VAL -> res += int_val(depth+1, sv, lv, ov, dv, mv).res(); + case CALL_C_VAL -> res += com_val(depth+1, sv, lv, ov, dv, mv).res(); case CALL_I_PIN -> res += int_pin(depth+1, (int)res); case CALL_C_PIN -> res += com_pin(depth+1, (int)res); case CALL_I_MANY -> res += int_mny(depth+1, x1, d1, l1, f1, o1, x2, d2, l2, f2, o2, x3, d3, l3, f3, o3, x4, d4, l4, f4, o4); @@ -1005,6 +1284,11 @@ int com_mny(int depth, int x4, double d4, long l4, float f4, Object o4) { double res = x1 + d2 + f3 + l4 + (double)(o4 instanceof Double ? (Double)o4 : (Integer)o4); + SmallValue sv = new SmallValue(x1); + LargeValue lv = new LargeValue(x1); + OopsValue ov = new OopsValue(x1); + DoubleValue dv = new DoubleValue((double)x1); + MixedValue mv = new MixedValue(x1); for (int c = 1, index0 = index; c > 0; c--, maybeResetIndex(index0)) { // index0 is the index to which we return when we loop switch (next(c)) { @@ -1016,6 +1300,8 @@ int com_mny(int depth, case CALL_C_INT -> res += com_int(depth+1, (int)res); case CALL_I_DBL -> res += (int)int_dbl(depth+1, res); case CALL_C_DBL -> res += (int)com_dbl(depth+1, res); + case CALL_I_VAL -> res += int_val(depth+1, sv, lv, ov, dv, mv).res(); + case CALL_C_VAL -> res += com_val(depth+1, sv, lv, ov, dv, mv).res(); case CALL_I_PIN -> res += int_pin(depth+1, (int)res); case CALL_C_PIN -> res += com_pin(depth+1, (int)res); case CALL_I_MANY -> res += int_mny(depth+1, x1, d1, l1, f1, o1, x2, d2, l2, f2, o2, x3, d3, l3, f3, o3, x4, d4, l4, f4, o4); diff --git a/test/jdk/jdk/jfr/event/runtime/TestSyncOnValueBasedClassEvent.java b/test/jdk/jdk/jfr/event/runtime/TestSyncOnValueBasedClassEvent.java index 7ad50c83399..43f4f19271d 100644 --- a/test/jdk/jdk/jfr/event/runtime/TestSyncOnValueBasedClassEvent.java +++ b/test/jdk/jdk/jfr/event/runtime/TestSyncOnValueBasedClassEvent.java @@ -37,6 +37,7 @@ * @bug 8242263 * @requires vm.hasJFR * @requires vm.flagless + * @enablePreview false * @library /test/lib * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:DiagnoseSyncOnValueBasedClasses=2 jdk.jfr.event.runtime.TestSyncOnValueBasedClassEvent */ @@ -44,7 +45,7 @@ public class TestSyncOnValueBasedClassEvent { static final String EVENT_NAME = EventNames.SyncOnValueBasedClass; static String[] classesWanted = {"java/lang/Character", "java/lang/Boolean", "java/lang/Byte", "java/lang/Short", "java/lang/Integer", "java/lang/Long", "java/lang/Float", "java/lang/Double", - "java/time/Duration", "java/util/OptionalInt", "java/lang/Runtime$Version"}; + "java/lang/Runtime$Version"}; static List testObjects = new ArrayList(); static Integer counter = 0; @@ -57,8 +58,6 @@ private static void initTestObjects() { testObjects.add(Long.valueOf(0x4000000000000000L)); testObjects.add(Float.valueOf(1.20f)); testObjects.add(Double.valueOf(1.2345)); - testObjects.add(Duration.ofMillis(5)); - testObjects.add(OptionalInt.of(10)); testObjects.add(Runtime.version()); } diff --git a/test/jdk/jdk/modules/incubator/ServiceBinding.java b/test/jdk/jdk/modules/incubator/ServiceBinding.java index 4740e00c6d9..b68164cc8cf 100644 --- a/test/jdk/jdk/modules/incubator/ServiceBinding.java +++ b/test/jdk/jdk/modules/incubator/ServiceBinding.java @@ -26,7 +26,7 @@ * @bug 8233922 * @modules java.base/jdk.internal.module * @library /test/lib - * @build ServiceBinding TestBootLayer jdk.test.lib.util.ModuleInfoWriter + * @build ServiceBinding TestBootLayer * @run testng ServiceBinding * @summary Test service binding with incubator modules */ diff --git a/test/jdk/sun/tools/jcmd/TestProcessHelper.java b/test/jdk/sun/tools/jcmd/TestProcessHelper.java index 360c6bf3be7..833309d012a 100644 --- a/test/jdk/sun/tools/jcmd/TestProcessHelper.java +++ b/test/jdk/sun/tools/jcmd/TestProcessHelper.java @@ -59,7 +59,6 @@ * @library /test/lib * @build test.TestProcess * jdk.test.lib.util.JarUtils - * jdk.test.lib.util.ModuleInfoWriter * @run main/othervm TestProcessHelper */ public class TestProcessHelper { diff --git a/test/jdk/tools/jimage/ImageReaderDuplicateChildNodesTest.java b/test/jdk/tools/jimage/ImageReaderDuplicateChildNodesTest.java index 8656a4a3d00..3eed3bf972c 100644 --- a/test/jdk/tools/jimage/ImageReaderDuplicateChildNodesTest.java +++ b/test/jdk/tools/jimage/ImageReaderDuplicateChildNodesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/jdk/tools/jlink/JLinkNegativeTest.java b/test/jdk/tools/jlink/JLinkNegativeTest.java index f4d3bd2f1a4..72836e4ec60 100644 --- a/test/jdk/tools/jlink/JLinkNegativeTest.java +++ b/test/jdk/tools/jlink/JLinkNegativeTest.java @@ -35,7 +35,7 @@ * jdk.jlink/jdk.tools.jimage * jdk.compiler * @library /test/lib ../lib - * @build tests.* jdk.test.lib.util.ModuleInfoWriter + * @build tests.* * @run testng JLinkNegativeTest */ diff --git a/test/jdk/tools/launcher/modules/patch/basic/PatchTestWarningError.java b/test/jdk/tools/launcher/modules/patch/basic/PatchTestWarningError.java index 97824ba3d40..d20ab862840 100644 --- a/test/jdk/tools/launcher/modules/patch/basic/PatchTestWarningError.java +++ b/test/jdk/tools/launcher/modules/patch/basic/PatchTestWarningError.java @@ -135,6 +135,25 @@ public void testDuplicateModule() throws Exception { assertTrue(exitValue != 0); } + /** + * Test with --patch-module options patching the same module (not java.base). + * + */ + public void testDuplicateModuleLogging() throws Exception { + int exitValue = + executeTestJava("--patch-module", "java.logging=" + PATCHES1_DIR.resolve("java.base"), + "--patch-module", "java.logging=" + PATCHES2_DIR.resolve("java.base"), + "--module-path", MODS_DIR.toString(), + "-m", "test/jdk.test.Main") + .outputTo(System.out) + .errorTo(System.out) + // error output by VM + .shouldContain("Cannot specify a module more than once to --patch-module: java.logging") + .getExitValue(); + + assertTrue(exitValue != 0); + } + @DataProvider(name = "emptyItem") public Object[][] emptyItems() { String patch1 = PATCHES1_DIR.resolve("java.base").toString(); diff --git a/test/jdk/tools/sincechecker/modules/java.base/JavaBaseCheckSince.java b/test/jdk/tools/sincechecker/modules/java.base/JavaBaseCheckSince.java index 64d5bf2465f..3db0081e0d8 100644 --- a/test/jdk/tools/sincechecker/modules/java.base/JavaBaseCheckSince.java +++ b/test/jdk/tools/sincechecker/modules/java.base/JavaBaseCheckSince.java @@ -26,5 +26,5 @@ * @bug 8331051 * @summary Test for `@since` in java.base module * @library /test/lib /test/jdk/tools/sincechecker - * @run main SinceChecker java.base --exclude java.lang.classfile + * @run main SinceChecker java.base --ignoreSince Valhalla --exclude java.lang.classfile */ diff --git a/test/jdk/tools/sincechecker/modules/java.compiler/JavaCompilerCheckSince.java b/test/jdk/tools/sincechecker/modules/java.compiler/JavaCompilerCheckSince.java index c843c962d27..08b54e0ea90 100644 --- a/test/jdk/tools/sincechecker/modules/java.compiler/JavaCompilerCheckSince.java +++ b/test/jdk/tools/sincechecker/modules/java.compiler/JavaCompilerCheckSince.java @@ -26,5 +26,5 @@ * @bug 8341399 * @summary Test for `@since` in java.compiler module * @library /test/lib /test/jdk/tools/sincechecker - * @run main SinceChecker java.compiler + * @run main SinceChecker java.compiler --ignoreSince Valhalla */ diff --git a/test/jdk/valhalla/valuetypes/ArrayElementVarHandleTest.java b/test/jdk/valhalla/valuetypes/ArrayElementVarHandleTest.java new file mode 100644 index 00000000000..fc66233a67f --- /dev/null +++ b/test/jdk/valhalla/valuetypes/ArrayElementVarHandleTest.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +/* + * @test + * @summary test VarHandle on value class array + * @enablePreview + * @run junit/othervm -XX:+UseArrayFlattening ArrayElementVarHandleTest + * @run junit/othervm -XX:-UseArrayFlattening ArrayElementVarHandleTest + */ + +import java.lang.invoke.*; +import java.util.stream.Stream; + +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.*; + +public class ArrayElementVarHandleTest { + static value class Point { + public int x; + public int y; + Point(int x, int y) { + this.x = x; + this.y = y; + } + } + + static value class Line { + @NullRestricted @Strict + Point p1; + @NullRestricted @Strict + Point p2; + + Line(Point p1, Point p2) { + this.p1 = p1; + this.p2 = p2; + } + Line(int x1, int y1, int x2, int y2) { + this(new Point(x1, y1), new Point(x2, y2)); + } + } + + private static final Point[] POINTS = new Point[]{ + new Point(1, 2), + new Point(10, 20), + new Point(100, 200), + null + }; + + private static final Line[] LINES = new Line[]{ + new Line(1, 2, 3, 4), + new Line(10, 20, 30, 40), + null + }; + + static Stream testCases() throws Throwable { + int plen = POINTS.length; + int llen = LINES.length; + return Stream.of( + Arguments.of(newArray(Object[].class, plen), POINTS), + Arguments.of(newArray(Object[].class, plen), new Object[] { "abc", new Point(1, 2) }), + Arguments.of(newArray(Point[].class, plen), POINTS), + Arguments.of(new Point[plen], POINTS), + + Arguments.of(newArray(Object[].class, llen), LINES), + Arguments.of(newArray(Line[].class, llen), LINES), + Arguments.of(new Line[llen], LINES) + ); + } + + /* + * Constructs a new array of the specified type and size using + * MethodHandle. + */ + private static Object[] newArray(Class arrayType, int size) throws Throwable { + MethodHandle ctor = MethodHandles.arrayConstructor(arrayType); + return (Object[]) ctor.invoke(size); + } + + /* + * Test VarHandle to set elements of the given array with + * various access mode. + */ + @ParameterizedTest + @MethodSource("testCases") + public void testSetArrayElements(Object[] array, Object[] elements) { + Class arrayType = array.getClass(); + assertTrue(array.length >= elements.length); + + VarHandle vh = MethodHandles.arrayElementVarHandle(arrayType); + set(vh, array.clone(), elements); + setVolatile(vh, array.clone(), elements); + setOpaque(vh, array.clone(), elements); + setRelease(vh, array.clone(), elements); + getAndSet(vh, array.clone(), elements); + compareAndSet(vh, array.clone(), elements); + compareAndExchange(vh, array.clone(), elements); + } + + // VarHandle::set + void set(VarHandle vh, Object[] array, Object[] elements) { + for (int i = 0; i < elements.length; i++) { + vh.set(array, i, elements[i]); + } + for (int i = 0; i < elements.length; i++) { + Object v = (Object) vh.get(array, i); + assertEquals(elements[i], v); + } + } + + // VarHandle::setVolatile + void setVolatile(VarHandle vh, Object[] array, Object[] elements) { + for (int i = 0; i < elements.length; i++) { + vh.setVolatile(array, i, elements[i]); + } + for (int i = 0; i < elements.length; i++) { + Object v = (Object) vh.getVolatile(array, i); + assertEquals(elements[i], v); + } + } + + // VarHandle::setOpaque + void setOpaque(VarHandle vh, Object[] array, Object[] elements) { + for (int i = 0; i < elements.length; i++) { + vh.setOpaque(array, i, elements[i]); + } + for (int i = 0; i < elements.length; i++) { + Object v = (Object) vh.getOpaque(array, i); + assertEquals(elements[i], v); + } + } + + // VarHandle::setRelease + void setRelease(VarHandle vh, Object[] array, Object[] elements) { + for (int i = 0; i < elements.length; i++) { + vh.setRelease(array, i, elements[i]); + } + for (int i = 0; i < elements.length; i++) { + Object v = (Object) vh.getAcquire(array, i); + assertEquals(elements[i], v); + } + } + + void getAndSet(VarHandle vh, Object[] array, Object[] elements) { + for (int i = 0; i < elements.length; i++) { + Object o = vh.getAndSet(array, i, elements[i]); + } + for (int i = 0; i < elements.length; i++) { + Object v = (Object) vh.get(array, i); + assertEquals(elements[i], v); + } + } + + // sanity CAS test + // see test/jdk/java/lang/invoke/VarHandles tests + void compareAndSet(VarHandle vh, Object[] array, Object[] elements) { + // initialize to some values + for (int i = 0; i < elements.length; i++) { + vh.set(array, i, elements[i]); + } + // shift to the right element + for (int i = 0; i < elements.length; i++) { + Object v = elements[i + 1 < elements.length ? i + 1 : 0]; + boolean cas = vh.compareAndSet(array, i, elements[i], v); + if (!cas) + System.out.format("cas = %s array[%d] = %s vs old = %s new = %s%n", cas, i, array[i], elements[i], v); + assertTrue(cas); + } + } + + void compareAndExchange(VarHandle vh, Object[] array, Object[] elements) { + // initialize to some values + for (int i = 0; i < elements.length; i++) { + vh.set(array, i, elements[i]); + } + // shift to the right element + for (int i = 0; i < elements.length; i++) { + Object v = elements[i + 1 < elements.length ? i + 1 : 0]; + assertEquals(elements[i], vh.compareAndExchange(array, i, elements[i], v)); + } + } +} diff --git a/test/jdk/valhalla/valuetypes/FlatVarHandleTest.java b/test/jdk/valhalla/valuetypes/FlatVarHandleTest.java new file mode 100644 index 00000000000..78ded142e36 --- /dev/null +++ b/test/jdk/valhalla/valuetypes/FlatVarHandleTest.java @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. 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. + */ + +import static org.junit.jupiter.api.Assertions.*; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.ParameterizedTest; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.lang.invoke.VarHandle.AccessMode; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.Function; + +/* + * @test + * @summary Test atomic access modes on var handles for flattened values + * @enablePreview + * @modules java.base/jdk.internal.value java.base/jdk.internal.vm.annotation + * @run junit/othervm -XX:-UseArrayFlattening -XX:-UseNullableValueFlattening FlatVarHandleTest + * @run junit/othervm -XX:+UseArrayFlattening -XX:+UseNullableValueFlattening FlatVarHandleTest + */ +public class FlatVarHandleTest { + + interface Pointable { } + + @FunctionalInterface + interface TriFunction { + + R apply(A a, B b, C c); + + default TriFunction andThen( + Function after) { + Objects.requireNonNull(after); + return (A a, B b, C c) -> after.apply(apply(a, b, c)); + } + } + + @LooselyConsistentValue + static value class WeakPoint implements Pointable { + short x,y; + WeakPoint(int i, int j) { x = (short)i; y = (short)j; } + + static WeakPoint[] makePoints(int len, BiFunction, Integer, Object[]> arrayFactory) { + WeakPoint[] array = (WeakPoint[])arrayFactory.apply(WeakPoint.class, len); + for (int i = 0; i < len; ++i) { + array[i] = new WeakPoint(i, i); + } + return array; + } + + static WeakPoint[] makePoints(int len, Object initval, TriFunction, Integer, Object, Object[]> arrayFactory) { + WeakPoint[] array = (WeakPoint[])arrayFactory.apply(WeakPoint.class, len, initval); + for (int i = 0; i < len; ++i) { + array[i] = new WeakPoint(i, i); + } + return array; + } + } + + static class WeakPointHolder { + WeakPoint p_i = new WeakPoint(0, 0); + static WeakPoint p_s = new WeakPoint(0, 0); + @Strict + @NullRestricted + WeakPoint p_i_nr = new WeakPoint(0, 0); + @Strict + @NullRestricted + static WeakPoint p_s_nr = new WeakPoint(0, 0); + } + + static value class StrongPoint implements Pointable { + short x,y; + StrongPoint(int i, int j) { x = (short)i; y = (short)j; } + + static StrongPoint[] makePoints(int len, BiFunction, Integer, Object[]> arrayFactory) { + StrongPoint[] array = (StrongPoint[])arrayFactory.apply(StrongPoint.class, len); + for (int i = 0; i < len; ++i) { + array[i] = new StrongPoint(i, i); + } + return array; + } + + static StrongPoint[] makePoints(int len, Object initval, TriFunction, Integer, Object, Object[]> arrayFactory) { + StrongPoint[] array = (StrongPoint[])arrayFactory.apply(StrongPoint.class, len, initval); + for (int i = 0; i < len; ++i) { + array[i] = new StrongPoint(i, i); + } + return array; + } + } + + static class StrongPointHolder { + StrongPoint p_i = new StrongPoint(0, 0); + static StrongPoint p_s = new StrongPoint(0, 0); + } + + private static List fieldAccessProvider() { + try { + List fields = List.of( + WeakPointHolder.class.getDeclaredField("p_s"), + WeakPointHolder.class.getDeclaredField("p_i"), + WeakPointHolder.class.getDeclaredField("p_s_nr"), + WeakPointHolder.class.getDeclaredField("p_i_nr"), + StrongPointHolder.class.getDeclaredField("p_s"), + StrongPointHolder.class.getDeclaredField("p_i")); + List arguments = new ArrayList<>(); + for (AccessMode accessMode : AccessMode.values()) { + for (Field field : fields) { + boolean isStatic = (field.getModifiers() & Modifier.STATIC) != 0; + boolean isWeak = field.getDeclaringClass().equals(WeakPointHolder.class); + Object holder = null; + if (!isStatic) { + holder = isWeak ? new WeakPointHolder() : new StrongPointHolder(); + } + BiFunction factory = isWeak ? + (i1, i2) -> new WeakPoint(i1, i2) : + (i1, i2) -> new StrongPoint(i1, i2); + boolean allowsNonPlainAccess = (field.getModifiers() & Modifier.VOLATILE) != 0 || + !ValueClass.isNullRestrictedField(field) || + !isWeak; + arguments.add(Arguments.of(accessMode, holder, factory, field, allowsNonPlainAccess)); + } + } + return arguments; + } catch (ReflectiveOperationException ex) { + throw new IllegalStateException(ex); + } + } + + /* + * Verify that atomic access modes are not supported on flat fields. + */ + @ParameterizedTest + @MethodSource("fieldAccessProvider") + public void testFieldAccess(AccessMode accessMode, Object holder, BiFunction factory, + Field field, boolean allowsNonPlainAccess) throws Throwable { + VarHandle varHandle = MethodHandles.lookup().unreflectVarHandle(field); + if (varHandle.isAccessModeSupported(accessMode)) { + assertTrue(isPlain(accessMode) || (allowsNonPlainAccess && !isBitwise(accessMode) && !isNumeric(accessMode))); + MethodHandle methodHandle = varHandle.toMethodHandle(accessMode); + List arguments = new ArrayList<>(); + if (holder != null) { + arguments.add(holder); // receiver + } + for (int i = arguments.size(); i < methodHandle.type().parameterCount(); i++) { + arguments.add(factory.apply(i, i)); // add extra setter param + } + methodHandle.invokeWithArguments(arguments.toArray()); + } else { + assertTrue(!allowsNonPlainAccess || isBitwise(accessMode) || isNumeric(accessMode)); + } + } + + private static List arrayAccessProvider() { + List arrayObjects = List.of( + WeakPoint.makePoints(10, ValueClass::newNullableAtomicArray), + WeakPoint.makePoints(10, new WeakPoint(0, 0), ValueClass::newNullRestrictedNonAtomicArray), + WeakPoint.makePoints(10, new WeakPoint(0, 0), ValueClass::newNullRestrictedAtomicArray), + new WeakPoint[10], + StrongPoint.makePoints(10, ValueClass::newNullableAtomicArray), + StrongPoint.makePoints(10, new StrongPoint(0, 0), ValueClass::newNullRestrictedNonAtomicArray), + StrongPoint.makePoints(10, new StrongPoint(0, 0), ValueClass::newNullRestrictedAtomicArray), + new StrongPoint[10]); + + List arguments = new ArrayList<>(); + for (AccessMode accessMode : AccessMode.values()) { + if (accessMode.ordinal() != 2) continue; + for (Object[] arrayObject : arrayObjects) { + boolean isWeak = arrayObject.getClass().getComponentType().equals(WeakPoint.class); + List> arrayTypes = List.of( + isWeak ? WeakPoint[].class : StrongPoint[].class, Pointable[].class, Object[].class); + for (Class arrayType : arrayTypes) { + BiFunction factory = isWeak ? + (i1, i2) -> new WeakPoint(i1, i2) : + (i1, i2) -> new StrongPoint((short)(int)i1, (short)(int)i2); + boolean allowsNonPlainAccess = !ValueClass.isNullRestrictedArray(arrayObject) || + ValueClass.isAtomicArray(arrayObject) || + !isWeak; + arguments.add(Arguments.of(accessMode, arrayObject, factory, arrayType, allowsNonPlainAccess)); + } + } + } + return arguments; + } + + /* + * Verify that atomic access modes are not supported on flat array instances. + */ + @ParameterizedTest + @MethodSource("arrayAccessProvider") + public void testArrayAccess(AccessMode accessMode, Object[] arrayObject, BiFunction factory, + Class arrayType, boolean allowsNonPlainAccess) throws Throwable { + VarHandle varHandle = MethodHandles.arrayElementVarHandle(arrayType); + if (varHandle.isAccessModeSupported(accessMode)) { + assertTrue(!isBitwise(accessMode) && !isNumeric(accessMode)); + MethodHandle methodHandle = varHandle.toMethodHandle(accessMode); + List arguments = new ArrayList<>(); + arguments.add(arrayObject); // array receiver + arguments.add(0); // index + for (int i = 2; i < methodHandle.type().parameterCount(); i++) { + arguments.add(factory.apply(i, i)); // add extra setter param + } + try { + methodHandle.invokeWithArguments(arguments.toArray()); + } catch (IllegalArgumentException ex) { + assertFalse(allowsNonPlainAccess); + } + } else { + assertTrue(isBitwise(accessMode) || isNumeric(accessMode)); + } + } + + boolean isBitwise(AccessMode accessMode) { + return switch (accessMode) { + case GET_AND_BITWISE_AND, GET_AND_BITWISE_AND_ACQUIRE, + GET_AND_BITWISE_AND_RELEASE, GET_AND_BITWISE_OR, + GET_AND_BITWISE_OR_ACQUIRE, GET_AND_BITWISE_OR_RELEASE, + GET_AND_BITWISE_XOR, GET_AND_BITWISE_XOR_ACQUIRE, + GET_AND_BITWISE_XOR_RELEASE -> true; + default -> false; + }; + } + + boolean isNumeric(AccessMode accessMode) { + return switch (accessMode) { + case GET_AND_ADD, GET_AND_ADD_ACQUIRE, GET_AND_ADD_RELEASE -> true; + default -> false; + }; + } + + boolean isPlain(AccessMode accessMode) { + return switch (accessMode) { + case GET, SET -> true; + default -> false; + }; + } +} diff --git a/test/jdk/valhalla/valuetypes/IsIdentityClassTest.java b/test/jdk/valhalla/valuetypes/IsIdentityClassTest.java new file mode 100644 index 00000000000..58ea38eb38f --- /dev/null +++ b/test/jdk/valhalla/valuetypes/IsIdentityClassTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Test that IsIdentityClass and modifiers return true for arrays that can be flattened. + * @library /test/lib + * @enablePreview false + * @modules java.base/jdk.internal.misc + * java.base/jdk.internal.value + * @run junit/othervm IsIdentityClassTest + * @run junit/othervm --enable-preview IsIdentityClassTest + */ + +import org.junit.jupiter.api.Test; + +import java.lang.reflect.AccessFlag; +import java.lang.reflect.Modifier; +import java.util.Set; + +import jdk.internal.misc.PreviewFeatures; + +import static jdk.test.lib.Asserts.*; + +public class IsIdentityClassTest { + + @Test + void testIsIdentityClass() { + assertEquals(!PreviewFeatures.isEnabled(), Integer.class.isIdentity(), "Integer is not an IDENTITY type"); + assertTrue(Integer[].class.isIdentity(), "Arrays of inline types are IDENTITY types"); + } + + @Test + void testModifiers() { + // Without --enable-preview (before Valhalla), there was no IDENTITY modifier. + // With --enable-preview, Integer still should not have the IDENTITY modifier. + // So only verify this in preview mode. + if (PreviewFeatures.isEnabled()) { + int imod = Integer.class.getModifiers(); + assertFalse(Modifier.isIdentity(imod), "Modifier of Integer should not have IDENTITY set"); + } + int amod = Integer[].class.getModifiers(); + assertEquals(PreviewFeatures.isEnabled(), Modifier.isIdentity(amod), "Modifier of array should have IDENTITY set"); + } + + @Test + void testAccessFlags() { + // Without --enable-preview (before Valhalla), there was no IDENTITY accessflag. + // With --enable-preview, Integer still should not have the IDENTITY accessflag. + // So only verify this in preview mode. + if (PreviewFeatures.isEnabled()) { + Set iacc = Integer.class.accessFlags(); + assertFalse(iacc.contains(AccessFlag.IDENTITY), "Access flags should not contain IDENTITY"); + } + // AccessFlags for arrays set the IDENTITY accessflag. + Set aacc = Integer[].class.accessFlags(); + assertEquals(PreviewFeatures.isEnabled(), aacc.contains(AccessFlag.IDENTITY), "Access flags of array of inline types should contain IDENTITY"); + } +} diff --git a/test/jdk/valhalla/valuetypes/LambdaMetaFactory/LambdaConversion.java b/test/jdk/valhalla/valuetypes/LambdaMetaFactory/LambdaConversion.java new file mode 100644 index 00000000000..e3327ff12ae --- /dev/null +++ b/test/jdk/valhalla/valuetypes/LambdaMetaFactory/LambdaConversion.java @@ -0,0 +1,68 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary test lambda type conversion of value class + * @enablePreview + * @run junit/othervm LambdaConversion + */ + +import java.util.function.ToIntFunction; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class LambdaConversion { + + static class c_int { } + static value class Pointer { + long addr; + + public Pointer(long addr) { + this.addr = addr; + } + + long address() { return addr; } + } + + @Test + public static void test() { + Pointer p_int = new Pointer<>(12); + assertTrue(doAction(p_int, LambdaConversion::one) == 1); + assertTrue(doAction(p_int, LambdaConversion::two) == 2); + } + + static int doAction(Pointer pointer, ToIntFunction> action) { + return action.applyAsInt(pointer); + } + + static int one(Pointer pointer) { + return 1; + } + + static int two(Pointer p_int) { + return 2; + } +} + diff --git a/test/jdk/valhalla/valuetypes/LambdaMetaFactory/LambdaTest.java b/test/jdk/valhalla/valuetypes/LambdaMetaFactory/LambdaTest.java new file mode 100644 index 00000000000..ff3e81ef0e6 --- /dev/null +++ b/test/jdk/valhalla/valuetypes/LambdaMetaFactory/LambdaTest.java @@ -0,0 +1,87 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Test lambdas with parameter types or return type of value class + * @enablePreview + * @run junit/othervm LambdaTest + */ + +import java.util.function.Function; +import java.util.function.IntFunction; + +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class LambdaTest { + static value class V { + int x; + V(int x) { + this.x = x; + } + + static V get(int x) { + return new V(x); + } + } + + static value class Value { + @NullRestricted @Strict + V v; + Value(V v) { + this.v = v; + } + static Value get(int x) { + return new Value(new V(x)); + } + } + + static int getV(V v) { + return v.x; + } + + static int getValue(Value v) { + return v.v.x; + } + + @Test + public void testValueParameterType() { + Function func1 = LambdaTest::getValue; + assertTrue(func1.apply(new Value(new V(100))) == 100); + + Function func2 = LambdaTest::getV; + assertTrue(func2.apply(new V(200)) == 200); + } + + @Test + public void testValueReturnType() { + IntFunction func1 = Value::get; + assertEquals(func1.apply(10), new Value(new V(10))); + + IntFunction func2 = V::get; + assertEquals(func2.apply(20), new V(20)); + } +} diff --git a/test/jdk/valhalla/valuetypes/LambdaMetaFactory/src/IdentityRunnable.java b/test/jdk/valhalla/valuetypes/LambdaMetaFactory/src/IdentityRunnable.java new file mode 100644 index 00000000000..c3146828274 --- /dev/null +++ b/test/jdk/valhalla/valuetypes/LambdaMetaFactory/src/IdentityRunnable.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * 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. + */ + +public interface IdentityRunnable { + void run(); +} diff --git a/test/jdk/valhalla/valuetypes/LambdaMetaFactory/src/Test.java b/test/jdk/valhalla/valuetypes/LambdaMetaFactory/src/Test.java new file mode 100644 index 00000000000..2c8583c01f9 --- /dev/null +++ b/test/jdk/valhalla/valuetypes/LambdaMetaFactory/src/Test.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * 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. + */ + +public class Test { + public static void testValueRunnable() { + ValueRunnable r = () -> { + System.out.println("ValueRunnable::run"); + }; + r.run(); + } + + public static void testIdentityRunnable() { + IdentityRunnable r = () -> { + System.out.println("IdentityRunnable::run"); + }; + r.run(); + } +} diff --git a/test/jdk/valhalla/valuetypes/LambdaMetaFactory/src/ValueRunnable.java b/test/jdk/valhalla/valuetypes/LambdaMetaFactory/src/ValueRunnable.java new file mode 100644 index 00000000000..82c3ab5cccc --- /dev/null +++ b/test/jdk/valhalla/valuetypes/LambdaMetaFactory/src/ValueRunnable.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * 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. + */ + +public interface ValueRunnable { + void run(); +} diff --git a/test/jdk/valhalla/valuetypes/LayoutIterationTest.java b/test/jdk/valhalla/valuetypes/LayoutIterationTest.java new file mode 100644 index 00000000000..5eecd474e1e --- /dev/null +++ b/test/jdk/valhalla/valuetypes/LayoutIterationTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import jdk.internal.value.LayoutIteration; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/* + * @test + * @summary test LayoutIteration + * @enablePreview + * @requires vm.flagless + * @modules java.base/jdk.internal.vm.annotation + * java.base/jdk.internal.value + * @run junit/othervm LayoutIterationTest + */ +class LayoutIterationTest { + + @LooselyConsistentValue + static value class One { + int a; + short b; + + One(int a, short b) { + this.a = a; + this.b = b; + super(); + } + } + + @LooselyConsistentValue + static value class Two { + @NullRestricted + One one = new One(5, (short) 3); + One anotherOne = new One(4, (short) 2); + long l = 5L; + } + + @Test + void test() { + Two t = new Two(); + Set> classes = LayoutIteration.computeElementGetters(One.class).stream() + .map(mh -> mh.type().returnType()).collect(Collectors.toSet()); + assertEquals(Set.of(int.class, short.class), classes); + Map, Object> values = LayoutIteration.computeElementGetters(Two.class).stream() + .collect(Collectors.toMap(mh -> mh.type().returnType(), mh -> { + try { + return (Object) mh.invoke(t); + } catch (Throwable ex) { + return Assertions.fail(ex); + } + })); + assertEquals(Map.of( + int.class, t.one.a, + short.class, t.one.b, + One.class, t.anotherOne, + long.class, t.l + ), values); + } +} diff --git a/test/jdk/valhalla/valuetypes/MethodHandleTest.java b/test/jdk/valhalla/valuetypes/MethodHandleTest.java new file mode 100644 index 00000000000..10b3c5bded1 --- /dev/null +++ b/test/jdk/valhalla/valuetypes/MethodHandleTest.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +/* + * @test + * @summary test MethodHandle and VarHandle of value classes + * @enablePreview + * @run junit/othervm MethodHandleTest + */ + +import java.lang.invoke.*; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.*; + +public class MethodHandleTest { + @LooselyConsistentValue + static value class Point { + public int x; + public int y; + Point(int x, int y) { + this.x = x; + this.y = y; + } + } + + @LooselyConsistentValue + static value class Line { + @NullRestricted @Strict + Point p1; + @NullRestricted @Strict + Point p2; + + Line(int x1, int y1, int x2, int y2) { + this.p1 = new Point(x1, y1); + this.p2 = new Point(x2, y2); + } + } + + static class Ref { + @NullRestricted @Strict + Point p; + Line l; + List list; + ValueOptional vo; + + Ref(Point p, Line l) { + this.p = p; + this.l = l; + } + } + + static value class ValueOptional { + private Object o; + public ValueOptional(Object o) { + this.o = o; + } + } + + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + static final Point P = new Point(1, 2); + static final Line L = new Line(1, 2, 3, 4); + static final Ref R = new Ref(P, null); + + static Stream fields() { + return Stream.of( + // value class with int fields + Arguments.of("MethodHandleTest$Point", P, Set.of("x", "y")), + // value class whose fields are null-restricted and of value class + Arguments.of("MethodHandleTest$Line", L, Set.of("p1", "p2")), + // identity class whose non-final fields are of value type, + Arguments.of("MethodHandleTest$Ref", R, Set.of("p", "l", "list", "vo")) + ); + } + + /** + * Test MethodHandle invocation on the fields of a given class. + * MethodHandle produced by Lookup::unreflectGetter, Lookup::findGetter, + * Lookup::findVarHandle. + */ + @ParameterizedTest + @MethodSource("fields") + public void testFieldGetter(String cn, Object o, Set fields) throws Throwable { + Class c = Class.forName(cn); + for (String name : fields) { + Field f = c.getDeclaredField(name); + var mh = LOOKUP.findGetter(c, f.getName(), f.getType()); + var v1 = mh.invoke(o); + + var vh = LOOKUP.findVarHandle(c, f.getName(), f.getType()); + var v2 = vh.get(o); + + var mh3 = LOOKUP.unreflectGetter(f); + var v3 = mh3.invoke(o); + + if (c.isValue()) + ensureImmutable(f, o); + } + } + + static Stream setters() { + return Stream.of( + Arguments.of(Ref.class, R, "p", true), + Arguments.of(Ref.class, R, "l", false), + Arguments.of(Ref.class, R, "list", false), + Arguments.of(Ref.class, R, "vo", false) + ); + } + @ParameterizedTest + @MethodSource("setters") + public void testFieldSetter(Class cls, Object o, String name, boolean nullRestricted) throws Throwable { + Field f = cls.getDeclaredField(name); + var mh = LOOKUP.findSetter(cls, f.getName(), f.getType()); + var vh = LOOKUP.findVarHandle(cls, f.getName(), f.getType()); + var mh3 = LOOKUP.unreflectSetter(f); + + if (nullRestricted) { + assertThrows(NullPointerException.class, () -> mh.invoke(o, null)); + assertThrows(NullPointerException.class, () -> vh.set(o, null)); + assertThrows(NullPointerException.class, () -> mh3.invoke(o, null)); + } else { + mh.invoke(o, null); + vh.set(o, null); + mh3.invoke(o, null); + } + } + + static Stream arrays() throws Throwable { + return Stream.of( + Arguments.of(Point[].class, newArray(Point[].class), P, false), + Arguments.of(Point[].class, newNullRestrictedNonAtomicArray(Point.class, new Point(0, 0)), P, true), + Arguments.of(Line[].class, newArray(Line[].class), L, false), + Arguments.of(Line[].class, newNullRestrictedNonAtomicArray(Line.class, new Line(0, 0, 0, 0)), L, true), + Arguments.of(Ref[].class, newArray(Ref[].class), R, false) + ); + } + + private static final int ARRAY_SIZE = 5; + private static Object[] newArray(Class arrayClass) throws Throwable { + MethodHandle ctor = MethodHandles.arrayConstructor(arrayClass); + return (Object[])ctor.invoke(ARRAY_SIZE); + } + private static Object[] newNullRestrictedNonAtomicArray(Class componentClass, Object initVal) throws Throwable { + return ValueClass.newNullRestrictedNonAtomicArray(componentClass, ARRAY_SIZE, initVal); + } + + @ParameterizedTest + @MethodSource("arrays") + public void testArrayElementSetterAndGetter(Class arrayClass, Object[] array, Object element, boolean nullRestricted) throws Throwable { + MethodHandle setter = MethodHandles.arrayElementSetter(array.getClass()); + MethodHandle getter = MethodHandles.arrayElementGetter(array.getClass()); + VarHandle vh = MethodHandles.arrayElementVarHandle(arrayClass); + Class componentType = arrayClass.getComponentType(); + + for (int i=0; i < ARRAY_SIZE; i++) { + var v = getter.invoke(array, i); + if (nullRestricted) { + assertTrue(v != null); + } else { + assertTrue(v == null); + } + } + for (int i=0; i < ARRAY_SIZE; i++) { + setter.invoke(array, i, element); + assertTrue(getter.invoke(array, i) == element); + } + // set an array element to null + if (nullRestricted) { + assertTrue(vh.get(array, 1) != null); + assertThrows(NullPointerException.class, () -> setter.invoke(array, 1, null)); + assertThrows(NullPointerException.class, () -> vh.set(array, 1, null)); + } else { + setter.invoke(array, 1, null); + assertNull(getter.invoke(array, 1)); + vh.set(array, 1, null); + } + } + + static void ensureImmutable(Field f, Object o) throws Throwable { + Class c = f.getDeclaringClass(); + assertTrue(Modifier.isFinal(f.getModifiers())); + assertFalse(Modifier.isStatic(f.getModifiers())); + assertTrue(f.trySetAccessible()); + + Object v = f.get(o); + + assertThrows(IllegalAccessException.class, () -> LOOKUP.findSetter(c, f.getName(), f.getType())); + assertThrows(IllegalAccessException.class, () -> LOOKUP.unreflectSetter(f)); + VarHandle vh = LOOKUP.findVarHandle(c, f.getName(), f.getType()); + + // test var handle + assertThrows(UnsupportedOperationException.class, () -> vh.set(o, v)); + } +} diff --git a/test/jdk/valhalla/valuetypes/MethodReference.java b/test/jdk/valhalla/valuetypes/MethodReference.java new file mode 100644 index 00000000000..69322c28d2a --- /dev/null +++ b/test/jdk/valhalla/valuetypes/MethodReference.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary test method reference of value class as the parameter type + * @enablePreview + * @run main/othervm MethodReference + */ + +import java.util.function.Supplier; + +public value class MethodReference { + final int x; + final int y; + MethodReference() { + this.x = 1234; + this.y = 5678; + } + + public static void main(String... args) { + Supplier supplier = MethodReference::new; + MethodReference o = supplier.get(); + if (o.x != 1234 || o.y != 5678) + throw new AssertionError(o); + } +} diff --git a/test/jdk/valhalla/valuetypes/Nest.java b/test/jdk/valhalla/valuetypes/Nest.java new file mode 100644 index 00000000000..4a4c72185fa --- /dev/null +++ b/test/jdk/valhalla/valuetypes/Nest.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Test substitutability of inner class and anonymous class that + * has the enclosing instance and possibly other captured outer locals + * @enablePreview + * @run junit/othervm Nest + */ + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +public class Nest { + @Test + public void test2() { + Outer n = new Outer(1); + Outer.Inner inner = n.new Inner(10); + Outer n1 = new Outer(1); + Outer n2 = new Outer(2); + // o1.new Inner(1) == o2.new Inner(1) iff o1 == o2 + assertEquals(n1.new Inner(10), inner); + assertEquals(n2.new Inner(10), new Outer(2).new Inner(10)); + } + + value class Outer { + final int i; + Outer(int i) { + this.i = i; + } + + value class Inner { + final int ic; + Inner(int ic) { + this.ic = ic; + } + } + } +} diff --git a/test/jdk/valhalla/valuetypes/NullRestrictedArraysTest.java b/test/jdk/valhalla/valuetypes/NullRestrictedArraysTest.java new file mode 100644 index 00000000000..983b955805c --- /dev/null +++ b/test/jdk/valhalla/valuetypes/NullRestrictedArraysTest.java @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @enablePreview + * @run junit/othervm -XX:-UseArrayFlattening -XX:-UseNullableValueFlattening NullRestrictedArraysTest + * @run junit/othervm -XX:+UseArrayFlattening -XX:+UseNullableValueFlattening NullRestrictedArraysTest + */ + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.stream.Stream; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.*; + +public class NullRestrictedArraysTest { + interface I { + int getValue(); + } + @LooselyConsistentValue + static value class Value implements I { + int v; + Value() { + this(0); + } + Value(int v) { + this.v = v; + } + public int getValue() { + return v; + } + } + + static class T { + String s; + Value obj; // can be null + @Strict + @NullRestricted + Value value = new Value(); + } + + static Stream checkedField() throws ReflectiveOperationException { + Value v = new Value(); + return Stream.of( + Arguments.of(T.class.getDeclaredField("s"), String.class, "", false), + Arguments.of(T.class.getDeclaredField("obj"), Value.class, null, false), + Arguments.of(T.class.getDeclaredField("value"), Value.class, v, true) + ); + } + + /* + * Test creating null-restricted arrays + */ + @ParameterizedTest + @MethodSource("checkedField") + public void testNullRestrictedArrays(Field field, Class type, Object initValue, + boolean nullRestricted) throws ReflectiveOperationException { + boolean nr = ValueClass.isNullRestrictedField(field); + assertEquals(nr, nullRestricted); + assertTrue(field.getType() == type); + Object[] array = nullRestricted + ? ValueClass.newNullRestrictedAtomicArray(type, 4, initValue) + : (Object[]) Array.newInstance(type, 4); + assertTrue(ValueClass.isNullRestrictedArray(array) == nullRestricted); + for (int i=0; i < array.length; i++) { + array[i] = type.newInstance(); + } + if (nullRestricted) { + // NPE thrown if elements in a null-restricted array set to null + assertThrows(NullPointerException.class, () -> array[0] = null); + } else { + array[0] = null; + } + } + + /* + * Test Arrays::copyOf and Arrays::copyOfRange to create null-restricted arrays. + */ + @Test + public void testArraysCopyOf() { + int len = 4; + Object[] array = (Object[]) Array.newInstance(Value.class, len); + Object[] nullRestrictedArray = ValueClass.newNullRestrictedNonAtomicArray(Value.class, len, new Value()); + for (int i=0; i < len; i++) { + array[i] = new Value(i); + nullRestrictedArray[i] = new Value(i); + } + testCopyOf(array, nullRestrictedArray); + // Cannot extend a null-restricted array without providing a value to fill the new elements + // testCopyOfRange(array, nullRestrictedArray, 1, len+2); + }; + + private void testCopyOf(Object[] array, Object[] nullRestrictedArray) { + Object[] newArray1 = Arrays.copyOf(array, array.length); + Object[] newArray2 = Arrays.copyOf(nullRestrictedArray, nullRestrictedArray.length); + + assertFalse(ValueClass.isNullRestrictedArray(newArray1)); + assertTrue(ValueClass.isNullRestrictedArray(newArray2)); + + // elements in a normal array can be null + for (int i=0; i < array.length; i++) { + newArray1[i] = null; + } + // NPE thrown if elements in a null-restricted array set to null + assertThrows(NullPointerException.class, () -> newArray2[0] = null); + } + + private void testCopyOfRange(Object[] array, Object[] nullRestrictedArray, int from, int to) { + Object[] newArray1 = Arrays.copyOfRange(array, from, to); + + // elements in a normal array can be null + for (int i=0; i < newArray1.length; i++) { + newArray1[i] = null; + } + + // check the new array padded with null if normal array and + // zero instance if null-restricted array + for (int i=0; i < newArray1.length; i++) { + if (from+1 >= array.length) { + // padded with null + assertTrue(newArray1[i] == null); + } + } + + if (to > array.length) { + // NullRestricted arrays do not have a value to fill new array elements + assertThrows(IllegalArgumentException.class, () -> Arrays.copyOfRange(nullRestrictedArray, from, to)); + } else { + Object[] newArray2 = Arrays.copyOfRange(nullRestrictedArray, from, to); + System.out.println("newArray2 " + newArray2.length + " " + Arrays.toString(newArray2)); + // NPE thrown if elements in a null-restricted array set to null + assertThrows(NullPointerException.class, () -> newArray2[0] = null); + } + } + + @Test + public void testVarHandle() { + int len = 4; + Object[] array = (Object[]) Array.newInstance(Value.class, len); + Object[] nullRestrictedArray = ValueClass.newNullRestrictedNonAtomicArray(Value.class, len, new Value()); + + // Test var handles + testVarHandleArray(array, Value[].class, false); + testVarHandleArray(array, I[].class, false); + testVarHandleArray(nullRestrictedArray, Value[].class, true); + testVarHandleArray(nullRestrictedArray, I[].class, true); + } + + private void testVarHandleArray(Object[] array, Class arrayClass, boolean isNullRestricted) { + for (int i=0; i < array.length; i++) { + array[i] = new Value(i); + } + + VarHandle vh = MethodHandles.arrayElementVarHandle(arrayClass); + Value value = new Value(0); + Value value1 = new Value(1); + Value value2 = new Value(2); + + assertTrue(vh.get(array, 0) == value); + assertTrue(vh.getVolatile(array, 0) == value); + assertTrue(vh.getOpaque(array, 0) == value); + assertTrue(vh.getAcquire(array, 0) == value); + + // test set with null values + + if (!isNullRestricted) { + // if not null-restricted, we expect these set operations to succeed + + vh.set(array, 0, null); + assertNull(vh.get(array, 0)); + vh.setVolatile(array, 0, null); + assertNull(vh.get(array, 0)); + vh.setOpaque(array, 0, null); + assertNull(vh.get(array, 0)); + vh.setRelease(array, 0, null); + assertNull(vh.get(array, 0)); + + assertTrue(vh.compareAndSet(array, 1, value1, null)); + assertNull(vh.get(array, 0)); + vh.set(array, 1, value1); + + assertEquals(vh.compareAndExchange(array, 1, value1, null), value1); + assertNull(vh.get(array, 0)); + vh.set(array, 1, value1); + + assertEquals(vh.compareAndExchangeAcquire(array, 1, value1, null), value1); + assertNull(vh.get(array, 0)); + vh.set(array, 1, value1); + + assertEquals(vh.compareAndExchangeRelease(array, 1, value1, null), value1); + assertNull(vh.get(array, 0)); + vh.set(array, 1, value1); + + assertTrue(vh.weakCompareAndSet(array, 1, value1, null)); + assertNull(vh.get(array, 0)); + vh.set(array, 1, value1); + + assertTrue(vh.weakCompareAndSetAcquire(array, 1, value1, null)); + assertNull(vh.get(array, 0)); + vh.set(array, 1, value1); + + assertTrue(vh.weakCompareAndSetPlain(array, 1, value1, null)); + assertNull(vh.get(array, 0)); + vh.set(array, 1, value1); + + assertTrue(vh.weakCompareAndSetRelease(array, 1, value1, null)); + assertNull(vh.get(array, 0)); + vh.set(array, 1, value1); + } else { + // if null-restricted, we expect these set operations to fail + + assertThrows(NullPointerException.class, () -> vh.set(array, 0, null)); + assertThrows(NullPointerException.class, () -> vh.setVolatile(array, 0, null)); + assertThrows(NullPointerException.class, () -> vh.setOpaque(array, 0, null)); + assertThrows(NullPointerException.class, () -> vh.setRelease(array, 0, null)); + + assertThrows(NullPointerException.class, () -> vh.compareAndSet(array, 1, value1, null)); + assertThrows(NullPointerException.class, () -> vh.compareAndExchange(array, 1, value1, null)); + assertThrows(NullPointerException.class, () -> vh.compareAndExchangeAcquire(array, 1, value1, null)); + assertThrows(NullPointerException.class, () -> vh.compareAndExchangeRelease(array, 1, value1, null)); + assertThrows(NullPointerException.class, () -> vh.weakCompareAndSet(array, 1, value1, null)); + assertThrows(NullPointerException.class, () -> vh.weakCompareAndSetAcquire(array, 1, value1, null)); + assertThrows(NullPointerException.class, () -> vh.weakCompareAndSetPlain(array, 1, value1, null)); + assertThrows(NullPointerException.class, () -> vh.weakCompareAndSetRelease(array, 1, value1, null)); + } + + // test set with non-null values + + vh.set(array, 0, value1); + assertEquals(vh.get(array, 0), value1); + vh.setVolatile(array, 0, value1); + assertEquals(vh.get(array, 0), value1); + vh.setOpaque(array, 0, value1); + assertEquals(vh.get(array, 0), value1); + vh.setRelease(array, 0, value1); + assertEquals(vh.get(array, 0), value1); + + assertTrue(vh.compareAndSet(array, 1, value1, value2)); + assertEquals(vh.get(array, 1), value2); + vh.set(array, 1, value1); + + assertEquals(vh.compareAndExchange(array, 1, value1, value2), value1); + assertEquals(vh.get(array, 1), value2); + vh.set(array, 1, value1); + + assertEquals(vh.compareAndExchangeAcquire(array, 1, value1, value2), value1); + assertEquals(vh.get(array, 1), value2); + vh.set(array, 1, value1); + + assertEquals(vh.compareAndExchangeRelease(array, 1, value1, value2), value1); + assertEquals(vh.get(array, 1), value2); + vh.set(array, 1, value1); + + assertTrue(vh.weakCompareAndSet(array, 1, value1, value2)); + assertEquals(vh.get(array, 1), value2); + vh.set(array, 1, value1); + + assertTrue(vh.weakCompareAndSetAcquire(array, 1, value1, value2)); + assertEquals(vh.get(array, 1), value2); + vh.set(array, 1, value1); + + assertTrue(vh.weakCompareAndSetPlain(array, 1, value1, value2)); + assertEquals(vh.get(array, 1), value2); + vh.set(array, 1, value1); + + assertTrue(vh.weakCompareAndSetRelease(array, 1, value1, value2)); + assertEquals(vh.get(array, 1), value2); + vh.set(array, 1, value1); + + // test atomic set with null witness + + assertFalse(vh.compareAndSet(array, 2, null, value1)); + assertEquals(vh.get(array, 2), value2); + + assertNotNull(vh.compareAndExchange(array, 2, null, value1)); + assertEquals(vh.get(array, 2), value2); + + assertNotNull(vh.compareAndExchangeAcquire(array, 2, null, value1)); + assertEquals(vh.get(array, 2), value2); + + assertNotNull(vh.compareAndExchangeRelease(array, 2, null, value1)); + assertEquals(vh.get(array, 2), value2); + + assertFalse(vh.weakCompareAndSet(array, 2, null, value1)); + assertEquals(vh.get(array, 2), value2); + + assertFalse(vh.weakCompareAndSetAcquire(array, 2, null, value1)); + assertEquals(vh.get(array, 2), value2); + + assertFalse(vh.weakCompareAndSetPlain(array, 2, null, value1)); + assertEquals(vh.get(array, 2), value2); + + assertFalse(vh.weakCompareAndSetRelease(array, 2, null, value1)); + assertEquals(vh.get(array, 2), value2); + } +} diff --git a/test/jdk/valhalla/valuetypes/NullRestrictedTest.java b/test/jdk/valhalla/valuetypes/NullRestrictedTest.java new file mode 100644 index 00000000000..6e3ca47b419 --- /dev/null +++ b/test/jdk/valhalla/valuetypes/NullRestrictedTest.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +/* + * @test + * @summary Test reflection and method handle on accessing a field of a null-restricted value class + * that may be flattened or non-flattened + * @enablePreview + * @run junit/othervm NullRestrictedTest + * @run junit/othervm -XX:-UseFieldFlattening NullRestrictedTest + */ + +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.util.stream.Stream; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.*; + +public class NullRestrictedTest { + static value class EmptyValue { + public boolean isEmpty() { + return true; + } + } + + static value class Value { + Object o; + @NullRestricted @Strict + EmptyValue empty; + Value() { + this.o = null; + this.empty = new EmptyValue(); + } + Value(EmptyValue empty) { + this.o = null; + this.empty = empty; + } + } + + static class Mutable { + EmptyValue o; + @NullRestricted @Strict + EmptyValue empty = new EmptyValue(); + @NullRestricted @Strict + volatile EmptyValue vempty = new EmptyValue(); + } + + @Test + public void emptyValueClass() { + EmptyValue e = new EmptyValue(); + Field[] fields = e.getClass().getDeclaredFields(); + assertTrue(fields.length == 0); + } + + @Test + public void testNonNullFieldAssignment() { + var npe = assertThrows(NullPointerException.class, () -> new Value(null)); + System.err.println(npe); // log the exception message + } + + static Stream getterCases() { + Value v = new Value(); + EmptyValue emptyValue = new EmptyValue(); + Mutable m = new Mutable(); + + return Stream.of( + Arguments.of(Value.class, "o", Object.class, v, null), + Arguments.of(Value.class, "empty", EmptyValue.class, v, emptyValue), + Arguments.of(Mutable.class, "o", EmptyValue.class, m, null), + Arguments.of(Mutable.class, "empty", EmptyValue.class, m, emptyValue), + Arguments.of(Mutable.class, "vempty", EmptyValue.class, m, emptyValue) + ); + }; + + @ParameterizedTest + @MethodSource("getterCases") + public void testGetter(Class type, String name, Class ftype, Object obj, Object expected) throws Throwable { + var f = type.getDeclaredField(name); + assertTrue(f.getType() == ftype); + var o1 = f.get(obj); + assertTrue(expected == o1); + + var getter = MethodHandles.lookup().findGetter(type, name, ftype); + var o2 = getter.invoke(obj); + assertTrue(expected == o2); + + var vh = MethodHandles.lookup().findVarHandle(type, name, ftype); + var o3 = vh.get(obj); + assertTrue(expected == o3); + } + + static Stream setterCases() { + EmptyValue emptyValue = new EmptyValue(); + Mutable m = new Mutable(); + return Stream.of( + Arguments.of(Mutable.class, "o", EmptyValue.class, m, null), + Arguments.of(Mutable.class, "o", EmptyValue.class, m, emptyValue), + Arguments.of(Mutable.class, "empty", EmptyValue.class, m, emptyValue), + Arguments.of(Mutable.class, "vempty", EmptyValue.class, m, emptyValue) + ); + }; + + @ParameterizedTest + @MethodSource("setterCases") + public void testSetter(Class type, String name, Class ftype, Object obj, Object expected) throws Throwable { + var f = type.getDeclaredField(name); + assertTrue(f.getType() == ftype); + f.set(obj, expected); + assertTrue(f.get(obj) == expected); + + var setter = MethodHandles.lookup().findSetter(type, name, ftype); + setter.invoke(obj, expected); + assertTrue(f.get(obj) == expected); + } + + @Test + public void noWriteAccess() throws ReflectiveOperationException { + Value v = new Value(); + Field f = v.getClass().getDeclaredField("o"); + assertThrows(IllegalAccessException.class, () -> f.set(v, null)); + } + + static Stream nullRestrictedFields() { + Mutable m = new Mutable(); + return Stream.of( + Arguments.of(Mutable.class, "o", EmptyValue.class, m, false), + Arguments.of(Mutable.class, "empty", EmptyValue.class, m, true), + Arguments.of(Mutable.class, "vempty", EmptyValue.class, m, true) + ); + }; + + @ParameterizedTest + @MethodSource("nullRestrictedFields") + public void testNullRestrictedField(Class type, String name, Class ftype, Object obj, boolean nullRestricted) throws Throwable { + var f = type.getDeclaredField(name); + assertTrue(f.getType() == ftype); + if (nullRestricted) { + assertThrows(NullPointerException.class, () -> f.set(obj, null)); + } else { + f.set(obj, null); + assertTrue(f.get(obj) == null); + } + + var mh = MethodHandles.lookup().findSetter(type, name, ftype); + if (nullRestricted) { + assertThrows(NullPointerException.class, () -> mh.invoke(obj, null)); + } else { + mh.invoke(obj, null); + assertTrue(f.get(obj) == null); + } + + var vh = MethodHandles.lookup().findVarHandle(type, name, ftype); + if (nullRestricted) { + assertThrows(NullPointerException.class, () -> vh.set(obj, null)); + } else { + vh.set(obj, null); + assertTrue(f.get(obj) == null); + } + } +} diff --git a/test/jdk/valhalla/valuetypes/ObjectMethods.java b/test/jdk/valhalla/valuetypes/ObjectMethods.java new file mode 100644 index 00000000000..45a746bbf1e --- /dev/null +++ b/test/jdk/valhalla/valuetypes/ObjectMethods.java @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +/* + * @test + * @summary test Object methods on value classes + * @enablePreview + * @run junit/othervm -Dvalue.bsm.salt=1 -XX:-UseAtomicValueFlattening ObjectMethods + * @run junit/othervm -Dvalue.bsm.salt=1 -XX:-UseFieldFlattening ObjectMethods + */ +import java.util.Optional; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Stream; +import java.lang.reflect.AccessFlag; +import java.lang.reflect.Modifier; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.*; + +public class ObjectMethods { + static value class Point { + public int x; + public int y; + Point(int x, int y) { + this.x = x; + this.y = y; + } + } + + static value class Line { + @NullRestricted @Strict + Point p1; + @NullRestricted @Strict + Point p2; + + Line(int x1, int y1, int x2, int y2) { + this.p1 = new Point(x1, y1); + this.p2 = new Point(x2, y2); + } + } + + static class Ref { + @NullRestricted @Strict + Point p; + Line l; + Ref(Point p, Line l) { + this.p = p; + this.l = l; + } + } + + static value class Value { + @NullRestricted @Strict + Point p; + @NullRestricted @Strict + Line l; + Ref r; + String s; + Value(Point p, Line l, Ref r, String s) { + this.p = p; + this.l = l; + this.r = r; + this.s = s; + } + } + + static value class ValueOptional { + private Object o; + public ValueOptional(Object o) { + this.o = o; + } + } + + static value record ValueRecord(int i, String name) {} + + static final int SALT = 1; + static final Point P1 = new Point(1, 2); + static final Point P2 = new Point(30, 40); + static final Line L1 = new Line(1, 2, 3, 4); + static final Line L2 = new Line(10, 20, 3, 4); + static final Ref R1 = new Ref(P1, L1); + static final Ref R2 = new Ref(P2, null); + static final Value V = new Value(P1, L1, R1, "value"); + + // Instances to test, classes of each instance are tested too + static Stream identitiesData() { + Function lambda1 = (a) -> "xyz"; + return Stream.of( + Arguments.of(lambda1, true, false), // a lambda (Identity for now) + Arguments.of(new Object(), true, false), // java.lang.Object + Arguments.of("String", true, false), + Arguments.of(L1, false, true), + Arguments.of(V, false, true), + Arguments.of(new ValueRecord(1, "B"), false, true), + Arguments.of(new int[0], true, false), // arrays of primitive type are identity objects + Arguments.of(new Object[0], true, false), // arrays of identity classes are identity objects + Arguments.of(new String[0], true, false), // arrays of identity classes are identity objects + Arguments.of(new Value[0], true, false) // arrays of value classes are identity objects + ); + } + + // Classes to test + static Stream classesData() { + return Stream.of( + Arguments.of(int.class, false, true), // Fabricated primitive classes + Arguments.of(long.class, false, true), + Arguments.of(short.class, false, true), + Arguments.of(byte.class, false, true), + Arguments.of(float.class, false, true), + Arguments.of(double.class, false, true), + Arguments.of(char.class, false, true), + Arguments.of(void.class, false, true), + Arguments.of(String.class, true, false), + Arguments.of(Object.class, true, false), + Arguments.of(Function.class, false, true), // Interface + Arguments.of(Optional.class, false, true), // Concrete value classes... + Arguments.of(Character.class, false, true) + ); + } + + @ParameterizedTest + @MethodSource("identitiesData") + public void identityTests(Object obj, boolean identityClass, boolean valueClass) { + Class clazz = obj.getClass(); + assertEquals(identityClass, Objects.hasIdentity(obj), "Objects.hasIdentity(" + obj + ")"); + + // Run tests on the class + classTests(clazz, identityClass, valueClass); + } + + @ParameterizedTest + @MethodSource("classesData") + public void classTests(Class clazz, boolean identityClass, boolean valueClass) { + assertEquals(identityClass, clazz.isIdentity(), "Class.isIdentity(): " + clazz); + + assertEquals(valueClass, clazz.isValue(), "Class.isValue(): " + clazz); + + assertEquals(clazz.accessFlags().contains(AccessFlag.IDENTITY), + identityClass, "AccessFlag.IDENTITY: " + clazz); + + int modifiers = clazz.getModifiers(); + assertEquals(clazz.isIdentity(), (modifiers & Modifier.IDENTITY) != 0, "Class.getModifiers() & IDENTITY != 0"); + assertEquals(clazz.isValue(), (modifiers & Modifier.IDENTITY) == 0, "Class.getModifiers() & IDENTITY == 0"); + } + + @Test + public void identityTestNull() { + assertFalse(Objects.hasIdentity(null), "Objects.hasIdentity(null)"); + assertFalse(Objects.isValueObject(null), "Objects.isValueObject(null)"); + } + + static Stream equalsTests() { + return Stream.of( + Arguments.of(P1, P1, true), + Arguments.of(P1, new Point(1, 2), true), + Arguments.of(P1, P2, false), + Arguments.of(P1, L1, false), + Arguments.of(L1, new Line(1, 2, 3, 4), true), + Arguments.of(L1, L2, false), + Arguments.of(L1, L1, true), + Arguments.of(V, new Value(P1, L1, R1, "value"), true), + Arguments.of(V, new Value(new Point(1, 2), new Line(1, 2, 3, 4), R1, "value"), true), + Arguments.of(V, new Value(P1, L1, new Ref(P1, L1), "value"), false), + Arguments.of(new Value(P1, L1, R2, "value2"), new Value(P1, L1, new Ref(P2, null), "value2"), false), + Arguments.of(new ValueRecord(50, "fifty"), new ValueRecord(50, "fifty"), true), + + // reference classes containing fields of value class + Arguments.of(R1, new Ref(P1, L1), false), // identity object + + // uninitialized default value + Arguments.of(new ValueOptional(L1), new ValueOptional(L1), true), + Arguments.of(new ValueOptional(List.of(P1)), new ValueOptional(List.of(P1)), false) + ); + } + + @ParameterizedTest + @MethodSource("equalsTests") + public void testEquals(Object o1, Object o2, boolean expected) { + assertTrue(o1.equals(o2) == expected); + } + + static Stream toStringTests() { + return Stream.of( + Arguments.of(new Point(100, 200)), + Arguments.of(new Line(1, 2, 3, 4)), + Arguments.of(V), + Arguments.of(R1), + // enclosing instance field `this$0` should be filtered + Arguments.of(new Value(P1, L1, null, null)), + Arguments.of(new Value(P2, L2, new Ref(P1, null), "value")), + Arguments.of(new ValueOptional(P1)) + ); + } + + @ParameterizedTest + @MethodSource("toStringTests") + public void testToString(Object o) { + String expected = String.format("%s@%s", o.getClass().getName(), Integer.toHexString(o.hashCode())); + assertEquals(o.toString(), expected); + } + + @Test + public void testValueRecordToString() { + ValueRecord o = new ValueRecord(30, "thirty"); + assertEquals(o.toString(), "ValueRecord[i=30, name=thirty]"); + } + + static Stream hashcodeTests() { + Point p = new Point(0, 0); + Line l = new Line(0, 0, 0, 0); + // this is sensitive to the order of the returned fields from Class::getDeclaredFields + return Stream.of( + Arguments.of(P1, hash(Point.class, 1, 2)), + Arguments.of(L1, hash(Line.class, new Point(1, 2), new Point(3, 4))), + Arguments.of(V, hash(Value.class, P1, L1, V.r, V.s)), + Arguments.of(new Point(0, 0), hash(Point.class, 0, 0)), + Arguments.of(p, hash(Point.class, 0, 0)), + Arguments.of(new ValueOptional(P1), hash(ValueOptional.class, P1)) + ); + } + + @ParameterizedTest + @MethodSource("hashcodeTests") + public void testHashCode(Object o, int hash) { + assertEquals(o.hashCode(), hash); + assertEquals(System.identityHashCode(o), hash); + } + + private static int hash(Object... values) { + int hc = SALT; + for (Object o : values) { + hc = 31 * hc + (o != null ? o.hashCode() : 0); + } + return hc; + } + + interface Number { + int value(); + } + + static class ReferenceType implements Number { + int i; + public ReferenceType(int i) { + this.i = i; + } + public int value() { + return i; + } + @Override + public boolean equals(Object o) { + if (o != null && o instanceof Number) { + return this.value() == ((Number)o).value(); + } + return false; + } + } + + static value class ValueType1 implements Number { + int i; + public ValueType1(int i) { + this.i = i; + } + public int value() { + return i; + } + } + + static value class ValueType2 implements Number { + int i; + public ValueType2(int i) { + this.i = i; + } + public int value() { + return i; + } + @Override + public boolean equals(Object o) { + if (o != null && o instanceof Number) { + return this.value() == ((Number)o).value(); + } + return false; + } + } + + static Stream interfaceEqualsTests() { + return Stream.of( + Arguments.of(new ReferenceType(10), new ReferenceType(10), false, true), + Arguments.of(new ValueType1(10), new ValueType1(10), true, true), + Arguments.of(new ValueType2(10), new ValueType2(10), true, true), + Arguments.of(new ValueType1(20), new ValueType2(20), false, false), + Arguments.of(new ValueType2(20), new ValueType1(20), false, true), + Arguments.of(new ReferenceType(30), new ValueType1(30), false, true), + Arguments.of(new ReferenceType(30), new ValueType2(30), false, true) + ); + } + + @ParameterizedTest + @MethodSource("interfaceEqualsTests") + public void testNumber(Number n1, Number n2, boolean isSubstitutable, boolean isEquals) { + assertTrue((n1 == n2) == isSubstitutable); + assertTrue(n1.equals(n2) == isEquals); + } +} diff --git a/test/jdk/valhalla/valuetypes/ObjectMethodsViaCondy.java b/test/jdk/valhalla/valuetypes/ObjectMethodsViaCondy.java new file mode 100644 index 00000000000..5855b9b2d0f --- /dev/null +++ b/test/jdk/valhalla/valuetypes/ObjectMethodsViaCondy.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Test ObjectMethods::bootstrap call via condy + * @modules java.base/jdk.internal.value + * @enablePreview + * @run testng/othervm ObjectMethodsViaCondy + */ + +import java.io.IOException; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.lang.classfile.ClassFile; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDesc; +import java.lang.constant.DirectMethodHandleDesc; +import java.lang.constant.DynamicConstantDesc; +import java.lang.constant.MethodHandleDesc; +import java.lang.constant.MethodTypeDesc; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup.ClassOption; +import java.lang.invoke.MethodType; +import java.lang.runtime.ObjectMethods; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Stream; + +import org.testng.annotations.Test; + +import static java.lang.classfile.ClassFile.*; +import static java.lang.constant.ConstantDescs.*; +import static java.lang.invoke.MethodType.methodType; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.assertFalse; + +public class ObjectMethodsViaCondy { + public static value record ValueRecord(int i, String name) { + static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + static final MethodType TO_STRING_DESC = methodType(String.class, ValueRecord.class); + static final String NAME_LIST = "i;name"; + private static final ClassDesc CD_ValueRecord = ValueRecord.class.describeConstable().orElseThrow(); + private static final ClassDesc CD_ObjectMethods = ObjectMethods.class.describeConstable().orElseThrow(); + private static final MethodTypeDesc MTD_ObjectMethods_bootstrap = MethodTypeDesc.of(CD_Object, CD_MethodHandles_Lookup, CD_String, + ClassDesc.ofInternalName("java/lang/invoke/TypeDescriptor"), CD_Class, CD_String, CD_MethodHandle.arrayType()); + static final List ACCESSORS = accessors(); + + private static List accessors() { + try { + return List.of( + MethodHandleDesc.ofField(DirectMethodHandleDesc.Kind.GETTER, CD_ValueRecord, "i", CD_int), + MethodHandleDesc.ofField(DirectMethodHandleDesc.Kind.GETTER, CD_ValueRecord, "name", CD_String) + ); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + /** + * Returns the method handle for the given method for this ValueRecord class. + * This method defines a hidden class to invoke the ObjectMethods::bootstrap method + * via condy. + * + * @param methodName the name of the method to generate, which must be one of + * {@code "equals"}, {@code "hashCode"}, or {@code "toString"} + */ + static MethodHandle makeBootstrapMethod(String methodName) throws Throwable { + String className = "Test-" + methodName; + ClassDesc testClass = ClassDesc.of(className); + byte[] bytes = ClassFile.of().build(testClass, clb -> clb + .withVersion(JAVA_19_VERSION, 0) + .withFlags(ACC_FINAL | ACC_SUPER) + .withMethodBody(INIT_NAME, MTD_void, ACC_PUBLIC, cob -> cob + .aload(0) + .invokespecial(CD_Object, INIT_NAME, MTD_void) + .return_()) + .withMethodBody("bootstrap", MethodTypeDesc.of(CD_Object), ACC_PUBLIC | ACC_STATIC, cob -> cob + .loadConstant(DynamicConstantDesc.ofNamed( + MethodHandleDesc.ofMethod(DirectMethodHandleDesc.Kind.STATIC, CD_ObjectMethods, + "bootstrap", MTD_ObjectMethods_bootstrap), + methodName, + CD_MethodHandle, + Stream.concat(Stream.of(CD_ValueRecord, NAME_LIST), ACCESSORS.stream()).toArray(ConstantDesc[]::new))) + .areturn()) + ); + + Path p = Paths.get(className + ".class"); + try (OutputStream os = Files.newOutputStream(p)) { + os.write(bytes); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + MethodHandles.Lookup lookup = LOOKUP.defineHiddenClass(bytes, true, ClassOption.NESTMATE); + MethodType mtype = MethodType.methodType(Object.class); + MethodHandle mh = lookup.findStatic(lookup.lookupClass(), "bootstrap", mtype); + return (MethodHandle) mh.invoke(); + } + } + + @Test + public void testToString() throws Throwable { + MethodHandle handle = ValueRecord.makeBootstrapMethod("toString"); + assertEquals((String)handle.invokeExact(new ValueRecord(10, "ten")), "ValueRecord[i=10, name=ten]"); + assertEquals((String)handle.invokeExact(new ValueRecord(40, "forty")), "ValueRecord[i=40, name=forty]"); + } + + @Test + public void testToEquals() throws Throwable { + MethodHandle handle = ValueRecord.makeBootstrapMethod("equals"); + assertTrue((boolean)handle.invoke(new ValueRecord(10, "ten"), new ValueRecord(10, "ten"))); + assertFalse((boolean)handle.invoke(new ValueRecord(11, "eleven"), new ValueRecord(10, "ten"))); + } +} diff --git a/test/jdk/valhalla/valuetypes/ObjectNewInstance.java b/test/jdk/valhalla/valuetypes/ObjectNewInstance.java new file mode 100644 index 00000000000..d9b91003f45 --- /dev/null +++ b/test/jdk/valhalla/valuetypes/ObjectNewInstance.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Method handle and core reflection to invoke on the constructor of + * java.lang.Object (abstract class) should return an Identity instance + * @enablePreview + * @run testng/othervm ObjectNewInstance + */ + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Constructor; + +import org.testng.annotations.Test; +import static org.testng.Assert.*; + +public class ObjectNewInstance { + @Test + public void classNewInstance() throws ReflectiveOperationException { + Object o = Object.class.newInstance(); + assertTrue(o.getClass() == Object.class); + } + + @Test + public void constructorNewInstance() throws ReflectiveOperationException { + Constructor ctor = Object.class.getDeclaredConstructor(); + Object o = ctor.newInstance(); + assertTrue(o.getClass() == Object.class); + } + + @Test + public void methodHandle() throws Throwable { + MethodHandle mh = MethodHandles.publicLookup() + .findConstructor(Object.class, MethodType.methodType(void.class)); + Object o = mh.invokeExact(); + assertTrue(o.getClass() == Object.class); + } +} diff --git a/test/jdk/valhalla/valuetypes/ProxyTest.java b/test/jdk/valhalla/valuetypes/ProxyTest.java new file mode 100644 index 00000000000..f232445badc --- /dev/null +++ b/test/jdk/valhalla/valuetypes/ProxyTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Test dynamic proxies with parameter types or return type of value class + * @enablePreview + * @run testng/othervm ProxyTest + */ + +import java.lang.reflect.*; +import java.util.Arrays; + +import org.testng.annotations.Test; +import static org.testng.Assert.*; + +public class ProxyTest { + static value class V { + int v; + V(int v) { + this.v = v; + } + } + + static value class P { + int p; + P(int p) { + this.p = p; + } + } + + interface I { + int getV(V v); + int getP(P p); + } + + interface J { + int[] getV(V[] v); + } + + @Test + public void testProxy() throws Exception { + InvocationHandler handler = new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + switch (method.getName()) { + case "getV": + V v = (V)args[0]; + return v.v; + case "getP": + P p = (P)args[0]; + return p.p; + default: + throw new UnsupportedOperationException(method.toString()); + } + } + }; + + Class[] intfs = new Class[] { I.class }; + I i = (I) Proxy.newProxyInstance(ProxyTest.class.getClassLoader(), intfs, handler); + + assertTrue(i.getV(new V(100)) == 100); + assertTrue(i.getP(new P(200)) == 200); + } + + @Test + public void testValueArrayType() { + InvocationHandler handler = new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + switch (method.getName()) { + case "getV": + V[] vs = (V[])args[0]; + return Arrays.stream(vs).mapToInt(v -> v.v).toArray(); + default: + throw new UnsupportedOperationException(method.toString()); + } + } + }; + + Class[] intfs = new Class[] { J.class }; + J j = (J) Proxy.newProxyInstance(ProxyTest.class.getClassLoader(), intfs, handler); + + V[] array = new V[] { new V(10), new V(20), new V(30)}; + assertEquals(j.getV(array), new int[] { 10, 20, 30}); + } +} diff --git a/test/jdk/valhalla/valuetypes/RecursiveValueClass.java b/test/jdk/valhalla/valuetypes/RecursiveValueClass.java new file mode 100644 index 00000000000..e12084776f9 --- /dev/null +++ b/test/jdk/valhalla/valuetypes/RecursiveValueClass.java @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @enablePreview + * @run junit/othervm -Xint -Djdk.value.recursion.threshold=100000 RecursiveValueClass + */ + +/* + * @ignore 8296056 + * @enablePreview + * @test + * @run junit/othervm -XX:TieredStopAtLevel=1 -Djdk.value.recursion.threshold=100000 RecursiveValueClass + */ + +/* + * @ignore 8296056 + * @enablePreview + * @test + * @run junit/othervm -Xcomp -Djdk.value.recursion.threshold=100000 RecursiveValueClass + */ + +import jdk.internal.value.ValueClass; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.Arguments; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +public class RecursiveValueClass { + static value class Node { + Node left; + Node right; + + Node(Node l, Node r) { + this.left = l; + this.right = r; + } + } + + static value class P { + Node node; + V v; + P(Node node, V v) { + this.node = node; + this.v = v; + } + } + + static value class V { + P p; + V(P p) { + this.p = p; + } + } + + static value class A { + B b; + E e; + A(B b, E e) { + this.b = b; + this.e = e; + } + } + + static value class B { + C c; + D d; + B(C c, D d) { + this.c = c; + this.d = d; + } + } + + static value class C { + A a; + C(A a) { + this.a = a; + } + } + + static value class D { + int x; + D(int x) { + this.x = x; + } + } + + static value class E { + F f; + E(F f) { + this.f = f; + } + } + + static value class F { + E e; + F(E e) { + this.e = e; + } + } + + static Stream objectsProvider() { + var n1 = new Node(null, null); + var n2 = new Node(n1, null); + var n3 = new Node(n2, n1); + var n4 = new Node(n2, n1); + var v1 = new V(null); + var p1 = new P(n3, v1); + var p2 = new P(n4, v1); + var v2 = new V(p1); + var v3 = new V(p2); + var p3 = new P(n3, v2); + + var e1 = new E(new F(null)); + var f1 = new F(e1); + var e2 = new E(f1); + var f2 = new F(e2); + + var a = new A(new B(null, null), new E(null)); + + var d1 = new D(1); + var d2 = new D(2); + var c1 = new C(a); + var c2 = new C(a); + + var b1 = new B(c1, d1); + var b2 = new B(c1, d2); + var b3 = new B(c2, d2); + var a1 = new A(b1, e1); + var a2 = new A(b2, e2); + + return Stream.of( + // Node -> Node left & right + Arguments.of(n1, n1, true), + Arguments.of(n1, n2, false), + Arguments.of(n2, n3, false), + Arguments.of(n3, n4, true), + Arguments.of(null, n4, false), + Arguments.of(null, null, true), + Arguments.of(n1, "foo", false), + + // value class P -> value class V -> P + Arguments.of(p1, p2, true), + Arguments.of(p1, p3, false), + Arguments.of(p3, p3, true), + Arguments.of(v2, v3, true), + + // E -> F -> E + Arguments.of(e1, e2, false), + Arguments.of(e1, e1, true), + Arguments.of(f1, f2, false), + Arguments.of(f2, f2, true), + + // two cyclic memberships from A + // A -> B -> C -> A and E -> F -> E + Arguments.of(a1, a2, false), + Arguments.of(a2, a2, true), + Arguments.of(b1, b2, false), + Arguments.of(b2, b3, true), + Arguments.of(c1, c2, true) + ); + } + + @ParameterizedTest + @MethodSource("objectsProvider") + public void testAcmp(Object o1, Object o2, boolean expected) { + var value = o1 == o2; + assertEquals(expected, value, o1 + " == " + o2); + } + + static Stream hashCodeProvider() { + var n1 = new Node(null, null); + var n2 = new Node(n1, null); + var n3 = new Node(n2, n1); + var v1 = new V(null); + var p1 = new P(n3, v1); + var v2 = new V(p1); + + var e1 = new E(new F(null)); + var f1 = new F(e1); + var e2 = new E(f1); + var f2 = new F(e2); + + var a = new A(new B(null, null), new E(null)); + + var d1 = new D(1); + var d2 = new D(2); + var c1 = new C(a); + + var b1 = new B(c1, d1); + var b2 = new B(c1, d2); + var a1 = new A(b1, e1); + var a2 = new A(b2, e2); + + return Stream.of( + // Node -> Node left & right + Arguments.of(n1), + Arguments.of(n2), + Arguments.of(n3), + + // value class P -> value class V -> P + Arguments.of(p1), + Arguments.of(v2), + + // E -> F -> E + Arguments.of(e1), + Arguments.of(e2), + Arguments.of(f1), + Arguments.of(f2), + + // two cyclic memberships from A + // A -> B -> C -> A and E -> F -> E + Arguments.of(a1), + Arguments.of(a2), + Arguments.of(b1), + Arguments.of(b2), + Arguments.of(c1) + ); + } + + @ParameterizedTest + @MethodSource("hashCodeProvider") + public void testHashCode(Object o) { + int hc = o.hashCode(); + assertEquals(System.identityHashCode(o), hc, o.toString()); + } + + static value class N { + N l; + N r; + int id; + N(N l, N r, int id) { + this.l = l; + this.r = r; + this.id = id; + } + } + + private static N build() { + N n1 = new N(null, null, 0); + N n2 = new N(null, null, 0); + for (int id = 1; id < 100; ++id) { + N l = new N(n1, n2, id); + N r = new N(n1, n2, id); + n1 = l; + n2 = r; + } + return new N(n1, n2, 100); + } + + /* + * Throw SOE for large graph + */ + @Test + public void largeGraph() { + N node = build(); + long start = System.nanoTime(); + assertThrows(StackOverflowError.class, () -> { boolean v = node.l == node.r; }); + assertThrows(StackOverflowError.class, () -> { int hc = node.hashCode(); }); + System.out.format("testing large graph: %d ms%n", (System.nanoTime() - start) / 1000); + } +} diff --git a/test/jdk/valhalla/valuetypes/Reflection.java b/test/jdk/valhalla/valuetypes/Reflection.java new file mode 100644 index 00000000000..e1afbc88be6 --- /dev/null +++ b/test/jdk/valhalla/valuetypes/Reflection.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +/* + * @test + * @summary test core reflection on value classes + * @enablePreview + * @run junit/othervm Reflection + */ + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.stream.Stream; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.*; + +public class Reflection { + @LooselyConsistentValue + static value class V { + int x; + V(int x) { + this.x = x; + } + } + + @LooselyConsistentValue + static value class Value { + @NullRestricted @Strict + V v1; + V v2; + Value(V v1, V v2) { + this.v1 = v1; + this.v2 = v2; + } + + static Value newValue(V v1, V v2) { + return new Value(v1, v2); + } + } + + @Test + void testNewInstance() throws Exception { + V v = new V(10); + Constructor ctor = Value.class.getDeclaredConstructor(V.class, V.class); + Value o = ctor.newInstance(v, v); + assertEquals(o.getClass(), Value.class); + } + + @Test + void testAccess() throws Exception { + Field field = Value.class.getDeclaredField("v1"); + V v = new V(10); + Value o = new Value(v, null); + assertEquals(v, field.get(o)); + + // accessible but no write access + assertTrue(field.trySetAccessible()); + assertTrue(field.isAccessible()); + assertThrows(IllegalAccessException.class, () -> field.set(o, v)); + } + + @Test + void testNullRestricted() throws Exception { + Method m = Value.class.getDeclaredMethod("newValue", V.class, V.class); + Throwable t = assertThrows(InvocationTargetException.class, () -> m.invoke(null, new Object[] {null, null})); + assertEquals(NullPointerException.class, t.getCause().getClass()); + } + + static Stream arrays() { + V v1 = new V(10); + V v2 = new V(20); + Value value = new Value(v1, v2); + + V[] varray = (V[]) Array.newInstance(V.class, 2); + V[] varrayNR = (V[]) ValueClass.newNullRestrictedAtomicArray(V.class, 3, new V(0)); + Value[] valuearray = (Value[]) Array.newInstance(Value.class, 2); + Value[] valuearrayNR = (Value[]) ValueClass.newNullRestrictedNonAtomicArray(Value.class, 3, new Value(new V(0), new V(0))); + + return Stream.of( + Arguments.of(V[].class, varray, false, v1), + Arguments.of(V[].class, varrayNR, true, v2), + Arguments.of(Value[].class, valuearray, false, value), + Arguments.of(Value[].class, valuearrayNR, true, value) + + ); + } + + /** + * Setting the elements of an array. + * NPE will be thrown if null is set on an element in a null-restricted value class array + */ + @ParameterizedTest + @MethodSource("arrays") + public void testArrays(Class arrayClass, Object[] array, boolean nullRestricted, Object element) { + Class componentType = arrayClass.getComponentType(); + assertTrue(arrayClass.isArray()); + // TODO: check Array.getComponentType(array) instead + assertTrue(array.getClass() == arrayClass || nullRestricted); + assertTrue(array.getClass().getComponentType() == componentType || nullRestricted); + + for (int i = 0; i < array.length; i++) { + Object o = Array.get(array, i); + if (nullRestricted) { + assertTrue(o != null); + } else { + assertTrue(o == null); + } + } + + // set elements + for (int i = 0; i < array.length; i++) { + Array.set(array, i, element); + assertTrue(Array.get(array, i) == element); + } + + Arrays.setAll(array, i -> array[i]); + + // test nullable + for (int i = 0; i < array.length; i++) { + if (nullRestricted) { + final int index = i; + assertThrows(NullPointerException.class, () -> Array.set(array, index, null)); + } else { + Array.set(array, i, null); + } + } + } +} diff --git a/test/jdk/valhalla/valuetypes/StreamTest.java b/test/jdk/valhalla/valuetypes/StreamTest.java new file mode 100644 index 00000000000..7720ab6d2f9 --- /dev/null +++ b/test/jdk/valhalla/valuetypes/StreamTest.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Basic stream tests to iterate on nullable and null-restricted values + * @enablePreview + * @run junit/othervm StreamTest + */ + +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class StreamTest { + + static value class X { + int x; + X(int x) { + this.x = x; + } + int x() { + return x; + } + } + + static value class Point { + public int x; + public int y; + Point(int x, int y) { + this.x = x; + this.y = y; + } + } + + static value class Value { + int i; + @NullRestricted @Strict + Point p; + Point nullable; + List list; + Value(int i, Point/* Point! */ p, Point np, List list) { + this.i = i; + this.p = p; + this.nullable = np; + this.list = list; + } + + Point point() { + return p; + } + + Point nullablePoint() { + return nullable; + } + + int getI() { return i; } + + List list() { return list; } + } + + final Value[] values = init(); + private Value[] init() { + Value[] values = new Value[10]; + for (int i = 0; i < 10; i++) { + values[i] = new Value(i, + new Point(i,i*2), + (i%2) == 0 ? null : new Point(i*10, i*20), + List.of(new X(i), new X(i*10))); + } + return values; + } + + @Test + public void testValues() { + Arrays.stream(values) + .filter(v -> (v.i % 2) == 0) + .forEach(System.out::println); + } + + @Test + public void testNullRestrictedType() { + Arrays.stream(values) + .map(Value::point) + .filter(p -> p.x >= 5) + .forEach(System.out::println); + + } + + @Test + public void testNullableValueType() { + Arrays.stream(values) + .map(Value::nullablePoint) + .filter(p -> p != null) + .forEach(System.out::println); + } + + @Test + public void mapToInt() { + Stream stream = Arrays.stream(values) + .filter(v -> (v.getI() % 2) == 0) + .map(Value::point); + stream.forEach(p -> assertTrue((p.x % 2) == 0)); + } + + @Test + public void testListOfValues() { + long count = Arrays.stream(values) + .map(Value::list) + .flatMap(List::stream) + .map(X::x) + .filter(x -> x >= 10) + .count(); + assertEquals(count, values.length-1); + } +} diff --git a/test/jdk/valhalla/valuetypes/SubstitutabilityTest.java b/test/jdk/valhalla/valuetypes/SubstitutabilityTest.java new file mode 100644 index 00000000000..03d6dbba82a --- /dev/null +++ b/test/jdk/valhalla/valuetypes/SubstitutabilityTest.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @modules java.base/java.lang.runtime:open + * java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @enablePreview + * @run junit/othervm SubstitutabilityTest + */ + +import java.lang.reflect.Method; +import java.util.stream.Stream; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.*; + +public class SubstitutabilityTest { + static value class Point { + public int x; + public int y; + Point(int x, int y) { + this.x = x; + this.y = y; + } + } + + static value class Line { + @NullRestricted @Strict + Point p1; + @NullRestricted @Strict + Point p2; + + Line(Point p1, Point p2) { + this.p1 = p1; + this.p2 = p2; + } + Line(int x1, int y1, int x2, int y2) { + this(new Point(x1, y1), new Point(x2, y2)); + } + } + + // contains null-reference and null-restricted fields + static value class MyValue { + MyValue2 v1; + @NullRestricted @Strict + MyValue2 v2; + public MyValue(MyValue2 v1, MyValue2 v2) { + this.v1 = v1; + this.v2 = v2; + } + } + + static value class MyValue2 { + static int cnt = 0; + int x; + MyValue2(int x) { + this.x = x; + } + } + + static value class MyFloat { + public static float NaN1 = Float.intBitsToFloat(0x7ff00001); + public static float NaN2 = Float.intBitsToFloat(0x7ff00002); + float x; + MyFloat(float x) { + this.x = x; + } + public String toString() { + return Float.toString(x); + } + } + + static value class MyDouble { + public static double NaN1 = Double.longBitsToDouble(0x7ff0000000000001L); + public static double NaN2 = Double.longBitsToDouble(0x7ff0000000000002L); + double x; + MyDouble(double x) { + this.x = x; + } + public String toString() { + return Double.toString(x); + } + } + + static Stream substitutableCases() { + Point p1 = new Point(10, 10); + Point p2 = new Point(20, 20); + Line l1 = new Line(p1, p2); + MyValue v1 = new MyValue(null, new MyValue2(0)); + MyValue v2 = new MyValue(new MyValue2(2), new MyValue2(3)); + MyValue2 value2 = new MyValue2(2); + MyValue2 value3 = new MyValue2(3); + MyValue[] va = new MyValue[1]; + return Stream.of( + Arguments.of(new MyFloat(1.0f), new MyFloat(1.0f)), + Arguments.of(new MyDouble(1.0), new MyDouble(1.0)), + Arguments.of(new MyFloat(Float.NaN), new MyFloat(Float.NaN)), + Arguments.of(new MyDouble(Double.NaN), new MyDouble(Double.NaN)), + Arguments.of(p1, new Point(10, 10)), + Arguments.of(p2, new Point(20, 20)), + Arguments.of(l1, new Line(10,10, 20,20)), + Arguments.of(v2, new MyValue(value2, value3)), + Arguments.of(va[0], null) + ); + } + + @ParameterizedTest + @MethodSource("substitutableCases") + public void substitutableTest(Object a, Object b) { + assertTrue(isSubstitutable(a, b)); + } + + static Stream notSubstitutableCases() { + return Stream.of( + Arguments.of(new MyFloat(1.0f), new MyFloat(2.0f)), + Arguments.of(new MyDouble(1.0), new MyDouble(2.0)), + Arguments.of(new MyFloat(MyFloat.NaN1), new MyFloat(MyFloat.NaN2)), + Arguments.of(new MyDouble(MyDouble.NaN1), new MyDouble(MyDouble.NaN2)), + Arguments.of(new Point(10, 10), new Point(20, 20)), + /* + * Verify ValueObjectMethods::isSubstitutable that does not + * throw an exception if any one of parameter is null or if + * the parameters are of different types. + */ + Arguments.of(new Point(10, 10), Integer.valueOf(10)), + Arguments.of(Integer.valueOf(10), Integer.valueOf(20)) + ); + } + + @ParameterizedTest + @MethodSource("notSubstitutableCases") + public void notSubstitutableTest(Object a, Object b) { + assertFalse(isSubstitutable(a, b)); + } + + @Test + public void nullArguments() { + assertTrue(isSubstitutable(null, null)); + } + + private static final Method IS_SUBSTITUTABLE; + static { + Method m = null; + try { + Class c = Class.forName("java.lang.runtime.ValueObjectMethods"); + m = c.getDeclaredMethod("isSubstitutable", Object.class, Object.class); + m.setAccessible(true); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + IS_SUBSTITUTABLE = m; + } + private static boolean isSubstitutable(Object a, Object b) { + try { + return (boolean) IS_SUBSTITUTABLE.invoke(null, a, b); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } +} diff --git a/test/jdk/valhalla/valuetypes/TEST.properties b/test/jdk/valhalla/valuetypes/TEST.properties new file mode 100644 index 00000000000..77d6e69db06 --- /dev/null +++ b/test/jdk/valhalla/valuetypes/TEST.properties @@ -0,0 +1,2 @@ +modules = java.base/jdk.internal.value \ + java.base/jdk.internal.vm.annotation diff --git a/test/jdk/valhalla/valuetypes/UseValueClassTest.java b/test/jdk/valhalla/valuetypes/UseValueClassTest.java new file mode 100644 index 00000000000..16b36f3c16e --- /dev/null +++ b/test/jdk/valhalla/valuetypes/UseValueClassTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.ParameterizedTest; + +import jdk.internal.misc.PreviewFeatures; + +import java.util.Arrays; +import java.util.stream.Stream; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.MonthDay; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; +import java.time.Period; +import java.time.Year; +import java.time.YearMonth; +import java.time.ZonedDateTime; + +/* + * @test + * @summary Test that classes are value classes or not depending on --enable-preview + * @modules java.base/jdk.internal.misc + * @run junit/othervm -Xlog --enable-preview UseValueClassTest + * @run junit/othervm -Xlog UseValueClassTest + */ + +public class UseValueClassTest { + + // Classes to be checked + private static Stream> classProvider() { + Class[] classes = { + Byte.class, + Short.class, + Integer.class, + Long.class, + Float.class, + Double.class, + Boolean.class, + Character.class, + Number.class, + Record.class, + Duration.class, + Instant.class, + LocalDate.class, + LocalDateTime.class, + LocalTime.class, + MonthDay.class, + OffsetDateTime.class, + OffsetTime.class, + Optional.class, + OptionalDouble.class, + OptionalInt.class, + OptionalLong.class, + Period.class, + Year.class, + YearMonth.class, + ZonedDateTime.class, + }; + return Arrays.stream(classes, 0, classes.length); + } + + /** + * Verify that the class is a value class if --enable-preview is true + * @param clazz a class + */ + @ParameterizedTest + @MethodSource("classProvider") + public void testValue(Class clazz) { + System.out.printf("isPreview: %s%n", PreviewFeatures.isEnabled()); + assertEquals(PreviewFeatures.isEnabled(), clazz.isValue(), clazz.getName()); + } +} diff --git a/test/jdk/valhalla/valuetypes/ValueClassTest.java b/test/jdk/valhalla/valuetypes/ValueClassTest.java new file mode 100644 index 00000000000..04529b69d49 --- /dev/null +++ b/test/jdk/valhalla/valuetypes/ValueClassTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Test jdk.internal.value.ValueClass + * @modules java.base/jdk.internal.misc + * java.base/jdk.internal.value + * @run junit ValueClassTest + * @run junit/othervm --enable-preview ValueClassTest + */ + +import java.util.ArrayList; + +import jdk.internal.misc.PreviewFeatures; +import jdk.internal.value.ValueClass; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ValueClassTest { + @Test + void testIsValueObjectCompatible() { + isValueObjectCompatibleCase(false, int.class, "primitive"); + isValueObjectCompatibleCase(true, Object.class, "Object"); + isValueObjectCompatibleCase(true, Number.class, "abstract value class"); + isValueObjectCompatibleCase(true, Integer.class, "final value class"); + isValueObjectCompatibleCase(false, ClassValue.class, "abstract identity class"); + isValueObjectCompatibleCase(false, ArrayList.class, "identity class"); + isValueObjectCompatibleCase(false, String.class, "final identity class"); + isValueObjectCompatibleCase(true, Comparable.class, "interface"); + isValueObjectCompatibleCase(false, int[].class, "array class"); + isValueObjectCompatibleCase(false, Object[].class, "array class"); + isValueObjectCompatibleCase(false, Number[].class, "array class"); + isValueObjectCompatibleCase(false, Integer[].class, "array class"); + isValueObjectCompatibleCase(false, ClassValue[].class, "array class"); + isValueObjectCompatibleCase(false, ArrayList[].class, "array class"); + isValueObjectCompatibleCase(false, String[].class, "array class"); + isValueObjectCompatibleCase(false, Comparable[].class, "array class"); + } + + private static void isValueObjectCompatibleCase(boolean expected, Class arg, String classification) { + assertEquals(PreviewFeatures.isEnabled() && expected, + ValueClass.isValueObjectCompatible(arg), + () -> classification + ": " + arg.getTypeName()); + } + + @Test + void testIsConcreteValueClass() { + isConcreteValueClassCase(false, int.class, "primitive"); + isConcreteValueClassCase(false, Object.class, "Object"); + isConcreteValueClassCase(false, Number.class, "abstract value class"); + isConcreteValueClassCase(true, Integer.class, "final value class"); + isConcreteValueClassCase(false, ClassValue.class, "abstract identity class"); + isConcreteValueClassCase(false, ArrayList.class, "identity class"); + isConcreteValueClassCase(false, String.class, "final identity class"); + isConcreteValueClassCase(false, Comparable.class, "interface"); + isConcreteValueClassCase(false, int[].class, "array class"); + isConcreteValueClassCase(false, Object[].class, "array class"); + isConcreteValueClassCase(false, Number[].class, "array class"); + isConcreteValueClassCase(false, Integer[].class, "array class"); + isConcreteValueClassCase(false, ClassValue[].class, "array class"); + isConcreteValueClassCase(false, ArrayList[].class, "array class"); + isConcreteValueClassCase(false, String[].class, "array class"); + isConcreteValueClassCase(false, Comparable[].class, "array class"); + } + + private static void isConcreteValueClassCase(boolean expected, Class arg, String classification) { + assertEquals(PreviewFeatures.isEnabled() && expected, + ValueClass.isConcreteValueClass(arg), + () -> classification + ": " + arg.getTypeName()); + } +} diff --git a/test/jdk/valhalla/valuetypes/ValueConstantDesc.java b/test/jdk/valhalla/valuetypes/ValueConstantDesc.java new file mode 100644 index 00000000000..7a8525eda15 --- /dev/null +++ b/test/jdk/valhalla/valuetypes/ValueConstantDesc.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +/* + * @test + * @summary Test ConstantDesc for value classes + * @enablePreview + * @run junit/othervm ValueConstantDesc + */ + +import java.lang.constant.ClassDesc; +import java.lang.invoke.MethodHandles; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.*; + +public class ValueConstantDesc { + static value class V { } + + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + static Stream testCases() { + return Stream.of( + Arguments.of(V.class, "LValueConstantDesc$V;", "ValueConstantDesc$V"), + Arguments.of(V[].class, "[LValueConstantDesc$V;", "ValueConstantDesc$V[]"), + Arguments.of(V[][].class, "[[LValueConstantDesc$V;", "ValueConstantDesc$V[][]") + ); + } + + @ParameterizedTest + @MethodSource("testCases") + public void classDesc(Class type, String descriptor, String displayName) throws ReflectiveOperationException { + ClassDesc cd = type.describeConstable().orElseThrow(); + ClassDesc desc = ClassDesc.ofDescriptor(descriptor); + + if (type.isArray()) { + assertTrue(cd.isArray()); + assertFalse(cd.isClassOrInterface()); + } else { + assertFalse(cd.isArray()); + assertTrue(cd.isClassOrInterface()); + } + assertEquals(cd, desc); + assertEquals(cd.displayName(), displayName); + assertEquals(cd.descriptorString(), type.descriptorString()); + + Class c = cd.resolveConstantDesc(LOOKUP); + assertTrue(c == type); + } +} diff --git a/test/jdk/valhalla/valuetypes/WeakReferenceTest.java b/test/jdk/valhalla/valuetypes/WeakReferenceTest.java new file mode 100644 index 00000000000..cb3ff0abd54 --- /dev/null +++ b/test/jdk/valhalla/valuetypes/WeakReferenceTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.lang.ref.WeakReference; +import java.lang.ref.ReferenceQueue; + +import org.testng.annotations.Test; +import static org.testng.Assert.*; + +/* + * @test + * @summary Test value classes with Reference types + * @enablePreview + * @run testng/othervm WeakReferenceTest + */ +@Test +public class WeakReferenceTest { + static value class Value { + } + + @Test(expectedExceptions = IdentityException.class) + static void test1() { + Value v = new Value(); + WeakReference r = new WeakReference<>(v); + } + + @Test(expectedExceptions = IdentityException.class) + static void test2() { + ReferenceQueue q = new ReferenceQueue<>(); + Value v = new Value(); + WeakReference r = new WeakReference<>(v, q); + } +} diff --git a/test/jtreg-ext/requires/VMProps.java b/test/jtreg-ext/requires/VMProps.java index 96c84115fe3..f55e957097d 100644 --- a/test/jtreg-ext/requires/VMProps.java +++ b/test/jtreg-ext/requires/VMProps.java @@ -381,6 +381,7 @@ protected void vmOptFinalFlags(SafeMap map) { vmOptFinalFlag(map, "CriticalJNINatives"); vmOptFinalFlag(map, "EnableJVMCI"); vmOptFinalFlag(map, "EliminateAllocations"); + vmOptFinalFlag(map, "TieredCompilation"); vmOptFinalFlag(map, "UnlockExperimentalVMOptions"); vmOptFinalFlag(map, "UseCompressedOops"); vmOptFinalFlag(map, "UseLargePages"); diff --git a/test/langtools/ProblemList.txt b/test/langtools/ProblemList.txt index 59b5b240a29..96f2b7e88da 100644 --- a/test/langtools/ProblemList.txt +++ b/test/langtools/ProblemList.txt @@ -63,13 +63,11 @@ tools/javac/annotations/typeAnnotations/referenceinfos/Lambda.java tools/javac/annotations/typeAnnotations/referenceinfos/NestedTypes.java 8057687 generic-all emit correct byte code an attributes for type annotations tools/javac/warnings/suppress/TypeAnnotations.java 8057683 generic-all improve ordering of errors with type annotations tools/javac/modules/SourceInSymlinkTest.java 8180263 windows-all fails when run on a subst drive - ########################################################################### # # javap tools/javap/output/RepeatingTypeAnnotations.java 8057687 generic-all emit correct byte code an attributes for type annotations - ########################################################################### # # jdeps diff --git a/test/langtools/TEST.ROOT b/test/langtools/TEST.ROOT index e3bd3b74bcf..9b1c5368e66 100644 --- a/test/langtools/TEST.ROOT +++ b/test/langtools/TEST.ROOT @@ -15,7 +15,7 @@ keys=intermittent randomness needs-src needs-src-jdk_javadoc groups=TEST.groups # Minimum jtreg version -requiredVersion=7.5.2+1 +requiredVersion=7.5.1+1 # Use new module options useNewOptions=true diff --git a/test/langtools/jdk/javadoc/doclet/testValueClasses/TestValueClasses.java b/test/langtools/jdk/javadoc/doclet/testValueClasses/TestValueClasses.java new file mode 100644 index 00000000000..3e188e42661 --- /dev/null +++ b/test/langtools/jdk/javadoc/doclet/testValueClasses/TestValueClasses.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8308590 + * @summary value classes + * @library /tools/lib ../../lib + * @modules jdk.javadoc/jdk.javadoc.internal.tool + * @build toolbox.ToolBox javadoc.tester.* + * @run main TestValueClasses + */ + +import java.io.IOException; +import java.nio.file.Path; + +import javadoc.tester.JavadocTester; +import toolbox.ToolBox; + +public class TestValueClasses extends JavadocTester { + + public static void main(String... args) throws Exception { + var tester = new TestValueClasses(); + tester.runTests(); + } + + private final ToolBox tb = new ToolBox(); + + @Test + public void testConcreteValueClass(Path base) throws IOException { + Path src = base.resolve("src"); + tb.writeJavaFiles(src, + "package p; public value class ValueClass {}"); + + javadoc("-d", base.resolve("out").toString(), + "--enable-preview", "-source", String.valueOf(Runtime.version().feature()), + "-sourcepath", src.toString(), + "p"); + checkExit(Exit.OK); + + checkOutput("p/ValueClass.html", true, + """ +
    public value final class ValueClass + """); + } + + @Test + public void testAbstractValueClass(Path base) throws IOException { + Path src = base.resolve("src"); + tb.writeJavaFiles(src, + "package p; public abstract value class ValueClass {}"); + + javadoc("-d", base.resolve("out").toString(), + "--enable-preview", "-source", String.valueOf(Runtime.version().feature()), + "-sourcepath", src.toString(), + "p"); + checkExit(Exit.OK); + + checkOutput("p/ValueClass.html", true, + """ +
    public abstract value class ValueClass + """); + } + + @Test + public void testValueRecord(Path base) throws IOException { + Path src = base.resolve("src"); + tb.writeJavaFiles(src, + "package p; public value record ValueRecord() {}"); + + javadoc("-d", base.resolve("out").toString(), + "--enable-preview", "-source", String.valueOf(Runtime.version().feature()), + "-sourcepath", src.toString(), + "p"); + checkExit(Exit.OK); + + checkOutput("p/ValueRecord.html", true, + """ +
    public value record ValueRecord() + """); + } +} diff --git a/test/langtools/jdk/jshell/Test8294583.java b/test/langtools/jdk/jshell/Test8294583.java index 3d2ce2e3638..544b8aab3be 100644 --- a/test/langtools/jdk/jshell/Test8294583.java +++ b/test/langtools/jdk/jshell/Test8294583.java @@ -27,6 +27,7 @@ * @summary JShell: NPE in switch with non existing record pattern * @build KullaTesting TestingInputStream * @run testng Test8294583 + * @ignore 8316628 */ import org.testng.annotations.Test; diff --git a/test/langtools/jdk/jshell/Test8296012.java b/test/langtools/jdk/jshell/Test8296012.java index 73e5cc06ae0..b8209f099ab 100644 --- a/test/langtools/jdk/jshell/Test8296012.java +++ b/test/langtools/jdk/jshell/Test8296012.java @@ -27,6 +27,7 @@ * @summary jshell crashes on mismatched record pattern * @build KullaTesting TestingInputStream * @run testng Test8296012 + * @ignore 8316628 */ import org.testng.annotations.Test; diff --git a/test/langtools/jdk/jshell/ToolEnablePreviewTest.java b/test/langtools/jdk/jshell/ToolEnablePreviewTest.java index 1825e7b39ad..8464ec85960 100644 --- a/test/langtools/jdk/jshell/ToolEnablePreviewTest.java +++ b/test/langtools/jdk/jshell/ToolEnablePreviewTest.java @@ -26,6 +26,7 @@ * @bug 8199193 * @summary Tests for the --enable-preview option * @run testng ToolEnablePreviewTest + * @ignore 8316628 */ import java.io.IOException; diff --git a/test/langtools/lib/combo/tools/javac/combo/CompilationTestCase.java b/test/langtools/lib/combo/tools/javac/combo/CompilationTestCase.java index 0760352e23b..c590ea710e2 100644 --- a/test/langtools/lib/combo/tools/javac/combo/CompilationTestCase.java +++ b/test/langtools/lib/combo/tools/javac/combo/CompilationTestCase.java @@ -112,6 +112,10 @@ protected void assertOKWithWarning(String warning, String... constructs) { assertCompile(expandMarkers(constructs), () -> assertCompileSucceededWithWarning(warning), false); } + protected void assertOKWithWarning(String warning, int numberOfTimes, String... constructs) { + assertCompile(expandMarkers(constructs), () -> assertCompileSucceededWithWarning(warning, numberOfTimes), false); + } + protected void assertFail(String expectedDiag, String... constructs) { assertCompile(expandMarkers(constructs), () -> assertCompileFailed(expectedDiag), false); } diff --git a/test/langtools/lib/combo/tools/javac/combo/Diagnostics.java b/test/langtools/lib/combo/tools/javac/combo/Diagnostics.java index 47f496b5891..dc7efc0b249 100644 --- a/test/langtools/lib/combo/tools/javac/combo/Diagnostics.java +++ b/test/langtools/lib/combo/tools/javac/combo/Diagnostics.java @@ -84,6 +84,12 @@ public boolean containsWarningKey(String key) { .anyMatch(d -> d.getCode().equals(key)); } + public boolean containsWarningKey(String key, int numberOfWarnings) { + return diags.stream() + .filter(d -> d.getKind() == Diagnostic.Kind.WARNING || d.getKind() == Diagnostic.Kind.MANDATORY_WARNING) + .filter(d -> d.getCode().equals(key)).count() == numberOfWarnings; + } + /** Get the error keys */ public List errorKeys() { return diags.stream() diff --git a/test/langtools/lib/combo/tools/javac/combo/JavacTemplateTestBase.java b/test/langtools/lib/combo/tools/javac/combo/JavacTemplateTestBase.java index 02c94b589c8..cce2d19c617 100644 --- a/test/langtools/lib/combo/tools/javac/combo/JavacTemplateTestBase.java +++ b/test/langtools/lib/combo/tools/javac/combo/JavacTemplateTestBase.java @@ -154,6 +154,14 @@ protected void assertCompileSucceededWithWarning(String warning) { } } + protected void assertCompileSucceededWithWarning(String warning, int numberOfWarnings) { + if (diags.errorsFound()) + fail("Expected successful compilation"); + if (!diags.containsWarningKey(warning, numberOfWarnings)) { + fail(String.format("Expected compilation warning with %s, found %s", warning, diags.keys())); + } + } + /** * If the provided boolean is true, assert all previous compiles succeeded, * otherwise assert that a compile failed. diff --git a/test/langtools/tools/javac/8278078/InvalidThisAndSuperInConstructorArgTest.out b/test/langtools/tools/javac/8278078/InvalidThisAndSuperInConstructorArgTest.out index 1109eb0fdc4..4fd6ebf1323 100644 --- a/test/langtools/tools/javac/8278078/InvalidThisAndSuperInConstructorArgTest.out +++ b/test/langtools/tools/javac/8278078/InvalidThisAndSuperInConstructorArgTest.out @@ -1,14 +1,14 @@ -InvalidThisAndSuperInConstructorArgTest.java:23:29: compiler.err.cant.ref.before.ctor.called: super -InvalidThisAndSuperInConstructorArgTest.java:26:28: compiler.err.cant.ref.before.ctor.called: super -InvalidThisAndSuperInConstructorArgTest.java:29:29: compiler.err.cant.ref.before.ctor.called: this -InvalidThisAndSuperInConstructorArgTest.java:32:28: compiler.err.cant.ref.before.ctor.called: this +InvalidThisAndSuperInConstructorArgTest.java:23:35: compiler.err.cant.ref.before.ctor.called: toString() +InvalidThisAndSuperInConstructorArgTest.java:26:34: compiler.err.cant.ref.before.ctor.called: toString() +InvalidThisAndSuperInConstructorArgTest.java:29:34: compiler.err.cant.ref.before.ctor.called: toString() +InvalidThisAndSuperInConstructorArgTest.java:32:33: compiler.err.cant.ref.before.ctor.called: toString() InvalidThisAndSuperInConstructorArgTest.java:35:33: compiler.err.not.encl.class: java.lang.AssertionError InvalidThisAndSuperInConstructorArgTest.java:38:32: compiler.err.not.encl.class: java.lang.AssertionError InvalidThisAndSuperInConstructorArgTest.java:41:33: compiler.err.not.encl.class: java.lang.AssertionError InvalidThisAndSuperInConstructorArgTest.java:44:32: compiler.err.not.encl.class: java.lang.AssertionError -InvalidThisAndSuperInConstructorArgTest.java:47:38: compiler.err.cant.ref.before.ctor.called: super +InvalidThisAndSuperInConstructorArgTest.java:47:44: compiler.err.cant.ref.before.ctor.called: get() InvalidThisAndSuperInConstructorArgTest.java:50:39: compiler.err.not.encl.class: InvalidThisAndSuperInConstructorArgTest.InterfaceWithDefault InvalidThisAndSuperInConstructorArgTest.java:53:38: compiler.err.not.encl.class: InvalidThisAndSuperInConstructorArgTest.InterfaceWithDefault -InvalidThisAndSuperInConstructorArgTest.java:56:39: compiler.err.cant.ref.before.ctor.called: super +InvalidThisAndSuperInConstructorArgTest.java:56:45: compiler.err.cant.ref.before.ctor.called: get() InvalidThisAndSuperInConstructorArgTest.java:59:28: compiler.err.cant.ref.before.ctor.called: this 13 errors diff --git a/test/langtools/tools/javac/AnonymousClass/AnonymousClassFlags.java b/test/langtools/tools/javac/AnonymousClass/AnonymousClassFlags.java index f60cb33798b..0fa82b0189c 100644 --- a/test/langtools/tools/javac/AnonymousClass/AnonymousClassFlags.java +++ b/test/langtools/tools/javac/AnonymousClass/AnonymousClassFlags.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,9 +22,20 @@ */ /* - * @test + * @test id=NoPreview * @bug 8161013 * @summary Verify that anonymous class binaries have the correct flags set + * @modules java.base/jdk.internal.misc + * @run main AnonymousClassFlags + */ + +/* + * @test id=Preview + * @bug 8161013 + * @summary Verify that anonymous class binaries have the correct flags set + * @modules java.base/jdk.internal.misc + * @enablePreview + * @compile -XDforcePreview AnonymousClassFlags.java * @run main AnonymousClassFlags */ @@ -36,23 +47,33 @@ import java.lang.classfile.attribute.InnerClassInfo; import java.lang.classfile.attribute.InnerClassesAttribute; +import jdk.internal.misc.PreviewFeatures; + public class AnonymousClassFlags { public static void main(String[] args) throws Exception { new AnonymousClassFlags().test(System.getProperty("test.classes", ".")); } + AnonymousClassFlags() { + System.currentTimeMillis(); + super(); // Triggers force preview + } + + // ACC_SUPER does not exist in InnerClasses before Value Objects + private static final int EXPECTED_ACCESS_FLAGS = PreviewFeatures.isEnabled() ? ClassFile.ACC_IDENTITY : 0; + /** Maps names of anonymous classes to their expected inner_class_access_flags */ private static Map anonClasses = new LinkedHashMap<>(); // ******* TEST CASES ******** static Object o1 = new Object() { - { anonClasses.put(getClass().getName(), 0); } + { anonClasses.put(getClass().getName(), EXPECTED_ACCESS_FLAGS); } }; static void staticMethod() { Object o2 = new Object() { - { anonClasses.put(getClass().getName(), 0); } + { anonClasses.put(getClass().getName(), EXPECTED_ACCESS_FLAGS); } }; } @@ -60,17 +81,17 @@ static void staticMethod() { staticMethod(); Object o3 = new Object() { - { anonClasses.put(getClass().getName(), 0); } + { anonClasses.put(getClass().getName(), EXPECTED_ACCESS_FLAGS); } }; } Object o4 = new Object() { - { anonClasses.put(getClass().getName(), 0); } + { anonClasses.put(getClass().getName(), EXPECTED_ACCESS_FLAGS); } }; void instanceMethod() { Object o5 = new Object() { - { anonClasses.put(getClass().getName(), 0); } + { anonClasses.put(getClass().getName(), EXPECTED_ACCESS_FLAGS); } }; } @@ -78,7 +99,7 @@ void instanceMethod() { instanceMethod(); Object o6 = new Object() { - { anonClasses.put(getClass().getName(), 0); } + { anonClasses.put(getClass().getName(), EXPECTED_ACCESS_FLAGS); } }; } @@ -103,7 +124,7 @@ void test(String classesDir) throws Exception { static void assertClassFlags(ClassModel classFile, String name, int expected) { int mask = ClassFile.ACC_PUBLIC | ClassFile.ACC_FINAL | ClassFile.ACC_INTERFACE | ClassFile.ACC_ABSTRACT | - ClassFile.ACC_SYNTHETIC | ClassFile.ACC_ANNOTATION | ClassFile.ACC_ENUM; + ClassFile.ACC_SYNTHETIC | ClassFile.ACC_ANNOTATION | ClassFile.ACC_ENUM | ClassFile.ACC_IDENTITY; int classExpected = (expected & mask) | ClassFile.ACC_SUPER; int classActual = classFile.flags().flagsMask(); if (classActual != classExpected) { diff --git a/test/langtools/tools/javac/ClassFileModifiers/ClassModifiers.out b/test/langtools/tools/javac/ClassFileModifiers/ClassModifiers.out index bdccff8a31a..18556692158 100644 --- a/test/langtools/tools/javac/ClassFileModifiers/ClassModifiers.out +++ b/test/langtools/tools/javac/ClassFileModifiers/ClassModifiers.out @@ -1,121 +1,121 @@ CLASSFILE T.iC ---- SUPER +--- INNERCLASS iC --- CLASSFILE T.iSC ---- SUPER +--- INNERCLASS iSC --- STATIC CLASSFILE T.iVC ---- SUPER +--- INNERCLASS iVC --- PRIVATE CLASSFILE T.iSVC ---- SUPER +--- INNERCLASS iSVC --- PRIVATE STATIC CLASSFILE T.iFC ---- FINAL SUPER +--- FINAL INNERCLASS iFC --- FINAL CLASSFILE T.iSFC ---- FINAL SUPER +--- FINAL INNERCLASS iSFC --- STATIC FINAL CLASSFILE T.iFVC ---- FINAL SUPER +--- FINAL INNERCLASS iFVC --- PRIVATE FINAL CLASSFILE T.iSFVC ---- FINAL SUPER +--- FINAL INNERCLASS iSFVC --- PRIVATE STATIC FINAL CLASSFILE T.iAC ---- SUPER ABSTRACT +--- ABSTRACT INNERCLASS iAC --- ABSTRACT CLASSFILE T.iSAC ---- SUPER ABSTRACT +--- ABSTRACT INNERCLASS iSAC --- STATIC ABSTRACT CLASSFILE T.iAVC ---- SUPER ABSTRACT +--- ABSTRACT INNERCLASS iAVC --- PRIVATE ABSTRACT CLASSFILE T.iSAVC ---- SUPER ABSTRACT +--- ABSTRACT INNERCLASS iSAVC --- PRIVATE STATIC ABSTRACT CLASSFILE T.iRC ---- PUBLIC SUPER +--- PUBLIC INNERCLASS iRC --- PROTECTED CLASSFILE T.iSRC ---- PUBLIC SUPER +--- PUBLIC INNERCLASS iSRC --- PROTECTED STATIC CLASSFILE T.iUC ---- PUBLIC SUPER +--- PUBLIC INNERCLASS iUC --- PUBLIC CLASSFILE T.iSUC ---- PUBLIC SUPER +--- PUBLIC INNERCLASS iSUC --- PUBLIC STATIC CLASSFILE T.iFRC ---- PUBLIC FINAL SUPER +--- PUBLIC FINAL INNERCLASS iFRC --- PROTECTED FINAL CLASSFILE T.iSFRC ---- PUBLIC FINAL SUPER +--- PUBLIC FINAL INNERCLASS iSFRC --- PROTECTED STATIC FINAL CLASSFILE T.iFUC ---- PUBLIC FINAL SUPER +--- PUBLIC FINAL INNERCLASS iFUC --- PUBLIC FINAL CLASSFILE T.iSFUC ---- PUBLIC FINAL SUPER +--- PUBLIC FINAL INNERCLASS iSFUC --- PUBLIC STATIC FINAL CLASSFILE T.iARC ---- PUBLIC SUPER ABSTRACT +--- PUBLIC ABSTRACT INNERCLASS iARC --- PROTECTED ABSTRACT CLASSFILE T.iSARC ---- PUBLIC SUPER ABSTRACT +--- PUBLIC ABSTRACT INNERCLASS iSARC --- PROTECTED STATIC ABSTRACT CLASSFILE T.iAUC ---- PUBLIC SUPER ABSTRACT +--- PUBLIC ABSTRACT INNERCLASS iAUC --- PUBLIC ABSTRACT CLASSFILE T.iSAUC ---- PUBLIC SUPER ABSTRACT +--- PUBLIC ABSTRACT INNERCLASS iSAUC --- PUBLIC STATIC ABSTRACT @@ -200,7 +200,7 @@ INNERCLASS iSAUI --- PUBLIC STATIC INTERFACE ABSTRACT CLASSFILE T ---- SUPER +--- INNERCLASS iSAUI --- PUBLIC STATIC INTERFACE ABSTRACT INNERCLASS iAUI @@ -283,62 +283,62 @@ INNERCLASS iC --- CLASSFILE U.jC ---- PUBLIC SUPER +--- PUBLIC INNERCLASS jC --- PUBLIC STATIC CLASSFILE U.jSC ---- PUBLIC SUPER +--- PUBLIC INNERCLASS jSC --- PUBLIC STATIC CLASSFILE U.jUC ---- PUBLIC SUPER +--- PUBLIC INNERCLASS jUC --- PUBLIC STATIC CLASSFILE U.jSUC ---- PUBLIC SUPER +--- PUBLIC INNERCLASS jSUC --- PUBLIC STATIC CLASSFILE U.jFC ---- PUBLIC FINAL SUPER +--- PUBLIC FINAL INNERCLASS jFC --- PUBLIC STATIC FINAL CLASSFILE U.jSFC ---- PUBLIC FINAL SUPER +--- PUBLIC FINAL INNERCLASS jSFC --- PUBLIC STATIC FINAL CLASSFILE U.jFUC ---- PUBLIC FINAL SUPER +--- PUBLIC FINAL INNERCLASS jFUC --- PUBLIC STATIC FINAL CLASSFILE U.jSFUC ---- PUBLIC FINAL SUPER +--- PUBLIC FINAL INNERCLASS jSFUC --- PUBLIC STATIC FINAL CLASSFILE U.jAC ---- PUBLIC SUPER ABSTRACT +--- PUBLIC ABSTRACT INNERCLASS jAC --- PUBLIC STATIC ABSTRACT CLASSFILE U.jSAC ---- PUBLIC SUPER ABSTRACT +--- PUBLIC ABSTRACT INNERCLASS jSAC --- PUBLIC STATIC ABSTRACT CLASSFILE U.jAUC ---- PUBLIC SUPER ABSTRACT +--- PUBLIC ABSTRACT INNERCLASS jAUC --- PUBLIC STATIC ABSTRACT CLASSFILE U.jSAUC ---- PUBLIC SUPER ABSTRACT +--- PUBLIC ABSTRACT INNERCLASS jSAUC --- PUBLIC STATIC ABSTRACT diff --git a/test/langtools/tools/javac/ClassFileModifiers/MemberModifiers.out b/test/langtools/tools/javac/ClassFileModifiers/MemberModifiers.out index 6d26076a57f..5f7371ee213 100644 --- a/test/langtools/tools/javac/ClassFileModifiers/MemberModifiers.out +++ b/test/langtools/tools/javac/ClassFileModifiers/MemberModifiers.out @@ -1,6 +1,6 @@ CLASSFILE MemberModifiers.c ---- SUPER +--- METHOD --- @@ -8,7 +8,7 @@ CLASSFILE MemberModifiers.i --- INTERFACE ABSTRACT CLASSFILE MemberModifiers ---- PUBLIC FINAL SUPER +--- PUBLIC FINAL FIELD f --- METHOD @@ -17,12 +17,12 @@ METHOD m --- CLASSFILE MemberModifiersAux.Foo.c ---- SUPER +--- METHOD --- CLASSFILE MemberModifiersAux.Foo ---- FINAL SUPER +--- FINAL FIELD f --- METHOD @@ -31,6 +31,6 @@ METHOD m --- CLASSFILE MemberModifiersAux ---- SUPER +--- METHOD --- diff --git a/test/langtools/tools/javac/DefiniteAssignment/DA_DUConstructors.java b/test/langtools/tools/javac/DefiniteAssignment/DA_DUConstructors.java index 60d79d93129..236b46d3fba 100644 --- a/test/langtools/tools/javac/DefiniteAssignment/DA_DUConstructors.java +++ b/test/langtools/tools/javac/DefiniteAssignment/DA_DUConstructors.java @@ -1,6 +1,6 @@ /* * @test /nodynamiccopyright/ - * @bug 8325805 + * @bug 8324873 8325805 * @summary Permit non-superclass instance field assignments before this/super in constructors * @compile/fail/ref=DA_DUConstructors.out -XDrawDiagnostics DA_DUConstructors.java */ @@ -16,16 +16,6 @@ class C1 { } } - class C2 { - final int x; - C2() { - this(x = 3); // error - } - C2(int i) { - x = 4; - } - } - class C3 { C3(int i) {} } diff --git a/test/langtools/tools/javac/DefiniteAssignment/DA_DUConstructors.out b/test/langtools/tools/javac/DefiniteAssignment/DA_DUConstructors.out index 03cd6c2574b..377ab8eb1b1 100644 --- a/test/langtools/tools/javac/DefiniteAssignment/DA_DUConstructors.out +++ b/test/langtools/tools/javac/DefiniteAssignment/DA_DUConstructors.out @@ -1,3 +1,2 @@ -DA_DUConstructors.java:22:17: compiler.err.var.might.already.be.assigned: x -DA_DUConstructors.java:41:23: compiler.err.var.might.not.have.been.initialized: x -2 errors +DA_DUConstructors.java:31:23: compiler.err.var.might.not.have.been.initialized: x +1 error diff --git a/test/langtools/tools/javac/Diagnostics/8043251/T8043251.out b/test/langtools/tools/javac/Diagnostics/8043251/T8043251.out index 2b17605d7de..77af02419d4 100644 --- a/test/langtools/tools/javac/Diagnostics/8043251/T8043251.out +++ b/test/langtools/tools/javac/Diagnostics/8043251/T8043251.out @@ -1,2 +1,2 @@ -T8043251.java:9:42: compiler.err.cant.apply.symbol.noargs: kindname.method, identity, kindname.interface, java.util.function.Function, (compiler.misc.wrong.number.type.args: 1) +T8043251.java:9:42: compiler.err.cant.apply.symbol.noargs: kindname.method, identity, java.util.function.Function, (compiler.misc.wrong.number.type.args: 1) 1 error diff --git a/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview1.java b/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview1.java index 0d7938d7216..7ad3981dc9b 100644 --- a/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview1.java +++ b/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview1.java @@ -2,20 +2,17 @@ * @test /nodynamiccopyright/ * @bug 8334258 * @summary Disallow early assignment if FLEXIBLE_CONSTRUCTORS preview feature is not enabled - * @compile/fail/ref=EarlyAssignmentNoPreview1.out --release 24 -XDrawDiagnostics EarlyAssignmentNoPreview1.java + * @compile/fail/ref=EarlyAssignmentNoPreview1.out -source 24 -XDrawDiagnostics EarlyAssignmentNoPreview1.java */ public class EarlyAssignmentNoPreview1 { Runnable r; public EarlyAssignmentNoPreview1() { - this(r = () -> System.out.println("hello")); + r = () -> System.out.println("hello"); + this(r); } public EarlyAssignmentNoPreview1(Runnable r) { } - - public static void main(String[] args) { - new EarlyAssignmentNoPreview1(); - } } diff --git a/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview1.out b/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview1.out index c28d53b3774..da6ebde1d3b 100644 --- a/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview1.out +++ b/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview1.out @@ -1,2 +1,4 @@ -EarlyAssignmentNoPreview1.java:12:14: compiler.err.feature.not.supported.in.source: (compiler.misc.feature.flexible.constructors), 24, 25 +- compiler.warn.source.no.system.modules.path: 24, (compiler.misc.source.no.system.modules.path: 24) +EarlyAssignmentNoPreview1.java:13:14: compiler.err.feature.not.supported.in.source: (compiler.misc.feature.flexible.constructors), 24, 25 1 error +1 warning diff --git a/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview2.java b/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview2.java index 64332244e01..68169e594d2 100644 --- a/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview2.java +++ b/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview2.java @@ -2,20 +2,17 @@ * @test /nodynamiccopyright/ * @bug 8334258 * @summary Disallow early assignment if FLEXIBLE_CONSTRUCTORS preview feature is not enabled - * @compile/fail/ref=EarlyAssignmentNoPreview2.out --release 24 -XDrawDiagnostics EarlyAssignmentNoPreview2.java + * @compile/fail/ref=EarlyAssignmentNoPreview2.out -source 24 -XDrawDiagnostics EarlyAssignmentNoPreview2.java */ public class EarlyAssignmentNoPreview2 { Runnable r; public EarlyAssignmentNoPreview2() { - this(this.r = () -> System.out.println("hello")); + this.r = () -> System.out.println("hello"); + this(this.r); } public EarlyAssignmentNoPreview2(Runnable r) { } - - public static void main(String[] args) { - new EarlyAssignmentNoPreview2(); - } } diff --git a/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview2.out b/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview2.out index 1250c714c4a..c54f16fbaf2 100644 --- a/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview2.out +++ b/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview2.out @@ -1,2 +1,4 @@ -EarlyAssignmentNoPreview2.java:12:14: compiler.err.feature.not.supported.in.source: (compiler.misc.feature.flexible.constructors), 24, 25 +- compiler.warn.source.no.system.modules.path: 24, (compiler.misc.source.no.system.modules.path: 24) +EarlyAssignmentNoPreview2.java:13:18: compiler.err.feature.not.supported.in.source: (compiler.misc.feature.flexible.constructors), 24, 25 1 error +1 warning diff --git a/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview3.java b/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview3.java index e021d80e6c1..3badd40dd57 100644 --- a/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview3.java +++ b/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview3.java @@ -2,20 +2,17 @@ * @test /nodynamiccopyright/ * @bug 8334258 * @summary Disallow early assignment if FLEXIBLE_CONSTRUCTORS preview feature is not enabled - * @compile/fail/ref=EarlyAssignmentNoPreview3.out --release 24 -XDrawDiagnostics EarlyAssignmentNoPreview3.java + * @compile/fail/ref=EarlyAssignmentNoPreview3.out -source 24 -XDrawDiagnostics EarlyAssignmentNoPreview3.java */ public class EarlyAssignmentNoPreview3 { Runnable r; public EarlyAssignmentNoPreview3() { - this(EarlyAssignmentNoPreview3.this.r = () -> System.out.println("hello")); + EarlyAssignmentNoPreview3.this.r = () -> System.out.println("hello"); + this(EarlyAssignmentNoPreview3.this.r); } public EarlyAssignmentNoPreview3(Runnable r) { } - - public static void main(String[] args) { - new EarlyAssignmentNoPreview3(); - } } diff --git a/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview3.out b/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview3.out index f1ccf9cfa7d..f368fbeea3e 100644 --- a/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview3.out +++ b/test/langtools/tools/javac/SuperInit/EarlyAssignmentNoPreview3.out @@ -1,2 +1,4 @@ -EarlyAssignmentNoPreview3.java:12:39: compiler.err.feature.not.supported.in.source: (compiler.misc.feature.flexible.constructors), 24, 25 +- compiler.warn.source.no.system.modules.path: 24, (compiler.misc.source.no.system.modules.path: 24) +EarlyAssignmentNoPreview3.java:13:44: compiler.err.feature.not.supported.in.source: (compiler.misc.feature.flexible.constructors), 24, 25 1 error +1 warning diff --git a/test/langtools/tools/javac/SuperInit/EarlyAssignments.java b/test/langtools/tools/javac/SuperInit/EarlyAssignments.java index 0168ed9f4b4..fdc9eb27d60 100644 --- a/test/langtools/tools/javac/SuperInit/EarlyAssignments.java +++ b/test/langtools/tools/javac/SuperInit/EarlyAssignments.java @@ -2,7 +2,10 @@ * @test /nodynamiccopyright/ * @bug 8325805 * @summary Permit non-superclass instance field assignments before this/super in constructors + * @enablePreview * @compile/fail/ref=EarlyAssignments.out -XDrawDiagnostics EarlyAssignments.java + * @build InitializationWarningTester + * @run main InitializationWarningTester EarlyAssignments EarlyAssignmentsWarnings.out */ public class EarlyAssignments { @@ -17,9 +20,9 @@ public Inner1() { } public Inner1(int y) { - y = x; // FAIL - early 'this' reference - y = this.x; // FAIL - early 'this' reference - y = Inner1.this.x; // FAIL - early 'this' reference + y = x; // OK - "x" belongs to this class + y = this.x; // OK - "x" belongs to this class + y = Inner1.this.x; // OK - "x" belongs to this class super(); } @@ -94,19 +97,19 @@ public static class Inner4 { public Inner4() { x = 0; // OK - x = x + 1; // FAIL - illegal early access + x = x + 1; // OK super(); } public Inner4(int a) { this.x = 0; // OK - this.x = this.x + 1; // FAIL - illegal early access + this.x = this.x + 1; // OK super(); } public Inner4(char a) { Inner4.this.x = 0; // OK - Inner4.this.x = Inner4.this.x + 1; // FAIL - illegal early access + Inner4.this.x = Inner4.this.x + 1; // OK super(); } } @@ -168,4 +171,13 @@ public Inner8() { super(); } } + + public static class Inner9 { + int x = 1; + int y; + Inner9() { + y = x; // FAIL, x has an initializer + super(); + } + } } diff --git a/test/langtools/tools/javac/SuperInit/EarlyAssignments.out b/test/langtools/tools/javac/SuperInit/EarlyAssignments.out index ac765c141e7..7f360964aaf 100644 --- a/test/langtools/tools/javac/SuperInit/EarlyAssignments.out +++ b/test/langtools/tools/javac/SuperInit/EarlyAssignments.out @@ -1,27 +1,23 @@ -EarlyAssignments.java:20:17: compiler.err.cant.ref.before.ctor.called: x -EarlyAssignments.java:21:17: compiler.err.cant.ref.before.ctor.called: this -EarlyAssignments.java:22:23: compiler.err.cant.ref.before.ctor.called: this -EarlyAssignments.java:30:21: compiler.err.cant.ref.before.ctor.called: super -EarlyAssignments.java:31:21: compiler.err.cant.ref.before.ctor.called: x -EarlyAssignments.java:32:26: compiler.err.cant.ref.before.ctor.called: x -EarlyAssignments.java:33:34: compiler.err.cant.ref.before.ctor.called: x -EarlyAssignments.java:35:36: compiler.err.cant.ref.before.ctor.called: this -EarlyAssignments.java:39:17: compiler.err.cant.ref.before.ctor.called: x -EarlyAssignments.java:43:21: compiler.err.cant.ref.before.ctor.called: x -EarlyAssignments.java:47:22: compiler.err.cant.ref.before.ctor.called: x -EarlyAssignments.java:65:13: compiler.err.cant.ref.before.ctor.called: x -EarlyAssignments.java:66:17: compiler.err.cant.ref.before.ctor.called: x -EarlyAssignments.java:67:25: compiler.err.cant.ref.before.ctor.called: this -EarlyAssignments.java:68:31: compiler.err.cant.ref.before.ctor.called: this -EarlyAssignments.java:97:17: compiler.err.cant.ref.before.ctor.called: x -EarlyAssignments.java:103:22: compiler.err.cant.ref.before.ctor.called: this -EarlyAssignments.java:109:35: compiler.err.cant.ref.before.ctor.called: this -EarlyAssignments.java:118:17: compiler.err.cant.ref.before.ctor.called: x -EarlyAssignments.java:123:22: compiler.err.cant.ref.before.ctor.called: x -EarlyAssignments.java:128:29: compiler.err.cant.ref.before.ctor.called: x -EarlyAssignments.java:133:17: compiler.err.cant.ref.before.ctor.called: super -EarlyAssignments.java:138:23: compiler.err.cant.ref.before.ctor.called: this -EarlyAssignments.java:147:13: compiler.err.cant.assign.initialized.before.ctor.called: x -EarlyAssignments.java:156:13: compiler.err.cant.assign.val.to.var: final, x -EarlyAssignments.java:167:13: compiler.err.cant.ref.before.ctor.called: this -26 errors +EarlyAssignments.java:33:26: compiler.err.cant.ref.before.ctor.called: x +EarlyAssignments.java:34:21: compiler.err.cant.ref.before.ctor.called: x +EarlyAssignments.java:35:26: compiler.err.cant.ref.before.ctor.called: x +EarlyAssignments.java:36:34: compiler.err.cant.ref.before.ctor.called: x +EarlyAssignments.java:38:36: compiler.err.cant.ref.before.ctor.called: this +EarlyAssignments.java:42:17: compiler.err.cant.ref.before.ctor.called: x +EarlyAssignments.java:46:21: compiler.err.cant.ref.before.ctor.called: x +EarlyAssignments.java:50:22: compiler.err.cant.ref.before.ctor.called: x +EarlyAssignments.java:68:13: compiler.err.cant.ref.before.ctor.called: x +EarlyAssignments.java:69:17: compiler.err.cant.ref.before.ctor.called: x +EarlyAssignments.java:70:25: compiler.err.cant.ref.before.ctor.called: this +EarlyAssignments.java:71:31: compiler.err.cant.ref.before.ctor.called: this +EarlyAssignments.java:121:17: compiler.err.cant.ref.before.ctor.called: x +EarlyAssignments.java:126:22: compiler.err.cant.ref.before.ctor.called: x +EarlyAssignments.java:131:29: compiler.err.cant.ref.before.ctor.called: x +EarlyAssignments.java:136:22: compiler.err.cant.ref.before.ctor.called: x +EarlyAssignments.java:141:28: compiler.err.cant.ref.before.ctor.called: x +EarlyAssignments.java:150:13: compiler.err.cant.assign.initialized.before.ctor.called: x +EarlyAssignments.java:159:13: compiler.err.cant.assign.val.to.var: final, x +EarlyAssignments.java:170:13: compiler.err.cant.ref.before.ctor.called: this +EarlyAssignments.java:170:18: compiler.err.cant.ref.before.ctor.called: EarlyAssignments.Inner8 +EarlyAssignments.java:179:17: compiler.err.cant.ref.before.ctor.called: x +22 errors diff --git a/test/langtools/tools/javac/SuperInit/EarlyAssignmentsWarnings.out b/test/langtools/tools/javac/SuperInit/EarlyAssignmentsWarnings.out new file mode 100644 index 00000000000..eae7d001e18 --- /dev/null +++ b/test/langtools/tools/javac/SuperInit/EarlyAssignmentsWarnings.out @@ -0,0 +1,23 @@ +EarlyAssignments.java:159:13: compiler.err.cant.assign.val.to.var: final, x +EarlyAssignments.java:33:26: compiler.warn.would.not.be.allowed.in.prologue: x +EarlyAssignments.java:34:21: compiler.warn.would.not.be.allowed.in.prologue: x +EarlyAssignments.java:35:26: compiler.warn.would.not.be.allowed.in.prologue: x +EarlyAssignments.java:36:34: compiler.warn.would.not.be.allowed.in.prologue: x +EarlyAssignments.java:38:36: compiler.warn.would.not.be.allowed.in.prologue: this +EarlyAssignments.java:42:17: compiler.warn.would.not.be.allowed.in.prologue: x +EarlyAssignments.java:46:21: compiler.warn.would.not.be.allowed.in.prologue: x +EarlyAssignments.java:50:22: compiler.warn.would.not.be.allowed.in.prologue: x +EarlyAssignments.java:68:13: compiler.warn.would.not.be.allowed.in.prologue: x +EarlyAssignments.java:69:17: compiler.warn.would.not.be.allowed.in.prologue: x +EarlyAssignments.java:70:25: compiler.warn.would.not.be.allowed.in.prologue: this +EarlyAssignments.java:71:31: compiler.warn.would.not.be.allowed.in.prologue: this +EarlyAssignments.java:121:17: compiler.warn.would.not.be.allowed.in.prologue: x +EarlyAssignments.java:126:22: compiler.warn.would.not.be.allowed.in.prologue: x +EarlyAssignments.java:131:29: compiler.warn.would.not.be.allowed.in.prologue: x +EarlyAssignments.java:136:22: compiler.warn.would.not.be.allowed.in.prologue: x +EarlyAssignments.java:141:28: compiler.warn.would.not.be.allowed.in.prologue: x +EarlyAssignments.java:150:13: compiler.warn.would.not.be.allowed.in.prologue: x +EarlyAssignments.java:159:13: compiler.warn.would.not.be.allowed.in.prologue: x +EarlyAssignments.java:170:13: compiler.warn.would.not.be.allowed.in.prologue: this +EarlyAssignments.java:170:18: compiler.warn.would.not.be.allowed.in.prologue: EarlyAssignments.Inner8 +EarlyAssignments.java:179:17: compiler.warn.would.not.be.allowed.in.prologue: x diff --git a/test/langtools/tools/javac/SuperInit/EarlyIndirectOuterCapture.java b/test/langtools/tools/javac/SuperInit/EarlyIndirectOuterCapture.java index 9336c4dc183..401ffe15be5 100644 --- a/test/langtools/tools/javac/SuperInit/EarlyIndirectOuterCapture.java +++ b/test/langtools/tools/javac/SuperInit/EarlyIndirectOuterCapture.java @@ -2,7 +2,8 @@ * @test /nodynamiccopyright/ * @bug 8334248 * @summary Invalid error for early construction local class constructor method reference - * @compile/fail/ref=EarlyIndirectOuterCapture.out -XDrawDiagnostics EarlyIndirectOuterCapture.java + * @build InitializationWarningTester + * @run main InitializationWarningTester EarlyIndirectOuterCapture */ public class EarlyIndirectOuterCapture { @@ -18,7 +19,7 @@ class InnerSuperclass { } static class InnerOuter extends EarlyIndirectOuterCapture { // accessible class InnerInnerOuter extends EarlyIndirectOuterCapture { // not accessible InnerInnerOuter() { - super(/* which enclosing instance here ? */new InnerSuperclass() { }); + super(new InnerSuperclass() { }); // should this be accepted?, InnerSuperclass is not an inner class of InnerInnerOuter } InnerInnerOuter(boolean b) { diff --git a/test/langtools/tools/javac/SuperInit/EarlyIndirectOuterCapture.out b/test/langtools/tools/javac/SuperInit/EarlyIndirectOuterCapture.out deleted file mode 100644 index 7b96671a2bd..00000000000 --- a/test/langtools/tools/javac/SuperInit/EarlyIndirectOuterCapture.out +++ /dev/null @@ -1,2 +0,0 @@ -EarlyIndirectOuterCapture.java:21:60: compiler.err.cant.ref.before.ctor.called: this -1 error diff --git a/test/langtools/tools/javac/SuperInit/EarlyInnerAccessErrorMessageTest.java b/test/langtools/tools/javac/SuperInit/EarlyInnerAccessErrorMessageTest.java index c8279543244..6d990dd363b 100644 --- a/test/langtools/tools/javac/SuperInit/EarlyInnerAccessErrorMessageTest.java +++ b/test/langtools/tools/javac/SuperInit/EarlyInnerAccessErrorMessageTest.java @@ -3,6 +3,8 @@ * @bug 8334488 * @summary Verify the error message generated for early access from inner class * @compile/fail/ref=EarlyInnerAccessErrorMessageTest.out -XDrawDiagnostics EarlyInnerAccessErrorMessageTest.java + * @build InitializationWarningTester + * @run main InitializationWarningTester EarlyInnerAccessErrorMessageTest EarlyInnerAccessErrorMessageTestWarnings.out */ public class EarlyInnerAccessErrorMessageTest { int x; diff --git a/test/langtools/tools/javac/SuperInit/EarlyInnerAccessErrorMessageTest.out b/test/langtools/tools/javac/SuperInit/EarlyInnerAccessErrorMessageTest.out index 2bea02bed10..0e68bac7d7f 100644 --- a/test/langtools/tools/javac/SuperInit/EarlyInnerAccessErrorMessageTest.out +++ b/test/langtools/tools/javac/SuperInit/EarlyInnerAccessErrorMessageTest.out @@ -1,2 +1,2 @@ -EarlyInnerAccessErrorMessageTest.java:11:34: compiler.err.cant.ref.before.ctor.called: x +EarlyInnerAccessErrorMessageTest.java:13:34: compiler.err.cant.ref.before.ctor.called: x 1 error diff --git a/test/langtools/tools/javac/SuperInit/EarlyInnerAccessErrorMessageTestWarnings.out b/test/langtools/tools/javac/SuperInit/EarlyInnerAccessErrorMessageTestWarnings.out new file mode 100644 index 00000000000..fc935ddca6b --- /dev/null +++ b/test/langtools/tools/javac/SuperInit/EarlyInnerAccessErrorMessageTestWarnings.out @@ -0,0 +1 @@ +EarlyInnerAccessErrorMessageTest.java:13:34: compiler.warn.would.not.be.allowed.in.prologue: x diff --git a/test/langtools/tools/javac/SuperInit/EarlyLambdaReturn.java b/test/langtools/tools/javac/SuperInit/EarlyLambdaReturn.java index a56974ea252..d7573e89088 100644 --- a/test/langtools/tools/javac/SuperInit/EarlyLambdaReturn.java +++ b/test/langtools/tools/javac/SuperInit/EarlyLambdaReturn.java @@ -24,6 +24,9 @@ * @test * @bug 8345438 * @summary Verify 'return' allowed in a lambda declared in an early construction context + * @run main EarlyLambdaReturn + * @build InitializationWarningTester + * @run main InitializationWarningTester EarlyLambdaReturn */ public class EarlyLambdaReturn { diff --git a/test/langtools/tools/javac/SuperInit/EarlyLocalClass.java b/test/langtools/tools/javac/SuperInit/EarlyLocalClass.java index 5ac5aba82ed..f3281d45891 100644 --- a/test/langtools/tools/javac/SuperInit/EarlyLocalClass.java +++ b/test/langtools/tools/javac/SuperInit/EarlyLocalClass.java @@ -3,6 +3,8 @@ * @bug 8325805 * @summary Verify local class in early construction context has no outer instance * @compile/fail/ref=EarlyLocalClass.out -XDrawDiagnostics EarlyLocalClass.java + * @build InitializationWarningTester + * @run main InitializationWarningTester EarlyLocalClass EarlyLocalClassWarnings.out */ public class EarlyLocalClass { EarlyLocalClass() { diff --git a/test/langtools/tools/javac/SuperInit/EarlyLocalClass.out b/test/langtools/tools/javac/SuperInit/EarlyLocalClass.out index 1024b5bdf26..e5edf3ef14a 100644 --- a/test/langtools/tools/javac/SuperInit/EarlyLocalClass.out +++ b/test/langtools/tools/javac/SuperInit/EarlyLocalClass.out @@ -1,2 +1,2 @@ -EarlyLocalClass.java:11:32: compiler.err.cant.ref.before.ctor.called: this +EarlyLocalClass.java:13:37: compiler.err.cant.ref.before.ctor.called: hashCode() 1 error diff --git a/test/langtools/tools/javac/SuperInit/EarlyLocalClassWarnings.out b/test/langtools/tools/javac/SuperInit/EarlyLocalClassWarnings.out new file mode 100644 index 00000000000..8a80212b56b --- /dev/null +++ b/test/langtools/tools/javac/SuperInit/EarlyLocalClassWarnings.out @@ -0,0 +1 @@ +EarlyLocalClass.java:13:37: compiler.warn.would.not.be.allowed.in.prologue: hashCode() diff --git a/test/langtools/tools/javac/SuperInit/EarlyLocalTest1.java b/test/langtools/tools/javac/SuperInit/EarlyLocalTest1.java index a51189c11c9..e8b77702479 100644 --- a/test/langtools/tools/javac/SuperInit/EarlyLocalTest1.java +++ b/test/langtools/tools/javac/SuperInit/EarlyLocalTest1.java @@ -24,6 +24,9 @@ * @test * @bug 8333313 * @summary Verify references to local classes declared in early construction contexts + * @run main EarlyLocalTest1 + * @build InitializationWarningTester + * @run main InitializationWarningTester EarlyLocalTest1 */ public class EarlyLocalTest1 { diff --git a/test/langtools/tools/javac/SuperInit/EarlyLocalTest2.java b/test/langtools/tools/javac/SuperInit/EarlyLocalTest2.java index 09dbc66e2d3..521513a4174 100644 --- a/test/langtools/tools/javac/SuperInit/EarlyLocalTest2.java +++ b/test/langtools/tools/javac/SuperInit/EarlyLocalTest2.java @@ -24,6 +24,9 @@ * @test * @bug 8333313 * @summary Verify references to local classes declared in early construction contexts + * @run main EarlyLocalTest2 + * @build InitializationWarningTester + * @run main InitializationWarningTester EarlyLocalTest2 */ public class EarlyLocalTest2 { diff --git a/test/langtools/tools/javac/SuperInit/EarlyLocalTest3.java b/test/langtools/tools/javac/SuperInit/EarlyLocalTest3.java index 34d280532c3..31183dc0b28 100644 --- a/test/langtools/tools/javac/SuperInit/EarlyLocalTest3.java +++ b/test/langtools/tools/javac/SuperInit/EarlyLocalTest3.java @@ -24,7 +24,11 @@ * @test * @bug 8333313 * @summary Verify references to local classes declared in early construction contexts + * @run main EarlyLocalTest3 + * @build InitializationWarningTester + * @run main InitializationWarningTester EarlyLocalTest3 */ + public class EarlyLocalTest3 { class Test { diff --git a/test/langtools/tools/javac/SuperInit/EarlyLocalTest4.java b/test/langtools/tools/javac/SuperInit/EarlyLocalTest4.java index 7f6fa032ec7..d5e4ad4e2b5 100644 --- a/test/langtools/tools/javac/SuperInit/EarlyLocalTest4.java +++ b/test/langtools/tools/javac/SuperInit/EarlyLocalTest4.java @@ -24,6 +24,9 @@ * @test * @bug 8333313 * @summary Verify references to local classes declared in early construction contexts + * @run main EarlyLocalTest4 + * @build InitializationWarningTester + * @run main InitializationWarningTester EarlyLocalTest4 */ public class EarlyLocalTest4 { diff --git a/test/langtools/tools/javac/SuperInit/EarlyLocalTest5.java b/test/langtools/tools/javac/SuperInit/EarlyLocalTest5.java index c75b97c6beb..254c882038f 100644 --- a/test/langtools/tools/javac/SuperInit/EarlyLocalTest5.java +++ b/test/langtools/tools/javac/SuperInit/EarlyLocalTest5.java @@ -24,6 +24,9 @@ * @test * @bug 8333313 * @summary Verify references to local classes declared in early construction contexts + * @run main EarlyLocalTest5 + * @build InitializationWarningTester + * @run main InitializationWarningTester EarlyLocalTest5 */ import java.util.concurrent.atomic.AtomicReference; diff --git a/test/langtools/tools/javac/SuperInit/EarlyLocalTest6.java b/test/langtools/tools/javac/SuperInit/EarlyLocalTest6.java index 35eda1a5257..8721bb0b29b 100644 --- a/test/langtools/tools/javac/SuperInit/EarlyLocalTest6.java +++ b/test/langtools/tools/javac/SuperInit/EarlyLocalTest6.java @@ -24,6 +24,9 @@ * @test * @bug 8333313 * @summary Verify references to local classes declared in early construction contexts + * @run main EarlyLocalTest6 + * @build InitializationWarningTester + * @run main InitializationWarningTester EarlyLocalTest6 */ import java.util.concurrent.atomic.AtomicReference; diff --git a/test/langtools/tools/javac/SuperInit/EarlyLocalTest7.java b/test/langtools/tools/javac/SuperInit/EarlyLocalTest7.java index 885783eb9b0..6a9ca7132a4 100644 --- a/test/langtools/tools/javac/SuperInit/EarlyLocalTest7.java +++ b/test/langtools/tools/javac/SuperInit/EarlyLocalTest7.java @@ -24,6 +24,9 @@ * @test * @bug 8333313 * @summary Verify references to local classes declared in early construction contexts + * @run main EarlyLocalTest7 + * @build InitializationWarningTester + * @run main InitializationWarningTester EarlyLocalTest7 */ import java.util.concurrent.atomic.AtomicReference; diff --git a/test/langtools/tools/javac/SuperInit/EarlyLocalTest8.java b/test/langtools/tools/javac/SuperInit/EarlyLocalTest8.java index d09e12fcd5d..deea1fd5f63 100644 --- a/test/langtools/tools/javac/SuperInit/EarlyLocalTest8.java +++ b/test/langtools/tools/javac/SuperInit/EarlyLocalTest8.java @@ -24,6 +24,9 @@ * @test * @bug 8333313 * @summary Verify references to local classes declared in early construction contexts + * @run main EarlyLocalTest8 + * @build InitializationWarningTester + * @run main InitializationWarningTester EarlyLocalTest8 */ import java.util.concurrent.atomic.AtomicReference; diff --git a/test/langtools/tools/javac/SuperInit/InitializationWarningTester.java b/test/langtools/tools/javac/SuperInit/InitializationWarningTester.java new file mode 100644 index 00000000000..101f35f5b20 --- /dev/null +++ b/test/langtools/tools/javac/SuperInit/InitializationWarningTester.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.io.PrintWriter; +import java.io.IOException; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Stream; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.charset.Charset; + +import javax.tools.Diagnostic; +import javax.tools.DiagnosticListener; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; + +import com.sun.tools.javac.code.Attribute; +import com.sun.tools.javac.comp.Attr; +import com.sun.tools.javac.comp.AttrContext; +import com.sun.tools.javac.comp.CompileStates; +import com.sun.tools.javac.comp.Env; +import com.sun.tools.javac.comp.Modules; +import com.sun.tools.javac.file.JavacFileManager; +import com.sun.tools.javac.file.PathFileObject; +import com.sun.tools.javac.main.JavaCompiler; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.*; +import com.sun.tools.javac.tree.TreeInfo; +import com.sun.tools.javac.util.Assert; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.DiagnosticSource; +import com.sun.tools.javac.util.JCDiagnostic; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Log; +import com.sun.tools.javac.util.Options; + +public class InitializationWarningTester { + Context context; + Options options; + MyJavaCompiler javaCompiler; + JavacFileManager javacFileManager; + PrintWriter errOut; + DiagnosticListener diagnosticListener; + + public static void main(String... args) throws Throwable { + String testSrc = System.getProperty("test.src"); + Path baseDir = Paths.get(testSrc); + InitializationWarningTester tester = new InitializationWarningTester(); + Assert.check(args.length > 0, "no args, ending"); + Assert.check(args.length <= 2, "unexpected number of arguments"); + String className = args[0]; + String warningsGoldenFileName = args.length > 1 ? args[1] : null; + tester.test(baseDir, className, warningsGoldenFileName); + } + + java.util.List compilationOutput = new ArrayList<>(); + + public InitializationWarningTester() { + context = new Context(); + diagnosticListener = new DiagnosticListener() { + public void report(Diagnostic message) { + JCDiagnostic diagnostic = (JCDiagnostic) message; + String msgData = ((PathFileObject)diagnostic.getDiagnosticSource().getFile()).getShortName() + + ":" + diagnostic.getLineNumber() + ":" + diagnostic.getColumnNumber() + ": " + diagnostic.getCode(); + if (diagnostic.getArgs() != null && diagnostic.getArgs().length > 0) { + msgData += ": " + Arrays.stream(diagnostic.getArgs()).map(o -> o.toString()) + .collect(Collectors.joining(", ")); + } + compilationOutput.add(msgData); + } + }; + context.put(DiagnosticListener.class, diagnosticListener); + JavacFileManager.preRegister(context); + MyAttr.preRegister(context, this); + options = Options.instance(context); + options.put("--enable-preview", "--enable-preview"); + options.put("--source", Integer.toString(Runtime.version().feature())); + options.put("-Xlint:initialization", "-Xlint:initialization"); + javaCompiler = new MyJavaCompiler(context); + javacFileManager = new JavacFileManager(context, false, Charset.defaultCharset()); + } + + void test(Path baseDir, String className, String warningsGoldenFileName) throws Throwable { + Path javaFile = baseDir.resolve(className + ".java"); + Path goldenFile = warningsGoldenFileName != null ? baseDir.resolve(warningsGoldenFileName) : null; + + // compile + javaCompiler.compile(com.sun.tools.javac.util.List.of(javacFileManager.getJavaFileObject(javaFile))); + if (goldenFile != null) { + java.util.List goldenFileContent = Files.readAllLines(goldenFile); + if (goldenFileContent.size() != compilationOutput.size()) { + System.err.println("compilation output length mismatch"); + System.err.println(" golden file content:"); + for (String s : goldenFileContent) { + System.err.println(" " + s); + } + System.err.println(" warning compilation result:"); + for (String s : compilationOutput) { + System.err.println(" " + s); + } + throw new AssertionError("compilation output length mismatch"); + } + for (int i = 0; i < goldenFileContent.size(); i++) { + String goldenLine = goldenFileContent.get(i); + String warningLine = compilationOutput.get(i); + Assert.check(warningLine.equals(goldenLine), "error, found:\n" + warningLine + "\nexpected:\n" + goldenLine); + } + } else { + if (compilationOutput.size() != 0) { + System.err.println(" expecting empty compilation output, got:"); + for (String s : compilationOutput) { + System.err.println(" " + s); + } + throw new AssertionError("expected empty compilation output"); + } + } + } + + static class MyJavaCompiler extends JavaCompiler { + MyJavaCompiler(Context context) { + super(context); + // do not generate code + this.shouldStopPolicyIfNoError = CompileStates.CompileState.LOWER; + } + } + + static class MyAttr extends Attr { + InitializationWarningTester tester; + static void preRegister(Context context, InitializationWarningTester tester) { + context.put(attrKey, (com.sun.tools.javac.util.Context.Factory) c -> new MyAttr(c, tester)); + } + + MyAttr(Context context, InitializationWarningTester tester) { + super(context); + this.tester = tester; + } + + @Override + public void visitMethodDef(JCMethodDecl tree) { + if (TreeInfo.isConstructor(tree)) { + /* remove the super constructor call if it has no arguments, that way the Attr super class + * will add a super() as the first statement in the constructor and will analyze the rest + * of the code in warnings only mode + */ + if (TreeInfo.hasAnyConstructorCall(tree)) { + ListBuffer newStats = new ListBuffer<>(); + for (JCStatement statement : tree.body.stats) { + if (statement instanceof JCExpressionStatement expressionStatement && + expressionStatement.expr instanceof JCMethodInvocation methodInvocation) { + if (TreeInfo.isConstructorCall(methodInvocation) && + methodInvocation.args.isEmpty()) { + continue; + } + } + newStats.add(statement); + } + tree.body.stats = newStats.toList(); + } + } + super.visitMethodDef(tree); + } + } +} diff --git a/test/langtools/tools/javac/SuperInit/LambdaLocalEarlyCrash.java b/test/langtools/tools/javac/SuperInit/LambdaLocalEarlyCrash.java index 2d2f8c030ee..310b9ce8a26 100644 --- a/test/langtools/tools/javac/SuperInit/LambdaLocalEarlyCrash.java +++ b/test/langtools/tools/javac/SuperInit/LambdaLocalEarlyCrash.java @@ -24,6 +24,9 @@ * @test * @bug 8334037 * @summary Test for compiler crash when local class created in early lambda + * @run main LambdaLocalEarlyCrash + * @build InitializationWarningTester + * @run main InitializationWarningTester LambdaLocalEarlyCrash */ public class LambdaLocalEarlyCrash { diff --git a/test/langtools/tools/javac/SuperInit/LambdaOuterCapture.java b/test/langtools/tools/javac/SuperInit/LambdaOuterCapture.java index f1f8535e2dd..108e990bbbf 100644 --- a/test/langtools/tools/javac/SuperInit/LambdaOuterCapture.java +++ b/test/langtools/tools/javac/SuperInit/LambdaOuterCapture.java @@ -24,6 +24,9 @@ * @test * @bug 8334252 * @summary Test lambda declared in early construction context + * @run main LambdaOuterCapture + * @build InitializationWarningTester + * @run main InitializationWarningTester LambdaOuterCapture */ public class LambdaOuterCapture { diff --git a/test/langtools/tools/javac/SuperInit/SuperInitFails.java b/test/langtools/tools/javac/SuperInit/SuperInitFails.java index f9cfe5cd7c4..3fac4657350 100644 --- a/test/langtools/tools/javac/SuperInit/SuperInitFails.java +++ b/test/langtools/tools/javac/SuperInit/SuperInitFails.java @@ -2,7 +2,10 @@ * @test /nodynamiccopyright/ * @bug 8194743 * @summary Permit additional statements before this/super in constructors + * @enablePreview * @compile/fail/ref=SuperInitFails.out -XDrawDiagnostics SuperInitFails.java + * @build InitializationWarningTester + * @run main InitializationWarningTester SuperInitFails SuperInitFailsWarnings.out */ import java.util.concurrent.atomic.AtomicReference; public class SuperInitFails extends AtomicReference implements Iterable { @@ -151,7 +154,7 @@ public SuperInitFails(float[][] x) { } public SuperInitFails(int[][] z) { - super((Runnable)() -> x); // this should FAIL + super((Runnable)() -> System.err.println(x)); // this should FAIL } public SuperInitFails(long[][] z) { @@ -186,6 +189,17 @@ public SuperInitFails(double[][] x) { super(); } + public int xx; + + SuperInitFails(short[][] ignore) { + int i = new SuperInitFails(){ + void foo() { + System.err.println(xx); // this one is OK, reading field `xx` in the anonymous class + } + }.xx; // this one is OK too, field of a fully constructed class + super(null); + } + public static class Inner4 { Inner4() { Runnable r = () -> { @@ -208,4 +222,51 @@ class A { super(); }; } + + static class Inner5 { + int x = 4; + static String m1(Runnable r) { return null; } + static String m2(Object r) { return null; } + Inner5() { + m1(() -> System.out.println(x)).toString(); + m2(x).toString(); + super(); + } + } + + static class Inner6 { + Inner6() { + class Bar { + Bar() { + Object o = Bar.this; + super(); + } + } + super(); + } + } + + static class Inner7 { + private int x; + + public Inner7(byte y) { + x = y; + this((int)y); + } + public Inner7(int x) { + this.x = x; + super(); + } + } + + static class Inner8 { + final int x; + + Inner8() { + this(x = 3); // error + } + Inner8(int i) { + x = 4; + } + } } diff --git a/test/langtools/tools/javac/SuperInit/SuperInitFails.out b/test/langtools/tools/javac/SuperInit/SuperInitFails.out index 1a8e8797135..1fadddcf7da 100644 --- a/test/langtools/tools/javac/SuperInit/SuperInitFails.out +++ b/test/langtools/tools/javac/SuperInit/SuperInitFails.out @@ -1,34 +1,38 @@ -SuperInitFails.java:56:9: compiler.err.cant.ref.before.ctor.called: hashCode() -SuperInitFails.java:61:9: compiler.err.cant.ref.before.ctor.called: this -SuperInitFails.java:66:9: compiler.err.cant.ref.before.ctor.called: super -SuperInitFails.java:71:23: compiler.err.cant.ref.before.ctor.called: this -SuperInitFails.java:76:23: compiler.err.cant.ref.before.ctor.called: super -SuperInitFails.java:93:9: compiler.err.cant.ref.before.ctor.called: this -SuperInitFails.java:98:33: compiler.err.cant.ref.before.ctor.called: this -SuperInitFails.java:103:14: compiler.err.cant.ref.before.ctor.called: this -SuperInitFails.java:107:20: compiler.err.not.encl.class: java.lang.Object -SuperInitFails.java:111:17: compiler.err.cant.ref.before.ctor.called: super -SuperInitFails.java:118:22: compiler.err.call.must.only.appear.in.ctor -SuperInitFails.java:124:9: compiler.err.cant.ref.before.ctor.called: this -SuperInitFails.java:132:9: compiler.err.non.canonical.constructor.invoke.another.constructor: SuperInitFails.Record1 -SuperInitFails.java:137:9: compiler.err.non.canonical.constructor.invoke.another.constructor: SuperInitFails.Record2 -SuperInitFails.java:154:31: compiler.err.cant.ref.before.ctor.called: x -SuperInitFails.java:158:15: compiler.err.cant.ref.before.ctor.called: this -SuperInitFails.java:167:13: compiler.err.cant.ref.before.ctor.called: x -SuperInitFails.java:171:17: compiler.err.cant.ref.before.ctor.called: x -SuperInitFails.java:175:24: compiler.err.cant.ref.before.ctor.called: x -SuperInitFails.java:179:18: compiler.err.cant.ref.before.ctor.called: x -SuperInitFails.java:185:28: compiler.err.cant.ref.before.ctor.called: this -SuperInitFails.java:194:25: compiler.err.return.before.superclass.initialized -SuperInitFails.java:199:33: compiler.err.ctor.calls.not.allowed.here -SuperInitFails.java:204:29: compiler.err.redundant.superclass.init -SuperInitFails.java:32:13: compiler.err.call.must.only.appear.in.ctor -SuperInitFails.java:36:14: compiler.err.call.must.only.appear.in.ctor -SuperInitFails.java:40:14: compiler.err.call.must.only.appear.in.ctor -SuperInitFails.java:44:13: compiler.err.call.must.only.appear.in.ctor -SuperInitFails.java:48:33: compiler.err.call.must.only.appear.in.ctor -SuperInitFails.java:52:32: compiler.err.call.must.only.appear.in.ctor -SuperInitFails.java:82:18: compiler.err.ctor.calls.not.allowed.here -SuperInitFails.java:88:13: compiler.err.return.before.superclass.initialized -SuperInitFails.java:149:18: compiler.err.call.must.only.appear.in.ctor -33 errors +SuperInitFails.java:59:9: compiler.err.cant.ref.before.ctor.called: hashCode() +SuperInitFails.java:64:13: compiler.err.cant.ref.before.ctor.called: hashCode() +SuperInitFails.java:69:14: compiler.err.cant.ref.before.ctor.called: hashCode() +SuperInitFails.java:74:28: compiler.err.cant.ref.before.ctor.called: hashCode() +SuperInitFails.java:79:29: compiler.err.cant.ref.before.ctor.called: hashCode() +SuperInitFails.java:101:33: compiler.err.cant.ref.before.ctor.called: this +SuperInitFails.java:106:14: compiler.err.cant.ref.before.ctor.called: this +SuperInitFails.java:110:20: compiler.err.not.encl.class: java.lang.Object +SuperInitFails.java:114:23: compiler.err.cant.ref.before.ctor.called: spliterator() +SuperInitFails.java:121:22: compiler.err.call.must.only.appear.in.ctor +SuperInitFails.java:127:9: compiler.err.cant.ref.before.ctor.called: SuperInitFails +SuperInitFails.java:135:9: compiler.err.non.canonical.constructor.invoke.another.constructor: SuperInitFails.Record1 +SuperInitFails.java:140:9: compiler.err.non.canonical.constructor.invoke.another.constructor: SuperInitFails.Record2 +SuperInitFails.java:157:50: compiler.err.cant.ref.before.ctor.called: x +SuperInitFails.java:161:15: compiler.err.cant.ref.before.ctor.called: SuperInitFails +SuperInitFails.java:170:13: compiler.err.cant.ref.before.ctor.called: x +SuperInitFails.java:174:17: compiler.err.cant.ref.before.ctor.called: x +SuperInitFails.java:178:24: compiler.err.cant.ref.before.ctor.called: x +SuperInitFails.java:182:18: compiler.err.cant.ref.before.ctor.called: x +SuperInitFails.java:188:32: compiler.err.cant.ref.before.ctor.called: x +SuperInitFails.java:208:25: compiler.err.return.before.superclass.initialized +SuperInitFails.java:213:33: compiler.err.ctor.calls.not.allowed.here +SuperInitFails.java:218:29: compiler.err.redundant.superclass.init +SuperInitFails.java:231:41: compiler.err.cant.ref.before.ctor.called: x +SuperInitFails.java:232:16: compiler.err.cant.ref.before.ctor.called: x +SuperInitFails.java:241:35: compiler.err.cant.ref.before.ctor.called: this +SuperInitFails.java:253:13: compiler.err.cant.ref.before.ctor.called: x +SuperInitFails.java:266:18: compiler.err.cant.ref.before.ctor.called: x +SuperInitFails.java:35:13: compiler.err.call.must.only.appear.in.ctor +SuperInitFails.java:39:14: compiler.err.call.must.only.appear.in.ctor +SuperInitFails.java:43:14: compiler.err.call.must.only.appear.in.ctor +SuperInitFails.java:47:13: compiler.err.call.must.only.appear.in.ctor +SuperInitFails.java:51:33: compiler.err.call.must.only.appear.in.ctor +SuperInitFails.java:55:32: compiler.err.call.must.only.appear.in.ctor +SuperInitFails.java:85:18: compiler.err.ctor.calls.not.allowed.here +SuperInitFails.java:91:13: compiler.err.return.before.superclass.initialized +SuperInitFails.java:152:18: compiler.err.call.must.only.appear.in.ctor +37 errors diff --git a/test/langtools/tools/javac/SuperInit/SuperInitFailsWarnings.out b/test/langtools/tools/javac/SuperInit/SuperInitFailsWarnings.out new file mode 100644 index 00000000000..9ad8128dae2 --- /dev/null +++ b/test/langtools/tools/javac/SuperInit/SuperInitFailsWarnings.out @@ -0,0 +1,34 @@ +SuperInitFails.java:106:14: compiler.err.cant.ref.before.ctor.called: this +SuperInitFails.java:110:20: compiler.err.not.encl.class: java.lang.Object +SuperInitFails.java:121:22: compiler.err.call.must.only.appear.in.ctor +SuperInitFails.java:135:9: compiler.err.non.canonical.constructor.invoke.another.constructor: SuperInitFails.Record1 +SuperInitFails.java:140:9: compiler.err.non.canonical.constructor.invoke.another.constructor: SuperInitFails.Record2 +SuperInitFails.java:157:50: compiler.err.cant.ref.before.ctor.called: x +SuperInitFails.java:161:15: compiler.err.cant.ref.before.ctor.called: SuperInitFails +SuperInitFails.java:213:33: compiler.err.ctor.calls.not.allowed.here +SuperInitFails.java:253:13: compiler.err.cant.ref.before.ctor.called: x +SuperInitFails.java:266:18: compiler.err.cant.ref.before.ctor.called: x +SuperInitFails.java:35:13: compiler.err.call.must.only.appear.in.ctor +SuperInitFails.java:39:14: compiler.err.call.must.only.appear.in.ctor +SuperInitFails.java:43:14: compiler.err.call.must.only.appear.in.ctor +SuperInitFails.java:47:13: compiler.err.call.must.only.appear.in.ctor +SuperInitFails.java:51:33: compiler.err.call.must.only.appear.in.ctor +SuperInitFails.java:55:32: compiler.err.call.must.only.appear.in.ctor +SuperInitFails.java:85:18: compiler.err.ctor.calls.not.allowed.here +SuperInitFails.java:152:18: compiler.err.call.must.only.appear.in.ctor +SuperInitFails.java:59:9: compiler.warn.would.not.be.allowed.in.prologue: hashCode() +SuperInitFails.java:64:13: compiler.warn.would.not.be.allowed.in.prologue: hashCode() +SuperInitFails.java:69:14: compiler.warn.would.not.be.allowed.in.prologue: hashCode() +SuperInitFails.java:74:28: compiler.warn.would.not.be.allowed.in.prologue: hashCode() +SuperInitFails.java:79:29: compiler.warn.would.not.be.allowed.in.prologue: hashCode() +SuperInitFails.java:101:33: compiler.warn.would.not.be.allowed.in.prologue: this +SuperInitFails.java:114:23: compiler.warn.would.not.be.allowed.in.prologue: spliterator() +SuperInitFails.java:127:9: compiler.warn.would.not.be.allowed.in.prologue: SuperInitFails +SuperInitFails.java:170:13: compiler.warn.would.not.be.allowed.in.prologue: x +SuperInitFails.java:174:17: compiler.warn.would.not.be.allowed.in.prologue: x +SuperInitFails.java:178:24: compiler.warn.would.not.be.allowed.in.prologue: x +SuperInitFails.java:182:18: compiler.warn.would.not.be.allowed.in.prologue: x +SuperInitFails.java:188:32: compiler.warn.would.not.be.allowed.in.prologue: x +SuperInitFails.java:231:41: compiler.warn.would.not.be.allowed.in.prologue: x +SuperInitFails.java:232:16: compiler.warn.would.not.be.allowed.in.prologue: x +SuperInitFails.java:241:35: compiler.warn.would.not.be.allowed.in.prologue: this diff --git a/test/langtools/tools/javac/SuperInit/SuperInitGood.java b/test/langtools/tools/javac/SuperInit/SuperInitGood.java index 5952ecc6615..b26f242c5dc 100644 --- a/test/langtools/tools/javac/SuperInit/SuperInitGood.java +++ b/test/langtools/tools/javac/SuperInit/SuperInitGood.java @@ -24,6 +24,9 @@ * @test * @bug 8194743 8345438 8356551 8349754 * @summary Test valid placements of super()/this() in constructors + * @run main SuperInitGood + * @build InitializationWarningTester + * @run main InitializationWarningTester SuperInitGood SuperInitGoodWarnings.out */ import java.util.concurrent.atomic.AtomicReference; @@ -44,6 +47,7 @@ static class Test0 { static class Test1 { Test1() { } + Test1(int a) { this.hashCode(); } @@ -421,11 +425,6 @@ public Test20(char x) { Test20.this.x = x; super(); } - public Test20(byte y) { - x = y; - this((int)y); - this.x++; - } } // allow creating and using local and anonymous classes before super() @@ -512,6 +511,20 @@ class Sub extends Test24 { } } + public static class Test25 { + public Test25(Object o) {} + + class Sub extends Test25 { + public Sub() { + super(new Object() { + void foo() { + getClass(); + } + }); + } + } + } + public static void main(String[] args) { new Test0(); new Test1(); @@ -559,5 +572,6 @@ public static void main(String[] args) { new Test22('x'); new Test23(); new Test24(); + new Test25(null); } } diff --git a/test/langtools/tools/javac/SuperInit/SuperInitGoodWarnings.out b/test/langtools/tools/javac/SuperInit/SuperInitGoodWarnings.out new file mode 100644 index 00000000000..00a3a5a4ecd --- /dev/null +++ b/test/langtools/tools/javac/SuperInit/SuperInitGoodWarnings.out @@ -0,0 +1 @@ +SuperInitGood.java:52:17: compiler.warn.would.not.be.allowed.in.prologue: hashCode() diff --git a/test/langtools/tools/javac/SuperInit/TEST.properties b/test/langtools/tools/javac/SuperInit/TEST.properties new file mode 100644 index 00000000000..9c18600d65b --- /dev/null +++ b/test/langtools/tools/javac/SuperInit/TEST.properties @@ -0,0 +1,10 @@ +modules = \ + jdk.compiler/com.sun.tools.javac.api \ + jdk.compiler/com.sun.tools.javac.main \ + jdk.jlink \ + jdk.compiler/com.sun.tools.javac.code \ + jdk.compiler/com.sun.tools.javac.comp \ + jdk.compiler/com.sun.tools.javac.file \ + jdk.compiler/com.sun.tools.javac.main \ + jdk.compiler/com.sun.tools.javac.tree \ + jdk.compiler/com.sun.tools.javac.util diff --git a/test/langtools/tools/javac/SuperInit/ValueClassSuperInitFails.java b/test/langtools/tools/javac/SuperInit/ValueClassSuperInitFails.java new file mode 100644 index 00000000000..6c4a1de0818 --- /dev/null +++ b/test/langtools/tools/javac/SuperInit/ValueClassSuperInitFails.java @@ -0,0 +1,181 @@ +/* + * @test /nodynamiccopyright/ + * @bug 8324873 + * @summary Permit additional statements before this/super in constructors + * @compile/fail/ref=ValueClassSuperInitFails.out -XDrawDiagnostics ValueClassSuperInitFails.java + * @enablePreview + */ +import java.util.function.Function; +abstract value class AR implements java.io.Serializable { + public AR(V initialValue) { + } + + public AR() { + } +} + +value class ValueClassSuperInitFails extends AR implements Iterable { + + private int x; + +/// GOOD EXAMPLES + + public ValueClassSuperInitFails() { // this should be OK + // super() + } + + public ValueClassSuperInitFails(Object x) { + this.x = x.hashCode(); // this should be OK + // super(); the compiler will introduce the super call at this location + } + + public ValueClassSuperInitFails(byte x) { + super(); // this should be OK + } + + public ValueClassSuperInitFails(char x) { + this((int)x); // this should be OK + } + +/// FAIL EXAMPLES + + { + this(1); // this should FAIL + } + + { + super(); // this should FAIL + } + + void normalMethod1() { + super(); // this should FAIL + } + + void normalMethod2() { + this(); // this should FAIL + } + + void normalMethod3() { + Runnable r = () -> super(); // this should FAIL + } + + void normalMethod4() { + Runnable r = () -> this(); // this should FAIL + } + + public ValueClassSuperInitFails(short x) { + hashCode(); // this should FAIL + //super(); + } + + public ValueClassSuperInitFails(float x) { + this.hashCode(); // this should FAIL + //super(); + } + + public ValueClassSuperInitFails(int x) { + super.hashCode(); // this should FAIL + //super(); + } + + public ValueClassSuperInitFails(long x) { + ValueClassSuperInitFails.this.hashCode(); // this should FAIL + //super(); + } + + public ValueClassSuperInitFails(double x) { + ValueClassSuperInitFails.super.hashCode(); // this should FAIL + //super(); + } + + public ValueClassSuperInitFails(byte[] x) { + { + super(); // this should FAIL + } + } + + public ValueClassSuperInitFails(char[] x) { + if (x.length == 0) + return; // this should FAIL + //super(); + } + + public ValueClassSuperInitFails(short[] x) { + this.x = x.length; // this should be OK + //super(); + } + + public ValueClassSuperInitFails(float[] x) { + System.identityHashCode(this); // this should FAIL + //super(); + } + + public ValueClassSuperInitFails(int[] x) { + this(this); // this should FAIL + } + + public ValueClassSuperInitFails(long[] x) { + this(Object.this); // this should FAIL + } + + public ValueClassSuperInitFails(double[] x) { + Iterable.super.spliterator(); // this should FAIL + //super(); + } + + public ValueClassSuperInitFails(byte[][] x) { + super(new Object() { + { + super(); // this should FAIL + } + }); + } + + public ValueClassSuperInitFails(char[][] x) { + new Inner1(); // this should FAIL + //super(); + } + + class Inner1 { + } + + record Record1(int value) { + Record1(float x) { // this should FAIL + } + } + + record Record2(int value) { + Record2(float x) { // this should FAIL + super(); + } + } + + @Override + public java.util.Iterator iterator() { + return null; + } + + public ValueClassSuperInitFails(short[][] x) { + class Foo { + Foo() { + ValueClassSuperInitFails.this.hashCode(); + } + }; + new Foo(); // this should FAIL + //super(); + } + + public ValueClassSuperInitFails(float[][] x) { + Runnable r = () -> { + super(); // this should FAIL + }; + } + + public ValueClassSuperInitFails(int[][] z) { + super((Function) f -> x); + } + + public ValueClassSuperInitFails(long[][] z) { + super(new Inner1()); // this should FAIL + } +} diff --git a/test/langtools/tools/javac/SuperInit/ValueClassSuperInitFails.out b/test/langtools/tools/javac/SuperInit/ValueClassSuperInitFails.out new file mode 100644 index 00000000000..9ce5ced7c4d --- /dev/null +++ b/test/langtools/tools/javac/SuperInit/ValueClassSuperInitFails.out @@ -0,0 +1,28 @@ +ValueClassSuperInitFails.java:67:9: compiler.err.cant.ref.before.ctor.called: hashCode() +ValueClassSuperInitFails.java:72:13: compiler.err.cant.ref.before.ctor.called: hashCode() +ValueClassSuperInitFails.java:77:14: compiler.err.cant.ref.before.ctor.called: hashCode() +ValueClassSuperInitFails.java:82:38: compiler.err.cant.ref.before.ctor.called: hashCode() +ValueClassSuperInitFails.java:87:39: compiler.err.cant.ref.before.ctor.called: hashCode() +ValueClassSuperInitFails.java:109:33: compiler.err.cant.ref.before.ctor.called: this +ValueClassSuperInitFails.java:114:14: compiler.err.cant.ref.before.ctor.called: this +ValueClassSuperInitFails.java:118:20: compiler.err.not.encl.class: java.lang.Object +ValueClassSuperInitFails.java:122:23: compiler.err.cant.ref.before.ctor.called: spliterator() +ValueClassSuperInitFails.java:129:22: compiler.err.call.must.only.appear.in.ctor +ValueClassSuperInitFails.java:135:9: compiler.err.cant.ref.before.ctor.called: ValueClassSuperInitFails +ValueClassSuperInitFails.java:143:9: compiler.err.non.canonical.constructor.invoke.another.constructor: ValueClassSuperInitFails.Record1 +ValueClassSuperInitFails.java:148:9: compiler.err.non.canonical.constructor.invoke.another.constructor: ValueClassSuperInitFails.Record2 +ValueClassSuperInitFails.java:161:46: compiler.err.cant.ref.before.ctor.called: hashCode() +ValueClassSuperInitFails.java:175:49: compiler.err.cant.ref.before.ctor.called: x +ValueClassSuperInitFails.java:179:15: compiler.err.cant.ref.before.ctor.called: ValueClassSuperInitFails +ValueClassSuperInitFails.java:43:13: compiler.err.call.must.only.appear.in.ctor +ValueClassSuperInitFails.java:47:14: compiler.err.call.must.only.appear.in.ctor +ValueClassSuperInitFails.java:51:14: compiler.err.call.must.only.appear.in.ctor +ValueClassSuperInitFails.java:55:13: compiler.err.call.must.only.appear.in.ctor +ValueClassSuperInitFails.java:59:33: compiler.err.call.must.only.appear.in.ctor +ValueClassSuperInitFails.java:63:32: compiler.err.call.must.only.appear.in.ctor +ValueClassSuperInitFails.java:93:18: compiler.err.ctor.calls.not.allowed.here +ValueClassSuperInitFails.java:99:13: compiler.err.return.before.superclass.initialized +ValueClassSuperInitFails.java:170:18: compiler.err.call.must.only.appear.in.ctor +- compiler.note.preview.filename: ValueClassSuperInitFails.java, DEFAULT +- compiler.note.preview.recompile +25 errors diff --git a/test/langtools/tools/javac/SuperInit/ValueClassSuperInitGood.java b/test/langtools/tools/javac/SuperInit/ValueClassSuperInitGood.java new file mode 100644 index 00000000000..ec4566362cf --- /dev/null +++ b/test/langtools/tools/javac/SuperInit/ValueClassSuperInitGood.java @@ -0,0 +1,424 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8324873 + * @summary Test valid placements of super()/this() in constructors + * @enablePreview + */ + +import java.util.ArrayList; +import java.util.List; + +public value class ValueClassSuperInitGood { + + ValueClassSuperInitGood(Object obj) { + } + + ValueClassSuperInitGood(int x) { + } + + // Default constructor provided by compiler + static value class Test0 { + } + + // No explicit calls to this()/super() + static abstract value class Test1 { + Test1() { + } + Test1(int a) { + super(); + this.hashCode(); + } + } + + // Explicit calls to this()/super() + static abstract value class Test2 { + static int i; + Test2() { + this(0); + } + Test2(int i) { + Test2.i = i; + super(); + } + Test2(T obj) { + this(java.util.Objects.hashCode(obj)); + } + public T get() { + return null; + } + } + + // Explicit this()/super() with stuff in front + static value class Test3 { + int x; + final int y; + final int z; + + Test3() { + new Object().hashCode(); + new Object().hashCode(); + this.x = new Object().hashCode(); + this.y = new Object().hashCode() % 17; + this.z = this.x + this.y; + super(); + } + } + + static abstract value class Test5Abstract { + Test5Abstract(Object obj) {} + } + + // Reference within constructor to outer class that's also my superclass + abstract value class Test5 extends Test5Abstract { + Test5(Object obj) { + if (obj == null) + throw new IllegalArgumentException(); + super(ValueClassSuperInitGood.this); // NOT a 'this' reference + } + } + + // Initialization blocks + value class Test6 { + final long startTime; + List l = new ArrayList<>(); + { + l.add(""); + } + Test6() { + long now = System.nanoTime(); + long then = now + 1000000L; + while (System.nanoTime() < then) { + try { + Thread.sleep(1); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + this.startTime = now; + super(); + } + } + + // Mix up inner classes, proxies, and super() calls + // Copied mostly from UnverifiableInitForNestedLocalClassTest.java + public static void test7(final String arg) { + final String inlined = " inlined "; + abstract value class LocalClass { + String m() { + return "LocalClass " + arg + inlined; + } + + abstract value class SubClass extends LocalClass { + @Override + String m() { + return "SubClass " + arg + inlined; + } + } + + value class SubSubClass extends SubClass { + @Override + String m() { + return "SubSubClass " + arg + inlined; + } + } + + value class AnotherLocal { + value class AnotherSub extends LocalClass { + AnotherSub() { + } + AnotherSub(int x) { + this((char)x); + } + AnotherSub(char y) { + super(); + } + @Override + String m() { + return "AnotherSub " + arg + inlined; + } + } + } + } + } + + // Anonymous inner class + public static void test8() { + new Test2(null) { + @Override + public Byte get() { + return (byte)-1; + } + }; + } + + // Qualified super() invocation + public static value class Test9 extends Test5 { + + public Test9(ValueClassSuperInitGood implicit, Object obj) { + obj.hashCode(); + implicit.super(obj); + } + } + + // Copied from WhichImplicitThis6 + public static abstract value class Test10 { + private int i; + public Test10(int i) { this.i = i; } + public value class Sub extends Test10 { + public Sub() { + super(i); // i is not inherited, so it is the enclosing i + } + } + } + + // Two constructors where only one invokes super() + public static value class Test11 { + public Test11() { + } + public Test11(int x) { + super(); + } + } + + // Nested version of the previous test + public static value class Test12 { + Test12() { + class Sub { + public Sub() { + } + public Sub(int j) { + super(); + } + } + } + } + + // Nested super()'s requiring initialization code appended + public static value class Test13 extends Test5Abstract { + final int x = new Object().hashCode(); + Test13() { + super(new Object() { + public void foo() { + class Bar { + final int y = new Object().hashCode(); + Bar() { + super(); + } + Bar(int ignored) { + } + } + } + }); + } + } + + // Qualified super() invocation with superclass instance + public static abstract value class Test15 { + + final String name; + + public Test15(String name) { + this.name = name; + } + + public abstract value class Test15b extends Test15 { + + public Test15b(String name) { + super(name); + } + + public String getName() { + return Test15.this.name; + } + } + } + + public static value class Test15c extends Test15.Test15b { + public Test15c(Test15 a, String name) { + a.super(name); + } + } + + // Mixing up outer instances, proxies, and initializers + public static value class Test16 { + + final String x = String.valueOf(new Object().hashCode()); + + public void run() { + + final String y = String.valueOf(new Object().hashCode()); + + class Sub { + + final String z; + + Sub(String z, int ignored) { + this(z, (float)ignored); + } + + Sub(String z, float ignored) { + this.z = z; + } + + Sub(String z, byte ignored) { + super(); + this.z = z; + } + + Sub(String z, char ignored) { + this(z, (int)ignored); + } + + String x() { + return x; + } + + String y() { + return y; + } + + String z() { + return z; + } + } + + final String z = String.valueOf(new Object().hashCode()); + + final Sub[] subs = new Sub[] { + new Sub(z, 1), + new Sub(z, -1), + new Sub(z, (float)0), + new Sub(z, (byte)0), + new Sub(z, (char)0) + }; + + for (int i = 0; i < subs.length; i++) { + //System.err.println("i = " + i); + final Sub sub = subs[i]; + final String subx = sub.x(); + final String suby = sub.y(); + final String subz = sub.z(); + if (!x.equals(subx)) + throw new RuntimeException("x=" + x + " but sub[" + i + "].x()=" + subx); + if (!y.equals(suby)) + throw new RuntimeException("y=" + y + " but sub[" + i + "].y()=" + suby); + if (!z.equals(subz)) + throw new RuntimeException("z=" + z + " but sub[" + i + "].z()=" + subz); + } + } + } + + // Records + public value class Test17 { + + record Rectangle(float length, float width) { } + + record StringHolder(String string) { + StringHolder { + java.util.Objects.requireNonNull(string); + } + } + + record ValueHolder(int value) { + ValueHolder(float x) { + if (Float.isNaN(x)) + throw new IllegalArgumentException(); + this((int)x); + } + } + } + + static abstract value class AR implements java.io.Serializable { + public AR(V initialValue) { + } + + public AR() { + } + + public final V get() { + return null; + } + } + + // super()/this() within outer try block but inside inner class + public static value class Test19 { + public Test19(int x) { + try { + new Test1(x) { + @Override + public int hashCode() { + return x ^ super.hashCode(); + } + }; + } catch (StackOverflowError e) { + // ignore + } + } + } + + public static value class Test20 { + private final int[] data1 = new int[10]; + private final int[] data2 = new int[10]; + private final int[] data3 = new int[10]; + Test20() { + for (int i = 0; i < data1.length; i++) { + data1[i] = i; // OK we are assigning to an array component + this.data2[i] = i; // OK we are assigning to an array component + Test20.this.data3[i] = i; // OK we are assigning to an array component + } + } + } + + public static void main(String[] args) { + new Test0(); + new Test1() {}; + new Test1(7) {}; + new Test2() {}; + new Test2<>(args) {}; + new Test3(); + new ValueClassSuperInitGood(3).new Test5(3) {}; + new ValueClassSuperInitGood(3).new Test6(); + ValueClassSuperInitGood.test7("foo"); + ValueClassSuperInitGood.test8(); + new Test9(new ValueClassSuperInitGood(5), "abc"); + new Test10(7) {}; + new Test11(9); + new Test12(); + new Test13(); + new Test15c(new Test15("foo"){}, "bar"); + new Test16().run(); + new Test17.StringHolder("foo"); + try { + new Test17.StringHolder(null); + throw new Error(); + } catch (NullPointerException e) { + // expected + } + new Test19(123); + new Test20(); + } +} diff --git a/test/langtools/tools/javac/annotations/typeAnnotations/classfile/BridgeShouldHaveNoInteriorAnnotationsTest.java b/test/langtools/tools/javac/annotations/typeAnnotations/classfile/BridgeShouldHaveNoInteriorAnnotationsTest.java index 0ec1a790d72..3b7547acbab 100644 --- a/test/langtools/tools/javac/annotations/typeAnnotations/classfile/BridgeShouldHaveNoInteriorAnnotationsTest.java +++ b/test/langtools/tools/javac/annotations/typeAnnotations/classfile/BridgeShouldHaveNoInteriorAnnotationsTest.java @@ -71,7 +71,7 @@ public void remove() { }; - // Expected output can't be directly encoded into NestedLambdasCastedTest !!! + // Expected output can't be directly encoded into BridgeShouldHaveNoInteriorAnnotationsTest !!! static class OutputExpectedOnceHolder { public String[] outputs = { "0: #120(): CAST, offset=1, type_index=0, location=[TYPE_ARGUMENT(0)]", diff --git a/test/langtools/tools/javac/annotations/typeAnnotations/referenceinfos/NewObjects.java b/test/langtools/tools/javac/annotations/typeAnnotations/referenceinfos/NewObjects.java index c19029ec753..bd113147be1 100644 --- a/test/langtools/tools/javac/annotations/typeAnnotations/referenceinfos/NewObjects.java +++ b/test/langtools/tools/javac/annotations/typeAnnotations/referenceinfos/NewObjects.java @@ -60,14 +60,14 @@ public String initObjectGeneric() { @TADescription(annotation = "TA", type = NEW, offset = ReferenceInfoUtil.IGNORE_VALUE) public String eqtestObject() { - return "void eqtestObject() { if (null == new @TA String()); }"; + return "void eqtestObject(String s) { if (s == new @TA String()); }"; } @TADescription(annotation = "TA", type = NEW, offset = ReferenceInfoUtil.IGNORE_VALUE) @TADescription(annotation = "TB", type = NEW, genericLocation = { 3, 0 }, offset = ReferenceInfoUtil.IGNORE_VALUE) public String eqtestObjectGeneric() { - return "void eqtestObjectGeneric() { if (null == new @TA ArrayList<@TB String >()); }"; + return "void eqtestObjectGeneric(ArrayList as) { if (as == new @TA ArrayList<@TB String >()); }"; } @TADescription(annotation = "TA", type = NEW, offset = ReferenceInfoUtil.IGNORE_VALUE, @@ -164,14 +164,14 @@ public String initObjectGenericRepeatableAnnotation() { @TADescription(annotation = "RTAs", type = NEW, offset = ReferenceInfoUtil.IGNORE_VALUE) public String eqtestObjectRepeatableAnnotation() { - return "void eqtestObject() { if (null == new @RTA @RTA String()); }"; + return "void eqtestObject(String s) { if (s == new @RTA @RTA String()); }"; } @TADescription(annotation = "RTAs", type = NEW, offset = ReferenceInfoUtil.IGNORE_VALUE) @TADescription(annotation = "RTBs", type = NEW, genericLocation = { 3, 0 }, offset = ReferenceInfoUtil.IGNORE_VALUE) public String eqtestObjectGenericRepeatableAnnotation() { - return "void eqtestObjectGeneric() { if (null == new @RTA @RTA ArrayList<@RTB @RTB String >()); }"; + return "void eqtestObjectGeneric(ArrayList as) { if (as == new @RTA @RTA ArrayList<@RTB @RTB String >()); }"; } @TADescription(annotation = "RTAs", type = NEW, offset = ReferenceInfoUtil.IGNORE_VALUE, diff --git a/test/langtools/tools/javac/api/TestApisWithProjections.java b/test/langtools/tools/javac/api/TestApisWithProjections.java new file mode 100644 index 00000000000..ad13f64e20d --- /dev/null +++ b/test/langtools/tools/javac/api/TestApisWithProjections.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* This test "covers"/verifies com.sun.tools.javac.model.JavacTypes#asMemberOf's calls + to asSuper work*properly with primitive types. +*/ + +/** + * @test + * @bug 8244712 + * @summary Test API usage with reference projection types. + * @ignore + * @library ./lib + * @modules jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.code + * @build ToolTester + */ + +import java.io.*; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; +import javax.tools.*; + +import com.sun.tools.javac.api.JavacTaskImpl; + +public class TestApisWithProjections extends ToolTester { + public static void main(String... args) throws Exception { + try (TestApisWithProjections t = new TestApisWithProjections()) { + t.run(); + } + } + + void run() throws Exception { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + File file = new File(test_src, "TestApisWithProjections.java"); + final Iterable compilationUnits = + fm.getJavaFileObjects(new File[] {file}); + task = (JavacTaskImpl)tool.getTask(pw, fm, null, null, null, compilationUnits); + elements = task.getElements(); + types = task.getTypes(); + + Iterable toplevels; + toplevels = ElementFilter.typesIn(task.enter(task.parse())); + + for (TypeElement clazz : toplevels) { + System.out.format("Testing %s:%n%n", clazz.getSimpleName()); + testParseType(clazz); + } + + pw.close(); + + String out = sw.toString(); + System.out.println(out); + + if (out.contains("com.sun.tools.javac.util")) + throw new Exception("Unexpected output from compiler"); + } + + void testParseType(TypeElement clazz) { + DeclaredType type = (DeclaredType)task.parseType("PrimitiveClass", clazz); + for (Element member : elements.getAllMembers((TypeElement)type.asElement())) { + TypeMirror mt = types.asMemberOf(type, member); + System.out.format("%s : %s -> %s%n", member.getSimpleName(), member.asType(), mt); + } + } + + JavacTaskImpl task; + Elements elements; + Types types; +} + +abstract class Base { + void foo(T t) {} +} + +primitive class PrimitiveClass extends Base { +} diff --git a/test/langtools/tools/javac/cantReferenceBeforeCtor/CantReferenceBeforeConstructorTest.out b/test/langtools/tools/javac/cantReferenceBeforeCtor/CantReferenceBeforeConstructorTest.out index 78b0b35e373..e7352bb162f 100644 --- a/test/langtools/tools/javac/cantReferenceBeforeCtor/CantReferenceBeforeConstructorTest.out +++ b/test/langtools/tools/javac/cantReferenceBeforeCtor/CantReferenceBeforeConstructorTest.out @@ -1,2 +1,2 @@ -CantReferenceBeforeConstructorTest.java:30:13: compiler.err.cant.ref.before.ctor.called: this +CantReferenceBeforeConstructorTest.java:30:17: compiler.err.cant.ref.before.ctor.called: CantReferenceBeforeConstructorTest.BB.CC() 1 error diff --git a/test/langtools/tools/javac/classfiles/InnerClasses/SyntheticClasses.java b/test/langtools/tools/javac/classfiles/InnerClasses/SyntheticClasses.java index 4585ef4fdef..80e724e47a7 100644 --- a/test/langtools/tools/javac/classfiles/InnerClasses/SyntheticClasses.java +++ b/test/langtools/tools/javac/classfiles/InnerClasses/SyntheticClasses.java @@ -44,6 +44,11 @@ private void run() throws IOException { File testClasses = new File(System.getProperty("test.classes")); for (File classFile : Objects.requireNonNull(testClasses.listFiles(f -> f.getName().endsWith(".class")))) { ClassModel cf = ClassFile.of().parse(classFile.toPath()); + if ((cf.flags().flagsMask() & (ClassFile.ACC_SYNTHETIC | ClassFile.ACC_ABSTRACT)) == ClassFile.ACC_SYNTHETIC) { + if ((cf.flags().flagsMask() & ClassFile.ACC_IDENTITY) == 0) { + throw new IllegalStateException("Missing ACC_IDENTITY on synthetic concrete identity class: " + cf.thisClass().asInternalName()); + } + } if (cf.thisClass().asInternalName().matches(".*\\$[0-9]+")) { EnclosingMethodAttribute encl = cf.findAttribute(Attributes.enclosingMethod()).orElse(null); if (encl != null) { diff --git a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerAnnotationsInInnerAnnotationTest.java b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerAnnotationsInInnerAnnotationTest.java index e0346be1cda..dcd55c947d9 100644 --- a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerAnnotationsInInnerAnnotationTest.java +++ b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerAnnotationsInInnerAnnotationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerAnnotationsInInnerClassTest.java b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerAnnotationsInInnerClassTest.java index 441d70a27d4..836e5bbffad 100644 --- a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerAnnotationsInInnerClassTest.java +++ b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerAnnotationsInInnerClassTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerAnnotationsInInnerEnumTest.java b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerAnnotationsInInnerEnumTest.java index 8ae5054ea1b..0eeabdd5afc 100644 --- a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerAnnotationsInInnerEnumTest.java +++ b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerAnnotationsInInnerEnumTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerAnnotationsInInnerInterfaceTest.java b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerAnnotationsInInnerInterfaceTest.java index aed539005d3..385d4bf29c7 100644 --- a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerAnnotationsInInnerInterfaceTest.java +++ b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerAnnotationsInInnerInterfaceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesHierarchyTest.java b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesHierarchyTest.java index 05b48e47013..6348210a25c 100644 --- a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesHierarchyTest.java +++ b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesHierarchyTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInAnonymousClassTest.java b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInAnonymousClassTest.java index f75b08d571b..682356efbc3 100644 --- a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInAnonymousClassTest.java +++ b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInAnonymousClassTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -58,7 +58,7 @@ public void setProperties() { @Override public List generateTestCases() { currentClassType = ClassType.CLASS; - setPrefix("class Anonymous {} {new Anonymous() {"); + setPrefix("class Anonymous { int f; } {new Anonymous() {"); // impose identity to make testing predictable. List sources = super.generateTestCases(); currentClassType = ClassType.INTERFACE; @@ -77,6 +77,6 @@ public List generateTestCases() { public void getAdditionalFlags(Map> class2Flags, ClassType type, Modifier... flags) { super.getAdditionalFlags(class2Flags, type, flags); class2Flags.put("Anonymous", getFlags(currentClassType, Arrays.asList(flags))); - class2Flags.put("1", new HashSet<>() {}); + class2Flags.put("1", Set.of()); } } diff --git a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInInnerAnnotationTest.java b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInInnerAnnotationTest.java index 7572ee96157..459db9c1124 100644 --- a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInInnerAnnotationTest.java +++ b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInInnerAnnotationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInInnerClassTest.java b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInInnerClassTest.java index be7891d2587..d43c8750d6c 100644 --- a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInInnerClassTest.java +++ b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInInnerClassTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInInnerEnumTest.java b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInInnerEnumTest.java index ea400defaea..619ccec9f76 100644 --- a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInInnerEnumTest.java +++ b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInInnerEnumTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInInnerInterfaceTest.java b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInInnerInterfaceTest.java index 9c1988f5dc1..ce6d766dbe9 100644 --- a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInInnerInterfaceTest.java +++ b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInInnerInterfaceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInLocalClassTest.java b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInLocalClassTest.java index aeb8398a6ab..27c200fe67a 100644 --- a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInLocalClassTest.java +++ b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesInLocalClassTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -52,7 +52,7 @@ public InnerClassesInLocalClassTest() { for (Modifier outerModifier : LOCAL_CLASS_MODIFIERS) { StringBuilder sb = new StringBuilder(); sb.append(outerModifier.getString()).append(' '); - sb.append("class Local {"); + sb.append("class Local { int f; "); // impose identity to make testing predictable. Map> class2Flags = new HashMap<>(); for (int i = 0; i < LOCAL_CLASS_MODIFIERS.length; ++i) { Modifier innerModifier = LOCAL_CLASS_MODIFIERS[i]; diff --git a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesIndexTest.java b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesIndexTest.java index 1c71cbb9881..55ca7c50098 100644 --- a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesIndexTest.java +++ b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesIndexTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesTest.java b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesTest.java index f07476fa84c..f89068ba697 100644 --- a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesTest.java +++ b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesTestBase.java b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesTestBase.java index 4b11dfdc944..8516de54bac 100644 --- a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesTestBase.java +++ b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerClassesTestBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -277,6 +277,8 @@ protected List generateTestCases() { .append(toString(outerMod)).append(' ') .append(outerClassType).append(' ') .append(prefix).append(' ').append('\n'); + if (outerClassType == ClassType.CLASS && outerMod.contains(Modifier.ABSTRACT)) + sb.append("int f;\n"); // impose identity to make testing predicatable int count = 0; Map> class2Flags = new HashMap<>(); List syntheticClasses = new ArrayList<>(); @@ -287,8 +289,10 @@ protected List generateTestCases() { privateConstructor = "private A" + count + "() {}"; syntheticClasses.add("new A" + count + "();"); } + String instField = innerClassType == ClassType.CLASS && innerMod.contains(Modifier.ABSTRACT) ? + "int f; " : ""; // impose identity to make testing predicatable sb.append(toString(innerMod)).append(' '); - sb.append(String.format("%s A%d {%s}\n", innerClassType, count, privateConstructor)); + sb.append(String.format("%s A%d { %s %s}\n", innerClassType, count, instField, privateConstructor)); Set flags = getFlags(innerClassType, innerMod); class2Flags.put("A" + count, flags); } @@ -322,7 +326,8 @@ protected Set getFlags(ClassType type, List mods) { } protected List getCompileOptions() { - return Collections.emptyList(); + // Use a release before value classes for now. + return List.of("--release", "25"); } private List> getAllCombinations(Modifier[] accessModifiers, Modifier[] otherModifiers) { @@ -439,7 +444,8 @@ public enum Modifier { PUBLIC("public"), PRIVATE("private"), PROTECTED("protected"), DEFAULT("default"), FINAL("final"), ABSTRACT("abstract"), - STATIC("static"), EMPTY(""); + STATIC("static"), EMPTY(""), + IDENTITY("identity"); private final String str; diff --git a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerEnumInInnerAnnotationTest.java b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerEnumInInnerAnnotationTest.java index ed17e63a0e3..9b60942336d 100644 --- a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerEnumInInnerAnnotationTest.java +++ b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerEnumInInnerAnnotationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerEnumInInnerEnumTest.java b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerEnumInInnerEnumTest.java index f946aac791d..e5ac325712e 100644 --- a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerEnumInInnerEnumTest.java +++ b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerEnumInInnerEnumTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerEnumInInnerInterfaceTest.java b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerEnumInInnerInterfaceTest.java index d8c73ac5d48..5aa33290497 100644 --- a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerEnumInInnerInterfaceTest.java +++ b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerEnumInInnerInterfaceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerEnumsInInnerClassTest.java b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerEnumsInInnerClassTest.java index 43aafed64b4..8bc77daa023 100644 --- a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerEnumsInInnerClassTest.java +++ b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerEnumsInInnerClassTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerInterfacesInInnerAnnotationTest.java b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerInterfacesInInnerAnnotationTest.java index 0879beef2ab..cdfca557835 100644 --- a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerInterfacesInInnerAnnotationTest.java +++ b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerInterfacesInInnerAnnotationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerInterfacesInInnerClassTest.java b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerInterfacesInInnerClassTest.java index 80cb3672bf8..8cd2f6603b0 100644 --- a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerInterfacesInInnerClassTest.java +++ b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerInterfacesInInnerClassTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerInterfacesInInnerEnumTest.java b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerInterfacesInInnerEnumTest.java index 632e32a332e..2bcedd6a9a0 100644 --- a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerInterfacesInInnerEnumTest.java +++ b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerInterfacesInInnerEnumTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerInterfacesInInnerInterfaceTest.java b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerInterfacesInInnerInterfaceTest.java index 3be3c38cd85..88fda23ecd0 100644 --- a/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerInterfacesInInnerInterfaceTest.java +++ b/test/langtools/tools/javac/classfiles/attributes/innerclasses/InnerInterfacesInInnerInterfaceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/langtools/tools/javac/classfiles/attributes/innerclasses/NoInnerClassesTest.java b/test/langtools/tools/javac/classfiles/attributes/innerclasses/NoInnerClassesTest.java index 3ebd621e4b7..e38ef440836 100644 --- a/test/langtools/tools/javac/classfiles/attributes/innerclasses/NoInnerClassesTest.java +++ b/test/langtools/tools/javac/classfiles/attributes/innerclasses/NoInnerClassesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/langtools/tools/javac/diags/CheckExamples.java b/test/langtools/tools/javac/diags/CheckExamples.java index 502f5fa8b88..75253a3a65e 100644 --- a/test/langtools/tools/javac/diags/CheckExamples.java +++ b/test/langtools/tools/javac/diags/CheckExamples.java @@ -45,12 +45,22 @@ /** * Check invariants for a set of examples. + * + * READ THIS IF THIS TEST FAILS AFTER ADDING A NEW KEY TO 'compiler.properties': + * The 'examples' subdirectory contains a number of examples which provoke + * the reporting of most of the compiler message keys. + * * -- each example should exactly declare the keys that will be generated when * it is run. + * -- this is done by the "// key:"-comment in each fine. * -- together, the examples should cover the set of resource keys in the * compiler.properties bundle. A list of exceptions may be given in the * not-yet.txt file. Entries on the not-yet.txt list should not be * covered by examples. + * -- some keys are only reported by the compiler when specific options are + * supplied. For the purposes of this test, this can be specified by a + * comment e.g. like this: "// options: -Xlint:empty" + * * When new keys are added to the resource bundle, it is strongly recommended * that corresponding new examples be added here, if at all practical, instead * of simply and lazily being added to the not-yet.txt list. diff --git a/test/langtools/tools/javac/diags/CheckResourceKeys.java b/test/langtools/tools/javac/diags/CheckResourceKeys.java index 1675d99275e..b29fffd4148 100644 --- a/test/langtools/tools/javac/diags/CheckResourceKeys.java +++ b/test/langtools/tools/javac/diags/CheckResourceKeys.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it diff --git a/test/langtools/tools/javac/diags/examples.not-yet.txt b/test/langtools/tools/javac/diags/examples.not-yet.txt index b29f20e5ebb..a1881daad84 100644 --- a/test/langtools/tools/javac/diags/examples.not-yet.txt +++ b/test/langtools/tools/javac/diags/examples.not-yet.txt @@ -59,6 +59,7 @@ compiler.warn.runtime.invisible.parameter.annotations # bad class file compiler.warn.runtime.visible.invisible.param.annotations.mismatch # bad class file compiler.misc.bad.signature # bad class file compiler.misc.bad.requires.flag # bad class file +compiler.misc.bad.access.flags # bad class file compiler.misc.bad.utf8.byte.sequence.at # bad class file compiler.misc.bad.type.annotation.value compiler.misc.class.file.not.found # ClassReader @@ -210,6 +211,7 @@ compiler.err.source.target.conflict compiler.err.target.default.source.conflict compiler.err.preview.not.latest compiler.err.preview.without.source.or.release + compiler.misc.illegal.signature # the compiler can now detect more non-denotable types before class writing # this one needs a forged class file to be reproduced @@ -217,6 +219,8 @@ compiler.err.annotation.unrecognized.attribute.name # this one is transitional (waiting for FFM API to exit preview) compiler.warn.restricted.method +# Value Objects +compiler.misc.feature.value.classes # Pending removal compiler.note.implicit.annotation.processing diff --git a/test/langtools/tools/javac/diags/examples/CantRefBeforeConstr.java b/test/langtools/tools/javac/diags/examples/CantRefBeforeConstr.java index cc83c86f80d..45728a3c86b 100644 --- a/test/langtools/tools/javac/diags/examples/CantRefBeforeConstr.java +++ b/test/langtools/tools/javac/diags/examples/CantRefBeforeConstr.java @@ -24,12 +24,11 @@ // key: compiler.err.cant.ref.before.ctor.called class Base { + int i; Base(int i) { } } class CantRefBeforeConstr extends Base { - int i; - CantRefBeforeConstr() { super(i); } diff --git a/test/langtools/tools/javac/diags/examples/ConcreteSuperclassOfValueClass.java b/test/langtools/tools/javac/diags/examples/ConcreteSuperclassOfValueClass.java new file mode 100644 index 00000000000..cc60b7a170a --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/ConcreteSuperclassOfValueClass.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * 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. + */ + +// key: compiler.err.concrete.supertype.for.value.class +// key: compiler.note.preview.filename +// key: compiler.note.preview.recompile +// options: --enable-preview -source ${jdk.version} + +public class ConcreteSuperclassOfValueClass { + static abstract value class V extends ConcreteSuperclassOfValueClass {} +} diff --git a/test/langtools/tools/javac/diags/examples/FieldAssigmentAfterSuper.java b/test/langtools/tools/javac/diags/examples/FieldAssigmentAfterSuper.java new file mode 100644 index 00000000000..f376ff6dc05 --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/FieldAssigmentAfterSuper.java @@ -0,0 +1,35 @@ +/* + * 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. + */ + +// key: compiler.err.strict.field.not.have.been.initialized.before.super +// key: compiler.note.preview.filename +// key: compiler.note.preview.recompile +// options: --enable-preview -source ${jdk.version} + +value class V { + int i; + V(int i) { + super(); + this.i = i; + } +} diff --git a/test/langtools/tools/javac/diags/examples/InitializationWarning.java b/test/langtools/tools/javac/diags/examples/InitializationWarning.java new file mode 100644 index 00000000000..c3ada72dfe1 --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/InitializationWarning.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// key: compiler.warn.would.not.be.allowed.in.prologue +// options: -Xlint:initialization --enable-preview -source ${jdk.version} + +public class InitializationWarning { + Object o = null; + + InitializationWarning(Object oo) { + this.o = oo; + } +} diff --git a/test/langtools/tools/javac/diags/examples/NonAbstractValueClassCantBeSealed.java b/test/langtools/tools/javac/diags/examples/NonAbstractValueClassCantBeSealed.java new file mode 100644 index 00000000000..1f32e425f3d --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/NonAbstractValueClassCantBeSealed.java @@ -0,0 +1,30 @@ +/* + * 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. + */ + +// key: compiler.err.non.abstract.value.class.cant.be.sealed.or.non.sealed +// key: compiler.note.preview.filename +// key: compiler.note.preview.recompile +// options: --enable-preview -source ${jdk.version} + +sealed value class NonAbstractValueClassCantBeSealed {} + diff --git a/test/langtools/tools/javac/diags/examples/SerializableIdentityClass.java b/test/langtools/tools/javac/diags/examples/SerializableIdentityClass.java new file mode 100644 index 00000000000..bc007a3c459 --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/SerializableIdentityClass.java @@ -0,0 +1,37 @@ +/* + * 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. + */ + +// key: compiler.warn.serializable.value.class.without.write.replace.2 +// key: compiler.note.preview.filename +// key: compiler.note.preview.recompile +// options: -Xlint:serial --enable-preview -source ${jdk.version} + +import java.io.Serializable; + +abstract value class ValueSuper implements Serializable { + private static final long serialVersionUID = 1; +} + +class SerializableIdentityClass extends ValueSuper { + private static final long serialVersionUID = 1; +} diff --git a/test/langtools/tools/javac/diags/examples/SerializableValueClass.java b/test/langtools/tools/javac/diags/examples/SerializableValueClass.java new file mode 100644 index 00000000000..7c441230306 --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/SerializableValueClass.java @@ -0,0 +1,33 @@ +/* + * 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. + */ + +// key: compiler.warn.serializable.value.class.without.write.replace.1 +// key: compiler.note.preview.filename +// key: compiler.note.preview.recompile +// options: -Xlint:serial --enable-preview -source ${jdk.version} + +import java.io.Serializable; + +value class SerializableValueClass implements Serializable { + private static final long serialVersionUID = 1; +} diff --git a/test/langtools/tools/javac/diags/examples/StrictFieldNotInitialized.java b/test/langtools/tools/javac/diags/examples/StrictFieldNotInitialized.java new file mode 100644 index 00000000000..5633b85045c --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/StrictFieldNotInitialized.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// key: compiler.err.strict.field.not.have.been.initialized.before.super +// key: compiler.note.preview.filename +// key: compiler.note.preview.recompile +// options: --enable-preview -source ${jdk.version} + +value class Point { + int x; + int y; + Point (int x, int y) { + this.x = x; + // y hasn't been initialized + } +} \ No newline at end of file diff --git a/test/langtools/tools/javac/diags/examples/SuperClassMethodCannotBeSynchronized.java b/test/langtools/tools/javac/diags/examples/SuperClassMethodCannotBeSynchronized.java new file mode 100644 index 00000000000..da4974dd027 --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/SuperClassMethodCannotBeSynchronized.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * 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. + */ + +// key: compiler.err.super.class.method.cannot.be.synchronized +// key: compiler.note.preview.filename +// key: compiler.note.preview.recompile +// options: --enable-preview -source ${jdk.version} + +public abstract class SuperClassMethodCannotBeSynchronized { + synchronized void foo() {} +} + +value class V extends SuperClassMethodCannotBeSynchronized {} diff --git a/test/langtools/tools/javac/diags/examples/ThisExposedPrematurely.java b/test/langtools/tools/javac/diags/examples/ThisExposedPrematurely.java new file mode 100644 index 00000000000..7bd44a42efe --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/ThisExposedPrematurely.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * 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. + */ + +// key: compiler.err.cant.ref.before.ctor.called +// key: compiler.note.preview.filename +// key: compiler.note.preview.recompile +// options: --enable-preview -source ${jdk.version} + +value class ThisExposedPrematurely { + int x; + ThisExposedPrematurely() { + foo(this); // Error. + x = 10; + } + void foo(ThisExposedPrematurely v) {} +} diff --git a/test/langtools/tools/javac/diags/examples/TypeReqIdentity.java b/test/langtools/tools/javac/diags/examples/TypeReqIdentity.java new file mode 100644 index 00000000000..6db97d7595c --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/TypeReqIdentity.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * 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. + */ + +// key: compiler.misc.type.req.identity +// key: compiler.err.type.found.req +// key: compiler.note.preview.filename +// key: compiler.note.preview.recompile +// options: --enable-preview -source ${jdk.version} + +value class TypeReqIdentity { + { synchronized (this) {} } +} diff --git a/test/langtools/tools/javac/diags/examples/ValueClassWithFinalizeMethod.java b/test/langtools/tools/javac/diags/examples/ValueClassWithFinalizeMethod.java new file mode 100644 index 00000000000..9e651b7f0b6 --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/ValueClassWithFinalizeMethod.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. + * + * 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. + */ + +// key: compiler.warn.value.finalize +// key: compiler.note.preview.filename +// key: compiler.note.preview.recompile +// key: compiler.warn.has.been.deprecated.for.removal +// options: --enable-preview -source ${jdk.version} + +value class ValueWithFinalizeMethod { + protected void finalize() throws Throwable { } +} diff --git a/test/langtools/tools/javac/diags/examples/ValueWithIdentitySuper.java b/test/langtools/tools/javac/diags/examples/ValueWithIdentitySuper.java new file mode 100644 index 00000000000..1f6bb0338ec --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/ValueWithIdentitySuper.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// key: compiler.err.value.type.has.identity.super.type +// key: compiler.note.preview.filename +// key: compiler.note.preview.recompile +// options: --enable-preview -source ${jdk.version} + +abstract class AC {} +value class V extends AC {} diff --git a/test/langtools/tools/javac/failover/CheckAttributedTree.java b/test/langtools/tools/javac/failover/CheckAttributedTree.java index 9b28d2bebd4..9f6d7a060c2 100644 --- a/test/langtools/tools/javac/failover/CheckAttributedTree.java +++ b/test/langtools/tools/javac/failover/CheckAttributedTree.java @@ -406,7 +406,7 @@ private boolean mandatoryType(JCTree that) { that.hasTag(CLASSDEF); } - private final List excludedFields = Arrays.asList("varargsElement", "targetType"); + private final List excludedFields = Arrays.asList("varargsElement", "targetType", "factoryProduct"); void check(boolean ok, String label, Info self) { if (!ok) { diff --git a/test/langtools/tools/javac/flags/FlagsTest.java b/test/langtools/tools/javac/flags/FlagsTest.java index a7a9051af33..9c3bdbf72c7 100644 --- a/test/langtools/tools/javac/flags/FlagsTest.java +++ b/test/langtools/tools/javac/flags/FlagsTest.java @@ -24,7 +24,7 @@ /** * @test - * @bug 8211138 8362885 + * @bug 8211138 8215246 8362885 * @summary Missing Flag enum constants * @library /tools/javac/lib * @modules jdk.compiler/com.sun.tools.javac.code diff --git a/test/langtools/tools/javac/implicitThis/NewBeforeOuterConstructed.out b/test/langtools/tools/javac/implicitThis/NewBeforeOuterConstructed.out index a938776a8cb..0c251910be0 100644 --- a/test/langtools/tools/javac/implicitThis/NewBeforeOuterConstructed.out +++ b/test/langtools/tools/javac/implicitThis/NewBeforeOuterConstructed.out @@ -1,2 +1,2 @@ -NewBeforeOuterConstructed.java:27:21: compiler.err.cant.ref.before.ctor.called: this +NewBeforeOuterConstructed.java:27:21: compiler.err.cant.ref.before.ctor.called: NewBeforeOuterConstructed 1 error diff --git a/test/langtools/tools/javac/implicitThis/NewBeforeOuterConstructed2.out b/test/langtools/tools/javac/implicitThis/NewBeforeOuterConstructed2.out index 68e957aacf5..4c82aa9d32e 100644 --- a/test/langtools/tools/javac/implicitThis/NewBeforeOuterConstructed2.out +++ b/test/langtools/tools/javac/implicitThis/NewBeforeOuterConstructed2.out @@ -1,2 +1,2 @@ -NewBeforeOuterConstructed2.java:20:35: compiler.err.cant.ref.before.ctor.called: this +NewBeforeOuterConstructed2.java:20:35: compiler.err.cant.ref.before.ctor.called: NewBeforeOuterConstructed2 1 error diff --git a/test/langtools/tools/javac/lambda/MethodReferenceNoThisTest.out b/test/langtools/tools/javac/lambda/MethodReferenceNoThisTest.out index a411c340484..f5a535b73ff 100644 --- a/test/langtools/tools/javac/lambda/MethodReferenceNoThisTest.out +++ b/test/langtools/tools/javac/lambda/MethodReferenceNoThisTest.out @@ -1,2 +1,2 @@ -MethodReferenceNoThisTest.java:22:15: compiler.err.cant.ref.before.ctor.called: this +MethodReferenceNoThisTest.java:22:15: compiler.err.cant.ref.before.ctor.called: MethodReferenceNoThisTest 1 error diff --git a/test/langtools/tools/javac/lambda/deduplication/ClassFieldDeduplication.java b/test/langtools/tools/javac/lambda/deduplication/ClassFieldDeduplication.java index 89fd4d6c1a5..e489980128a 100644 --- a/test/langtools/tools/javac/lambda/deduplication/ClassFieldDeduplication.java +++ b/test/langtools/tools/javac/lambda/deduplication/ClassFieldDeduplication.java @@ -24,6 +24,7 @@ /* * @test * @bug 8202141 + * @enablePreview * @summary Verify that .class synthetic Symbols are not duplicated. * @library /tools/javac/lib * @modules jdk.compiler/com.sun.tools.javac.api diff --git a/test/langtools/tools/javac/lambda/methodReference/MethodReferenceInConstructorInvocation.out b/test/langtools/tools/javac/lambda/methodReference/MethodReferenceInConstructorInvocation.out index 1f850a022b2..1d41e3776b1 100644 --- a/test/langtools/tools/javac/lambda/methodReference/MethodReferenceInConstructorInvocation.out +++ b/test/langtools/tools/javac/lambda/methodReference/MethodReferenceInConstructorInvocation.out @@ -1,3 +1,3 @@ MethodReferenceInConstructorInvocation.java:20:21: compiler.err.cant.ref.before.ctor.called: super -MethodReferenceInConstructorInvocation.java:24:30: compiler.err.cant.ref.before.ctor.called: super +MethodReferenceInConstructorInvocation.java:24:36: compiler.err.cant.ref.before.ctor.called: getString() 2 errors diff --git a/test/langtools/tools/javac/launcher/SourceLauncherTest.java b/test/langtools/tools/javac/launcher/SourceLauncherTest.java index 6b147d32d00..712b2b62902 100644 --- a/test/langtools/tools/javac/launcher/SourceLauncherTest.java +++ b/test/langtools/tools/javac/launcher/SourceLauncherTest.java @@ -33,6 +33,7 @@ * java.base/jdk.internal.module * @build toolbox.JavaTask toolbox.JavacTask toolbox.TestRunner toolbox.ToolBox * @run main SourceLauncherTest + * @ignore Verifier error */ import java.lang.classfile.*; diff --git a/test/langtools/tools/javac/lib/JavacTestingAbstractProcessor.java b/test/langtools/tools/javac/lib/JavacTestingAbstractProcessor.java index e181ef8bf63..685466aa1ae 100644 --- a/test/langtools/tools/javac/lib/JavacTestingAbstractProcessor.java +++ b/test/langtools/tools/javac/lib/JavacTestingAbstractProcessor.java @@ -98,10 +98,11 @@ protected void addExports(String moduleName, String... packageNames) { for (String packageName : packageNames) { try { ModuleLayer layer = ModuleLayer.boot(); - Optional m = layer.findModule(moduleName); - if (!m.isPresent()) + // removing dependency on java.util.Optional, valhalla only, to avoid VM warnings + Module m = layer.findModule(moduleName).get(); + if (m == null) throw new Error("module not found: " + moduleName); - m.get().addExports(packageName, getClass().getModule()); + m.addExports(packageName, getClass().getModule()); } catch (Exception e) { throw new Error("failed to add exports for " + moduleName + "/" + packageName); } diff --git a/test/langtools/tools/javac/patterns/BreakAndLoops.java b/test/langtools/tools/javac/patterns/BreakAndLoops.java index 31ad8908f53..00796c6cd60 100644 --- a/test/langtools/tools/javac/patterns/BreakAndLoops.java +++ b/test/langtools/tools/javac/patterns/BreakAndLoops.java @@ -237,4 +237,4 @@ public String expand(String optParameter) { return code; } } -} \ No newline at end of file +} diff --git a/test/langtools/tools/javac/patterns/NullsInDeconstructionPatterns2.java b/test/langtools/tools/javac/patterns/NullsInDeconstructionPatterns2.java index 1ba2d04c0b9..0e30978f0fc 100644 --- a/test/langtools/tools/javac/patterns/NullsInDeconstructionPatterns2.java +++ b/test/langtools/tools/javac/patterns/NullsInDeconstructionPatterns2.java @@ -28,6 +28,7 @@ * @enablePreview * @compile NullsInDeconstructionPatterns2.java * @run main NullsInDeconstructionPatterns2 + * @ignore Verifier error */ import java.util.Objects; diff --git a/test/langtools/tools/javac/patterns/PrettyTest.java b/test/langtools/tools/javac/patterns/PrettyTest.java index 5425127e91b..0e988d4a865 100644 --- a/test/langtools/tools/javac/patterns/PrettyTest.java +++ b/test/langtools/tools/javac/patterns/PrettyTest.java @@ -26,6 +26,7 @@ * @enablePreview * @summary Test behavior of Pretty * @modules jdk.compiler + * @ignore Verifier error */ import java.io.IOException; diff --git a/test/langtools/tools/javac/patterns/T8314226.java b/test/langtools/tools/javac/patterns/T8314226.java index ac6cea63647..04bb912c589 100644 --- a/test/langtools/tools/javac/patterns/T8314226.java +++ b/test/langtools/tools/javac/patterns/T8314226.java @@ -27,6 +27,7 @@ * @enablePreview * @compile T8314226.java * @run main T8314226 + * @ignore Verifier error */ public class T8314226 { diff --git a/test/langtools/tools/javac/patterns/Unnamed.java b/test/langtools/tools/javac/patterns/Unnamed.java index 34e325642fd..126f54428e7 100644 --- a/test/langtools/tools/javac/patterns/Unnamed.java +++ b/test/langtools/tools/javac/patterns/Unnamed.java @@ -27,6 +27,7 @@ * @summary Compiler Implementation for Unnamed patterns and variables * @compile Unnamed.java * @run main Unnamed + * @ignore Verifier error */ import java.util.Objects; diff --git a/test/langtools/tools/javac/platform/createsymbols/CreateSymbolsTestImpl.java b/test/langtools/tools/javac/platform/createsymbols/CreateSymbolsTestImpl.java index 2680ca6488f..6c6cf4f0f35 100644 --- a/test/langtools/tools/javac/platform/createsymbols/CreateSymbolsTestImpl.java +++ b/test/langtools/tools/javac/platform/createsymbols/CreateSymbolsTestImpl.java @@ -31,7 +31,7 @@ * jdk.compiler/com.sun.tools.javac.main * jdk.compiler/com.sun.tools.javac.util * @clean * - * @run main/othervm CreateSymbolsTest + * @run junit/othervm CreateSymbolsTest */ import java.io.File; @@ -67,6 +67,8 @@ import java.util.jar.JarFile; import java.util.stream.Collectors; import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; import toolbox.JavacTask; import toolbox.Task; import toolbox.Task.Expect; @@ -86,23 +88,6 @@ public class CreateSymbolsTestImpl { static final String CREATE_SYMBOLS_NAME = "symbolgenerator.CreateSymbols"; - public static void main(String... args) throws Exception { - new CreateSymbolsTestImpl().doTest(); - } - - void doTest() throws Exception { - boolean testRun = false; - for (Method m : CreateSymbolsTestImpl.class.getDeclaredMethods()) { - if (m.isAnnotationPresent(Test.class)) { - m.invoke(this); - testRun = true; - } - } - if (!testRun) { - throw new IllegalStateException("No tests found."); - } - } - @Test void testMethodRemoved() throws Exception { doTest("package t; public class T { public void m() { } }", @@ -859,28 +844,28 @@ class name api/Ann class name api/Api header extends nonapi/Impl$Nested$Exp flags 21 - innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 9 - innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 9 + innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 29 + innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 29 method name descriptor ()V flags 1 class name nonapi/Impl header extends java/lang/Object nestMembers nonapi/Impl$Nested,nonapi/Impl$Nested$Exp flags 21 - innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 9 - innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 9 + innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 29 + innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 29 field name C descriptor Ljava/lang/String; constantValue flags 19 method name descriptor ()V flags 1 method name test descriptor ()V flags 1 class name nonapi/Impl$Nested header extends java/lang/Object nestHost nonapi/Impl flags 21 classAnnotations @Lapi/Ann; - innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 9 - innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 9 + innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 29 + innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 29 method name descriptor ()V flags 1 class name nonapi/Impl$Nested$Exp header extends nonapi/Impl$Nested implements java/lang/Runnable nestHost nonapi/Impl flags 21 - innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 9 - innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 9 + innerclass innerClass nonapi/Impl$Nested outerClass nonapi/Impl innerClassName Nested flags 29 + innerclass innerClass nonapi/Impl$Nested$Exp outerClass nonapi/Impl$Nested innerClassName Exp flags 29 method name descriptor ()V flags 1 method name run descriptor ()V flags 1 method name get descriptor ()Lnonapi/Impl$OtherNested; flags 1 @@ -1352,8 +1337,4 @@ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) th } }); } - - @Retention(RetentionPolicy.RUNTIME) - @interface Test { - } } diff --git a/test/langtools/tools/javac/processing/GenerateAndError.java b/test/langtools/tools/javac/processing/GenerateAndError.java index c1beca99059..5313ae7250a 100644 --- a/test/langtools/tools/javac/processing/GenerateAndError.java +++ b/test/langtools/tools/javac/processing/GenerateAndError.java @@ -26,6 +26,7 @@ * @bug 8217381 * @summary Check error are convenient when AP generates a source file and * an error in the same round + * @enablePreview * @library /tools/javac/lib * @modules jdk.compiler * @build JavacTestingAbstractProcessor GenerateAndError diff --git a/test/langtools/tools/javac/processing/errors/TestErrorCount.java b/test/langtools/tools/javac/processing/errors/TestErrorCount.java index 9c20ed5d16a..721dd80678d 100644 --- a/test/langtools/tools/javac/processing/errors/TestErrorCount.java +++ b/test/langtools/tools/javac/processing/errors/TestErrorCount.java @@ -25,6 +25,7 @@ * @test * @bug 6988079 * @summary Errors reported via Messager.printMessage(ERROR,"error message") are not tallied correctly + * @enablePreview * @library /tools/javac/lib * @modules java.compiler * jdk.compiler diff --git a/test/langtools/tools/javac/processing/model/TestSourceVersion.java b/test/langtools/tools/javac/processing/model/TestSourceVersion.java index 3a56bf76007..621cfabffe8 100644 --- a/test/langtools/tools/javac/processing/model/TestSourceVersion.java +++ b/test/langtools/tools/javac/processing/model/TestSourceVersion.java @@ -104,7 +104,7 @@ private static void testRestrictedKeywords() { Set.of("open", "module", "requires", "transitive", "exports", "opens", "to", "uses", "provides", "with", // Assume "record" and "sealed" will be restricted keywords. - "record", "sealed"); + "record", "sealed", "value"); for (String key : restrictedKeywords) { for (SourceVersion version : SourceVersion.values()) { @@ -138,6 +138,18 @@ private static void testYield() { } } + private static void testValue() { + for (SourceVersion version : SourceVersion.values()) { + Predicate isKeywordVersion = (String s) -> isKeyword(s, version); + Predicate isNameVersion = (String s) -> isName(s, version); + + for (String name : List.of("value", "foo.value", "value.foo")) { + check(false, name, isKeywordVersion, "keyword", version); + check(true, name, isNameVersion, "name", version); + } + } + } + private static void check(boolean expected, String input, Predicate predicate, diff --git a/test/langtools/tools/javac/processing/model/TestSymtabItems.java b/test/langtools/tools/javac/processing/model/TestSymtabItems.java index 169adc45c36..606d821bb87 100644 --- a/test/langtools/tools/javac/processing/model/TestSymtabItems.java +++ b/test/langtools/tools/javac/processing/model/TestSymtabItems.java @@ -47,6 +47,8 @@ import javax.lang.model.util.*; import com.sun.tools.javac.code.Symbol.ClassSymbol; +import com.sun.tools.javac.code.Symbol.Completer; +import com.sun.tools.javac.code.Symbol.ModuleSymbol; import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.file.JavacFileManager; import com.sun.tools.javac.main.JavaCompiler; diff --git a/test/langtools/tools/javac/processing/model/completionfailure/NoAbortForBadClassFile.java b/test/langtools/tools/javac/processing/model/completionfailure/NoAbortForBadClassFile.java index 65029c711b0..eef68ec2a04 100644 --- a/test/langtools/tools/javac/processing/model/completionfailure/NoAbortForBadClassFile.java +++ b/test/langtools/tools/javac/processing/model/completionfailure/NoAbortForBadClassFile.java @@ -213,7 +213,7 @@ private List complete(Path test, List order, Path missing, boolean long flags = sym.flags_field; - flags &= ~(Flags.CLASS_SEEN | Flags.SOURCE_SEEN); + flags &= ~(Flags.CLASS_SEEN | Flags.SOURCE_SEEN | Flags.IDENTITY_TYPE); // earlier ACC_SUPER was dropped by javac. result.add("sym: " + sym.flatname + ", " + sym.owner.flatName() + ", " + sym.type + ", " + sym.members_field + ", " + flags); diff --git a/test/langtools/tools/javac/processing/model/element/TestValueClasses.java b/test/langtools/tools/javac/processing/model/element/TestValueClasses.java new file mode 100644 index 00000000000..d96443fc80e --- /dev/null +++ b/test/langtools/tools/javac/processing/model/element/TestValueClasses.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8308590 + * @summary Test basic modeling for value classes + * @library /tools/lib /tools/javac/lib + * @modules + * jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * @build toolbox.ToolBox toolbox.JavacTask JavacTestingAbstractProcessor + * @run main TestValueClasses + */ + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; + +import javax.annotation.processing.*; +import javax.lang.model.*; +import javax.lang.model.element.*; +import javax.lang.model.type.*; +import javax.lang.model.util.*; +import java.time.*; + +import toolbox.JavacTask; +import toolbox.Task; +import toolbox.Task.Mode; +import toolbox.Task.OutputKind; +import toolbox.TestRunner; +import toolbox.ToolBox; + +public class TestValueClasses extends TestRunner { + + protected ToolBox tb; + + TestValueClasses() { + super(System.err); + tb = new ToolBox(); + } + + public static void main(String... args) throws Exception { + new TestValueClasses().runTests(); + } + + /** + * Run all methods annotated with @Test, and throw an exception if any + * errors are reported.. + * + * @throws Exception if any errors occurred + */ + protected void runTests() throws Exception { + runTests(m -> new Object[] { Paths.get(m.getName()) }); + } + + Path[] findJavaFiles(Path... paths) throws IOException { + return tb.findJavaFiles(paths); + } + + void checkOutputContains(String log, String... expect) throws Exception { + for (String e : expect) { + if (!log.contains(e)) { + throw new Exception("expected output not found: " + e); + } + } + } + + @Test + public void testValueClassesProcessor(Path base) throws Exception { + Path src = base.resolve("src"); + Path r = src.resolve("Test"); + + Path classes = base.resolve("classes"); + + Files.createDirectories(classes); + + tb.writeJavaFiles(r, + """ + interface Interface {} + + value class ValueClass {} + + class IdentityClass {} + + value record ValueRecord() {} + """ + ); + + List expected = List.of( + "- compiler.note.proc.messager: visiting: Interface Modifiers: [abstract]", + "- compiler.note.proc.messager: visiting: ValueClass Modifiers: [value, final]", + "- compiler.note.proc.messager: constructor modifiers: []", + "- compiler.note.proc.messager: visiting: IdentityClass Modifiers: []", + "- compiler.note.proc.messager: constructor modifiers: []", + "- compiler.note.proc.messager: visiting: ValueRecord Modifiers: [value, final]", + "- compiler.note.proc.messager: constructor modifiers: []", + "- compiler.note.preview.filename: Interface.java, DEFAULT", + "- compiler.note.preview.recompile" + ); + + for (Mode mode : new Mode[] {Mode.API}) { + List log = new JavacTask(tb, mode) + .options("--enable-preview", "-source", String.valueOf(Runtime.version().feature()), "-processor", ValueClassesProcessor.class.getName(), + "-XDrawDiagnostics") + .files(findJavaFiles(src)) + .outdir(classes) + .run() + .writeAll() + .getOutputLines(Task.OutputKind.DIRECT); + + System.out.println("log:" +log); + + if (!expected.equals(log)) { + if (expected.size() == log.size()) { + for (int i = 0; i < expected.size(); i++) { + if (!expected.get(i).equals(log.get(i))) { + System.err.println("failing at line " + (i + 1)); + System.err.println(" expecting " + expected.get(i)); + System.err.println(" found " + log.get(i)); + } + } + } else { + System.err.println("expected and log lists differ in length"); + } + throw new AssertionError("Unexpected output: " + log); + } + } + } + + public static final class ValueClassesProcessor extends JavacTestingAbstractProcessor { + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + if (!roundEnv.processingOver()) { + Messager messager = processingEnv.getMessager(); + ElementScanner scanner = new ValueClassesScanner(messager); + for(Element rootElement : roundEnv.getRootElements()) { + scanner.visit(rootElement); + } + } + return true; + } + + class ValueClassesScanner extends ElementScanner { + + Messager messager; + + public ValueClassesScanner(Messager messager) { + this.messager = messager; + } + + @Override + public Void visitType(TypeElement element, Void p) { + messager.printNote("visiting: " + element.getSimpleName() + " Modifiers: " + element.getModifiers()); + List enclosedElements = element.getEnclosedElements(); + for (Element elem : enclosedElements) { + System.out.println("visiting " + elem.getSimpleName()); + switch (elem.getSimpleName().toString()) { + case "": + messager.printNote(" constructor modifiers: " + elem.getModifiers()); + break; + default: + break; + } + } + return super.visitType(element, p); + } + } + } +} diff --git a/test/langtools/tools/javac/processing/werror/WErrorLast.java b/test/langtools/tools/javac/processing/werror/WErrorLast.java index 4987695bdac..83010f19ff7 100644 --- a/test/langtools/tools/javac/processing/werror/WErrorLast.java +++ b/test/langtools/tools/javac/processing/werror/WErrorLast.java @@ -24,6 +24,7 @@ /* * @test 6403456 * @summary -Werror should work with annotation processing + * @enablePreview * @library /tools/javac/lib * @modules java.compiler * jdk.compiler diff --git a/test/langtools/tools/javac/records/RecordReading.java b/test/langtools/tools/javac/records/RecordReading.java index 6133a04fca9..9f51f82f5e1 100644 --- a/test/langtools/tools/javac/records/RecordReading.java +++ b/test/langtools/tools/javac/records/RecordReading.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. * 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,17 +25,18 @@ * @test * @summary test the records can be read by javac properly * @library /tools/lib + * @bug 8273018 * @modules jdk.compiler/com.sun.tools.javac.api * jdk.compiler/com.sun.tools.javac.main * @build toolbox.ToolBox toolbox.JavacTask * @run main RecordReading */ - import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.List; import java.util.Objects; import toolbox.TestRunner; import toolbox.ToolBox; @@ -125,5 +126,4 @@ public R(int i, throw new AssertionError("Unexpected output: " + output); } } - } diff --git a/test/langtools/tools/javac/records/mandated_members/CheckRecordMembers.java b/test/langtools/tools/javac/records/mandated_members/CheckRecordMembers.java index f0aab5985d5..d77b68a8e34 100644 --- a/test/langtools/tools/javac/records/mandated_members/CheckRecordMembers.java +++ b/test/langtools/tools/javac/records/mandated_members/CheckRecordMembers.java @@ -28,6 +28,7 @@ * @bug 8246774 * @summary check that the accessors, equals, hashCode and toString methods * work as expected + * @enablePreview * @library /tools/javac/lib * @modules jdk.compiler/com.sun.tools.javac.file * jdk.compiler/com.sun.tools.javac.api diff --git a/test/langtools/tools/javac/recovery/ClassBlockExits.java b/test/langtools/tools/javac/recovery/ClassBlockExits.java index b1c54b456ae..75dd70eeb9f 100644 --- a/test/langtools/tools/javac/recovery/ClassBlockExits.java +++ b/test/langtools/tools/javac/recovery/ClassBlockExits.java @@ -138,4 +138,4 @@ public String expand(String optParameter) { return code; } } -} \ No newline at end of file +} diff --git a/test/langtools/tools/javac/tree/MakeTypeTest.java b/test/langtools/tools/javac/tree/MakeTypeTest.java index f9495d58743..65ed2465bf7 100644 --- a/test/langtools/tools/javac/tree/MakeTypeTest.java +++ b/test/langtools/tools/javac/tree/MakeTypeTest.java @@ -25,6 +25,7 @@ * @test * @bug 8042239 * @summary Verify that TreeMaker.Type(Type) can handle all reasonable types + * @enablePreview * @library /tools/javac/lib * @modules jdk.compiler/com.sun.tools.javac.api * jdk.compiler/com.sun.tools.javac.code diff --git a/test/langtools/tools/javac/unnamed/UnnamedLocalVariableTable.java b/test/langtools/tools/javac/unnamed/UnnamedLocalVariableTable.java index 6cfeefa140e..6f172b9199e 100644 --- a/test/langtools/tools/javac/unnamed/UnnamedLocalVariableTable.java +++ b/test/langtools/tools/javac/unnamed/UnnamedLocalVariableTable.java @@ -28,6 +28,7 @@ * @enablePreview * @compile -g UnnamedLocalVariableTable.java * @run main UnnamedLocalVariableTable + * @ignore Verifier error */ public class UnnamedLocalVariableTable { public static void main(String... args) { diff --git a/test/langtools/tools/javac/valhalla/classfile/InnerClassesIdentityFlagTest.java b/test/langtools/tools/javac/valhalla/classfile/InnerClassesIdentityFlagTest.java new file mode 100644 index 00000000000..c2d51a75f18 --- /dev/null +++ b/test/langtools/tools/javac/valhalla/classfile/InnerClassesIdentityFlagTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8364095 + * @summary InnerClasses generation against an old class with and without preview; + * no SUPER pollution and no missing IDENTITY + * @library /tools/lib /test/lib + * @run junit InnerClassesIdentityFlagTest + */ + +import java.lang.classfile.Attributes; +import java.lang.classfile.ClassFile; +import java.nio.file.Path; + +import jdk.test.lib.compiler.CompilerUtils; +import org.junit.jupiter.api.Test; +import toolbox.ToolBox; + +import static java.lang.classfile.ClassFile.ACC_IDENTITY; +import static java.lang.classfile.ClassFile.ACC_SUPER; +import static org.junit.jupiter.api.Assertions.assertEquals; + +final class InnerClassesIdentityFlagTest { + /// Last release feature number with no value classes (preview exempt) + private static final String LAST_NON_VALUE_FEATURE = "25"; + + @Test + void test() throws Exception { + ToolBox toolBox = new ToolBox(); + var libSourceDir = Path.of("libsrc"); + var libDestinationDir = Path.of("libdest"); + toolBox.writeJavaFiles(libSourceDir, """ + public class One { + public class Inner {} + } + """); + CompilerUtils.compile(libSourceDir, libDestinationDir, "--release", LAST_NON_VALUE_FEATURE); + + var userSourceDir = Path.of("usersrc"); + var userRegularDestinationDir = Path.of("userdest"); + var userPreviewDestinationDir = Path.of("userdestPreview"); + toolBox.writeJavaFiles(userSourceDir, """ + class Observer { + Observer() { + new One().new Inner(); + super(); // triggers -XDforcePreview + } + } + """); + CompilerUtils.compile(userSourceDir, userRegularDestinationDir, "--release", LAST_NON_VALUE_FEATURE, + "-cp", libDestinationDir.toString()); + CompilerUtils.compile(userSourceDir, userPreviewDestinationDir, "--release", + Integer.toString(Runtime.version().feature()), "--enable-preview", "-XDforcePreview", + "-cp", libDestinationDir.toString()); + + var regularClass = ClassFile.of().parse(userRegularDestinationDir.resolve("Observer.class")); + var previewClass = ClassFile.of().parse(userPreviewDestinationDir.resolve("Observer.class")); + assertEquals(0, regularClass.minorVersion()); + assertEquals(ClassFile.PREVIEW_MINOR_VERSION, previewClass.minorVersion()); + var regularRelation = regularClass.findAttribute(Attributes.innerClasses()).orElseThrow().classes().getFirst(); + var previewRelation = previewClass.findAttribute(Attributes.innerClasses()).orElseThrow().classes().getFirst(); + assertEquals("One$Inner", regularRelation.innerClass().asInternalName()); + assertEquals("One$Inner", previewRelation.innerClass().asInternalName()); + int regularFlags = regularRelation.flagsMask(); + int previewFlags = previewRelation.flagsMask(); + assertEquals(0, regularFlags & ACC_SUPER, () -> "ACC_SUPER pollution: " + Integer.toHexString(regularFlags)); + assertEquals(ACC_IDENTITY, previewFlags & ACC_IDENTITY, () -> "Missing ACC_IDENTITY: " + Integer.toHexString(previewFlags)); + } +} diff --git a/test/langtools/tools/javac/valhalla/value-objects/CanonicalCtorTest.java b/test/langtools/tools/javac/valhalla/value-objects/CanonicalCtorTest.java new file mode 100644 index 00000000000..09000a2e537 --- /dev/null +++ b/test/langtools/tools/javac/valhalla/value-objects/CanonicalCtorTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8208067 + * @enablePreview + * @summary Verify that instance methods are callable from ctor after all instance fields are DA. + */ + +public value class CanonicalCtorTest { + + private final int x, ymx; + + CanonicalCtorTest(int x, int y) { + + ymx = y - x; + this.x = x; + + // all fields are assigned now, but we need a explicit `super()` invocation before accessing `this` + super(); + + validate(); // OK: DU = {} + this.validate(); // OK: DU = {} + CanonicalCtorTest.this.validate(); // OK: DU = {} + + assert (this.x > 0); // OK: DU = {} + assert (this.y() > 0); // OK: DU = {} + } + + int x() { + return x; + } + + int y() { + return ymx + x; + } + + void validate() { + assert (x() > 0 && y() > 0); + } + + public static void main(String... av) { + CanonicalCtorTest z = new CanonicalCtorTest(1, 10); + assert (z.x() == 1); + assert (z.y() == 10); + } +} diff --git a/test/langtools/tools/javac/valhalla/value-objects/CheckSynchronized.java b/test/langtools/tools/javac/valhalla/value-objects/CheckSynchronized.java new file mode 100644 index 00000000000..3fdedc93b8a --- /dev/null +++ b/test/langtools/tools/javac/valhalla/value-objects/CheckSynchronized.java @@ -0,0 +1,44 @@ +/* + * @test /nodynamiccopyright/ + * @summary Check behavior of synzhronized key word on value classes instances and methods. + * @enablePreview + * @compile/fail/ref=CheckSynchronized.out -XDrawDiagnostics CheckSynchronized.java + */ + +value final class CheckSynchronized implements java.io.Serializable { + synchronized void foo() { // <<-- ERROR, no monitor associated with `this' + } + void goo() { + synchronized(this) {} // <<-- ERROR, no monitor associated with `this' + } + synchronized static void zoo(CheckSynchronized cs) { // OK, static method. + synchronized(cs) { // <<-- ERROR, no monitor associated with value class instance. + } + + CheckSynchronized csr = cs; + synchronized(csr) { + // Error, no identity. + } + + synchronized(x) { + // Error, no identity. + } + + Object o = cs; + synchronized(o) { + // Error BUT not discernible at compile time + } + java.io.Serializable jis = cs; + synchronized(jis) { + // Error BUT not discernible at compile time + } + } + static int x = 10; + + value record CheckSynchronizedRecord(int x, int y) { + synchronized void foo() { // <<-- ERROR, no monitor associated with `this' + } + synchronized static void zoo() { // OK, static method. + } + } +} diff --git a/test/langtools/tools/javac/valhalla/value-objects/CheckSynchronized.out b/test/langtools/tools/javac/valhalla/value-objects/CheckSynchronized.out new file mode 100644 index 00000000000..52742aeb262 --- /dev/null +++ b/test/langtools/tools/javac/valhalla/value-objects/CheckSynchronized.out @@ -0,0 +1,9 @@ +CheckSynchronized.java:9:23: compiler.err.mod.not.allowed.here: synchronized +CheckSynchronized.java:39:27: compiler.err.mod.not.allowed.here: synchronized +CheckSynchronized.java:12:9: compiler.err.type.found.req: CheckSynchronized, (compiler.misc.type.req.identity) +CheckSynchronized.java:15:9: compiler.err.type.found.req: CheckSynchronized, (compiler.misc.type.req.identity) +CheckSynchronized.java:19:9: compiler.err.type.found.req: CheckSynchronized, (compiler.misc.type.req.identity) +CheckSynchronized.java:23:9: compiler.err.type.found.req: int, (compiler.misc.type.req.identity) +- compiler.note.preview.filename: CheckSynchronized.java, DEFAULT +- compiler.note.preview.recompile +6 errors diff --git a/test/langtools/tools/javac/valhalla/value-objects/DualNonDuplicateErrors.java b/test/langtools/tools/javac/valhalla/value-objects/DualNonDuplicateErrors.java new file mode 100644 index 00000000000..1ac7964b2ff --- /dev/null +++ b/test/langtools/tools/javac/valhalla/value-objects/DualNonDuplicateErrors.java @@ -0,0 +1,26 @@ +/* + * @test /nodynamiccopyright/ + * @bug 8267843 + * @summary Check that javac diagnoses `this` being passed around and instance method being invoked before value class instance is fully initialized. + * @enablePreview + * @compile/fail/ref=DualNonDuplicateErrors.out -XDrawDiagnostics DualNonDuplicateErrors.java + */ + +public value class DualNonDuplicateErrors { + + int x; + + DualNonDuplicateErrors() { + // The call below should trigger two errors - they are not duplicates really. + // First one is for `this` being passed around ("exposed") + // Second is for instance method being invoked thereby allowing that method to + // observe the value class instance in a partially initialized state. + foo(this); + x = 10; + super(); + foo(this); // No error here. + } + + void foo(DualNonDuplicateErrors x) { + } +} diff --git a/test/langtools/tools/javac/valhalla/value-objects/DualNonDuplicateErrors.out b/test/langtools/tools/javac/valhalla/value-objects/DualNonDuplicateErrors.out new file mode 100644 index 00000000000..3ba6f7d19ee --- /dev/null +++ b/test/langtools/tools/javac/valhalla/value-objects/DualNonDuplicateErrors.out @@ -0,0 +1,5 @@ +DualNonDuplicateErrors.java:18:13: compiler.err.cant.ref.before.ctor.called: this +DualNonDuplicateErrors.java:18:9: compiler.err.cant.ref.before.ctor.called: foo(DualNonDuplicateErrors) +- compiler.note.preview.filename: DualNonDuplicateErrors.java, DEFAULT +- compiler.note.preview.recompile +2 errors diff --git a/test/langtools/tools/javac/valhalla/value-objects/GenericPoint.java b/test/langtools/tools/javac/valhalla/value-objects/GenericPoint.java new file mode 100644 index 00000000000..67cdf1f5c2a --- /dev/null +++ b/test/langtools/tools/javac/valhalla/value-objects/GenericPoint.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * 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. + */ + +public value class GenericPoint { + + T x, y; + + GenericPoint(T x, T y) { + this.x = x; + this.y = y; + } +} diff --git a/test/jdk/java/lang/Class/getModifiers/StripACC_SUPER.java b/test/langtools/tools/javac/valhalla/value-objects/IdentityExceptionTest.java similarity index 64% rename from test/jdk/java/lang/Class/getModifiers/StripACC_SUPER.java rename to test/langtools/tools/javac/valhalla/value-objects/IdentityExceptionTest.java index 0b329b308ea..df441f766e0 100644 --- a/test/jdk/java/lang/Class/getModifiers/StripACC_SUPER.java +++ b/test/langtools/tools/javac/valhalla/value-objects/IdentityExceptionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -21,18 +21,26 @@ * questions. */ -/* @test - @bug 4109635 - @summary VM adds ACC_SUPER bit to access flags of a class. This must - be stripped by the Class.getModifiers method, or else this - shows up as though the class is synchronized and that doesn't - make any sense. - @author Anand Palaniswamy +/* + * @test + * @bug 8329400 + * @summary test to check that IdentityException is thrown for value objects + * @enablePreview + * @run main IdentityExceptionTest */ -public class StripACC_SUPER { + +public value class IdentityExceptionTest { + void m(Object o) { + synchronized (o) {} + } + public static void main(String[] args) throws Exception { - int access = StripACC_SUPER.class.getModifiers(); - if (java.lang.reflect.Modifier.isSynchronized(access)) - throw new Exception("ACC_SUPER bit is not being stripped"); + IdentityExceptionTest v = new IdentityExceptionTest(); + try { + v.m(v); + throw new AssertionError("should have failed with IdentityException"); + } catch (IdentityException e) { + // as expected + } } } diff --git a/test/langtools/tools/javac/valhalla/value-objects/LoadableDescriptorsAttrTest2.java b/test/langtools/tools/javac/valhalla/value-objects/LoadableDescriptorsAttrTest2.java new file mode 100644 index 00000000000..e16c064b069 --- /dev/null +++ b/test/langtools/tools/javac/valhalla/value-objects/LoadableDescriptorsAttrTest2.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8331461 + * @summary [lworld] javac is generating a class file with the LoadableDescriptors attribute but with minor version '0' + * @library /tools/lib + * @enablePreview + * @modules + * jdk.compiler/com.sun.tools.javac.code + * jdk.compiler/com.sun.tools.javac.util + * jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.file + * jdk.compiler/com.sun.tools.javac.main + * @build toolbox.ToolBox toolbox.JavacTask + * @run main LoadableDescriptorsAttrTest2 + */ + +import java.lang.classfile.Attributes; +import java.lang.classfile.ClassFile; +import java.nio.file.Path; +import java.nio.file.Paths; + +import com.sun.tools.javac.util.Assert; + +import toolbox.TestRunner; +import toolbox.ToolBox; + +public class LoadableDescriptorsAttrTest2 extends TestRunner { + ToolBox tb = new ToolBox(); + + public LoadableDescriptorsAttrTest2() { + super(System.err); + } + + protected void runTests() throws Exception { + runTests(m -> new Object[] { Paths.get(m.getName()) }); + } + + Path[] findJavaFiles(Path... paths) throws Exception { + return tb.findJavaFiles(paths); + } + + public static void main(String... args) throws Exception { + new LoadableDescriptorsAttrTest2().runTests(); + } + + @Test + public void testLoadableDescField(Path base) throws Exception { + Path src = base.resolve("src"); + tb.writeJavaFiles(src, + """ + value class Val {} + """, + """ + class Ident { + Val val; + } + """); + Path classes = base.resolve("classes"); + tb.createDirectories(classes); + + new toolbox.JavacTask(tb) + .options("--enable-preview", "-source", Integer.toString(Runtime.version().feature())) + .outdir(classes) + .files(findJavaFiles(src)) + .run() + .writeAll(); + Path classFilePath = classes.resolve("Ident.class"); + var classFile = ClassFile.of().parse(classFilePath); + Assert.check(classFile.minorVersion() == 65535); + Assert.check(classFile.findAttribute(Attributes.loadableDescriptors()).isPresent()); + + // now with the value class in the classpath + new toolbox.JavacTask(tb) + .options("--enable-preview", "-source", Integer.toString(Runtime.version().feature()), "-cp", classes.toString()) + .outdir(classes) + .files(src.resolve("Ident.java")) + .run() + .writeAll(); + + classFilePath = classes.resolve("Ident.class"); + classFile = ClassFile.of().parse(classFilePath); + Assert.check(classFile.minorVersion() == 65535); + Assert.check(classFile.findAttribute(Attributes.loadableDescriptors()).isPresent()); + } + + @Test + public void testLoadableDescMethodArg(Path base) throws Exception { + Path src = base.resolve("src"); + tb.writeJavaFiles(src, + """ + value class Val {} + """, + """ + class Ident { + void m(Val val) {} + } + """); + Path classes = base.resolve("classes"); + tb.createDirectories(classes); + + new toolbox.JavacTask(tb) + .options("--enable-preview", "-source", Integer.toString(Runtime.version().feature())) + .outdir(classes) + .files(findJavaFiles(src)) + .run() + .writeAll(); + Path classFilePath = classes.resolve("Ident.class"); + var classFile = ClassFile.of().parse(classFilePath); + Assert.check(classFile.minorVersion() == 65535); + Assert.check(classFile.findAttribute(Attributes.loadableDescriptors()).isPresent()); + + // now with the value class in the classpath + new toolbox.JavacTask(tb) + .options("--enable-preview", "-source", Integer.toString(Runtime.version().feature()), "-cp", classes.toString()) + .outdir(classes) + .files(src.resolve("Ident.java")) + .run() + .writeAll(); + + classFilePath = classes.resolve("Ident.class"); + classFile = ClassFile.of().parse(classFilePath); + Assert.check(classFile.minorVersion() == 65535); + Assert.check(classFile.findAttribute(Attributes.loadableDescriptors()).isPresent()); + + } + + @Test + public void testLoadableDescReturnType(Path base) throws Exception { + Path src = base.resolve("src"); + tb.writeJavaFiles(src, + """ + value class Val {} + """, + """ + class Ident { + Val m() { + return null; + } + } + """); + Path classes = base.resolve("classes"); + tb.createDirectories(classes); + + new toolbox.JavacTask(tb) + .options("--enable-preview", "-source", Integer.toString(Runtime.version().feature())) + .outdir(classes) + .files(findJavaFiles(src)) + .run() + .writeAll(); + Path classFilePath = classes.resolve("Ident.class"); + var classFile = ClassFile.of().parse(classFilePath); + Assert.check(classFile.minorVersion() == 65535); + Assert.check(classFile.findAttribute(Attributes.loadableDescriptors()).isPresent()); + + + // now with the value class in the classpath + new toolbox.JavacTask(tb) + .options("--enable-preview", "-source", Integer.toString(Runtime.version().feature()), "-cp", classes.toString()) + .outdir(classes) + .files(src.resolve("Ident.java")) + .run() + .writeAll(); + + classFilePath = classes.resolve("Ident.class"); + classFile = ClassFile.of().parse(classFilePath); + Assert.check(classFile.minorVersion() == 65535); + Assert.check(classFile.findAttribute(Attributes.loadableDescriptors()).isPresent()); + + } +} diff --git a/test/langtools/tools/javac/valhalla/value-objects/LoadableDescriptorsAttributeTest.java b/test/langtools/tools/javac/valhalla/value-objects/LoadableDescriptorsAttributeTest.java new file mode 100644 index 00000000000..d4436edf20e --- /dev/null +++ b/test/langtools/tools/javac/valhalla/value-objects/LoadableDescriptorsAttributeTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8280164 8334313 + * @summary Check emission of LoadableDescriptors attribute + * @enablePreview + * @run main LoadableDescriptorsAttributeTest + */ + +import java.lang.classfile.Attributes; +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassModel; +import java.lang.classfile.constantpool.Utf8Entry; +import java.util.Set; +import java.util.stream.Collectors; + +public class LoadableDescriptorsAttributeTest { + + value class V1 {} + value class V2 {} + value class V3 {} + value class V4 {} + value class V5 {} + value class V6 {} + value class V7 {} + value class V8 {} + value class V9 {} + abstract value class V10 {} + + static final value class X { + final V1 [] v1 = null; // field descriptor, encoding array type - no LoadableDescriptors. + V2 foo() { // method descriptor encoding value type, to be preloaded + return null; + } // return type is value type so should be preloaded + void foo(V3 v3) { // method descriptor encoding value type, to be preloaded + } + void foo(int x) { + V4 [] v4 = null; // local variable encoding array type - no preload. + } + void goo(V6[] v6) { // parameter uses value type but as array component - no preload. + V5 v5 = null; // no preload as value type is used for local type. + if (v5 == null) { + // ... + } else { + V5 [] v52 = null; + } + } + final V7 v7 = null; // field descriptor uses value type - to be preloaded. + V8 [] goo(V9 [] v9) { // neither V8 nor V9 call for preload being array component types + return null; + } + V10 v10 = null; // abstract shouldn't be in the loadable descriptors attr + } + // So we expect ONLY V2, V3, V7 to be in LoadableDescriptors list + + public static void main(String[] args) throws Exception { + ClassModel cls; + try (var in = LoadableDescriptorsAttributeTest.class.getResourceAsStream("LoadableDescriptorsAttributeTest$X.class")) { + cls = ClassFile.of().parse(in.readAllBytes()); + } + + /* Check emission of LoadableDescriptors attribute */ + var descriptors = cls.findAttribute(Attributes.loadableDescriptors()).orElseThrow(); + if (descriptors.loadableDescriptors().size() != 3) { + throw new AssertionError("Expected 3 loadable descriptors, found: " + descriptors.loadableDescriptors()); + } + + Set expected = Set.of( + "LLoadableDescriptorsAttributeTest$V2;", + "LLoadableDescriptorsAttributeTest$V3;", + "LLoadableDescriptorsAttributeTest$V7;" + ); + + Set found = descriptors.loadableDescriptors() + .stream() + .map(Utf8Entry::stringValue) + .collect(Collectors.toSet()); + + if (!expected.equals(found)) { + throw new AssertionError("LoadableDescriptors mismatch, found: " + found); + } + } +} diff --git a/test/langtools/tools/javac/valhalla/value-objects/NoUnnecessaryLoadableDescriptorsTest.java b/test/langtools/tools/javac/valhalla/value-objects/NoUnnecessaryLoadableDescriptorsTest.java new file mode 100644 index 00000000000..3b8d21d8aa4 --- /dev/null +++ b/test/langtools/tools/javac/valhalla/value-objects/NoUnnecessaryLoadableDescriptorsTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8281323 + * @summary Check emission of LoadableDescriptors attribute to make sure javac does not emit unneeded entries. + * @enablePreview + * @run main NoUnnecessaryLoadableDescriptorsTest + */ + +import java.lang.classfile.Attributes; +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassModel; +import java.lang.classfile.attribute.LoadableDescriptorsAttribute; +import java.lang.classfile.constantpool.Utf8Entry; +import java.util.List; + +public class NoUnnecessaryLoadableDescriptorsTest { + + public value class LoadableDescriptorsTest1 { + byte b; + public LoadableDescriptorsTest1(byte b) { + this.b = b; + } + } + + public class LoadableDescriptorsTest2 { + static class Inner1 { + static value class Inner2 {} + Inner2 inner; + } + } + + public static void main(String[] args) throws Exception { + ClassModel cls; + LoadableDescriptorsAttribute loadableDescriptors; + + // There should be no LoadableDescriptors attribute in NoUnnecessaryLoadableDescriptorsTest.class + try (var in = NoUnnecessaryLoadableDescriptorsTest.class.getResourceAsStream("NoUnnecessaryLoadableDescriptorsTest.class")) { + cls = ClassFile.of().parse(in.readAllBytes()); + } + + /* Check emission of LoadableDescriptors attribute */ + if (cls.findAttribute(Attributes.loadableDescriptors()).isPresent()) { + throw new AssertionError("Unexpected LoadableDescriptors attribute!"); + } + + // There should be no LoadableDescriptors attribute in NoUnnecessaryLoadableDescriptorsTest$LoadableDescriptorsTest1.class + try (var in = NoUnnecessaryLoadableDescriptorsTest.class.getResourceAsStream("NoUnnecessaryLoadableDescriptorsTest$LoadableDescriptorsTest1.class")) { + cls = ClassFile.of().parse(in.readAllBytes()); + } + + /* Check emission of LoadableDescriptors attribute */ + if (cls.findAttribute(Attributes.loadableDescriptors()).isPresent()) { + throw new AssertionError("Unexpected LoadableDescriptors attribute!"); + } + + // There should be no LoadableDescriptors attribute in NoUnnecessaryLoadableDescriptorsTest$LoadableDescriptorsTest2.class + try (var in = NoUnnecessaryLoadableDescriptorsTest.class.getResourceAsStream("NoUnnecessaryLoadableDescriptorsTest$LoadableDescriptorsTest2.class")) { + cls = ClassFile.of().parse(in.readAllBytes()); + } + + /* Check emission of LoadableDescriptors attribute */ + if (cls.findAttribute(Attributes.loadableDescriptors()).isPresent()) { + throw new AssertionError("Unexpected LoadableDescriptors attribute!"); + } + + // There should be no LoadableDescriptors attribute in NoUnnecessaryLoadableDescriptorsTest$LoadableDescriptorsTest2$Inner2.class + try (var in = NoUnnecessaryLoadableDescriptorsTest.class.getResourceAsStream("NoUnnecessaryLoadableDescriptorsTest$LoadableDescriptorsTest2$Inner1$Inner2.class")) { + cls = ClassFile.of().parse(in.readAllBytes()); + } + + /* Check emission of LoadableDescriptors attribute */ + if (cls.findAttribute(Attributes.loadableDescriptors()).isPresent()) { + throw new AssertionError("Unexpected LoadableDescriptors attribute!"); + } + + // There should be ONE LoadableDescriptors attribute entry in NoUnnecessaryLoadableDescriptorsTest$LoadableDescriptorsTest2$Inner1.class + try (var in = NoUnnecessaryLoadableDescriptorsTest.class.getResourceAsStream("NoUnnecessaryLoadableDescriptorsTest$LoadableDescriptorsTest2$Inner1.class")) { + cls = ClassFile.of().parse(in.readAllBytes()); + } + + if (cls == null) { + throw new AssertionError("Could not locate the class files"); + } + + /* Check emission of LoadableDescriptors attribute */ + loadableDescriptors = cls.findAttribute(Attributes.loadableDescriptors()).orElseThrow(); + + List expected = List.of("LNoUnnecessaryLoadableDescriptorsTest$LoadableDescriptorsTest2$Inner1$Inner2;"); + List found = loadableDescriptors.loadableDescriptors().stream().map(Utf8Entry::stringValue).toList(); + if (!expected.equals(found)) { + throw new AssertionError("Expected one LoadableDescriptors class entry Inner2, but found " + found); + } + } +} diff --git a/test/langtools/tools/javac/valhalla/value-objects/ValueBasedFlagsTest.java b/test/langtools/tools/javac/valhalla/value-objects/ValueBasedFlagsTest.java new file mode 100644 index 00000000000..d7f95e3008b --- /dev/null +++ b/test/langtools/tools/javac/valhalla/value-objects/ValueBasedFlagsTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8331462 + * @summary [lworld] javac is setting the ACC_STRICT flag for value-based classes + * @library /tools/lib + * @modules + * jdk.compiler/com.sun.tools.javac.code + * jdk.compiler/com.sun.tools.javac.util + * jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.file + * jdk.compiler/com.sun.tools.javac.main + * @build toolbox.ToolBox toolbox.JavacTask + * @run main ValueBasedFlagsTest + */ + +import java.lang.classfile.ClassFile; +import java.nio.file.Path; +import java.nio.file.Paths; + +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.util.Assert; + +import toolbox.TestRunner; +import toolbox.ToolBox; +import toolbox.JavacTask; +import toolbox.Task; + +public class ValueBasedFlagsTest extends TestRunner { + ToolBox tb = new ToolBox(); + + public ValueBasedFlagsTest() { + super(System.err); + } + + protected void runTests() throws Exception { + runTests(m -> new Object[] { Paths.get(m.getName()) }); + } + + Path[] findJavaFiles(Path... paths) throws Exception { + return tb.findJavaFiles(paths); + } + + public static void main(String... args) throws Exception { + new ValueBasedFlagsTest().runTests(); + } + + @Test + public void testValueBased(Path base) throws Exception { + Path src = base.resolve("src"); + tb.writeJavaFiles(src, + """ + package java.lang; + @jdk.internal.ValueBased + final class ValueBasedTest {} + """); + Path classes = base.resolve("classes"); + tb.createDirectories(classes); + + new toolbox.JavacTask(tb) + .options("--patch-module", "java.base=" + src.toString(), + "--enable-preview", "-source", Integer.toString(Runtime.version().feature())) + .outdir(classes) + .files(findJavaFiles(src)) + .run() + .writeAll(); + Path classFilePath = classes.resolve("java", "lang", "ValueBasedTest.class"); + var classFile = ClassFile.of().parse(classFilePath); + Assert.check((classFile.flags().flagsMask() & Flags.ACC_STRICT) == 0); + } +} diff --git a/test/langtools/tools/javac/valhalla/value-objects/ValueCreationTest.java b/test/langtools/tools/javac/valhalla/value-objects/ValueCreationTest.java new file mode 100644 index 00000000000..64121c4b84c --- /dev/null +++ b/test/langtools/tools/javac/valhalla/value-objects/ValueCreationTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Check code generation for value creation ops + * @modules jdk.compiler/com.sun.tools.javac.util jdk.jdeps/com.sun.tools.javap + * @enablePreview + * @run main ValueCreationTest + */ + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.file.Paths; + +public class ValueCreationTest { + + static final value class Point { + + final int x; + final int y; + + Point (int x, int y) { + this.x = x; + this.y = y; + } + + public static void main(String [] args) { + Point p = new Point(10, 20); + } + } + + public static void main(String[] args) { + new ValueCreationTest().run(); + } + + void run() { + String [] params = new String [] { "-v", + Paths.get(System.getProperty("test.classes"), + "ValueCreationTest$Point.class").toString() }; + runCheck(params, new String [] { + + "final value class ValueCreationTest$Point", + "flags: (0x0010) ACC_FINAL", + "ValueCreationTest$Point(int, int);", + "descriptor: (II)V", + "flags: (0x0000)", + "0: aload_0", + "1: iload_1", + "2: putfield #1 // Field x:I", + "5: aload_0", + "6: iload_2", + "7: putfield #7 // Field y:I", + "10: aload_0", + "11: invokespecial #10 // Method java/lang/Object.\"\":()V", + }); + } + + void runCheck(String [] params, String [] expectedOut) { + StringWriter s; + String out; + + try (PrintWriter pw = new PrintWriter(s = new StringWriter())) { + com.sun.tools.javap.Main.run(params, pw); + out = s.toString(); + } + int errors = 0; + for (String eo: expectedOut) { + if (!out.contains(eo)) { + System.err.println("Match not found for string: " + eo); + errors++; + } + } + if (errors > 0) { + throw new AssertionError("Unexpected javap output: " + out); + } + } +} diff --git a/test/langtools/tools/javac/valhalla/value-objects/ValueObjectCompilationTests.java b/test/langtools/tools/javac/valhalla/value-objects/ValueObjectCompilationTests.java new file mode 100644 index 00000000000..e30e04f4dc9 --- /dev/null +++ b/test/langtools/tools/javac/valhalla/value-objects/ValueObjectCompilationTests.java @@ -0,0 +1,1640 @@ +/* + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * ValueObjectCompilationTests + * + * @test + * @bug 8287136 8292630 8279368 8287136 8287770 8279840 8279672 8292753 8287763 8279901 8287767 8293183 8293120 + * 8329345 8341061 8340984 8334484 + * @summary Negative compilation tests, and positive compilation (smoke) tests for Value Objects + * @enablePreview + * @library /lib/combo /tools/lib + * @modules + * jdk.compiler/com.sun.tools.javac.util + * jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * jdk.compiler/com.sun.tools.javac.code + * @build toolbox.ToolBox toolbox.JavacTask + * @run junit ValueObjectCompilationTests + */ + +import java.io.File; + +import java.lang.classfile.Attributes; +import java.lang.classfile.ClassFile; +import java.lang.classfile.Instruction; +import java.lang.classfile.Opcode; +import java.lang.classfile.instruction.FieldInstruction; +import java.lang.constant.ConstantDescs; +import java.lang.reflect.AccessFlag; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +import com.sun.tools.javac.util.Assert; + +import com.sun.tools.javac.code.Flags; + +import org.junit.jupiter.api.Test; +import tools.javac.combo.CompilationTestCase; +import toolbox.ToolBox; + +class ValueObjectCompilationTests extends CompilationTestCase { + + private static String[] PREVIEW_OPTIONS = { + "--enable-preview", + "-source", Integer.toString(Runtime.version().feature()) + }; + + private static String[] PREVIEW_OPTIONS_PLUS_VM_ANNO = { + "--enable-preview", + "-source", Integer.toString(Runtime.version().feature()), + "--add-exports=java.base/jdk.internal.vm.annotation=ALL-UNNAMED" + }; + + public ValueObjectCompilationTests() { + setDefaultFilename("ValueObjectsTest.java"); + setCompileOptions(PREVIEW_OPTIONS); + } + + @Test + void testValueModifierConstraints() { + assertFail("compiler.err.illegal.combination.of.modifiers", + """ + value @interface IA {} + """); + assertFail("compiler.err.illegal.combination.of.modifiers", + """ + value interface I {} + """); + assertFail("compiler.err.mod.not.allowed.here", + """ + class Test { + value int x; + } + """); + assertFail("compiler.err.mod.not.allowed.here", + """ + class Test { + value int foo(); + } + """); + assertFail("compiler.err.mod.not.allowed.here", + """ + value enum Enum {} + """); + } + + record TestData(String message, String snippet, String[] compilerOptions, boolean testLocalToo) { + TestData(String snippet) { + this("", snippet, null, true); + } + + TestData(String snippet, boolean testLocalToo) { + this("", snippet, null, testLocalToo); + } + + TestData(String message, String snippet) { + this(message, snippet, null, true); + } + + TestData(String snippet, String[] compilerOptions) { + this("", snippet, compilerOptions, true); + } + + TestData(String message, String snippet, String[] compilerOptions) { + this(message, snippet, compilerOptions, true); + } + + TestData(String message, String snippet, boolean testLocalToo) { + this(message, snippet, null, testLocalToo); + } + } + + private void testHelper(List testDataList) { + String ttt = + """ + class TTT { + void m() { + #LOCAL + } + } + """; + for (TestData td : testDataList) { + String localSnippet = ttt.replace("#LOCAL", td.snippet); + String[] previousOptions = getCompileOptions(); + try { + if (td.compilerOptions != null) { + setCompileOptions(td.compilerOptions); + } + if (td.message == "") { + assertOK(td.snippet); + if (td.testLocalToo) { + assertOK(localSnippet); + } + } else if (td.message.startsWith("compiler.err")) { + assertFail(td.message, td.snippet); + if (td.testLocalToo) { + assertFail(td.message, localSnippet); + } + } else { + assertOKWithWarning(td.message, td.snippet); + if (td.testLocalToo) { + assertOKWithWarning(td.message, localSnippet); + } + } + } finally { + setCompileOptions(previousOptions); + } + } + } + + private static final List superClassConstraints = List.of( + new TestData( + "compiler.err.super.class.method.cannot.be.synchronized", + """ + abstract class I { + synchronized void foo() {} + } + value class V extends I {} + """ + ), + new TestData( + "compiler.err.concrete.supertype.for.value.class", + """ + class ConcreteSuperType { + static abstract value class V extends ConcreteSuperType {} // Error: concrete super. + } + """ + ), + new TestData( + """ + value record Point(int x, int y) {} + """ + ), + new TestData( + """ + value class One extends Number { + public int intValue() { return 0; } + public long longValue() { return 0; } + public float floatValue() { return 0; } + public double doubleValue() { return 0; } + } + """ + ), + new TestData( + """ + value class V extends Object {} + """ + ), + new TestData( + "compiler.err.value.type.has.identity.super.type", + """ + abstract class A {} + value class V extends A {} + """ + ) + ); + + @Test + void testSuperClassConstraints() { + testHelper(superClassConstraints); + } + + @Test + void testRepeatedModifiers() { + assertFail("compiler.err.repeated.modifier", "value value class ValueTest {}"); + } + + @Test + void testParserTest() { + assertOK( + """ + value class Substring implements CharSequence { + private String str; + private int start; + private int end; + + public Substring(String str, int start, int end) { + checkBounds(start, end, str.length()); + this.str = str; + this.start = start; + this.end = end; + } + + public int length() { + return end - start; + } + + public char charAt(int i) { + checkBounds(0, i, length()); + return str.charAt(start + i); + } + + public Substring subSequence(int s, int e) { + checkBounds(s, e, length()); + return new Substring(str, start + s, start + e); + } + + public String toString() { + return str.substring(start, end); + } + + private static void checkBounds(int start, int end, int length) { + if (start < 0 || end < start || length < end) + throw new IndexOutOfBoundsException(); + } + } + """ + ); + } + + private static final List semanticsViolations = List.of( + new TestData( + "compiler.err.cant.inherit.from.final", + """ + value class Base {} + class Subclass extends Base {} + """ + ), + new TestData( + "compiler.err.cant.assign.val.to.var", + """ + value class Point { + int x = 10; + int y; + Point (int x, int y) { + this.x = x; // Error, final field 'x' is already assigned to. + this.y = y; // OK. + } + } + """ + ), + new TestData( + "compiler.err.cant.assign.val.to.var", + """ + value class Point { + int x; + int y; + Point (int x, int y) { + this.x = x; + this.y = y; + } + void foo(Point p) { + this.y = p.y; // Error, y is final and can't be written outside of ctor. + } + } + """ + ), + new TestData( + "compiler.err.cant.assign.val.to.var", + """ + abstract value class Point { + int x; + int y; + Point (int x, int y) { + this.x = x; + this.y = y; + } + void foo(Point p) { + this.y = p.y; // Error, y is final and can't be written outside of ctor. + } + } + """ + ), + new TestData( + "compiler.err.strict.field.not.have.been.initialized.before.super", + """ + value class Point { + int x; + int y; + Point (int x, int y) { + this.x = x; + // y hasn't been initialized + } + } + """ + ), + new TestData( + "compiler.err.mod.not.allowed.here", + """ + abstract value class V { + synchronized void foo() { + // Error, abstract value class may not declare a synchronized instance method. + } + } + """ + ), + new TestData( + """ + abstract value class V { + static synchronized void foo() {} // OK static + } + """ + ), + new TestData( + "compiler.err.mod.not.allowed.here", + """ + value class V { + synchronized void foo() {} + } + """ + ), + new TestData( + """ + value class V { + synchronized static void soo() {} // OK static + } + """ + ), + new TestData( + "compiler.err.type.found.req", + """ + value class V { + { synchronized(this) {} } + } + """ + ), + new TestData( + "compiler.err.mod.not.allowed.here", + """ + value record R() { + synchronized void foo() { } // Error; + synchronized static void soo() {} // OK. + } + """ + ), + new TestData( + "compiler.err.cant.ref.before.ctor.called", + """ + value class V { + int x; + V() { + foo(this); // Error. + x = 10; + } + void foo(V v) {} + } + """ + ), + new TestData( + "compiler.err.cant.ref.before.ctor.called", + """ + value class V { + int x; + V() { + x = 10; + foo(this); // error + } + void foo(V v) {} + } + """ + ), + new TestData( + "compiler.err.type.found.req", + """ + interface I {} + interface VI extends I {} + class C {} + value class VC { + void m(T t) { + synchronized(t) {} // error + } + } + """ + ), + new TestData( + "compiler.err.type.found.req", + """ + interface I {} + interface VI extends I {} + class C {} + value class VC { + void foo(Object o) { + synchronized ((VC & I)o) {} // error + } + } + """ + ), + new TestData( + // OK if the value class is abstract + """ + interface I {} + abstract value class VI implements I {} + class C {} + value class VC { + void bar(Object o) { + synchronized ((VI & I)o) {} // error + } + } + """ + ), + new TestData( + "compiler.err.type.found.req", // --enable-preview -source" + """ + class V { + final Integer val = Integer.valueOf(42); + void test() { + synchronized (val) { // error + } + } + } + """ + ), + new TestData( + "compiler.err.type.found.req", // --enable-preview -source" + """ + import java.time.*; + class V { + final Duration val = Duration.ZERO; + void test() { + synchronized (val) { // warn + } + } + } + """, + false // cant do local as there is an import statement + ), + new TestData( + "compiler.warn.attempt.to.synchronize.on.instance.of.value.based.class", // empty options + """ + class V { + final Integer val = Integer.valueOf(42); + void test() { + synchronized (val) { // warn + } + } + } + """, + new String[] {} + ), + new TestData( + "compiler.warn.attempt.to.synchronize.on.instance.of.value.based.class", // --source + """ + class V { + final Integer val = Integer.valueOf(42); + void test() { + synchronized (val) { // warn + } + } + } + """, + new String[] {"--source", Integer.toString(Runtime.version().feature())} + ), + new TestData( + "compiler.warn.attempt.to.synchronize.on.instance.of.value.based.class", // --source + """ + class V { + final Integer val = Integer.valueOf(42); + void test() { + synchronized (val) { // warn + } + } + } + """, + new String[] {"--source", Integer.toString(Runtime.version().feature())} + ), + new TestData( + "compiler.err.illegal.combination.of.modifiers", // --enable-preview -source" + """ + value class V { + volatile int f = 1; + } + """ + ) + ); + + @Test + void testSemanticsViolations() { + testHelper(semanticsViolations); + } + + private static final List sealedClassesData = List.of( + new TestData( + """ + abstract sealed value class SC {} + value class VC extends SC {} + """, + false // local sealed classes are not allowed + ), + new TestData( + """ + abstract sealed interface SI {} + value class VC implements SI {} + """, + false // local sealed classes are not allowed + ), + new TestData( + """ + abstract sealed class SC {} + final class IC extends SC {} + non-sealed class IC2 extends SC {} + final class IC3 extends IC2 {} + """, + false + ), + new TestData( + """ + abstract sealed interface SI {} + final class IC implements SI {} + non-sealed class IC2 implements SI {} + final class IC3 extends IC2 {} + """, + false // local sealed classes are not allowed + ), + new TestData( + "compiler.err.non.abstract.value.class.cant.be.sealed.or.non.sealed", + """ + abstract sealed value class SC {} + non-sealed value class VC extends SC {} + """, + false + ), + new TestData( + "compiler.err.non.abstract.value.class.cant.be.sealed.or.non.sealed", + """ + sealed value class SI {} + """, + false + ), + new TestData( + """ + sealed abstract value class SI {} + value class V extends SI {} + """, + false + ), + new TestData( + """ + sealed abstract value class SI permits V {} + value class V extends SI {} + """, + false + ), + new TestData( + """ + sealed interface I {} + non-sealed abstract value class V implements I {} + """, + false + ), + new TestData( + """ + sealed interface I permits V {} + non-sealed abstract value class V implements I {} + """, + false + ) + ); + + @Test + void testInteractionWithSealedClasses() { + testHelper(sealedClassesData); + } + + @Test + void testCheckClassFileFlags() throws Exception { + for (String source : List.of( + """ + interface I {} + class Test { + I i = new I() {}; + } + """, + """ + class C {} + class Test { + C c = new C() {}; + } + """, + """ + class Test { + Object o = new Object() {}; + } + """, + """ + class Test { + abstract class Inner {} + } + """ + )) { + File dir = assertOK(true, source); + for (final File fileEntry : dir.listFiles()) { + if (fileEntry.getName().contains("$")) { + var classFile = ClassFile.of().parse(fileEntry.toPath()); + Assert.check(classFile.flags().has(AccessFlag.IDENTITY)); + } + } + } + + for (String source : List.of( + """ + class C {} + """, + """ + abstract class A { + int i; + } + """, + """ + abstract class A { + synchronized void m() {} + } + """, + """ + class C { + synchronized void m() {} + } + """, + """ + abstract class A { + int i; + { i = 0; } + } + """, + """ + abstract class A { + A(int i) {} + } + """, + """ + enum E {} + """, + """ + record R() {} + """ + )) { + File dir = assertOK(true, source); + for (final File fileEntry : dir.listFiles()) { + var classFile = ClassFile.of().parse(fileEntry.toPath()); + Assert.check(classFile.flags().has(AccessFlag.IDENTITY)); + } + } + + { + String source = + """ + abstract value class A {} + value class Sub extends A {} //implicitly final + """; + File dir = assertOK(true, source); + for (final File fileEntry : dir.listFiles()) { + var classFile = ClassFile.of().parse(fileEntry.toPath()); + switch (classFile.thisClass().asInternalName()) { + case "Sub": + Assert.check((classFile.flags().flagsMask() & (Flags.FINAL)) != 0); + break; + case "A": + Assert.check((classFile.flags().flagsMask() & (Flags.ABSTRACT)) != 0); + break; + default: + throw new AssertionError("you shoulnd't be here"); + } + } + } + + for (String source : List.of( + """ + value class V { + int i = 0; + static int j; + } + """, + """ + abstract value class A { + static int j; + } + + value class V extends A { + int i = 0; + } + """ + )) { + File dir = assertOK(true, source); + for (final File fileEntry : dir.listFiles()) { + var classFile = ClassFile.of().parse(fileEntry.toPath()); + for (var field : classFile.fields()) { + if (!field.flags().has(AccessFlag.STATIC)) { + Set fieldFlags = field.flags().flags(); + Assert.check(fieldFlags.size() == 2 && fieldFlags.contains(AccessFlag.FINAL) && fieldFlags.contains(AccessFlag.STRICT_INIT)); + } + } + } + } + + // testing experimental @Strict annotation + String[] previousOptions = getCompileOptions(); + try { + setCompileOptions(PREVIEW_OPTIONS_PLUS_VM_ANNO); + for (String source : List.of( + """ + import jdk.internal.vm.annotation.Strict; + class Test { + @Strict int i = 0; + } + """, + """ + import jdk.internal.vm.annotation.Strict; + class Test { + @Strict final int i = 0; + } + """ + )) { + File dir = assertOK(true, source); + for (final File fileEntry : dir.listFiles()) { + var classFile = ClassFile.of().parse(fileEntry.toPath()); + Assert.check(classFile.flags().has(AccessFlag.IDENTITY)); + for (var field : classFile.fields()) { + if (!field.flags().has(AccessFlag.STATIC)) { + Set fieldFlags = field.flags().flags(); + Assert.check(fieldFlags.contains(AccessFlag.STRICT_INIT)); + } + } + } + } + } finally { + setCompileOptions(previousOptions); + } + } + + @Test + void testConstruction() throws Exception { + record Data(String src, boolean isRecord) { + Data(String src) { + this(src, false); + } + } + for (Data data : List.of( + new Data( + """ + value class Test { + int i = 100; + } + """), + new Data( + """ + value class Test { + int i; + Test() { + i = 100; + } + } + """), + new Data( + """ + value class Test { + int i; + Test() { + i = 100; + super(); + } + } + """), + new Data( + """ + value class Test { + int i; + Test() { + this.i = 100; + super(); + } + } + """), + new Data( + """ + value record Test(int i) {} + """, true) + )) { + String expectedCodeSequence = "aload_0,bipush,putfield,aload_0,invokespecial,return"; + String expectedCodeSequenceRecord = "aload_0,iload_1,putfield,aload_0,invokespecial,return"; + File dir = assertOK(true, data.src); + for (final File fileEntry : dir.listFiles()) { + var classFile = ClassFile.of().parse(fileEntry.toPath()); + classFile.methods().stream() + .filter(mm -> mm.methodName().equalsString(ConstantDescs.INIT_NAME)) + .map(mm -> mm.findAttribute(Attributes.code()).orElseThrow()) + .forEach(code -> { + List mnemonics = new ArrayList<>(); + for (var coe : code) { + if (coe instanceof Instruction inst) { + mnemonics.add(inst.opcode().name().toLowerCase(Locale.ROOT)); + } + } + var foundCodeSequence = String.join(",", mnemonics); + if (!data.isRecord) { + Assert.check(expectedCodeSequence.equals(foundCodeSequence)); + } else { + Assert.check(expectedCodeSequenceRecord.equals(foundCodeSequence)); + } + }); + } + } + + String source = + """ + value class Test { + int i = 100; + int j = 0; + { + System.out.println(j); + } + } + """; + { + String expectedCodeSequence = "aload_0,bipush,putfield,aload_0,iconst_0,putfield,aload_0,invokespecial,getstatic,iconst_0,invokevirtual,return"; + File dir = assertOK(true, source); + for (final File fileEntry : dir.listFiles()) { + var classFile = ClassFile.of().parse(fileEntry.toPath()); + classFile.methods().stream() + .filter(mm -> mm.methodName().equalsString(ConstantDescs.INIT_NAME)) + .map(mm -> mm.findAttribute(Attributes.code()).orElseThrow()) + .forEach(code -> { + List mnemonics = new ArrayList<>(); + for (var coe : code) { + if (coe instanceof Instruction inst) { + mnemonics.add(inst.opcode().name().toLowerCase(Locale.ROOT)); + } + } + var foundCodeSequence = String.join(",", mnemonics); + Assert.check(expectedCodeSequence.equals(foundCodeSequence), "found " + foundCodeSequence); + }); + } + } + + assertFail("compiler.err.cant.ref.before.ctor.called", + """ + value class Test { + Test() { + m(); + } + void m() {} + } + """ + ); + assertFail("compiler.err.strict.field.not.have.been.initialized.before.super", + """ + value class Test { + int i; + Test() { + super(); + this.i = i; + } + } + """ + ); + assertOK( + """ + class UnrelatedThisLeak { + value class V { + int f; + V() { + UnrelatedThisLeak x = UnrelatedThisLeak.this; + f = 10; + x = UnrelatedThisLeak.this; + } + } + } + """ + ); + assertFail("compiler.err.cant.ref.before.ctor.called", + """ + value class Test { + Test t = null; + Runnable r = () -> { System.err.println(t); }; // cant reference `t` from a lambda expression in the prologue + } + """ + ); + assertFail("compiler.err.strict.field.not.have.been.initialized.before.super", + """ + value class Test { + int f; + { + f = 1; + } + } + """ + ); + assertOK( + """ + value class V { + int x; + int y = x + 1; // allowed + V() { + x = 12; + // super(); + } + } + """ + ); + assertFail("compiler.err.cant.ref.before.ctor.called", + """ + value class V2 { + int x; + V2() { this(x = 3); } // error + V2(int i) { x = 4; } + } + """ + ); + assertOK( + """ + abstract value class AV1 { + AV1(int i) {} + } + value class V3 extends AV1 { + int x; + V3() { + super(x = 3); // ok + } + } + """ + ); + assertOK( + """ + value class V4 { + int x; + int y = x + 1; + V4() { + x = 12; + } + V4(int i) { + x = i; + } + } + """ + ); + assertOK( + """ + value class V { + final int x = "abc".length(); + { System.out.println(x); } + } + """ + ); + assertFail("compiler.err.illegal.forward.ref", + """ + value class V { + { System.out.println(x); } + final int x = "abc".length(); + } + """ + ); + assertOK( + """ + value class V { + int x = "abc".length(); + int y = x; + } + """ + ); + assertOK( + """ + value class V { + int x = "abc".length(); + { int y = x; } + } + """ + ); + assertOK( + """ + value class V { + String s1; + { System.out.println(s1); } + String s2 = (s1 = "abc"); + } + """ + ); + + String[] previousOptions = getCompileOptions(); + try { + setCompileOptions(PREVIEW_OPTIONS_PLUS_VM_ANNO); + String[] sources = new String[]{ + """ + import jdk.internal.vm.annotation.Strict; + class Test { + static value class IValue { + int i = 0; + } + @Strict + final IValue val = new IValue(); + } + """, + """ + import jdk.internal.vm.annotation.Strict; + class Test { + static value class IValue { + int i = 0; + } + @Strict + final IValue val; + Test() { + val = new IValue(); + } + } + """ + }; + var expectedCodeSequence = "aload_0,new,dup,invokespecial,putfield,aload_0,invokespecial,return"; + for (String src : sources) { + File dir = assertOK(true, src); + for (final File fileEntry : dir.listFiles()) { + var classFile = ClassFile.of().parse(fileEntry.toPath()); + if (classFile.thisClass().name().equalsString("Test")) { + for (var method : classFile.methods()) { + if (method.methodName().equalsString("")) { + var code = method.findAttribute(Attributes.code()).orElseThrow(); + List mnemonics = new ArrayList<>(); + for (var coe : code) { + if (coe instanceof Instruction inst) { + mnemonics.add(inst.opcode().name().toLowerCase(Locale.ROOT)); + } + } + var foundCodeSequence = String.join(",", mnemonics); + Assert.check(expectedCodeSequence.equals(foundCodeSequence), "found " + foundCodeSequence); + } + } + } + } + } + + assertFail("compiler.err.cant.ref.before.ctor.called", + """ + import jdk.internal.vm.annotation.NullRestricted; + import jdk.internal.vm.annotation.Strict; + class StrictNR { + static value class IValue { + int i = 0; + } + value class SValue { + short s = 0; + } + @Strict + @NullRestricted + IValue val = new IValue(); + @Strict + @NullRestricted + final IValue val2; + @Strict + @NullRestricted + SValue val3 = new SValue(); + } + """ + ); + assertFail("compiler.err.strict.field.not.have.been.initialized.before.super", + """ + import jdk.internal.vm.annotation.Strict; + class Test { + @Strict int i; + } + """ + ); + assertFail("compiler.err.strict.field.not.have.been.initialized.before.super", + """ + import jdk.internal.vm.annotation.Strict; + class Test { + @Strict int i; + Test() { + super(); + i = 0; + } + } + """ + ); + assertFail("compiler.err.cant.ref.before.ctor.called", + """ + import jdk.internal.vm.annotation.NullRestricted; + import jdk.internal.vm.annotation.Strict; + class StrictNR { + static value class IValue { + int i = 0; + } + value class SValue { + short s = 0; + } + @Strict + @NullRestricted + IValue val = new IValue(); + @Strict + @NullRestricted + SValue val4; + public StrictNR() { + val4 = new SValue(); + } + } + """ + ); + } finally { + setCompileOptions(previousOptions); + } + + source = + """ + value class V { + int i = 1; + int y; + V() { + y = 2; + } + } + """; + { + File dir = assertOK(true, source); + File fileEntry = dir.listFiles()[0]; + var expectedCodeSequence = "putfield i,putfield y"; + var classFile = ClassFile.of().parse(fileEntry.toPath()); + for (var method : classFile.methods()) { + if (method.methodName().equalsString("")) { + var code = method.findAttribute(Attributes.code()).orElseThrow(); + List mnemonics = new ArrayList<>(); + for (var coe : code) { + if (coe instanceof FieldInstruction inst && inst.opcode() == Opcode.PUTFIELD) { + mnemonics.add(inst.opcode().name().toLowerCase(Locale.ROOT) + " " + inst.name()); + } + } + var foundCodeSequence = String.join(",", mnemonics); + Assert.check(expectedCodeSequence.equals(foundCodeSequence), "found " + foundCodeSequence); + } + } + } + } + + @Test + void testThisCallingConstructor() throws Exception { + // make sure that this() calling constructors doesn't initialize final fields + String source = + """ + value class Test { + int i; + Test() { + this(0); + } + + Test(int i) { + this.i = i; + } + } + """; + File dir = assertOK(true, source); + File fileEntry = dir.listFiles()[0]; + String expectedCodeSequenceThisCallingConst = "aload_0,iconst_0,invokespecial,return"; + String expectedCodeSequenceNonThisCallingConst = "aload_0,iload_1,putfield,aload_0,invokespecial,return"; + var classFile = ClassFile.of().parse(fileEntry.toPath()); + for (var method : classFile.methods()) { + if (method.methodName().equalsString("")) { + var code = method.findAttribute(Attributes.code()).orElseThrow(); + List mnemonics = new ArrayList<>(); + for (var coe : code) { + if (coe instanceof Instruction inst) { + mnemonics.add(inst.opcode().name().toLowerCase(Locale.ROOT)); + } + } + var foundCodeSequence = String.join(",", mnemonics); + var expected = method.methodTypeSymbol().parameterCount() == 0 ? + expectedCodeSequenceThisCallingConst : expectedCodeSequenceNonThisCallingConst; + Assert.check(expected.equals(foundCodeSequence), "found " + foundCodeSequence); + } + } + } + + @Test + void testSelectors() throws Exception { + assertOK( + """ + value class V { + void selector() { + Class c = int.class; + } + } + """ + ); + assertFail("compiler.err.expected", + """ + value class V { + void selector() { + int i = int.some_selector; + } + } + """ + ); + } + + @Test + void testNullAssigment() throws Exception { + assertOK( + """ + value final class V { + final int x = 10; + + value final class X { + final V v; + final V v2; + + X() { + this.v = null; + this.v2 = null; + } + + X(V v) { + this.v = v; + this.v2 = v; + } + + V foo(X x) { + x = new X(null); // OK + return x.v; + } + } + V bar(X x) { + x = new X(null); // OK + return x.v; + } + + class Y { + V v; + V [] va = { null }; // OK: array initialization + V [] va2 = new V[] { null }; // OK: array initialization + void ooo(X x) { + x = new X(null); // OK + v = null; // legal assignment. + va[0] = null; // legal. + va = new V[] { null }; // legal + } + } + } + """ + ); + } + + @Test + void testSerializationWarnings() throws Exception { + String[] previousOptions = getCompileOptions(); + try { + setCompileOptions(new String[] {"-Xlint:serial", "--enable-preview", "--source", + Integer.toString(Runtime.version().feature())}); + assertOK( + """ + import java.io.*; + abstract value class AVC implements Serializable {} + """); + assertOKWithWarning("compiler.warn.serializable.value.class.without.write.replace.1", + """ + import java.io.*; + value class VC implements Serializable { + private static final long serialVersionUID = 0; + } + """); + assertOK( + """ + import java.io.*; + class C implements Serializable { + private static final long serialVersionUID = 0; + } + """); + assertOK( + """ + import java.io.*; + abstract value class Super implements Serializable { + private static final long serialVersionUID = 0; + protected Object writeReplace() throws ObjectStreamException { + return null; + } + } + value class ValueSerializable extends Super { + private static final long serialVersionUID = 1; + } + """); + assertOK( + """ + import java.io.*; + abstract value class Super implements Serializable { + private static final long serialVersionUID = 0; + Object writeReplace() throws ObjectStreamException { + return null; + } + } + value class ValueSerializable extends Super { + private static final long serialVersionUID = 1; + } + """); + assertOK( + """ + import java.io.*; + abstract value class Super implements Serializable { + private static final long serialVersionUID = 0; + public Object writeReplace() throws ObjectStreamException { + return null; + } + } + value class ValueSerializable extends Super { + private static final long serialVersionUID = 1; + } + """); + assertOKWithWarning("compiler.warn.serializable.value.class.without.write.replace.1", + """ + import java.io.*; + abstract value class Super implements Serializable { + private static final long serialVersionUID = 0; + private Object writeReplace() throws ObjectStreamException { + return null; + } + } + value class ValueSerializable extends Super { + private static final long serialVersionUID = 1; + } + """); + assertOKWithWarning("compiler.warn.serializable.value.class.without.write.replace.2", + """ + import java.io.*; + abstract value class Super implements Serializable { + private static final long serialVersionUID = 0; + private Object writeReplace() throws ObjectStreamException { + return null; + } + } + class Serializable1 extends Super { + private static final long serialVersionUID = 1; + } + class Serializable2 extends Serializable1 { + private static final long serialVersionUID = 1; + } + """); + assertOK( + """ + import java.io.*; + abstract value class Super implements Serializable { + private static final long serialVersionUID = 0; + Object writeReplace() throws ObjectStreamException { + return null; + } + } + class ValueSerializable extends Super { + private static final long serialVersionUID = 1; + } + """); + assertOK( + """ + import java.io.*; + abstract value class Super implements Serializable { + private static final long serialVersionUID = 0; + public Object writeReplace() throws ObjectStreamException { + return null; + } + } + class ValueSerializable extends Super { + private static final long serialVersionUID = 1; + } + """); + assertOK( + """ + import java.io.*; + abstract value class Super implements Serializable { + private static final long serialVersionUID = 0; + protected Object writeReplace() throws ObjectStreamException { + return null; + } + } + class ValueSerializable extends Super { + private static final long serialVersionUID = 1; + } + """); + assertOK( + """ + import java.io.*; + value record ValueRecord() implements Serializable { + private static final long serialVersionUID = 1; + } + """); + assertOK( + // Number is a special case, no warning for identity classes extending it + """ + class NumberSubClass extends Number { + private static final long serialVersionUID = 0L; + @Override + public double doubleValue() { return 0; } + @Override + public int intValue() { return 0; } + @Override + public long longValue() { return 0; } + @Override + public float floatValue() { return 0; } + } + """ + ); + } finally { + setCompileOptions(previousOptions); + } + } + + @Test + void testAssertUnsetFieldsSMEntry() throws Exception { + String[] previousOptions = getCompileOptions(); + try { + String[] testOptions = { + "--enable-preview", + "-source", Integer.toString(Runtime.version().feature()), + "-XDnoLocalProxyVars", + "-XDdebug.stackmap", + "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED" + }; + setCompileOptions(testOptions); + + record Data(String src, int[] expectedFrameTypes, String[][] expectedUnsetFields) {} + for (Data data : List.of( + new Data( + """ + import jdk.internal.vm.annotation.Strict; + class Test { + @Strict + final int x; + @Strict + final int y; + Test(boolean a, boolean b) { + if (a) { // early_larval {x, y} + x = 1; + if (b) { // early_larval {y} + y = 1; + } else { // early_larval {y} + y = 2; + } + } else { // early_larval {x, y} + x = y = 3; + } + super(); + } + } + """, + // three unset_fields entries, entry type 246, are expected in the stackmap table + new int[] {246, 246, 246}, + // expected fields for each of them: + new String[][] { new String[] { "y:I" }, new String[] { "x:I", "y:I" }, new String[] {} } + ), + new Data( + """ + import jdk.internal.vm.annotation.Strict; + class Test { + @Strict + final int x; + @Strict + final int y; + Test(int n) { + switch(n) { + case 2: + x = y = 2; + break; + default: + x = y = 100; + break; + } + super(); + } + } + """, + // here we expect only one + new int[] {20, 12, 246}, + // stating that no field is unset + new String[][] { new String[] {} } + ), + new Data( + """ + import jdk.internal.vm.annotation.Strict; + class Test { + @Strict + final int x; + @Strict + final int y; + Test(int n) { + if (n % 3 == 0) { + x = n / 3; + } else { // no unset change + x = n + 2; + } // early_larval {y} + y = n >>> 3; + super(); + if ((char) n != n) { + n -= 5; + } // no uninitializedThis - automatically cleared unsets + Math.abs(n); + } + } + """, + // here we expect only one, none for the post-larval frame + new int[] {16, 246, 255}, + // stating that y is unset when if-else finishes + new String[][] { new String[] {"y:I"} } + ) + )) { + File dir = assertOK(true, data.src()); + for (final File fileEntry : dir.listFiles()) { + var classFile = ClassFile.of().parse(fileEntry.toPath()); + for (var method : classFile.methods()) { + if (method.methodName().equalsString(ConstantDescs.INIT_NAME)) { + var code = method.findAttribute(Attributes.code()).orElseThrow(); + var stackMapTable = code.findAttribute(Attributes.stackMapTable()).orElseThrow(); + Assert.check(data.expectedFrameTypes().length == stackMapTable.entries().size(), "unexpected stackmap length"); + int entryIndex = 0; + int expectedUnsetFieldsIndex = 0; + for (var entry : stackMapTable.entries()) { + Assert.check(data.expectedFrameTypes()[entryIndex++] == entry.frameType(), "expected " + data.expectedFrameTypes()[entryIndex - 1] + " found " + entry.frameType()); + if (entry.frameType() == 246) { + Assert.check(data.expectedUnsetFields()[expectedUnsetFieldsIndex].length == entry.unsetFields().size()); + int index = 0; + for (var nat : entry.unsetFields()) { + String unsetStr = nat.name() + ":" + nat.type(); + Assert.check(data.expectedUnsetFields()[expectedUnsetFieldsIndex][index++].equals(unsetStr)); + } + expectedUnsetFieldsIndex++; + } + } + } + } + } + } + } finally { + setCompileOptions(previousOptions); + } + } + + @Test + void testLocalProxyVars() throws Exception { + String[] previousOptions = getCompileOptions(); + try { + String[] testOptions = { + "--enable-preview", + "-source", Integer.toString(Runtime.version().feature()), + "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED" + }; + setCompileOptions(testOptions); + String[] sources = new String[] { + """ + value class Test { + int i; + int j; + Test() {// javac should generate a proxy local var for `i` + i = 1; + j = i; // as here `i` is being read during the early construction phase, use the local var instead + super(); + System.err.println(i); + } + } + """, + """ + import jdk.internal.vm.annotation.Strict; + class Test { + @Strict + int i; + @Strict + int j; + Test() { + i = 1; + j = i; + super(); + System.err.println(i); + } + } + """ + }; + for (String source : sources) { + File dir = assertOK(true, source); + File fileEntry = dir.listFiles()[0]; + String expectedCodeSequence = "iconst_1,istore_1,aload_0,iload_1,putfield,aload_0,iload_1,putfield," + + "aload_0,invokespecial,getstatic,aload_0,getfield,invokevirtual,return"; + var classFile = ClassFile.of().parse(fileEntry.toPath()); + for (var method : classFile.methods()) { + if (method.methodName().equalsString("")) { + var code = method.findAttribute(Attributes.code()).orElseThrow(); + List mnemonics = new ArrayList<>(); + for (var coe : code) { + if (coe instanceof Instruction inst) { + mnemonics.add(inst.opcode().name().toLowerCase(Locale.ROOT)); + } + } + var foundCodeSequence = String.join(",", mnemonics); + Assert.check(expectedCodeSequence.equals(foundCodeSequence), "found " + foundCodeSequence); + } + } + } + } finally { + setCompileOptions(previousOptions); + } + } +} diff --git a/test/langtools/tools/javac/valhalla/value-objects/ValueObjectsBinaryCompatibilityTests.java b/test/langtools/tools/javac/valhalla/value-objects/ValueObjectsBinaryCompatibilityTests.java new file mode 100644 index 00000000000..89ba8a8ef1f --- /dev/null +++ b/test/langtools/tools/javac/valhalla/value-objects/ValueObjectsBinaryCompatibilityTests.java @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test 8292817 + * @summary binary compatibility tests for value objects + * @library /tools/lib + * @modules jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * jdk.compiler/com.sun.tools.javac.util + * jdk.compiler/com.sun.tools.javac.code + * @build toolbox.ToolBox toolbox.JavacTask + * @enablePreview + * @run main ValueObjectsBinaryCompatibilityTests + */ + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import toolbox.TestRunner; +import toolbox.ToolBox; +import toolbox.JavaTask; +import toolbox.JavacTask; +import toolbox.Task; + +public class ValueObjectsBinaryCompatibilityTests extends TestRunner { + ToolBox tb; + + ValueObjectsBinaryCompatibilityTests() { + super(System.err); + tb = new ToolBox(); + } + + protected void runTests() throws Exception { + runTests(m -> new Object[]{Paths.get(m.getName())}); + } + + public static void main(String... args) throws Exception { + new ValueObjectsBinaryCompatibilityTests().runTests(); + } + + Path[] findJavaFiles(Path... paths) throws IOException { + return tb.findJavaFiles(paths); + } + + /* 1- compiles the first version of the source code, code1, along with the client source code + * 2- executes the client class just to make sure that it works, sanity check + * 3- compiles the second version, code2 + * 4- executes the client class and makes sure that the VM throws the expected error or not + * depending on the shouldFail argument + */ + private void testCompatibilityAfterChange( + Path base, + String code1, + String code2, + String clientCode, + boolean shouldFail, + Class expectedError) throws Exception { + Path src = base.resolve("src"); + Path pkg = src.resolve("pkg"); + Path src1 = pkg.resolve("Test"); + Path client = pkg.resolve("Client"); + + tb.writeJavaFiles(src1, code1); + tb.writeJavaFiles(client, clientCode); + + Path out = base.resolve("out"); + Files.createDirectories(out); + + new JavacTask(tb) + .outdir(out) + .options("--enable-preview", "-source", String.valueOf(Runtime.version().feature())) + .files(findJavaFiles(pkg)) + .run(); + + // let's execute to check that it's working + String output = new JavaTask(tb) + .classpath(out.toString()) + .classArgs("pkg.Client") + .vmOptions("--enable-preview") + .run() + .writeAll() + .getOutput(Task.OutputKind.STDOUT); + + // let's first check that it runs wo issues + if (!output.contains("Hello World!")) { + throw new AssertionError("execution of Client didn't finish"); + } + + // now lets change the first class + tb.writeJavaFiles(src1, code2); + + new JavacTask(tb) + .outdir(out) + .files(findJavaFiles(src1)) + .options("--enable-preview", "-source", String.valueOf(Runtime.version().feature())) + .run(); + + if (shouldFail) { + // let's now check that we get the expected error + output = new JavaTask(tb) + .classpath(out.toString()) + .classArgs("pkg.Client") + .vmOptions("--enable-preview") + .run(Task.Expect.FAIL) + .writeAll() + .getOutput(Task.OutputKind.STDERR); + if (!output.contains(expectedError.getName())) { + throw new AssertionError(expectedError.getName() + " expected"); + } + } else { + new JavaTask(tb) + .classpath(out.toString()) + .classArgs("pkg.Client") + .vmOptions("--enable-preview") + .run(Task.Expect.SUCCESS); + } + } + + @Test + public void testAbstractValueToValueClass(Path base) throws Exception { + /* Removing the abstract modifier from a value class declaration has the side-effect of making the class final, + * with binary compatibility risks outlined in 13.4.2.3 + */ + testCompatibilityAfterChange( + base, + """ + package pkg; + public abstract value class A {} + """, + """ + package pkg; + public value class A {} + """, + """ + package pkg; + public value class Client extends A { + public static void main(String... args) { + System.out.println("Hello World!"); + } + } + """, + true, + IncompatibleClassChangeError.class + ); + } + + @Test + public void testAbstractOrFinalIdentityToValueClass(Path base) throws Exception { + /* Modifying an abstract or final identity class to be a value class does not break compatibility + * with pre-existing binaries + */ + testCompatibilityAfterChange( + base, + """ + package pkg; + public abstract class A {} + """, + """ + package pkg; + public abstract value class A {} + """, + """ + package pkg; + public class Client extends A { + public static void main(String... args) { + System.out.println("Hello World!"); + } + } + """, + false, + null + ); + + testCompatibilityAfterChange( + base, + """ + package pkg; + public final class A {} + """, + """ + package pkg; + public final value class A {} + """, + """ + package pkg; + public class Client { + A a; + public static void main(String... args) { + System.out.println("Hello World!"); + } + } + """, + false, + null + ); + } + + @Test + public void testAddingValueModifier(Path base) throws Exception { + /* Adding the value modifier to a non-abstract, non-final class declaration has the side-effect of making + * the class final, with binary compatibility risks outlined in 13.4.2.3 + */ + testCompatibilityAfterChange( + base, + """ + package pkg; + public class A {} + """, + """ + package pkg; + public value class A {} + """, + """ + package pkg; + public class Client extends A { + public static void main(String... args) { + System.out.println("Hello World!"); + } + } + """, + true, + IncompatibleClassChangeError.class + ); + } + + @Test + public void testValueToIdentityClass(Path base) throws Exception { + /* If a value class is changed to be an identity class, then an IncompatibleClassChangeError is thrown if a + * binary of a pre-existing value subclass of this class is loaded + */ + testCompatibilityAfterChange( + base, + """ + package pkg; + public abstract value class A {} + """, + """ + package pkg; + public abstract class A {} + """, + """ + package pkg; + public value class Client extends A { + public static void main(String... args) { + System.out.println("Hello World!"); + } + } + """, + true, + IncompatibleClassChangeError.class + ); + /* Removing the value modifier from a non-abstract value class does not break compatibility with + * pre-existing binaries. + */ + testCompatibilityAfterChange( + base, + """ + package pkg; + public value class A {} + """, + """ + package pkg; + public class A {} + """, + """ + package pkg; + public class Client { + public static void main(String... args) { + A a = new A(); + System.out.println("Hello World!"); + } + } + """, + false, + null + ); + } +} diff --git a/test/langtools/tools/javap/4870651/T4870651.java b/test/langtools/tools/javap/4870651/T4870651.java index dd1536db859..2679beb2454 100644 --- a/test/langtools/tools/javap/4870651/T4870651.java +++ b/test/langtools/tools/javap/4870651/T4870651.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 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,11 +27,13 @@ * @summary javap should recognize generics, varargs, enum; * javap prints "extends java.lang.Object" * @modules jdk.jdeps/com.sun.tools.javap + * @modules java.base/jdk.internal.misc * @build T4870651 Test * @run main T4870651 */ import java.io.*; +import jdk.internal.misc.PreviewFeatures; public class T4870651 { public static void main(String[] args) throws Exception { @@ -46,7 +48,9 @@ public void run() throws IOException { "v1(java.lang.String...)"); verify("Test$Enum", - "flags: (0x4030) ACC_FINAL, ACC_SUPER, ACC_ENUM", + PreviewFeatures.isEnabled() + ? "flags: (0x4030) ACC_FINAL, ACC_IDENTITY, ACC_ENUM" + : "flags: (0x4030) ACC_FINAL, ACC_SUPER, ACC_ENUM", "flags: (0x4019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM"); if (errors > 0) diff --git a/test/langtools/tools/javap/T4975569.java b/test/langtools/tools/javap/T4975569.java index c43598b41db..7454494b096 100644 --- a/test/langtools/tools/javap/T4975569.java +++ b/test/langtools/tools/javap/T4975569.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,10 +22,23 @@ */ /* - * @test + * @test id=NoPreview * @bug 4975569 6622215 8034861 * @summary javap doesn't print new flag bits * @modules jdk.jdeps/com.sun.tools.javap + * @modules java.base/jdk.internal.misc + * @run main T4975569 + */ + +/* + * @test id=Preview + * @bug 4975569 6622215 8034861 + * @summary javap doesn't print new flag bits - Preview + * @modules jdk.jdeps/com.sun.tools.javap + * @modules java.base/jdk.internal.misc + * @enablePreview + * @compile -XDforcePreview T4975569.java + * @run main T4975569 */ import java.io.*; @@ -33,6 +46,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import jdk.internal.misc.PreviewFeatures; + public class T4975569 { private static final String NEW_LINE = System.getProperty("line.separator"); private static final String TEST_CLASSES = System.getProperty("test.classes", "."); @@ -41,9 +56,16 @@ public static void main(String... args) { new T4975569().run(); } + T4975569() { + System.currentTimeMillis(); + super(); // Trigger forced preview + } + void run() { verify(Anno.class.getName(), "flags: \\(0x2600\\) ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION"); - verify(E.class.getName(), "flags: \\(0x4030\\) ACC_FINAL, ACC_SUPER, ACC_ENUM"); + verify(E.class.getName(), PreviewFeatures.isEnabled() + ? "flags: \\(0x4030\\) ACC_FINAL, ACC_IDENTITY, ACC_ENUM" + : "flags: \\(0x4030\\) ACC_FINAL, ACC_SUPER, ACC_ENUM"); verify(S.class.getName(), "flags: \\(0x1040\\) ACC_BRIDGE, ACC_SYNTHETIC", "InnerClasses:\n static [# =\\w]+; +// "); verify(V.class.getName(), "void m\\(java.lang.String...\\)", @@ -105,4 +127,3 @@ static class S extends T4975569 { protected class Prot { } private class Priv { int i; } } - diff --git a/test/langtools/tools/javap/UndefinedAccessFlagTest.java b/test/langtools/tools/javap/UndefinedAccessFlagTest.java index 682483223ae..551f2040010 100644 --- a/test/langtools/tools/javap/UndefinedAccessFlagTest.java +++ b/test/langtools/tools/javap/UndefinedAccessFlagTest.java @@ -92,7 +92,7 @@ void test(TestLocation location) throws Throwable { }); case InnerClassesAttribute attr when location == TestLocation.INNER_CLASS -> cb .with(InnerClassesAttribute.of(attr.classes().stream() - .map(ic -> InnerClassInfo.of(ic.innerClass(), ic.outerClass(), ic.innerName(), ic.flagsMask() | ACC_SUPER)) + .map(ic -> InnerClassInfo.of(ic.innerClass(), ic.outerClass(), ic.innerName(), ic.flagsMask() | 0x0050)) .toList())); default -> cb.with(ce); } diff --git a/test/langtools/tools/javap/stackmap/StackmapTest.java b/test/langtools/tools/javap/stackmap/StackmapTest.java index 7fdaed746ef..c0ea2c6725c 100644 --- a/test/langtools/tools/javap/stackmap/StackmapTest.java +++ b/test/langtools/tools/javap/stackmap/StackmapTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -66,10 +66,10 @@ public class StackmapTest { "}\n"; private static final String goldenOut = - " frame_type = 255 /* full_frame */\n" + - " frame_type = 255 /* full_frame */\n" + + " frame_type = 255 /* full_entry */\n" + + " frame_type = 255 /* full_entry */\n" + " frame_type = 73 /* same_locals_1_stack_item */\n" + - " frame_type = 255 /* full_frame */\n" + + " frame_type = 255 /* full_entry */\n" + " offset_delta = 19\n" + " offset_delta = 0\n" + " offset_delta = 2\n" + diff --git a/test/langtools/tools/javap/value_classes/ValueClassesJavapTest.java b/test/langtools/tools/javap/value_classes/ValueClassesJavapTest.java new file mode 100644 index 00000000000..a6af3f339b1 --- /dev/null +++ b/test/langtools/tools/javap/value_classes/ValueClassesJavapTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test 8335770 + * @summary improve javap code coverage for value classes + * @enablePreview + * @library /tools/lib + * @modules jdk.jdeps/com.sun.tools.javap + * @build toolbox.ToolBox toolbox.JavapTask + * @run main ValueClassesJavapTest + */ + +import java.nio.file.*; +import java.util.*; + +import toolbox.JavapTask; +import toolbox.TestRunner; +import toolbox.ToolBox; +import toolbox.Task; + +public class ValueClassesJavapTest extends TestRunner { + ToolBox tb = new ToolBox(); + + abstract value class AbstractValueClass {} + value class ValueClass {} + class IdentityClass { + ValueClass val; + } + class IdentityClass2 { + void m(ValueClass param) {} + } + + private static final List expectedOutput1 = List.of( + "Compiled from \"ValueClassesJavapTest.java\"", + "final value class ValueClassesJavapTest$ValueClass {", + " ValueClassesJavapTest$ValueClass(ValueClassesJavapTest);", + "}"); + private static final List expectedOutput2 = List.of( + "Compiled from \"ValueClassesJavapTest.java\"", + "abstract value class ValueClassesJavapTest$AbstractValueClass {", + " ValueClassesJavapTest$AbstractValueClass(ValueClassesJavapTest);", + "}"); + private static final List expectedOutput3 = List.of( + "LoadableDescriptors:", + " LValueClassesJavapTest$ValueClass;" + ); + + ValueClassesJavapTest() throws Exception { + super(System.err); + } + + public static void main(String... args) throws Exception { + ValueClassesJavapTest tester = new ValueClassesJavapTest(); + tester.runTests(); + } + + protected void runTests() throws Exception { + runTests(m -> new Object[] { Paths.get(m.getName()) }); + } + + @Test + public void testMain(Path base) throws Exception { + Path testClassesPath = Paths.get(System.getProperty("test.classes")); + List output = new JavapTask(tb) + .options("-p", testClassesPath.resolve(this.getClass().getSimpleName() + "$ValueClass.class").toString()) + .run() + .writeAll() + .getOutputLines(Task.OutputKind.DIRECT); + System.out.println(output); + if (!output.equals(expectedOutput1)) { + throw new AssertionError(String.format("unexpected output:\n %s", output)); + } + + output = new JavapTask(tb) + .options("-p", testClassesPath.resolve(this.getClass().getSimpleName() + "$AbstractValueClass.class").toString()) + .run() + .writeAll() + .getOutputLines(Task.OutputKind.DIRECT); + System.out.println(output); + if (!output.equals(expectedOutput2)) { + throw new AssertionError(String.format("unexpected output:\n %s", output)); + } + + output = new JavapTask(tb) + .options("-p", "-v", testClassesPath.resolve(this.getClass().getSimpleName() + "$IdentityClass.class").toString()) + .run() + .writeAll() + .getOutputLines(Task.OutputKind.DIRECT); + System.out.println(output); + if (!output.containsAll(expectedOutput3)) { + throw new AssertionError(String.format("unexpected output:\n %s", output)); + } + + output = new JavapTask(tb) + .options("-p", "-v", testClassesPath.resolve(this.getClass().getSimpleName() + "$IdentityClass2.class").toString()) + .run() + .writeAll() + .getOutputLines(Task.OutputKind.DIRECT); + System.out.println(output); + if (!output.containsAll(expectedOutput3)) { + throw new AssertionError(String.format("unexpected output:\n %s", output)); + } + } +} diff --git a/test/lib-test/TEST.ROOT b/test/lib-test/TEST.ROOT index 162e6e15ec2..21b6fba41f2 100644 --- a/test/lib-test/TEST.ROOT +++ b/test/lib-test/TEST.ROOT @@ -29,7 +29,7 @@ keys=randomness # Minimum jtreg version -requiredVersion=7.5.2+1 +requiredVersion=7.5.1+1 # Allow querying of various System properties in @requires clauses requires.extraPropDefns = ../jtreg-ext/requires/VMProps.java diff --git a/test/lib/jdk/test/lib/NetworkConfiguration.java b/test/lib/jdk/test/lib/NetworkConfiguration.java index a9e6821b2e6..12c478cd48b 100644 --- a/test/lib/jdk/test/lib/NetworkConfiguration.java +++ b/test/lib/jdk/test/lib/NetworkConfiguration.java @@ -57,6 +57,7 @@ public class NetworkConfiguration { private boolean has_linklocaladdress = false; private boolean has_globaladdress = false; + @SuppressWarnings("initialization") private NetworkConfiguration( Map> ip4Interfaces, Map> ip6Interfaces) { diff --git a/test/lib/jdk/test/lib/cds/CDSAppTester.java b/test/lib/jdk/test/lib/cds/CDSAppTester.java index fd244c6acc6..f353e546406 100644 --- a/test/lib/jdk/test/lib/cds/CDSAppTester.java +++ b/test/lib/jdk/test/lib/cds/CDSAppTester.java @@ -65,6 +65,7 @@ abstract public class CDSAppTester { * - name.classlist * - name.jsa */ + @SuppressWarnings("initialization") public CDSAppTester(String name) { if (CDSTestUtils.DYNAMIC_DUMP) { throw new SkippedException("Tests based on CDSAppTester should be excluded when -Dtest.dynamic.cds.archive is specified"); diff --git a/test/lib/jdk/test/lib/cds/CDSTestUtils.java b/test/lib/jdk/test/lib/cds/CDSTestUtils.java index 13ac2e4e97a..ef7d11e145c 100644 --- a/test/lib/jdk/test/lib/cds/CDSTestUtils.java +++ b/test/lib/jdk/test/lib/cds/CDSTestUtils.java @@ -135,7 +135,7 @@ public static class Result { private final CDSOptions options; private final boolean hasNormalExit; private final String CDS_DISABLED = "warning: CDS is disabled when the"; - + @SuppressWarnings("initialization") public Result(CDSOptions opts, OutputAnalyzer out) throws Exception { checkMappingFailure(out); this.options = opts; diff --git a/test/lib/jdk/test/lib/classloader/GeneratingClassLoader.java b/test/lib/jdk/test/lib/classloader/GeneratingClassLoader.java index 19dd4df9591..3ea4b67c848 100644 --- a/test/lib/jdk/test/lib/classloader/GeneratingClassLoader.java +++ b/test/lib/jdk/test/lib/classloader/GeneratingClassLoader.java @@ -66,6 +66,7 @@ public synchronized Class loadClass(String name, boolean resolve) * Create generating class loader that will use class file for given class * from classpath as template. */ + @SuppressWarnings("initialization") public GeneratingClassLoader(String templateClassName) { this.templateClassName = templateClassName; classPath = System.getProperty("java.class.path").split(File.pathSeparator); diff --git a/test/lib/jdk/test/lib/containers/docker/DockerRunOptions.java b/test/lib/jdk/test/lib/containers/docker/DockerRunOptions.java index 1942ae734d9..a5ad54888ef 100644 --- a/test/lib/jdk/test/lib/containers/docker/DockerRunOptions.java +++ b/test/lib/jdk/test/lib/containers/docker/DockerRunOptions.java @@ -54,6 +54,7 @@ public class DockerRunOptions { * * @return Default docker run options */ + @SuppressWarnings("initialization") public DockerRunOptions(String imageNameAndTag, String javaCmd, String classToRun, String... javaOpts) { this.imageNameAndTag = imageNameAndTag; diff --git a/test/lib/jdk/test/lib/containers/systemd/SystemdRunOptions.java b/test/lib/jdk/test/lib/containers/systemd/SystemdRunOptions.java index 5f5d513a8b2..e1166b64e4c 100644 --- a/test/lib/jdk/test/lib/containers/systemd/SystemdRunOptions.java +++ b/test/lib/jdk/test/lib/containers/systemd/SystemdRunOptions.java @@ -48,6 +48,7 @@ public class SystemdRunOptions { * * @return Default docker run options */ + @SuppressWarnings("initialization") public SystemdRunOptions(String classToRun, String... javaOpts) { this.classToRun = classToRun; Collections.addAll(this.javaOpts, javaOpts); diff --git a/test/lib/jdk/test/lib/hprof/model/InlinedJavaField.java b/test/lib/jdk/test/lib/hprof/model/InlinedJavaField.java new file mode 100644 index 00000000000..89c30f3715a --- /dev/null +++ b/test/lib/jdk/test/lib/hprof/model/InlinedJavaField.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * 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.test.lib.hprof.model; + +public class InlinedJavaField extends JavaField { + + private final JavaClass inlinedFieldClass; + + public InlinedJavaField(String name, String signature, JavaClass inlinedFieldClass) { + super(name, signature); + this.inlinedFieldClass = inlinedFieldClass; + } + + @Override + public boolean hasId() { + return false; + } + + public JavaClass getInlinedFieldClass() { + return inlinedFieldClass; + } + +} diff --git a/test/lib/jdk/test/lib/hprof/model/InlinedJavaObject.java b/test/lib/jdk/test/lib/hprof/model/InlinedJavaObject.java new file mode 100644 index 00000000000..a0f487d6f2c --- /dev/null +++ b/test/lib/jdk/test/lib/hprof/model/InlinedJavaObject.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * 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.test.lib.hprof.model; + +import java.io.IOException; + +/** + * Represents Inlined Java object + */ +public class InlinedJavaObject extends JavaObject { + + /** + * Construct a new InlinedJavaObject. + * + * @param clazz the class object + * @param offset The offset of field data + */ + public InlinedJavaObject(JavaClass clazz, long offset) { + super(clazz, offset); + } + + @Override + public String toString() { + return "inlined " + super.toString(); + } + + @Override + protected final long readValueLength() throws IOException { + // TODO: revise - clazz.getInlinedInstanceSize()? + return 0; + } + + @Override + protected long dataStartOffset() { + return getOffset(); + } + +} diff --git a/test/lib/jdk/test/lib/hprof/model/JavaClass.java b/test/lib/jdk/test/lib/hprof/model/JavaClass.java index eea149ceee8..827402cebc8 100644 --- a/test/lib/jdk/test/lib/hprof/model/JavaClass.java +++ b/test/lib/jdk/test/lib/hprof/model/JavaClass.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved. * 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,6 +73,9 @@ public class JavaClass extends JavaHeapObject { // Total number of fields including inherited ones private int totalNumFields; + // Size of the instance in the object of the class is inlined. + // Calculated lazily. + private int inlinedInstanceSize = -1; public JavaClass(long id, String name, long superclassId, long loaderId, long signersId, long protDomainId, @@ -114,6 +117,46 @@ public void resolve(Snapshot snapshot) { return; } mySnapshot = snapshot; + + // Resolve inlined fields. Should be done before resolveSuperclass for correct field counting + Snapshot.ClassInlinedFields[] inlinedFields = snapshot.findClassInlinedFields(id); + if (inlinedFields != null) { + int newCount = fields.length; + for (Snapshot.ClassInlinedFields f: inlinedFields) { + if (f.synthFieldCount == 0) { + // Empty primitive class. just skip it - no data there. + continue; + } + JavaHeapObject clazz = snapshot.findThing(f.fieldClassID); + if (clazz instanceof JavaClass fieldClass) { + fieldClass.resolve(snapshot); + + // Set new field. + fields[f.fieldIndex] = new InlinedJavaField(f.fieldName, 'Q' + fieldClass.getName() + ';', fieldClass); + newCount -= (f.synthFieldCount - 1); + // Reset invalid fields. + for (int i = 1; i < f.synthFieldCount; i++) { + fields[f.fieldIndex + i] = null; + } + } else { + // The field class not found. + System.out.println("WARNING: class of inlined field not found:" + getName() + "." + f.fieldName); + } + } + + // Set new fields. + JavaField[] newFields = new JavaField[newCount]; + int oldIndex = 0; + for (int i = 0; i < newFields.length; i++) { + while (fields[oldIndex] == null) { + oldIndex++; + } + newFields[i] = fields[oldIndex]; + oldIndex++; + } + fields = newFields; + } + resolveSuperclass(snapshot); if (superclass != null) { ((JavaClass) superclass).addSubclass(this); @@ -128,6 +171,7 @@ public void resolve(Snapshot snapshot) { } snapshot.getJavaLangClass().addInstance(this); super.resolve(snapshot); + return; } @@ -377,6 +421,44 @@ public int getInstanceSize() { return instanceSize + mySnapshot.getMinimumObjectSize(); } + public int getInlinedInstanceSize() { + if (inlinedInstanceSize < 0) { + int size = 0; + for (JavaField f: fields) { + if (f instanceof InlinedJavaField inlinedField) { + size += inlinedField.getInlinedFieldClass().getInlinedInstanceSize(); + } else { + char sig = f.getSignature().charAt(0); + switch (sig) { + case 'L': + case '[': + size += mySnapshot.getIdentifierSize(); + break; + case 'B': + case 'Z': + size += 1; + break; + case 'C': + case 'S': + size += 2; + break; + case 'I': + case 'F': + size += 4; + break; + case 'J': + case 'D': + size += 8; + break; + default: + throw new RuntimeException("unknown field type: " + sig); + } + } + } + inlinedInstanceSize = size; + } + return inlinedInstanceSize; + } /** * @return The size of all instances of this class. Correctly handles diff --git a/test/lib/jdk/test/lib/hprof/model/JavaObject.java b/test/lib/jdk/test/lib/hprof/model/JavaObject.java index 6ab2634588f..023a74752db 100644 --- a/test/lib/jdk/test/lib/hprof/model/JavaObject.java +++ b/test/lib/jdk/test/lib/hprof/model/JavaObject.java @@ -42,6 +42,12 @@ public class JavaObject extends JavaLazyReadObject { private Object clazz; // Number before resolve // JavaClass after resolve + + public JavaObject(Object clazz, long offset) { + super(offset); + this.clazz = clazz; + } + /** * Construct a new JavaObject. * @@ -49,8 +55,7 @@ public class JavaObject extends JavaLazyReadObject { * @param offset The offset of field data */ public JavaObject(long classID, long offset) { - super(offset); - this.clazz = makeId(classID); + this(makeId(classID), offset); } public void resolve(Snapshot snapshot) { @@ -216,7 +221,7 @@ public String toString() { * byte[length] */ @Override - protected final long readValueLength() throws IOException { + protected long readValueLength() throws IOException { long lengthOffset = getOffset() + 2 * idSize() + 4; return buf().getInt(lengthOffset); } @@ -226,7 +231,7 @@ protected final JavaThing[] readValue() throws IOException { return parseFields(false); } - private long dataStartOffset() { + protected long dataStartOffset() { return getOffset() + idSize() + 4 + idSize() + 4; } @@ -260,71 +265,77 @@ private JavaThing[] parseFields(boolean verbose) { JavaField f = fields[fieldNo]; char sig = f.getSignature().charAt(0); try { - switch (sig) { - case 'L': - case '[': { - long id = objectIdAt(offset); - offset += idSize(); - JavaObjectRef ref = new JavaObjectRef(id); - fieldValues[target+fieldNo] = ref.dereference(snapshot, f, verbose); - break; - } - case 'Z': { - byte value = byteAt(offset); - offset++; - fieldValues[target+fieldNo] = new JavaBoolean(value != 0); - break; - } - case 'B': { - byte value = byteAt(offset); - offset++; - fieldValues[target+fieldNo] = new JavaByte(value); - break; - } - case 'S': { - short value = shortAt(offset); - offset += 2; - fieldValues[target+fieldNo] = new JavaShort(value); - break; - } - case 'C': { - char value = charAt(offset); - offset += 2; - fieldValues[target+fieldNo] = new JavaChar(value); - break; - } - case 'I': { - int value = intAt(offset); - offset += 4; - fieldValues[target+fieldNo] = new JavaInt(value); - break; - } - case 'J': { - long value = longAt(offset); - offset += 8; - fieldValues[target+fieldNo] = new JavaLong(value); - break; - } - case 'F': { - float value = floatAt(offset); - offset += 4; - fieldValues[target+fieldNo] = new JavaFloat(value); - break; - } - case 'D': { - double value = doubleAt(offset); - offset += 8; - fieldValues[target+fieldNo] = new JavaDouble(value); - break; - } - default: - throw new RuntimeException("invalid signature: " + sig); + if (f instanceof InlinedJavaField inlinedField) { + JavaClass fieldClass = inlinedField.getInlinedFieldClass(); + fieldValues[target+fieldNo] = new InlinedJavaObject(fieldClass, offset); + offset += fieldClass.getInlinedInstanceSize(); + } else { + switch (sig) { + case 'L': + case '[': { + long id = objectIdAt(offset); + offset += idSize(); + JavaObjectRef ref = new JavaObjectRef(id); + fieldValues[target + fieldNo] = ref.dereference(snapshot, f, verbose); + break; + } + case 'Z': { + byte value = byteAt(offset); + offset++; + fieldValues[target + fieldNo] = new JavaBoolean(value != 0); + break; + } + case 'B': { + byte value = byteAt(offset); + offset++; + fieldValues[target + fieldNo] = new JavaByte(value); + break; + } + case 'S': { + short value = shortAt(offset); + offset += 2; + fieldValues[target + fieldNo] = new JavaShort(value); + break; + } + case 'C': { + char value = charAt(offset); + offset += 2; + fieldValues[target + fieldNo] = new JavaChar(value); + break; + } + case 'I': { + int value = intAt(offset); + offset += 4; + fieldValues[target + fieldNo] = new JavaInt(value); + break; + } + case 'J': { + long value = longAt(offset); + offset += 8; + fieldValues[target + fieldNo] = new JavaLong(value); + break; + } + case 'F': { + float value = floatAt(offset); + offset += 4; + fieldValues[target + fieldNo] = new JavaFloat(value); + break; + } + case 'D': { + double value = doubleAt(offset); + offset += 8; + fieldValues[target + fieldNo] = new JavaDouble(value); + break; + } + default: + throw new RuntimeException("invalid signature: " + sig); + } } - } catch (IOException exp) { - System.err.println("lazy read failed at offset " + offset); - exp.printStackTrace(); - return Snapshot.EMPTY_JAVATHING_ARRAY; + } catch (IOException exp) { + System.err.println("lazy read failed at offset " + offset); + exp.printStackTrace(); + return Snapshot.EMPTY_JAVATHING_ARRAY; } } return fieldValues; diff --git a/test/lib/jdk/test/lib/hprof/model/JavaValueArray.java b/test/lib/jdk/test/lib/hprof/model/JavaValueArray.java index 74812701a43..e8c19be43c8 100644 --- a/test/lib/jdk/test/lib/hprof/model/JavaValueArray.java +++ b/test/lib/jdk/test/lib/hprof/model/JavaValueArray.java @@ -35,36 +35,14 @@ import java.util.Objects; /** - * An array of values, that is, an array of ints, boolean, floats or the like. + * An array of values, that is, an array of ints, boolean, floats, etc. + * or flat array of primitive objects. * * @author Bill Foote */ public class JavaValueArray extends JavaLazyReadObject /*imports*/ implements ArrayTypeCodes { - private static String arrayTypeName(byte sig) { - switch (sig) { - case 'B': - return "byte[]"; - case 'Z': - return "boolean[]"; - case 'C': - return "char[]"; - case 'S': - return "short[]"; - case 'I': - return "int[]"; - case 'F': - return "float[]"; - case 'J': - return "long[]"; - case 'D': - return "double[]"; - default: - throw new RuntimeException("invalid array element sig: " + sig); - } - } - private static int elementSize(byte type) { switch (type) { case 'B': @@ -100,7 +78,7 @@ protected final long readValueLength() throws IOException { // length of the array in elements long len = buf().getInt(offset); // byte length of array - return len * elementSize(getElementType()); + return len * elementSize(getRealElementType()); } private long dataStartOffset() { @@ -172,6 +150,13 @@ protected final JavaThing[] readValue() throws IOException { } return res; } + case 'Q': { + for (int i = 0; i < len; i++) { + res[i] = new InlinedJavaObject(flatArrayElementClass, offset); + offset += flatArrayElementClass.getInlinedInstanceSize(); + } + return res; + } default: { throw new RuntimeException("unknown primitive type?"); } @@ -182,6 +167,8 @@ protected final JavaThing[] readValue() throws IOException { // JavaClass set only after resolve. private JavaClass clazz; + private long objID; + // This field contains elementSignature byte and // divider to be used to calculate length. Note that // length of content byte[] is not same as array length. @@ -197,8 +184,12 @@ protected final JavaThing[] readValue() throws IOException { // Number of bits to shift to get length divider private static final int LENGTH_DIVIDER_SHIFT = 8; - public JavaValueArray(byte elementSignature, long offset) { + // Flat array support. + private JavaClass flatArrayElementClass; + + public JavaValueArray(long id, byte elementSignature, long offset) { super(offset); + this.objID = id; this.data = (elementSignature & SIGNATURE_MASK); } @@ -206,6 +197,14 @@ public JavaClass getClazz() { return clazz; } + public boolean isFlatArray() { + return flatArrayElementClass != null; + } + + public JavaClass getFlatElementClazz() { + return flatArrayElementClass; + } + public void visitReferencedObjects(JavaHeapObjectVisitor v) { super.visitReferencedObjects(v); } @@ -214,11 +213,25 @@ public void resolve(Snapshot snapshot) { if (clazz instanceof JavaClass) { return; } - byte elementSig = getElementType(); - clazz = snapshot.findClass(arrayTypeName(elementSig)); - if (clazz == null) { - clazz = snapshot.getArrayClass("" + ((char) elementSig)); + + byte elementType = getElementType(); + String elementSig = "" + (char)elementType; + // Check if this is a flat array of primitive objects. + Number elementClassID = snapshot.findFlatArrayElementType(objID); + if (elementClassID != null) { + // This is flat array. + JavaHeapObject elementClazz = snapshot.findThing(getIdValue(elementClassID)); + if (elementClazz instanceof JavaClass elementJavaClazz) { + flatArrayElementClass = elementJavaClazz; + // need to resolve the element class + flatArrayElementClass.resolve(snapshot); + elementSig = "Q" + flatArrayElementClass.getName() + ";"; + } else { + // The class not found. + System.out.println("WARNING: flat array element class not found"); + } } + clazz = snapshot.getArrayClass(elementSig); getClazz().addInstance(this); super.resolve(snapshot); } @@ -244,6 +257,9 @@ public int getLength() { case 'D': divider = 8; break; + case 'Q': + divider = flatArrayElementClass.getInlinedInstanceSize(); + break; default: throw new RuntimeException("unknown primitive type: " + elementSignature); @@ -258,6 +274,10 @@ public JavaThing[] getElements() { } public byte getElementType() { + return isFlatArray() ? (byte)'Q' : getRealElementType(); + } + + private byte getRealElementType() { return (byte) (data & SIGNATURE_MASK); } @@ -280,7 +300,7 @@ public String valueString(boolean bigLimit) { StringBuilder result; JavaThing[] things = getValue(); byte elementSignature = getElementType(); - if (elementSignature == 'C') { + if (elementSignature == 'C' && !isFlatArray()) { result = new StringBuilder(); for (int i = 0; i < things.length; i++) { result.append(things[i]); diff --git a/test/lib/jdk/test/lib/hprof/model/ReachableExcludesImpl.java b/test/lib/jdk/test/lib/hprof/model/ReachableExcludesImpl.java index 9c4f68a1399..e69715f10c9 100644 --- a/test/lib/jdk/test/lib/hprof/model/ReachableExcludesImpl.java +++ b/test/lib/jdk/test/lib/hprof/model/ReachableExcludesImpl.java @@ -57,6 +57,7 @@ public class ReachableExcludesImpl implements ReachableExcludes { * Create a new ReachableExcludesImpl over the given file. The file will be * re-read whenever the timestamp changes. */ + @SuppressWarnings("initialization") public ReachableExcludesImpl(File excludesFile) { this.excludesFile = excludesFile; readFile(); diff --git a/test/lib/jdk/test/lib/hprof/model/ReachableObjects.java b/test/lib/jdk/test/lib/hprof/model/ReachableObjects.java index 3dcc5a08823..d5f4abe7d59 100644 --- a/test/lib/jdk/test/lib/hprof/model/ReachableObjects.java +++ b/test/lib/jdk/test/lib/hprof/model/ReachableObjects.java @@ -41,6 +41,7 @@ */ public class ReachableObjects { + @SuppressWarnings("initialization") public ReachableObjects(JavaHeapObject root, final ReachableExcludes excludes) { this.root = root; diff --git a/test/lib/jdk/test/lib/hprof/model/Root.java b/test/lib/jdk/test/lib/hprof/model/Root.java index c3b821be076..1a6b2c90394 100644 --- a/test/lib/jdk/test/lib/hprof/model/Root.java +++ b/test/lib/jdk/test/lib/hprof/model/Root.java @@ -66,7 +66,7 @@ public Root(long id, long referrerId, int type, String description) { this(id, referrerId, type, description, null); } - + @SuppressWarnings("initialization") public Root(long id, long referrerId, int type, String description, StackTrace stackTrace) { this.id = id; diff --git a/test/lib/jdk/test/lib/hprof/model/Snapshot.java b/test/lib/jdk/test/lib/hprof/model/Snapshot.java index dbb9c7fd1f3..c65abfe0334 100644 --- a/test/lib/jdk/test/lib/hprof/model/Snapshot.java +++ b/test/lib/jdk/test/lib/hprof/model/Snapshot.java @@ -65,6 +65,10 @@ public class Snapshot implements AutoCloseable { private Map classes = new TreeMap(); + private Map flatArrays = new HashMap<>(); + + private Map inlinedFields = new HashMap<>(); + // new objects relative to a baseline - lazily initialized private volatile Map newObjects; @@ -205,6 +209,41 @@ JavaClass addFakeInstanceClass(long classID, int instSize) { return c; } + public void addFlatArray(long objID, long elementClassID) { + flatArrays.put(makeId(objID), makeId(elementClassID)); + } + + /** + * @return null if the array is not flat array + */ + Number findFlatArrayElementType(long arrayObjectID) { + return flatArrays.get(makeId(arrayObjectID)); + } + + public static class ClassInlinedFields { + final int fieldIndex; + final int synthFieldCount; + final String fieldName; + final long fieldClassID; + + public ClassInlinedFields(int fieldIndex, int synthFieldCount, String fieldName, long fieldClassID) { + this.fieldIndex = fieldIndex; + this.synthFieldCount = synthFieldCount; + this.fieldName = fieldName; + this.fieldClassID = fieldClassID; + } + } + + public void addClassInlinedFields(long classID, ClassInlinedFields[] fields) { + inlinedFields.put(makeId(classID), fields); + } + + /** + * @return null if the class has no inlined fields + */ + ClassInlinedFields[] findClassInlinedFields(long classID) { + return inlinedFields.get(makeId(classID)); + } /** * @return true iff it's possible that some JavaThing instances might diff --git a/test/lib/jdk/test/lib/hprof/parser/HprofReader.java b/test/lib/jdk/test/lib/hprof/parser/HprofReader.java index 08deac80c0e..1a410e7c4c6 100644 --- a/test/lib/jdk/test/lib/hprof/parser/HprofReader.java +++ b/test/lib/jdk/test/lib/hprof/parser/HprofReader.java @@ -96,6 +96,12 @@ public class HprofReader extends Reader /* imports */ implements ArrayTypeCodes static final int HPROF_LOCKSTATS_WAIT_TIME = 0x10; static final int HPROF_LOCKSTATS_HOLD_TIME = 0x11; + static final int HPROF_FLAT_ARRAYS = 0x12; + static final int HPROF_INLINED_FIELDS = 0x13; + + static final int HPROF_FLAT_ARRAY = 0x01; + static final int HPROF_CLASS_WITH_INLINED_FIELDS = 0x01; + static final int HPROF_GC_ROOT_UNKNOWN = 0xff; static final int HPROF_GC_ROOT_JNI_GLOBAL = 0x01; static final int HPROF_GC_ROOT_JNI_LOCAL = 0x02; @@ -242,7 +248,7 @@ public Snapshot read() throws IOException { } case HPROF_HEAP_DUMP: { - if (dumpsToSkip <= 0) { + if (dumpsToSkip == 0) { try { readHeapDump(length, currPos); } catch (EOFException exp) { @@ -251,7 +257,6 @@ public Snapshot read() throws IOException { if (debugLevel > 0) { System.out.println(" Finished processing instances in heap dump."); } - return snapshot; } else { dumpsToSkip--; skipBytes(length); @@ -261,9 +266,9 @@ public Snapshot read() throws IOException { case HPROF_HEAP_DUMP_END: { if (version >= VERSION_JDK6) { - if (dumpsToSkip <= 0) { - skipBytes(length); // should be no-op - return snapshot; + if (dumpsToSkip == 0) { + // update dumpsToSkip to skip other dumps + dumpsToSkip--; } else { // skip this dump (of the end record for a sequence of dump segments) dumpsToSkip--; @@ -278,7 +283,7 @@ public Snapshot read() throws IOException { case HPROF_HEAP_DUMP_SEGMENT: { if (version >= VERSION_JDK6) { - if (dumpsToSkip <= 0) { + if (dumpsToSkip == 0) { try { // read the dump segment readHeapDump(length, currPos); @@ -352,6 +357,15 @@ public Snapshot read() throws IOException { skipBytes(length); break; } + case HPROF_FLAT_ARRAYS: { + readFlatArrays(length); + break; + } + case HPROF_INLINED_FIELDS: { + readInlinedFields(length); + break; + } + default: { skipBytes(length); warn("Ignoring unrecognized record type " + type); @@ -852,7 +866,7 @@ private long readArray(boolean isPrimitive) throws IOException { if (primitiveSignature != 0x00) { long size = elSize * (long)num; bytesRead += size; - JavaValueArray va = new JavaValueArray(primitiveSignature, start); + JavaValueArray va = new JavaValueArray(id, primitiveSignature, start); skipBytes(size); snapshot.addHeapObject(id, va); snapshot.setSiteTrace(va, stackTrace); @@ -902,6 +916,58 @@ private byte signatureFromTypeId(byte typeId) throws IOException { } } + private void readFlatArrays(long length) throws IOException { + while (length > 0) { + byte tag = in.readByte(); + length--; + switch (tag) { + case HPROF_FLAT_ARRAY: { + long objId = readID(); + length -= identifierSize; + long elementClassId = readID(); + length -= identifierSize; + snapshot.addFlatArray(objId, elementClassId); + break; + } + default: { + throw new IOException("Invalid tag " + tag); + } + } + } + } + + private void readInlinedFields(long length) throws IOException { + while (length > 0) { + byte tag = in.readByte(); + length--; + switch (tag) { + case HPROF_CLASS_WITH_INLINED_FIELDS: { + long classID = readID(); + length -= identifierSize; + int fieldNum = in.readUnsignedShort(); + length -= 2; + Snapshot.ClassInlinedFields[] fields = new Snapshot.ClassInlinedFields[fieldNum]; + for (int i = 0; i < fieldNum; i++) { + int fieldIndex = in.readUnsignedShort(); + length -= 2; + int synthFieldCount = in.readUnsignedShort(); + length -= 2; + String fieldName = getNameFromID(readID()); + length -= identifierSize; + long fieldClassId = readID(); + length -= identifierSize; + fields[i] = new Snapshot.ClassInlinedFields(fieldIndex, synthFieldCount, fieldName, fieldClassId); + } + snapshot.addClassInlinedFields(classID, fields); + break; + } + default: { + throw new IOException("Invalid tag " + tag); + } + } + } + } + private void handleEOF(EOFException exp, Snapshot snapshot) { if (debugLevel > 0) { exp.printStackTrace(); diff --git a/test/lib/jdk/test/lib/net/HttpHeaderParser.java b/test/lib/jdk/test/lib/net/HttpHeaderParser.java index 86e45fcd4de..851dd81cfb3 100644 --- a/test/lib/jdk/test/lib/net/HttpHeaderParser.java +++ b/test/lib/jdk/test/lib/net/HttpHeaderParser.java @@ -86,7 +86,7 @@ enum State { INITIAL, public HttpHeaderParser() { } - + @SuppressWarnings("initialization") public HttpHeaderParser(InputStream is) throws IOException, ProtocolException { parse(is); } diff --git a/test/lib/jdk/test/lib/net/SimpleSSLContext.java b/test/lib/jdk/test/lib/net/SimpleSSLContext.java index 3c26809f128..8429cd8422d 100644 --- a/test/lib/jdk/test/lib/net/SimpleSSLContext.java +++ b/test/lib/jdk/test/lib/net/SimpleSSLContext.java @@ -47,6 +47,7 @@ public SimpleSSLContext() throws IOException { this(() -> "TLS"); } + @SuppressWarnings("initialization") private SimpleSSLContext(Supplier protocols) throws IOException { String proto = protocols.get(); String paths = System.getProperty("test.src.path"); @@ -66,6 +67,7 @@ private SimpleSSLContext(Supplier protocols) throws IOException { /** * loads default keystore from given directory */ + @SuppressWarnings("initialization") public SimpleSSLContext(String dir) throws IOException { String file = dir + "/testkeys"; try (FileInputStream fis = new FileInputStream(file)) { diff --git a/test/lib/jdk/test/lib/os/linux/HugePageConfiguration.java b/test/lib/jdk/test/lib/os/linux/HugePageConfiguration.java index d95f20d27fd..575c2986381 100644 --- a/test/lib/jdk/test/lib/os/linux/HugePageConfiguration.java +++ b/test/lib/jdk/test/lib/os/linux/HugePageConfiguration.java @@ -121,6 +121,7 @@ public boolean supportsExplicitHugePages() { return _explicitDefaultHugePageSize > 0 && _explicitHugePageConfigurations.size() > 0; } + @SuppressWarnings("initialization") public HugePageConfiguration(Set explicitHugePageConfigurations, long explicitDefaultHugePageSize, long explicitAvailableHugePageNumber, THPMode _thpMode, long _thpPageSize, ShmemTHPMode _shmemThpMode) { this._explicitHugePageConfigurations = explicitHugePageConfigurations; this._explicitDefaultHugePageSize = explicitDefaultHugePageSize; diff --git a/test/lib/jdk/test/lib/process/OutputBuffer.java b/test/lib/jdk/test/lib/process/OutputBuffer.java index 0390535bf89..1286525feb5 100644 --- a/test/lib/jdk/test/lib/process/OutputBuffer.java +++ b/test/lib/jdk/test/lib/process/OutputBuffer.java @@ -140,7 +140,7 @@ private final void logProgress(String state) { + " for process " + p.pid()); System.out.flush(); } - + @SuppressWarnings("initialization") private LazyOutputBuffer(Process p, Charset cs) { this.p = p; logProgress("Gathering output"); diff --git a/test/lib/jdk/test/lib/security/SimpleOCSPServer.java b/test/lib/jdk/test/lib/security/SimpleOCSPServer.java index 4e25467ca80..bd755d0bd64 100644 --- a/test/lib/jdk/test/lib/security/SimpleOCSPServer.java +++ b/test/lib/jdk/test/lib/security/SimpleOCSPServer.java @@ -1009,6 +1009,7 @@ public class LocalOcspRequest { * @throws CertificateException if certificates are found in the * OCSP request and they do not parse correctly. */ + @SuppressWarnings("initialization") private LocalOcspRequest(byte[] requestBytes) throws IOException, CertificateException { Objects.requireNonNull(requestBytes, "Received null input"); @@ -1203,6 +1204,7 @@ public String toString() { * Inner class designed to handle the decoding/representation of * single requests within a {@code LocalOcspRequest} object. */ + @SuppressWarnings("initialization") public class LocalSingleRequest { private final CertId cid; private Map extensions = Collections.emptyMap(); @@ -1334,6 +1336,7 @@ public LocalOcspResponse(OCSPResponse.ResponseStatus respStat) * @throws GeneralSecurityException if errors occur while obtaining * the signature object or any algorithm identifier parameters. */ + @SuppressWarnings("initialization") public LocalOcspResponse(OCSPResponse.ResponseStatus respStat, Map itemMap, Map reqExtensions) diff --git a/test/lib/jdk/test/lib/security/timestamp/TsaServer.java b/test/lib/jdk/test/lib/security/timestamp/TsaServer.java index e103821c9b8..7de56d1cce2 100644 --- a/test/lib/jdk/test/lib/security/timestamp/TsaServer.java +++ b/test/lib/jdk/test/lib/security/timestamp/TsaServer.java @@ -50,6 +50,7 @@ public class TsaServer implements AutoCloseable { * @param handler a {@link TsaHandler} instance * @throws IOException the I/O exception */ + @SuppressWarnings("initialization") public TsaServer(int port, TsaHandler handler) throws IOException { server = HttpServer.create(new InetSocketAddress(port), 0); if (handler != null) { diff --git a/test/lib/jdk/test/whitebox/WhiteBox.java b/test/lib/jdk/test/whitebox/WhiteBox.java index ce8b61b6393..698711f00c7 100644 --- a/test/lib/jdk/test/whitebox/WhiteBox.java +++ b/test/lib/jdk/test/whitebox/WhiteBox.java @@ -151,6 +151,20 @@ public int encodeConstantPoolIndyIndex(int index) { return encodeConstantPoolIndyIndex0(index); } + private native Object[] getObjectsViaKlassOopMaps0(Object thing); + public Object[] getObjectsViaKlassOopMaps(Object thing) { + Objects.requireNonNull(thing); + return getObjectsViaKlassOopMaps0(thing); + } + + private native Object[] getObjectsViaOopIterator0(Object thing); + public Object[] getObjectsViaOopIterator(Object thing) { + Objects.requireNonNull(thing); + return getObjectsViaOopIterator0(thing); + } + + public native Object[] getObjectsViaFrameOopIterator(int depth); + private native int getFieldEntriesLength0(Class aClass); public int getFieldEntriesLength(Class aClass) { Objects.requireNonNull(aClass); @@ -723,10 +737,13 @@ public boolean concurrentGCRunTo(String breakpoint, boolean errorIfFail) { public native Long getSizeTVMFlag(String name); public native String getStringVMFlag(String name); public native Double getDoubleVMFlag(String name); - private final List> flagsGetters = Arrays.asList( - this::getBooleanVMFlag, this::getIntVMFlag, this::getUintVMFlag, - this::getIntxVMFlag, this::getUintxVMFlag, this::getUint64VMFlag, - this::getSizeTVMFlag, this::getStringVMFlag, this::getDoubleVMFlag); + private final List> flagsGetters; + { + flagsGetters = Arrays.asList( + this::getBooleanVMFlag, this::getIntVMFlag, this::getUintVMFlag, + this::getIntxVMFlag, this::getUintxVMFlag, this::getUint64VMFlag, + this::getSizeTVMFlag, this::getStringVMFlag, this::getDoubleVMFlag); + } public Object getVMFlag(String name) { return flagsGetters.stream() @@ -765,6 +782,7 @@ public int getCDSConstantForName(String name) throws Exception { public native Long getMethodUintxOption(Executable method, String name); public native Double getMethodDoubleOption(Executable method, String name); public native String getMethodStringOption(Executable method, String name); + @SuppressWarnings("initialization") private final List> methodOptionGetters = Arrays.asList(this::getMethodBooleanOption, this::getMethodIntxOption, this::getMethodUintxOption, this::getMethodDoubleOption, diff --git a/test/lib/org/openjdk/asmtools/JtregDriver.java b/test/lib/org/openjdk/asmtools/JtregDriver.java new file mode 100644 index 00000000000..877d0180027 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/JtregDriver.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools; + +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.nio.file.Path; +import java.nio.file.InvalidPathException; +import java.nio.file.Files; +import java.io.PrintWriter; +import java.io.IOException; +import org.openjdk.asmtools.jdis.uEscWriter; + +/** + * An entry point to AsmTools for use with the jtreg '@run driver' command. + * Unlike 'Main', never invokes 'System.exit' (which crashes jtreg). + * + * Also adjusts file paths: + * + * - For jasm and jcoder, source files are expected to appear in ${test.src}, + * and output is sent to ${test.classes} + * + * - For other tools, class files are expected to appear in ${test.classes}, + * and output is sent to the scratch working directory + * + * Example jtreg usage: + * + * @library /test/lib + * @build org.openjdk.asmtools.* org.openjdk.asmtools.jasm.* + * @run driver org.openjdk.asmtools.JtregDriver jasm -strict TestFile.jasm + */ +public class JtregDriver { + + public static void main(String... args) throws Throwable { + // run AltLoaderMain from a class loader that prefers the modified AsmTools classes + ClassLoader loader = JtregDriver.class.getClassLoader(); + Path file = Path.of(loader.getResource("org/openjdk/asmtools/JtregDriver.class").toURI()); + Path root = file.getParent().getParent().getParent().getParent(); + ClassLoader altLoader = new AsmToolsClassLoader(root); + Class altMain = altLoader.loadClass(AltLoaderMain.class.getName()); + try { + altMain.getMethod("main", String[].class).invoke(null, (Object) args); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } + + public static class AltLoaderMain { + + public static void main(String... args) throws IOException { + if (args.length == 0) { + throw new IllegalArgumentException("Missing asmtools command"); + } + String cmd = args[0]; + if (!cmd.equals("jasm") && !cmd.equals("jdis") && !cmd.equals("jcoder") + && !cmd.equals("jdec") && !cmd.equals("jcdec")) { + throw new IllegalArgumentException("Unrecognized asmtools command: " + cmd); + } + boolean isAssembler = cmd.equals("jasm") || cmd.equals("jcoder"); + String srcDir = System.getProperty("test.src", "."); + String clsDir = System.getProperty("test.classes", "."); + String fileDir = isAssembler ? srcDir : clsDir; + + ArrayList toolArgList = new ArrayList(); + + if (isAssembler) { + Path destPath = Path.of(clsDir); + if (!Files.exists(destPath)) { + // jtreg creates classes dir on demand, might not have happened yet + Files.createDirectories(destPath); + } + toolArgList.add("-d"); + toolArgList.add(clsDir); + } + + boolean isOptionArg = false; // marks an argument to a previous option + for (int i = 1; i < args.length; i++) { + String arg = args[i]; + if (isOptionArg) { + isOptionArg = false; // reset for next + } else { + if (arg.equals("-d")) { + isOptionArg = true; + } else if (!arg.startsWith("-") && !arg.startsWith("/")) { + // adjust filename + arg = Path.of(fileDir, arg).toString(); + } + } + toolArgList.add(arg); + } + + String[] toolArgs = toolArgList.toArray(new String[0]); + boolean success = switch (cmd) { + case "jasm" -> { + PrintWriter out = new PrintWriter(System.out); + yield new org.openjdk.asmtools.jasm.Main(out, "jasm").compile(toolArgs); + } + case "jdis" -> { + PrintWriter out = new PrintWriter(new uEscWriter(System.out)); + PrintWriter err = new PrintWriter(System.err); + yield new org.openjdk.asmtools.jdis.Main(out, err, "jdis").disasm(toolArgs); + } + case "jcoder" -> { + PrintWriter out = new PrintWriter(System.out); + yield new org.openjdk.asmtools.jcoder.Main(out, "jcoder").compile(toolArgs); + } + case "jdec" -> { + PrintWriter out = new PrintWriter(new uEscWriter(System.out)); + PrintWriter err = new PrintWriter(System.err); + yield new org.openjdk.asmtools.jdec.Main(out, err, "jdec").decode(toolArgs); + } + case "jcdec" -> { + PrintWriter out = new PrintWriter(new uEscWriter(System.out)); + yield new org.openjdk.asmtools.jcdec.Main(out, "jcdec").decode(toolArgs); + } + default -> throw new AssertionError(); + }; + if (!success) { + throw new RuntimeException("asmtools execution failed"); + } + } + + } + + /** + * Class loader for the AsmTools classes. Allows classes colocated with + * JtregDriver to have priority over versions of the classes loaded by + * jtreg (which includes its own copy of AsmTools). + */ + private static class AsmToolsClassLoader extends ClassLoader { + private final Path root; + private final String separator; + + public AsmToolsClassLoader(Path root) { + super(AsmToolsClassLoader.class.getClassLoader()); + this.root = root; + this.separator = root.getFileSystem().getSeparator(); + } + + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (name.startsWith("org.openjdk.asmtools.")) { + Class result = findClass(name); + if (resolve) resolveClass(result); + return result; + } else { + return super.loadClass(name, resolve); + } + } + + protected Class findClass(String name) throws ClassNotFoundException { + String filename = name.replace(".",separator) + ".class"; + try { + Path classFile = root.resolve(filename); + byte[] bytes = Files.readAllBytes(classFile); + return defineClass(name, bytes, 0, bytes.length); + } catch (InvalidPathException | IOException e) { + throw new ClassNotFoundException("can't read class " + filename, e); + } + } + + } + +} diff --git a/test/lib/org/openjdk/asmtools/Main.java b/test/lib/org/openjdk/asmtools/Main.java new file mode 100644 index 00000000000..844c0ee4fa6 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/Main.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2009, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools; + +import org.openjdk.asmtools.util.I18NResourceBundle; +import org.openjdk.asmtools.util.ProductInfo; + +/** + * Wrapper class that reads the first command line argument and invokes a corresponding + * tool. + */ +public class Main { + + public static final I18NResourceBundle i18n + = I18NResourceBundle.getBundleForClass(Main.class); + + /** + * Parses the first argument and deligates execution to an appropriate tool + * + * @param args - command line arguments + */ + public static void main(String args[]) { + if (args.length == 0) { + usage(i18n.getString("main.error.no_arguments"), 1); + } + String cmd = args[0]; + if (cmd.equals("-?") || cmd.equals("-h") || cmd.equals("-help")) { + usage(null, 0); + } else if (cmd.equals("-version")) { + printVersion(); + } else { + String[] newArgs = new String[args.length - 1]; + System.arraycopy(args, 1, newArgs, 0, args.length - 1); + if (cmd.equals("jasm")) { + jasm(newArgs); + } else if (cmd.equals("jdis")) { + jdis(newArgs); + } else if (cmd.equals("jcoder")) { + jcoder(newArgs); + } else if (cmd.equals("jdec")) { + jdec(newArgs); + } else if (cmd.equals("jcdec")) { + jcdec(newArgs); + } else { + usage(i18n.getString("main.error.unknown_tool", cmd), 1); + } + } + } + + /** + * Prints usage info and error message, afterwards invokes System.exit() + * + * @param msg - error message to print, or null if no errors occurred + * @param exitCode - exit code to be returned by System.exit() + */ + public static void usage(String msg, int exitCode) { + System.err.println(i18n.getString("main.usage", "asmtools.jar")); + if (msg != null) { + System.err.println(msg); + } + System.exit(exitCode); + } + + /** + * Prints the tools version + */ + public static void printVersion() { + System.out.println(ProductInfo.FULL_VERSION); + } + + /** + * Invokes jasm main class with passed arguments + */ + public static void jasm(String[] args) { + org.openjdk.asmtools.jasm.Main.main(args); + } + + /** + * Invokes jcdec main class with passed arguments + */ + public static void jcdec(String[] args) { + org.openjdk.asmtools.jcdec.Main.main(args); + } + + /** + * Invokes jcoder main class with passed arguments + */ + public static void jcoder(String[] args) { + org.openjdk.asmtools.jcoder.Main.main(args); + } + + /** + * Invokes jdec main class with passed arguments + */ + public static void jdec(String[] args) { + org.openjdk.asmtools.jdec.Main.main(args); + } + + /** + * Invokes jdis main class with passed arguments + */ + public static void jdis(String[] args) { + org.openjdk.asmtools.jdis.Main.main(args); + } +} diff --git a/test/lib/org/openjdk/asmtools/asmutils/HexUtils.java b/test/lib/org/openjdk/asmtools/asmutils/HexUtils.java new file mode 100644 index 00000000000..0d20b4ab72c --- /dev/null +++ b/test/lib/org/openjdk/asmtools/asmutils/HexUtils.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.asmutils; + +/** + * + */ +public class HexUtils { + /*======================================================== Hex */ + + private static final String hexString = "0123456789ABCDEF"; + private static final char hexTable[] = hexString.toCharArray(); + + public static String toHex(long val, int width) { + StringBuffer sb = new StringBuffer(); + for (int i = width - 1; i >= 0; i--) { + sb.append(hexTable[((int) (val >> (4 * i))) & 0xF]); + } + String s = sb.toString(); + return "0x" + (s.isEmpty() ? "0" : s); + } + + public static String toHex(long val) { + int width; + for (width = 16; width > 0; width--) { + if ((val >> (width - 1) * 4) != 0) { + break; + } + } + return toHex(val, width); + } + + public static String toHex(int val) { + int width; + for (width = 8; width > 0; width--) { + if ((val >> (width - 1) * 4) != 0) { + break; + } + } + return toHex(val, width); + } + +} diff --git a/test/lib/org/openjdk/asmtools/asmutils/StringUtils.java b/test/lib/org/openjdk/asmtools/asmutils/StringUtils.java new file mode 100644 index 00000000000..598511a97b5 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/asmutils/StringUtils.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.asmutils; + +/** + * Utility class to share common tools/methods. + */ +public class StringUtils { + /** + * Converts CONSTANT_Utf8_info string to a printable string for jdis/jdes. + * @param utf8 UTF8 string taken from within ConstantPool of a class file + * @return output string for jcod/jasm + */ + public static String Utf8ToString(String utf8) { + StringBuilder sb = new StringBuilder("\""); + for (int k = 0; k < utf8.length(); k++) { + char c = utf8.charAt(k); + switch (c) { + case '\t': + sb.append('\\').append('t'); + break; + case '\n': + sb.append('\\').append('n'); + break; + case '\r': + sb.append('\\').append('r'); + break; + case '\b': + sb.append('\\').append('b'); + break; + case '\f': + sb.append('\\').append('f'); + break; + case '\"': + sb.append('\\').append('\"'); + break; + case '\'': + sb.append('\\').append('\''); + break; + case '\\': + sb.append('\\').append('\\'); + break; + default: + sb.append(c); + } + } + return sb.append('\"').toString(); + } +} diff --git a/test/lib/org/openjdk/asmtools/common/Module.java b/test/lib/org/openjdk/asmtools/common/Module.java new file mode 100644 index 00000000000..a1294752c47 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/common/Module.java @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.common; + +import org.openjdk.asmtools.jdis.Indenter; + +import java.util.*; +import java.util.stream.Collectors; + +import static java.lang.String.format; + +/** + * Internal presentation of a module + */ +public final class Module extends Indenter { + + //* A module name and module_flags + public final Header header; + //* A service dependence's of this module + public final Set uses; + //* Modules on which the current module has a dependence. + public final Set requires; + //* A module exports, may be qualified or unqualified. + public final Map> exports; + //* Packages, to be opened by the current module + public final Map> opens; + //* A service that a module provides one or more implementations of. + public final Map> provides; + + private Module(Builder builder) { + this.header = builder.header; + this.requires = Collections.unmodifiableSet(builder.requires); + this.exports = Collections.unmodifiableMap(builder.exports); + this.opens = Collections.unmodifiableMap(builder.opens); + this.uses = Collections.unmodifiableSet(builder.uses); + this.provides = Collections.unmodifiableMap(builder.provides); + } + + public String getModuleFlags () { + return Modifier.getModuleModifiers(header.getFlags()); + } + public String getModuleName () { return header.getModuleName(); } + public String getModuleVersion() { return header.getModuleVersion(); }; + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + int l = 0; + requires.stream() + .sorted() + .forEach(d -> sb.append(getIndentString()).append(format("requires %s;%s%n", + d.toString(), + d.getModuleVersion() == null ? "" : " // @" + d.getModuleVersion()))); + // + l = newLine(sb,l); + exports.entrySet().stream() + .filter(e -> e.getValue().isEmpty()) + .sorted(Map.Entry.comparingByKey()) + .map(e -> format("%sexports %s;%n", getIndentString(), e.getKey().toString())) + .forEach(sb::append); + exports.entrySet().stream() + .filter(e -> !e.getValue().isEmpty()) + .sorted(Map.Entry.comparingByKey()) + .map(e -> format("%sexports %s to%n%s;%n", getIndentString(), e.getKey().toString(), + e.getValue().stream().sorted() + .map(mn -> format("%s %s", getIndentString(), mn)) + .collect(Collectors.joining(",\n")))) + .forEach(sb::append); + // + l = newLine(sb,l); + opens.entrySet().stream() + .filter(e -> e.getValue().isEmpty()) + .sorted(Map.Entry.comparingByKey()) + .map(e -> format("%sopens %s;%n", getIndentString(), e.getKey().toString())) + .forEach(sb::append); + opens.entrySet().stream() + .filter(e -> !e.getValue().isEmpty()) + .sorted(Map.Entry.comparingByKey()) + .map(e -> format("%sopens %s to%n%s;%n", getIndentString(), e.getKey().toString(), + e.getValue().stream().sorted() + .map(mn -> format("%s %s", getIndentString(), mn)) + .collect(Collectors.joining(",\n")))) + .forEach(sb::append); + // + l = newLine(sb,l); + uses.stream().sorted() + .map(s -> format("%suses %s;%n", getIndentString(), s)) + .forEach(sb::append); + // + l = newLine(sb,l); + provides.entrySet().stream() + .filter(e -> !e.getValue().isEmpty()) + .sorted(Map.Entry.comparingByKey()) + .map(e -> format("%sprovides %s with%n%s;%n", getIndentString(), e.getKey().toString(), + e.getValue().stream().sorted() + .map(mn -> format("%s %s", getIndentString(), mn)) + .collect(Collectors.joining(",\n")))) + .forEach(sb::append); + // + if( Character.isWhitespace(sb.charAt(sb.length()-1)) ) + sb.deleteCharAt(sb.length()-1); + return sb.toString(); + } + + private int newLine(StringBuilder sb, int length) { + if(sb.length() > length) { + sb.append("\n"); + return sb.length() + 1; + } + return length; + } + + /** + * Modules flags + */ + public enum Modifier { + ACC_NONE(0x0000, "", ""), + ACC_OPEN(0x0020, "open", "ACC_OPEN"), + ACC_TRANSITIVE(0x0020, "transitive", "ACC_TRANSITIVE"), + ACC_STATIC_PHASE(0x0040, "static", "ACC_STATIC_PHASE"), + ACC_SYNTHETIC(0x1000, "", "ACC_SYNTHETIC"), + ACC_MANDATED(0x8000, "", "ACC_MANDATED"); + private final int value; + private final String keyword; + private final String flag; + Modifier(int value, String keyword, String flagName) { + this.value = value; + this.keyword = keyword; + this.flag = flagName; + } + + public int asInt() { return value; } + + public static String getModuleModifiers(int flag) { + return asString(flag, false, ACC_TRANSITIVE); + } + + public static String getModuleFlags(int flag) { + return asString(flag, true, ACC_TRANSITIVE); + } + + public static String getStatementModifiers(int flag) { + return asString(flag, false, ACC_OPEN); + } + + public static String getStatementFlags(int flag) { + return asString(flag, true, ACC_OPEN); + } + + private static String asString(int value, boolean flagFormat, Modifier skipped ) { + String buf = ""; + for(Module.Modifier m : values()) { + if( m != skipped && (value & m.value) != 0) { + buf += ((flagFormat) ? m.flag : m.keyword) + " "; + value ^= m.value; + } + } + if( flagFormat && value != 0 ) + buf += String.format("0x%04X ", value); + return buf; + } + } + + // A module header consists of a module name and module flags + public final static class Header extends VersionedFlaggedTargetType{ + Header(String typeName, int flag) { this(typeName, flag, null); } + Header(String typeName, int flag, String moduleVersion) { super(typeName, flag, moduleVersion); } + public String getModuleName() { return getTypeName(); } + public int getModuleFlags() { return getFlags(); } + public String getModuleVersion() { return getVersion(); } + } + + //* A module on which the current module has a dependence. + public final static class Dependence extends VersionedFlaggedTargetType { + public Dependence(String moduleName, int flag) {this(moduleName, flag, null);} + public Dependence(String moduleName, int flag, String moduleVersion) {super(moduleName, flag, moduleVersion);} + public Dependence(String moduleName, boolean transitive, boolean staticPhase) { this(moduleName,transitive,staticPhase,null);} + public Dependence(String moduleName, boolean transitive, boolean staticPhase, String moduleVersion) { + this(moduleName, + (transitive ? Modifier.ACC_TRANSITIVE.value : Modifier.ACC_NONE.value) | + (staticPhase ? Modifier.ACC_STATIC_PHASE.value : Modifier.ACC_NONE.value), moduleVersion); + } + public String getModuleVersion() { return getVersion(); } + } + + public final static class Uses extends TargetType { + public Uses(String typeName) { super(typeName); } + } + + //* A provided type of the current module. + public final static class Provided extends TargetType { + public Provided(String typeName) { super(typeName); } + } + + //* An opened package of the current module. + public final static class Opened extends FlaggedTargetType { + public Opened(String typeName) { + super(typeName, 0); + } + public Opened(String typeName, int opensFlags) { + super(typeName, opensFlags); + } + } + + //* An exported package of the current module. + public final static class Exported extends FlaggedTargetType { + public Exported(String typeName) { + super(typeName, 0); + } + + public Exported(String typeName, int exportsFlags) { + super(typeName, exportsFlags); + } + } + + public static class VersionedFlaggedTargetType extends FlaggedTargetType { + private String version; + + VersionedFlaggedTargetType(String typeName, int flag) { + this(typeName,flag, null); + } + + VersionedFlaggedTargetType(String typeName, int flag, String version) { + super(typeName, flag); + this.version = version != null && !version.isEmpty() ? version : null; + } + public String getVersion() { return version; } + + @Override + public int hashCode() { + int code = version == null ? 0 : version.hashCode(); + return code + super.hashCode(); + } + } + + public static class FlaggedTargetType extends TargetType { + private int flag; + + FlaggedTargetType(String typeName, int flag) { + super(typeName); + this.flag = flag; + } + + public boolean isFlagged() { + return true; + } + + public int getFlags() { + return flag; + } + + public void setFlag(int value) { flag = value; } + + @Override + public int hashCode() { + return super.hashCode() + flag; + } + + @Override + public boolean equals(Object o) { + return super.equals(o) && ((FlaggedTargetType) o).flag == this.flag; + } + + @Override + public String toString() { + return Modifier.getStatementModifiers(this.flag)+ super.toString(); + } + } + + public static class TargetType implements Comparable { + private String typeName; + + TargetType(String typeName) { this.typeName = typeName; } + + public String getTypeName() { + return typeName; + } + + public void setTypeName(String value) { typeName = value; } + + public boolean isFlagged() { + return false; + } + + @Override + public int hashCode() { return typeName.hashCode() * 11; } + + @Override + public boolean equals(Object o) { + if (o instanceof TargetType) { + TargetType t = (TargetType) o; + return this.typeName.equals(t.getTypeName()); + } + return false; + } + + @Override + public int compareTo(TargetType t) { + return this.typeName.compareTo(t.getTypeName()); + } + + @Override + public String toString() { + return typeName; + } + } + + /** + * The module builder. + */ + public static final class Builder { + final Header header; + final Set requires = new HashSet<>(); + final Map> exports = new HashMap<>(); + final Map> opens = new HashMap<>(); + final Set uses = new HashSet<>(); + final Map> provides = new HashMap<>(); + + + public Builder() { + this("", Modifier.ACC_NONE.asInt(), null); + } + + public Builder(String moduleName, int moduleFlags, String moduleVersion) { + header = new Header( moduleName,moduleFlags, moduleVersion); + } + + public Builder setModuleFlags(int moduleFlags) { + header.setFlag(header.getFlags() | moduleFlags); + return this; + } + + public Builder setModuleFlags(Modifier... moduleFlags) { + for (Modifier m : moduleFlags) + setModuleFlags(m.value); + return this; + } + + public Builder setModuleName(String value) { + header.setTypeName(value); + return this; + } + + public Builder require(String d, boolean transitive, boolean staticPhase, String version) { + requires.add(new Dependence(d, transitive, staticPhase, version)); + return this; + } + + public Builder require(String d, int requiresFlag, String version) { + requires.add(new Dependence(d, requiresFlag, version)); + return this; + } + + public Builder require(String d, int requiresFlag) { + requires.add(new Dependence(d, requiresFlag, null)); + return this; + } + + public Builder opens(Opened p, Set ms) { + return add(opens, p, ms); + } + + public Builder opens(String packageName, int exportFlags, Set ms) { + return add(opens, new Opened(packageName, exportFlags), ms); + } + + public Builder opens(String packageName, int exportFlags) { + return add(opens, new Opened(packageName, exportFlags), new HashSet<>()); + } + + + public Builder exports(Exported p, Set ms) { + return add(exports, p, ms); + } + + public Builder exports(String packageName, int exportFlags, Set ms) { + return add(exports, new Exported(packageName, exportFlags), ms); + } + + public Builder exports(String packageName, int exportFlags) { + return add(exports, new Exported(packageName, exportFlags), new HashSet<>()); + } + + public Builder uses(String serviceName) { + uses.add(new Uses(serviceName)); + return this; + } + + + public Builder uses(Set serviceNames) { + uses.addAll(serviceNames.stream().map(Uses::new).collect(Collectors.toList())); + return this; + } + + public Builder provides(Provided t, Set implementations) { + return add(provides, t, implementations); + } + + public Builder provides(String serviceName, Set implementations) { + return add(provides, new Provided(serviceName), implementations); + } + + + /** + * @return The new module + */ + public Module build() { + return new Module(this); + } + + private Builder add( Map> collection, T source, Set target) { + Objects.requireNonNull(source); + Objects.requireNonNull(target); + if (!collection.containsKey(source)) + collection.put(source, new HashSet<>()); + collection.get(source).addAll(target); + return this; + } + } +} diff --git a/test/lib/org/openjdk/asmtools/common/Tool.java b/test/lib/org/openjdk/asmtools/common/Tool.java new file mode 100644 index 00000000000..9ce9606ecfb --- /dev/null +++ b/test/lib/org/openjdk/asmtools/common/Tool.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.common; + + +import java.io.DataInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; + +public abstract class Tool { + + // Name of the program. + protected final String programName; + // Errors counter. + protected int nerrors = 0; + // The stream where error message are printed. + protected PrintWriter err; + + // Output stream + protected PrintWriter out; + + // A consumer to print a error message if the tool can't read a file + protected Consumer printCannotReadMsg; + + // A supplier to get a status of a debug flag + protected BooleanSupplier DebugFlag = () -> false; + + public Tool(PrintWriter out, String programName) { + this(out, out, programName); + } + + public Tool(PrintWriter out, PrintWriter err, String programName) { + this.out = out; + this.err = err; + this.programName = programName; + } + + + public String getError(String msg) { + return programName + ": " + msg; + } + + /** + * Top level error message + */ + public void error(String msg) { + err.println(getError(msg)); + err.flush(); + } + + /** + * Top level print message + */ + public void println(String msg) { + out.println(msg); + out.flush(); + } + + public void println() { + println(""); + } + + public void print(String msg) { + out.print(getError(msg)); + out.flush(); + } + + /** + * @param fname file name + * @return DataInputStream or null if the method can't read a file + */ + public DataInputStream getDataInputStream(String fname) { + try { + return new DataInputStream(new FileInputStream(fname)); + } catch (IOException ex) { + if (fname.matches("^[A-Za-z]+:.*")) { + try { + final URI uri = new URI(fname); + final URL url = uri.toURL(); + final URLConnection conn = url.openConnection(); + conn.setUseCaches(false); + return new DataInputStream(conn.getInputStream()); + } catch (URISyntaxException | IOException e) { + if (DebugFlag.getAsBoolean()) + e.printStackTrace(); + } + } + if (printCannotReadMsg != null) + printCannotReadMsg.accept(fname); + } + return null; + } + + /** + * Usage + */ + protected abstract void usage(); +} diff --git a/test/lib/org/openjdk/asmtools/i18n.properties b/test/lib/org/openjdk/asmtools/i18n.properties new file mode 100644 index 00000000000..72c5d5aab41 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/i18n.properties @@ -0,0 +1,33 @@ +# Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. +# 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. + +main.usage=\ +Usage: \n\ +to run an assembly tool: \n\ +\ $ java -jar {0} toolName [args...] \n\ +\ where toolName one of: jasm, jdis, jcoder, jdec, jcdec \n\ +to get the version: \n\ +\ $ java -jar {0} -version \n\ +to get this message \n\ +\ $ java -jar {0} -?|-h|-help\n + +main.error.no_arguments=No arguments provided! See options above. +main.error.unknown_tool=Tool name ''{0}'' unrecognized. See usage above for possible tool choices. \ No newline at end of file diff --git a/test/lib/org/openjdk/asmtools/jasm/AnnotationData.java b/test/lib/org/openjdk/asmtools/jasm/AnnotationData.java new file mode 100644 index 00000000000..60072a8a05c --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/AnnotationData.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.io.IOException; +import java.util.ArrayList; + +/** + * JVMS 4.7.16. + * + * annotation { + * u2 type_index; + * u2 num_element_value_pairs; + * { u2 element_name_index; + * element_value value; + * } element_value_pairs[num_element_value_pairs]; + * } + */ +class AnnotationData implements Data { + + boolean invisible; + Argument typeCPX; + ArrayList elemValuePairs; + int annotationLength = 0; + + /** + * AnnotationElemValue + * + * Used to store Annotation Data + */ + static public class ElemValuePair implements Data { + + ConstantPool.ConstCell name; + Data value; + + public ElemValuePair(ConstantPool.ConstCell name, Data value) { + this.name = name; + this.value = value; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + name.write(out); + value.write(out); + } + + @Override + public int getLength() { + return 2 + value.getLength(); + } + } + + public AnnotationData(Argument typeCPX, boolean invisible) { + this.typeCPX = typeCPX; + this.elemValuePairs = new ArrayList<>(); + this.invisible = invisible; + } + + public void add(ElemValuePair elemValuePair) { + elemValuePairs.add(elemValuePair); + annotationLength += elemValuePair.getLength(); + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeShort(typeCPX.arg); + out.writeShort(elemValuePairs.size()); + + for (Data pair : elemValuePairs) { + pair.write(out); + } + } + + @Override + public int getLength() { + return 4 + annotationLength; + } +} diff --git a/test/lib/org/openjdk/asmtools/jasm/Argument.java b/test/lib/org/openjdk/asmtools/jasm/Argument.java new file mode 100644 index 00000000000..316c4012bc6 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/Argument.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +/** + * + */ +class Argument { + + static final int NotSet = -1; + int arg; + + Argument() { + arg = NotSet; + } + + Argument(int arg) { + this.arg = arg; + } + + public int hashCode() { + return arg; + } + + /** + * Compares this object to the specified object. + * + * @param obj the object to compare with + * @return true if the objects are the same; false otherwise. + */ + public boolean equals(Object obj) { + throw new Parser.CompilerError("ConstCell.equals"); + } + + boolean isSet() { + return arg != NotSet; + } +} diff --git a/test/lib/org/openjdk/asmtools/jasm/AttrData.java b/test/lib/org/openjdk/asmtools/jasm/AttrData.java new file mode 100644 index 00000000000..2b3c896b3d9 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/AttrData.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.io.IOException; + +/** + * AttrData + * + * AttrData is the base class for many attributes (or parts of attributes), and it is + * instantiated directly for simple attributes (like Synthetic or Deprecated). + */ +class AttrData implements Data { + + private final ClassData clsData; + private final Argument attrNameCPX; + + AttrData(ClassData cdata, String name) { + clsData = cdata; + attrNameCPX = cdata.pool.FindCellAsciz(name); + } + + protected ClassData getClassData() { + return clsData; + } + + // full length of the attribute + // declared in Data + public int getLength() { + return 6 + attrLength(); + } + + // subclasses must redefine this + public int attrLength() { + return 0; + } + + public void write(CheckedDataOutputStream out) throws IOException { + out.writeShort(attrNameCPX.arg); + out.writeInt(attrLength()); // attr len + } +} // end class AttrData + diff --git a/test/lib/org/openjdk/asmtools/jasm/BootstrapMethodData.java b/test/lib/org/openjdk/asmtools/jasm/BootstrapMethodData.java new file mode 100644 index 00000000000..31bdfe47ae8 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/BootstrapMethodData.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.io.IOException; +import java.util.ArrayList; + +/** + * + */ +class BootstrapMethodData extends Argument implements Data { + + ConstantPool.ConstCell bootstrapMethodHandle; + ArrayList arguments; + public int placeholder_index = -1; + + public BootstrapMethodData(ConstantPool.ConstCell bsmHandle, ArrayList arguments) { + super(); + this.bootstrapMethodHandle = bsmHandle; + this.arguments = arguments; + } + + public BootstrapMethodData(int placeholder) { + super(); + this.bootstrapMethodHandle = null; + this.arguments = null; + this.placeholder_index = placeholder; + } + + public int getLength() { + return 4 + arguments.size() * 2; + } + + public boolean isPlaceholder() { + return placeholder_index > -1; + } + + public void write(CheckedDataOutputStream out) throws IOException { + out.writeShort(bootstrapMethodHandle.arg); + out.writeShort(arguments.size()); + + for (ConstantPool.ConstCell argument : arguments) { + out.writeShort(argument.arg); + } + } +} diff --git a/test/lib/org/openjdk/asmtools/jasm/CFVersion.java b/test/lib/org/openjdk/asmtools/jasm/CFVersion.java new file mode 100644 index 00000000000..7d1944eb80a --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/CFVersion.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +/* + * Class File Version + */ +public class CFVersion implements Cloneable{ + /** + * Default versions of class file + */ + public static final short DEFAULT_MAJOR_VERSION = 45; + public static final short DEFAULT_MINOR_VERSION = 3; + public static final short DEFAULT_MODULE_MAJOR_VERSION = 53; + public static final short DEFAULT_MODULE_MINOR_VERSION = 0; + public static final short UNDEFINED_VERSION = -1; + + private short major_version; + private short minor_version; + private boolean frozen; + private boolean isSet; + + public CFVersion() { + frozen = false; + isSet = false; + major_version = UNDEFINED_VERSION; + minor_version = UNDEFINED_VERSION; + } + + public CFVersion(boolean frozenCFV, short major_version, short minor_version) { + isSet = true; + frozen = frozenCFV; + this.major_version = major_version; + this.minor_version = minor_version; + } + + public void setMajorVersion(short major_version) { + if ( !frozen ) { + isSet = true; + this.major_version = major_version; + } + } + + public void setMinorVersion(short minor_version) { + if (!frozen) { + isSet = true; + this.minor_version = minor_version; + } + } + + public String asString() { + return (isSet) ? this.major_version + ":" +this.minor_version : "(undef):(undef)"; + } + + public void initModuleDefaults() { + if( ! isSet) { + major_version = DEFAULT_MODULE_MAJOR_VERSION; + minor_version = DEFAULT_MODULE_MINOR_VERSION; + } + } + + public void initClassDefaults() { + if( !isSet ) { + major_version = DEFAULT_MAJOR_VERSION; + minor_version = DEFAULT_MINOR_VERSION; + } + } + + public short minor_version() { + return this.minor_version; + } + + public short major_version() { + return this.major_version; + } + + public CFVersion clone() { + try { + return (CFVersion)super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/test/lib/org/openjdk/asmtools/jasm/CPXAttr.java b/test/lib/org/openjdk/asmtools/jasm/CPXAttr.java new file mode 100644 index 00000000000..57aede1d576 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/CPXAttr.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.io.IOException; + +/** + * Constant Pool Index Attribute + */ +class CPXAttr extends AttrData { + + Argument cell; + + public CPXAttr(ClassData cls, String attrName, Argument cell) { + super(cls, attrName); + this.cell = cell; + } + + public int attrLength() { + return 2; + } + + public void write(CheckedDataOutputStream out) throws IOException { + super.write(out); // attr name, attr len + out.writeShort(cell.arg); + } +} // end class CPXAttr + diff --git a/test/lib/org/openjdk/asmtools/jasm/CheckedDataOutputStream.java b/test/lib/org/openjdk/asmtools/jasm/CheckedDataOutputStream.java new file mode 100644 index 00000000000..abb0dfd6ce0 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/CheckedDataOutputStream.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. + * 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. + */ + +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.openjdk.asmtools.jasm; + +import java.io.DataOutput; +import java.io.IOException; + +/** + * + */ +public interface CheckedDataOutputStream { + + public void write(int b) throws IOException; + + public void write(byte b[], int off, int len) throws IOException; + + public void writeBoolean(boolean v) throws IOException; + + public void writeByte(int v) throws IOException; + + public void writeShort(int v) throws IOException; + + public void writeChar(int v) throws IOException; + + public void writeInt(int v) throws IOException; + + public void writeLong(long v) throws IOException; + + public void writeFloat(float v) throws IOException; + + public void writeDouble(double v) throws IOException; + + public void writeBytes(String s) throws IOException; + + public void writeChars(String s) throws IOException; + + public void writeUTF(String s) throws IOException; + +} diff --git a/test/lib/org/openjdk/asmtools/jasm/ClassArrayAttr.java b/test/lib/org/openjdk/asmtools/jasm/ClassArrayAttr.java new file mode 100644 index 00000000000..6bd0d799eb6 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/ClassArrayAttr.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.io.IOException; +import java.util.List; + +/** + * Base class of the "classes[]" data of attributes + *

    + * JEP 181 (Nest-based Access Control): class file 55.0 + * NestMembers_attribute { + * u2 attribute_name_index; + * u4 attribute_length; + * u2 number_of_classes; + * u2 classes[number_of_classes]; + * } + *

    + * JEP 360 (Sealed types): class file 59.65535 + * PermittedSubclasses_attribute { + * u2 attribute_name_index; + * u4 attribute_length; + * u2 number_of_classes; + * u2 classes[number_of_classes]; + * } + */ +public class ClassArrayAttr extends AttrData { + + List classes; + + public ClassArrayAttr(String attributeName, ClassData cdata, List classes) { + super(cdata, attributeName); + this.classes = classes; + } + + @Override + public int attrLength() { + return 2 + classes.size() * 2; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + super.write(out); + out.writeShort(classes.size()); + for (ConstantPool.ConstCell c : classes) { + out.writeShort(c.arg); + } + } +} diff --git a/test/lib/org/openjdk/asmtools/jasm/ClassData.java b/test/lib/org/openjdk/asmtools/jasm/ClassData.java new file mode 100644 index 00000000000..ac25b0bf2fc --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/ClassData.java @@ -0,0 +1,650 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; + +import static org.openjdk.asmtools.jasm.Tables.*; + +/** + * ClassData + * + * This is the main data structure for representing parsed class data. This structure + * renders directly to a class file. + * + */ +class ClassData extends MemberData { + + /* ClassData Fields */ + CFVersion cfv; + ConstantPool.ConstCell me, father; + String myClassName; + AttrData sourceFileNameAttr; + ArrayList interfaces; + ArrayList fields = new ArrayList<>(); + ArrayList methods = new ArrayList<>(); + DataVectorAttr innerClasses = null; + DataVectorAttr bootstrapMethodsAttr = null; + + // JEP 181 - NestHost, NestMembers attributes since class version 55.0 + CPXAttr nestHostAttr; + NestMembersAttr nestMembersAttr; + + // JEP 359 - Record attribute since class file 58.65535 + private RecordData recordData; + + // JEP 360 - PermittedSubclasses attribute since class file 59.65535 + private PermittedSubclassesAttr permittedSubclassesAttr; + + private PreloadAttr preloadAttr; + + ModuleAttr moduleAttribute = null; + Environment env; + protected ConstantPool pool; + + private static final String DEFAULT_EXTENSION = ".class"; + String fileExtension = DEFAULT_EXTENSION; + public CDOutputStream cdos; + + /** + * Initializes the ClassData. + * + * @param me The constant pool reference to this class + * @param father The constant pool reference to the super class + * @param interfaces A list of interfaces that this class implements + */ + public final void init(int access, ConstantPool.ConstCell me, ConstantPool.ConstCell father, ArrayList interfaces) { + this.access = access; + + // normalize the modifiers to access flags + if (Modifiers.hasPseudoMod(access)) { + createPseudoMod(); + } + + this.me = me; + if (father == null) { + father = pool.FindCellClassByName("java/lang/Object"); + } + this.father = father; + this.interfaces = interfaces; + // Set default class file version if it is not set. + cfv.initClassDefaults(); + } + + public final void initAsModule() { + this.access = RuntimeConstants.ACC_MODULE; + // this_class" module-info + this.me = pool.FindCellClassByName("module-info"); + // super_class: zero + this.father = new ConstantPool.ConstCell(0); + cfv.initModuleDefaults(); + } + + /** + * canonical default constructor + * + * @param env The error reporting environment. + * @param cfv The class file version that this class file supports. + */ + public ClassData(Environment env, CFVersion cfv) { + super(null, 0); // for a class, these get inited in the super - later. + cls = this; + + this.env = env; + this.cfv = cfv; + + pool = new ConstantPool(env); + cdos = new CDOutputStream(); + } + + /** + * Predicate that describes if this class has an access flag indicating that it is an + * interface. + * + * @return True if the classes access flag indicates it is an interface. + */ + public final boolean isInterface() { + return Modifiers.isInterface(access); + } + + /* + * After a constant pool has been explicitly declared, + * this method links the Constant_InvokeDynamic Constant_Dynamic + * constants with any bootstrap methods that they index in the + * Bootstrap Methods Attribute + */ + protected void relinkBootstrapMethods() { + if (bootstrapMethodsAttr == null) { + return; + } + + env.traceln("relinkBootstrapMethods"); + + for (ConstantPool.ConstCell cell : pool) { + ConstantPool.ConstValue ref = null; + if (cell != null) { + ref = cell.ref; + } + if (ref != null + && (ref.tag == ConstType.CONSTANT_INVOKEDYNAMIC || ref.tag == ConstType.CONSTANT_DYNAMIC)) { + // Find only the Constant + ConstantPool.ConstValue_IndyOrCondyPair refval = (ConstantPool.ConstValue_IndyOrCondyPair) ref; + BootstrapMethodData bsmdata = refval.bsmData; + // only care about BSM Data that were placeholders + if (bsmdata != null && bsmdata.isPlaceholder()) { + // find the real BSM Data at the index + int bsmindex = bsmdata.placeholder_index; + if (bsmindex < 0 || bsmindex > bootstrapMethodsAttr.size()) { + // bad BSM index -- + // give a warning, but place the index in the arg anyway + env.traceln("Warning: (ClassData.relinkBootstrapMethods()): Bad bootstrapMethods index: " + bsmindex); + // env.error("const.bsmindex", bsmindex); + bsmdata.arg = bsmindex; + } else { + + BootstrapMethodData realbsmdata = bootstrapMethodsAttr.get(bsmindex); + // make the IndyPairs BSM Data point to the one from the attribute + refval.bsmData = realbsmdata; + } + } + } + } + } + + protected void numberBootstrapMethods() { + env.traceln("Numbering Bootstrap Methods"); + if (bootstrapMethodsAttr == null) { + return; + } + + int index = 0; + for (BootstrapMethodData data : bootstrapMethodsAttr) { + data.arg = index++; + } + } + + // API + // Record + public RecordData setRecord(int where) { + if( recordAttributeExists() ) { + env.error(where, "warn.record.repeated"); + } + this.recordData = new RecordData(cls); + return this.recordData; + } + + /** + * Rejects a record: removes the record attribute if there are no components + */ + public void rejectRecord() { + this.recordData = null; + } + + // Field + public ConstantPool.ConstValue_Pair mkNape(ConstantPool.ConstCell name, ConstantPool.ConstCell sig) { + return new ConstantPool.ConstValue_Pair(ConstType.CONSTANT_NAMEANDTYPE, name, sig); + } + + public ConstantPool.ConstValue_Pair mkNape(String name, String sig) { + return mkNape(pool.FindCellAsciz(name), pool.FindCellAsciz(sig)); + } + + public FieldData addFieldIfAbsent(int access, ConstantPool.ConstCell name, ConstantPool.ConstCell sig) { + ConstantPool.ConstValue_Pair nape = mkNape(name, sig); + env.traceln(" [ClassData.addFieldIfAbsent]: #" + nape.left.arg + ":#" + nape.right.arg); + FieldData fd = getField(nape); + if( fd == null ) { + env.traceln(" [ClassData.addFieldIfAbsent]: new field."); + fd = addField(access,nape); + } + return fd; + } + + private FieldData getField(ConstantPool.ConstValue_Pair nape) { + for (FieldData fd : fields) { + if( fd.getNameDesc().equals(nape) ) { + return fd; + } + } + return null; + } + + public FieldData addField(int access, ConstantPool.ConstValue_Pair nape) { + env.traceln(" [ClassData.addField]: #" + nape.left.arg + ":#" + nape.right.arg); + FieldData res = new FieldData(this, access, nape); + fields.add(res); + return res; + } + + public FieldData addField(int access, ConstantPool.ConstCell name, ConstantPool.ConstCell sig) { + return addField(access, mkNape(name, sig)); + } + + public FieldData addField(int access, String name, String type) { + return addField(access, pool.FindCellAsciz(name), pool.FindCellAsciz(type)); + } + + public ConstantPool.ConstCell LocalFieldRef(FieldData field) { + return pool.FindCell(ConstType.CONSTANT_FIELD, me, pool.FindCell(field.getNameDesc())); + } + + public ConstantPool.ConstCell LocalFieldRef(ConstantPool.ConstValue nape) { + return pool.FindCell(ConstType.CONSTANT_FIELD, me, pool.FindCell(nape)); + } + + public ConstantPool.ConstCell LocalFieldRef(ConstantPool.ConstCell name, ConstantPool.ConstCell sig) { + return LocalFieldRef(mkNape(name, sig)); + } + + public ConstantPool.ConstCell LocalFieldRef(String name, String sig) { + return LocalFieldRef(pool.FindCellAsciz(name), pool.FindCellAsciz(sig)); + } + + MethodData curMethod; + + public MethodData StartMethod(int access, ConstantPool.ConstCell name, ConstantPool.ConstCell sig, ArrayList exc_table) { + EndMethod(); + env.traceln(" [ClassData.StartMethod]: #" + name.arg + ":#" + sig.arg); + curMethod = new MethodData(this, access, name, sig, exc_table); + methods.add(curMethod); + return curMethod; + } + + public void EndMethod() { + curMethod = null; + } + + public ConstantPool.ConstCell LocalMethodRef(ConstantPool.ConstValue nape) { + return pool.FindCell(ConstType.CONSTANT_METHOD, me, pool.FindCell(nape)); + } + + public ConstantPool.ConstCell LocalMethodRef(ConstantPool.ConstCell name, ConstantPool.ConstCell sig) { + return LocalMethodRef(mkNape(name, sig)); + } + + void addLocVarData(int opc, Argument arg) { + } + + public void addInnerClass(int access, ConstantPool.ConstCell name, ConstantPool.ConstCell innerClass, ConstantPool.ConstCell outerClass) { + env.traceln("addInnerClass (with indexes: Name (" + name.toString() + "), Inner (" + innerClass.toString() + "), Outer (" + outerClass.toString() + ")."); + if (innerClasses == null) { + innerClasses = new DataVectorAttr<>(this, AttrTag.ATT_InnerClasses.parsekey()); + } + innerClasses.add(new InnerClassData(access, name, innerClass, outerClass)); + } + + public void addBootstrapMethod(BootstrapMethodData bsmData) { + env.traceln("addBootstrapMethod"); + if (bootstrapMethodsAttr == null) { + bootstrapMethodsAttr = new DataVectorAttr<>(this, AttrTag.ATT_BootstrapMethods.parsekey()); + } + bootstrapMethodsAttr.add(bsmData); + } + + public void addNestHost(ConstantPool.ConstCell hostClass) { + env.traceln("addNestHost"); + nestHostAttr = new CPXAttr(this, AttrTag.ATT_NestHost.parsekey(), hostClass); + } + + public void addNestMembers(List classes) { + env.traceln("addNestMembers"); + nestMembersAttr = new NestMembersAttr(this, classes); + } + + public void addPermittedSubclasses(List classes) { + env.traceln("addPermittedSubclasses"); + permittedSubclassesAttr = new PermittedSubclassesAttr(this, classes); + } + + public void addPreloads(List classes) { + env.traceln("addPreloads"); + preloadAttr = new PreloadAttr(this, classes); + } + + public void endClass() { + sourceFileNameAttr = new CPXAttr(this, + AttrTag.ATT_SourceFile.parsekey(), + pool.FindCellAsciz(env.getSimpleInputFileName())); + pool.NumberizePool(); + pool.CheckGlobals(); + numberBootstrapMethods(); + try { + me = pool.uncheckedGetCell(me.arg); + env.traceln("me=" + me); + ConstantPool.ConstValue_Cell me_value = (ConstantPool.ConstValue_Cell) me.ref; + ConstantPool.ConstCell ascicell = me_value.cell; + env.traceln("ascicell=" + ascicell); + ConstantPool.ConstValue_String me_str = (ConstantPool.ConstValue_String) ascicell.ref; + myClassName = me_str.value; + env.traceln("-------------------"); + env.traceln("-- Constant Pool --"); + env.traceln("-------------------"); + pool.printPool(); + env.traceln("-------------------"); + env.traceln(" "); + env.traceln(" "); + env.traceln("-------------------"); + env.traceln("-- Inner Classes --"); + env.traceln("-------------------"); + printInnerClasses(); + + } catch (Throwable e) { + env.traceln("check name:" + e); + env.error("no.classname"); + e.printStackTrace(); + } + } + + public void endModule(ModuleAttr moduleAttr) { + moduleAttribute = moduleAttr.build(); + pool.NumberizePool(); + pool.CheckGlobals(); + myClassName = "module-info"; + } + + private void printInnerClasses() { + if (innerClasses != null) { + int i = 1; + for (InnerClassData entry : innerClasses) { + env.trace(" InnerClass[" + i + "]: (" + Modifiers.toString(entry.access, CF_Context.CTX_INNERCLASS) + "]), "); + env.trace("Name: " + entry.name.toString() + " "); + env.trace("IC_info: " + entry.innerClass.toString() + " "); + env.trace("OC_info: " + entry.outerClass.toString() + " "); + env.traceln(" "); + i += 1; + } + } else { + env.traceln("<< NO INNER CLASSES >>"); + } + + } + + public void write(CheckedDataOutputStream out) throws IOException { + + // Write the header + out.writeInt(JAVA_MAGIC); + out.writeShort(cfv.minor_version()); + out.writeShort(cfv.major_version()); + + pool.write(out); + out.writeShort(access); // & MM_CLASS; // Q + out.writeShort(me.arg); + out.writeShort(father.arg); + + // Write the interface names + if (interfaces != null) { + out.writeShort(interfaces.size()); + for (Argument intf : interfaces) { + out.writeShort(intf.arg); + } + } else { + out.writeShort(0); + } + + // Write the fields + if (fields != null) { + out.writeShort(fields.size()); + for (FieldData field : fields) { + field.write(out); + } + } else { + out.writeShort(0); + } + + // Write the methods + if (methods != null) { + out.writeShort(methods.size()); + for (MethodData method : methods) { + method.write(out); + } + } else { + out.writeShort(0); + } + + // Write the attributes + DataVector attrs = getAttrVector(); + attrs.write(out); + } // end ClassData.write() + + @Override + protected DataVector getAttrVector() { + DataVector attrs = new DataVector(); + if( moduleAttribute != null ) { + if (annotAttrVis != null) + attrs.add(annotAttrVis); + if (annotAttrInv != null) + attrs.add(annotAttrInv); + attrs.add(moduleAttribute); + } else { + attrs.add(sourceFileNameAttr); + // JEP 359 since class file 58.65535 + if( recordData != null ) { + attrs.add(recordData); + } + if (innerClasses != null) + attrs.add(innerClasses); + if (syntheticAttr != null) + attrs.add(syntheticAttr); + if (deprecatedAttr != null) + attrs.add(deprecatedAttr); + if (annotAttrVis != null) + attrs.add(annotAttrVis); + if (annotAttrInv != null) + attrs.add(annotAttrInv); + if (type_annotAttrVis != null) + attrs.add(type_annotAttrVis); + if (type_annotAttrInv != null) + attrs.add(type_annotAttrInv); + if (bootstrapMethodsAttr != null) + attrs.add(bootstrapMethodsAttr); + // since class version 55.0 + if(nestHostAttributeExists()) + attrs.add(nestHostAttr); + if(nestMembersAttributesExist()) + attrs.add(nestMembersAttr); + // since class version 59.65535 (JEP 360) + if ( permittedSubclassesAttributesExist() ) + attrs.add(permittedSubclassesAttr); + if (preloadAttributeExists()) + attrs.add(preloadAttr); + } + return attrs; + } + + static char fileSeparator; //=System.getProperty("file.separator"); + + /** + * Writes to the directory passed with -d option + */ + public void write(File destdir) throws IOException { + File outfile; + if (destdir == null) { + int startofname = myClassName.lastIndexOf("/"); + if (startofname != -1) { + myClassName = myClassName.substring(startofname + 1); + } + outfile = new File(myClassName + fileExtension); + } else { + env.traceln("writing -d " + destdir.getPath()); + if (fileSeparator == 0) { + fileSeparator = System.getProperty("file.separator").charAt(0); + } + if (fileSeparator != '/') { + myClassName = myClassName.replace('/', fileSeparator); + } + outfile = new File(destdir, myClassName + fileExtension); + File outdir = new File(outfile.getParent()); + if (!outdir.exists() && !outdir.mkdirs()) { + env.error("cannot.write", outdir.getPath()); + return; + } + } + + DataOutputStream dos = new DataOutputStream( + new BufferedOutputStream(new FileOutputStream(outfile))); + cdos.setDataOutputStream(dos); + try { + write(cdos); + } finally { + dos.close(); + } + } // end write() + + public void setByteLimit(int bytelimit) { + cdos.enable(); + cdos.setLimit(bytelimit); + } + + public boolean nestHostAttributeExists() { + return nestHostAttr != null; + } + + public boolean nestMembersAttributesExist() { return nestMembersAttr != null; } + + public boolean permittedSubclassesAttributesExist() { return permittedSubclassesAttr != null; } + + public boolean recordAttributeExists() { return recordData != null; } + + public boolean preloadAttributeExists() { return preloadAttr != null; } + + /** + * This is a wrapper for DataOutputStream, used for debugging purposes. it allows + * writing the byte-stream of a class up to a given byte number. + */ + static private class CDOutputStream implements CheckedDataOutputStream { + + private int bytelimit; + private DataOutputStream dos; + public boolean enabled = false; + + public CDOutputStream() { + dos = null; + } + + public CDOutputStream(OutputStream out) { + setOutputStream(out); + } + + public final void setOutputStream(OutputStream out) { + dos = new DataOutputStream(out); + } + + public void setDataOutputStream(DataOutputStream dos) { + this.dos = dos; + } + + public void setLimit(int lim) { + bytelimit = lim; + } + + public void enable() { + enabled = true; + } + + private synchronized void check(String loc) throws IOException { + if (enabled && dos.size() >= bytelimit) { + throw new IOException(loc); + } + } + + @Override + public synchronized void write(int b) throws IOException { + dos.write(b); + check("Writing byte: " + b); + } + + @Override + public synchronized void write(byte b[], int off, int len) throws IOException { + dos.write(b, off, len); + check("Writing byte-array: " + b); + } + + @Override + public final void writeBoolean(boolean v) throws IOException { + dos.writeBoolean(v); + check("Writing writeBoolean: " + (v ? "true" : "false")); + } + + @Override + public final void writeByte(int v) throws IOException { + dos.writeByte(v); + check("Writing writeByte: " + v); + } + + @Override + public void writeShort(int v) throws IOException { + dos.writeShort(v); + check("Writing writeShort: " + v); + } + + @Override + public void writeChar(int v) throws IOException { + dos.writeChar(v); + check("Writing writeChar: " + v); + } + + @Override + public void writeInt(int v) throws IOException { + dos.writeInt(v); + check("Writing writeInt: " + v); + } + + @Override + public void writeLong(long v) throws IOException { + dos.writeLong(v); + check("Writing writeLong: " + v); + } + + @Override + public void writeFloat(float v) throws IOException { + dos.writeFloat(v); + check("Writing writeFloat: " + v); + } + + @Override + public void writeDouble(double v) throws IOException { + dos.writeDouble(v); + check("Writing writeDouble: " + v); + } + + @Override + public void writeBytes(String s) throws IOException { + dos.writeBytes(s); + check("Writing writeBytes: " + s); + } + + @Override + public void writeChars(String s) throws IOException { + dos.writeChars(s); + check("Writing writeChars: " + s); + } + + @Override + public void writeUTF(String s) throws IOException { + dos.writeUTF(s); + check("Writing writeUTF: " + s); + } + } +}// end class ClassData diff --git a/test/lib/org/openjdk/asmtools/jasm/CodeAttr.java b/test/lib/org/openjdk/asmtools/jasm/CodeAttr.java new file mode 100644 index 00000000000..c01eb6c4870 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/CodeAttr.java @@ -0,0 +1,529 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import org.openjdk.asmtools.jasm.OpcodeTables.Opcode; +import org.openjdk.asmtools.jasm.Tables.AttrTag; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; + +import static org.openjdk.asmtools.jasm.RuntimeConstants.SPLIT_VERIFIER_CFV; + +class CodeAttr extends AttrData { + + protected ClassData cls; + + protected MethodData mtd; + protected Environment env; + protected Argument max_stack, max_locals; + protected Instr zeroInstr, lastInstr; + protected int cur_pc = 0; + protected DataVector trap_table; // TrapData + protected DataVectorAttr lin_num_tb; // LineNumData + protected int lastln = 0; + protected DataVectorAttr loc_var_tb; // LocVarData + protected DataVector> attrs; + protected ArrayList slots; + protected HashMap locvarsHash; + protected HashMap labelsHash; + protected HashMap trapsHash; + protected StackMapData curMapEntry = null; + protected DataVectorAttr stackMap; + // type annotations + protected DataVectorAttr type_annotAttrVis = null; + protected DataVectorAttr type_annotAttrInv = null; + + public CodeAttr(MethodData mtd, int pos, int paramcnt, Argument max_stack, Argument max_locals) { + super(mtd.cls, AttrTag.ATT_Code.parsekey()); + this.mtd = mtd; + this.cls = mtd.cls; + this.env = cls.env; + this.max_stack = max_stack; + this.max_locals = max_locals; + lastInstr = zeroInstr = new Instr(); + trap_table = new DataVector<>(0); // TrapData + attrs = new DataVector<>(); + if (env.debugInfoFlag) { + lin_num_tb = new DataVectorAttr<>(cls, AttrTag.ATT_LineNumberTable.parsekey()); + attrs.add(lin_num_tb); + } + slots = new ArrayList<>(paramcnt); + for (int k = 0; k < paramcnt; k++) { + slots.add(k, 1); + } + } + + void endCode() { + checkTraps(); + checkLocVars(); + checkLabels(); + // + if (type_annotAttrVis != null) { + attrs.add(type_annotAttrVis); + } + if (type_annotAttrInv != null) { + attrs.add(type_annotAttrInv); + } + } + + public void addAnnotations(ArrayList list) { + for (AnnotationData item : list) { + boolean invisible = item.invisible; + if (item instanceof TypeAnnotationData) { + // Type Annotations + TypeAnnotationData ta = (TypeAnnotationData) item; + if (invisible) { + if (type_annotAttrInv == null) { + type_annotAttrInv = new DataVectorAttr(cls, + AttrTag.ATT_RuntimeInvisibleTypeAnnotations.parsekey()); + } + type_annotAttrInv.add(ta); + } else { + if (type_annotAttrVis == null) { + type_annotAttrVis = new DataVectorAttr(cls, + AttrTag.ATT_RuntimeVisibleTypeAnnotations.parsekey()); + } + type_annotAttrVis.add(ta); + } + } + } + } + + /* -------------------------------------- Traps */ + Trap trapDecl(int pos, String name) { + Trap local; + if (trapsHash == null) { + trapsHash = new HashMap<>(10); + local = null; + } else { + local = trapsHash.get(name); + } + if (local == null) { + local = new Trap(pos, name); + trapsHash.put(name, local); + } + return local; + } + + void beginTrap(int pos, String name) { + Trap trap = trapDecl(pos, name); + if (trap.start_pc != Argument.NotSet) { + env.error("trap.tryredecl", name); + return; + } + trap.start_pc = cur_pc; + } + + void endTrap(int pos, String name) { + Trap trap = trapDecl(pos, name); + if (trap.end_pc != Argument.NotSet) { + env.error("trap.endtryredecl", name); + return; + } + trap.end_pc = cur_pc; + } + + void trapHandler(int pos, String name, Argument type) { + Trap trap = trapDecl(pos, name); + trap.refd = true; + TrapData trapData = new TrapData(pos, trap, cur_pc, type); + trap_table.addElement(trapData); + } + + void checkTraps() { + if (trapsHash == null) { + return; + } + for (Trap trap : trapsHash.values()) { + if (!trap.refd) { + env.error(trap.pos, "warn.trap.notref", trap.name); + } + } + + for (TrapData trapData : trap_table) { + Trap trapLabel = trapData.trap; + if (trapLabel.start_pc == Argument.NotSet) { + env.error(trapData.pos, "trap.notry", trapLabel.name); + } + if (trapLabel.end_pc == Argument.NotSet) { + env.error(trapData.pos, "trap.noendtry", trapLabel.name); + } + } + } + + /* -------------------------------------- Labels */ + Label labelDecl(String name) { + Label local; + if (labelsHash == null) { + labelsHash = new HashMap<>(10); + local = null; + } else { + local = labelsHash.get(name); + } + if (local == null) { + local = new Label(name); + labelsHash.put(name, local); + } + return local; + } + + public Label LabelDef(int pos, String name) { + Label label = labelDecl(name); + if (label.defd) { + env.error(pos, "label.redecl", name); + return null; + } + label.defd = true; + label.arg = cur_pc; + return label; + } + + public Label LabelRef(String name) { + Label label = labelDecl(name); + label.refd = true; + return label; + } + + void checkLabels() { + if (labelsHash == null) { + return; + } + + for (Label local : labelsHash.values()) { + // check that every label is defined + if (!local.defd) { + env.error("label.undecl", local.name); + } + } + } + + /* -------------------------------------- Variables */ + LocVarData locvarDecl(String name) { + LocVarData local; + if (locvarsHash == null) { + locvarsHash = new HashMap<>(10); + local = null; + } else { + local = locvarsHash.get(name); + } + if (local == null) { + local = new LocVarData(name); + locvarsHash.put(name, local); + } + return local; + } + + public void LocVarDataDef(int slot) { + slots.set(slot, 1); + if ((max_locals != null) && (max_locals.arg < slots.size())) { + env.error("warn.illslot", Integer.toString(slot)); + } + } + + public void LocVarDataDef(String name, ConstantPool.ConstCell type) { + LocVarData locvar = locvarDecl(name); + if (locvar.defd) { + env.error("locvar.redecl", name); + return; + } + locvar.defd = true; + locvar.start_pc = (short) cur_pc; + locvar.name_cpx = cls.pool.FindCellAsciz(name); + locvar.sig_cpx = type; + int k; + findSlot: + { + for (k = 0; k < slots.size(); k++) { + if (slots.get(k) == 0) { + break findSlot; + } + } + k = slots.size(); + } + LocVarDataDef(k); + locvar.arg = k; + if (loc_var_tb == null) { + loc_var_tb = new DataVectorAttr<>(cls, AttrTag.ATT_LocalVariableTable.parsekey()); + attrs.add(loc_var_tb); + } + loc_var_tb.add(locvar); + } + + public Argument LocVarDataRef(String name) { + LocVarData locvar = locvarDecl(name); + if (!locvar.defd) { + env.error("locvar.undecl", name); + locvar.defd = true; // to avoid multiple error messages + } + locvar.refd = true; + return locvar; + } + + public void LocVarDataEnd(int slot) { + slots.set(slot, 0); + } + + public void LocVarDataEnd(String name) { + LocVarData locvar = locvarsHash.get(name); + if (locvar == null) { + env.error("locvar.undecl", name); + return; + } else if (!locvar.defd) { + env.error("locvar.undecl", name); + return; + } + locvar.length = (short) (cur_pc - locvar.start_pc); + + slots.set(locvar.arg, 0); + locvarsHash.put(name, new LocVarData(name)); + } + + void checkLocVars() { + if (locvarsHash == null) { + return; + } + for (LocVarData locvar : locvarsHash.values()) { + if (!locvar.defd) { + continue; + } // this is false locvar + // set end of scope, if not set + if (slots.get(locvar.arg) == 1) { + locvar.length = (short) (cur_pc - locvar.start_pc); + slots.set(locvar.arg, 0); + } + } + } + + /* -------------------------------------- StackMap */ + public StackMapData getStackMap() { + if (curMapEntry == null) { + curMapEntry = new StackMapData(env); + if (cls.cfv.major_version() >= SPLIT_VERIFIER_CFV) { + curMapEntry.setIsStackMapTable(true); + } + } + return curMapEntry; + } + + /*====================================================== Instr */ + void addInstr(int mnenoc_pos, Opcode opcode, Argument arg, Object arg2) { + Instr newInstr = new Instr(cur_pc, cls.env.pos, opcode, arg, arg2); + lastInstr.next = newInstr; + lastInstr = newInstr; + int len = opcode.length(); + switch (opcode) { + case opc_tableswitch: + len = ((SwitchTable) arg2).recalcTableSwitch(cur_pc); + break; + case opc_lookupswitch: + len = ((SwitchTable) arg2).calcLookupSwitch(cur_pc); + break; + case opc_ldc: + ((ConstantPool.ConstCell) arg).setRank(ConstantPool.ReferenceRank.LDC); + break; + default: + if (arg instanceof ConstantPool.ConstCell) { + ((ConstantPool.ConstCell) arg).setRank(ConstantPool.ReferenceRank.ANY); + } + } + if (env.debugInfoFlag) { + int ln = env.lineNumber(mnenoc_pos); + if (ln != lastln) { // only one entry in lin_num_tb per line + lin_num_tb.add(new LineNumData(cur_pc, ln)); + lastln = ln; + } + } + if (curMapEntry != null) { + curMapEntry.pc = cur_pc; + StackMapData prevStackFrame = null; + if (stackMap == null) { + if (cls.cfv.major_version() >= SPLIT_VERIFIER_CFV) { + stackMap = new DataVectorAttr<>(cls, AttrTag.ATT_StackMapTable.parsekey()); + } else { + stackMap = new DataVectorAttr<>(cls, AttrTag.ATT_StackMap.parsekey()); + } + attrs.add(stackMap); + } else if (stackMap.size() > 0) { + prevStackFrame = stackMap.get(stackMap.size() - 1); + } + curMapEntry.setOffset(prevStackFrame); + stackMap.add(curMapEntry); + curMapEntry = null; + } + + cur_pc += len; + } + + /*====================================================== Attr interface */ + // subclasses must redefine this + @Override + public int attrLength() { + return 2 + 2 + 4 // for max_stack, max_locals, and cur_pc + + cur_pc // + 2+trap_table.size()*8 + + trap_table.getLength() + attrs.getLength(); + } + + @Override + public void write(CheckedDataOutputStream out) + throws IOException, Parser.CompilerError { + int mxstck = (max_stack != null) ? max_stack.arg : 0; + int mxloc = (max_locals != null) ? max_locals.arg : slots.size(); + super.write(out); // attr name, attr len + out.writeShort(mxstck); + out.writeShort(mxloc); + out.writeInt(cur_pc); + for (Instr instr = zeroInstr.next; instr != null; instr = instr.next) { + instr.write(out, env); + } + + trap_table.write(out); + + attrs.write(out); + } + + /*-------------------------------------------------------- */ + /* CodeAttr inner classes */ + static public class Local extends Argument { + + String name; + boolean defd = false, refd = false; + + public Local(String name) { + this.name = name; + } + } + + /** + * + */ + static public class Label extends Local { + + public Label(String name) { + super(name); + } + } + + /** + * + */ + class LocVarData extends Local implements Data { + + // arg means slot + short start_pc, length; + ConstantPool.ConstCell name_cpx, sig_cpx; + + public LocVarData(String name) { + super(name); + } + + @Override + public int getLength() { + return 10; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeShort(start_pc); + out.writeShort(length); + out.writeShort(name_cpx.arg); + out.writeShort(sig_cpx.arg); + out.writeShort(arg); + } + } + + /** + * + */ + class LineNumData implements Data { + + int start_pc, line_number; + + public LineNumData(int start_pc, int line_number) { + this.start_pc = start_pc; + this.line_number = line_number; + } + + @Override + public int getLength() { + return 4; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeShort(start_pc); + out.writeShort(line_number); + } + } + + /** + * + */ + class Trap extends Local { + + int start_pc = Argument.NotSet, end_pc = Argument.NotSet; + int pos; + + Trap(int pos, String name) { + super(name); + this.pos = pos; + } + } + + /** + * + */ + class TrapData implements Data { + + int pos; + Trap trap; + int handler_pc; + Argument catchType; + + public TrapData(int pos, Trap trap, int handler_pc, Argument catchType) { + this.pos = pos; + this.trap = trap; + this.handler_pc = handler_pc; + this.catchType = catchType; + } + + @Override + public int getLength() { + return 8; // add the length of number of elements + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeShort(trap.start_pc); + out.writeShort(trap.end_pc); + out.writeShort(handler_pc); + if (catchType.isSet()) { + out.writeShort(catchType.arg); + } else { + out.writeShort(0); + } + } + } // end TrapData +} // end CodeAttr + diff --git a/test/lib/org/openjdk/asmtools/jasm/ConstantPool.java b/test/lib/org/openjdk/asmtools/jasm/ConstantPool.java new file mode 100644 index 00000000000..75515bd14e9 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/ConstantPool.java @@ -0,0 +1,1264 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import org.openjdk.asmtools.jasm.Tables.ConstType; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.stream.Collectors; + +/** + * ConstantPool + * + * ConstantPool is the class responsible for maintaining constants for a given class file. + * + */ +public class ConstantPool implements Iterable { + + + static public enum ReferenceRank { + LDC(0), // 0 - highest - ref from ldc + ANY(1), // 1 - any ref + NO(2); // 2 - no ref + final int rank; + ReferenceRank(int rank) { + this.rank = rank; + } + } + + /*-------------------------------------------------------- */ + /* ConstantPool Inner Classes */ + /** + * ConstValue + * + * A (typed) tagged value in the constant pool. + */ + static public class ConstValue { + + protected ConstType tag; + protected boolean isSet = false; + private boolean visited = false; + + public ConstValue(ConstType tag) { + this.tag = tag; + } + + public int size() { + return 1; + } + + public boolean hasValue() { + return isSet; + } + + /** + * Compute the hash-code, based on the value of the native (_hashCode()) hashcode. + */ + @Override + public int hashCode() { + if (visited) { + throw new Parser.CompilerError("CV hash:" + this); + } + visited = true; + int res = _hashCode() + tag.value() * 1023; + visited = false; + return res; + } + + // sub-classes override this. + // this is the default for getting a hash code. + protected int _hashCode() { + return 37; + } + + /** + * Compares this object to the specified object. + * + * Sub-classes must override this + * + * @param obj the object to compare with + * @return true if the objects are the same; false otherwise. + */ + @Override + public boolean equals(Object obj) { + return false; + } + + @Override + public String toString() { + String tagstr = tag.printval(); + String retval = ""; + if (tagstr == null) { + return "BOGUS_TAG:" + tag; + } + + String valueStr = _toString(); + if (valueStr != null) { + retval = "<" + tagstr + " " + valueStr + ">"; + } else { + retval = "<" + tagstr + ">"; + } + return retval; + } + + protected String _toString() { + return ""; + } + + public void write(CheckedDataOutputStream out) throws IOException { + out.writeByte(tag.value()); + } + } // end ConstValue + + /** + * ConstValue + * + * A (typed) tagged value in the constant pool. + */ + static public class ConstValue_Zero extends ConstValue { + + public ConstValue_Zero() { + super(ConstType.CONSTANT_ZERO); + isSet = false; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + throw new Parser.CompilerError("Trying to write Constant 0."); + } + } + + /** + * ConstValue + * + * A (typed) tagged value in the constant pool. + */ + static public class ConstValue_String extends ConstValue { + + String value; + + public ConstValue_String(String value) { + super(ConstType.CONSTANT_UTF8); + this.value = value; + isSet = (value != null); + } + + @Override + protected String _toString() { + return value; + } + + @Override + protected int _hashCode() { + return value.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if ((obj == null) || !(obj instanceof ConstValue_String)) { + return false; + } + ConstValue_String dobj = (ConstValue_String) obj; + if (tag != dobj.tag) { + return false; + } + return value.equals(dobj.value); + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + super.write(out); + out.writeUTF(value); + } + } + + /** + * ConstValue + * + * A (typed) tagged value in the constant pool. + */ + static public class ConstValue_Integer extends ConstValue { + + Integer value; + + public ConstValue_Integer(ConstType tag, Integer value) { + super(tag); + this.value = value; + isSet = (value != null); + } + + @Override + protected String _toString() { + return value.toString(); + } + + @Override + public boolean equals(Object obj) { + if ((obj == null) || !(obj instanceof ConstValue_Integer)) { + return false; + } + ConstValue_Integer dobj = (ConstValue_Integer) obj; + if (tag != dobj.tag) { + return false; + } + return value.equals(dobj.value); + } + + @Override + protected int _hashCode() { + return value.hashCode(); + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + super.write(out); + out.writeInt(value.intValue()); + } + } + + /** + * ConstValue + * + * A (typed) tagged value in the constant pool. + */ + static public class ConstValue_Long extends ConstValue { + + Long value; + + public ConstValue_Long(ConstType tag, Long value) { + super(tag); + this.value = value; + isSet = (value != null); + } + + @Override + public int size() { + return 2; + } + + @Override + protected String _toString() { + return value.toString(); + } + + @Override + public boolean equals(Object obj) { + if ((obj == null) || !(obj instanceof ConstValue_Long)) { + return false; + } + ConstValue_Long dobj = (ConstValue_Long) obj; + if (tag != dobj.tag) { + return false; + } + return value.equals(dobj.value); + } + + @Override + protected int _hashCode() { + return value.hashCode(); + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + super.write(out); + out.writeLong(value.longValue()); + } + } + + /** + * ConstValue + * + * A (typed) tagged value in the constant pool. + */ + static public class ConstValue_Cell extends ConstValue { + + ConstCell cell; + + public ConstValue_Cell(ConstType tag, ConstCell cell) { + super(tag); + this.cell = cell; + isSet = (cell != null); + } + + @Override + protected String _toString() { + return cell.toString(); + } + + @Override + public boolean equals(Object obj) { + if ((obj == null) || !(obj instanceof ConstValue_Cell)) { + return false; + } + ConstValue_Cell dobj = (ConstValue_Cell) obj; + if (tag != dobj.tag) { + return false; + } + return cell.equals(dobj.cell); + } + + @Override + protected int _hashCode() { + return cell.hashCode(); + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + super.write(out); + cell.write(out); + } + } + + /** + * ConstValue + * + * A (typed) tagged value in the constant pool. + */ + static public class ConstValue_Pair extends ConstValue { + + ConstCell left, right; + + public ConstValue_Pair(ConstType tag, ConstCell left, ConstCell right) { + super(tag); + this.left = left; + this.right = right; + isSet = (left != null && right != null); + } + + @Override + public boolean equals(Object obj) { + if ((obj == null) || !(obj instanceof ConstValue_Pair)) { + return false; + } + ConstValue_Pair dobj = (ConstValue_Pair) obj; + if (tag != dobj.tag) { + return false; + } + if (dobj.left != null) + if (!dobj.left.equals(left)) + return false; + if (dobj.right != null) + if (!dobj.right.equals(right)) + return false; + return true; + } + + @Override + public String toString() { + return super.toString() + "{" + left + "," + right + "}"; + } + + @Override + protected int _hashCode() { + return left.hashCode() * right.hashCode(); + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + super.write(out); + if (tag == ConstType.CONSTANT_METHODHANDLE) { + out.writeByte(left.arg); // write subtag value + } else { + out.writeShort(left.arg); + } + out.writeShort(right.arg); + } + } + + static public class ConstValue_IndyOrCondyPair extends ConstValue { + BootstrapMethodData bsmData; + ConstantPool.ConstCell napeCell; + + protected ConstValue_IndyOrCondyPair(ConstType tag, BootstrapMethodData bsmdata, ConstCell napeCell) { + super(tag); + assert (tag == ConstType.CONSTANT_DYNAMIC && ConstValue_CondyPair.class.isAssignableFrom(getClass())) || + tag == ConstType.CONSTANT_INVOKEDYNAMIC && ConstValue_IndyPair.class.isAssignableFrom(getClass()); + + this.bsmData = bsmdata; + this.napeCell = napeCell; + isSet = (bsmdata != null && napeCell != null); + } + + @Override + public boolean equals(Object obj) { + if ((obj == null) || !(getClass().isInstance(obj))) { + return false; + } + + ConstValue_IndyOrCondyPair iobj = (ConstValue_IndyOrCondyPair) obj; + return (iobj.bsmData == bsmData) && (iobj.napeCell == napeCell); + } + + @Override + public String toString() { + return super.toString() + "{" + bsmData + "," + napeCell + "}"; + } + + @Override + protected int _hashCode() { + if (bsmData.isPlaceholder()) { + return napeCell.hashCode(); + } + return bsmData.hashCode() * napeCell.hashCode(); + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + super.write(out); + out.writeShort(bsmData.arg); + out.writeShort(napeCell.arg); + } + } + /** + * ConstValue + * + * A (typed) tagged value in the constant pool. + */ + static public class ConstValue_CondyPair extends ConstValue_IndyOrCondyPair { + + public ConstValue_CondyPair(BootstrapMethodData bsmdata, ConstCell napeCell) { + super(ConstType.CONSTANT_DYNAMIC, bsmdata, napeCell); + } + } + + /** + * ConstValue + * + * A (typed) tagged value in the constant pool. + */ + static public class ConstValue_IndyPair extends ConstValue_IndyOrCondyPair { + + public ConstValue_IndyPair(BootstrapMethodData bsmdata, ConstCell napeCell) { + super(ConstType.CONSTANT_INVOKEDYNAMIC, bsmdata, napeCell); + } + } + + /*-------------------------------------------------------- */ + /* ConstantPool Inner Classes */ + /** + * ConstantCell + * + * ConstantCell is a type of data that can be in a constant pool. + */ + static public class ConstCell extends Argument implements Data { + + ConstValue ref; + // 0 - highest - ref from ldc, 1 - any ref, 2 - no ref + ReferenceRank rank = ReferenceRank.NO; + + ConstCell(int arg, ConstValue ref) { + this.arg = arg; + this.ref = ref; + } + + ConstCell(ConstValue ref) { + this(NotSet, ref); + } + + ConstCell(int arg) { + this(arg, null); + } + + @Override + public int getLength() { + return 2; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeShort(arg); + } + + public void setRank(ReferenceRank rank) { + // don't change a short ref to long due to limitation of ldc - max 256 indexes allowed + if( this.rank != ReferenceRank.LDC) { + this.rank = rank; + } + } + + @Override + public int hashCode() { + if (arg == NotSet) { + if (ref != null) { + return ref.hashCode(); + } else { + throw new Parser.CompilerError("Can't generate Hash Code, Null ConstCell Reference."); + } + } + return arg; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + ConstCell cc = (ConstCell)obj; + if( cc.ref == null ) { + return this.ref == null && cc.rank == this.rank; + } + return cc.ref.equals(this.ref) && cc.rank == this.rank; + } + + public boolean isUnset() { + return (arg == NotSet) && (ref == null); + } + + @Override + public String toString() { + return "#" + arg + "=" + ref; + } + } + + /** + * CPVisitor + * + * CPVisitor base class defining a visitor for decoding constants. + */ + public static class CPTagVisitor implements Constants { + + public CPTagVisitor() { + } + + public final R visit(ConstType tag) { + R retVal = null; + switch (tag) { + case CONSTANT_UTF8: + retVal = visitUTF8(tag); + break; + case CONSTANT_INTEGER: + retVal = visitInteger(tag); + break; + case CONSTANT_FLOAT: + retVal = visitFloat(tag); + break; + case CONSTANT_DOUBLE: + retVal = visitDouble(tag); + break; + case CONSTANT_LONG: + retVal = visitLong(tag); + break; + case CONSTANT_METHODTYPE: + retVal = visitMethodtype(tag); + break; + case CONSTANT_STRING: + retVal = visitString(tag); + break; + case CONSTANT_CLASS: + retVal = visitClass(tag); + break; + case CONSTANT_METHOD: + retVal = visitMethod(tag); + break; + case CONSTANT_FIELD: + retVal = visitField(tag); + break; + case CONSTANT_INTERFACEMETHOD: + retVal = visitInterfacemethod(tag); + break; + case CONSTANT_NAMEANDTYPE: + retVal = visitNameandtype(tag); + break; + case CONSTANT_METHODHANDLE: + retVal = visitMethodhandle(tag); + break; + case CONSTANT_DYNAMIC: + retVal = visitDynamic(tag); + break; + case CONSTANT_INVOKEDYNAMIC: + retVal = visitInvokedynamic(tag); + break; + default: + visitDefault(tag); + } + return retVal; + } + + public R visitUTF8(ConstType tag) { + return null; + } + + public R visitInteger(ConstType tag) { + return null; + } + + public R visitFloat(ConstType tag) { + return null; + } + + public R visitDouble(ConstType tag) { + return null; + } + + public R visitLong(ConstType tag) { + return null; + } + + public R visitMethodtype(ConstType tag) { + return null; + } + + public R visitString(ConstType tag) { + return null; + } + + public R visitClass(ConstType tag) { + return null; + } + + public R visitMethod(ConstType tag) { + return null; + } + + public R visitField(ConstType tag) { + return null; + } + + public R visitInterfacemethod(ConstType tag) { + return null; + } + + public R visitNameandtype(ConstType tag) { + return null; + } + + public R visitMethodhandle(ConstType tag) { + return null; + } + + public R visitDynamic(ConstType tag) { + return null; + } + + public R visitInvokedynamic(ConstType tag) { + return null; + } + + public R visitModule(ConstType tag) { + return null; + } + + public R visitPackage(ConstType tag) { + return null; + } + + public void visitDefault(ConstType tag) { + } + } + + /** + * CPVisitor + * + * CPVisitor base class defining a visitor for decoding constants. + */ + public static class CPVisitor implements Constants { + + public CPVisitor() { + } + + public final R visit(ConstValue val) { + R retVal = null; + ConstType tag = val.tag; + switch (tag) { + case CONSTANT_UTF8: + retVal = visitUTF8((ConstValue_String) val); + break; + case CONSTANT_INTEGER: + retVal = visitInteger((ConstValue_Integer) val); + break; + case CONSTANT_FLOAT: + retVal = visitFloat((ConstValue_Integer) val); + break; + case CONSTANT_DOUBLE: + retVal = visitDouble((ConstValue_Long) val); + break; + case CONSTANT_LONG: + retVal = visitLong((ConstValue_Long) val); + break; + case CONSTANT_METHODTYPE: + retVal = visitMethodtype((ConstValue_Cell) val); + break; + case CONSTANT_STRING: + retVal = visitString((ConstValue_Cell) val); + break; + case CONSTANT_CLASS: + retVal = visitClass((ConstValue_Cell) val); + break; + case CONSTANT_METHOD: + retVal = visitMethod((ConstValue_Pair) val); + break; + case CONSTANT_FIELD: + retVal = visitField((ConstValue_Pair) val); + break; + case CONSTANT_INTERFACEMETHOD: + retVal = visitInterfacemethod((ConstValue_Pair) val); + break; + case CONSTANT_NAMEANDTYPE: + retVal = visitNameandtype((ConstValue_Pair) val); + break; + case CONSTANT_METHODHANDLE: + retVal = visitMethodhandle((ConstValue_Pair) val); + break; + case CONSTANT_DYNAMIC: + retVal = visitDynamic((ConstValue_CondyPair) val); + break; + case CONSTANT_INVOKEDYNAMIC: + retVal = visitInvokedynamic((ConstValue_IndyPair) val); + break; + case CONSTANT_MODULE: + retVal = visitModule((ConstValue_Cell) val); + break; + case CONSTANT_PACKAGE: + retVal = visitPackage((ConstValue_Cell) val); + break; + default: + visitDefault(tag); + } + return retVal; + } + + public R visitUTF8(ConstValue_String p) { + return null; + } + + ; + public R visitInteger(ConstValue_Integer p) { + return null; + } + + ; + public R visitFloat(ConstValue_Integer p) { + return null; + } + + ; + public R visitDouble(ConstValue_Long p) { + return null; + } + + ; + public R visitLong(ConstValue_Long p) { + return null; + } + + ; + public R visitMethodtype(ConstValue_Cell p) { + return null; + } + + ; + public R visitString(ConstValue_Cell p) { + return null; + } + + ; + public R visitClass(ConstValue_Cell p) { + return null; + } + + ; + public R visitMethod(ConstValue_Pair p) { + return null; + } + + ; + public R visitField(ConstValue_Pair p) { + return null; + } + + ; + public R visitInterfacemethod(ConstValue_Pair p) { + return null; + } + + ; + public R visitNameandtype(ConstValue_Pair p) { + return null; + } + + ; + public R visitMethodhandle(ConstValue_Pair p) { + return null; + } + + ; + public R visitDynamic(ConstValue_CondyPair p) { return null;} + + ; + public R visitInvokedynamic(ConstValue_IndyPair p) { return null;} + + ; + public R visitModule(ConstValue_Cell p) { return null; } + + ; + public R visitPackage(ConstValue_Cell p) { return null; } + ; + + public void visitDefault(ConstType tag) {} + ; + + } + + + + /*-------------------------------------------------------- */ + /* Constant Pool Fields */ + + private ArrayList pool = new ArrayList<>(20); + + private final ConstValue ConstValue0 + = new ConstValue_String(""); +// private final ConstValue ConstValue0 = +// new ConstValue(CONSTANT_UTF8, ""); + private final ConstCell nullConst + = new ConstCell(null); + private final ConstCell constant_0 + = new ConstCell(new ConstValue_Zero()); +// private final ConstCell constant_0 = +// new ConstCell(new ConstValue(CONSTANT_ZERO, null)); + + // For hashing by value + Hashtable cpoolHashByValue + = new Hashtable<>(40); + + public Environment env; + + private static boolean debugCP = false; + + /*-------------------------------------------------------- */ + /** + * main constructor + * + * @param env The error reporting environment + */ + public ConstantPool(Environment env) { + this.env = env; + pool.add(constant_0); + + } + + public void debugStr(String s) { + if (debugCP) { + env.traceln(s); + } + } + + @Override + public Iterator iterator() { + return pool.iterator(); + } + + + /* + * Fix Refs in constant pool. + * + * This is used when scanning JASM files produced from JDis with the verbose + * option (eg. where the constant pool is declared in the jasm itself). In + * this scenario, we need two passes - the first pass to scan the entries + * (which creates constant references with indexes, but no reference values); + * and the second pass, which links references to existing constants. + * + */ + public void fixRefsInPool() { + // used to fix CP refs when a constant pool is constructed by refs alone. + env.traceln("Fixing CP for explicit Constant Entries."); + int i = 0; + // simply iterate through the pool. + for (ConstCell item : pool) { + i += 1; + // first item is always null + if (item == null) { + continue; + } + + checkAndFixCPRef(i, item); + } + } + + protected void CheckGlobals() { + env.traceln("Checking Globals"); + // + // This fn will put empty UTF8 string entries on any unset + // CP entries - before the last CP entry. + // + for (int cpx = 1; cpx < pool.size(); cpx++) { + ConstCell cell = pool.get(cpx); + if (cell == nullConst) { // gap + cell = new ConstCell(cpx, ConstValue0); + pool.set(cpx, cell); + } + ConstValue cval = cell.ref; + if ((cval == null) || !cval.hasValue()) { + String name = Integer.toString(cpx); + env.error("const.undecl", name); + } + } + } + + /* + * Helper function for "fixRefsInPool" + * + * Does recursive checking of references, + * using a locally-defined visitor. + */ + private void checkAndFixCPRef(int i, ConstCell item) { + ConstValue cv = item.ref; + if (cv != null) { + fixCPVstr.visit(cv); + } + } + + private CPVisitor fixCPVstr = new CPVisitor() { + @Override + public Void visitUTF8(ConstValue_String p) { + return null; + } + + ; + @Override + public Void visitInteger(ConstValue_Integer p) { + return null; + } + + ; + @Override + public Void visitFloat(ConstValue_Integer p) { + return null; + } + + ; + @Override + public Void visitDouble(ConstValue_Long p) { + return null; + } + + ; + @Override + public Void visitLong(ConstValue_Long p) { + return null; + } + + ; + @Override + public Void visitMethodtype(ConstValue_Cell p) { + handleClassRef(p); + return null; + } + + ; + @Override + public Void visitString(ConstValue_Cell p) { + handleClassRef(p); + return null; + } + + ; + @Override + public Void visitClass(ConstValue_Cell p) { + handleClassRef(p); + return null; + } + + ; + @Override + public Void visitMethod(ConstValue_Pair p) { + handleMemberRef(p); + return null; + } + + ; + @Override + public Void visitField(ConstValue_Pair p) { + handleMemberRef(p); + return null; + } + + ; + @Override + public Void visitInterfacemethod(ConstValue_Pair p) { + handleMemberRef(p); + return null; + } + + ; + @Override + public Void visitNameandtype(ConstValue_Pair p) { + handleMemberRef(p); + return null; + } + + ; + @Override + public Void visitMethodhandle(ConstValue_Pair p) { + handleMemberRef(p); + return null; + } + + ; + @Override + public Void visitDynamic(ConstValue_CondyPair p) { + return null; + } + + ; + @Override + public Void visitInvokedynamic(ConstValue_IndyPair p) { + return null; + } + ; + + @Override + public Void visitModule(ConstValue_Cell p) { + handleClassRef(p); + return null; + } + ; + + @Override + public Void visitPackage(ConstValue_Cell p) { + handleClassRef(p); + return null; + } + ; + + + public void handleClassRef(ConstValue_Cell cv) { + ConstCell clref = cv.cell; + if (clref.ref == null) { + ConstCell refval = cpool_get(clref.arg); + if (refval != null) { + checkAndFixCPRef(clref.arg, refval); + clref.ref = refval.ref; + } else { + clref.ref = null; + } + // env.traceln("FIXED ConstPool[" + i + "](" + cv.TagString(cv.tag) + ") = " + cv.value); + } + } + + public void handleMemberRef(ConstValue_Pair cv) { + // env.traceln("ConstPool[" + i + "](" + cv.TagString(cv.tag) + ") = " + cv.value); + ConstCell clref = cv.left; + ConstCell typref = cv.right; + if (clref.ref == null) { + ConstCell refval = cpool_get(clref.arg); + if (refval != null) { + checkAndFixCPRef(clref.arg, refval); + clref.ref = refval.ref; + } else { + clref.ref = null; + } + // env.traceln("FIXED ConstPool[" + i + "](" + cv.TagString(cv.tag) + ") = " + cv.value); + } + if (typref.ref == null) { + ConstCell refval = cpool_get(typref.arg); + if (refval != null) { + checkAndFixCPRef(typref.arg, refval); + typref.ref = refval.ref; + } else { + typref.ref = null; + } + // env.traceln("FIXED ConstPool[" + i + "](" + cv.TagString(cv.tag) + ") = " + cv.value); + } + } + + }; + + /* + * Help debug Constant Pools + */ + public void printPool() { + int i = 0; + for (ConstCell item : pool) { + env.traceln("^^^^^^^^^^^^^ const #" + i + ": " + item); + i += 1; + } + } + + private ConstCell cpool_get(int cpx) { + if (cpx >= pool.size()) { + return null; + } + return pool.get(cpx); + } + + private void cpool_set(int cpx, ConstCell cell, int sz) { + debugStr("cpool_set1: " + cpx + " " + cell); + debugStr("param_size: " + sz); + debugStr("pool_size: " + pool.size()); + cell.arg = cpx; + if (cpx + sz >= pool.size()) { + debugStr("calling ensureCapacity( " + (cpx + sz + 1) + ")"); + int low = pool.size(); + int high = cpx + sz; + for (int i = 0; i < high - low; i++) { + pool.add(nullConst); + } + } + pool.set(cpx, cell); + if (sz == 2) { + pool.set(cpx + 1, new ConstCell(cpx + 1, ConstValue0)); + } + debugStr(" cpool_set2: " + cpx + " " + cell); + } + + protected ConstCell uncheckedGetCell(int cpx) { // by index + return pool.get(cpx); + } + + public ConstCell getCell(int cpx) { // by index + ConstCell cell = cpool_get(cpx); + if (cell != null) { + return cell; + } + cell = new ConstCell(cpx, null); + return cell; + } + + public void setCell(int cpx, ConstCell cell) { + ConstValue value = cell.ref; + if (value == null) { + throw new Parser.CompilerError(env.errorStr("comperr.constcell.nullvalset")); + } + int sz = value.size(); + + if (cpx == 0) { + // It is correct to warn about redeclaring constant zero, + // since this value is never written out to a class file. + env.error("warn.const0.redecl"); + } else { + if ((cpool_get(cpx) != null) || ((sz == 2) && (cpool_get(cpx + 1) != null))) { + String name = "#" + cpx; + env.error("const.redecl", name); + return; + } + if (cell.isSet() && (cell.arg != cpx)) { + env.traceln("setCell: new ConstCell"); + cell = new ConstCell(value); + } + } + cpool_set(cpx, cell, sz); + } + + protected void NumberizePool() { + env.traceln("NumberizePool"); + + for (ReferenceRank rank : ReferenceRank.values()) { + for (ConstCell cell : cpoolHashByValue.values().stream(). + filter(v-> !v.isSet() && rank.equals(v.rank)). + collect(Collectors.toList())) { + + ConstValue value = cell.ref; + if (value == null) { + throw new Parser.CompilerError(env.errorStr("comperr.constcell.nullvalhash")); + } + int sz = value.size(), cpx; +find: + for (cpx = 1; cpx < pool.size(); cpx++) { + if ((pool.get(cpx) == nullConst) && ((sz == 1) || (pool.get(cpx + 1) == nullConst))) { + break find; + } + } + cpool_set(cpx, cell, sz); + } + } + + ConstCell firstCell = cpool_get(0); + firstCell.arg = 0; + } + + public ConstCell FindCell(ConstValue ref) { + if (ref == null) { + throw new Parser.CompilerError(env.errorStr("comperr.constcell.nullval")); + } + ConstCell pconst = null; + try { + pconst = cpoolHashByValue.get(ref); + } catch (Parser.CompilerError e) { + throw new Parser.CompilerError(env.errorStr("comperr.constcell.nullvalhash")); + } + // If we fund a cached ConstValue + if (pconst != null) { + ConstValue value = pconst.ref; + if (!value.equals(ref)) { + throw new Parser.CompilerError(env.errorStr("comperr.val.noteq")); + } + return pconst; + } + // If we didn't find a cached ConstValue + // Add it to the cache + pconst = new ConstCell(ref); + cpoolHashByValue.put(ref, pconst); + return pconst; + } + + public ConstCell FindCell(ConstType tag, String value) { + return FindCell(new ConstValue_String(value)); + } + + public ConstCell FindCell(ConstType tag, Integer value) { + return FindCell(new ConstValue_Integer(tag, value)); + } + + public ConstCell FindCell(ConstType tag, Long value) { + return FindCell(new ConstValue_Long(tag, value)); + } + + public ConstCell FindCell(ConstType tag, ConstCell value) { + return FindCell(new ConstValue_Cell(tag, value)); + } + + public ConstCell FindCell(ConstType tag, ConstCell left, ConstCell right) { + return FindCell(new ConstValue_Pair(tag, left, right)); + } + + public ConstCell FindCellAsciz(String str) { + return FindCell(ConstType.CONSTANT_UTF8, str); + } + + public ConstCell FindCellClassByName(String name) { return FindCell(ConstType.CONSTANT_CLASS, FindCellAsciz(name)); } + + public ConstCell FindCellModuleByName(String name) { return FindCell(ConstType.CONSTANT_MODULE, FindCellAsciz(name)); } + + public ConstCell FindCellPackageByName(String name) { return FindCell(ConstType.CONSTANT_PACKAGE, FindCellAsciz(name)); } + + public void write(CheckedDataOutputStream out) throws IOException { + // Write the constant pool + int length = pool.size(); + out.writeShort(length); + int i; + env.traceln("wr.pool:size=" + length); + for (i = 1; i < length;) { + ConstCell cell = pool.get(i); + ConstValue value = cell.ref; + if (cell.arg != i) { + throw new Parser.CompilerError(env.errorStr("comperr.constcell.invarg", Integer.toString(i), cell.arg)); + } + value.write(out); + i += value.size(); + } + } +} diff --git a/test/lib/org/openjdk/asmtools/jasm/Constants.java b/test/lib/org/openjdk/asmtools/jasm/Constants.java new file mode 100644 index 00000000000..0d93e035cf0 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/Constants.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 1996, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +/** + * This interface defines constant that are used throughout the compiler. It inherits from + * RuntimeConstants, which is an autogenerated class that contains constants defined in + * the interpreter. + */ +public interface Constants extends RuntimeConstants { + + + /** + * End of input + */ + public static final int EOF = -1; + + /* + * Flags + */ + public static final int F_VERBOSE = 1 << 0; + public static final int F_DUMP = 1 << 1; + public static final int F_WARNINGS = 1 << 2; + public static final int F_DEBUG = 1 << 3; + public static final int F_OPTIMIZE = 1 << 4; + public static final int F_DEPENDENCIES = 1 << 5; + + /* + * Type codes + */ + public static final int TC_BOOLEAN = 0; + public static final int TC_BYTE = 1; + public static final int TC_CHAR = 2; + public static final int TC_SHORT = 3; + public static final int TC_INT = 4; + public static final int TC_LONG = 5; + public static final int TC_FLOAT = 6; + public static final int TC_DOUBLE = 7; + public static final int TC_NULL = 8; + public static final int TC_ARRAY = 9; + public static final int TC_CLASS = 10; + public static final int TC_VOID = 11; + public static final int TC_METHOD = 12; + public static final int TC_ERROR = 13; + + /* + * Type Masks + */ + public static final int TM_NULL = 1 << TC_NULL; + public static final int TM_VOID = 1 << TC_VOID; + public static final int TM_BOOLEAN = 1 << TC_BOOLEAN; + public static final int TM_BYTE = 1 << TC_BYTE; + public static final int TM_CHAR = 1 << TC_CHAR; + public static final int TM_SHORT = 1 << TC_SHORT; + public static final int TM_INT = 1 << TC_INT; + public static final int TM_LONG = 1 << TC_LONG; + public static final int TM_FLOAT = 1 << TC_FLOAT; + public static final int TM_DOUBLE = 1 << TC_DOUBLE; + public static final int TM_ARRAY = 1 << TC_ARRAY; + public static final int TM_CLASS = 1 << TC_CLASS; + public static final int TM_METHOD = 1 << TC_METHOD; + public static final int TM_ERROR = 1 << TC_ERROR; + + public static final int TM_INT32 = TM_BYTE | TM_SHORT | TM_CHAR | TM_INT; + public static final int TM_NUM32 = TM_INT32 | TM_FLOAT; + public static final int TM_NUM64 = TM_LONG | TM_DOUBLE; + public static final int TM_INTEGER = TM_INT32 | TM_LONG; + public static final int TM_REAL = TM_FLOAT | TM_DOUBLE; + public static final int TM_NUMBER = TM_INTEGER | TM_REAL; + public static final int TM_REFERENCE = TM_ARRAY | TM_CLASS | TM_NULL; + + /* + * Class status + */ + public static final int CS_UNDEFINED = 0; + public static final int CS_UNDECIDED = 1; + public static final int CS_BINARY = 2; + public static final int CS_SOURCE = 3; + public static final int CS_PARSED = 4; + public static final int CS_COMPILED = 5; + public static final int CS_NOTFOUND = 6; + + /* + * Attributes + */ + public static final int ATT_ALL = -1; + public static final int ATT_CODE = 1; + + /* + * Number of bits used in file offsets + */ + public static final int OFFSETBITS = 19; + public static final int MAXFILESIZE = (1 << OFFSETBITS) - 1; + public static final int MAXLINENUMBER = (1 << (32 - OFFSETBITS)) - 1; + + /* + * Operator precedence + */ + /* Who uses this???? + public static final int opPrecedence[] = { + 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 12, 13, 14, 15, 16, 17, 18, + 18, 19, 19, 19, 19, 19, 20, 20, 20, 21, + 21, 22, 22, 22, 23, 24, 24, 24, 24, 24, + 24, 25, 25, 26, 26, 26, 26, 26, 26 + }; + * */ +} diff --git a/test/lib/org/openjdk/asmtools/jasm/Data.java b/test/lib/org/openjdk/asmtools/jasm/Data.java new file mode 100644 index 00000000000..b9feeeb67d4 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/Data.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.io.IOException; + +/** + * Base contract for writeable structures + */ +interface Data { + + void write(CheckedDataOutputStream out) throws IOException; + + int getLength(); + + default String tabString(int tabLevel) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < tabLevel; i++) { + sb.append('\t'); + } + return sb.toString(); + } +} diff --git a/test/lib/org/openjdk/asmtools/jasm/DataVector.java b/test/lib/org/openjdk/asmtools/jasm/DataVector.java new file mode 100644 index 00000000000..a268e25e121 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/DataVector.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 1996, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * + */ +public class DataVector implements Iterable { + + ArrayList elements; + + public DataVector(int initSize) { + elements = new ArrayList<>(initSize); + } + + public DataVector() { + this(12); + } + + public Iterator iterator() { + return elements.iterator(); + } + + public void add(T element) { + elements.add(element); + } + + public void addAll(List collection) { + elements.addAll(collection); + } + + // full length of the attribute conveyor + // declared in Data + public int getLength() { + int length = 0; + // calculate overall size here rather than in add() + // because it may not be available at the time of invoking of add() + for (T element : elements) { + length += element.getLength(); + } + + return 2 + length; // add the length of number of elements + } + + public void write(CheckedDataOutputStream out) + throws IOException { + out.writeShort(elements.size()); + writeElements(out); + } + + public void writeElements(CheckedDataOutputStream out) + throws IOException { + for (Data element : elements) { + element.write(out); + } + } + + /* for compatibility with Vector */ + public void addElement(T element) { + elements.add(element); + } + + public int size() { + return elements.size(); + } + + public Data elementAt(int k) { + return elements.get(k); + } +}// end class DataVector + diff --git a/test/lib/org/openjdk/asmtools/jasm/DataVectorAttr.java b/test/lib/org/openjdk/asmtools/jasm/DataVectorAttr.java new file mode 100644 index 00000000000..711feb1722a --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/DataVectorAttr.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; + +/** + * + */ +// public class DataVectorAttr extends AttrData implements Constants { +// } +class DataVectorAttr extends AttrData implements Iterable { + + private ArrayList elements; + private boolean byteIndex; + + private DataVectorAttr(ClassData cls, String name, boolean byteIndex, ArrayList initialData) { + super(cls, name); + this.elements = initialData; + this.byteIndex = byteIndex; + } + + DataVectorAttr(ClassData cls, String name, ArrayList initialData) { + this(cls, name, false, initialData); + } + + DataVectorAttr(ClassData cls, String name) { + this(cls, name, false, new ArrayList<>()); + + } + + DataVectorAttr(ClassData cls, String name, boolean byteIndex) { + this(cls, name, byteIndex, new ArrayList<>()); + + } + + public T get(int index) { + return elements.get(index); + } + + public void add(T element) { + elements.add(element); + } + + public void put(int i, T element) { + elements.set(i, element); + } + + public int size() { + return elements.size(); + } + + @Override + public Iterator iterator() { + return elements.iterator(); + } + + @Override + public int attrLength() { + int length = 0; + // calculate overall size here rather than in add() + // because it may not be available at the time of invoking of add() + for (T elem : elements) { + length += elem.getLength(); + } + + // add the length of number of elements + if (byteIndex) { + length += 1; + } else { + length += 2; + } + + return length; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + super.write(out); // attr name, attr len + if (byteIndex) { + out.writeByte(elements.size()); + } else { + out.writeShort(elements.size()); + } // number of elements + for (T elem : elements) { + elem.write(out); + } + } + +} diff --git a/test/lib/org/openjdk/asmtools/jasm/DefaultAnnotationAttr.java b/test/lib/org/openjdk/asmtools/jasm/DefaultAnnotationAttr.java new file mode 100644 index 00000000000..af8168782de --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/DefaultAnnotationAttr.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.io.IOException; + +/** + * DefaultAnnotationAttr + * + * Used to represent Default Annotation Attributes + * + */ +public class DefaultAnnotationAttr extends AttrData { + + Data element; // Data + + public DefaultAnnotationAttr(ClassData cls, String name, Data element) { + super(cls, name); + this.element = element; + } + + public DefaultAnnotationAttr(ClassData cls, String name) { + super(cls, name); + this.element = null; + } + + public void add(Data element) { + this.element = element; + } + + @Override + public int attrLength() { + return element.getLength(); // add the length of number of elements + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + super.write(out); // attr name, attr len + element.write(out); + } +}// end class DataVectorAttr + diff --git a/test/lib/org/openjdk/asmtools/jasm/Environment.java b/test/lib/org/openjdk/asmtools/jasm/Environment.java new file mode 100644 index 00000000000..93ca2786adc --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/Environment.java @@ -0,0 +1,454 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import static org.openjdk.asmtools.jasm.Constants.EOF; +import static org.openjdk.asmtools.jasm.Constants.OFFSETBITS; +import org.openjdk.asmtools.util.I18NResourceBundle; + +import java.io.*; +import java.nio.file.Paths; +import java.text.MessageFormat; + +/** + * An input stream for java programs. The stream treats either "\n", "\r" or "\r\n" as the + * end of a line, it always returns \n. It also parses UNICODE characters expressed as + * \uffff. However, if it sees "\\", the second slash cannot begin a unicode sequence. It + * keeps track of the current position in the input stream. + * + * An position consists of: ((linenr << OFFSETBITS) | offset) this means that both + * the line number and the exact offset into the file are encoded in each position + * value.

    + */ +public class Environment { + + /*-------------------------------------------------------- */ + /* Environment Inner Classes */ + /** + * A sorted list of error messages + */ + final class ErrorMessage { + + int where; + String message; + ErrorMessage next; + + /** + * Constructor + */ + ErrorMessage(int where, String message) { + this.where = where; + this.message = message; + } + } + + /*-------------------------------------------------------- */ + /* Environment Fields */ + static boolean traceFlag = false; + boolean debugInfoFlag = false; + + private String inputFileName; + private String simpleInputFileName; + public PrintWriter out; + private boolean nowarn; + private byte[] data; + private int bytepos; + private int linepos; + public int pos; + /*-------------------------------------------------------- */ + + public Environment(DataInputStream dis, String inputFileName, PrintWriter out, boolean nowarn) throws IOException { + this.out = out; + this.inputFileName = inputFileName; + this.nowarn = nowarn; + // Read the file + data = new byte[dis.available()]; + dis.read(data); + dis.close(); + bytepos = 0; + linepos = 1; + } + + public String getInputFileName() { + return inputFileName; + } + + public String getSimpleInputFileName() { + if( simpleInputFileName == null ) { + simpleInputFileName = Paths.get(inputFileName).getFileName().toString(); + } + return simpleInputFileName; + } + + int lookForward() { + try { + return data[bytepos]; + } catch (ArrayIndexOutOfBoundsException e) { + return EOF; + } + } + + int convertUnicode() { + int c; + try { + while ((c = data[bytepos]) == 'u') { + bytepos++; + } + int d = 0; + for (int i = 0; i < 4; i++) { + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + d = (d << 4) + c - '0'; + break; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + d = (d << 4) + 10 + c - 'a'; + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + d = (d << 4) + 10 + c - 'A'; + break; + default: + error(pos, "invalid.escape.char"); + return d; + } + ++bytepos; + c = data[bytepos]; + } + return d; + } catch (ArrayIndexOutOfBoundsException e) { + error(pos, "invalid.escape.char"); + return EOF; + } + } + + public int read() { + int c; + pos = (linepos << OFFSETBITS) | bytepos; + try { + c = data[bytepos]; + } catch (ArrayIndexOutOfBoundsException e) { + return EOF; + } + bytepos++; + + // parse special characters + switch (c) { + /* case '\\': + if (lookForward() != 'u') { + return '\\'; + } + // we have a unicode sequence + return convertUnicode();*/ + case '\n': + linepos++; + return '\n'; + + case '\r': + if (lookForward() == '\n') { + bytepos++; + } + linepos++; + return '\n'; + + default: + return c; + } + } + + int lineNumber(int lcpos) { + return lcpos >>> OFFSETBITS; + } + + int lineNumber() { + return lineNumber(pos); + } + + int lineOffset(int lcpos) { + return lcpos & ((1 << OFFSETBITS) - 1); + } + + int lineOffset() { + return lineOffset(pos); + } + + /*============================================================== Environment */ + /** + * The number of errors and warnings + */ + public int nerrors; + public int nwarnings; + public static final I18NResourceBundle i18n + = I18NResourceBundle.getBundleForClass(Main.class); + + /** + * Error String + */ + String errorString(String err, Object arg1, Object arg2, Object arg3) { + String str = null; + + //str = getProperty(err); + str = i18n.getString(err); + if (str == null) { + return "error message '" + err + "' not found"; + } + + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if ((c == '%') && (i + 1 < str.length())) { + switch (str.charAt(++i)) { + case 's': + String arg = arg1.toString(); + for (int j = 0; j < arg.length(); j++) { + switch (c = arg.charAt(j)) { + case ' ': + case '\t': + case '\n': + case '\r': + buf.append((char) c); + break; + + default: + if ((c > ' ') && (c <= 255)) { + buf.append((char) c); + } else { + buf.append('\\'); + buf.append('u'); + buf.append(Integer.toString(c, 16)); + } + } + } + arg1 = arg2; + arg2 = arg3; + break; + + case '%': + buf.append('%'); + break; + + default: + buf.append('?'); + break; + } + } else { + buf.append((char) c); + } + } + // KTL + // Need to do message format to substitute args + String msg = buf.toString(); + MessageFormat form = new MessageFormat(msg); + Object args[] = {arg1, arg2, arg3}; + msg = form.format(args); + + return msg; + + } + + /** + * List of outstanding error messages + */ + ErrorMessage errors; + + /** + * Insert an error message in the list of outstanding error messages. The list is + * sorted on input position. + */ + void insertError(int where, String message) { + //output("ERR = " + message); + ErrorMessage msg = new ErrorMessage(where, message); + if (errors == null) { + errors = msg; + } else if (errors.where > where) { + msg.next = errors; + errors = msg; + } else { + ErrorMessage m = errors; + for (; (m.next != null) && (m.next.where <= where); m = m.next); + msg.next = m.next; + m.next = msg; + } + } + + /** + * Flush outstanding errors + */ + public void flushErrors() { + if (errors == null) { + traceln("flushErrors: errors == null"); + return; + } + + // Report the errors + for (ErrorMessage msg = errors; msg != null; msg = msg.next) { + int off = lineOffset(msg.where); + + int i, j; + for (i = off; (i > 0) && (data[i - 1] != '\n') && (data[i - 1] != '\r'); i--); + for (j = off; (j < data.length) && (data[j] != '\n') && (data[j] != '\r'); j++); + + outputln( String.format( "%s (%d:%d) %s", getSimpleInputFileName(), lineNumber(msg.where), off - i, msg.message)); + outputln(new String(data, i, j - i)); + + char strdata[] = new char[(off - i) + 1]; + for (j = i; j < off; j++) { + strdata[j - i] = (data[j] == '\t') ? '\t' : ' '; + } + strdata[off - i] = '^'; + outputln(new String(strdata)); + } + errors = null; + } + + /** + * Output a string. This can either be an error message or something for debugging. + * This should be used instead of print. + */ + public void output(String msg) { + int len = msg.length(); + for (int i = 0; i < len; i++) { + out.write(msg.charAt(i)); + } + out.flush(); + } + + /** + * Output a string. This can either be an error message or something for debugging. + * This should be used instead of println. + */ + public void outputln(String msg) { + output((msg == null ? "" : msg) + "\n"); + } + + /** + * Issue an error. source - the input source, usually a file name string offset - the + * offset in the source of the error err - the error number (as defined in this + * interface) arg1 - an optional argument to the error (null if not applicable) arg2 - + * a second optional argument to the error (null if not applicable) arg3 - a third + * optional argument to the error (null if not applicable) + */ + /** + * Issue an error + */ + public void error(int where, String err, Object arg1, Object arg2, Object arg3) { + String msg; + if (err.startsWith("warn.")) { + if (nowarn) { + return; + } + nwarnings++; + msg = "Warning: "; + } else { + err = "err." + err; + nerrors++; + msg = "Error: "; + } + msg = msg + errorString(err, arg1, arg2, arg3); + traceln(msg); + insertError(where, msg); + } + + public final void error(int where, String err, Object arg1, Object arg2) { + error(where, err, arg1, arg2, null); + } + + public final void error(int where, String err, Object arg1) { + error(where, err, arg1, null, null); + } + + public final void error(int where, String err) { + error(where, err, null, null, null); + } + + public final void error(String err, Object arg1, Object arg2, Object arg3) { + error(pos, err, arg1, arg2, arg3); + } + + public final void error(String err, Object arg1, Object arg2) { + error(pos, err, arg1, arg2, null); + } + + public final void error(String err, Object arg1) { + error(pos, err, arg1, null, null); + } + + public final void error(String err) { + error(pos, err, null, null, null); + } + + public final String errorStr(String err, Object arg1, Object arg2, Object arg3) { + return errorString(err, arg1, arg2, arg3); + } + + public final String errorStr(String err, Object arg1, Object arg2) { + return errorStr(err, arg1, arg2, null); + } + + public final String errorStr(String err, Object arg1) { + return errorStr(err, arg1, null, null); + } + + public final String errorStr(String err) { + return errorStr(err, null, null, null); + } + + /*============================================================== trace */ + public boolean isTraceEnabled() { + return traceFlag; + } + + public boolean isDebugEnabled() { + return debugInfoFlag; + } + + void trace(String message) { + if (traceFlag) { + output(message); + } + } + + void traceln(String message) { + if (traceFlag) { + outputln(message); + } + } + +} // end Environment diff --git a/test/lib/org/openjdk/asmtools/jasm/FieldData.java b/test/lib/org/openjdk/asmtools/jasm/FieldData.java new file mode 100644 index 00000000000..c4a6c262605 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/FieldData.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import org.openjdk.asmtools.jasm.Tables.AttrTag; +import java.io.IOException; + +/** + * field_info + */ +class FieldData extends MemberData { + + /* FieldData Fields */ + private ConstantPool.ConstValue_Pair nape; + private AttrData initValue; + + public FieldData(ClassData cls, int acc, ConstantPool.ConstValue_Pair nape) { + super(cls, acc); + this.nape = nape; + if (Modifiers.hasPseudoMod(acc)) { + createPseudoMod(); + } + } + + public ConstantPool.ConstValue_Pair getNameDesc() { + return nape; + } + + public void SetValue(Argument value_cpx) { + initValue = new CPXAttr(cls, AttrTag.ATT_ConstantValue.parsekey(), + value_cpx); + } + + @Override + protected DataVector getAttrVector() { + return getDataVector(initValue, syntheticAttr, deprecatedAttr, signatureAttr); + } + + public void write(CheckedDataOutputStream out) throws IOException, Parser.CompilerError { + out.writeShort(access); + out.writeShort(nape.left.arg); + out.writeShort(nape.right.arg); + DataVector attrs = getAttrVector(); + attrs.write(out); + } +} // end FieldData + diff --git a/test/lib/org/openjdk/asmtools/jasm/InnerClassData.java b/test/lib/org/openjdk/asmtools/jasm/InnerClassData.java new file mode 100644 index 00000000000..0afef3ea5a3 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/InnerClassData.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.io.IOException; + +/** + * + */ +class InnerClassData implements Data { + + int access; + ConstantPool.ConstCell name, innerClass, outerClass; + + public InnerClassData(int access, ConstantPool.ConstCell name, ConstantPool.ConstCell innerClass, ConstantPool.ConstCell outerClass) { + this.access = access; + this.name = name; + this.innerClass = innerClass; + this.outerClass = outerClass; + } + + @Override + public int getLength() { + return 8; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeShort(innerClass.arg); + if (outerClass.isSet()) { + out.writeShort(outerClass.arg); + } else { + out.writeShort(0); + } + if (name.isSet()) { + out.writeShort(name.arg); + } else { + out.writeShort(0); + } + out.writeShort(access); + } +}// end class InnerClassData + diff --git a/test/lib/org/openjdk/asmtools/jasm/Instr.java b/test/lib/org/openjdk/asmtools/jasm/Instr.java new file mode 100644 index 00000000000..0035308f628 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/Instr.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.io.IOException; + +import static org.openjdk.asmtools.jasm.OpcodeTables.*; + +/** + * + */ +class Instr { + + Instr next = null; + int pc; + int pos; + Opcode opc; + Argument arg; + Object arg2; // second or unusual argument + + public Instr(int pc, int pos, Opcode opc, Argument arg, Object arg2) { + this.pc = pc; + this.pos = pos; + this.opc = opc; + this.arg = arg; + this.arg2 = arg2; + } + + public Instr() { + } + + public void write(CheckedDataOutputStream out, Environment env) throws IOException { + OpcodeType type = opc.type(); + switch (type) { + case NORMAL: { + if (opc == Opcode.opc_bytecode) { + out.writeByte(arg.arg); + return; + } + out.writeByte(opc.value()); + int opcLen = opc.length(); + if (opcLen == 1) { + return; + } + + switch (opc) { + case opc_tableswitch: + ((SwitchTable) arg2).writeTableSwitch(out); + return; + case opc_lookupswitch: + ((SwitchTable) arg2).writeLookupSwitch(out); + return; + } + + int iarg; + try { + iarg = arg.arg; + } catch (NullPointerException e) { + throw new Parser.CompilerError(env.errorStr("comperr.instr.nullarg", opc.parsekey())); + } +//env.traceln("instr:"+opcNamesTab[opc]+" len="+opcLen+" arg:"+iarg); + switch (opc) { + case opc_jsr: + case opc_goto: + case opc_ifeq: + case opc_ifge: + case opc_ifgt: + case opc_ifle: + case opc_iflt: + case opc_ifne: + case opc_if_icmpeq: + case opc_if_icmpne: + case opc_if_icmpge: + case opc_if_icmpgt: + case opc_if_icmple: + case opc_if_icmplt: + case opc_if_acmpeq: + case opc_if_acmpne: + case opc_ifnull: + case opc_ifnonnull: + case opc_jsr_w: + case opc_goto_w: + iarg = iarg - pc; + break; + case opc_iinc: + iarg = (iarg << 8) | (((Argument) arg2).arg & 0xFF); + break; + case opc_invokeinterface: + iarg = ((iarg << 8) | (((Argument) arg2).arg & 0xFF)) << 8; + break; + case opc_invokedynamic: // JSR-292 + iarg = (iarg << 16); + break; + case opc_ldc: + if ((iarg & 0xFFFFFF00) != 0) { + throw new Parser.CompilerError( + env.errorStr("comperr.instr.arglong", opc.parsekey(), iarg)); + } + break; + } + switch (opcLen) { + case 1: + return; + case 2: + out.writeByte(iarg); + return; + case 3: + out.writeShort(iarg); + return; + case 4: // opc_multianewarray only + out.writeShort(iarg); + iarg = ((Argument) arg2).arg; + out.writeByte(iarg); + return; + case 5: + out.writeInt(iarg); + return; + default: + throw new Parser.CompilerError( + env.errorStr("comperr.instr.opclen", opc.parsekey())); + } + } + case WIDE: + out.writeByte(Opcode.opc_wide.value()); + out.writeByte(opc.value() & 0xFF); + out.writeShort(arg.arg); + if (opc == Opcode.opc_iinc_w) { + out.writeShort(((Argument) arg2).arg); + } + return; + case PRIVELEGED: + case NONPRIVELEGED: + out.writeByte(opc.value() >> 8); + out.writeByte(opc.value() & 0xFF); + return; + default: + throw new Parser.CompilerError( + env.errorStr("comperr.instr.opclen", opc.parsekey())); + } // end writeSpecCode + + } +} // end Instr + diff --git a/test/lib/org/openjdk/asmtools/jasm/JasmTokens.java b/test/lib/org/openjdk/asmtools/jasm/JasmTokens.java new file mode 100644 index 00000000000..a9b1c89f04c --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/JasmTokens.java @@ -0,0 +1,493 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.util.Arrays; +import java.util.EnumSet; +import java.util.Optional; + +/** + * + * JasmTokens + * + * This class contains tokens specific to parsing JASM syntax. + * + * The classes in JasmTokens are following a Singleton Pattern. These classes are Enums, + * and they are contained in private hash maps (lookup tables and reverse lookup tables). + * These hash maps all have public accessors, which clients use to look-up enums. + * + * Tokens in this table carry no external state, and are typically treated as constants. + * They do not need to be reset. + */ +public class JasmTokens { + + /*-------------------------------------------------------- */ + /* Marker: describes the type of Keyword */ + static public enum KeywordType { + TOKEN (0, "TOKEN"), + VALUE (1, "VALUE"), + JASMIDENTIFIER (2, "JASM"), + KEYWORD (3, "KEYWORD"); + + private final Integer value; + private final String printval; + + KeywordType(Integer val, String print) { + value = val; + printval = print; + } + + public String printval() { + return printval; + } + } + + + /*-------------------------------------------------------- */ + /* Marker - describes the type of token */ + /* this is rather cosmetic, no function currently. */ + static public enum TokenType { + MODIFIER (1, "Modifier"), + OPERATOR (2, "Operator"), + VALUE (3, "Value"), + TYPE (4, "Type"), + EXPRESSION (5, "Expression"), + STATEMENT (6, "Statement"), + DECLARATION (7, "Declaration"), + PUNCTUATION (8, "Punctuation"), + SPECIAL (9, "Special"), + JASM (10, "Jasm"), + MISC (11, "Misc"), + JASM_IDENT (12, "Jasm identifier"), + MODULE_NAME (13, "Module Name"), + TYPE_PATH_KIND (14, "Type path kind") // Table 4.7.20.2-A Interpretation of type_path_kind values + ; + + private final Integer value; + private final String printval; + + TokenType(Integer val, String print) { + value = val; + printval = print; + } + public String printval() { + return printval; + } + } + + public enum AnnotationType { + Visible("@+"), + Invisible("@-"), + VisibleType("@T+"), + InvisibleType("@T-"); + + private final String jasmPrefix; + + AnnotationType(String jasmPrefix) { + this.jasmPrefix = jasmPrefix; + } + + /** + * isAnnotationToken + * + * examines the beginning of a string to see if it starts with an annotation + * characters ('@+' = visible annotation, '@-' = invisible). + * + * @param str String to be analyzed + * @return True if the string starts with an annotation char. + */ + static public boolean isAnnotationToken(String str) { + return (str.startsWith(AnnotationType.Invisible.jasmPrefix) || + str.startsWith(AnnotationType.Visible.jasmPrefix)); + } + + /** + * isTypeAnnotationToken + * + * examines the beginning of a string to see if it starts with type annotation + * characters ('@T+' = visible type annotation, '@T-' = invisible). + * + * @param str String to be analyzed + * @return True if the string starts with an annotation char. + */ + static public boolean isTypeAnnotationToken(String str) { + return (str.startsWith(AnnotationType.InvisibleType.jasmPrefix) || + str.startsWith(AnnotationType.VisibleType.jasmPrefix)); + } + + /** + * isAnnotation + * + * examines the beginning of a string to see if it starts with an annotation character + * + * @param str String to be analyzed + * @return True if the string starts with an annotation char. + */ + static public boolean isAnnotation(String str) { + return (str.startsWith("@")); + } + + /** + * isInvisibleAnnotationToken + * + * examines the end of an annotation token to determine visibility ('+' = visible + * annotation, '-' = invisible). + * + * @param str String to be analyzed + * @return True if the token implies invisible annotation. + */ + static public boolean isInvisibleAnnotationToken(String str) { + return (str.endsWith("-")); + } + } + + /** + * Scanner Tokens (Definitive List) + */ + public enum Token { + EOF (-1, "EOF", "EOF", EnumSet.of(TokenType.MISC)), + COMMA (0, "COMMA", ",", EnumSet.of(TokenType.OPERATOR)), + ASSIGN (1, "ASSIGN", "=", EnumSet.of(TokenType.OPERATOR)), + + ASGMUL (2, "ASGMUL", "*=", EnumSet.of(TokenType.OPERATOR)), + ASGDIV (3, "ASGDIV", "/=", EnumSet.of(TokenType.OPERATOR)), + ASGREM (4, "ASGREM", "%=", EnumSet.of(TokenType.OPERATOR)), + ASGADD (5, "ASGADD", "+=", EnumSet.of(TokenType.OPERATOR)), + ASGSUB (6, "ASGSUB", "-=", EnumSet.of(TokenType.OPERATOR)), + ASGLSHIFT (7, "ASGLSHIFT", "<<=", EnumSet.of(TokenType.OPERATOR)), + ASGRSHIFT (8, "ASGRSHIFT", ">>=", EnumSet.of(TokenType.OPERATOR)), + ASGURSHIFT (9, "ASGURSHIFT", "<<<=", EnumSet.of(TokenType.OPERATOR)), + ASGBITAND (10, "ASGBITAND", "&=", EnumSet.of(TokenType.OPERATOR)), + ASGBITOR (11, "ASGBITOR", "|=", EnumSet.of(TokenType.OPERATOR)), + ASGBITXOR (12, "ASGBITXOR", "^=", EnumSet.of(TokenType.OPERATOR)), + + COND (13, "COND", "?:", EnumSet.of(TokenType.OPERATOR)), + OR (14, "OR", "||", EnumSet.of(TokenType.OPERATOR)), + AND (15, "AND", "&&", EnumSet.of(TokenType.OPERATOR)), + BITOR (16, "BITOR", "|", EnumSet.of(TokenType.OPERATOR)), + BITXOR (17, "BITXOR", "^", EnumSet.of(TokenType.OPERATOR)), + BITAND (18, "BITAND", "&", EnumSet.of(TokenType.OPERATOR)), + NE (19, "NE", "!=", EnumSet.of(TokenType.OPERATOR)), + EQ (20, "EQ", "==", EnumSet.of(TokenType.OPERATOR)), + GE (21, "GE", ">=", EnumSet.of(TokenType.OPERATOR)), + GT (22, "GT", ">", EnumSet.of(TokenType.OPERATOR)), + LE (23, "LE", "<=", EnumSet.of(TokenType.OPERATOR)), + LT (24, "LT", "<", EnumSet.of(TokenType.OPERATOR)), + INSTANCEOF (25, "INSTANCEOF", "instanceof", EnumSet.of(TokenType.OPERATOR, TokenType.MODULE_NAME)), + LSHIFT (26, "LSHIFT", "<<", EnumSet.of(TokenType.OPERATOR)), + RSHIFT (27, "RSHIFT", ">>", EnumSet.of(TokenType.OPERATOR)), + URSHIFT (28, "URSHIFT", "<<<", EnumSet.of(TokenType.OPERATOR)), + ADD (29, "ADD", "+", EnumSet.of(TokenType.OPERATOR)), + SUB (30, "SUB", "-", EnumSet.of(TokenType.OPERATOR)), + DIV (31, "DIV", "/", EnumSet.of(TokenType.OPERATOR)), + REM (32, "REM", "%", EnumSet.of(TokenType.OPERATOR)), + MUL (33, "MUL", "*", EnumSet.of(TokenType.OPERATOR)), + CAST (34, "CAST", "cast", EnumSet.of(TokenType.OPERATOR, TokenType.MODULE_NAME)), + POS (35, "POS", "+", EnumSet.of(TokenType.OPERATOR)), + NEG (36, "NEG", "-", EnumSet.of(TokenType.OPERATOR)), + NOT (37, "NOT", "!", EnumSet.of(TokenType.OPERATOR)), + BITNOT (38, "BITNOT", "~", EnumSet.of(TokenType.OPERATOR)), + PREINC (39, "PREINC", "++", EnumSet.of(TokenType.OPERATOR)), + PREDEC (40, "PREDEC", "--", EnumSet.of(TokenType.OPERATOR)), + NEWARRAY (41, "NEWARRAY", "new", EnumSet.of(TokenType.OPERATOR, TokenType.MODULE_NAME)), + NEWINSTANCE (42, "NEWINSTANCE", "new", EnumSet.of(TokenType.OPERATOR, TokenType.MODULE_NAME)), + NEWFROMNAME (43, "NEWFROMNAME", "new", EnumSet.of(TokenType.OPERATOR, TokenType.MODULE_NAME)), + POSTINC (44, "POSTINC", "++", EnumSet.of(TokenType.OPERATOR)), + POSTDEC (45, "POSTDEC", "--", EnumSet.of(TokenType.OPERATOR)), + FIELD (46, "FIELD", "field", EnumSet.of(TokenType.OPERATOR, TokenType.MODULE_NAME)), + METHOD (47, "METHOD", "method", EnumSet.of(TokenType.OPERATOR, TokenType.MODULE_NAME)), + ARRAYACCESS (48, "ARRAYACCESS", "[]", EnumSet.of(TokenType.OPERATOR)), + NEW (49, "NEW", "new", EnumSet.of(TokenType.OPERATOR, TokenType.MODULE_NAME)), + INC (50, "INC", "++", EnumSet.of(TokenType.OPERATOR)), + DEC (51, "DEC", "--", EnumSet.of(TokenType.OPERATOR)), + + CONVERT (55, "CONVERT", "convert", EnumSet.of(TokenType.OPERATOR, TokenType.MODULE_NAME)), + EXPR (56, "EXPR", "expr", EnumSet.of(TokenType.OPERATOR, TokenType.MODULE_NAME)), + ARRAY (57, "ARRAY", "array", EnumSet.of(TokenType.OPERATOR, TokenType.MODULE_NAME)), + GOTO (58, "GOTO", "goto", EnumSet.of(TokenType.OPERATOR, TokenType.MODULE_NAME)), + + /* + * Value tokens + */ + IDENT (60, "IDENT", "Identifier", EnumSet.of(TokenType.VALUE, TokenType.MODULE_NAME, TokenType.JASM_IDENT), KeywordType.VALUE), + BOOLEANVAL (61, "BOOLEANVAL", "Boolean", EnumSet.of(TokenType.VALUE, TokenType.MODULE_NAME), KeywordType.VALUE), + BYTEVAL (62, "BYTEVAL", "Byte", EnumSet.of(TokenType.VALUE, TokenType.MODULE_NAME)), + CHARVAL (63, "CHARVAL", "Char", EnumSet.of(TokenType.VALUE, TokenType.MODULE_NAME)), + SHORTVAL (64, "SHORTVAL", "Short", EnumSet.of(TokenType.VALUE, TokenType.MODULE_NAME)), + INTVAL (65, "INTVAL", "Integer", EnumSet.of(TokenType.VALUE, TokenType.MODULE_NAME), KeywordType.VALUE), + LONGVAL (66, "LONGVAL", "Long", EnumSet.of(TokenType.VALUE, TokenType.MODULE_NAME), KeywordType.VALUE), + FLOATVAL (67, "FLOATVAL", "Float", EnumSet.of(TokenType.VALUE, TokenType.MODULE_NAME), KeywordType.VALUE), + DOUBLEVAL (68, "DOUBLEVAL", "Double", EnumSet.of(TokenType.VALUE, TokenType.MODULE_NAME), KeywordType.VALUE), + STRINGVAL (69, "STRINGVAL", "String", EnumSet.of(TokenType.VALUE, TokenType.MODULE_NAME), KeywordType.VALUE), + + /* + * Type keywords + */ + BYTE (70, "BYTE", "byte", EnumSet.of(TokenType.TYPE, TokenType.MODULE_NAME )), + CHAR (71, "CHAR", "char", EnumSet.of(TokenType.TYPE, TokenType.MODULE_NAME )), + SHORT (72, "SHORT", "short", EnumSet.of(TokenType.TYPE, TokenType.MODULE_NAME )), + INT (73, "INT", "int", EnumSet.of(TokenType.TYPE, TokenType.MODULE_NAME )), + LONG (74, "LONG", "long", EnumSet.of(TokenType.TYPE, TokenType.MODULE_NAME )), + FLOAT (75, "FLOAT", "float", EnumSet.of(TokenType.TYPE, TokenType.MODULE_NAME)), + DOUBLE (76, "DOUBLE", "double", EnumSet.of(TokenType.TYPE, TokenType.MODULE_NAME)), + VOID (77, "VOID", "void", EnumSet.of(TokenType.TYPE, TokenType.MODULE_NAME)), + BOOLEAN (78, "BOOLEAN", "boolean", EnumSet.of(TokenType.TYPE, TokenType.MODULE_NAME)), + + /* + * Expression keywords + */ + TRUE (80, "TRUE", "true", EnumSet.of(TokenType.EXPRESSION, TokenType.MODULE_NAME )), + FALSE (81, "FALSE", "false", EnumSet.of(TokenType.EXPRESSION, TokenType.MODULE_NAME )), + THIS (82, "THIS", "this", EnumSet.of(TokenType.EXPRESSION, TokenType.MODULE_NAME )), + SUPER (83, "SUPER", "super", EnumSet.of(TokenType.MODIFIER, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + NULL (84, "NULL", "null", EnumSet.of(TokenType.EXPRESSION, TokenType.MODULE_NAME )), + + /* + * Statement keywords + */ + IF (90, "IF", "if", EnumSet.of(TokenType.STATEMENT, TokenType.MODULE_NAME )), + ELSE (91, "ELSE", "else", EnumSet.of(TokenType.STATEMENT, TokenType.MODULE_NAME )), + FOR (92, "FOR", "for", EnumSet.of(TokenType.STATEMENT, TokenType.MODULE_NAME )), + WHILE (93, "WHILE", "while", EnumSet.of(TokenType.STATEMENT, TokenType.MODULE_NAME )), + DO (94, "DO", "do", EnumSet.of(TokenType.STATEMENT, TokenType.MODULE_NAME )), + SWITCH (95, "SWITCH", "switch", EnumSet.of(TokenType.STATEMENT, TokenType.MODULE_NAME )), + CASE (96, "CASE", "case", EnumSet.of(TokenType.STATEMENT, TokenType.MODULE_NAME )), + DEFAULT (97, "DEFAULT", "default", EnumSet.of(TokenType.STATEMENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + BREAK (98, "BREAK", "break", EnumSet.of(TokenType.STATEMENT, TokenType.MODULE_NAME )), + CONTINUE (99, "CONTINUE", "continue", EnumSet.of(TokenType.STATEMENT, TokenType.MODULE_NAME )), + RETURN (100, "RETURN", "return", EnumSet.of(TokenType.STATEMENT, TokenType.MODULE_NAME )), + TRY (101, "TRY", "try", EnumSet.of(TokenType.STATEMENT, TokenType.MODULE_NAME )), + + CATCH (102, "CATCH", "catch", EnumSet.of(TokenType.STATEMENT, TokenType.MODULE_NAME )), + FINALLY (103, "FINALLY", "finally", EnumSet.of(TokenType.STATEMENT, TokenType.MODULE_NAME )), + THROW (104, "THROW", "throw", EnumSet.of(TokenType.STATEMENT, TokenType.MODULE_NAME )), + STAT (105, "STAT", "stat", EnumSet.of(TokenType.STATEMENT, TokenType.MODULE_NAME )), + EXPRESSION (106, "EXPRESSION", "expression", EnumSet.of(TokenType.STATEMENT, TokenType.MODULE_NAME )), + DECLARATION (107, "DECLARATION", "declaration", EnumSet.of(TokenType.STATEMENT, TokenType.MODULE_NAME )), + VARDECLARATION (108, "VARDECLARATION", "vdeclaration", EnumSet.of(TokenType.STATEMENT, TokenType.MODULE_NAME )), + + /* + * Declaration keywords + */ + IMPORT (110, "IMPORT", "import", EnumSet.of(TokenType.DECLARATION, TokenType.MODULE_NAME )), + CLASS (111, "CLASS", "class", EnumSet.of(TokenType.DECLARATION, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + EXTENDS (112, "EXTENDS", "extends", EnumSet.of(TokenType.DECLARATION, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + IMPLEMENTS (113, "IMPLEMENTS", "implements", EnumSet.of(TokenType.DECLARATION, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + INTERFACE (114, "INTERFACE", "interface", EnumSet.of(TokenType.DECLARATION, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + PACKAGE (115, "PACKAGE", "package", EnumSet.of(TokenType.DECLARATION, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + ENUM (116, "ENUM", "enum", EnumSet.of(TokenType.DECLARATION, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + MANDATED (117, "MANDATED", "mandated", EnumSet.of(TokenType.DECLARATION, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + THROWS (118, "THROWS", "throws", EnumSet.of(TokenType.DECLARATION, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + + /* + * Modifier keywords + */ + ANNOTATION_ACCESS (119, "ANNOTATION_ACCESS", "annotation", EnumSet.of(TokenType.MODIFIER, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + PRIVATE (120, "PRIVATE", "private", EnumSet.of(TokenType.MODIFIER, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + PUBLIC (121, "PUBLIC", "public", EnumSet.of(TokenType.MODIFIER, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + PROTECTED (122, "PROTECTED", "protected", EnumSet.of(TokenType.MODIFIER, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + CONST (123, "CONST", "const", EnumSet.of(TokenType.DECLARATION, TokenType.MODULE_NAME), KeywordType.KEYWORD), + STATIC (124, "STATIC", "static", EnumSet.of(TokenType.MODIFIER, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + TRANSIENT (125, "TRANSIENT", "transient", EnumSet.of(TokenType.MODIFIER, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + SYNCHRONIZED (126, "SYNCHRONIZED", "synchronized", EnumSet.of(TokenType.MODIFIER, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + NATIVE (127, "NATIVE", "native", EnumSet.of(TokenType.MODIFIER, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + FINAL (128, "FINAL", "final", EnumSet.of(TokenType.MODIFIER, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + VOLATILE (129, "VOLATILE", "volatile", EnumSet.of(TokenType.MODIFIER, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + ABSTRACT (130, "ABSTRACT", "abstract", EnumSet.of(TokenType.MODIFIER, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + TRANSITIVE (131, "TRANSITIVE", "transitive", EnumSet.of(TokenType.MODIFIER, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + OPEN (132, "OPEN", "open", EnumSet.of(TokenType.MODIFIER, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + + /* + * Punctuation + */ + AT_SIGN (133, "AT", ";", EnumSet.of(TokenType.PUNCTUATION), KeywordType.VALUE), + SEMICOLON (134, "SEMICOLON", ";", EnumSet.of(TokenType.PUNCTUATION), KeywordType.VALUE), + COLON (135, "COLON", ":", EnumSet.of(TokenType.PUNCTUATION), KeywordType.VALUE), + QUESTIONMARK (136, "QUESTIONMARK", "?", EnumSet.of(TokenType.PUNCTUATION)), + LBRACE (137, "LBRACE", "{", EnumSet.of(TokenType.PUNCTUATION), KeywordType.VALUE), + RBRACE (138, "RBRACE", "}", EnumSet.of(TokenType.PUNCTUATION), KeywordType.VALUE), + LPAREN (139, "LPAREN", "(", EnumSet.of(TokenType.PUNCTUATION)), + RPAREN (140, "RPAREN", ")", EnumSet.of(TokenType.PUNCTUATION)), + LSQBRACKET (141, "LSQBRACKET", "[", EnumSet.of(TokenType.PUNCTUATION)), + RSQBRACKET (142, "RSQBRACKET", "]", EnumSet.of(TokenType.PUNCTUATION)), + + ESCAPED_COLON (201, "ESCCOLON", "\\:", EnumSet.of(TokenType.PUNCTUATION, TokenType.MODULE_NAME)), + ESCAPED_ATSIGH (202, "ESCATSIGH", "\\@", EnumSet.of(TokenType.PUNCTUATION, TokenType.MODULE_NAME)), + ESCAPED_BACKSLASH (203, "ESCBACKSLASH", "\\\\", EnumSet.of(TokenType.PUNCTUATION, TokenType.MODULE_NAME)), + /* + * Special tokens + */ + ERROR (145, "ERROR", "error", EnumSet.of(TokenType.MODIFIER, TokenType.MODULE_NAME)), + COMMENT (146, "COMMENT", "comment", EnumSet.of(TokenType.MODIFIER, TokenType.MODULE_NAME)), + TYPE (147, "TYPE", "type", EnumSet.of(TokenType.MODIFIER, TokenType.MODULE_NAME)), + LENGTH (148, "LENGTH", "length", EnumSet.of(TokenType.DECLARATION, TokenType.MODULE_NAME )), + INLINERETURN (149, "INLINERETURN", "inline-return", EnumSet.of(TokenType.MODIFIER)), + INLINEMETHOD (150, "INLINEMETHOD", "inline-method", EnumSet.of(TokenType.MODIFIER)), + INLINENEWINSTANCE (151, "INLINENEWINSTANCE", "inline-new",EnumSet.of(TokenType.MODIFIER)), + + /* + * Added for jasm + */ + METHODREF (152, "METHODREF", "Method", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + FIELDREF (153, "FIELD", "Field", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + STACK (154, "STACK", "stack", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + LOCAL (155, "LOCAL", "locals", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + CPINDEX (156, "CPINDEX", "CPINDEX", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME )), + CPNAME (157, "CPNAME", "CPName", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME )), + SIGN (158, "SIGN", "SIGN", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME )), + BITS (159, "BITS", "bits", EnumSet.of(TokenType.MISC, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + + INF (160, "INF", "Inf", "Infinity", EnumSet.of(TokenType.MISC, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + NAN (161, "NAN", "NaN", EnumSet.of(TokenType.MISC, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + + INNERCLASS (162, "INNERCLASS", "InnerClass", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + OF (163, "OF", "of", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + SYNTHETIC (164, "SYNTHETIC", "synthetic", EnumSet.of(TokenType.MODIFIER, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + STRICT (165, "STRICT", "strict", EnumSet.of(TokenType.MODIFIER, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + DEPRECATED (166, "DEPRECATED", "deprecated", EnumSet.of(TokenType.MODIFIER, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + VERSION (167, "VERSION", "version", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + MODULE (168, "MODULE", "module", EnumSet.of(TokenType.DECLARATION, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + ANNOTATION (169, "ANNOTATION", "@", EnumSet.of(TokenType.MISC, TokenType.MODULE_NAME )), + PARAM_NAME (173, "PARAM_NAME", "#", EnumSet.of(TokenType.MISC, TokenType.MODULE_NAME )), + + VARARGS (170, "VARARGS", "varargs", EnumSet.of(TokenType.MODIFIER, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + BRIDGE (171, "BRIDGE", "bridge", EnumSet.of(TokenType.MODIFIER, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + + // Declaration keywords + BOOTSTRAPMETHOD (172, "BOOTSTRAPMETHOD", "BootstrapMethod", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + NESTHOST (173, "NESTHOST", "NestHost", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + NESTMEMBERS (174, "NESTMEMBERS", "NestMembers", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + // + RECORD (175, "RECORD", "Record", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + COMPONENT (176, "COMPONENT", "Component", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + // + PERMITTEDSUBCLASSES (177, "PERMITTEDSUBCLASSES", "PermittedSubclasses", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + + //Module statements + REQUIRES (180, "REQUIRES", "requires", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + EXPORTS (182, "EXPORTS", "exports", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + TO (183, "TO", "to", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + USES (184, "USES", "uses", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + PROVIDES (185, "PROVIDES", "provides", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + WITH (186, "WITH", "with", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + OPENS (187, "OPENS", "opens", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + + // Table 4.7.20.2-1 type_path_kind + ARRAY_TYPEPATH (188, TypeAnnotationTypes.EPathKind.ARRAY.parseKey(), TypeAnnotationTypes.EPathKind.ARRAY.parseKey(), + EnumSet.of(TokenType.TYPE_PATH_KIND, TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + INNER_TYPE_TYPEPATH (189, TypeAnnotationTypes.EPathKind.INNER_TYPE.parseKey(), TypeAnnotationTypes.EPathKind.INNER_TYPE.parseKey(), + EnumSet.of(TokenType.TYPE_PATH_KIND, TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + WILDCARD_TYPEPATH (190, TypeAnnotationTypes.EPathKind.WILDCARD.parseKey(), TypeAnnotationTypes.EPathKind.WILDCARD.parseKey(), + EnumSet.of(TokenType.TYPE_PATH_KIND, TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + TYPE_ARGUMENT_TYPEPATH (191, TypeAnnotationTypes.EPathKind.TYPE_ARGUMENT.parseKey(), TypeAnnotationTypes.EPathKind.TYPE_ARGUMENT.parseKey(), + EnumSet.of(TokenType.TYPE_PATH_KIND, TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + + // Valhalla + VALUE (200, "VALUE", "value", EnumSet.of(TokenType.MODIFIER, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + PRIMITIVE (202, "PRIMITIVE", "primitive", EnumSet.of(TokenType.MODIFIER, TokenType.MODULE_NAME ), KeywordType.KEYWORD), + PRELOAD (203, "PRELOAD", "Preload", EnumSet.of(TokenType.DECLARATION, TokenType.JASM_IDENT, TokenType.MODULE_NAME ), KeywordType.KEYWORD); + + + final static EnumSet ALL_TOKENS = EnumSet.allOf(Token.class); + // Misc Keywords + final private Integer value; // 160 + final private String printval; // INF + final private String parsekey; // inf + final private String alias; // Infinity + final private EnumSet tokenType; // TokenType.MISC, TokenType.MODULE_NAME + final private KeywordType key_type; // KeywordType.KEYWORD + + public static Optional get(String parsekey, KeywordType ktype) { + return ALL_TOKENS.stream(). + filter(t->t.key_type == ktype). + filter(t->t.parsekey.equals(parsekey) || ( t.alias != null && t.alias.equals(parsekey))). + findFirst(); + } + + /** + * Checks that this enum element is in an enum list + * + * @param tokens the list of enum elements for checking + * @return true if a tokens list contains this enum element + */ + public boolean in(Token... tokens) { + return (tokens == null) ? false : Arrays.asList(tokens).contains(this); + } + + // By default, if a KeywordType is not specified, it has the value 'TOKEN' + Token(Integer val, String print, String parsekey, EnumSet ttype) { + this(val, print, parsekey, null, ttype, KeywordType.TOKEN); + } + + Token(Integer val, String print, String parsekey, String als, EnumSet ttype) { + this(val, print, parsekey, als, ttype, KeywordType.TOKEN); + } + + Token(Integer val, String print, String parsekey, EnumSet ttype, KeywordType ktype) { + this(val, print, parsekey, null, ttype, ktype); + } + + Token(Integer val, String print, String parsekey, String als, EnumSet ttype, KeywordType ktype) { + this.value = val; + this.printval = print; + this.parsekey = parsekey; + this.tokenType = ttype; + this.key_type = ktype; + this.alias = als; + } + + public String printValue() { + return printval; + } + + public String parseKey() { + return parsekey; + } + + public int value() { + return value; + } + + public boolean possibleJasmIdentifier() { + return tokenType.contains(TokenType.JASM_IDENT); + } + + public boolean possibleModuleName() { return tokenType.contains(TokenType.MODULE_NAME) && !tokenType.contains(TokenType.PUNCTUATION); } + + /** + * Checks a token belonging to the table: Table 4.7.20.2-A. Interpretation of type_path_kind values + * + * @return true if token is ARRAY, INNER_TYPE, WILDCARD or TYPE_ARGUMENT + */ + public boolean possibleTypePathKind() { return tokenType.contains(TokenType.TYPE_PATH_KIND); } + + @Override + public String toString() { + return "<" + printval + "> [" + value + "]"; + } + } + + public static Token keyword_token_ident(String idValue) { + return Token.get(idValue,KeywordType.KEYWORD).orElse(Token.IDENT); + } +} diff --git a/test/lib/org/openjdk/asmtools/jasm/Main.java b/test/lib/org/openjdk/asmtools/jasm/Main.java new file mode 100644 index 00000000000..2b99f6eefb9 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/Main.java @@ -0,0 +1,330 @@ +/* + * Copyright (c) 1996, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import static org.openjdk.asmtools.jasm.CFVersion.DEFAULT_MAJOR_VERSION; +import static org.openjdk.asmtools.jasm.CFVersion.DEFAULT_MINOR_VERSION; + +import org.openjdk.asmtools.common.Tool; +import org.openjdk.asmtools.util.I18NResourceBundle; +import org.openjdk.asmtools.util.ProductInfo; + +import java.io.*; +import java.util.ArrayList; + +/** + * + * + */ +public class Main extends Tool { + + public static final I18NResourceBundle i18n + = I18NResourceBundle.getBundleForClass(Main.class); + + private File destDir = null; + private boolean traceFlag = false; + private long tm = System.currentTimeMillis(); + private ArrayList v = new ArrayList<>(); + private boolean nowrite = false; + private boolean nowarn = false; + private boolean strict = false; + private String props = null; + private int nwarnings = 0; + private CFVersion cfv = new CFVersion(); + private int bytelimit = 0; + private boolean debugScanner = false; + private boolean debugMembers = false; + private boolean debugCP = false; + private boolean debugAnnot = false; + private boolean debugInstr = false; + + + public Main(PrintWriter out, String programName) { + super(out, programName); + printCannotReadMsg = (fname) -> + error( i18n.getString("jasm.error.cannot_read", fname)); + } + + public Main(PrintStream out, String program) { + this(new PrintWriter(out), program); + } + + @Override + public void usage() { + println(i18n.getString("jasm.usage")); + println(i18n.getString("jasm.opt.d")); + println(i18n.getString("jasm.opt.g")); + println(i18n.getString("jasm.opt.v")); + println(i18n.getString("jasm.opt.nowrite")); + println(i18n.getString("jasm.opt.nowarn")); + println(i18n.getString("jasm.opt.strict")); + println(i18n.getString("jasm.opt.cv", DEFAULT_MAJOR_VERSION, DEFAULT_MINOR_VERSION)); + println(i18n.getString("jasm.opt.version")); + } + + /** + * Run the compiler + */ + private synchronized boolean parseArgs(String argv[]) { + // Parse arguments + boolean frozenCFV = false; + for (int i = 0; i < argv.length; i++) { + String arg = argv[i]; + switch (arg) { + case "-v": + traceFlag = true; + break; + case "-g": + super.DebugFlag = () -> true; + break; + case "-nowrite": + nowrite = true; + break; + case "-strict": + strict = true; + break; + case "-nowarn": + nowarn = true; + break; + case "-version": + println(ProductInfo.FULL_VERSION); + break; + case "-d": + if ((i + 1) >= argv.length) { + error(i18n.getString("jasm.error.d_requires_argument")); + usage(); + return false; + } + destDir = new File(argv[++i]); + if (!destDir.exists()) { + error(i18n.getString("jasm.error.does_not_exist", destDir.getPath())); + return false; + } + break; + // non-public options + case "-XdScanner": + debugScanner = true; + break; + case "-XdMember": + debugMembers = true; + break; + case "-XdCP": + debugCP = true; + break; + case "-XdInstr": + debugInstr = true; + break; + case "-XdAnnot": + debugAnnot = true; + break; + case "-XdAll": + debugScanner = true; + debugMembers = true; + debugCP = true; + debugInstr = true; + debugAnnot = true; + break; + case "-Xdlimit": + // parses file until the specified byte number + if (i + 1 > argv.length) { + println(" Error: Unspecified byte-limit"); + return false; + } else { + i++; + String bytelimstr = argv[i]; + bytelimit = 0; + try { + bytelimit = Integer.parseInt(bytelimstr); + } catch (NumberFormatException e) { + println(" Error: Unspecified byte-limit"); + return false; + } + } + break; + case "-fixcv": + // overrides cf version if it's defined in the source file. + frozenCFV = true; + // public options + case "-cv": + if ((i + 1) >= argv.length) { + error(i18n.getString("jasm.error.cv_requires_arg")); + usage(); + return false; + } + String[] versions = {"", ""}; // workaround for String.split() + int index = argv[++i].indexOf("."); // + if (index != -1) { // + versions[0] = argv[i].substring(0, index); // + versions[1] = argv[i].substring(index + 1); // + } // + if (versions.length != 2) { + error(i18n.getString("jasm.error.invalid_major_minor_param")); + usage(); + return false; + } + try { + cfv = new CFVersion(frozenCFV, Short.parseShort(versions[0]), Short.parseShort(versions[1]) ); + } catch (NumberFormatException e) { + error(i18n.getString("jasm.error.invalid_major_minor_param")); + usage(); + return false; + } + break; + default: + if (arg.startsWith("-")) { + error(i18n.getString("jasm.error.invalid_option", arg)); + usage(); + return false; + } else { + v.add(argv[i]); + } + break; + } + } + if (v.size() == 0) { + usage(); + return false; + } + if (strict) { + nowarn = false; + } + return true; + } + + private void reset() { + destDir = null; + traceFlag = false; + super.DebugFlag = () -> false; + System.currentTimeMillis(); + v = new ArrayList<>(); + nowrite = false; + nowarn = false; + strict = false; + props = null; + nwarnings = 0; + bytelimit = 0; + } + + /** + * Run the compiler + */ + public synchronized boolean compile(String argv[]) { + // Reset the state of all objs + reset(); + + boolean validArgs = parseArgs(argv); + if (!validArgs) { + return false; + } + // compile all input files + Environment sf = null; + try { + for (String inpname : v) { + Parser p; + + DataInputStream dataInputStream = getDataInputStream(inpname); + if( dataInputStream == null ) { + nerrors++; + continue; + } + sf = new Environment(dataInputStream, inpname, out, nowarn); + sf.traceFlag = traceFlag; + sf.debugInfoFlag = DebugFlag.getAsBoolean(); + p = new Parser(sf, cfv.clone() ); + p.setDebugFlags(debugScanner, debugMembers, debugCP, debugAnnot, debugInstr); + p.parseFile(); + + nerrors += sf.nerrors; + nwarnings += sf.nwarnings; + if (nowrite || (nerrors > 0)) { + sf.flushErrors(); + continue; + } + try { + ClassData[] clsData = p.getClassesData(); + for (int i = 0; i < clsData.length; i++) { + ClassData cd = clsData[i]; + if (bytelimit > 0) { + cd.setByteLimit(bytelimit); + } + cd.write(destDir); + } + } catch (IOException ex) { + if (bytelimit > 0) { + // IO Error thrown from user-specified byte count + ex.printStackTrace(); + error("UserSpecified byte-limit at byte[" + bytelimit + "]: " + + ex.getMessage() + "\n" + + inpname + ": [" + sf.lineNumber() + ", " + sf.lineOffset() + "]"); + } else { + String er = i18n.getString("jasm.error.cannot_write", ex.getMessage()); + error(er + "\n" + inpname + ": [" + sf.lineNumber() + ", " + sf.lineOffset() + "]"); + } + } + sf.flushErrors(); // possible errors from write() + } + } catch (Error ee) { + if (DebugFlag.getAsBoolean()) { + ee.printStackTrace(); + } + String er = ee.getMessage() + "\n" + i18n.getString("jasm.error.fatal_error"); + error(er + "\n" + sf.getInputFileName() + ": [" + sf.lineNumber() + ", " + sf.lineOffset() + "]"); + } catch (Exception ee) { + if (DebugFlag.getAsBoolean()) { + ee.printStackTrace(); + } + String er = ee.getMessage() + "\n" + ee.getMessage() + "\n" + i18n.getString("jasm.error.fatal_exception"); + error(er + "\n" + sf.getInputFileName() + ": [" + sf.lineNumber() + ", " + sf.lineOffset() + "]"); + } + + boolean errs = nerrors > 0; + boolean warns = (nwarnings > 0) && (!nowarn); + boolean errsOrWarns = errs || warns; + if (!errsOrWarns) { + return true; + } + if (errs) { + out.print(nerrors > 1 ? (nerrors + " errors") : "1 error"); + } + if (errs && warns) { + out.print(", "); + } + if (warns) { + out.print(nwarnings > 1 ? (nwarnings + " warnings") : "1 warning"); + } + println(); + if (strict) { + return !errsOrWarns; + } else { + return !errs; + } + } + + /** + * main program + */ + public static void main(String argv[]) { + Main compiler = new Main(new PrintWriter(System.out), "jasm"); + System.exit(compiler.compile(argv) ? 0 : 1); + } +} diff --git a/test/lib/org/openjdk/asmtools/jasm/MemberData.java b/test/lib/org/openjdk/asmtools/jasm/MemberData.java new file mode 100644 index 00000000000..17aece65307 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/MemberData.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import static org.openjdk.asmtools.jasm.RuntimeConstants.DEPRECATED_ATTRIBUTE; +import static org.openjdk.asmtools.jasm.RuntimeConstants.SYNTHETIC_ATTRIBUTE; +import org.openjdk.asmtools.jasm.Tables.AttrTag; +import java.util.ArrayList; + +/** + * The common base structure for field_info, method_info, and component_info + */ +abstract public class MemberData { + + protected int access; + protected AttrData syntheticAttr, deprecatedAttr; + protected DataVectorAttr annotAttrVis = null; + protected DataVectorAttr annotAttrInv = null; + protected DataVectorAttr type_annotAttrVis = null; + protected DataVectorAttr type_annotAttrInv = null; + protected ClassData cls; + protected AttrData signatureAttr; + + public MemberData(ClassData cls, int access) { + this.cls = cls; + init(access); + } + + public MemberData(ClassData cls) { + this.cls = cls; + } + + public void init(int access) { + this.access = access; + } + + public void createPseudoMod() { + // If a member has a Pseudo-modifier + + // create the appropriate marker attributes, + // and clear the PseudoModifiers from the access flags. + if (Modifiers.isSyntheticPseudoMod(access)) { + syntheticAttr = new AttrData(cls, AttrTag.ATT_Synthetic.parsekey()); + access &= ~SYNTHETIC_ATTRIBUTE; + } + if (Modifiers.isDeprecatedPseudoMod(access)) { + deprecatedAttr = new AttrData(cls, AttrTag.ATT_Deprecated.parsekey()); + access &= ~DEPRECATED_ATTRIBUTE; + } + } + + public void setSignatureAttr(ConstantPool.ConstCell value_cpx) { + signatureAttr = new CPXAttr(cls, Tables.AttrTag.ATT_Signature.parsekey(), value_cpx); + } + + protected abstract DataVector getAttrVector(); + + protected final DataVector getDataVector(Data... extraAttrs) { + DataVector attrs = new DataVector(); + for( Data extra : extraAttrs ) { + if (extra != null) { + attrs.add(extra); + } + } + // common set for [ FieldData, MethodData, RecordData ] + if (annotAttrVis != null) { + attrs.add(annotAttrVis); + } + if (annotAttrInv != null) { + attrs.add(annotAttrInv); + } + if (type_annotAttrVis != null) { + attrs.add(type_annotAttrVis); + } + if (type_annotAttrInv != null) { + attrs.add(type_annotAttrInv); + } + return attrs; + } + + public void addAnnotations(ArrayList list) { + for (AnnotationData item : list) { + boolean invisible = item.invisible; + if (item instanceof TypeAnnotationData) { + // Type Annotations + TypeAnnotationData ta = (TypeAnnotationData) item; + if (invisible) { + if (type_annotAttrInv == null) { + type_annotAttrInv = new DataVectorAttr(cls, + AttrTag.ATT_RuntimeInvisibleTypeAnnotations.parsekey()); + } + type_annotAttrInv.add(ta); + } else { + if (type_annotAttrVis == null) { + type_annotAttrVis = new DataVectorAttr(cls, + AttrTag.ATT_RuntimeVisibleTypeAnnotations.parsekey()); + } + type_annotAttrVis.add(ta); + } + } else { + // Regular Annotations + if (invisible) { + if (annotAttrInv == null) { + annotAttrInv = new DataVectorAttr(cls, + AttrTag.ATT_RuntimeInvisibleAnnotations.parsekey()); + } + annotAttrInv.add(item); + } else { + if (annotAttrVis == null) { + annotAttrVis = new DataVectorAttr(cls, + AttrTag.ATT_RuntimeVisibleAnnotations.parsekey()); + } + annotAttrVis.add(item); + } + } + } + } +} diff --git a/test/lib/org/openjdk/asmtools/jasm/MethodData.java b/test/lib/org/openjdk/asmtools/jasm/MethodData.java new file mode 100644 index 00000000000..f9884710098 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/MethodData.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import org.openjdk.asmtools.jasm.Tables.AttrTag; +import org.openjdk.asmtools.jasm.ConstantPool.ConstCell; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; + +/** + * + */ +class MethodData extends MemberData { + + /** + * MethodParamData + */ + class ParamNameData implements Data { + + int access; + ConstCell name; + + public ParamNameData(int access, ConstCell name) { + this.access = access; + this.name = name; + } + + @Override + public int getLength() { + return 4; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + int nm = 0; + int ac = 0; + if (name != null) { + nm = name.arg; + ac = access; + } + out.writeShort(nm); + out.writeShort(ac); + } + }// end class MethodParamData + + /** + * Used to store Parameter Arrays (as attributes) + */ + static public class DataPArrayAttr extends AttrData implements Constants { + + TreeMap> elements; // Data + int paramsTotal; + + public DataPArrayAttr(ClassData cls, String name, int paramsTotal, TreeMap> elements) { + super(cls, name); + this.paramsTotal = paramsTotal; + this.elements = elements; + } + + public DataPArrayAttr(ClassData cls, String name, int paramsTotal) { + this(cls, name, paramsTotal, new TreeMap>()); + } + + public void put(int paramNum, T element) { + ArrayList v = get(paramNum); + if (v == null) { + v = new ArrayList<>(); + elements.put(paramNum, v); + } + + v.add(element); + } + + public ArrayList get(int paramNum) { + return elements.get(paramNum); + } + + @Override + public int attrLength() { + int length = 1; // One byte for the parameter count + + // calculate overall size here rather than in add() + // because it may not be available at the time of invoking of add() + for (int i = 0; i < paramsTotal; i++) { + ArrayList attrarray = get(i); + if (attrarray != null) { + for (Data item : attrarray) { + length += item.getLength(); + } + } + length += 2; // 2 bytes for the annotation count for each parameter + } + + return length; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + super.write(out); // attr name, attr len + out.writeByte(paramsTotal); // number of parameters total (in byte) + + for (int i = 0; i < paramsTotal; i++) { + ArrayList attrarray = get(i); + if (attrarray != null) { + // write out the number of annotations for the current param + out.writeShort(attrarray.size()); + for (T item : attrarray) { + item.write(out); // write the current annotation + } + } else { + out.writeShort(0); + // No annotations to write out + } + } + } + }// end class DataPArrayAttr + + + /* Method Data Fields */ + protected Environment env; + protected ConstCell nameCell, sigCell; + protected CodeAttr code; + protected DataVectorAttr exceptions = null; + protected DataVectorAttr paramNames = null; + protected DataPArrayAttr pannotAttrVis = null; + protected DataPArrayAttr pannotAttrInv = null; + protected DefaultAnnotationAttr defaultAnnot = null; + + public MethodData(ClassData cls, int acc, + ConstCell name, ConstCell sig, ArrayList exc_table) { + super(cls, acc); + this.env = cls.env; + nameCell = name; + sigCell = sig; + if ((exc_table != null) && (!exc_table.isEmpty())) { + exceptions = new DataVectorAttr<>(cls, + AttrTag.ATT_Exceptions.parsekey(), + exc_table); + } + // Normalize the modifiers to access flags + if (Modifiers.hasPseudoMod(acc)) { + createPseudoMod(); + } + } + + public void addMethodParameter(int totalParams, int paramNum, ConstCell name, int access) { + env.traceln("addMethodParameter Param[" + paramNum + "] (name: " + name.toString() + ", Flags (" + access + ")."); + if (paramNames == null) { + paramNames = new DataVectorAttr<>(cls, AttrTag.ATT_MethodParameters.parsekey(), true); + for (int i = 0; i < totalParams; i++) { + // initialize the paramName array (in case the name is not given in Jasm syntax) + paramNames.add(new ParamNameData(0, null)); + } + } + paramNames.put(paramNum, new ParamNameData(access, name)); + } + + public CodeAttr startCode(int pos, int paramcnt, Argument max_stack, Argument max_locals) { + code = new CodeAttr(this, pos, paramcnt, max_stack, max_locals); + return code; + } + + public void addDefaultAnnotation(DefaultAnnotationAttr data) { + defaultAnnot = data; + } + + public void addParamAnnotation(int totalParams, int paramNum, AnnotationData data) { + if (!data.invisible) { + if (pannotAttrVis == null) { + pannotAttrVis = new DataPArrayAttr<>(cls, + AttrTag.ATT_RuntimeVisibleParameterAnnotations.parsekey(), + totalParams); + } + pannotAttrVis.put(paramNum, data); + + } else { + if (pannotAttrInv == null) { + pannotAttrInv = new DataPArrayAttr<>(cls, + AttrTag.ATT_RuntimeInvisibleParameterAnnotations.parsekey(), + totalParams); + } + pannotAttrInv.put(paramNum, data); + } + } + + @Override + protected DataVector getAttrVector() { + DataVector dv = getDataVector( exceptions, syntheticAttr, deprecatedAttr, paramNames, code, defaultAnnot); + if (pannotAttrVis != null) { + dv.add(pannotAttrVis); + } + if (pannotAttrInv != null) { + dv.add(pannotAttrInv); + } + return dv; + } + + /*====================================================== Write */ + public void write(CheckedDataOutputStream out) throws IOException, Parser.CompilerError { + out.writeShort(access); + out.writeShort(nameCell.arg); + out.writeShort(sigCell.arg); + getAttrVector().write(out); + } +} // end MethodData + diff --git a/test/lib/org/openjdk/asmtools/jasm/Modifiers.java b/test/lib/org/openjdk/asmtools/jasm/Modifiers.java new file mode 100644 index 00000000000..58ebe9d858b --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/Modifiers.java @@ -0,0 +1,481 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import static org.openjdk.asmtools.jasm.JasmTokens.Token; +import static org.openjdk.asmtools.jasm.RuntimeConstants.*; +import static org.openjdk.asmtools.jasm.Tables.CF_Context; + +/** + * + * + */ +public class Modifiers { + + /* + * Modifier masks + */ + public static final int MM_ATTR = SYNTHETIC_ATTRIBUTE | DEPRECATED_ATTRIBUTE; + + public static final int MM_ACCESS = ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED; + + public static final int MM_INTRF = MM_ACCESS | ACC_ABSTRACT | ACC_INTERFACE | MM_ATTR | ACC_ANNOTATION; + + public static final int MM_CLASS = MM_ACCESS | ACC_FINAL | ACC_SUPER | ACC_ABSTRACT | ACC_ENUM | + MM_ATTR | ACC_MODULE | + ACC_VALUE | ACC_PRIMITIVE; + + public static final int MM_FIELD = MM_ACCESS | ACC_STATIC | ACC_FINAL | ACC_VOLATILE | ACC_TRANSIENT | + ACC_SYNTHETIC | ACC_ENUM | + ACC_MANDATED | // JEP 359 Record + MM_ATTR; + + public static final int MM_I_METHOD = ACC_ABSTRACT | ACC_PUBLIC | ACC_PRIVATE | ACC_STATIC | ACC_VARARGS | + ACC_BRIDGE | ACC_SYNTHETIC ; // interface method + + public static final int MM_A_METHOD = MM_ACCESS | ACC_ABSTRACT | MM_ATTR; + + public static final int MM_N_METHOD = MM_ACCESS | ACC_STRICT | ACC_VARARGS | ACC_SYNTHETIC | MM_ATTR; // + + public static final int MM_METHOD = MM_ACCESS | ACC_STATIC | ACC_FINAL | ACC_SYNCHRONIZED | ACC_BRIDGE | + ACC_VARARGS | ACC_NATIVE | ACC_ABSTRACT | ACC_STRICT | ACC_SYNTHETIC | + ACC_MANDATED | // JEP 359 Record + MM_ATTR ; + + public static final int MM_INNERCLASS = MM_ACCESS | ACC_STATIC | ACC_FINAL | ACC_SUPER | ACC_INTERFACE | + ACC_ABSTRACT | ACC_SYNTHETIC | ACC_ANNOTATION | ACC_ENUM | MM_ATTR | + ACC_VALUE | ACC_PRIMITIVE; + + public static final int MM_REQUIRES = ACC_TRANSITIVE | ACC_STATIC_PHASE | ACC_SYNTHETIC | ACC_MANDATED ; + + public static final int MM_EXPORTS = ACC_SYNTHETIC | ACC_MANDATED ; + + private Modifiers() { + } + + public static boolean validRequires(int mod) { + return (mod & ~MM_REQUIRES) == 0; + } + + public static boolean validExports(int mod) { return (mod & ~MM_EXPORTS) == 0; } + + public static boolean validInnerClass(int mod) { + return (mod & ~MM_INNERCLASS) == 0; + } + + public static boolean validField(int mod) { + return (mod & ~MM_FIELD) == 0; + } + + public static boolean validMethod(int mod) { + return (mod & ~MM_METHOD) == 0; + } + + public static boolean validInterface(int mod) { + return (mod & ~MM_INTRF) == 0; + } + + public static int getInvalidModifiers4Interface(int mod) { + return mod & ~MM_INTRF; + } + + public static boolean validClass(int mod) { + return (mod & ~MM_CLASS) == 0; + } + + public static int getInvalidModifiers4Class(int mod) { + return (mod & ~MM_CLASS); + } + + public static boolean validAbstractMethod(int mod) { + return (mod & ~MM_A_METHOD) == 0; + } + + public static boolean validInitMethod(int mod) { + return (mod & ~MM_N_METHOD) == 0; + } + + public static boolean validInterfaceMethod(int mod, ClassData cd) { + return ((mod & ~MM_I_METHOD) == 0) && + (cd.cfv.major_version() >= 52 || isPublic(mod) && isAbstract(mod) && !isStatic(mod)); + } + + public static boolean validInterfaceField(int mod) { + return mod == (ACC_STATIC | ACC_PUBLIC | ACC_FINAL); + } + + public static boolean isPublic(int mod) { + return (mod & ACC_PUBLIC) != 0; + } + + public static boolean isPrivate(int mod) { + return (mod & ACC_PRIVATE) != 0; + } + + public static boolean isProtected(int mod) { + return (mod & ACC_PROTECTED) != 0; + } + + public static boolean isInterface(int mod) { + return (mod & ACC_INTERFACE) != 0; + } + + public static boolean isAbstract(int mod) { + return (mod & ACC_ABSTRACT) != 0; + } + + public static boolean isFinal(int mod) { + return (mod & ACC_FINAL) != 0; + } + + public static boolean isStatic(int mod) { + return (mod & ACC_STATIC) != 0; + } + + public static boolean isSynthetic(int mod) { + return (mod & ACC_SYNTHETIC) != 0; + } + + public static boolean isDeprecated(int mod) { + return (mod & DEPRECATED_ATTRIBUTE) != 0; + } + + public static boolean isTransient(int mod) { + return (mod & ACC_TRANSIENT) != 0; + } + + public static boolean isAnnotation(int mod) { + return (mod & ACC_ANNOTATION) != 0; + } + + public static boolean isNative(int mod) { + return (mod & ACC_NATIVE) != 0; + } + + public static boolean isStrict(int mod) { + return (mod & ACC_STRICT) != 0; + } + + public static boolean isEnum(int mod) { + return (mod & ACC_ENUM) != 0; + } + + public static boolean isSuper(int mod) { + return (mod & ACC_SUPER) != 0; + } + + public static boolean isModule(int mod) { return (mod & ACC_MODULE)!=0; } + + public static boolean isMandated(int mod) { return (mod & ACC_MANDATED) != 0; } + + public static boolean isSynchronized(int mod) { + return (mod & ACC_SYNCHRONIZED) != 0; + } + + public static boolean isBridge(int mod) { + return (mod & ACC_BRIDGE) != 0; + } + + public static boolean isVolatile(int mod) { + return (mod & ACC_VOLATILE) != 0; + } + + public static boolean isVarArgs(int mod) { + return (mod & ACC_VARARGS) != 0; + } + + public static boolean isSyntheticPseudoMod(int mod) { + return (mod & SYNTHETIC_ATTRIBUTE) != 0; + } + + public static boolean isDeprecatedPseudoMod(int mod) { + return (mod & DEPRECATED_ATTRIBUTE) != 0; + } + + public static boolean hasPseudoMod(int mod) { + return isSyntheticPseudoMod(mod) || isDeprecatedPseudoMod(mod); + } + + public static boolean isTransitive(int mod) { return (mod & ACC_TRANSITIVE) != 0; } + + public static boolean isStaticPhase(int mod) { return (mod & ACC_STATIC_PHASE) != 0; } + + public static boolean isValue(int mod) { + return (mod & ACC_VALUE) != 0; + } + + public static boolean isPrimitive(int mod) { + return (mod & ACC_PRIMITIVE) != 0; + } + + /* + * Checks that only one (or none) of the Access flags are set. + */ + public static boolean validAccess(int mod) { + boolean retval = true; + switch (mod & MM_ACCESS) { + case 0: + case ACC_PUBLIC: + case ACC_PRIVATE: + case ACC_PROTECTED: + break; + default: + retval = false; + } + return retval; + } + + /* + * Are both flags set + * + */ + public static boolean both(int mod, int flagA, int flagB) { + return (mod & (flagA | flagB)) == (flagA | flagB); + } + + /** + * Check the modifier flags for the class + * + * @param env The error reporting environment. + * @param mod The modifier flags being checked + * @param scanner The file parser + */ + public static void checkClassModifiers(Environment env, int mod, Scanner scanner) { + if (isInterface(mod)) { + if( isEnum(mod) ) { + env.error(scanner.pos, "warn.invalid.modifier.class.intenum"); + } else if ( !validInterface(mod) ) { + env.error(scanner.pos, "warn.invalid.modifier.int", + toString(mod & ~MM_INTRF, CF_Context.CTX_CLASS)); + } + if (!isAbstract(mod)) { + env.error(scanner.pos, "warn.invalid.modifier.int.abs"); + } + } else { + if ( scanner.token != Token.CLASS && !isEnum(mod) && scanner.token != Token.ANNOTATION) { + env.error(scanner.pos, "warn.missing.modifier.class"); + } + if (! validClass(mod)) { + env.error(scanner.pos, "warn.invalid.modifier.class", + toString(mod & ~MM_CLASS, CF_Context.CTX_CLASS)); + } + if (isAbstract(mod) && Modifiers.isFinal(mod)) { + env.error(scanner.pos, "warn.invalid.modifier.class.finabs"); + } + } + } + + /** + * Check the modifier flags for the field + * + * @param cd The ClassData for the current class + * @param mod The modifier flags being checked + * @param pos the position of the parser in the file + */ + public static void checkFieldModifiers(ClassData cd, int mod, int pos) { + Environment env = cd.env; + if (cd.isInterface()) { + // For interfaces + if (!validInterfaceField(mod)) { + env.error(pos, "warn.invalid.modifier.intfield"); + } + } else { + // For non-interfaces + if (!validField(mod)) { + env.error(pos, "warn.invalid.modifier.field", + toString(mod & ~MM_FIELD, CF_Context.CTX_METHOD)); + } + if (both(mod, ACC_FINAL, ACC_VOLATILE)) { + env.error(pos, "warn.invalid.modifier.fiva"); + } + if (!validAccess(mod)) { + env.error(pos, "warn.invalid.modifier.acc"); + } + } + + } + + /** + * Check the modifier flags for the method + * + * @param cd The ClassData for the current class + * @param mod The modifier flags being checked + * @param pos the position of the parser in the file + */ + public static void checkMethodModifiers(ClassData cd, int mod, int pos, boolean is_init, boolean is_clinit) { + Environment env = cd.env; + if (!is_clinit) { + if (cd.isInterface()) { + if (is_init) { + env.error(pos, "warn.init.in_int"); + } else if (!validInterfaceMethod(mod, cd)) { + int badflags = (mod & ~MM_I_METHOD); + env.error(pos, "warn.invalid.modifier.intmth", toString(badflags, CF_Context.CTX_METHOD) + + " *****" + toString(mod, CF_Context.CTX_METHOD) + "*****"); + } + } else { + if (is_init && !validInitMethod(mod)) { + int badflags = (mod & ~MM_N_METHOD); + env.error(pos, "warn.invalid.modifier.init", toString(badflags, CF_Context.CTX_METHOD) + + " *****" + toString(mod, CF_Context.CTX_METHOD) + "*****"); + } else if (isAbstract(mod)) { + if (!validAbstractMethod(mod)) { + int badflags = (mod & ~MM_A_METHOD); + env.error(pos, "warn.invalid.modifier.abst", toString(badflags, CF_Context.CTX_METHOD) + + " *****" + toString(mod, CF_Context.CTX_METHOD) + "*****"); + } + } else { + if (!validMethod(mod)) { + env.error(pos, "warn.invalid.modifier.mth", + toString(mod & ~MM_METHOD, CF_Context.CTX_METHOD)); + } + } + if (!validAccess(mod)) { + env.error(pos, "warn.invalid.modifier.acc"); + } + } + } + } + + /** + * Check the modifier flags for the inner-class + * + * @param cd The ClassData for the current class + * @param mod The modifier flags being checked + * @param pos the position of the parser in the file + */ + public static void checkInnerClassModifiers(ClassData cd, int mod, int pos) { + Environment env = cd.env; + + if (!validInnerClass(mod)) { + int badflags = (mod & ~MM_INNERCLASS); + env.error(pos, "warn.invalid.modifier.innerclass", + toString(badflags, CF_Context.CTX_INNERCLASS) + + " *****" + toString(mod, CF_Context.CTX_INNERCLASS) + "*****"); + } + + } + + private static StringBuffer _accessString(int mod, CF_Context context) { + StringBuffer sb = new StringBuffer(); + if (context == CF_Context.CTX_CLASS && isModule(mod)) { + sb.append(Token.MODULE.parseKey() + " "); + } + if (isPublic(mod)) { + sb.append(Token.PUBLIC.parseKey() + " "); + } + if (isPrivate(mod)) { + sb.append(Token.PRIVATE.parseKey() + " "); + } + if (isProtected(mod)) { + sb.append(Token.PROTECTED.parseKey() + " "); + } + if (isStatic(mod)) { + sb.append(Token.STATIC.parseKey() + " "); + } + if (context == CF_Context.CTX_METHOD && isFinal(mod)) { + sb.append(Token.FINAL.parseKey() + " "); + } + if (context == CF_Context.CTX_FIELD && isTransient(mod)) { + sb.append(Token.TRANSIENT.parseKey() + " "); + } + if (context == CF_Context.CTX_CLASS && isSuper(mod)) { + sb.append(Token.SUPER.parseKey() + " "); + } + if (context == CF_Context.CTX_METHOD && isSynchronized(mod)) { + sb.append(Token.SYNCHRONIZED.parseKey() + " "); + } + if (context == CF_Context.CTX_METHOD) { + if (isBridge(mod)) { + sb.append(Token.BRIDGE.parseKey() + " "); + } + if (isVarArgs(mod)) { + sb.append(Token.VARARGS.parseKey() + " "); + } + if (isNative(mod)) { + sb.append(Token.NATIVE.parseKey() + " "); + } + if (isStrict(mod)) { + sb.append(Token.STRICT.parseKey() + " "); + } + } + if (isAbstract(mod)) { + if ((context != CF_Context.CTX_CLASS) || !isInterface(mod)) { + sb.append(Token.ABSTRACT.parseKey() + " "); + } + } + if ( context.isOneOf(CF_Context.CTX_CLASS, CF_Context.CTX_INNERCLASS, CF_Context.CTX_FIELD) && isFinal(mod)) { + sb.append(Token.FINAL.parseKey() + " "); + } + if (context.isOneOf(CF_Context.CTX_CLASS, CF_Context.CTX_INNERCLASS) && isInterface(mod)) { + if (isAnnotation(mod)) { + sb.append(Token.ANNOTATION_ACCESS.parseKey() + " "); + } + sb.append(Token.INTERFACE.parseKey() + " "); + } + if (isSynthetic(mod)) { + sb.append(Token.SYNTHETIC.parseKey() + " "); + } + if (context == CF_Context.CTX_FIELD && isVolatile(mod)) { + sb.append(Token.VOLATILE.parseKey() + " "); + } + if (isEnum(mod)) { + sb.append(Token.ENUM.parseKey() + " "); + } + if (context.isOneOf(CF_Context.CTX_METHOD, CF_Context.CTX_FIELD) && isMandated(mod)) { + sb.append(Token.MANDATED.parseKey() + " "); + } + if (context.isOneOf(CF_Context.CTX_CLASS, CF_Context.CTX_INNERCLASS) && isPrimitive(mod)) { + sb.append(Token.PRIMITIVE.parseKey() + " "); + } + if (context.isOneOf(CF_Context.CTX_CLASS, CF_Context.CTX_INNERCLASS) && isValue(mod)) { + sb.append(Token.VALUE.parseKey() + " "); + } + + return sb; + } + + public static String toString(int mod, CF_Context context) { + StringBuffer sb = _accessString(mod, context); + + if (isSyntheticPseudoMod(mod)) { + sb.append("Synthetic(Pseudo) "); + } + if (isDeprecatedPseudoMod(mod)) { + sb.append("Deprecated(Pseudo) "); + } + + return sb.toString().trim(); + } + + public static String moduleFlags( int flags ) { + return ""; + } + + public static String accessString(int mod, CF_Context context) { + return (context == CF_Context.CTX_MODULE) ? + moduleFlags(mod) : + _accessString(mod, context).toString(); + } + +} diff --git a/test/lib/org/openjdk/asmtools/jasm/ModuleAttr.java b/test/lib/org/openjdk/asmtools/jasm/ModuleAttr.java new file mode 100644 index 00000000000..0dec054eda5 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/ModuleAttr.java @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import org.openjdk.asmtools.common.Module; + +import java.io.IOException; +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * The module attribute + */ +class ModuleAttr extends AttrData { + // shared data + private Module.Builder builder; + private final ClassData clsData; + private final Function findCellAsciz; + private final Function findCellClassByName; + private final Function findCellModuleByName; + private final Function findCellPackageByName; + + // entries to populate tables of the module attribute + BiConsumer requires = (mn, f) -> this.builder.require(mn, f); + BiConsumer> exports = (pn, ms) -> this.builder.exports(new Module.Exported(pn), ms); + BiConsumer> opens = (pn, ms) -> this.builder.opens(new Module.Opened(pn), ms); + BiConsumer> provides = (tn, ts) -> this.builder.provides(new Module.Provided(tn), ts); + Consumer> uses = (ts) -> this.builder.uses(ts); + + ModuleAttr(ClassData cdata) { + super(cdata, Tables.AttrTag.ATT_Module.parsekey()); + builder = new Module.Builder(); + clsData = cdata; + findCellAsciz = (name) -> clsData.pool.FindCellAsciz(name); + findCellClassByName = (name) -> clsData.pool.FindCellClassByName(name); + findCellModuleByName = (name) -> clsData.pool.FindCellModuleByName(name); + findCellPackageByName = (name) -> clsData.pool.FindCellPackageByName(name); + } + + void openModule() { + builder.setModuleFlags(Module.Modifier.ACC_OPEN); + } + void setModuleName(String value) { builder.setModuleName(value);} + + ModuleAttr build() { + Module module = builder.build(); + Content.instance.header = new HeaderStruct(module.header, findCellModuleByName, findCellAsciz); + Content.instance.requiresStruct = new SetStruct<>(module.requires, findCellModuleByName, findCellAsciz); + Content.instance.exportsMapStruct = new MapStruct<>(module.exports, findCellPackageByName, findCellModuleByName ); + Content.instance.opensMapStruct = new MapStruct<>(module.opens,findCellPackageByName, findCellModuleByName ); + Content.instance.usesStruct = new SetStruct<>(module.uses, findCellClassByName, null); + Content.instance.providesMapStruct = new MapStruct<>(module.provides, findCellClassByName, findCellClassByName); + return this; + } + + @Override + public int attrLength() { + return Content.instance.getLength(); + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + super.write(out); + Content.instance.write(out); + } + + private enum Content implements Data { + instance { + @Override + public int getLength() { + return header.getLength() + + requiresStruct.getLength() + + exportsMapStruct.getLength() + + opensMapStruct.getLength() + + usesStruct.getLength() + + providesMapStruct.getLength(); + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + // keep order! + header.write(out); + requiresStruct.write(out); + exportsMapStruct.write(out); + opensMapStruct.write(out); + usesStruct.write(out); + providesMapStruct.write(out); + } + }; + + HeaderStruct header ; + SetStruct requiresStruct; + MapStruct exportsMapStruct; + MapStruct opensMapStruct; + SetStruct usesStruct; + MapStruct providesMapStruct; + } + + /** + * u2 {exports|opens}_count; + * { u2 {exports|opens}_index; + * u2 {exports|opens}_flags; + * u2 {exports|opens}_to_count; + * u2 {exports|opens}_to_index[{exports|opens}_to_count]; + * } {exports|opens}[{exports|opens}_count]; + * or + * u2 provides_count; + * { u2 provides_index; + * u2 provides_with_count; + * u2 provides_with_index[provides_with_count]; + * } provides[provides_count]; + */ + private class MapStruct implements Data { + final List>> exportsOpensList = new ArrayList<>(); + final List>> providesList = new ArrayList<>(); + + MapStruct(Map> source, + Function nameFinder, + Function targetFinder) { + Objects.requireNonNull(source); + source.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .forEach(e -> { + ArrayList to = new ArrayList<>(); + e.getValue().forEach(mn -> to.add(targetFinder.apply(mn))); + if (e.getKey().isFlagged()) { + exportsOpensList.add(new Triplet<> + ( nameFinder.apply(e.getKey().getTypeName()), + ((Module.FlaggedTargetType) e.getKey()).getFlags(), + to)); + } else { + providesList.add(new Pair<>(nameFinder.apply(e.getKey().getTypeName()), + to)); + } + } + ); + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + if (providesList.isEmpty()) { + out.writeShort(exportsOpensList.size()); // u2 {exports|opens}_count; + for (Triplet> triplet : exportsOpensList) { + out.writeShort(triplet.first.arg); // { u2 {exports|opens}_index; + out.writeShort(triplet.second); // u2 {exports|opens}_flags; + out.writeShort(triplet.third.size()); // u2 {exports|opens}_to_count; + for (ConstantPool.ConstCell to : triplet.third) + out.writeShort(to.arg); // u2 {exports|opens}_to_index[{exports|opens}_to_count]; } + } + } else { + out.writeShort(providesList.size()); // u2 provides_count; + for (Pair> pair : providesList) { + out.writeShort(pair.first.arg); // { u2 provides_index; + out.writeShort(pair.second.size()); // u2 provides_with_count; + for (ConstantPool.ConstCell to : pair.second) + out.writeShort(to.arg); // u2 provides_with_index[provides_with_count]; } + } + } + } + + @Override + public int getLength() { + if (providesList.isEmpty()) { + // (u2:{exports|opens}_count) + (u2:{exports|opens}_index + u2:{exports|opens}_flags u2:{exports|opens}_to_count) * {exports|opens}_count + + return 2 + 6 * exportsOpensList.size() + + // (u2:{exports|opens}_to_index) * {exports|opens}_to_count + exportsOpensList.stream().mapToInt(p -> p.third.size()).filter(s -> s > 0).sum() * 2; + } else { + // (u2 : provides_count) + (u2:provides_index + u2:provides_with_count) * provides_count + + return 2 + 4 * providesList.size() + + // (u2:provides_with_index) * provides_with_count + providesList.stream().mapToInt(p -> p.second.size()).filter(s -> s > 0).sum() * 2; + } + } + } + + private class HeaderStruct implements Data { + final ConstantPool.ConstCell index; + final int flags; + final ConstantPool.ConstCell versionIndex; + + HeaderStruct(Module.Header source, + Function nameFinder, + Function versionFinder) { + index = nameFinder.apply(source.getModuleName()); + versionIndex = (source.getModuleVersion() == null ) ? null : versionFinder.apply(source.getModuleVersion()); + flags = source.getModuleFlags(); + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeShort(index.arg); // u2 module_name_index; + out.writeShort(flags); // u2 module_flags; + out.writeShort(versionIndex == null ? 0 : versionIndex.arg); // u2 module_version_index; + } + + @Override + public int getLength() { + // u2:module_name_index) + u2:module_flags +u2:module_version_index + return 6; + } + } + + /** + * u2 uses_count; + * u2 uses_index[uses_count]; + * or + * u2 requires_count; + * { u2 requires_index; + * u2 requires_flags; + * u2 requires_version_index; + * } requires[requires_count]; + */ + private class SetStruct implements Data { + final List usesList = new ArrayList<>(); + final List> requiresList = new ArrayList<>(); + + SetStruct(Set source, + Function nameFinder, + Function versionFinder) { + Objects.requireNonNull(source); + source.forEach(e -> { + if (e.isFlagged()) { + requiresList.add(new Triplet<>( + nameFinder.apply(e.getTypeName()), + ((Module.FlaggedTargetType) e).getFlags(), + (((Module.VersionedFlaggedTargetType) e).getVersion() == null) ? + null : + versionFinder.apply(((Module.VersionedFlaggedTargetType) e).getVersion()))); + } else { + usesList.add(nameFinder.apply((e.getTypeName()))); + } + }); + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + if (usesList.isEmpty()) { + out.writeShort(requiresList.size()); // u2 requires_count; + for (Triplet r : requiresList) { + out.writeShort(r.first.arg); // u2 requires_index; + out.writeShort(r.second); // u2 requires_flags; + out.writeShort(r.third == null ? 0 : r.third.arg); // u2 requires_version_index; + } + } else { + out.writeShort(usesList.size()); // u2 uses_count; + for (ConstantPool.ConstCell u : usesList) + out.writeShort(u.arg); // u2 uses_index[uses_count]; + } + } + + @Override + public int getLength() { + return usesList.isEmpty() ? + // (u2:requires_count) + (u2:requires_index + u2:requires_flags + u2:requires_version_index) * requires_count + 2 + 6 * requiresList.size() : + // (u2:uses_count) + (u2:uses_index) * uses_count + 2 + 2 * usesList.size(); + } + } + + // Helper classes + private class Pair { + final F first; + final S second; + + Pair(F first, S second) { + this.first = first; + this.second = second; + } + } + + public class Triplet extends Pair { + private final T third; + Triplet(F first, S second, T third) { + super(first,second); + this.third = third; + } + } + +} diff --git a/test/lib/org/openjdk/asmtools/jasm/NestMembersAttr.java b/test/lib/org/openjdk/asmtools/jasm/NestMembersAttr.java new file mode 100644 index 00000000000..143c7437b1f --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/NestMembersAttr.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.util.List; + +/** + * The "classes[]" data of attributes + * JEP 181 (Nest-based Access Control): class file 55.0 + * NestMembers_attribute { + * u2 attribute_name_index; + * u4 attribute_length; + * u2 number_of_classes; + * u2 classes[number_of_classes]; + * } + */ +public class NestMembersAttr extends ClassArrayAttr { + public NestMembersAttr(ClassData cdata, List classes) { + super(Tables.AttrTag.ATT_NestMembers.parsekey(), cdata, classes); + } +} diff --git a/test/lib/org/openjdk/asmtools/jasm/OpcodeTables.java b/test/lib/org/openjdk/asmtools/jasm/OpcodeTables.java new file mode 100644 index 00000000000..4ba6d83e738 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/OpcodeTables.java @@ -0,0 +1,569 @@ +/* + * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.util.HashMap; + +/** + * + * OpcodeTables + * + * The OpcodeTables class follows a Singleton Pattern. This class contains Enums, that are + * contained in private hash maps (lookup tables and reverse lookup tables). These hash + * maps all have public accessors, which clients use to look-up opcodes. + * + * Tokens in this table carry no external state, and are typically treated as constants. + * They do not need to be reset. + * + */ +public class OpcodeTables { + + /** + * Initialized keyword and token Hash Maps (and Reverse Tables) + */ + static private final int MaxOpcodes = 301; + static private HashMap IntToNormalOpcodes = new HashMap<>(MaxOpcodes); + static private HashMap IntToAllOpcodes = new HashMap<>(MaxOpcodes); + static private HashMap mnemocodes = new HashMap<>(MaxOpcodes); + + static private HashMap IntToPrivOpcode = new HashMap<>(MaxOpcodes); + static private HashMap PrivMnemocodes = new HashMap<>(MaxOpcodes); + + static private HashMap IntToNonPrivOpcode = new HashMap<>(MaxOpcodes); + static private HashMap NonPrivMnemocodes = new HashMap<>(MaxOpcodes); + + static { + // register all of the tokens + for (Opcode opc : Opcode.values()) { + registerOpcode(opc); + } + + } + + private static void registerOpcode(Opcode opc) { + IntToAllOpcodes.put(opc.value, opc); + mnemocodes.put(opc.parsekey, opc); + if (opc.alias != null) { + mnemocodes.put(opc.alias, opc); + } + + if (opc.type == OpcodeType.PRIVELEGED) { + PrivMnemocodes.put(opc.parsekey, opc); + IntToPrivOpcode.put(opc.baseVal, opc); + } else if (opc.type == OpcodeType.NONPRIVELEGED) { + NonPrivMnemocodes.put(opc.parsekey, opc); + IntToNonPrivOpcode.put(opc.baseVal, opc); + } + + } + + public static Opcode opcode(String mnemonic) { + return mnemocodes.get(mnemonic); + } + + public static Opcode opcode(Integer mnem_code) { + return IntToAllOpcodes.get(mnem_code); + } + + /*-------------------------------------------------------- */ + /** + * Marker: describes the type of Opcode. + * + * certain types of Opcodes will be added to specific lookup tables. + */ + static public enum OpcodeType { + NORMAL (0, "Normal"), + NONPRIVELEGED (1, "NonPriv"), + PRIVELEGED (2, "Priv"), + WIDE (3, "Wide"); + + private final Integer value; + private final String printval; + + OpcodeType(Integer val, String print) { + value = val; + printval = print; + } + + public String printval() { + return printval; + } + + } + + /*-------------------------------------------------------- */ + /* Opcode Enums */ + static public enum Opcode { + /* Opcodes */ + opc_dead (-2, " opc_dead", 0), + opc_label (-1, "opc_label", 0), + opc_nop (0, "nop", 1), + opc_aconst_null (1, "aconst_null", 1), + opc_iconst_m1 (2, "iconst_m1", 1), + opc_iconst_0 (3, "iconst_0", 1), + opc_iconst_1 (4, "iconst_1", 1), + opc_iconst_2 (5, "iconst_2", 1), + opc_iconst_3 (6, "iconst_3", 1), + opc_iconst_4 (7, "iconst_4", 1), + opc_iconst_5 (8, "iconst_5", 1), + opc_lconst_0 (9, "lconst_0", 1), + opc_lconst_1 (10, "lconst_1", 1), + opc_fconst_0 (11, "fconst_0", 1), + opc_fconst_1 (12, "fconst_1", 1), + opc_fconst_2 (13, "fconst_2", 1), + opc_dconst_0 (14, "dconst_0", 1), + opc_dconst_1 (15, "dconst_1", 1), + opc_bipush (16, "bipush", 2), + opc_sipush (17, "sipush", 3), + opc_ldc (18, "ldc", 2), + opc_ldc_w (19, "ldc_w", 3), + opc_ldc2_w (20, "ldc2_w", 3), + opc_iload (21, "iload", 2), + opc_lload (22, "lload", 2), + opc_fload (23, "fload", 2), + opc_dload (24, "dload", 2), + opc_aload (25, "aload", 2), + opc_iload_0 (26, "iload_0", 1), + opc_iload_1 (27, "iload_1", 1), + opc_iload_2 (28, "iload_2", 1), + opc_iload_3 (29, "iload_3", 1), + opc_lload_0 (30, "lload_0", 1), + opc_lload_1 (31, "lload_1", 1), + opc_lload_2 (32, "lload_2", 1), + opc_lload_3 (33, "lload_3", 1), + opc_fload_0 (34, "fload_0", 1), + opc_fload_1 (35, "fload_1", 1), + opc_fload_2 (36, "fload_2", 1), + opc_fload_3 (37, "fload_3", 1), + opc_dload_0 (38, "dload_0", 1), + opc_dload_1 (39, "dload_1", 1), + opc_dload_2 (40, "dload_2", 1), + opc_dload_3 (41, "dload_3", 1), + opc_aload_0 (42, "aload_0", 1), + opc_aload_1 (43, "aload_1", 1), + opc_aload_2 (44, "aload_2", 1), + opc_aload_3 (45, "aload_3", 1), + opc_iaload (46, "iaload", 1), + opc_laload (47, "laload", 1), + opc_faload (48, "faload", 1), + opc_daload (49, "daload", 1), + opc_aaload (50, "aaload", 1), + opc_baload (51, "baload", 1), + opc_caload (52, "caload", 1), + opc_saload (53, "saload", 1), + opc_istore (54, "istore", 2), + opc_lstore (55, "lstore", 2), + opc_fstore (56, "fstore", 2), + opc_dstore (57, "dstore", 2), + opc_astore (58, "astore", 2), + opc_istore_0 (59, "istore_0", 1), + opc_istore_1 (60, "istore_1", 1), + opc_istore_2 (61, "istore_2", 1), + opc_istore_3 (62, "istore_3", 1), + opc_lstore_0 (63, "lstore_0", 1), + opc_lstore_1 (64, "lstore_1", 1), + opc_lstore_2 (65, "lstore_2", 1), + opc_lstore_3 (66, "lstore_3", 1), + opc_fstore_0 (67, "fstore_0", 1), + opc_fstore_1 (68, "fstore_1", 1), + opc_fstore_2 (69, "fstore_2", 1), + opc_fstore_3 (70, "fstore_3", 1), + opc_dstore_0 (71, "dstore_0", 1), + opc_dstore_1 (72, "dstore_1", 1), + opc_dstore_2 (73, "dstore_2", 1), + opc_dstore_3 (74, "dstore_3", 1), + opc_astore_0 (75, "astore_0", 1), + opc_astore_1 (76, "astore_1", 1), + opc_astore_2 (77, "astore_2", 1), + opc_astore_3 (78, "astore_3", 1), + opc_iastore (79, "iastore", 1), + opc_lastore (80, "lastore", 1), + opc_fastore (81, "fastore", 1), + opc_dastore (82, "dastore", 1), + opc_aastore (83, "aastore", 1), + opc_bastore (84, "bastore", 1), + opc_castore (85, "castore", 1), + opc_sastore (86, "sastore", 1), + opc_pop (87, "pop", 1), + opc_pop2 (88, "pop2", 1), + opc_dup (89, "dup", 1), + opc_dup_x1 (90, "dup_x1", 1), + opc_dup_x2 (91, "dup_x2", 1), + opc_dup2 (92, "dup2", 1), + opc_dup2_x1 (93, "dup2_x1", 1), + opc_dup2_x2 (94, "dup2_x2", 1), + opc_swap (95, "swap", 1), + opc_iadd (96, "iadd", 1), + opc_ladd (97, "ladd", 1), + opc_fadd (98, "fadd", 1), + opc_dadd (99, "dadd", 1), + opc_isub (100, "isub", 1), + opc_lsub (101, "lsub", 1), + opc_fsub (102, "fsub", 1), + opc_dsub (103, "dsub", 1), + opc_imul (104, "imul", 1), + opc_lmul (105, "lmul", 1), + opc_fmul (106, "fmul", 1), + opc_dmul (107, "dmul", 1), + opc_idiv (108, "idiv", 1), + opc_ldiv (109, "ldiv", 1), + opc_fdiv (110, "fdiv", 1), + opc_ddiv (111, "ddiv", 1), + opc_irem (112, "irem", 1), + opc_lrem (113, "lrem", 1), + opc_frem (114, "frem", 1), + opc_drem (115, "drem", 1), + opc_ineg (116, "ineg", 1), + opc_lneg (117, "lneg", 1), + opc_fneg (118, "fneg", 1), + opc_dneg (119, "dneg", 1), + opc_ishl (120, "ishl", 1), + opc_lshl (121, "lshl", 1), + opc_ishr (122, "ishr", 1), + opc_lshr (123, "lshr", 1), + opc_iushr (124, "iushr", 1), + opc_lushr (125, "lushr", 1), + opc_iand (126, "iand", 1), + opc_land (127, "land", 1), + opc_ior (128, "ior", 1), + opc_lor (129, "lor", 1), + opc_ixor (130, "ixor", 1), + opc_lxor (131, "lxor", 1), + opc_iinc (132, "iinc", 3), + opc_i2l (133, "i2l", 1), + opc_i2f (134, "i2f", 1), + opc_i2d (135, "i2d", 1), + opc_l2i (136, "l2i", 1), + opc_l2f (137, "l2f", 1), + opc_l2d (138, "l2d", 1), + opc_f2i (139, "f2i", 1), + opc_f2l (140, "f2l", 1), + opc_f2d (141, "f2d", 1), + opc_d2i (142, "d2i", 1), + opc_d2l (143, "d2l", 1), + opc_d2f (144, "d2f", 1), + opc_i2b (145, "i2b", 1), + opc_i2c (146, "i2c", 1), + opc_i2s (147, "i2s", 1), + opc_lcmp (148, "lcmp", 1), + opc_fcmpl (149, "fcmpl", 1), + opc_fcmpg (150, "fcmpg", 1), + opc_dcmpl (151, "dcmpl", 1), + opc_dcmpg (152, "dcmpg", 1), + opc_ifeq (153, "ifeq", 3), + opc_ifne (154, "ifne", 3), + opc_iflt (155, "iflt", 3), + opc_ifge (156, "ifge", 3), + opc_ifgt (157, "ifgt", 3), + opc_ifle (158, "ifle", 3), + opc_if_icmpeq (159, "if_icmpeq", 3), + opc_if_icmpne (160, "if_icmpne", 3), + opc_if_icmplt (161, "if_icmplt", 3), + opc_if_icmpge (162, "if_icmpge", 3), + opc_if_icmpgt (163, "if_icmpgt", 3), + opc_if_icmple (164, "if_icmple", 3), + opc_if_acmpeq (165, "if_acmpeq", 3), + opc_if_acmpne (166, "if_acmpne", 3), + opc_goto (167, "goto", 3), + opc_jsr (168, "jsr", 3), + opc_ret (169, "ret", 2), + opc_tableswitch (170, "tableswitch", 99), + opc_lookupswitch (171, "lookupswitch", 99), + opc_ireturn (172, "ireturn", 1), + opc_lreturn (173, "lreturn", 1), + opc_freturn (174, "freturn", 1), + opc_dreturn (175, "dreturn", 1), + opc_areturn (176, "areturn", 1), + opc_return (177, "return", 1), + opc_getstatic (178, "getstatic", 3), + opc_putstatic (179, "putstatic", 3), + opc_getfield (180, "getfield", 3), + opc_putfield (181, "putfield", 3), + opc_invokevirtual (182, "invokevirtual", 3), + opc_invokespecial (183, "invokespecial", "invokenonvirtual", 3), + opc_invokestatic (184, "invokestatic", 3), + opc_invokeinterface (185, "invokeinterface", 5), + opc_invokedynamic (186, "invokedynamic", 5), + opc_new (187, "new", 3), + opc_newarray (188, "newarray", 2), + opc_anewarray (189, "anewarray", 3), + opc_arraylength (190, "arraylength", 1), + opc_athrow (191, "athrow", 1), + opc_checkcast (192, "checkcast", 3), + opc_instanceof (193, "instanceof", 3), + opc_monitorenter (194, "monitorenter", 1), + opc_monitorexit (195, "monitorexit", 1), + + // Wide Marker (not really an opcode) + opc_wide (196, null, 0), + opc_multianewarray (197, "multianewarray", 4), + opc_ifnull (198, "ifnull", 3), + opc_ifnonnull (199, "ifnonnull", 3), + opc_goto_w (200, "goto_w", 5), + opc_jsr_w (201, "jsr_w", 5), + opc_aconst_init (203, "aconst_init", 3), + opc_withfield (204, "withfield", 3), + + /* Pseudo-instructions */ + opc_bytecode (210, "bytecode", 1), + opc_try (211, "try", 0), + opc_endtry (212, "endtry", 0), + opc_catch (213, "catch", 0), + opc_var (214, "var", 0), + opc_endvar (215, "endvar", 0), + opc_locals_map (216, "locals_map", 0), + opc_stack_map (217, "stack_map", 0), + opc_stack_frame_type (218, "stack_frame_type", 0), + + + // Priv/NonPriv Marker (not really an opcode) + opc_nonpriv (254, "priv", 0), + opc_priv (255, "nonpriv", 0), + + + /* Wide instructions */ + opc_iload_w (opc_iload.value, "iload_w", 4, OpcodeType.WIDE), + opc_lload_w (opc_lload.value, "lload_w", 4, OpcodeType.WIDE), + opc_fload_w (opc_fload.value, "fload_w", 4, OpcodeType.WIDE), + opc_dload_w (opc_dload.value, "dload_w", 4, OpcodeType.WIDE), + opc_aload_w (opc_aload.value, "aload_w", 4, OpcodeType.WIDE), + opc_istore_w (opc_istore.value, "istore_w", 4, OpcodeType.WIDE), + opc_lstore_w (opc_lstore.value, "lstore_w", 4, OpcodeType.WIDE), + opc_fstore_w (opc_fstore.value, "fstore_w", 4, OpcodeType.WIDE), + opc_dstore_w (opc_dstore.value, "dstore_w", 4, OpcodeType.WIDE), + opc_astore_w (opc_astore.value, "astore_w", 4, OpcodeType.WIDE), + opc_ret_w (opc_ret.value, "ret_w", 4, OpcodeType.WIDE), + opc_iinc_w (opc_iinc.value, "iinc_w", 6, OpcodeType.WIDE), + + + /* Priveleged instructions */ + opc_load_ubyte (0, "load_ubyte", OpcodeType.NONPRIVELEGED), + opc_priv_load_ubyte (0, "priv_load_ubyte", OpcodeType.PRIVELEGED), + opc_load_byte (1, "load_byte", OpcodeType.NONPRIVELEGED), + opc_priv_load_byte (1, "priv_load_byte", OpcodeType.PRIVELEGED), + opc_load_char (2, "load_char", OpcodeType.NONPRIVELEGED), + opc_priv_load_char (2, "priv_load_char", OpcodeType.PRIVELEGED), + opc_load_short (3, "load_short", OpcodeType.NONPRIVELEGED), + opc_priv_load_short (3, "priv_load_short", OpcodeType.PRIVELEGED), + opc_load_word (4, "load_word", OpcodeType.NONPRIVELEGED), + opc_priv_load_word (4, "priv_load_word", OpcodeType.PRIVELEGED), + opc_load_char_oe (10, "load_char_oe", OpcodeType.NONPRIVELEGED), + opc_priv_load_char_oe (10, "priv_load_char_oe", OpcodeType.PRIVELEGED), + opc_load_short_oe (11, "load_short_oe", OpcodeType.NONPRIVELEGED), + opc_priv_load_short_oe (11, "priv_load_short_oe", OpcodeType.PRIVELEGED), + opc_load_word_oe (12, "load_word_oe", OpcodeType.NONPRIVELEGED), + opc_priv_load_word_oe (12, "priv_load_word_oe", OpcodeType.PRIVELEGED), + opc_ncload_ubyte (16, "ncload_ubyte", OpcodeType.NONPRIVELEGED), + opc_priv_ncload_ubyte (16, "priv_ncload_ubyte", OpcodeType.PRIVELEGED), + opc_ncload_byte (17, "ncload_byte", OpcodeType.NONPRIVELEGED), + opc_priv_ncload_byte (17, "priv_ncload_byte", OpcodeType.PRIVELEGED), + opc_ncload_char (18, "ncload_char", OpcodeType.NONPRIVELEGED), + opc_priv_ncload_char (18, "priv_ncload_char", OpcodeType.PRIVELEGED), + opc_ncload_short (19, "ncload_short", OpcodeType.NONPRIVELEGED), + opc_priv_ncload_short (19, "priv_ncload_short", OpcodeType.PRIVELEGED), + opc_ncload_word (20, "ncload_word", OpcodeType.NONPRIVELEGED), + opc_priv_ncload_word (20, "priv_ncload_word", OpcodeType.PRIVELEGED), + opc_ncload_char_oe (26, "ncload_char_oe", OpcodeType.NONPRIVELEGED), + opc_priv_ncload_char_oe (26, "priv_ncload_char_oe", OpcodeType.PRIVELEGED), + opc_ncload_short_oe (27, "ncload_short_oe", OpcodeType.NONPRIVELEGED), + opc_priv_ncload_short_oe (27, "priv_ncload_short_oe", OpcodeType.PRIVELEGED), + opc_ncload_word_oe (28, "ncload_word_oe", OpcodeType.NONPRIVELEGED), + opc_priv_ncload_word_oe (28, "priv_ncload_word_oe", OpcodeType.PRIVELEGED), + opc_cache_flush (30, "cache_flush", OpcodeType.NONPRIVELEGED), + opc_priv_cache_flush (30, "priv_cache_flush", OpcodeType.PRIVELEGED), + opc_store_byte (32, "store_byte", OpcodeType.NONPRIVELEGED), + opc_priv_store_byte (32, "priv_store_byte", OpcodeType.PRIVELEGED), + opc_store_short (34, "store_short", OpcodeType.NONPRIVELEGED), + opc_priv_store_short (34, "priv_store_short", OpcodeType.PRIVELEGED), + opc_store_word (36, "store_word", OpcodeType.NONPRIVELEGED), + opc_priv_store_word (36, "priv_store_word", OpcodeType.PRIVELEGED), + opc_store_short_oe (42, "store_short_oe", OpcodeType.NONPRIVELEGED), + opc_priv_store_short_oe (42, "priv_store_short_oe", OpcodeType.PRIVELEGED), + opc_store_word_oe (44, "store_word_oe", OpcodeType.NONPRIVELEGED), + opc_priv_store_word_oe (44, "priv_store_word_oe", OpcodeType.PRIVELEGED), + opc_ncstore_byte (48, "ncstore_byte", OpcodeType.NONPRIVELEGED), + opc_priv_ncstore_byte (48, "priv_ncstore_byte", OpcodeType.PRIVELEGED), + opc_ncstore_short (50, "ncstore_short", OpcodeType.NONPRIVELEGED), + opc_priv_ncstore_short (50, "priv_ncstore_short", OpcodeType.PRIVELEGED), + opc_ncstore_word (52, "ncstore_word", OpcodeType.NONPRIVELEGED), + opc_priv_ncstore_word (52, "priv_ncstore_word", OpcodeType.PRIVELEGED), + opc_ncstore_short_oe (58, "ncstore_short_oe", OpcodeType.NONPRIVELEGED), + opc_priv_ncstore_short_oe (58, "priv_ncstore_short_oe", OpcodeType.PRIVELEGED), + opc_ncstore_word_oe (60, "ncstore_word_oe", OpcodeType.NONPRIVELEGED), + opc_priv_ncstore_word_oe (60, "priv_ncstore_word_oe", OpcodeType.PRIVELEGED), + opc_zero_line (62, "zero_line", OpcodeType.NONPRIVELEGED), + opc_priv_zero_line (62, "priv_zero_line", OpcodeType.PRIVELEGED), + opc_ret_from_sub (5, "ret_from_sub", OpcodeType.NONPRIVELEGED), + opc_enter_sync_method (63, "enter_sync_method", OpcodeType.NONPRIVELEGED), + opc_priv_ret_from_trap (5, "priv_ret_from_trap", OpcodeType.PRIVELEGED), + opc_priv_read_dcache_tag (6, "priv_read_dcache_tag", OpcodeType.PRIVELEGED), + opc_priv_read_dcache_data (7, "priv_read_dcache_data", OpcodeType.PRIVELEGED), + opc_priv_read_icache_tag (14, "priv_read_icache_tag", OpcodeType.PRIVELEGED), + opc_priv_read_icache_data (15, "priv_read_icache_data", OpcodeType.PRIVELEGED), + opc_priv_powerdown (22, "priv_powerdown", OpcodeType.PRIVELEGED), + opc_priv_read_scache_data (23, "priv_read_scache_data", OpcodeType.PRIVELEGED), + opc_priv_cache_index_flush (31, "priv_cache_index_flush", OpcodeType.PRIVELEGED), + opc_priv_write_dcache_tag (38, "priv_write_dcache_tag", OpcodeType.PRIVELEGED), + opc_priv_write_dcache_data (39, "priv_write_dcache_data", OpcodeType.PRIVELEGED), + opc_priv_write_icache_tag (46, "priv_write_icache_tag", OpcodeType.PRIVELEGED), + opc_priv_write_icache_data (47, "priv_write_icache_data", OpcodeType.PRIVELEGED), + opc_priv_reset (54, "priv_reset", OpcodeType.PRIVELEGED), + opc_priv_write_scache_data (55, "priv_write_scache_data", OpcodeType.PRIVELEGED), + opc_priv_read_reg_0 (64, "priv_read_reg_0", OpcodeType.PRIVELEGED), + opc_priv_read_reg_1 (65, "priv_read_reg_1", OpcodeType.PRIVELEGED), + opc_priv_read_reg_2 (66, "priv_read_reg_2", OpcodeType.PRIVELEGED), + opc_priv_read_reg_3 (67, "priv_read_reg_3", OpcodeType.PRIVELEGED), + opc_priv_read_reg_4 (68, "priv_read_reg_4", OpcodeType.PRIVELEGED), + opc_priv_read_reg_5 (69, "priv_read_reg_5", OpcodeType.PRIVELEGED), + opc_priv_read_reg_6 (70, "priv_read_reg_6", OpcodeType.PRIVELEGED), + opc_priv_read_reg_7 (71, "priv_read_reg_7", OpcodeType.PRIVELEGED), + opc_priv_read_reg_8 (72, "priv_read_reg_8", OpcodeType.PRIVELEGED), + opc_priv_read_reg_9 (73, "priv_read_reg_9", OpcodeType.PRIVELEGED), + opc_priv_read_reg_10 (74, "priv_read_reg_10", OpcodeType.PRIVELEGED), + opc_priv_read_reg_11 (75, "priv_read_reg_11", OpcodeType.PRIVELEGED), + opc_priv_read_reg_12 (76, "priv_read_reg_12", OpcodeType.PRIVELEGED), + opc_priv_read_reg_13 (77, "priv_read_reg_13", OpcodeType.PRIVELEGED), + opc_priv_read_reg_14 (78, "priv_read_reg_14", OpcodeType.PRIVELEGED), + opc_priv_read_reg_15 (79, "priv_read_reg_15", OpcodeType.PRIVELEGED), + opc_priv_read_reg_16 (80, "priv_read_reg_16", OpcodeType.PRIVELEGED), + opc_priv_read_reg_17 (81, "priv_read_reg_17", OpcodeType.PRIVELEGED), + opc_priv_read_reg_18 (82, "priv_read_reg_18", OpcodeType.PRIVELEGED), + opc_priv_read_reg_19 (83, "priv_read_reg_19", OpcodeType.PRIVELEGED), + opc_priv_read_reg_20 (84, "priv_read_reg_20", OpcodeType.PRIVELEGED), + opc_priv_read_reg_21 (85, "priv_read_reg_21", OpcodeType.PRIVELEGED), + opc_priv_read_reg_22 (86, "priv_read_reg_22", OpcodeType.PRIVELEGED), + opc_priv_read_reg_23 (87, "priv_read_reg_23", OpcodeType.PRIVELEGED), + opc_priv_read_reg_24 (88, "priv_read_reg_24", OpcodeType.PRIVELEGED), + opc_priv_read_reg_25 (89, "priv_read_reg_25", OpcodeType.PRIVELEGED), + opc_priv_read_reg_26 (90, "priv_read_reg_26", OpcodeType.PRIVELEGED), + opc_priv_read_reg_27 (91, "priv_read_reg_27", OpcodeType.PRIVELEGED), + opc_priv_read_reg_28 (92, "priv_read_reg_28", OpcodeType.PRIVELEGED), + opc_priv_read_reg_29 (93, "priv_read_reg_29", OpcodeType.PRIVELEGED), + opc_priv_read_reg_30 (94, "priv_read_reg_30", OpcodeType.PRIVELEGED), + opc_priv_read_reg_31 (95, "priv_read_reg_31", OpcodeType.PRIVELEGED), + opc_priv_write_reg_0 (96, "priv_write_reg_0", OpcodeType.PRIVELEGED), + opc_priv_write_reg_1 (97, "priv_write_reg_1", OpcodeType.PRIVELEGED), + opc_priv_write_reg_2 (98, "priv_write_reg_2", OpcodeType.PRIVELEGED), + opc_priv_write_reg_3 (99, "priv_write_reg_3", OpcodeType.PRIVELEGED), + opc_priv_write_reg_4 (100, "priv_write_reg_4", OpcodeType.PRIVELEGED), + opc_priv_write_reg_5 (101, "priv_write_reg_5", OpcodeType.PRIVELEGED), + opc_priv_write_reg_6 (102, "priv_write_reg_6", OpcodeType.PRIVELEGED), + opc_priv_write_reg_7 (103, "priv_write_reg_7", OpcodeType.PRIVELEGED), + opc_priv_write_reg_8 (104, "priv_write_reg_8", OpcodeType.PRIVELEGED), + opc_priv_write_reg_9 (105, "priv_write_reg_9", OpcodeType.PRIVELEGED), + opc_priv_write_reg_10 (106, "priv_write_reg_10", OpcodeType.PRIVELEGED), + opc_priv_write_reg_11 (107, "priv_write_reg_11", OpcodeType.PRIVELEGED), + opc_priv_write_reg_12 (108, "priv_write_reg_12", OpcodeType.PRIVELEGED), + opc_priv_write_reg_13 (109, "priv_write_reg_13", OpcodeType.PRIVELEGED), + opc_priv_write_reg_14 (110, "priv_write_reg_14", OpcodeType.PRIVELEGED), + opc_priv_write_reg_15 (111, "priv_write_reg_15", OpcodeType.PRIVELEGED), + opc_priv_write_reg_16 (112, "priv_write_reg_16", OpcodeType.PRIVELEGED), + opc_priv_write_reg_17 (113, "priv_write_reg_17", OpcodeType.PRIVELEGED), + opc_priv_write_reg_18 (114, "priv_write_reg_18", OpcodeType.PRIVELEGED), + opc_priv_write_reg_19 (115, "priv_write_reg_19", OpcodeType.PRIVELEGED), + opc_priv_write_reg_20 (116, "priv_write_reg_20", OpcodeType.PRIVELEGED), + opc_priv_write_reg_21 (117, "priv_write_reg_21", OpcodeType.PRIVELEGED), + opc_priv_write_reg_22 (118, "priv_write_reg_22", OpcodeType.PRIVELEGED), + opc_priv_write_reg_23 (119, "priv_write_reg_23", OpcodeType.PRIVELEGED), + opc_priv_write_reg_24 (120, "priv_write_reg_24", OpcodeType.PRIVELEGED), + opc_priv_write_reg_25 (121, "priv_write_reg_25", OpcodeType.PRIVELEGED), + opc_priv_write_reg_26 (122, "priv_write_reg_26", OpcodeType.PRIVELEGED), + opc_priv_write_reg_27 (123, "priv_write_reg_27", OpcodeType.PRIVELEGED), + opc_priv_write_reg_28 (124, "priv_write_reg_28", OpcodeType.PRIVELEGED), + opc_priv_write_reg_29 (125, "priv_write_reg_29", OpcodeType.PRIVELEGED), + opc_priv_write_reg_30 (126, "priv_write_reg_30", OpcodeType.PRIVELEGED), + opc_priv_write_reg_31 (127, "priv_write_reg_31", OpcodeType.PRIVELEGED); + + private Integer value; + private String parsekey; + private String alias; + private Integer length; + private Integer baseVal; + private OpcodeType type; + + Opcode(Integer val, String parse, OpcodeType tp) { + init(val, parse, null, 2, tp); + } + + Opcode(Integer val, String parse, int len, OpcodeType tp) { + init(val, parse, null, len, tp); + } + + Opcode(Integer val, String parse) { + init(val, parse, null, 2, OpcodeType.NORMAL); + } + + Opcode(Integer val, String parse, int len) { + init(val, parse, null, len, OpcodeType.NORMAL); + } + + Opcode(Integer val, String parse, String als, int len) { + init(val, parse, als, len, OpcodeType.NORMAL); + } + + Opcode(Integer val, String parse, String als, int len, OpcodeType tp) { + init(val, parse, als, len, tp); + } + + private void init(Integer val, String parse, String als, int len, OpcodeType tp) { + type = tp; + baseVal = null; + switch (tp) { + case NORMAL: + value = val; + break; + case WIDE: + value = (opc_wide.value << 8) | val; + break; + case PRIVELEGED: + value = (opc_priv.value * 0xFF) + val; + baseVal = val; + break; + case NONPRIVELEGED: + value = (opc_nonpriv.value * 0xFF) + val; + baseVal = val; + break; + } + parsekey = parse; + alias = als; + length = len; + } + + public Integer value() { + return value; + } + + public int length() { + return length; + } + + public String parsekey() { + return parsekey; + } + + public OpcodeType type() { + return type; + } + } + +} diff --git a/test/lib/org/openjdk/asmtools/jasm/ParseBase.java b/test/lib/org/openjdk/asmtools/jasm/ParseBase.java new file mode 100644 index 00000000000..5e7dc96bcdf --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/ParseBase.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +/** + * + */ +public class ParseBase { + + protected boolean debugFlag; + protected Scanner scanner; + protected Parser parser; + protected Environment env; + + public ParseBase() { + init(null, null, null); + } + + public void init(Scanner scnr, Parser prsr, Environment envr) { + debugFlag = false; + scanner = scnr; + parser = prsr; + env = envr; + } + + public void enableDebug(boolean debState) { + debugFlag = debState; + } + + protected void debugStr(String str) { + if (debugFlag) { + env.traceln(str); + } + } + + protected void debugScan(String str) { + if (debugFlag) { + scanner.debugScan(str); + } + } + +} diff --git a/test/lib/org/openjdk/asmtools/jasm/Parser.java b/test/lib/org/openjdk/asmtools/jasm/Parser.java new file mode 100644 index 00000000000..c985ef1ed45 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/Parser.java @@ -0,0 +1,2087 @@ +/* + * Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import org.openjdk.asmtools.common.Module; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import static org.openjdk.asmtools.jasm.ConstantPool.*; +import static org.openjdk.asmtools.jasm.JasmTokens.Token; +import static org.openjdk.asmtools.jasm.JasmTokens.Token.*; +import static org.openjdk.asmtools.jasm.RuntimeConstants.*; +import static org.openjdk.asmtools.jasm.Tables.*; + +/** + * This class is used to parse Jasm statements and expressions. + * The result is a parse tree.

    + *

    + * This class implements an operator precedence parser. Errors are + * reported to the Environment object, if the error can't be + * resolved immediately, a SyntaxError exception is thrown.

    + *

    + * Error recovery is implemented by catching Scanner.SyntaxError exceptions + * and discarding input scanner.tokens until an input token is reached that + * is possibly a legal continuation.

    + *

    + * The parse tree that is constructed represents the input + * exactly (no rewrites to simpler forms). This is important + * if the resulting tree is to be used for code formatting in + * a programming environment. Currently only documentation comments + * are retained.

    + *

    + * A parser owns several components (scanner, constant-parser, + * instruction-parser, annotations-parser) to which it delegates certain + * parsing responsibilities. This parser contains functions to parse the + * overall form of a class, and any members (fields, methods, inner-classes). + *

    + *

    + * Syntax errors, should always be caught inside the + * parser for error recovery. + */ +class Parser extends ParseBase { + + /* Parser Fields */ + protected ConstantPool pool = null; + + ClassData cd = null; + + CodeAttr curCode; + + private ArrayList clsDataList = new ArrayList<>(); + private String pkg = null; + private String pkgPrefix = ""; + private ArrayList pkgAnnttns = null; + private ArrayList clsAnnttns = null; + private ArrayList memberAnnttns = null; + private boolean explicitcp = false; + private ModuleAttr moduleAttribute; + private CFVersion currentCFV; + /** + * other parser components + */ + private ParserAnnotation annotParser; // For parsing Annotations + private ParserCP cpParser; // for parsing Constants + private ParserInstr instrParser; // for parsing Instructions + + + /** + * Create a parser + */ + protected Parser(Environment sf, CFVersion cfVersion) throws IOException { + super.init(new Scanner(sf), this, sf); + this.currentCFV = cfVersion; + this.annotParser = new ParserAnnotation(scanner, this, env); + this.cpParser = new ParserCP(scanner, this, env); + this.instrParser = new ParserInstr(scanner, this, cpParser, env); + } + + void setDebugFlags(boolean debugScanner, boolean debugMembers, + boolean debugCP, boolean debugAnnot, boolean debugInstr) { + + enableDebug(debugMembers); + scanner.enableDebug(debugScanner); + cpParser.enableDebug(debugCP); + annotParser.enableDebug(debugAnnot); + instrParser.enableDebug(debugInstr); + } + + String encodeClassString(String classname) { + return "L" + classname + ";"; + } + + + /*-------------------------------------------------------- */ + + /** + * Parses version in package statements + */ + + private void parseVersionPkg() throws IOException { + if (scanner.token == SEMICOLON) { + return; + } + parse_ver: + { + if (scanner.token != Token.VERSION) { + break parse_ver; + } + scanner.scan(); + if (scanner.token != Token.INTVAL) { + break parse_ver; + } + currentCFV.setMajorVersion((short) scanner.intValue); + scanner.scan(); + if (scanner.token != Token.COLON) { + break parse_ver; + } + scanner.scan(); + if (scanner.token != Token.INTVAL) { + break parse_ver; + } + currentCFV.setMinorVersion((short) scanner.intValue); + scanner.scan(); + debugScan(" [Parser.parseVersionPkg]: " + currentCFV.asString()); + return; + } + env.error(scanner.pos, "version.expected"); + throw new Scanner.SyntaxError(); + } + + private void parseVersion() throws IOException { + if (scanner.token == Token.LBRACE) { + return; + } + parse_ver: + { + if (scanner.token != Token.VERSION) { + break parse_ver; + } + scanner.scan(); + if (scanner.token != Token.INTVAL) { + break parse_ver; + } + cd.cfv.setMajorVersion((short) scanner.intValue); + scanner.scan(); + if (scanner.token != Token.COLON) { + break parse_ver; + } + scanner.scan(); + if (scanner.token != Token.INTVAL) { + break parse_ver; + } + cd.cfv.setMinorVersion((short) scanner.intValue); + scanner.scan(); + debugStr("parseVersion: " + cd.cfv.asString()); + return; + } + env.error(scanner.pos, "version.expected"); + throw new Scanner.SyntaxError(); + } + + + /*---------------------------------------------*/ + + /** + * Parse an internal name: identifier. + */ + String parseIdent() throws Scanner.SyntaxError, IOException { + String v = scanner.idValue; + scanner.expect(Token.IDENT); + return v; + } + + /** + * Parse a local variable + */ + void parseLocVarDef() throws Scanner.SyntaxError, IOException { + if (scanner.token == Token.INTVAL) { + int v = scanner.intValue; + scanner.scan(); + curCode.LocVarDataDef(v); + } else { + String name = scanner.stringValue, type; + scanner.expect(Token.IDENT); + if (scanner.token == Token.COLON) { + scanner.scan(); + type = parseIdent(); + } else { + type = "I"; // TBD + } + curCode.LocVarDataDef(name, pool.FindCellAsciz(type)); + } + } + + Argument parseLocVarRef() throws Scanner.SyntaxError, IOException { + if (scanner.token == Token.INTVAL) { + int v = scanner.intValue; + scanner.scan(); + return new Argument(v); + } else { + String name = scanner.stringValue; + scanner.expect(Token.IDENT); + return curCode.LocVarDataRef(name); + } + } + + void parseLocVarEnd() throws Scanner.SyntaxError, IOException { + if (scanner.token == Token.INTVAL) { + int v = scanner.intValue; + scanner.scan(); + curCode.LocVarDataEnd(v); + } else { + String name = scanner.stringValue; + scanner.expect(Token.IDENT); + curCode.LocVarDataEnd(name); + } + } + + void parseMapItem(DataVector map) throws Scanner.SyntaxError, IOException { + StackMapType itemType = stackMapType(scanner.intValue, null); + ConstType tag = null; + Argument arg = null; + Token ptoken = scanner.token; + int iValue = scanner.intValue; + String sValue = scanner.stringValue; + scanner.scan(); + resolve: + { + switch (ptoken) { + case INTVAL: + break resolve; + case CLASS: + itemType = StackMapType.ITEM_Object; + tag = ConstType.CONSTANT_CLASS; + break resolve; + case CPINDEX: + itemType = StackMapType.ITEM_Object; + arg = pool.getCell(iValue); + break resolve; + case IDENT: + itemType = stackMapType(sValue); + tag = Tables.tag(sValue); + if (itemType != null) { // itemType OK + if ((tag != null) // ambiguity: "int," or "int 77,"? + && (scanner.token != SEMICOLON) + && (scanner.token != COMMA)) { + itemType = StackMapType.ITEM_Object; + } + break resolve; + } else if (tag != null) { // tag OK + itemType = StackMapType.ITEM_Object; + break resolve; + } + } + // resolution failed: + itemType = StackMapType.ITEM_Bogus; + env.error("itemtype.expected", "<" + ptoken.printValue() + ">"); + } + switch (itemType) { + case ITEM_Object: // followed by CP index + if (arg == null) { + arg = pool.FindCell(cpParser.parseConstValue(tag)); + } + map.addElement(new StackMapData.StackMapItem2(itemType, arg)); + break; + case ITEM_NewObject: // followed by label + arg = instrParser.parseLabelRef(); + map.addElement(new StackMapData.StackMapItem2(itemType, arg)); + break; + default: + map.addElement(new StackMapData.StackMapItem1(itemType)); + } + } + + /** + * Parse an external name: CPINDEX, string, or identifier. + */ + ConstCell parseName() throws Scanner.SyntaxError, IOException { + debugScan("------- [Parser.parseName]: "); + String v; + switch (scanner.token) { + case CPINDEX: { + int cpx = scanner.intValue; + scanner.scan(); + return pool.getCell(cpx); + } + case STRINGVAL: + v = scanner.stringValue; + scanner.scan(); + return pool.FindCellAsciz(v); + + // In many cases, Identifiers can correctly have the same + // names as keywords. We need to allow these. + case OPEN: + case MODULE: + case VARARGS: + case REQUIRES: + case EXPORTS: + case TO: + case USES: + case PROVIDES: + case WITH: + case OPENS: + + case ARRAY_TYPEPATH: + case INNER_TYPE_TYPEPATH: + case WILDCARD_TYPEPATH: + case TYPE_ARGUMENT_TYPEPATH: + case PERMITTEDSUBCLASSES: + case INF: + case NAN: + case COMPONENT: + + case SYNTHETIC: + case DEPRECATED: + case VERSION: + case BITS: + case STACK: + case LOCAL: + case OF: + case INNERCLASS: + case STRICT: + case FIELDREF: + case METHODREF: + case IDENT: + case BRIDGE: + case VALUE: + case PRIMITIVE: + v = scanner.idValue; + scanner.scan(); + return pool.FindCellAsciz(v); + default: + env.error(scanner.pos, "name.expected", scanner.token); + throw new Scanner.SyntaxError(); + } + } + + /** + * Parses a field or method reference for method handle. + */ + ConstCell parseMethodHandle(SubTag subtag) throws Scanner.SyntaxError, IOException { + ConstCell refCell; + final int pos = parser.env.pos; + switch (subtag) { + // If the value of the reference_kind item is + // 1 (REF_getField), 2 (REF_getStatic), 3 (REF_putField) or 4 (REF_putStatic), + // then the constant_pool entry at that index must be a CONSTANT_Fieldref_info structure (4.4.2) + // representing a field for which a method handle is to be created. jvms-4.4.8-200-C-A + case REF_GETFIELD: + case REF_GETSTATIC: + case REF_PUTFIELD: + case REF_PUTSTATIC: + refCell = pool.FindCell(cpParser.parseConstValue(ConstType.CONSTANT_FIELD)); + break; + // If the value of the reference_kind item is + // 5 (REF_invokeVirtual) or 8 (REF_newInvokeSpecial), + // then the constant_pool entry at that index must be a CONSTANT_Methodref_info structure (4.4.2) + // representing a class's method or constructor (2.9.1) for which a method handle is to be created. + // jvms-4.4.8-200-C-B + case REF_INVOKEVIRTUAL: + case REF_NEWINVOKESPECIAL: + cpParser.setExitImmediately(true); + refCell = cpParser.parseConstRef(ConstType.CONSTANT_METHOD, ConstType.CONSTANT_INTERFACEMETHOD); + cpParser.setExitImmediately(false); + checkReferenceIndex(pos, ConstType.CONSTANT_METHOD, null); + break; + case REF_INVOKESTATIC: + case REF_INVOKESPECIAL: + // CODETOOLS-7902333 + // 4.4.8. The CONSTANT_MethodHandle_info Structure + // reference_index + // The value of the reference_index item must be a valid index into the constant_pool table. + // The constant_pool entry at that index must be as follows: + // If the value of the reference_kind item is 6 (REF_invokeStatic) or 7 (REF_invokeSpecial), + // then if the class file version number is less than 52.0, the constant_pool entry at that index must be + // a CONSTANT_Methodref_info structure representing a class's method for which a method handle is to be created; + // if the class file version number is 52.0 or above, the constant_pool entry at that index must be + // either a CONSTANT_Methodref_info structure or a CONSTANT_InterfaceMethodref_info structure (4.4.2) + // representing a class's or interface's method for which a method handle is to be created. + ConstType ctype01 = ConstType.CONSTANT_METHOD; + ConstType ctype02 = ConstType.CONSTANT_INTERFACEMETHOD; + if (this.cd.cfv.major_version() >= 52 && Modifiers.isInterface(this.cd.access)) { + ctype01 = ConstType.CONSTANT_INTERFACEMETHOD; + ctype02 = ConstType.CONSTANT_METHOD; + } + cpParser.setExitImmediately(true); + refCell = cpParser.parseConstRef(ctype01, ctype02); + cpParser.setExitImmediately(false); + checkReferenceIndex(pos, ctype01, ctype02); + break; + + case REF_INVOKEINTERFACE: + cpParser.setExitImmediately(true); + refCell = cpParser.parseConstRef(ConstType.CONSTANT_INTERFACEMETHOD, ConstType.CONSTANT_METHOD); + cpParser.setExitImmediately(false); + checkReferenceIndex(pos, ConstType.CONSTANT_INTERFACEMETHOD, null); + break; + default: + // should not reach + throw new Scanner.SyntaxError(); + } + return refCell; + } + + /** + * Check the pair reference_kind:reference_index where reference_kind is any from: + * REF_invokeVirtual, REF_newInvokeSpecial, REF_invokeStatic, REF_invokeSpecial, REF_invokeInterface + * and reference_index is one of [Empty], Method or InterfaceMethod + * There are possible entries: + * ldc Dynamic REF_newInvokeSpecial:InterfaceMethod LdcConDyTwice."": + * ldc Dynamic REF_invokeInterface:LdcConDyTwice."": + * ldc Dynamic REF_newInvokeSpecial:Method LdcConDyTwice."": + * ldc MethodHandle REF_newInvokeSpecial:InterfaceMethod LdcConDyTwice."": + * ldc MethodHandle REF_invokeInterface:LdcConDyTwice."": + * ldc MethodHandle REF_newInvokeSpecial:Method LdcConDyTwice."": + * invokedynamic MethodHandle REF_invokeStatic:Method java/lang/invoke/StringConcatFactory.makeConcatWithConstants: + * invokedynamic MethodHandle REF_invokeStatic:java/lang/invoke/StringConcatFactory.makeConcatWithConstants + * .... + * @param position the position in a source file + * @param defaultTag expected reference_index tag (Method or InterfaceMethod) + * @param defaultTag 2nd expected reference_index tag (Method or InterfaceMethod) + */ + private void checkReferenceIndex(int position, ConstType defaultTag, ConstType default2Tag) { + if ( ! scanner.token.in(COLON, SEMICOLON) ) { + if (default2Tag != null) { + env.error(position, "wrong.tag2", defaultTag.parseKey(), default2Tag.parseKey()); + } else { + env.error(position, "wrong.tag", defaultTag.parseKey()); + } + throw new Scanner.SyntaxError().Fatal(); + } + } + + /** + * Parses a sub-tag value in method handle. + */ + SubTag parseSubtag() throws Scanner.SyntaxError, IOException { + SubTag subtag = null; + switch (scanner.token) { + case IDENT: + subtag = subtag(scanner.stringValue); + break; + case INTVAL: + subtag = subtag(scanner.intValue); + break; + } + if (subtag == null) { + env.error("subtag.expected"); + throw new Scanner.SyntaxError(); + } + scanner.scan(); + return subtag; + } + + ConstCell parseClassName(boolean uncond) throws Scanner.SyntaxError, IOException { + String v; + switch (scanner.token) { + case CPINDEX: { + int cpx = scanner.intValue; + scanner.scan(); + return pool.getCell(cpx); + } + case STRINGVAL: + v = scanner.stringValue; + scanner.scan(); + v = prependPackage(v, uncond); + return pool.FindCellAsciz(v); + // Some identifiers might coincide with token names. + // these should be OK to use as identifier names. + case OPEN: + case MODULE: + case VARARGS: + case REQUIRES: + case EXPORTS: + case TO: + case USES: + case PROVIDES: + case WITH: + case OPENS: + + case ARRAY_TYPEPATH: + case INNER_TYPE_TYPEPATH: + case WILDCARD_TYPEPATH: + case TYPE_ARGUMENT_TYPEPATH: + case PERMITTEDSUBCLASSES: + case INF: + case NAN: + case COMPONENT: + + case SYNTHETIC: + case DEPRECATED: + case VERSION: + case BITS: + case STACK: + case LOCAL: + case OF: + case INNERCLASS: + case STRICT: + case FIELDREF: + case METHODREF: + case BRIDGE: + case IDENT: + case VALUE: + case PRIMITIVE: + v = scanner.idValue; + scanner.scan(); + v = prependPackage(v, uncond); + return pool.FindCellAsciz(v); + default: + ConstType key = Tables.tag(scanner.token.value()); + env.traceln("%%%%% Unrecognized token [" + scanner.token + "]: '" + (key == null ? "null" : key.parseKey()) + "'."); + env.error(scanner.prevPos, "name.expected", "\"" + scanner.token.parseKey() + "\""); + throw new Scanner.SyntaxError(); + } + } + + private String prependPackage(String className, boolean uncond) { + if (uncond || (scanner.token == Token.FIELD)) { + if ((!className.contains("/")) // class identifier doesn't contain "/" + && (!className.contains("["))) { // class identifier doesn't contain "[" + className = pkgPrefix + className; // add package + } + } + return className; + } + + /** + * Parse a signed integer of size bytes long. + * size = 1 or 2 + */ + Argument parseInt(int size) throws Scanner.SyntaxError, IOException { + if (scanner.token == Token.BITS) { + scanner.scan(); + } + if (scanner.token != Token.INTVAL) { + env.error(scanner.pos, "int.expected"); + throw new Scanner.SyntaxError(); + } + int arg = scanner.intValue * scanner.sign; + switch (size) { + case 1: +// if ((arg>127)||(arg<-128)) { // 0xFF not allowed + if ((arg > 255) || (arg < -128)) { // to allow 0xFF + env.error(scanner.pos, "value.large", "1 byte"); + throw new Scanner.SyntaxError(); + } + break; + case 2: +// if ((arg > 32767) || (arg < -32768)) { //this seems +// natural but is not backward compatible. Some tests contain +// expressions like: +// sipush 0x8765; + + if ((arg > 65535) || (arg < -32768)) { + env.error(scanner.pos, "value.large", "2 bytes"); + throw new Scanner.SyntaxError(); + } + break; + default: + throw new InternalError("parseInt(" + size + ")"); + } + scanner.scan(); + return new Argument(arg); + } + + /** + * Parse an unsigned integer of size bytes long. + * size = 1 or 2 + */ + Argument parseUInt(int size) throws Scanner.SyntaxError, IOException { + if (scanner.token != Token.INTVAL) { + env.error(scanner.pos, "int.expected"); + throw new Scanner.SyntaxError(); + } + if (scanner.sign == -1) { + env.error(scanner.pos, "neg.forbidden"); + throw new Scanner.SyntaxError(); + } + int arg = scanner.intValue; + switch (size) { + case 1: + if (arg > 255) { + env.error(scanner.pos, "value.large", "1 byte"); + throw new Scanner.SyntaxError(); + } + break; + case 2: + if (arg > 65535) { + env.error(scanner.pos, "value.large", "2 bytes"); + throw new Scanner.SyntaxError(); + } + break; + default: + throw new InternalError("parseUInt(" + size + ")"); + } + scanner.scan(); + return new Argument(arg); + } + + /** + * Parse constant declaration + */ + private void parseConstDef() throws IOException { + for (; ; ) { + if (scanner.token == Token.CPINDEX) { + int cpx = scanner.intValue; + scanner.scan(); + scanner.expect(Token.ASSIGN); + env.traceln("parseConstDef:" + cpx); + pool.setCell(cpx, cpParser.parseConstRef(null)); + } else { + env.error("const.def.expected"); + throw new Scanner.SyntaxError(); + } + if (scanner.token != COMMA) { + scanner.expect(SEMICOLON); + return; + } + scanner.scan(); // COMMA + } + } + + /** + * Parse the modifiers + */ + private int scanModifier(int mod) throws IOException { + int nextmod, prevpos; + + while (true) { + nextmod = 0; + switch (scanner.token) { + case PUBLIC: + nextmod = ACC_PUBLIC; + break; + case PRIVATE: + nextmod = ACC_PRIVATE; + break; + case PROTECTED: + nextmod = ACC_PROTECTED; + break; + case STATIC: + nextmod = ACC_STATIC; + break; + case FINAL: + nextmod = ACC_FINAL; + break; + case SYNCHRONIZED: + nextmod = ACC_SYNCHRONIZED; + break; + case SUPER: + nextmod = ACC_SUPER; + break; + case VOLATILE: + nextmod = ACC_VOLATILE; + break; + case BRIDGE: + nextmod = ACC_BRIDGE; + break; + case TRANSIENT: + nextmod = ACC_TRANSIENT; + break; + case VARARGS: + nextmod = ACC_VARARGS; + break; + case NATIVE: + nextmod = ACC_NATIVE; + break; + case INTERFACE: + nextmod = ACC_INTERFACE; + break; + case ABSTRACT: + nextmod = ACC_ABSTRACT; + break; + case STRICT: + nextmod = ACC_STRICT; + break; + case ENUM: + nextmod = ACC_ENUM; + break; + case SYNTHETIC: + nextmod = ACC_SYNTHETIC; + break; + case ANNOTATION_ACCESS: + nextmod = ACC_ANNOTATION; + break; + + case DEPRECATED: + nextmod = DEPRECATED_ATTRIBUTE; + break; + case MANDATED: + nextmod = ACC_MANDATED; + break; + case VALUE: + nextmod = ACC_VALUE; + break; + case PRIMITIVE: + nextmod = ACC_PRIMITIVE; + break; + default: + return nextmod; + } + prevpos = scanner.pos; + scanner.scan(); + if ((mod & nextmod) == 0) { + return nextmod; + } + env.error(prevpos, "warn.repeated.modifier"); + } + } + + int scanModifiers() throws IOException { + int mod = 0, nextmod; + + while (true) { + nextmod = scanModifier(mod); + if (nextmod == 0) { + return mod; + } + mod = mod | nextmod; + } + } + + /** + * Parse a field. + */ + private void parseField(int mod) throws Scanner.SyntaxError, IOException { + debugStr(" [Parser.parseField]: <<>>"); + // check access modifiers: + Modifiers.checkFieldModifiers(cd, mod, scanner.pos); + + while (true) { + ConstCell nameCell = parseName(); + scanner.expect(Token.COLON); + ConstCell typeCell = parseName(); + + // Define the variable + FieldData fld = cd.addField(mod, nameCell, typeCell); + + if (memberAnnttns != null) { + fld.addAnnotations(memberAnnttns); + } + + // Parse the optional attribute: signature + if (scanner.token == Token.COLON) { + scanner.scan(); + ConstCell signatureCell = parseName(); + fld.setSignatureAttr(signatureCell); + } + + // Parse the optional initializer + if (scanner.token == Token.ASSIGN) { + scanner.scan(); + fld.SetValue(cpParser.parseConstRef(null)); + } + + // If the next scanner.token is a comma, then there is more + debugScan(" [Parser.parseField]: Field: " + fld + " "); + + if (scanner.token != COMMA) { + scanner.expect(SEMICOLON); + return; + } + scanner.scan(); + } // end while + } // end parseField + + /** + * Scan method's signature to determine size of parameters. + */ + private int countParams(ConstCell sigCell) throws Scanner.SyntaxError { + String sig; + try { + ConstValue_String strConst = (ConstValue_String) sigCell.ref; + sig = strConst.value; + } catch (NullPointerException | ClassCastException e) { + return 0; // ??? TBD + } + int siglen = sig.length(), k = 0, loccnt = 0, errparam = 0; + boolean arraytype = false; + scan: + { + if (k >= siglen) { + break scan; + } + if (sig.charAt(k) != '(') { + errparam = 1; + break scan; + } + for (k = 1; k < siglen; k++) { + switch (sig.charAt(k)) { + case ')': + if (arraytype) { + errparam = 2; + break scan; + } + return loccnt; + case '[': + arraytype = true; + break; + case 'B': + case 'C': + case 'F': + case 'I': + case 'S': + case 'Z': + loccnt++; + arraytype = false; + break; + case 'D': + case 'J': + loccnt++; + if (arraytype) { + arraytype = false; + } else { + loccnt++; + } + break; + case 'L': + case 'Q': + for (; ; k++) { + if (k >= siglen) { + errparam = 3; + break scan; + } + if (sig.charAt(k) == ';') { + break; + } + } + loccnt++; + arraytype = false; + break; + default: + errparam = 4; + break scan; + } + } + } + env.error(scanner.pos, "msig.malformed", Integer.toString(k), Integer.toString(errparam)); + return loccnt; + } + + /** + * Parse a method. + */ + private void parseMethod(int mod) throws Scanner.SyntaxError, IOException { + + // The start of the method + int posa = scanner.pos; + debugStr(" [Parser.parseMethod]: <<>>"); + + ConstCell nameCell = parseName(); + ConstValue_String strConst = (ConstValue_String) nameCell.ref; + String name = strConst.value; + boolean is_clinit = name.equals(""); + boolean is_init = name.equals("") + && !Modifiers.isStatic(mod); // TODO: not a good way to detect factories... + DefaultAnnotationAttr defAnnot = null; + + // check access modifiers: + Modifiers.checkMethodModifiers(cd, mod, posa, is_init, is_clinit); + + scanner.expect(Token.COLON); + ConstCell typeCell = parseName(); + int paramcnt = countParams(typeCell); + if ((!Modifiers.isStatic(mod)) && !is_clinit) { + paramcnt++; + } + if (paramcnt > 255) { + env.error(scanner.pos, "warn.msig.more255", Integer.toString(paramcnt)); + } + // Parse throws clause + ArrayList exc_table = null; + if (scanner.token == Token.THROWS) { + scanner.scan(); + exc_table = new ArrayList<>(); + for (; ; ) { + posa = scanner.pos; + ConstCell exc = cpParser.parseConstRef(ConstType.CONSTANT_CLASS); + if (exc_table.contains(exc)) { + env.error(posa, "warn.exc.repeated"); + } else { + exc_table.add(exc); + env.traceln("THROWS:" + exc.arg); + } + if (scanner.token != COMMA) { + break; + } + scanner.scan(); + } + } + if (scanner.token == Token.DEFAULT) { + // need to scan the annotation value + defAnnot = annotParser.parseDefaultAnnotation(); + } + + MethodData curMethod = cd.StartMethod(mod, nameCell, typeCell, exc_table); + Argument max_stack = null, max_locals = null; + + if (scanner.token == Token.STACK) { + scanner.scan(); + max_stack = parseUInt(2); + } + if (scanner.token == Token.LOCAL) { + scanner.scan(); + max_locals = parseUInt(2); + } + if (scanner.token == Token.INTVAL) { + annotParser.parseParamAnnots(paramcnt, curMethod); + } + + if (scanner.token == SEMICOLON) { + if ((max_stack != null) || (max_locals != null)) { + env.error("token.expected", "{"); + } + scanner.scan(); + } else { + scanner.expect(Token.LBRACE); + curCode = curMethod.startCode(posa, paramcnt, max_stack, max_locals); + while ((scanner.token != Token.EOF) && (scanner.token != Token.RBRACE)) { + instrParser.parseInstr(); + if (scanner.token == Token.RBRACE) { + break; + } + // code's type annotation(s) + if (scanner.token == Token.ANNOTATION) { + curCode.addAnnotations(annotParser.scanAnnotations()); + break; + } + scanner.expect(SEMICOLON); + } + curCode.endCode(); + scanner.expect(Token.RBRACE); + } + + if (defAnnot != null) { + curMethod.addDefaultAnnotation(defAnnot); + } + if (memberAnnttns != null) { + curMethod.addAnnotations(memberAnnttns); + } + cd.EndMethod(); + debugStr(" [Parser.parseMethod]: Method: " + curMethod); + + } // end parseMethod + + /** + * Parse a (CPX based) BootstrapMethod entry. + */ + private void parseCPXBootstrapMethod() throws Scanner.SyntaxError, IOException { + // Parses in the form: + // BOOTSTRAPMETHOD CPX_MethodHandle (CPX_Arg)* ; + if (scanner.token == Token.CPINDEX) { + // CPX can be a CPX to an MethodHandle constant, + int cpx = scanner.intValue; + ConstCell MHCell = pool.getCell(cpx); + scanner.scan(); + ArrayList bsm_args = new ArrayList<>(256); + + while (scanner.token != SEMICOLON) { + if (scanner.token == Token.CPINDEX) { + bsm_args.add(pool.getCell(scanner.intValue)); + + } else { + // throw error, bootstrap method is not recognizable + env.error(scanner.pos, "invalid.bootstrapmethod"); + throw new Scanner.SyntaxError(); + } + scanner.scan(); + } + BootstrapMethodData bsmData = new BootstrapMethodData(MHCell, bsm_args); + cd.addBootstrapMethod(bsmData); + } else { + // throw error, bootstrap method is not recognizable + env.error(scanner.pos, "invalid.bootstrapmethod"); + throw new Scanner.SyntaxError(); + } + } + + /** + * Parse a NestHost entry + */ + private void parseNestHost() throws Scanner.SyntaxError, IOException { + // Parses in the form: + // NESTHOST IDENT; + debugStr(" [Parser.parseNestHost]: <<>>"); + String className = prependPackage(parseIdent(), true); + ConstCell hostClass = pool.FindCellClassByName(className); + debugScan(" [Parser.parseNestHost]: NestHost: class " + className); + scanner.expect(SEMICOLON); + cd.addNestHost(hostClass); + } + + /** + * Parse a list of classes belonging to the + * [NestMembers | PermittedSubclasses | Preload] entry + */ + private void parseClasses(Consumer> classesConsumer) + throws Scanner.SyntaxError, IOException { + ArrayList classes = new ArrayList<>(); + // Parses in the form: + // (NESTMEMBERS|PERMITTEDSUBCLASSES)? IDENT(, IDENT)*; + debugStr(" [Parser.parseClasses]: <<>>"); + while (true) { + String className = prependPackage(parseIdent(), true); + classes.add(pool.FindCellClassByName(className)); + debugScan(" [Parser.parseClasses]: class " + className); + if (scanner.token != COMMA) { + scanner.expect(SEMICOLON); + classesConsumer.accept(classes); + return; + } + scanner.scan(); + } + } + + /** + * Parse the Record entry + */ + private void parseRecord() throws Scanner.SyntaxError, IOException { + // Parses in the form: + // RECORD { (COMPONENT)+ } + // where + // COMPONENT Component (ANNOTATION)* NAME:DESCRIPTOR(:SIGNATURE)? (,|;) + // NAME = (CPINDEX | IDENT) + // DESCRIPTOR = (CPINDEX | STRING) + // SIGNATURE = (CPINDEX | STRING) + debugScan("[Parser.parseRecord]: Begin"); + scanner.expect(Token.LBRACE); + + ArrayList componentAnntts = null; + boolean grouped = false; + RecordData rd = cd.setRecord(scanner.pos); + + while (true) { + if (scanner.token == Token.RBRACE) { + if (rd.isEmpty()) { + env.error(scanner.pos, "warn.no.components.in.record.attribute"); + cd.rejectRecord(); + } else if (grouped) { + env.error(scanner.pos, "grouped.component.expected"); + } + scanner.scan(); + break; + } + + ConstCell nameCell, descCell, signatureCell = null; + if (scanner.token == Token.ANNOTATION) { + componentAnntts = annotParser.scanAnnotations(); + } + + scanner.expect(Token.COMPONENT); + + nameCell = parseName(); + scanner.expect(Token.COLON); + descCell = parseName(); + // Parse the optional attribute: signature + if (scanner.token == Token.COLON) { + scanner.scan(); + signatureCell = parseName(); + } + + rd.addComponent(nameCell, descCell, signatureCell, componentAnntts); + + switch (scanner.token) { + case COMMA: + grouped = true; + break; + case SEMICOLON: + grouped = false; + componentAnntts = null; + break; + default: + env.error(scanner.pos, "one.of.two.token.expected", + "<" + SEMICOLON.printValue() + ">", + "<" + COMMA.printValue() + ">"); + break; + } + // next component + scanner.scan(); + } // end while + debugScan("[Parser.parseRecord]: End"); + } + + /** + * Parse an inner class. + */ + private void parseInnerClass(int mod) throws Scanner.SyntaxError, IOException { + // Parses in the form: + // MODIFIERS (INNERCLASSNAME =)? (INNERCLASS) (OF OUTERCLASS)? ; + // + // where + // INNERCLASSNAME = (IDENT | CPX_IN-CL-NM) + // INNERCLASS = (CLASS IDENT | CPX_IN-CL) (S2) + // OUTERCLASS = (CLASS IDENT | CPX_OT-CL) (S3) + // + // Note: + // If a class reference cannot be identified using IDENT, CPX indexes must be used. + + // check access modifiers: + debugScan("[Parser.parseInnerClass]: Begin "); + Modifiers.checkInnerClassModifiers(cd, mod, scanner.pos); + + ConstCell nameCell; + ConstCell innerClass = null; + ConstCell outerClass = null; + + + if (scanner.token == Token.CLASS) { + nameCell = pool.getCell(0); // no NameIndex + parseInnerClass_s2(mod, nameCell, innerClass, outerClass); + } else { + if ((scanner.token == Token.IDENT) || scanner.checkTokenIdent()) { + // Got a Class Name + nameCell = parseName(); + parseInnerClass_s1(mod, nameCell, innerClass, outerClass); + } else if (scanner.token == Token.CPINDEX) { + // CPX can be either a CPX to an InnerClassName, + // or a CPX to an InnerClassInfo + int cpx = scanner.intValue; + nameCell = pool.getCell(cpx); + ConstValue nameCellValue = nameCell.ref; + + if (nameCellValue instanceof ConstValue_String) { + // got a name cell + scanner.scan(); + parseInnerClass_s1(mod, nameCell, innerClass, outerClass); + } else { + // got a CPRef cell + nameCell = pool.getCell(0); // no NameIndex + parseInnerClass_s2(mod, nameCell, innerClass, outerClass); + } + } else { + pic_error(); + } + + } + } + + private void parseInnerClass_s1(int mod, ConstCell nameCell, ConstCell innerClass, ConstCell outerClass) throws IOException { + // next scanner.token must be '=' + if (scanner.token == Token.ASSIGN) { + scanner.scan(); + parseInnerClass_s2(mod, nameCell, innerClass, outerClass); + } else { + pic_error(); + } + + } + + private void parseInnerClass_s2(int mod, ConstCell nameCell, ConstCell innerClass, ConstCell outerClass) throws IOException { + // scanner.token is either "CLASS IDENT" or "CPX_Class" + if ((scanner.token == Token.CPINDEX) || (scanner.token == Token.CLASS)) { + if (scanner.token == Token.CPINDEX) { + innerClass = cpParser.parseConstRef(ConstType.CONSTANT_CLASS); + } + + if (scanner.token == Token.CLASS) { + // next symbol needs to be InnerClass + scanner.scan(); + innerClass = cpParser.parseConstRef(ConstType.CONSTANT_CLASS); + } + + // See if declaration is terminated + if (scanner.token == SEMICOLON) { + // InnerClass is complete, no OUTERINFO; + outerClass = pool.getCell(0); + pic_tracecreate(mod, nameCell, innerClass, outerClass); + cd.addInnerClass(mod, nameCell, innerClass, outerClass); + } else if (scanner.token == Token.OF) { + // got an outer class reference + parseInnerClass_s3(mod, nameCell, innerClass, outerClass); + } else { + pic_error(); + } + + } else { + pic_error(); + } + + } + + private void parseInnerClass_s3(int mod, ConstCell nameCell, ConstCell innerClass, ConstCell outerClass) throws IOException { + scanner.scan(); + if ((scanner.token == Token.CLASS) || (scanner.token == Token.CPINDEX)) { + if (scanner.token == Token.CLASS) { + // next symbol needs to be InnerClass + scanner.scan(); + outerClass = cpParser.parseConstRef(ConstType.CONSTANT_CLASS); + } + if (scanner.token == Token.CPINDEX) { + outerClass = cpParser.parseConstRef(ConstType.CONSTANT_CLASS); + } + + if (scanner.token == SEMICOLON) { + pic_tracecreate(mod, nameCell, innerClass, outerClass); + cd.addInnerClass(mod, nameCell, innerClass, outerClass); + } else { + pic_error(); + } + } else { + pic_error(); + } + } + + private void pic_tracecreate(int mod, ConstCell nameCell, ConstCell innerClass, ConstCell outerClass) { + // throw error, IC is not recognizable + env.trace(" Creating InnerClass: [" + Modifiers.toString(mod, CF_Context.CTX_INNERCLASS) + "], "); + + if (nameCell != pool.getCell(0)) { + ConstValue value = nameCell.ref; + if (value != null) { + env.trace(value.toString() + " = "); + } + } + + ConstValue_Cell ici_val = (ConstValue_Cell) innerClass.ref; + ConstCell ici_ascii = ici_val.cell; + // Constant pool may not be numberized yet. + // + // check values before dereference on a trace. + if (ici_ascii.ref == null) { + env.trace("<#cpx-unresolved> "); + } else { + ConstValue_String cval = (ConstValue_String) ici_ascii.ref; + if (cval.value == null) { + env.trace("<#cpx-0> "); + } else { + env.trace(cval.value + " "); + } + } + + if (outerClass != pool.getCell(0)) { + if (outerClass.arg != 0) { + ConstValue_Cell oci_val = (ConstValue_Cell) outerClass.ref; + ConstCell oci_ascii = oci_val.cell; + if (oci_ascii.ref == null) { + env.trace(" of <#cpx-unresolved> "); + } else { + ConstValue_String cval = (ConstValue_String) oci_ascii.ref; + if (cval.value == null) { + env.trace(" of <#cpx-0> "); + } else { + env.trace(" of " + cval.value); + } + } + } + } + + env.traceln(""); + } + + private void pic_error() { + // throw error, IC is not recognizable + env.error(scanner.pos, "invalid.innerclass"); + throw new Scanner.SyntaxError(); + } + + /** + * The match() method is used to quickly match opening + * brackets (ie: '(', '{', or '[') with their closing + * counter part. This is useful during error recovery.

    + *

    + * Scan to a matching '}', ']' or ')'. The current scanner.token must be + * a '{', '[' or '('; + */ + private void match(Token open, Token close) throws IOException { + int depth = 1; + + while (true) { + scanner.scan(); + if (scanner.token == open) { + depth++; + } else if (scanner.token == close) { + if (--depth == 0) { + return; + } + } else if (scanner.token == Token.EOF) { + env.error(scanner.pos, "unbalanced.paren"); + return; + } + } + } + + /** + * Recover after a syntax error in a field. This involves + * discarding scanner.tokens until an EOF or a possible legal + * continuation is encountered. + */ + private void recoverField() throws Scanner.SyntaxError, IOException { + while (true) { + switch (scanner.token) { + case EOF: + case STATIC: + case FINAL: + case PUBLIC: + case PRIVATE: + case SYNCHRONIZED: + case TRANSIENT: + case PROTECTED: + case VOLATILE: + case NATIVE: +// case INTERFACE: see below + case ABSTRACT: + case ANNOTATION_ACCESS: + // possible begin of a field, continue + return; + + case LBRACE: + match(Token.LBRACE, Token.RBRACE); + scanner.scan(); + break; + + case LPAREN: + match(Token.LPAREN, Token.RPAREN); + scanner.scan(); + break; + + case LSQBRACKET: + match(Token.LSQBRACKET, Token.RSQBRACKET); + scanner.scan(); + break; + + case RBRACE: + case INTERFACE: + case CLASS: + case IMPORT: + case PACKAGE: + // begin of something outside a class, panic more + endClass(); + scanner.debugStr(" [Parser.recoverField]: pos: [" + scanner.pos + "]: "); + throw new Scanner.SyntaxError().Fatal(); + default: + // don't know what to do, skip + scanner.scan(); + break; + } + } + } + + /** + * Parse a class or interface declaration. + */ + private void parseClass(int mod) throws IOException { + int posa = scanner.pos; + debugStr(" [Parser.parseClass]: Begin "); + // check access modifiers: + Modifiers.checkClassModifiers(env, mod, scanner); + + if (cd == null) { + cd = new ClassData(env, currentCFV.clone()); + pool = cd.pool; + } + + if (clsAnnttns != null) { + cd.addAnnotations(clsAnnttns); + } + + // move the tokenizer to the identifier: + if (scanner.token == Token.CLASS) { + scanner.scan(); + } else if (scanner.token == Token.ANNOTATION) { + scanner.scan(); + if (scanner.token == Token.INTERFACE) { + mod |= ACC_ANNOTATION | ACC_INTERFACE; + scanner.scan(); + } else { + env.error(scanner.prevPos, "token.expected", Token.ANNOTATION.parseKey() + Token.INTERFACE.parseKey()); + throw new Scanner.SyntaxError(); + } + } + + // Parse the class name + ConstCell nm = cpParser.parseConstRef(ConstType.CONSTANT_CLASS, null, true); + + if (scanner.token == Token.FIELD) { // DOT + String fileExtension; + scanner.scan(); + switch (scanner.token) { + case STRINGVAL: + fileExtension = scanner.stringValue; + break; + case IDENT: + fileExtension = scanner.idValue; + break; + default: + env.error(scanner.pos, "name.expected"); + throw new Scanner.SyntaxError(); + } + scanner.scan(); + cd.fileExtension = "." + fileExtension; + } else if (scanner.token == Token.MODULE) { + env.error(scanner.prevPos, "token.expected", Token.OPEN.parseKey()); + throw new Scanner.SyntaxError(); + } else if (scanner.token == SEMICOLON) { + // drop the semi-colon following a name + scanner.scan(); + } + + // Parse extends clause + ConstCell sup = null; + if (scanner.token == Token.EXTENDS) { + scanner.scan(); + sup = cpParser.parseConstRef(ConstType.CONSTANT_CLASS); + while (scanner.token == COMMA) { + scanner.scan(); + env.error(posa, "multiple.inherit"); + cpParser.parseConstRef(ConstType.CONSTANT_CLASS); + } + } + + // Parse implements clause + ArrayList impl = new ArrayList<>(); + if (scanner.token == Token.IMPLEMENTS) { + do { + scanner.scan(); + Argument intf = cpParser.parseConstRef(ConstType.CONSTANT_CLASS); + if (impl.contains(intf)) { + env.error(posa, "warn.intf.repeated", intf); + } else { + impl.add(intf); + } + } while (scanner.token == COMMA); + } + parseVersion(); + scanner.expect(Token.LBRACE); + + // Begin a new class + cd.init(mod, nm, sup, impl); + + // Parse constant declarations + + // Parse class members + while ((scanner.token != Token.EOF) && (scanner.token != Token.RBRACE)) { + switch (scanner.token) { + case SEMICOLON: + // Empty fields are allowed + scanner.scan(); + break; + case CONST: + scanner.scan(); + parseConstDef(); + explicitcp = true; + break; + default: // scanner.token is some member. + parseClassMembers(); + } // end switch + } // while + scanner.expect(Token.RBRACE); + // End the class + endClass(); + } // end parseClass + + /** + * Parses a package or type name in a module statement(s) + */ + private String parseTypeName() throws IOException { + String name = "", field = ""; + while (true) { + if (scanner.token.possibleModuleName()) { + name = name + field + scanner.idValue; + scanner.scan(); + } else { + env.error(scanner.pos, "name.expected", "\"" + scanner.token.parseKey() + "\""); + throw new Scanner.SyntaxError(); + } + if (scanner.token == Token.FIELD) { + env.error(scanner.pos, "warn.dot.will.be.converted"); + field = "/"; + scanner.scan(); + } else { + break; + } + } + return name; + } + + /** + * Parses a module name in a module statement(s) + */ + private String parseModuleName() throws IOException { + String name = "", field = ""; + while (true) { + if (scanner.token.possibleModuleName()) { + name = name + field + scanner.idValue; + scanner.scanModuleStatement(); + } else { + env.error(scanner.pos, "module.name.expected", "\"" + scanner.token.parseKey() + "\""); + throw new Scanner.SyntaxError().Fatal(); + } + if (scanner.token == Token.FIELD) { + field = Character.toString((char) scanner.token.value()); + scanner.scanModuleStatement(); + } else { + break; + } + } + return name; + } + + /** + * Parse a module declaration. + */ + private void parseModule() throws IOException { + debugStr(" [Parser.parseModule]: Begin "); + if (cd == null) { + cd = new ClassData(env, currentCFV.clone()); + pool = cd.pool; + } + if (clsAnnttns != null) { + cd.addAnnotations(clsAnnttns); + } + moduleAttribute = new ModuleAttr(cd); + + if (scanner.token == Token.OPEN) { + moduleAttribute.openModule(); + scanner.scan(); + } + + // move the tokenizer to the identifier: + if (scanner.token == Token.MODULE) { + scanner.scanModuleStatement(); + // scanner.scan(); + } else { + env.error(scanner.pos, "token.expected", Token.MODULE.parseKey()); + throw new Scanner.SyntaxError().Fatal(); + } + // Parse the module name + String moduleName = parseModuleName(); + if (moduleName.isEmpty()) { + env.error(scanner.pos, "name.expected"); + throw new Scanner.SyntaxError().Fatal(); + } + moduleAttribute.setModuleName(moduleName); + + parseVersion(); + scanner.expect(Token.LBRACE); + + // Begin a new class as module + cd.initAsModule(); + + // Parse module statement(s) + while ((scanner.token != Token.EOF) && (scanner.token != Token.RBRACE)) { + switch (scanner.token) { + case REQUIRES: + scanRequires(moduleAttribute.requires); + break; + case EXPORTS: + scanStatement(moduleAttribute.exports, + this::parseTypeName, + this::parseModuleName, + Token.TO, + true, + "exports.expected"); + break; + case OPENS: + scanStatement(moduleAttribute.opens, + this::parseTypeName, + this::parseModuleName, + Token.TO, true, "opens.expected"); + break; + case USES: + scanStatement(moduleAttribute.uses, "uses.expected"); + break; + case PROVIDES: + scanStatement(moduleAttribute.provides, + this::parseTypeName, + this::parseTypeName, + Token.WITH, + false, + "provides.expected"); + break; + case SEMICOLON: + // Empty fields are allowed + scanner.scan(); + break; + default: + env.error(scanner.pos, "module.statement.expected"); + throw new Scanner.SyntaxError().Fatal(); + } // end switch + } // while + scanner.expect(Token.RBRACE); + // End the module + endModule(); + } // end parseModule + + /** + * Scans ModuleStatement: requires [transitive] [static] ModuleName ; + */ + private void scanRequires(BiConsumer action) throws IOException { + int flags = 0; + String mn = ""; + scanner.scanModuleStatement(); + while (scanner.token != SEMICOLON) { + switch (scanner.token) { + case STATIC: + if (((flags & (1 << Module.Modifier.ACC_STATIC_PHASE.asInt())) != 0) || !mn.isEmpty()) { + env.error(scanner.pos, "requires.expected"); + throw new Scanner.SyntaxError().Fatal(); + } + flags |= Module.Modifier.ACC_STATIC_PHASE.asInt(); + break; + case TRANSITIVE: + if (((flags & (1 << Module.Modifier.ACC_TRANSITIVE.asInt())) != 0) || !mn.isEmpty()) { + env.error(scanner.pos, "requires.expected"); + throw new Scanner.SyntaxError().Fatal(); + } + flags |= Module.Modifier.ACC_TRANSITIVE.asInt(); + break; + case IDENT: + if (!mn.isEmpty()) { + env.error(scanner.pos, "requires.expected"); + throw new Scanner.SyntaxError().Fatal(); + } + mn = parseModuleName(); + continue; + default: + if (mn.isEmpty() && scanner.token.possibleModuleName()) { + mn = parseModuleName(); + continue; + } else { + env.error(scanner.pos, "requires.expected"); + throw new Scanner.SyntaxError().Fatal(); + } + } + scanner.scanModuleStatement(); + } + // Token.SEMICOLON + if (mn.isEmpty()) { + env.error(scanner.pos, "requires.expected"); + throw new Scanner.SyntaxError().Fatal(); + } + action.accept(mn, flags); + scanner.scanModuleStatement(); + } + + /** + * Scans ModuleStatement: uses TypeName; + */ + private void scanStatement(Consumer> action, String err) throws IOException { + HashSet names = scanList(() -> scanner.scan(), this::parseTypeName, err, true); + // Token.SEMICOLON + if (names.size() != 1) { + env.error(scanner.pos, err); + throw new Scanner.SyntaxError().Fatal(); + } + action.accept(names); + scanner.scan(); + } + + /** + * Scans Module Statement(s): + * exports packageName [to ModuleName {, ModuleName}] ; + * opens packageName [to ModuleName {, ModuleName}] ; + * provides TypeName with TypeName [,typeName] ; + */ + private void scanStatement(BiConsumer> action, + NameSupplier source, + NameSupplier target, + Token startList, + boolean emptyListAllowed, + String err) throws IOException { + String typeName = ""; + HashSet names = new HashSet<>(); + scanner.scan(); + while (scanner.token != SEMICOLON) { + if (scanner.token == Token.IDENT) { + if (typeName.isEmpty()) { + typeName = source.get(); + continue; + } + env.error(scanner.pos, err); + throw new Scanner.SyntaxError().Fatal(); + } + if (scanner.token == startList) { + if (typeName.isEmpty()) { + env.error(scanner.pos, err); + throw new Scanner.SyntaxError().Fatal(); + } + names = scanList(scanner.token == Token.TO ? () -> scanner.scanModuleStatement() : () -> scanner.scan(), target, err, false); + break; + } else { + env.error(scanner.pos, err); + throw new Scanner.SyntaxError().Fatal(); + } + } + // Token.SEMICOLON + if (typeName.isEmpty() || (names.isEmpty() && !emptyListAllowed)) { + env.error(scanner.pos, err); + throw new Scanner.SyntaxError().Fatal(); + } + action.accept(typeName, names); + scanner.scan(); + } + + /** + * Scans the "to" or "with" part of ModuleStatement: exports PackageName [to ModuleName {, ModuleName}] ;, + * opens packageName [to ModuleName {, ModuleName}] ; + * provides TypeName with TypeName [,typeName] ; + * uses TypeName; + * : [ModuleName {, ModuleName}]; , [TypeName [,typeName]]; or TypeName; + */ + private HashSet scanList(Method scanMethod, NameSupplier target, String err, boolean onlyOneElement) throws IOException { + HashSet names = new HashSet<>(); + boolean comma = false, first = true; + scanMethod.call(); + while (scanner.token != SEMICOLON) { + switch (scanner.token) { + case COMMA: + if (comma || first || onlyOneElement) { + env.error(scanner.pos, err); + throw new Scanner.SyntaxError().Fatal(); + } + comma = true; + break; + case IDENT: + if (!first && !comma) { + env.error(scanner.pos, err); + throw new Scanner.SyntaxError().Fatal(); + } + names.add(target.get()); + comma = false; + first = false; + continue; + default: + env.error(scanner.pos, err); + throw new Scanner.SyntaxError().Fatal(); + } + scanner.scan(); + } + // Token.SEMICOLON + if (names.isEmpty() || comma) { + env.error(scanner.pos, err); + throw new Scanner.SyntaxError().Fatal(); + } + return names; + } + + private void parseClassMembers() throws IOException { + debugScan("[Parser.parseClassMembers]: Begin "); + // Parse annotations + if (scanner.token == Token.ANNOTATION) { + memberAnnttns = annotParser.scanAnnotations(); + } + // Parse modifiers + int mod = scanModifiers(); + try { + switch (scanner.token) { + case FIELDREF: + scanner.scan(); + parseField(mod); + break; + case METHODREF: + scanner.scan(); + parseMethod(mod); + break; + case INNERCLASS: + scanner.scan(); + parseInnerClass(mod); + break; + case BOOTSTRAPMETHOD: + scanner.scan(); + parseCPXBootstrapMethod(); + break; + case NESTHOST: + if (cd.nestHostAttributeExists()) { + env.error(scanner.pos, "extra.nesthost.attribute"); + throw new Scanner.SyntaxError(); + } else if (cd.nestMembersAttributesExist()) { + env.error(scanner.pos, "both.nesthost.nestmembers.found"); + throw new Scanner.SyntaxError(); + } + scanner.scan(); + parseNestHost(); + break; + case NESTMEMBERS: + if (cd.nestMembersAttributesExist()) { + env.error(scanner.pos, "extra.nestmembers.attribute"); + throw new Scanner.SyntaxError(); + } else if (cd.nestHostAttributeExists()) { + env.error(scanner.pos, "both.nesthost.nestmembers.found"); + throw new Scanner.SyntaxError(); + } + scanner.scan(); + parseClasses(list -> cd.addNestMembers(list)); + break; + case PERMITTEDSUBCLASSES: // JEP 360 + if (cd.nestMembersAttributesExist()) { + env.error(scanner.pos, "extra.permittedsubclasses.attribute"); + throw new Scanner.SyntaxError(); + } + scanner.scan(); + parseClasses(list -> cd.addPermittedSubclasses(list)); + break; + case RECORD: // JEP 359 + if (cd.recordAttributeExists()) { + env.error(scanner.pos, "extra.record.attribute"); + throw new Scanner.SyntaxError(); + } + scanner.scan(); + parseRecord(); + break; + case PRELOAD: + if (cd.preloadAttributeExists()) { + env.error(scanner.pos, "extra.preload.attribute"); + throw new Scanner.SyntaxError(); + } + scanner.scan(); + parseClasses(list -> cd.addPreloads(list)); + break; + default: + env.error(scanner.pos, "field.expected"); + throw new Scanner.SyntaxError(); + } // end switch + } catch (Scanner.SyntaxError e) { + recoverField(); + } + memberAnnttns = null; + } + + /** + * Recover after a syntax error in the file. + * This involves discarding scanner.tokens until an EOF + * or a possible legal continuation is encountered. + */ + private void recoverFile() throws IOException { + while (true) { + env.traceln("recoverFile: scanner.token=" + scanner.token); + switch (scanner.token) { + case CLASS: + case INTERFACE: + // Start of a new source file statement, continue + return; + + case LBRACE: + match(Token.LBRACE, Token.RBRACE); + scanner.scan(); + break; + + case LPAREN: + match(Token.LPAREN, Token.RPAREN); + scanner.scan(); + break; + + case LSQBRACKET: + match(Token.LSQBRACKET, Token.RSQBRACKET); + scanner.scan(); + break; + + case EOF: + return; + + default: + // Don't know what to do, skip + scanner.scan(); + break; + } + } + } + + /** + * End class + */ + private void endClass() { + if (explicitcp) { + // Fix references in the constant pool (for explicitly coded CPs) + pool.fixRefsInPool(); + // Fix any bootstrap Method references too + cd.relinkBootstrapMethods(); + } + cd.endClass(); + clsDataList.add(cd); + cd = null; + } + + /** + * End module + */ + private void endModule() { + cd.endModule(moduleAttribute); + clsDataList.add(cd); + cd = null; + } + + final ClassData[] getClassesData() { + return clsDataList.toArray(new ClassData[0]); + } + + /** + * Determines whether the JASM file is for a package-info class + * or for a module-info class. + *

    + * creates the correct kind of ClassData accordingly. + * + * @throws IOException + */ + private void parseJasmPackages() throws IOException { + try { + // starting annotations could either be + // a package annotation, or a class annotation + if (scanner.token == Token.ANNOTATION) { + if (cd == null) { + cd = new ClassData(env, currentCFV.clone()); + pool = cd.pool; + } + pkgAnnttns = annotParser.scanAnnotations(); + } + if (scanner.token == Token.PACKAGE) { + // Package statement + scanner.scan(); + int where = scanner.pos; + String id = parseIdent(); + parseVersionPkg(); + scanner.expect(SEMICOLON); + + if (pkg == null) { + pkg = id; + pkgPrefix = id + "/"; + } else { + env.error(where, "package.repeated"); + } + debugScan("[Parser.parseJasmPackages] {PARSED} package-prefix: " + pkgPrefix + " "); + } + } catch (Scanner.SyntaxError e) { + recoverFile(); + } + // skip bogus semi colons + while (scanner.token == SEMICOLON) { + scanner.scan(); + } + + // checks that we compile module or package compilation unit + if (scanner.token == Token.EOF) { + env.traceln("Scanner: EOF"); + String sourceName = env.getSimpleInputFileName(); + int mod = ACC_INTERFACE | ACC_ABSTRACT; + + // package-info + if (sourceName.endsWith("package-info.jasm")) { + env.traceln("Creating \"package-info.jasm\": package: " + pkg + " " + currentCFV.asString()); + + if (cd == null) { + cd = new ClassData(env, currentCFV.clone()); + pool = cd.pool; + } else { + cd.cfv = currentCFV.clone(); + } + ConstCell me = pool.FindCellClassByName(pkgPrefix + "package-info"); + + // Interface package-info should be marked synthetic and abstract + if (currentCFV.major_version() > 49) { + mod |= SYNTHETIC_ATTRIBUTE; + } + cd.init(mod, me, new ConstCell(0), null); + + if (pkgAnnttns != null) { + cd.addAnnotations(pkgAnnttns); + } + + endClass(); + } + return; + } + + if (pkg == null && pkgAnnttns != null) { // RemoveModules + clsAnnttns = pkgAnnttns; + pkgAnnttns = null; + } + } + + /** + * Parse an Jasm file. + */ + void parseFile() { + try { + // First, parse any package identifiers (and associated package annotations) + parseJasmPackages(); + + while (scanner.token != Token.EOF) { + // Second, parse any class identifiers (and associated class annotations) + try { + // Parse annotations + if (scanner.token == Token.ANNOTATION) { + if (cd == null) { + cd = new ClassData(env, currentCFV.clone()); + pool = cd.pool; + } else { + cd.cfv = currentCFV.clone(); + } + clsAnnttns = annotParser.scanAnnotations(); + } + + // Parse class modifiers + int mod = scanModifiers(); + if (mod == 0) { + switch (scanner.token) { + case OPEN: + case MODULE: + case CLASS: + case CPINDEX: + case STRINGVAL: + case IDENT: + // this is a class declaration anyway + break; + case SEMICOLON: + // Bogus semi colon + scanner.scan(); + continue; + default: + // no class declaration found + debugScan(" [Parser.parseFile]: "); + env.error(scanner.pos, "toplevel.expected"); + throw new Scanner.SyntaxError(); + } + } else if (Modifiers.isInterface(mod) && (scanner.token != Token.CLASS)) { + // rare syntactic sugar: + // interface == abstract interface class + mod |= ACC_ABSTRACT; + } + if (scanner.token == Token.MODULE || scanner.token == Token.OPEN) + parseModule(); + else + parseClass(mod); + clsAnnttns = null; + + } catch (Scanner.SyntaxError e) { + // KTL + env.traceln("^^^^^^^ Syntax Error ^^^^^^^^^^^^"); + if (scanner.debugFlag) + e.printStackTrace(); + if (e.isFatal()) { + break; + } + recoverFile(); + } + } + } catch (IOException e) { + env.error(scanner.pos, "io.exception", env.getSimpleInputFileName()); + } catch (Error er) { + er.printStackTrace(); + } + } //end parseFile + + @FunctionalInterface + interface NameSupplier { + String get() throws IOException; + } + + @FunctionalInterface + interface Method { + void call() throws IOException; + } + + /** + * The main compile error for the parser + */ + static class CompilerError extends Error { + + CompilerError(String message) { + super(message); + } + } +} //end Parser diff --git a/test/lib/org/openjdk/asmtools/jasm/ParserAnnotation.java b/test/lib/org/openjdk/asmtools/jasm/ParserAnnotation.java new file mode 100644 index 00000000000..d9683fe5330 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/ParserAnnotation.java @@ -0,0 +1,1085 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import org.openjdk.asmtools.jasm.TypeAnnotationTargetInfoData.*; + +import static org.openjdk.asmtools.jasm.JasmTokens.AnnotationType.isInvisibleAnnotationToken; +import static org.openjdk.asmtools.jasm.TypeAnnotationTypes.*; +import static org.openjdk.asmtools.jasm.JasmTokens.*; +import static org.openjdk.asmtools.jasm.ConstantPool.*; +import static org.openjdk.asmtools.jasm.Tables.*; +import java.io.IOException; +import java.util.ArrayList; +import java.util.TreeMap; + +/** + * ParserAnnotation + * + * ParserAnnotation is a parser class owned by Parser.java. It is primarily responsible + * for parsing Annotations (for classes, methods or fields). + * + * ParserAnnotation can parse the different types of Annotation Attributes: + * Runtime(In)Visible Annotations (JDK 6+) Default Annotations (JDK 6+) + * Runtime(In)VisibleParameter Annotations (JDK 7+) Runtime(In)VisibleType Annotations + * (JSR308, JDK8+) + */ +public class ParserAnnotation extends ParseBase { + + /*-------------------------------------------------------- */ + /* Annotation Inner Classes */ + /** + * AnnotationElemValue + * + * Used to store Annotation values + */ + static class AnnotationElemValue implements Data { + + AnnotationData annotation; + + AnnotationElemValue(AnnotationData annotation) { + this.annotation = annotation; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeByte('@'); + annotation.write(out); + } + + @Override + public int getLength() { + return 1 + annotation.getLength(); + } + } + + /** + * ClassElemValue + * + * Annotation Element value referring to a class + */ + static class ClassElemValue implements Data { + + ConstCell indx; + + ClassElemValue(ConstCell indx) { + this.indx = indx; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeByte('c'); + indx.write(out); + } + + @Override + public int getLength() { + return 3; + } + } + + /** + * ArrayElemValue + * + * Annotation Element value referring to an Array + */ + static class ArrayElemValue implements Data { + + ArrayList elemValues; + int arrayLength = 0; + + ArrayElemValue() { + this.elemValues = new ArrayList<>(); + } + + void add(Data elemValue) { + elemValues.add(elemValue); + arrayLength += elemValue.getLength(); + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeByte('['); + out.writeShort(elemValues.size()); + + for (Data eval : elemValues) { + eval.write(out); + } + } + + @Override + public int getLength() { + return 3 + arrayLength; + } + } + + /** + * ConstElemValue + * + * Annotation Element value referring to a Constant + */ + static class ConstElemValue implements Data { + + char tag; + ConstCell indx; + + ConstElemValue(char tag, ConstCell indx) { + this.tag = tag; + this.indx = indx; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeByte(tag); + indx.write(out); + } + + @Override + public int getLength() { + return 3; + } + } + + /** + * EnumElemValue + * + * Element Value for Enums + */ + static class EnumElemValue implements Data { + + ConstCell type; + ConstCell value; + + EnumElemValue(ConstCell type, ConstCell value) { + this.type = type; + this.value = value; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeByte('e'); + type.write(out); + value.write(out); + } + + @Override + public int getLength() { + return 5; + } + } + + /** + * local handles on the scanner, main parser, and the error reporting env + */ + private static TTVis ttVisitor; + + protected ParserAnnotation(Scanner scanner, Parser parser, Environment env) { + super.init(scanner, parser, env); + ttVisitor = new TTVis(); + ttVisitor.init(env, scanner); + } + + protected void scanParamName(int totalParams, int paramNum, MethodData curMethod) throws IOException { + debugScan(" - - - > [ParserAnnotation.scanParamName]: Begin "); + scanner.scan(); + scanner.expect(Token.LBRACE); + // First scan the Name (String, or CPX to name) + ConstCell nameCell; + if ((scanner.token == Token.IDENT) || scanner.checkTokenIdent()) { + // Got a Class Name + nameCell = parser.parseName(); + } else if (scanner.token == Token.CPINDEX) { + int cpx = scanner.intValue; + nameCell = parser.pool.getCell(cpx); + // check the constant + ConstValue nameCellValue = nameCell.ref; + if (!(nameCellValue instanceof ConstValue_String)) { + // throw an error + env.error(scanner.pos, "paramname.constnum.invaltype", cpx); + throw new Scanner.SyntaxError(); + } + + } else { + // throw scan error - unexpected token + env.error(scanner.pos, "paramname.token.unexpected", scanner.stringValue); + throw new Scanner.SyntaxError(); + } + + // Got the name cell. Next, scan the access flags + int mod = parser.scanModifiers(); + + scanner.expect(Token.RBRACE); + + curMethod.addMethodParameter(totalParams, paramNum, nameCell, mod); + + debugScan(" - - - > [ParserAnnotation.scanParamName]: End "); + } + + /** + * The main entry for parsing an annotation list. + * + * @return An ArrayList of parsed annotations + * @throws IOException + */ + ArrayList scanAnnotations() throws IOException { + ArrayList list = new ArrayList<>(); + while (scanner.token == Token.ANNOTATION) { + if ( JasmTokens.AnnotationType.isAnnotationToken(scanner.stringValue)) { + list.add(parseAnnotation()); + } else if (JasmTokens.AnnotationType.isTypeAnnotationToken(scanner.stringValue)) { + list.add(parseTypeAnnotation()); + } else { + return null; + } + } + if (list.size() > 0) { + return list; + } else { + return null; + } + } + + /** + * parseDefaultAnnotation + * + * parses a default Annotation attribute + * + * @return the parsed Annotation Attribute + * @throws org.openjdk.asmtools.jasm.Scanner.SyntaxError + * @throws IOException + */ + protected DefaultAnnotationAttr parseDefaultAnnotation() throws Scanner.SyntaxError, IOException { + scanner.scan(); + DefaultAnnotationAttr attr = null; + Data value = null; + scanner.expect(Token.LBRACE); + + if ((scanner.token != Token.EOF) && (scanner.token != Token.RBRACE)) { + value = scanAnnotationData("default"); + } + scanner.expect(Token.RBRACE); + attr = new DefaultAnnotationAttr(parser.cd, + AttrTag.ATT_AnnotationDefault.parsekey(), + value); + return attr; + } + + /** + * parseParamAnnots + * + * Parses Parameter Annotations attributes. + * + * @param _totalParams + * @param curMethod + * @throws org.openjdk.asmtools.jasm.Scanner.SyntaxError + * @throws IOException + */ + protected void parseParamAnnots(int _totalParams, MethodData curMethod) throws Scanner.SyntaxError, IOException { + debugScan(" - - - > [ParserAnnotation.parseParamAnnots]: Begin, totalParams = " + _totalParams + " "); + // The _method thinks there are N+1 params in the signature + // (N = total params in the call list) + 1 (return value) + int totalParams = _totalParams - 1; + TreeMap> pAnnots = new TreeMap<>(); + + while (scanner.token == Token.INTVAL) { + // Create the Parameter Array for Param Annotations + + // Do something with Parameter annotations + // -------------------- + // First - validate that the parameter number (integer) + // (eg >= 0, < numParams, and param num is not previously set) + int paramNum = scanner.intValue; + Integer iParamNum = Integer.valueOf(paramNum); + if (paramNum < 0 || paramNum >= totalParams) { + //invalid Parameter number. Throw an error. + env.error(scanner.pos, "invalid.paramnum", paramNum); + } + if (pAnnots.get(iParamNum) != null) { + // paramter is already populated with annotations/pnames, Throw an error. + env.error(scanner.pos, "duplicate.paramnum", paramNum); + } + // 2nd - Parse the COLON (invalid if not present) + scanner.scan(); + scanner.expect(Token.COLON); + + // 3rd - parse either an optional ParamName, or a list of annotations + if (scanner.token == Token.PARAM_NAME) { + //parse the ParamName + scanParamName(totalParams, iParamNum, curMethod); + } + + // 4th - parse each Annotation (followed by comma, followed by annotation + // assign array of annotations to param array + if (scanner.token == Token.ANNOTATION) { + ArrayList pAnnot = scanAnnotations(); + pAnnots.put(iParamNum, pAnnot); + + for (AnnotationData data : pAnnot) { + curMethod.addParamAnnotation(totalParams, paramNum, data); + } + } + + } + } + + /* ************************* Private Members *************************** */ + /** + * parseTypeAnnotation + * + * parses an individual annotation. + * + * @return a parsed annotation. + * @throws IOException + */ + private AnnotationData parseTypeAnnotation() throws Scanner.SyntaxError, IOException { + boolean invisible = isInvisibleAnnotationToken(scanner.stringValue); + scanner.scan(); + debugScan(" [ParserAnnotation.parseTypeAnnotation]: id = " + scanner.stringValue + " "); + String annoName = "L" + scanner.stringValue + ";"; + TypeAnnotationData anno = new TypeAnnotationData(parser.pool.FindCellAsciz(annoName), invisible); + scanner.scan(); + debugScan(" [ParserAnnotation.parseTypeAnnotation]:new type annotation: " + annoName + " "); + + scanner.expect(Token.LBRACE); + + // Scan the usual annotation data + _scanAnnotation(anno); + + // scan the Target (u1: target_type, union{...}: target_info) + _scanTypeTarget(anno); + + if( scanner.token != Token.RBRACE ) { + // scan the Location (type_path: target_path) + _scanTargetPath(anno); + } + + scanner.expect(Token.RBRACE); + return anno; + } + + /** + * scanAnnotation + * + * parses an individual annotation. + * + * @return a parsed annotation. + * @throws IOException + */ + private AnnotationData parseAnnotation() throws Scanner.SyntaxError, IOException { + debugScan(" - - - > [ParserAnnotation.parseAnnotation]: Begin "); + boolean invisible = isInvisibleAnnotationToken(scanner.stringValue); + scanner.scan(); + String annoName = "L" + scanner.stringValue + ";"; + + AnnotationData anno = new AnnotationData(parser.pool.FindCellAsciz(annoName), invisible); + scanner.scan(); + debugScan("[ParserAnnotation.parseAnnotation]: new annotation: " + annoName); + _scanAnnotation(anno); + + return anno; + } + + /** + * _scanAnnotation + * + * parses an individual annotation-data. + * + * @return a parsed annotation. + * @throws IOException + */ + private void _scanAnnotation(AnnotationData annotData) throws Scanner.SyntaxError, IOException { + debugScan(" - - - > [ParserAnnotation._scanAnnotation]: Begin"); + scanner.expect(Token.LBRACE); + + while ((scanner.token != Token.EOF) && (scanner.token != Token.RBRACE)) { + ConstCell nameCell = parser.parseName(); + scanner.expect(Token.ASSIGN); + + ConstValue cellref = nameCell.ref; + if (cellref.tag != ConstType.CONSTANT_UTF8) { + throw new Scanner.SyntaxError(); + } + String name = ((ConstValue_String) cellref)._toString(); + debugScan(" [ParserAnnotation._scanAnnotation]: Annot - Field Name: " + name); + Data data = scanAnnotationData(name); + annotData.add(new AnnotationData.ElemValuePair(nameCell, data)); + + // consume tokens inbetween annotation fields + if (scanner.token == Token.COMMA) { + scanner.scan(); + } + } + scanner.expect(Token.RBRACE); + } + + /** + * _scanAnnotation + * + * parses an individual annotation-data. + * + * @return a parsed annotation. + * @throws IOException + */ + private void _scanTypeTarget(TypeAnnotationData annotData) throws Scanner.SyntaxError, IOException { + debugScan(" [ParserAnnotation._scanTypeTarget]: Begin "); + scanner.expect(Token.LBRACE); + + //Scan the target_type and the target_info + scanner.expect(Token.IDENT); + debugScan(" [ParserAnnotation._scanTypeTarget]: TargetType: " + scanner.idValue); + ETargetType targetType = ETargetType.getTargetType(scanner.idValue); + if (targetType == null) { + env.error(scanner.pos, "incorrect.typeannot.target", scanner.idValue); + throw new Scanner.SyntaxError(); + } + + debugScan(" [ParserAnnotation._scanTypeTarget]: Got TargetType: " + targetType); + + if (ttVisitor.scanner == null) { + ttVisitor.scanner = scanner; + } + ttVisitor.visitExcept(targetType); + + annotData.targetInfo = ttVisitor.getTargetInfo(); + annotData.targetType = targetType; + debugScan(" [ParserAnnotation._scanTypeTarget]: Got TargetInfo: " + annotData.targetInfo); + + scanner.expect(Token.RBRACE); + } + + /** + * TTVis + * + * Target Type visitor, used for constructing the target-info within a type + * annotation. visitExcept() is the entry point. ti is the constructed target info. + */ + private static class TTVis extends TypeAnnotationTypes.TypeAnnotationTargetVisitor { + + private TypeAnnotationTargetInfoData ti; + private IOException IOProb; + private Scanner.SyntaxError SyProb; + private Scanner scanner; + private Environment env; + + public TTVis() { + super(); + reset(); + } + + public void init(Environment en, Scanner scn) { + if (scanner == null) { + scanner = scn; + } + if (env == null) { + env = en; + } + } + + public final void reset() { + ti = null; + IOProb = null; + SyProb = null; + } + + //This is the entry point for a visitor that tunnels exceptions + public void visitExcept(ETargetType tt) throws IOException, Scanner.SyntaxError { + IOProb = null; + SyProb = null; + ti = null; + + visit(tt); + + if (IOProb != null) { + throw IOProb; + } + + if (SyProb != null) { + throw SyProb; + } + } + + public TypeAnnotationTargetInfoData getTargetInfo() { + return ti; + } + + // this fn gathers intvals, and tunnels any exceptions thrown by + // the scanner + private int scanIntVal(ETargetType tt) { + int ret = -1; + if (scanner.token == Token.INTVAL) { + ret = scanner.intValue; + try { + scanner.scan(); + } catch (IOException e) { + IOProb = e; + } catch (Scanner.SyntaxError e) { + SyProb = e; + } + } else { + env.error(scanner.pos, "incorrect.typeannot.targtype.int", tt.parseKey(), scanner.token); + SyProb = new Scanner.SyntaxError(); + } + return ret; + } + + // this fn gathers intvals, and tunnels any exceptions thrown by + // the scanner + private String scanStringVal(ETargetType tt) { + String ret = ""; + if (scanner.token == Token.STRINGVAL) { + ret = scanner.stringValue; + try { + scanner.scan(); + } catch (IOException e) { + IOProb = e; + } catch (Scanner.SyntaxError e) { + SyProb = e; + } + } else { + env.error(scanner.pos, "incorrect.typeannot.targtype.string", tt.parseKey(), scanner.token); + SyProb = new Scanner.SyntaxError(); + } + return ret; + } + + // this fn gathers intvals, and tunnels any exceptions thrown by + // the scanner + private void scanBrace(boolean left) { + try { + scanner.expect(left ? Token.LBRACE : Token.RBRACE); + } catch (IOException e) { + IOProb = e; + } catch (Scanner.SyntaxError e) { + SyProb = e; + } + } + + private boolean error() { + return IOProb != null || SyProb != null; + } + + @Override + public void visit_type_param_target(ETargetType tt) { + env.traceln("Type Param Target: "); + int byteval = scanIntVal(tt); // param index + if (!error()) { + ti = new TypeAnnotationTargetInfoData.type_parameter_target(tt, byteval); + } + } + + @Override + public void visit_supertype_target(ETargetType tt) { + env.traceln("SuperType Target: "); + int shortval = scanIntVal(tt); // type index + if (!error()) { + ti = new TypeAnnotationTargetInfoData.supertype_target(tt, shortval); + } + } + + @Override + public void visit_typeparam_bound_target(ETargetType tt) { + env.traceln("TypeParam Bound Target: "); + int byteval1 = scanIntVal(tt); // param index + if (error()) { + return; + } + int byteval2 = scanIntVal(tt); // bound index + if (error()) { + return; + } + ti = new TypeAnnotationTargetInfoData.type_parameter_bound_target(tt, byteval1, byteval2); + } + + @Override + public void visit_empty_target(ETargetType tt) { + env.traceln("Empty Target: "); + if (!error()) { + ti = new TypeAnnotationTargetInfoData.empty_target(tt); + } + } + + @Override + public void visit_methodformalparam_target(ETargetType tt) { + env.traceln("MethodParam Target: "); + int byteval = scanIntVal(tt); // param index + if (!error()) { + ti = new formal_parameter_target(tt, byteval); + } + } + + @Override + public void visit_throws_target(ETargetType tt) { + env.traceln("Throws Target: "); + int shortval = scanIntVal(tt); // exception index + if (!error()) { + ti = new throws_target(tt, shortval); + } + } + + @Override + public void visit_localvar_target(ETargetType tt) { + env.traceln("LocalVar Target: "); + localvar_target locvartab = new localvar_target(tt, 0); + ti = locvartab; + + while ((scanner.token != Token.EOF) && (scanner.token != Token.RBRACE)) { + // consume the left brace + scanBrace(true); + if (error()) { + return; + } + // scan the local var triple + int shortval1 = scanIntVal(tt); // startPC + if (error()) { + return; + } + int shortval2 = scanIntVal(tt); // length + if (error()) { + return; + } + int shortval3 = scanIntVal(tt); // CPX + locvartab.addEntry(shortval1, shortval2, shortval3); + scanBrace(false); + if (error()) { + return; + } + } + } + + @Override + public void visit_catch_target(ETargetType tt) { + env.traceln("Catch Target: "); + int shortval = scanIntVal(tt); // catch index + + ti = new catch_target(tt, shortval); + } + + @Override + public void visit_offset_target(ETargetType tt) { + env.traceln("Offset Target: "); + int shortval = scanIntVal(tt); // offset index + if (!error()) { + ti = new offset_target(tt, shortval); + } + } + + @Override + public void visit_typearg_target(ETargetType tt) { + env.traceln("TypeArg Target: "); + int shortval = scanIntVal(tt); // offset + if (error()) { + return; + } + int byteval = scanIntVal(tt); // type index + if (error()) { + return; + } + ti = new type_argument_target(tt, shortval, byteval); + } + + } + + /** + * _scanTargetPath + * + * parses and fills the type_path structure (4.7.20.2) + * + * type_path { + * u1 path_length; + * { u1 type_path_kind; + * u1 type_argument_index; + * } path[path_length]; + * } + * + * @throws Scanner.SyntaxError, IOException + */ + private void _scanTargetPath(TypeAnnotationData annotData) throws Scanner.SyntaxError, IOException { + // parse the location info + scanner.expect(Token.LBRACE); + + while ((scanner.token != Token.EOF) && (scanner.token != Token.RBRACE)) { + TypePathEntry tpe = _scanTypePathEntry(); + annotData.addTypePathEntry(tpe); + // throw away comma + if (scanner.token == Token.COMMA) { + scanner.scan(); + } + } + + scanner.expect(Token.RBRACE); + } + + /** + * _scanTypeLocation + * + * parses a path entry of the type_path. + * + * { u1 type_path_kind; + * u1 type_argument_index; + * } + * + * @return a parsed type path. + * @throws Scanner.SyntaxError, IOException + */ + private TypePathEntry _scanTypePathEntry() throws Scanner.SyntaxError, IOException { + TypePathEntry tpe; + + if ( (scanner.token != Token.EOF) && scanner.token.possibleTypePathKind() ) { + EPathKind pathKind = EPathKind.getPathKind(scanner.stringValue); + if (pathKind == EPathKind.TYPE_ARGUMENT) { + scanner.scan(); + // need to scan the index + // Take the form: TYPE_ARGUMENT{#} + scanner.expect(Token.LBRACE); + int index = 0; + if ((scanner.token != Token.EOF) && (scanner.token == Token.INTVAL)) { + index = scanner.intValue; + scanner.scan(); + } else { + // incorrect Arg index + env.error(scanner.pos, "incorrect.typeannot.pathentry.argindex", scanner.token); + throw new Scanner.SyntaxError(); + } + tpe = new TypePathEntry(pathKind, index); + scanner.expect(Token.RBRACE); + } else { + tpe = new TypePathEntry(pathKind, 0); + scanner.scan(); + } + } else { + // unexpected Type Path + env.error(scanner.pos, "incorrect.typeannot.pathentry", scanner.token); + throw new Scanner.SyntaxError(); + } + + return tpe; + } + + /** + * scanAnnotationArray + * + * Scans an Array of annotations. + * + * @param name Name of the annotation + * @return Array Element + * @throws IOException if scanning errors exist + */ + private ArrayElemValue scanAnnotationArray(String name) throws IOException { + scanner.scan(); + ArrayElemValue arrayElem = new ArrayElemValue(); + + while ((scanner.token != Token.EOF) && (scanner.token != Token.RBRACE)) { + Data data = scanAnnotationData(name + " {}"); + arrayElem.add(data); + + // consume tokens inbetween annotation fields + if (scanner.token == Token.COMMA) { + scanner.scan(); + } + } + + scanner.expect(Token.RBRACE); + return arrayElem; + } + + /** + * scanAnnotationEnum + * + * Scans an annotation enum val. + * + * @param name Annotation Name + * @return Constant element value for the Class Annotation. + * @throws IOException + */ + private Data scanAnnotationClass(String name) throws IOException { + Data constVal = null; + // scan the next identifier. + // if it is an Ident, consume it as the class name. + scanner.scan(); + switch (scanner.token) { + case IDENT: + env.traceln("[AnnotationParser.scanAnnotationData]:: Constant Class Field: " + name + " = " + scanner.stringValue); + //need to encode the stringval as an (internal) descriptor. + String desc = parser.encodeClassString(scanner.stringValue); + + // note: for annotations, a class field points to a string with the class descriptor. + constVal = new ConstElemValue('c', parser.pool.FindCellAsciz(desc)); + scanner.scan(); + break; + case CPINDEX: + // could be a reference to a class name + env.traceln("[AnnotationParser.scanAnnotationData]:: Constant Class Field: " + name + " = " + scanner.stringValue); + Integer ConstNmCPX = Integer.valueOf(scanner.stringValue); + constVal = new ClassElemValue(parser.pool.getCell(ConstNmCPX)); + scanner.scan(); + break; + default: + env.error(scanner.pos, "incorrect.annot.class", scanner.stringValue); + throw new Scanner.SyntaxError(); + } + + return constVal; + } + + /** + * scanAnnotationEnum + * + * Scans an annotation enum val. + * + * @param name Annotation Name + * @return Enumeration Element Value + * @throws IOException for scanning errors. + */ + private EnumElemValue scanAnnotationEnum(String name) throws IOException { + scanner.scan(); + EnumElemValue enumval = null; + switch (scanner.token) { + case IDENT: + // could be a string identifying enum class and name + String enumClassName = scanner.stringValue; + scanner.scan(); + // could be a string identifying enum class and name + switch (scanner.token) { + case IDENT: + // could be a string identifying enum class and name + String enumTypeName = scanner.stringValue; + env.traceln("[AnnotationParser.scanAnnotationEnum]:: Constant Enum Field: " + name + " = " + enumClassName + " " + enumTypeName); + String encodedClass = parser.encodeClassString(enumClassName); + ConstElemValue classConst = new ConstElemValue('s', parser.pool.FindCellAsciz(encodedClass)); + ConstElemValue typeConst = new ConstElemValue('s', parser.pool.FindCellAsciz(enumTypeName)); + enumval = new EnumElemValue(classConst.indx, typeConst.indx); + scanner.scan(); + break; + + default: + env.error(scanner.pos, "incorrect.annot.enum", scanner.stringValue); + throw new Scanner.SyntaxError(); + } + break; + case CPINDEX: + Integer typeNmCPX = Integer.valueOf(scanner.stringValue); + scanner.scan(); + //need two indexes to form a proper enum + switch (scanner.token) { + case CPINDEX: + Integer ConstNmCPX = Integer.valueOf(scanner.stringValue); + env.traceln("[AnnotationParser.scanAnnotationEnum]:: Enumeration Field: " + name + " = #" + typeNmCPX + " #" + ConstNmCPX); + enumval = new EnumElemValue(parser.pool.getCell(typeNmCPX), parser.pool.getCell(ConstNmCPX)); + scanner.scan(); + break; + default: + env.error(scanner.pos, "incorrect.annot.enum.cpx"); + throw new Scanner.SyntaxError(); + } + break; + } + + return enumval; + } + + /** + * scanAnnotationData + * + * parses the internals of an annotation. + * + * @param name Annotation Name + * @return a Data data structure containing the annotation data. + * @throws IOException for scanning errors. + */ + private Data scanAnnotationData(String name) throws IOException { + Data data = null; + switch (scanner.token) { + // This handles the Annotation types (as normalized in the constant pool) + // Some primitive types (Boolean, char, short, byte) are identified by a keyword. + case INTVAL: + env.traceln("[AnnotationParser.scanAnnotationData]:: Integer Field: " + name + " = " + scanner.intValue); + data = new ConstElemValue('I', parser.pool.FindCell(ConstType.CONSTANT_INTEGER, scanner.intValue)); + scanner.scan(); + break; + case DOUBLEVAL: + env.traceln("[AnnotationParser.scanAnnotationData]:: Double Field: " + name + " = " + scanner.doubleValue); + double dval = scanner.doubleValue; + long ivdal = Double.doubleToLongBits(dval); + Long val = ivdal; + data = new ConstElemValue('D', parser.pool.FindCell(ConstType.CONSTANT_DOUBLE, val)); + scanner.scan(); + break; + case FLOATVAL: + env.traceln("[AnnotationParser.scanAnnotationData]:: Float Field: " + name + " = " + scanner.floatValue); + float fval = scanner.floatValue; + int ifval = Float.floatToIntBits(fval); + Integer val1 = ifval; + data = new ConstElemValue('F', parser.pool.FindCell(ConstType.CONSTANT_FLOAT, val1)); + scanner.scan(); + break; + case LONGVAL: + env.traceln("[AnnotationParser.scanAnnotationData]:: Long Field: " + name + " = " + scanner.longValue); + data = new ConstElemValue('J', parser.pool.FindCell(ConstType.CONSTANT_LONG, scanner.longValue)); + scanner.scan(); + break; + case STRINGVAL: + env.traceln("[AnnotationParser.scanAnnotationData]:: String Field: " + name + " = " + scanner.stringValue); + data = new ConstElemValue('s', parser.pool.FindCellAsciz(scanner.stringValue)); + scanner.scan(); + break; + case CLASS: + env.traceln("[AnnotationParser.scanAnnotationData]:: Class) keyword: " + scanner.stringValue); + data = scanAnnotationClass(name); + break; + case ENUM: + // scan the next two identifiers (eg ident.ident), or 2 CPRefs. + // if it is an Ident, use consume it as the class name. + env.traceln("[AnnotationParser.scanAnnotationData]:: Enum) keyword: " + scanner.stringValue); + data = scanAnnotationEnum(name); + break; + case IDENT: + env.traceln("[AnnotationParser.scanAnnotationData]:: JASM Keyword: (annotation field name: " + name + ") keyword: " + scanner.stringValue); + data = scanAnnotationIdent(scanner.stringValue, name); + break; + case ANNOTATION: + env.traceln("[AnnotationParser.scanAnnotationData]:: Annotation Field: " + name + " = " + scanner.stringValue); + data = new AnnotationElemValue(parseAnnotation()); + break; + case LBRACE: + env.traceln("[AnnotationParser.scanAnnotationData]:: Annotation Array Field: " + name); + data = scanAnnotationArray(name); + break; + default: + env.error(scanner.pos, "incorrect.annot.token", scanner.token); + throw new Scanner.SyntaxError(); + } + + return data; + } + + /** + * scanAnnotationIdent + * + * parses the identifier of an annotation. + * + * @param ident Basic Type identifier + * @param name Annotation Name + * @return Basic Type Annotation data + * @throws IOException if scanning errors occur + */ + private Data scanAnnotationIdent(String ident, String name) throws IOException { + // Handle JASM annotation Keyword Identifiers + Data data; + BasicType type = basictype(ident); + switch (type) { + + case T_BOOLEAN: + // consume the keyword, get the value + scanner.scan(); + switch (scanner.token) { + case INTVAL: + // Handle Boolean value in integer form + env.traceln("Boolean Field: " + name + " = " + scanner.intValue); + Integer val = scanner.intValue; + if (val > 1 || val < 0) { + env.traceln("Warning: Boolean Field: " + name + " value is not 0 or 1, value = " + scanner.intValue); + } + data = new ConstElemValue('Z', parser.pool.FindCell(ConstType.CONSTANT_INTEGER, val)); + scanner.scan(); + break; + case IDENT: + // handle boolean value with true/false keywords + int val1; + switch (scanner.stringValue) { + case "true": + val1 = 1; + break; + case "false": + val1 = 0; + break; + default: + throw new IOException("Incorrect Annotation (boolean), expected true/false), got \"" + scanner.stringValue + "\"."); + } + env.traceln("Boolean Field: " + name + " = " + scanner.stringValue); + data = new ConstElemValue('Z', parser.pool.FindCell(ConstType.CONSTANT_INTEGER, val1)); + scanner.scan(); + break; + default: + env.error(scanner.pos, "incorrect.annot.bool", scanner.stringValue); + throw new Scanner.SyntaxError(); + } + break; + case T_BYTE: + // consume the keyword, get the value + scanner.scan(); + switch (scanner.token) { + case INTVAL: + env.traceln("Byte Field: " + name + " = " + scanner.intValue); + Integer val = scanner.intValue; + if (val > 0xFF) { + env.traceln("Warning: Byte Field: " + name + " value is greater than 0xFF, value = " + scanner.intValue); + } + data = new ConstElemValue('B', parser.pool.FindCell(ConstType.CONSTANT_INTEGER, val)); + scanner.scan(); + break; + default: + env.error(scanner.pos, "incorrect.annot.byte", scanner.stringValue); + throw new Scanner.SyntaxError(); + } + break; + case T_CHAR: + // consume the keyword, get the value + scanner.scan(); + switch (scanner.token) { + case INTVAL: + env.traceln("Char Field: " + name + " = " + scanner.intValue); + Integer val = scanner.intValue; + // Bounds check? + data = new ConstElemValue('C', parser.pool.FindCell(ConstType.CONSTANT_INTEGER, val)); + scanner.scan(); + break; + default: + env.error(scanner.pos, "incorrect.annot.char", scanner.stringValue); + throw new Scanner.SyntaxError(); + } + break; + case T_SHORT: + // consume the keyword, get the value + scanner.scan(); + switch (scanner.token) { + case INTVAL: + env.traceln("Short Field: " + name + " = " + scanner.intValue); + Integer val = scanner.intValue; + if (val > 0xFFFF) { + env.traceln("Warning: Short Field: " + name + " value is greater than 0xFFFF, value = " + scanner.intValue); + } + data = new ConstElemValue('S', parser.pool.FindCell(ConstType.CONSTANT_INTEGER, val)); + scanner.scan(); + break; + default: + env.error(scanner.pos, "incorrect.annot.short", scanner.stringValue); + throw new Scanner.SyntaxError(); + } + break; + default: + env.error(scanner.pos, "incorrect.annot.keyword", ident); + throw new Scanner.SyntaxError(); + } + return data; + } +} diff --git a/test/lib/org/openjdk/asmtools/jasm/ParserCP.java b/test/lib/org/openjdk/asmtools/jasm/ParserCP.java new file mode 100644 index 00000000000..1f725d2beaa --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/ParserCP.java @@ -0,0 +1,665 @@ +/* + * Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.function.BiFunction; + +import static org.openjdk.asmtools.jasm.JasmTokens.Token; +import static org.openjdk.asmtools.jasm.Tables.*; + +/** + * ParserCP + * + * ParseCP is a parser class owned by Parser.java. It is primarily responsible for parsing + * the constant pool and constant declarations. + */ +public class ParserCP extends ParseBase { + + /** + * Stop parsing a source file immediately and interpret any issue as an error + */ + private boolean exitImmediately = false; + + /** + * local handles on the scanner, main parser, and the error reporting env + */ + /** + * Visitor object + */ + private ParserCPVisitor pConstVstr; + /** + * counter of left braces + */ + private int lbrace = 0; + + + /** + * main constructor + * + * @param scanner + * @param parser + * @param env + */ + protected ParserCP(Scanner scanner, Parser parser, Environment env) { + super.init(scanner, parser, env); + pConstVstr = new ParserCPVisitor(); + } + + /** + * In particular cases it's necessary to interpret a warning issue as an error and + * stop parsing a source file immediately + * cpParser.setExitImmediately(true); + * çparseConstRef(...); + * cpParser.setExitImmediately(false); + */ + public void setExitImmediately(boolean exitImmediately) { + this.exitImmediately = exitImmediately; + } + + public boolean isExitImmediately() { + return exitImmediately; + } + + /** + * ParserCPVisitor + * + * This inner class overrides a constant pool visitor to provide specific parsing + * instructions (per method) for each type of Constant. + * + * Note: since the generic visitor throws no exceptions, this derived class tunnels + * the exceptions, rethrown in the visitEcept method. + */ + class ParserCPVisitor extends ConstantPool.CPTagVisitor { + + private IOException IOProb; + private Scanner.SyntaxError SyProb; + + + public ParserCPVisitor() { + IOProb = null; + SyProb = null; + } + + //This is the entry point for a visitor that tunnels exceptions + public ConstantPool.ConstValue visitExcept(ConstType tag) throws IOException, Scanner.SyntaxError { + IOProb = null; + SyProb = null; + debugStr("------- [ParserCPVisitor.visitExcept]: "); + ConstantPool.ConstValue ret = visit(tag); + + if (IOProb != null) { + throw IOProb; + } + + if (SyProb != null) { + throw SyProb; + } + + return ret; + } + + @Override + public ConstantPool.ConstValue visitUTF8(ConstType tag) { + debugStr("------- [ParserCPVisitor.visitUTF8]: "); + try { + scanner.expect(Token.STRINGVAL); + } catch (IOException e) { + IOProb = e; + } + ConstantPool.ConstValue_String obj + = new ConstantPool.ConstValue_String(scanner.stringValue); + return obj; + } + + @Override + public ConstantPool.ConstValue visitInteger(ConstType tag) { + debugStr("------- [ParserCPVisitor.visitInteger]: "); + ConstantPool.ConstValue_Integer obj; + int v = 0; + try { + if (scanner.token == Token.BITS) { + scanner.scan(); + scanner.inBits = true; + } + v = scanner.intValue * scanner.sign; + scanner.expect(Token.INTVAL); + } catch (IOException e) { + IOProb = e; + } + obj = new ConstantPool.ConstValue_Integer(tag, v); + return obj; + } + + @Override + public ConstantPool.ConstValue visitLong(ConstType tag) { + debugStr("------- [ParserCPVisitor.visitLong]: "); + ConstantPool.ConstValue_Long obj = null; + try { + long v; + if (scanner.token == Token.BITS) { + scanner.scan(); + scanner.inBits = true; + } + switch (scanner.token) { + case INTVAL: + v = scanner.intValue; + break; + case LONGVAL: + v = scanner.longValue; + break; + default: + env.error(scanner.prevPos, "token.expected", "Integer"); + throw new Scanner.SyntaxError(); + } + obj = new ConstantPool.ConstValue_Long(tag, v * scanner.sign); + scanner.scan(); + } catch (IOException e) { + IOProb = e; + } catch (Scanner.SyntaxError e) { + SyProb = e; + } + return obj; + } + + @Override + public ConstantPool.ConstValue visitFloat(ConstType tag) { + debugStr("------- [ParserCPVisitor.visitFloat]: "); + ConstantPool.ConstValue_Integer obj = null; + try { + int v; + float f; + scanner.inBits = false; // this needs to be initialized for each float! + if (scanner.token == Token.BITS) { + scanner.scan(); + scanner.inBits = true; + } +i2f: { + switch (scanner.token) { + case INTVAL: + if (scanner.inBits) { + v = scanner.intValue; + break i2f; + } else { + f = (float) scanner.intValue; + break; + } + case FLOATVAL: + f = scanner.floatValue; + break; + case DOUBLEVAL: + f = (float) scanner.doubleValue; // to be excluded? + break; + case INF: + f = Float.POSITIVE_INFINITY; + break; + case NAN: + f = Float.NaN; + break; + default: + env.traceln("token=" + scanner.token); + env.error(scanner.pos, "token.expected", ""); + throw new Scanner.SyntaxError(); + } + v = Float.floatToIntBits(f); + } + if (scanner.sign == -1) { + v = v ^ 0x80000000; + } + obj = new ConstantPool.ConstValue_Integer(tag, v); + scanner.scan(); + } catch (IOException e) { + IOProb = e; + } catch (Scanner.SyntaxError e) { + SyProb = e; + } + return obj; + } + + @Override + public ConstantPool.ConstValue visitDouble(ConstType tag) { + debugStr("------- [ParserCPVisitor.visitDouble]: "); + ConstantPool.ConstValue_Long obj = null; + try { + long v; + double d; + if (scanner.token == Token.BITS) { + scanner.scan(); + scanner.inBits = true; + } +d2l: { + switch (scanner.token) { + case INTVAL: + if (scanner.inBits) { + v = scanner.intValue; + break d2l; + } else { + d = scanner.intValue; + break; + } + case LONGVAL: + if (scanner.inBits) { + v = scanner.longValue; + break d2l; + } else { + d = (double) scanner.longValue; + break; + } + case FLOATVAL: + d = scanner.floatValue; + break; + case DOUBLEVAL: + d = scanner.doubleValue; + break; + case INF: + d = Double.POSITIVE_INFINITY; + break; + case NAN: + d = Double.NaN; + break; + default: + env.error(scanner.pos, "token.expected", "Double"); + throw new Scanner.SyntaxError(); + } + v = Double.doubleToLongBits(d); + } + if (scanner.sign == -1) { + v = v ^ 0x8000000000000000L; + } + obj = new ConstantPool.ConstValue_Long(tag, v); + scanner.scan(); + } catch (IOException e) { + IOProb = e; + } catch (Scanner.SyntaxError e) { + SyProb = e; + } + return obj; + } + + private ConstantPool.ConstCell visitName(ConstType tag) { + debugStr("------- [ParserCPVisitor.visitName]: "); + ConstantPool.ConstCell obj = null; + try { + obj = parser.parseName(); + } catch (IOException e) { + IOProb = e; + } + return obj; + } + + @Override + public ConstantPool.ConstValue visitMethodtype(ConstType tag) { + debugStr("------- [ParserCPVisitor.visitMethodtype]: "); + ConstantPool.ConstValue_Cell obj = null; + ConstantPool.ConstCell cell = visitName(tag); + if (IOProb == null) { + obj = new ConstantPool.ConstValue_Cell(tag, cell); + } + return obj; + } + + @Override + public ConstantPool.ConstValue visitString(ConstType tag) { + debugStr("------- [ParserCPVisitor.visitString]: "); + ConstantPool.ConstValue_Cell obj = null; + ConstantPool.ConstCell cell = visitName(tag); + if (IOProb == null) { + obj = new ConstantPool.ConstValue_Cell(tag, cell); + } + return obj; + } + + @Override + public ConstantPool.ConstValue visitClass(ConstType tag) { + debugStr("------- [ParserCPVisitor.visitClass]: "); + ConstantPool.ConstValue_Cell obj = null; + try { + ConstantPool.ConstCell cell = parser.parseClassName(true); + obj = new ConstantPool.ConstValue_Cell(tag, cell); + } catch (IOException e) { + IOProb = e; + } + return obj; + } + + @Override + public ConstantPool.ConstValue visitMethodhandle(ConstType tag) { + debugStr("------- [ParserCPVisitor.visitMethodHandle]: "); + ConstantPool.ConstValue_Pair obj = null; + try { + ConstantPool.ConstCell refCell; + ConstantPool.ConstCell subtagCell; + SubTag subtag; + // MethodHandle [INVOKESUBTAG|INVOKESUBTAG_INDEX] : CONSTANT_FIELD | [FIELDREF|METHODREF|INTERFACEMETHODREF] + if (scanner.token == Token.INTVAL) { + // INVOKESUBTAG_INDEX + // Handle explicit constant pool form + subtag = subtag(scanner.intValue); + subtagCell = new ConstantPool.ConstCell(subtag.value()); + scanner.scan(); + scanner.expect(Token.COLON); + if (scanner.token == Token.CPINDEX) { + // CONSTANT_FIELD + int cpx = scanner.intValue; + refCell = parser.pool.getCell(cpx); + scanner.scan(); + } else { + // [FIELDREF|METHODREF|INTERFACEMETHODREF] + refCell = parser.parseMethodHandle(subtag); + } + } else { + // INVOKESUBTAG : REF_INVOKEINTERFACE, REF_NEWINVOKESPECIAL, ... + // normal JASM + subtag = parser.parseSubtag(); + subtagCell = new ConstantPool.ConstCell(subtag.value()); + scanner.expect(Token.COLON); + if (scanner.token == Token.CPINDEX) { + // CODETOOLS-7901522: Jasm doesn't allow to create REF_invoke* referring an InterfaceMethod + // Parsing the case when refCell is CP index (#1) + // const #1 = InterfaceMethod m:"()V"; + // const #2 = MethodHandle REF_invokeSpecial:#1; + int cpx = scanner.intValue; + refCell = parser.pool.getCell(cpx); + scanner.scan(); + } else { + refCell = parser.parseMethodHandle(subtag); + } + } + obj = new ConstantPool.ConstValue_Pair(tag, subtagCell, refCell); + } catch (IOException e) { + IOProb = e; + } + return obj; + } + + private ConstantPool.ConstValue_Pair visitMember(ConstType tag) { + debugStr("------- [ParserCPVisitor.visitMember]: "); + ConstantPool.ConstValue_Pair obj = null; + try { + Token prevtoken = scanner.token; + ConstantPool.ConstCell firstName, ClassCell, NameCell, NapeCell; + firstName = parser.parseClassName(false); + if (scanner.token == Token.FIELD) { // DOT + scanner.scan(); + if (prevtoken == Token.CPINDEX) { + ClassCell = firstName; + } else { + ClassCell = parser.pool.FindCell(ConstType.CONSTANT_CLASS, firstName); + } + NameCell = parser.parseName(); + } else { + // no class provided - assume current class + ClassCell = parser.cd.me; + NameCell = firstName; + } + if (scanner.token == Token.COLON) { + // name and type separately + scanner.scan(); + NapeCell = parser.pool.FindCell(ConstType.CONSTANT_NAMEANDTYPE, NameCell, parser.parseName()); + } else { + // name and type as single name + NapeCell = NameCell; + } + obj = new ConstantPool.ConstValue_Pair(tag, ClassCell, NapeCell); + } catch (IOException e) { + IOProb = e; + } + return obj; + } + + @Override + public ConstantPool.ConstValue visitField(ConstType tag) { + debugStr("------- [ParserCPVisitor.visitField]: "); + return visitMember(tag); + } + + @Override + public ConstantPool.ConstValue visitMethod(ConstType tag) { + debugStr("------- [ParserCPVisitor.visitMethod]: "); + return visitMember(tag); + } + + @Override + public ConstantPool.ConstValue visitInterfacemethod(ConstType tag) { + debugStr("------- [ParserCPVisitor.visitInterfacemethod]: "); + return visitMember(tag); + } + + @Override + public ConstantPool.ConstValue visitNameandtype(ConstType tag) { + debugStr("------- [ParserCPVisitor.visitNameandtype]: "); + ConstantPool.ConstValue_Pair obj = null; + try { + ConstantPool.ConstCell NameCell = parser.parseName(), TypeCell; + scanner.expect(Token.COLON); + TypeCell = parser.parseName(); + obj = new ConstantPool.ConstValue_Pair(tag, NameCell, TypeCell); + } catch (IOException e) { + IOProb = e; + } + return obj; + } + + @Override + public ConstantPool.ConstValue_IndyPair visitInvokedynamic(ConstType tag) { + debugStr("------- [ParserCPVisitor.visitInvokeDynamic]: "); + final BiFunction ctor = + (bsmData, napeCell) -> new ConstantPool.ConstValue_IndyPair(bsmData, napeCell); + return visitBsm(ctor); + } + + @Override + public ConstantPool.ConstValue_CondyPair visitDynamic(ConstType tag) { + debugStr("------- [ParserCPVisitor.visitDynamic]: "); + final BiFunction ctor = + (bsmData, napeCell) -> new ConstantPool.ConstValue_CondyPair(bsmData, napeCell); + return visitBsm(ctor); + } + + private E visitBsm(BiFunction ctor) { + E obj = null; + try { + if (scanner.token == Token.INTVAL) { + // Handle explicit constant pool form + int bsmIndex = scanner.intValue; + scanner.scan(); + scanner.expect(Token.COLON); + if (scanner.token != Token.CPINDEX) { + env.traceln("token=" + scanner.token); + env.error(scanner.pos, "token.expected", ""); + throw new Scanner.SyntaxError(); + } + int cpx = scanner.intValue; + scanner.scan(); + // Put a placeholder in place of BSM. + // resolve placeholder after the attributes are scanned. + BootstrapMethodData bsmData = new BootstrapMethodData(bsmIndex); + obj = ctor.apply(bsmData, parser.pool.getCell(cpx)); + } else { + // Handle full form + ConstantPool.ConstCell MHCell = parser.pool.FindCell(parseConstValue(ConstType.CONSTANT_METHODHANDLE)); + scanner.expect(Token.COLON); + ConstantPool.ConstCell NapeCell = parser.pool.FindCell(parseConstValue(ConstType.CONSTANT_NAMEANDTYPE)); + if(scanner.token == Token.LBRACE) { + ParserCP.this.lbrace++; + scanner.scan(); + } + ArrayList bsm_args = new ArrayList<>(256); + while(true) { + if( ParserCP.this.lbrace > 0 ) { + if(scanner.token == Token.RBRACE ) { + ParserCP.this.lbrace--; + scanner.scan(); + break; + } else if(scanner.token == Token.SEMICOLON) { + scanner.expect(Token.RBRACE); + } + } else if(scanner.token == Token.SEMICOLON) { + break; + } + if (scanner.token == Token.COMMA) { + scanner.scan(); + } + bsm_args.add(parseConstRef(null)); + } + if( ParserCP.this.lbrace == 0 ) { + scanner.check(Token.SEMICOLON); + } + BootstrapMethodData bsmData = new BootstrapMethodData(MHCell, bsm_args); + parser.cd.addBootstrapMethod(bsmData); + obj = ctor.apply(bsmData, NapeCell); + } + } catch (IOException e) { + IOProb = e; + } + return obj; + } + } // End Visitor + + /** + * Parse CONSTVALUE + */ + protected ConstantPool.ConstValue parseConstValue(ConstType tag) throws IOException, Scanner.SyntaxError { + return pConstVstr.visitExcept(tag); + } + + /** + * Parse [TAG] CONSTVALUE + */ + protected ConstantPool.ConstValue parseTagConstValue(ConstType defaultTag) throws Scanner.SyntaxError, IOException { + return parseTagConstValue(defaultTag, null, false); + } + + private ConstType scanConstByID(boolean ignoreKeywords) { + ConstType tag = null; + if (!ignoreKeywords) { + ConstType tg = Tables.tag(scanner.idValue); + if (tg != null) { + tag = tg; + } + debugStr(" *^*^*^*^ [ParserCP.scanConst]: {TAG = " + (tg == null ? "null" : tg.toString()) + " "); + } + return tag; + } + + private ConstType scanConstPrimVal() throws Scanner.SyntaxError, IOException { + ConstType tag = null; + switch (scanner.token) { + case INTVAL: + tag = ConstType.CONSTANT_INTEGER; + break; + case LONGVAL: + tag = ConstType.CONSTANT_LONG; + break; + case FLOATVAL: + tag = ConstType.CONSTANT_FLOAT; + break; + case DOUBLEVAL: + tag = ConstType.CONSTANT_DOUBLE; + break; + case STRINGVAL: + case BITS: + case IDENT: + tag = ConstType.CONSTANT_STRING; + break; + default: + // problem - no constant value + System.err.println("NEAR: " + scanner.token.printValue()); + env.error(scanner.pos, "value.expected"); + throw new Scanner.SyntaxError(); + } + return tag; + } + + private void checkWrongTag(ConstType tag, ConstType defaultTag, ConstType default2Tag) throws Scanner.SyntaxError, IOException { + if (defaultTag != null) { + if (tag != defaultTag) { + if (default2Tag == null) { + if( exitImmediately ) { + env.error("wrong.tag", defaultTag.parseKey()); + throw new Scanner.SyntaxError().Fatal(); + } + env.error("warn.wrong.tag", defaultTag.parseKey()); + } else if (tag != default2Tag) { + if( exitImmediately ) { + env.error("wrong.tag2", defaultTag.parseKey(), default2Tag.parseKey()); + throw new Scanner.SyntaxError().Fatal(); + } + env.error("warn.wrong.tag2", defaultTag.parseKey(), default2Tag.parseKey()); + } + } + } + } + + protected ConstantPool.ConstValue parseTagConstValue(ConstType defaultTag, ConstType default2Tag, boolean ignoreKeywords) throws Scanner.SyntaxError, IOException { + debugScan(" *^*^*^*^ [ParserCP.parseTagConstValue]: Begin default_tag: ignoreKeywords: " + (ignoreKeywords ? "true" : "false")); + // Lookup the Tag from the scanner + ConstType tag = scanConstByID(ignoreKeywords); + debugStr(" *^*^*^*^ [ParserCP.parseTagConstValue]: {tag = " + tag + ", defaulttag = " + defaultTag + "} "); + + // If the scanned tag is null + if (tag == null) { + // and, if the expected tag is null + if (defaultTag == null) { + // return some other type of constant as the tag + tag = scanConstPrimVal(); + } else { + // otherwise, make the scanned-tag the same constant-type + // as the expected tag. + tag = defaultTag; + } + } else { + // If the scanned tag is some constant type + // and the scanned type does not equal the expected type + checkWrongTag(tag, defaultTag, default2Tag); + scanner.scan(); + } + return parseConstValue(tag); + } // end parseTagConstValue + + protected ConstantPool.ConstCell parseConstRef(ConstType defaultTag) throws Scanner.SyntaxError, IOException { + return parseConstRef(defaultTag, null, false); + } + + protected ConstantPool.ConstCell parseConstRef(ConstType defaultTag, ConstType default2Tag) throws Scanner.SyntaxError, IOException { + return parseConstRef(defaultTag, default2Tag, false); + } + + /** + * Parse an instruction argument, one of: * #NUMBER, #NAME, [TAG] CONSTVALUE + */ + protected ConstantPool.ConstCell parseConstRef(ConstType defaultTag, + ConstType default2Tag, + boolean ignoreKeywords) throws Scanner.SyntaxError, IOException { + if (scanner.token == Token.CPINDEX) { + int cpx = scanner.intValue; + scanner.scan(); + return parser.pool.getCell(cpx); + } else { + ConstantPool.ConstValue ref = parseTagConstValue(defaultTag, default2Tag, ignoreKeywords); + return parser.pool.FindCell(ref); + } + } // end parseConstRef + +} diff --git a/test/lib/org/openjdk/asmtools/jasm/ParserInstr.java b/test/lib/org/openjdk/asmtools/jasm/ParserInstr.java new file mode 100644 index 00000000000..2e98ac8b407 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/ParserInstr.java @@ -0,0 +1,414 @@ +/* + * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import static org.openjdk.asmtools.jasm.JasmTokens.*; +import static org.openjdk.asmtools.jasm.Tables.*; +import static org.openjdk.asmtools.jasm.OpcodeTables.*; +import java.io.IOException; +import java.lang.reflect.Modifier; + +/** + * ParserInstr + * + * ParserInstr is a parser class owned by Parser.java. It is primarily responsible for + * parsing instruction byte codes. + */ +public class ParserInstr extends ParseBase { + + /** + * local handle for the constant parser - needed for parsing constants during + * instruction construction. + */ + private ParserCP cpParser = null; + + /** + * main constructor + * + * @param scanner + * @param parser + * @param env + */ + protected ParserInstr(Scanner scanner, Parser parser, ParserCP cpParser, Environment env) { + super.init(scanner, parser, env); + this.cpParser = cpParser; + } + + /** + * Parse an instruction. + */ + protected void parseInstr() throws Scanner.SyntaxError, IOException { + // ignore possible line numbers after java disassembler + if (scanner.token == Token.INTVAL) { + scanner.scan(); + } + // ignore possible numeric labels after java disassembler + if (scanner.token == Token.INTVAL) { + scanner.scan(); + } + if (scanner.token == Token.COLON) { + scanner.scan(); + } + + String mnemocode; + int mnenoc_pos; + for (;;) { // read labels + if (scanner.token != Token.IDENT) { + return; + } + mnemocode = scanner.idValue; + mnenoc_pos = scanner.pos; + scanner.scan(); + if (scanner.token != Token.COLON) { + break; + } + // actually it was a label + scanner.scan(); + parser.curCode.LabelDef(mnenoc_pos, mnemocode); + } + + Opcode opcode = OpcodeTables.opcode(mnemocode); + if (opcode == null) { + debugScan(" Error: mnemocode = '" + mnemocode + "'. "); + } + OpcodeType optype = opcode.type(); + + Argument arg = null; + Object arg2 = null; + StackMapData sMap = null; + + debugScan(" --IIIII---[ParserInstr:[parseInstr]: (Pos: " + mnenoc_pos + ") mnemocode: '" + opcode.parsekey() + "' "); + + switch (optype) { + case NORMAL: + switch (opcode) { + + // pseudo-instructions: + case opc_bytecode: + for (;;) { + parser.curCode.addInstr(mnenoc_pos, Opcode.opc_bytecode, parser.parseUInt(1), null); + if (scanner.token != Token.COMMA) { + return; + } + scanner.scan(); + } + case opc_try: + for (;;) { + parser.curCode.beginTrap(scanner.pos, parser.parseIdent()); + if (scanner.token != Token.COMMA) { + return; + } + scanner.scan(); + } + case opc_endtry: + for (;;) { + parser.curCode.endTrap(scanner.pos, parser.parseIdent()); + if (scanner.token != Token.COMMA) { + return; + } + scanner.scan(); + } + case opc_catch: + parser.curCode.trapHandler(scanner.pos, parser.parseIdent(), + cpParser.parseConstRef(ConstType.CONSTANT_CLASS)); + return; + case opc_var: + for (;;) { + parser.parseLocVarDef(); + if (scanner.token != Token.COMMA) { + return; + } + scanner.scan(); + } + case opc_endvar: + for (;;) { + parser.parseLocVarEnd(); + if (scanner.token != Token.COMMA) { + return; + } + scanner.scan(); + } + case opc_locals_map: + sMap = parser.curCode.getStackMap(); + if (sMap.localsMap != null) { + env.error(scanner.pos, "localsmap.repeated"); + } + ; + DataVector localsMap = new DataVector(); + sMap.localsMap = localsMap; + if (scanner.token == Token.SEMICOLON) { + return; // empty locals_map allowed + } + for (;;) { + parser.parseMapItem(localsMap); + if (scanner.token != Token.COMMA) { + return; + } + scanner.scan(); + } + case opc_stack_map: + sMap = parser.curCode.getStackMap(); + if (sMap.stackMap != null) { + env.error(scanner.pos, "stackmap.repeated"); + } + ; + DataVector stackMap = new DataVector(); + sMap.stackMap = stackMap; + if (scanner.token == Token.SEMICOLON) { + return; // empty stack_map allowed + } + for (;;) { + parser.parseMapItem(stackMap); + if (scanner.token != Token.COMMA) { + return; + } + scanner.scan(); + } + case opc_stack_frame_type: + sMap = parser.curCode.getStackMap(); + if (sMap.stackFrameType != null) { + env.error(scanner.pos, "frametype.repeated"); + } + ; + sMap.setStackFrameType(parser.parseIdent()); + return; + + // normal instructions: + case opc_aload: + case opc_astore: + case opc_fload: + case opc_fstore: + case opc_iload: + case opc_istore: + case opc_lload: + case opc_lstore: + case opc_dload: + case opc_dstore: + case opc_ret: + case opc_aload_w: + case opc_astore_w: + case opc_fload_w: + case opc_fstore_w: + case opc_iload_w: + case opc_istore_w: + case opc_lload_w: + case opc_lstore_w: + case opc_dload_w: + case opc_dstore_w: + case opc_ret_w: + // loc var + arg = parser.parseLocVarRef(); + break; + case opc_iinc: // loc var, const + arg = parser.parseLocVarRef(); + scanner.expect(Token.COMMA); + arg2 = parser.parseInt(1); + break; + case opc_tableswitch: + case opc_lookupswitch: + arg2 = parseSwitchTable(); + break; + case opc_newarray: { + int type; + if (scanner.token == Token.INTVAL) { + type = scanner.intValue; + } else if ((type = Tables.basictypeValue(scanner.idValue)) == -1) { + env.error(scanner.pos, "type.expected"); + throw new Scanner.SyntaxError(); + } + scanner.scan(); + arg = new Argument(type); + break; + } + case opc_new: + case opc_aconst_init: + case opc_anewarray: + case opc_instanceof: + case opc_checkcast: + arg = cpParser.parseConstRef(ConstType.CONSTANT_CLASS); + break; + case opc_bipush: + arg = parser.parseInt(1); + break; + case opc_sipush: + arg = parser.parseInt(2); + break; + case opc_ldc: + case opc_ldc_w: + case opc_ldc2_w: + arg = cpParser.parseConstRef(null); + break; + case opc_putstatic: + case opc_getstatic: + case opc_putfield: + case opc_getfield: + case opc_withfield: + arg = cpParser.parseConstRef(ConstType.CONSTANT_FIELD); + break; + case opc_invokevirtual: + arg = cpParser.parseConstRef(ConstType.CONSTANT_METHOD); + break; + case opc_invokestatic: + case opc_invokespecial: + ConstType ctype01 = ConstType.CONSTANT_METHOD; + ConstType ctype02 = ConstType.CONSTANT_INTERFACEMETHOD; + if(Modifier.isInterface(this.parser.cd.access)) { + ctype01 = ConstType.CONSTANT_INTERFACEMETHOD; + ctype02 = ConstType.CONSTANT_METHOD; + } + arg = cpParser.parseConstRef(ctype01, ctype02); + break; + case opc_jsr: + case opc_goto: + case opc_ifeq: + case opc_ifge: + case opc_ifgt: + case opc_ifle: + case opc_iflt: + case opc_ifne: + case opc_if_icmpeq: + case opc_if_icmpne: + case opc_if_icmpge: + case opc_if_icmpgt: + case opc_if_icmple: + case opc_if_icmplt: + case opc_if_acmpeq: + case opc_if_acmpne: + case opc_ifnull: + case opc_ifnonnull: + case opc_jsr_w: + case opc_goto_w: + arg = parseLabelRef(); + break; + + case opc_invokeinterface: + arg = cpParser.parseConstRef(ConstType.CONSTANT_INTERFACEMETHOD); + scanner.expect(Token.COMMA); + arg2 = parser.parseUInt(1); + break; + case opc_invokedynamic: + arg = cpParser.parseConstRef(ConstType.CONSTANT_INVOKEDYNAMIC); + break; + + case opc_multianewarray: + arg = cpParser.parseConstRef(ConstType.CONSTANT_CLASS); + scanner.expect(Token.COMMA); + arg2 = parser.parseUInt(1); + break; + case opc_wide: + case opc_nonpriv: + case opc_priv: + int opc2 = (opcode.value() << 8) | parser.parseUInt(1).arg; + opcode = opcode(opc2); + break; + } + break; + case WIDE: + arg = parser.parseLocVarRef(); + if (opcode == Opcode.opc_iinc_w) { // loc var, const + scanner.expect(Token.COMMA); + arg2 = parser.parseInt(2); + } + break; + case NONPRIVELEGED: + case PRIVELEGED: + break; + default: + env.error(scanner.prevPos, "wrong.mnemocode", mnemocode); + throw new Scanner.SyntaxError(); + } + // env.traceln(" [ParserInstr.parseInstr] ===============> Adding Instruction: [" + mnenoc_pos + "]: instr: "+ mnemocode /* opcNamesTab[opc] */); + parser.curCode.addInstr(mnenoc_pos, opcode, arg, arg2); + } //end parseInstr + + /** + * Parse a Switch Table. return value: SwitchTable. + */ + protected SwitchTable parseSwitchTable() throws Scanner.SyntaxError, IOException { + scanner.expect(Token.LBRACE); + Argument label; + int numpairs = 0, key; + SwitchTable table = new SwitchTable(env); +tableScan: + { + while (numpairs < 1000) { +// env.traceln("start tableScan:" + token); + switch (scanner.token) { + case INTVAL: +// env.traceln("enter tableScan:" + token); + key = scanner.intValue * scanner.sign; + scanner.scan(); + scanner.expect(Token.COLON); + table.addEntry(key, parseLabelRef()); + numpairs++; + if (scanner.token != Token.SEMICOLON) { +// env.traceln("break tableScan1:" + token); + break tableScan; + } + scanner.scan(); + break; + case DEFAULT: + scanner.scan(); + scanner.expect(Token.COLON); + if (table.deflabel != null) { + env.error("default.redecl"); + } + table.deflabel = parseLabelRef(); + if (scanner.token != Token.SEMICOLON) { +// env.traceln("break tableScan2:" + token); + break tableScan; + } + scanner.scan(); + break; + default: +// env.traceln("break tableScan3:" + token + "val=" + intValue); + break tableScan; + } // end switch + } // while (numpairs<1000) + env.error("long.switchtable", "1000"); + } // end tableScan + scanner.expect(Token.RBRACE); + return table; + } // end parseSwitchTable + + /** + * Parse a label instruction argument + */ + protected Argument parseLabelRef() throws Scanner.SyntaxError, IOException { + switch (scanner.token) { + case INTVAL: { + int v = scanner.intValue * scanner.sign; + scanner.scan(); + return new Argument(v); + } + case IDENT: { + String label = scanner.stringValue; + scanner.scan(); + return parser.curCode.LabelRef(label); + } + } + env.error("label.expected"); + throw new Scanner.SyntaxError(); + } + +} diff --git a/test/lib/org/openjdk/asmtools/jasm/PermittedSubclassesAttr.java b/test/lib/org/openjdk/asmtools/jasm/PermittedSubclassesAttr.java new file mode 100644 index 00000000000..436bba8fee6 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/PermittedSubclassesAttr.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.util.List; + +/** + * The "classes[]" data of attributes + * JEP 360 (Sealed types): class file 59.65535 + * PermittedSubclasses_attribute { + * u2 attribute_name_index; + * u4 attribute_length; + * u2 number_of_classes; + * u2 classes[number_of_classes]; + * } + */ +public class PermittedSubclassesAttr extends ClassArrayAttr { + public PermittedSubclassesAttr(ClassData cdata, List classes) { + super(Tables.AttrTag.ATT_PermittedSubclasses.parsekey(), cdata, classes); + } +} diff --git a/test/lib/org/openjdk/asmtools/jasm/PreloadAttr.java b/test/lib/org/openjdk/asmtools/jasm/PreloadAttr.java new file mode 100644 index 00000000000..9704d752aac --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/PreloadAttr.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.util.List; + +public class PreloadAttr extends ClassArrayAttr { + public PreloadAttr(ClassData cdata, List classes) { + super(Tables.AttrTag.ATT_Preload.parsekey(), cdata, classes); + } +} diff --git a/test/lib/org/openjdk/asmtools/jasm/RecordData.java b/test/lib/org/openjdk/asmtools/jasm/RecordData.java new file mode 100644 index 00000000000..c24308da766 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/RecordData.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static org.openjdk.asmtools.jasm.RuntimeConstants.*; + +/** + * The record attribute (JEP 359 since class file 58.65535) + */ +public class RecordData extends AttrData { + private List components = new ArrayList<>(); + + public RecordData(ClassData cls) { + super(cls, Tables.AttrTag.ATT_Record.parsekey()); + } + + public void addComponent(ConstantPool.ConstCell nameCell, + ConstantPool.ConstCell descCell, + ConstantPool.ConstCell signature, + ArrayList annotations) { + // Define a field if absent + FieldData fd = getClassData().addFieldIfAbsent(ACC_MANDATED & ACC_PRIVATE & ACC_FINAL, nameCell, descCell); + ComponentData cd = new ComponentData(fd); + if( annotations != null ) { + cd.addAnnotations(annotations); + } + if( signature != null ) { + cd.setSignatureAttr(signature); + } + components.add(cd); + } + + public boolean isEmpty() { + return components.isEmpty(); + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + super.write(out); + out.writeShort(components.size()); + for (ComponentData cd : components) { + cd.write(out); + } + } + + @Override + public int attrLength() { + int compsLength = components.stream().mapToInt(c -> c.getLength()).sum(); + return 2 + compsLength; + } + + class ComponentData extends MemberData { + private FieldData field; + + public ComponentData(FieldData field) { + super(getClassData()); + this.field = field; + } + + @Override + protected DataVector getAttrVector() { + return getDataVector(signatureAttr); + } + + public void write(CheckedDataOutputStream out) throws IOException, Parser.CompilerError { + out.writeShort(field.getNameDesc().left.arg); + out.writeShort(field.getNameDesc().right.arg); + DataVector attrs = getAttrVector(); + attrs.write(out); + } + + public int getLength() { + return 4 + getAttrVector().getLength(); + } + } +} \ No newline at end of file diff --git a/test/lib/org/openjdk/asmtools/jasm/RuntimeConstants.java b/test/lib/org/openjdk/asmtools/jasm/RuntimeConstants.java new file mode 100644 index 00000000000..7107891c0e8 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/RuntimeConstants.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 1996, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.util.HashMap; +import java.util.Map; + +/** + * + */ +public interface RuntimeConstants { + /* Access Flags */ + + int ACC_NONE = 0x0000; // <> + int ACC_PUBLIC = 0x0001; // class, inner, field, method + int ACC_PRIVATE = 0x0002; // inner, field, method + int ACC_PROTECTED = 0x0004; // inner, field, method + int ACC_STATIC = 0x0008; // inner, field, method + int ACC_FINAL = 0x0010; // class, inner, field, method + int ACC_TRANSITIVE = 0x0010; // requires(module) + int ACC_SUPER = 0x0020; // class + int ACC_STATIC_PHASE = 0x0020; // requires(module) + int ACC_SYNCHRONIZED = 0x0020; // method + int ACC_OPEN = 0x0020; // module + int ACC_VALUE = 0x0040; // class, inner + int ACC_VOLATILE = 0x0040; // field + int ACC_BRIDGE = 0x0040; // method + int ACC_TRANSIENT = 0x0080; // field + int ACC_VARARGS = 0x0080; // method + int ACC_NATIVE = 0x0100; // method + int ACC_INTERFACE = 0x0200; // class, inner + int ACC_ABSTRACT = 0x0400; // class, inner, method + int ACC_PRIMITIVE = 0x0800; // class, inner + int ACC_STRICT = 0x0800; // method + int ACC_SYNTHETIC = 0x1000; // class, inner, field, method, module requires(module) exports(module) + int ACC_ANNOTATION = 0x2000; // class, inner + int ACC_ENUM = 0x4000; // class, inner, field + int ACC_MODULE = 0x8000; // class + int ACC_MANDATED = 0x8000; // method module requires(module) exports(module) + + /* Attribute codes */ + int SYNTHETIC_ATTRIBUTE = 0x00010000; // actually, this is an attribute + int DEPRECATED_ATTRIBUTE = 0x00020000; // actually, this is an attribute + + Map ACC_NAMES = new HashMap() {{ + put(ACC_PUBLIC ,"public"); + put(ACC_PRIVATE ,"private"); + put(ACC_PROTECTED ,"protected"); + put(ACC_STATIC ,"static"); + put(ACC_FINAL ,"final"); + put(ACC_SUPER ,"super"); + put(ACC_SYNCHRONIZED ,"synchronized"); + put(ACC_VOLATILE ,"volatile"); + put(ACC_BRIDGE ,"bridge"); + put(ACC_TRANSIENT ,"transient"); + put(ACC_VARARGS ,"varargs"); + put(ACC_VALUE ,"value"); + put(ACC_NATIVE ,"native"); + put(ACC_INTERFACE ,"interface"); + put(ACC_ABSTRACT ,"abstract"); + put(ACC_PRIMITIVE ,"primitive"); + put(ACC_STRICT ,"strict"); + put(ACC_SYNTHETIC ,"synthetic"); + put(ACC_ANNOTATION ,"annotation"); + put(ACC_ENUM ,"enum"); + put(ACC_MODULE ,"module"); + put(ACC_MANDATED ,"mandated"); + put(SYNTHETIC_ATTRIBUTE ,"synthetic"); + }}; + + /* The version of a class file since which the compact format of stack map is necessary */ + int SPLIT_VERIFIER_CFV = 50; + +} diff --git a/test/lib/org/openjdk/asmtools/jasm/Scanner.java b/test/lib/org/openjdk/asmtools/jasm/Scanner.java new file mode 100644 index 00000000000..ae929c1c053 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/Scanner.java @@ -0,0 +1,1209 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import static java.lang.String.format; +import static org.openjdk.asmtools.jasm.JasmTokens.*; +import static org.openjdk.asmtools.jasm.Constants.EOF; +import static org.openjdk.asmtools.jasm.Constants.OFFSETBITS; +import java.io.IOException; +import java.util.function.Predicate; + +/** + * A Scanner for Jasm tokens. Errors are reported to the environment object.

    + * + * The scanner keeps track of the current token, the value of the current token (if any), + * and the start position of the current token.

    + * + * The scan() method advances the scanner to the next token in the input.

    + * + * The match() method is used to quickly match opening brackets (ie: '(', '{', or '[') + * with their closing counter part. This is useful during error recovery.

    + * + * The compiler treats either "\n", "\r" or "\r\n" as the end of a line.

    + */ +public class Scanner extends ParseBase { + + /** + * SyntaxError is the generic error thrown for parsing problems. + */ + protected static class SyntaxError extends Error { + boolean fatalError = false; + SyntaxError Fatal() { fatalError = true; return this; } + boolean isFatal() {return fatalError;} + } + + /** + * Input stream + */ + protected Environment in; + + /** + * The current character + */ + protected int ch; + + /** + * Current token + */ +// protected int token; + protected Token token; + + /** + * The position of the current token + */ + protected int pos; + + /* + * Token values. + */ + protected char charValue; + protected int intValue; + protected long longValue; + protected float floatValue; + protected double doubleValue; + protected String stringValue; + protected String idValue; + protected int radix; // Radix, when reading int or long + + /* doc comment preceding the most recent token */ + protected String docComment; + + /* A growable character buffer. */ + private int count; + private char buffer[] = new char[32]; + // + private Predicate escapingAllowed; + /** + * The position of the previous token + */ + protected int prevPos; + protected int sign; // sign, when reading number + protected boolean inBits; // inBits prefix, when reading number + + /** + * main constructor. + * + * Create a scanner to scan an input stream. + */ + protected Scanner(Environment env) throws IOException { + super.init(this, null, env); + escapingAllowed = noFunc; + this.in = env; + ch = env.read(); + xscan(); + } + + protected void scanModuleStatement() throws IOException { + try { + escapingAllowed = yesAndProcessFunc; + scan(); + } finally { + escapingAllowed = noFunc; + } + } + + /** + * scan + * + * Scan the next token. + * + * @throws IOException + */ + protected void scan() throws IOException { + int signloc = 1, cnt = 0; + prevPos = pos; +prefix: + for (;;) { + xscan(); + switch (token) { + case SIGN: + signloc = signloc * intValue; + break; + default: + break prefix; + } + cnt++; + } + switch (token) { + case INTVAL: + case LONGVAL: + case FLOATVAL: + case DOUBLEVAL: + case INF: + case NAN: + sign = signloc; + break; + default: + } + } + + /** + * Check the token may be identifier + */ + protected final boolean checkTokenIdent() { + return token.possibleJasmIdentifier(); + } + + static String readableConstant(int t) { + return "<" + Tables.tag(t) + "> [" + t + "]"; + } + + /** + * Expects a token, scans the next token or throws an exception. + */ + protected final void expect(Token t) throws SyntaxError, IOException { + check(t); + scan(); + } + + /** + * Checks a token, throws an exception if not the same + */ + protected final void check(Token t) throws SyntaxError, IOException { + if (token != t) { + if ((t != Token.IDENT) || !checkTokenIdent()) { + env.traceln("expect: " + t + " instead of " + token); + switch (t) { + case IDENT: + env.error(pos, "identifier.expected"); + break; + default: + env.error(pos, "token.expected", "<" + t.printValue() + ">"); + break; + } + + if (debugFlag) { + debugStr("<<<<>>>>>>: "); + throw new Error("<<<<>>>>>>"); + } else { + throw new SyntaxError(); + } + } + } + } + + private void putCh(int ch) { + if (count == buffer.length) { + char newBuffer[] = new char[buffer.length * 2]; + System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); + buffer = newBuffer; + } + buffer[count++] = (char) ch; + } + + private String bufferString() { + char buf[] = new char[count]; + System.arraycopy(buffer, 0, buf, 0, count); + return new String(buf); + } + + /** + * Returns true if the character is a unicode digit. + * + * @param ch the character to be checked + */ + public static boolean isUCDigit(int ch) { + if ((ch >= '0') && (ch <= '9')) { + return true; + } + switch (ch >> 8) { + case 0x06: + return ((ch >= 0x0660) && (ch <= 0x0669)) || // Arabic-Indic + ((ch >= 0x06f0) && (ch <= 0x06f9)); // Eastern Arabic-Indic + case 0x07: + case 0x08: + default: + return false; + case 0x09: + return ((ch >= 0x0966) && (ch <= 0x096f)) || // Devanagari + ((ch >= 0x09e6) && (ch <= 0x09ef)); // Bengali + case 0x0a: + return ((ch >= 0x0a66) && (ch <= 0x0a6f)) || // Gurmukhi + ((ch >= 0x0ae6) && (ch <= 0x0aef)); // Gujarati + case 0x0b: + return ((ch >= 0x0b66) && (ch <= 0x0b6f)) || // Oriya + ((ch >= 0x0be7) && (ch <= 0x0bef)); // Tamil + case 0x0c: + return ((ch >= 0x0c66) && (ch <= 0x0c6f)) || // Telugu + ((ch >= 0x0ce6) && (ch <= 0x0cef)); // Kannada + case 0x0d: + return ((ch >= 0x0d66) && (ch <= 0x0d6f)); // Malayalam + case 0x0e: + return ((ch >= 0x0e50) && (ch <= 0x0e59)) || // Thai + ((ch >= 0x0ed0) && (ch <= 0x0ed9)); // Lao + case 0x0f: + return false; + case 0x10: + return ((ch >= 0x1040) && (ch <= 0x1049)); // Tibetan + } + } + + /** + * Returns true if the character is a Unicode letter. + * + * @param ch the character to be checked + */ + public static boolean isUCLetter(int ch) { + // fast check for Latin capitals and small letters + if (((ch >= 'A') && (ch <= 'Z')) + || ((ch >= 'a') && (ch <= 'z'))) { + return true; + } + // rest of ISO-LATIN-1 + if (ch < 0x0100) { + // fast check + if (ch < 0x00c0) { + return (ch == '_') || (ch == '$'); + } + // various latin letters and diacritics, + // but *not* the multiplication and division symbols + return ((ch >= 0x00c0) && (ch <= 0x00d6)) + || ((ch >= 0x00d8) && (ch <= 0x00f6)) + || ((ch >= 0x00f8) && (ch <= 0x00ff)); + } + // other non CJK alphabets and symbols, but not digits + if (ch <= 0x1fff) { + return !isUCDigit(ch); + } + // rest are letters only in five ranges: + // Hiragana, Katakana, Bopomofo and Hangul + // CJK Squared Words + // Korean Hangul Symbols + // Han (Chinese, Japanese, Korean) + // Han compatibility + return ((ch >= 0x3040) && (ch <= 0x318f)) + || ((ch >= 0x3300) && (ch <= 0x337f)) + || ((ch >= 0x3400) && (ch <= 0x3d2d)) + || ((ch >= 0x4e00) && (ch <= 0x9fff)) + || ((ch >= 0xf900) && (ch <= 0xfaff)); + } + + /** + * Scan a comment. This method should be called once the initial /, * and the next + * character have been read. + */ + private void skipComment() throws IOException { + while (true) { + switch (ch) { + case EOF: + env.error(pos, "eof.in.comment"); + return; + case '*': + if ((ch = in.read()) == '/') { + ch = in.read(); + return; + } + break; + default: + ch = in.read(); + break; + } + } + } + + /** + * Scan a doc comment. This method should be called once the initial /, * and * have + * been read. It gathers the content of the comment (without leading spaces and '*'s) + * in the string buffer. + */ + @SuppressWarnings("empty-statement") + private String scanDocComment() throws IOException { + count = 0; + + if (ch == '*') { + do { + ch = in.read(); + } while (ch == '*'); + if (ch == '/') { + ch = in.read(); + return ""; + } + } + switch (ch) { + case '\n': + case ' ': + ch = in.read(); + break; + } + + boolean seenstar = false; + int c = count; + while (true) { + switch (ch) { + case EOF: + env.error(pos, "eof.in.comment"); + return bufferString(); + case '\n': + putCh('\n'); + ch = in.read(); + seenstar = false; + c = count; + break; + case ' ': + case '\t': + putCh(ch); + ch = in.read(); + break; + case '*': + if (seenstar) { + if ((ch = in.read()) == '/') { + ch = in.read(); + count = c; + return bufferString(); + } + putCh('*'); + } else { + seenstar = true; + count = c; + while ((ch = in.read()) == '*'); + switch (ch) { + case ' ': + ch = in.read(); + break; + case '/': + ch = in.read(); + count = c; + return bufferString(); + } + } + break; + default: + if (!seenstar) { + seenstar = true; + } + putCh(ch); + ch = in.read(); + c = count; + break; + } + } + } + + /** + * Scan a decimal at this point + */ + private void scanCPRef() throws IOException { + switch (ch = in.read()) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + boolean overflow = false; + long value = ch - '0'; + count = 0; + putCh(ch); // save character in buffer +numberLoop: + for (;;) { + switch (ch = in.read()) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + putCh(ch); + if (overflow) { + break; + } + value = (value * 10) + (ch - '0'); + overflow = (value > 0xFFFF); + break; + default: + break numberLoop; + } + } // while true + intValue = (int) value; + stringValue = bufferString(); + token = Token.CPINDEX; + if (overflow) { + env.error(pos, "overflow"); + } + break; + } + default: + stringValue = Character.toString((char)ch); + env.error(in.pos, "invalid.number", stringValue); + intValue = 0; + token = Token.CPINDEX; + ch = in.read(); + } + } // scanCPRef() + + /** + * Scan a number. The first digit of the number should be the current character. We + * may be scanning hex, decimal, or octal at this point + */ + private void scanNumber() throws IOException { + boolean seenNonOctal = false; + boolean overflow = false; + radix = (ch == '0' ? 8 : 10); + long value = ch - '0'; + count = 0; + putCh(ch); // save character in buffer +numberLoop: + for (;;) { + switch (ch = in.read()) { + case '.': + if (radix == 16) { + break numberLoop; // an illegal character + } + scanReal(); + return; + + case '8': + case '9': + // We can't yet throw an error if reading an octal. We might + // discover we're really reading a real. + seenNonOctal = true; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + putCh(ch); + if (radix == 10) { + overflow = overflow || (value * 10) / 10 != value; + value = (value * 10) + (ch - '0'); + overflow = overflow || (value - 1 < -1); + } else if (radix == 8) { + overflow = overflow || (value >>> 61) != 0; + value = (value << 3) + (ch - '0'); + } else { + overflow = overflow || (value >>> 60) != 0; + value = (value << 4) + (ch - '0'); + } + break; + case 'd': + case 'D': + case 'e': + case 'E': + case 'f': + case 'F': + if (radix != 16) { + scanReal(); + return; + } + // fall through + case 'a': + case 'A': + case 'b': + case 'B': + case 'c': + case 'C': + putCh(ch); + if (radix != 16) { + break numberLoop; // an illegal character + } + overflow = overflow || (value >>> 60) != 0; + value = (value << 4) + 10 + + Character.toLowerCase((char) ch) - 'a'; + break; + case 'l': + case 'L': + ch = in.read(); // skip over 'l' + longValue = value; + token = Token.LONGVAL; + break numberLoop; + case 'x': + case 'X': + // if the first character is a '0' and this is the second + // letter, then read in a hexadecimal number. Otherwise, error. + if (count == 1 && radix == 8) { + radix = 16; + break; + } else { + // we'll get an illegal character error + break numberLoop; + } + default: + intValue = (int) value; + token = Token.INTVAL; + break numberLoop; + } + } // while true + // we have just finished reading the number. The next thing better + // not be a letter or digit. + if (isUCDigit(ch) || isUCLetter(ch) || ch == '.') { + env.error(in.pos, "invalid.number", Character.toString((char)ch)); + do { + ch = in.read(); + } while (isUCDigit(ch) || isUCLetter(ch) || ch == '.'); + intValue = 0; + token = Token.INTVAL; + } else if (radix == 8 && seenNonOctal) { + intValue = 0; + token = Token.INTVAL; + env.error(in.pos, "invalid.octal.number"); + } else if (overflow + || (token == Token.INTVAL + && ((radix == 10) ? (intValue - 1 < -1) + : ((value & 0xFFFFFFFF00000000L) != 0)))) { + intValue = 0; // so we don't get second overflow in Parser + longValue = 0; + env.error(pos, "overflow"); + } + } // scanNumber() + + /** + * Scan a float. We are either looking at the decimal, or we have already seen it and + * put it into the buffer. We haven't seen an exponent. Scan a float. Should be called + * with the current character is either the 'e', 'E' or '.' + */ + private void scanReal() throws IOException { + boolean seenExponent = false; + boolean isSingleFloat = false; + char lastChar; + if (ch == '.') { + putCh(ch); + ch = in.read(); + } + +numberLoop: + for (;; ch = in.read()) { + switch (ch) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + putCh(ch); + break; + case 'e': + case 'E': + if (seenExponent) { + break numberLoop; // we'll get a format error + } + putCh(ch); + seenExponent = true; + break; + case '+': + case '-': + lastChar = buffer[count - 1]; + if (lastChar != 'e' && lastChar != 'E') { + break numberLoop; // this isn't an error, though! + } + putCh(ch); + break; + case 'f': + case 'F': + ch = in.read(); // skip over 'f' + isSingleFloat = true; + break numberLoop; + case 'd': + case 'D': + ch = in.read(); // skip over 'd' + // fall through + default: + break numberLoop; + } // sswitch + } // loop + + // we have just finished reading the number. The next thing better + // not be a letter or digit. + if (isUCDigit(ch) || isUCLetter(ch) || ch == '.') { + env.error(in.pos, "invalid.number", Character.toString((char)ch)); + do { + ch = in.read(); + } while (isUCDigit(ch) || isUCLetter(ch) || ch == '.'); + doubleValue = 0; + token = Token.DOUBLEVAL; + } else { + token = isSingleFloat ? Token.FLOATVAL : Token.DOUBLEVAL; + try { + lastChar = buffer[count - 1]; + if (lastChar == 'e' || lastChar == 'E' + || lastChar == '+' || lastChar == '-') { + env.error(in.pos - 1, "float.format"); + } else if (isSingleFloat) { + floatValue = Float.valueOf(bufferString()); + if (Float.isInfinite(floatValue)) { + env.error(pos, "overflow"); + } + } else { + doubleValue = Double.valueOf(bufferString()); + if (Double.isInfinite(doubleValue)) { + env.error(pos, "overflow"); + env.error(pos, "overflow"); + } + } + } catch (NumberFormatException ee) { + env.error(pos, "float.format"); + doubleValue = 0; + floatValue = 0; + } + } + } // scanReal + + /** + * Scan an escape character. + * + * @return the character or '\\' + */ + private int scanEscapeChar() throws IOException { + int p = in.pos; + + switch (ch = in.read()) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': { + int n = ch - '0'; + for (int i = 2; i > 0; i--) { + switch (ch = in.read()) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + n = (n << 3) + ch - '0'; + break; + default: + if (n > 0xFF) { + env.error(p, "invalid.escape.char"); + } + return n; + } + } + ch = in.read(); + if (n > 0xFF) { + env.error(p, "invalid.escape.char"); + } + return n; + } + case 'r': + ch = in.read(); + return '\r'; + case 'n': + ch = in.read(); + return '\n'; + case 'f': + ch = in.read(); + return '\f'; + case 'b': + ch = in.read(); + return '\b'; + case 't': + ch = in.read(); + return '\t'; + case '\\': + ch = in.read(); + return '\\'; + case '\"': + ch = in.read(); + return '\"'; + case '\'': + ch = in.read(); + return '\''; + case 'u': + int unich = in.convertUnicode(); + ch = in.read(); + return unich; + } + return '\\'; + } + + /** + * Scan a string. The current character should be the opening " of the string. + */ + private void scanString() throws IOException { + token = Token.STRINGVAL; + count = 0; + ch = in.read(); + + // Scan a String + while (true) { + switch (ch) { + case EOF: + env.error(pos, "eof.in.string"); + stringValue = bufferString(); + return; + case '\n': + ch = in.read(); + env.error(pos, "newline.in.string"); + stringValue = bufferString(); + return; + case '"': + ch = in.read(); + stringValue = bufferString(); + return; + case '\\': { + int c = scanEscapeChar(); + if (c >= 0) { + putCh((char) c); + } + break; + } + default: + putCh(ch); + ch = in.read(); + break; + } + } + } + + + /** + * Scan an Identifier. The current character should be the first character of the + * identifier. + */ + private void scanIdentifier(char[] prefix) throws IOException { + int firstChar; + count = 0; + if(prefix != null) { + for(;;) { + for (int i = 0; i < prefix.length; i++) + putCh(prefix[i]); + ch = in.read(); + if (ch == '\\') { + ch = in.read(); + if (ch == 'u') { + ch = in.convertUnicode(); + if (!isUCLetter(ch) && !isUCDigit(ch)) { + prefix = new char[]{(char)ch}; + continue; + } + } else if (escapingAllowed.test(ch)) { + prefix = new char[]{(char)ch}; + continue; + } + int p = in.pos; + env.error(p, "invalid.escape.char"); + } + break; + } + } + firstChar = ch; + boolean firstIteration = true; +scanloop: + while (true) { + putCh(ch); + ch = in.read(); + + // Check to see if the annotation marker is at + // the front of the identifier. + if (firstIteration && firstChar == '@') { + // May be a type annotation + if (ch == 'T') { // type annotation + putCh(ch); + ch = in.read(); + } + + // is either a runtime visible or invisible annotation + if (ch == '+' || ch == '-') { // regular annotation + // possible annotation - + // need to eat up the '@+' or '@-' + putCh(ch); + ch = in.read(); + } + idValue = bufferString(); + stringValue = idValue; + token = Token.ANNOTATION; + return; + } + + firstIteration = false; + switch (ch) { + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '$': + case '_': + case '-': + case '[': + case ']': + case '(': + case ')': + case '<': + case '>': + break; + case '/': {// may be comment right after identifier + int c = in.lookForward(); + if ((c == '*') || (c == '/')) { + break scanloop; // yes, comment + } + break; // no, continue to parse identifier + } + case '\\': + ch = in.read(); + if ( ch == 'u') { + ch = in.convertUnicode(); + if (isUCLetter(ch) || isUCDigit(ch)) { + break; + } + } else if( escapingAllowed.test(ch)) { + break; + } + int p = in.pos; + env.error(p, "invalid.escape.char"); + default: +// if ((!isUCDigit(ch)) && (!isUCLetter(ch))) { + break scanloop; +// } + } // end switch + } // end scanloop + idValue = bufferString(); + stringValue = idValue; + token = keyword_token_ident(idValue); + debugStr(format("##### SCANNER (scanIdent) ######## token = %s value = \"%s\"\n", token, idValue)); + } // end scanIdentifier + +//============================== + @SuppressWarnings("empty-statement") + protected final void xscan() throws IOException { + docComment = null; +loop: + for (;;) { + pos = in.pos; + switch (ch) { + case EOF: + token = Token.EOF; + break loop; + case '\n': + case ' ': + case '\t': + case '\f': + ch = in.read(); + break; + case '/': + switch (ch = in.read()) { + case '/': + // Parse a // comment + while (((ch = in.read()) != EOF) && (ch != '\n')); + break; + case '*': + ch = in.read(); + if (ch == '*') { + docComment = scanDocComment(); + } else { + skipComment(); + } + break; + default: + token = Token.DIV; + break loop; + } + break; + case '"': + scanString(); + break loop; + case '-': + intValue = -1; + token = Token.SIGN; + ch = in.read(); + break loop; + case '+': + intValue = +1; + ch = in.read(); + token = Token.SIGN; + break loop; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + scanNumber(); + break loop; + case '.': + switch (ch = in.read()) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + count = 0; + putCh('.'); + scanReal(); + break; + default: + token = Token.FIELD; + } + break loop; + case '{': + ch = in.read(); + token = Token.LBRACE; + break loop; + case '}': + ch = in.read(); + token = Token.RBRACE; + break loop; + case ',': + ch = in.read(); + token = Token.COMMA; + break loop; + case ';': + ch = in.read(); + token = Token.SEMICOLON; + break loop; + case ':': + ch = in.read(); + token = Token.COLON; + break loop; + case '=': + if ((ch = in.read()) == '=') { + ch = in.read(); + token = Token.EQ; + break loop; + } + token = Token.ASSIGN; + break loop; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case '$': + case '_': + case '@': + case '[': + case ']': + case '(': + case ')': + case '<': + case '>': + scanIdentifier(null); + break loop; + case '\u001a': + // Our one concession to DOS. + if ((ch = in.read()) == EOF) { + token = Token.EOF; + break loop; + } + env.error(pos, "funny.char"); + ch = in.read(); + break; + case '#': + int c = in.lookForward(); + if (c == '{') { + // '#' char denotes a "paramMethod name" token + ch = in.read(); + token = Token.PARAM_NAME; + break loop; + } + // otherwise, it is a normal cpref + scanCPRef(); + break loop; + case '\\': + ch = in.read(); + if ( ch == 'u') { + ch = in.convertUnicode(); + if (isUCLetter(ch)) { + scanIdentifier(null); + break loop; + } + } else if( escapingAllowed.test(ch)) { + scanIdentifier(new char[]{'\\', (char)ch}); + break loop; + } +// if ((ch = in.read()) == 'u') { +// ch = in.convertUnicode(); +// if (isUCLetter(ch)) { +// scanIdentifier(); +// break loop; +// } +// } + default: + env.out.println("funny.char:" + env.lineNumber(pos) + "/" + (pos & ((1 << OFFSETBITS) - 1))); + env.error(pos, "funny.char"); + ch = in.read(); + } + } + } + + @Override + protected void debugScan(String dbstr) { + if (token == null) { + env.traceln(dbstr + "<<>>"); + return; + } + env.trace(dbstr + token); + switch (token) { + case IDENT: + env.traceln(" = '" + stringValue + "' {idValue = '" + idValue + "'}"); + break; + case STRINGVAL: + env.traceln(" = {stringValue}: \"" + stringValue + "\""); + break; + case INTVAL: + env.traceln(" = {intValue}: " + intValue + "}"); + break; + case FLOATVAL: + env.traceln(" = {floatValue}: " + floatValue); + break; + case DOUBLEVAL: + env.traceln(" = {doubleValue}: " + doubleValue); + break; + default: + env.traceln(""); + } + } + + private Predicate noFunc = (ch)-> false; + private Predicate yesAndProcessFunc = (ch) -> { + boolean res = ((ch == '\\') || (ch == ':') || (ch == '@')); + if (res) + putCh('\\'); + return res; + }; +} diff --git a/test/lib/org/openjdk/asmtools/jasm/StackMapData.java b/test/lib/org/openjdk/asmtools/jasm/StackMapData.java new file mode 100644 index 00000000000..68ea32b853c --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/StackMapData.java @@ -0,0 +1,281 @@ +/* + * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import static org.openjdk.asmtools.jasm.Tables.*; +import java.io.IOException; + +/** + * + */ +public class StackMapData implements Data { + + /** + * + */ + static public class StackMapItem1 implements Data { + + StackMapType itemType; + + StackMapItem1(StackMapType itemType) { + this.itemType = itemType; + } + + @Override + public int getLength() { + return 1; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeByte(itemType.value()); + } + } + + /** + * + */ + static public class StackMapItem2 implements Data { + + StackMapType itemType; + Argument arg; + + StackMapItem2(StackMapType itemType, Argument arg) { + this.itemType = itemType; + this.arg = arg; + } + + @Override + public int getLength() { + return 3; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeByte(itemType.value()); + out.writeShort(arg.arg); + } + } + + int pc; + int offset; + int type; + String stackFrameType = null; + boolean isStackMapTable = false; + DataVector localsMap, stackMap; + Environment env; + + StackMapData(Environment env) { + this.env = env; + } + + void setPC(int pc) { + this.pc = pc; + } + + void setOffset(int offset) { + this.offset = offset; + } + + void setOffset(StackMapData prevFrame) { + offset = (prevFrame == null) ? pc : (pc - prevFrame.pc - 1); + } + + void setStackFrameType(String stackFrameType) { + this.stackFrameType = stackFrameType; + + if (stackFrameType != null) { + type = stackMapFrameTypeValue(stackFrameType); + } + + if (stackFrameType == null || type == -1) { + env.error(pc, "invalid.stack.frame.type", stackFrameType, "" + type); + } + } + + void setIsStackMapTable(boolean isStackMapTable) { + this.isStackMapTable = isStackMapTable; + } + + void setLocalsMap(DataVector localsMap) { + this.localsMap = localsMap; + } + + void setStackMap(DataVector stackMap) { + this.stackMap = stackMap; + } + + @Override + public int getLength() { + int res = 0; + StackMapFrameType frame_type = StackMapFrameType.FULL_FRAME; + // int frame_type = FULL_FRAME; + + if (isStackMapTable) { + if (stackFrameType != null) { + frame_type = stackMapFrameType(type); + } + res += 1; + } + + switch (frame_type) { + case SAME_FRAME: + break; + case SAME_LOCALS_1_STACK_ITEM_FRAME: + res += stackMap.getLength() - 2; + break; + case SAME_LOCALS_1_STACK_ITEM_EXTENDED_FRAME: + res += stackMap.getLength(); + break; + case CHOP_1_FRAME: + case CHOP_2_FRAME: + case CHOP_3_FRAME: + res += 2; + break; + case SAME_FRAME_EX: + res += 2; + break; + case APPEND_FRAME: + res += 2 + (localsMap == null ? 0 : (localsMap.getLength() - 2)); + break; + case FULL_FRAME: + res += 2; + res += (localsMap == null ? 2 : localsMap.getLength()); + res += (stackMap == null ? 2 : stackMap.getLength()); + break; + default: + ; + } + return res; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + StackMapFrameType frame_type = StackMapFrameType.FULL_FRAME; + + if (isStackMapTable) { + if (stackFrameType != null) { + frame_type = stackMapFrameType(type); + } + } + + switch (frame_type) { + case SAME_FRAME: + if (offset >= 64) { + env.error(pc, "invalid.offset.same.frame", "" + offset); + } + out.writeByte(offset); + break; + case SAME_LOCALS_1_STACK_ITEM_FRAME: + if (stackMap == null) { + env.error(pc, "no.stack.map.same.locals"); + break; + } + + if (stackMap.elements.size() != 1) { + env.error(pc, "should.be.only.one.stack.map.element"); + break; + } + + if (offset >= 64) { + env.error(pc, "invalid.offset.same.locals", "" + offset); + break; + } + out.writeByte(frame_type.value() + offset); + stackMap.writeElements(out); + break; + case SAME_LOCALS_1_STACK_ITEM_EXTENDED_FRAME: + if (stackMap == null) { + env.error(pc, "no.stack.map.same.locals"); + break; + } + + if (stackMap.elements.size() != 1) { + env.error(pc, "should.be.only.one.stack.map.element"); + break; + } + out.writeByte(frame_type.value()); + out.writeShort(offset); + stackMap.writeElements(out); + break; + case CHOP_1_FRAME: + case CHOP_2_FRAME: + case CHOP_3_FRAME: + case SAME_FRAME_EX: + boolean error = false; + + if (stackMap != null) { + env.error(pc, "unexpected.stack.maps"); + error = true; + } + + if (localsMap != null) { + env.error(pc, "unexpected.locals.maps"); + error = true; + } + + if (error) { + break; + } + out.writeByte(frame_type.value()); + out.writeShort(offset); + break; + case APPEND_FRAME: + if (localsMap == null) { + env.error(pc, "no.locals.map.append"); + break; + } + + if (localsMap.elements.size() > 3) { + env.error(pc, "more.locals.map.elements"); + break; + } + out.writeByte(frame_type.value() + localsMap.elements.size() - 1); + out.writeShort(offset); + localsMap.writeElements(out); + break; + case FULL_FRAME: + if (isStackMapTable) { + out.writeByte(frame_type.value()); + out.writeShort(offset); + } else { + out.writeShort(pc); + } + + if (localsMap == null) { + out.writeShort(0); + } else { + localsMap.write(out); + } + + if (stackMap == null) { + out.writeShort(0); + } else { + stackMap.write(out); + } + break; + default: + env.error(pc, "invalid.stack.frame.type", "" + frame_type); + } + } +} diff --git a/test/lib/org/openjdk/asmtools/jasm/SwitchTable.java b/test/lib/org/openjdk/asmtools/jasm/SwitchTable.java new file mode 100644 index 00000000000..402ee60eaee --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/SwitchTable.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.io.IOException; +import java.util.ArrayList; + +/** + * + */ +class SwitchTable { + + Argument deflabel = null; + ArrayList labels = new ArrayList<>(); + ArrayList keys = new ArrayList<>(); + +// for tableswitch: + Argument[] resLabels; + int high, low; + + int pc, pad; + Environment env; + + SwitchTable(Environment env) { + this.env = env; + } + + void addEntry(int key, Argument label) { + keys.add(key); + labels.add(label); + } + +// for lookupswitch: + int calcLookupSwitch(int pc) { + this.pc = pc; + pad = ((3 - pc) & 0x3); + int len = 1 + pad + (keys.size() + 1) * 8; + if (deflabel == null) { + deflabel = new Argument(pc + len); + } + return len; + } + + void writeLookupSwitch(CheckedDataOutputStream out) throws IOException { + env.traceln(" writeLookupSwitch: pc=" + pc + " pad=" + pad + " deflabel=" + deflabel.arg); + int k; + for (k = 0; k < pad; k++) { + out.writeByte(0); + } + out.writeInt(deflabel.arg - pc); + out.writeInt(keys.size()); + for (k = 0; k < keys.size(); k++) { + out.writeInt(keys.get(k)); + out.writeInt((labels.get(k)).arg - pc); + } + } + + int recalcTableSwitch(int pc) { + int k; + int numpairs = keys.size(); + int high1 = Integer.MIN_VALUE, low1 = Integer.MAX_VALUE; + int numslots = 0; + if (numpairs > 0) { + for (k = 0; k < numpairs; k++) { + int key = keys.get(k); + if (key > high1) { + high1 = key; + } + if (key < low1) { + low1 = key; + } + } + numslots = high1 - low1 + 1; + } +// if (numslots>2000) env.error("long.switchtable", "2000"); + env.traceln(" recalcTableSwitch: low=" + low1 + " high=" + high1); + this.pc = pc; + pad = ((3 - pc) & 0x3); + int len = 1 + pad + (numslots + 3) * 4; + if (deflabel == null) { + deflabel = new Argument(pc + len); + } + Argument[] resLabels1 = new Argument[numslots]; + for (k = 0; k < numslots; k++) { + resLabels1[k] = deflabel; + } + for (k = 0; k < numpairs; k++) { + env.traceln(" keys.data[" + k + "]=" + keys.get(k)); + resLabels1[keys.get(k) - low1] = labels.get(k); + } + this.resLabels = resLabels1; + this.labels = null; + this.keys = null; + this.high = high1; + this.low = low1; + return len; + } + + void writeTableSwitch(CheckedDataOutputStream out) throws IOException { + int k; + for (k = 0; k < pad; k++) { + out.writeByte(0); + } + out.writeInt(deflabel.arg - pc); + out.writeInt(low); + out.writeInt(high); + for (k = 0; k < resLabels.length; k++) { + out.writeInt(resLabels[k].arg - pc); + } + } +} diff --git a/test/lib/org/openjdk/asmtools/jasm/Tables.java b/test/lib/org/openjdk/asmtools/jasm/Tables.java new file mode 100644 index 00000000000..4e46f05a0c3 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/Tables.java @@ -0,0 +1,689 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.io.PrintWriter; +import java.util.HashMap; + +/** + * + * Tables + * + * The classes in Tables are following a Singleton Pattern. These classes are Enums, and + * they are contained in private hash maps (lookup tables and reverse lookup tables). + * These hash maps all have public accessors, which clients use to look-up enums. + * + * Tokens in this table carry no external state, and are typically treated as constants. + * They do not need to be reset. + * + */ +public class Tables { + + public static final int JAVA_MAGIC = 0xCAFEBABE; + /** + * Lookup-tables for various types. + */ + private static HashMap NameToAttrTag = new HashMap<>(9); + private static HashMap AttrTags = new HashMap<>(9); + + private static HashMap NameToSubTag = new HashMap<>(9); + private static HashMap SubTags = new HashMap<>(9); + + private static HashMap NameToBasicType = new HashMap<>(10); + private static HashMap BasicTypes = new HashMap<>(10); + + private static HashMap NameToAnnotElemType = new HashMap<>(10); + private static HashMap AnnotElemTypes = new HashMap<>(10); + + private static HashMap KeyToStackMapType = new HashMap<>(10); + private static HashMap NameToStackMapType = new HashMap<>(10); + private static HashMap StackMapTypes = new HashMap<>(10); + + private static HashMap NameToStackMapFrameType = new HashMap<>(10); + private static HashMap StackMapFrameTypes = new HashMap<>(10); + + private static HashMap NameToConstantType = new HashMap<>(ConstType.maxTag); + private static HashMap ConstantTypes = new HashMap<>(ConstType.maxTag); + + static { + // register all of the tokens + for (ConstType ct : ConstType.values()) { + registerConstantType(ct); + } + + /* Type codes for SubTags */ + for (AttrTag at : AttrTag.values()) { + registerAttrtag(at); + } + + /* Type codes for SubTags */ + for (SubTag st : SubTag.values()) { + registerSubtag(st); + } + + /* Type codes for BasicTypes */ + for (BasicType bt : BasicType.values()) { + registerBasicType(bt); + } + + /* Type codes for BasicTypes */ + for (AnnotElemType aet : AnnotElemType.values()) { + registerAnnotElemType(aet); + } + + /* Type codes for StackMapTypes */ + for (StackMapType smt : StackMapType.values()) { + registerStackMapType(smt); + } + + /* Type codes for StackMapFrame attribute */ + for (StackMapFrameType smft : StackMapFrameType.values()) { + registerStackMapFrameType(smft); + } + + } + + /** + * ConstType + * + * A (typed) tag (constant) representing the type of Constant in the Constant Pool. + */ + static public enum ConstType { + CONSTANT_ZERO (-3, "CONSTANT_ZERO", ""), + CONSTANT_UTF8 (1, "CONSTANT_UTF8", "Asciz"), + CONSTANT_UNICODE (2, "CONSTANT_UNICODE", ""), + CONSTANT_INTEGER (3, "CONSTANT_INTEGER", "int"), + CONSTANT_FLOAT (4, "CONSTANT_FLOAT", "float"), + CONSTANT_LONG (5, "CONSTANT_LONG", "long"), + CONSTANT_DOUBLE (6, "CONSTANT_DOUBLE", "double"), + CONSTANT_CLASS (7, "CONSTANT_CLASS", "class"), + CONSTANT_STRING (8, "CONSTANT_STRING", "String"), + CONSTANT_FIELD (9, "CONSTANT_FIELD", "Field"), + CONSTANT_METHOD (10, "CONSTANT_METHOD", "Method"), + CONSTANT_INTERFACEMETHOD (11, "CONSTANT_INTERFACEMETHOD", "InterfaceMethod"), + CONSTANT_NAMEANDTYPE (12, "CONSTANT_NAMEANDTYPE", "NameAndType"), + // Constant 13 reserved + // Constant 14 reserved + CONSTANT_METHODHANDLE (15, "CONSTANT_METHODHANDLE", "MethodHandle"), + CONSTANT_METHODTYPE (16, "CONSTANT_METHODTYPE", "MethodType"), + CONSTANT_DYNAMIC (17, "CONSTANT_DYNAMIC", "Dynamic"), + CONSTANT_INVOKEDYNAMIC (18, "CONSTANT_INVOKEDYNAMIC", "InvokeDynamic"), + CONSTANT_MODULE (19, "CONSTANT_MODULE", "Module"), + CONSTANT_PACKAGE (20, "CONSTANT_PACKAGE", "Package"); + + static final public int maxTag = 20; + + private final int value; + private final String parseKey; + private final String printval; + + ConstType(int val, String print, String parse) { + value = val; + parseKey = parse; + printval = print; + } + + public int value() { + return value; + } + + public String parseKey() { + return parseKey; + } + + public String printval() { + return printval; + } + + public void print(PrintWriter out) { + out.print(parseKey); + } + + @Override + public String toString() { + return "<" + printval + "> [" + Integer.toString(value) + "]"; + } + }; + + static public ConstType tag(int i) { + return ConstantTypes.get(i); + } + + static public ConstType tag(String parsekey) { + return NameToConstantType.get(parsekey); + } + + private static void registerConstantType(ConstType tt) { + NameToConstantType.put(tt.parseKey, tt); + ConstantTypes.put(tt.value, tt); + } + + /** + * Attribute descriptor enums + */ + static public enum AttrTag { + + // Constant for ME Spec (StackMap does not appear in SE VM Spec) + ATT_Unrecognized (0, "ATT_Unrecognized", ""), + ATT_StackMap (1, "ATT_StackMap", "StackMap"), + // Numbers corespond to VM spec (chapter 4.7.X) + ATT_ConstantValue (2, "ATT_ConstantValue", "ConstantValue"), + ATT_Code (3, "ATT_Code", "Code"), + ATT_StackMapTable (4, "ATT_StackMapTable", "StackMapTable"), + ATT_Exceptions (5, "ATT_Exceptions", "Exceptions"), + ATT_InnerClasses (6, "ATT_InnerClasses", "InnerClasses"), + ATT_EnclosingMethod (7, "ATT_EnclosingMethod", "EnclosingMethod"), + ATT_Synthetic (8, "ATT_Synthetic", "Synthetic"), + ATT_Signature (9, "ATT_Signature", "Signature"), + ATT_SourceFile (10, "ATT_SourceFile", "SourceFile"), + ATT_SourceDebugExtension (11, "ATT_SourceDebugExtension", "SourceDebugExtension"), + ATT_LineNumberTable (12, "ATT_LineNumberTable", "LineNumberTable"), + ATT_LocalVariableTable (13, "ATT_LocalVariableTable", "LocalVariableTable"), + ATT_LocalVariableTypeTable (14, "ATT_LocalVariableTypeTable", "LocalVariableTypeTable"), + ATT_Deprecated (15, "ATT_Deprecated", "Deprecated"), + ATT_RuntimeVisibleAnnotations (16, "ATT_RuntimeVisibleAnnotations", "RuntimeVisibleAnnotations"), + ATT_RuntimeInvisibleAnnotations (17, "ATT_RuntimeInvisibleAnnotations", "RuntimeInvisibleAnnotations"), + ATT_RuntimeVisibleParameterAnnotations (18, "ATT_RuntimeVisibleParameterAnnotations", "RuntimeVisibleParameterAnnotations"), + ATT_RuntimeInvisibleParameterAnnotations (19, "ATT_RuntimeInvisibleParameterAnnotations", "RuntimeInvisibleParameterAnnotations"), + ATT_AnnotationDefault (20, "ATT_AnnotationDefault", "AnnotationDefault"), + ATT_BootstrapMethods (21, "ATT_BootstrapMethods", "BootstrapMethods"), + ATT_RuntimeVisibleTypeAnnotations (22, "ATT_RuntimeVisibleTypeAnnotations", "RuntimeVisibleTypeAnnotations"), + ATT_RuntimeInvisibleTypeAnnotations (23, "ATT_RuntimeInvisibleTypeAnnotations", "RuntimeInvisibleTypeAnnotations"), + ATT_MethodParameters (24, "ATT_MethodParameters", "MethodParameters"), + ATT_Module (25, "ATT_Module", "Module"), + ATT_Version (26, "ATT_Version", "Version"), + ATT_TargetPlatform (27, "ATT_TargetPlatform", "TargetPlatform"), + ATT_MainClass (28, "ATT_MainClass", "MainClass"), + ATT_ModulePackages (29, "ATT_ModulePackages", "ModulePackages"), + ATT_ModuleMainClass (30, "ATT_ModuleMainClass", "ModuleMainClass"), + ATT_ModuleTarget (31, "ATT_ModuleTarget", "ModuleTarget"), + // JEP 181: class file 55.0 + ATT_NestHost (32, "ATT_NestHost", "NestHost"), + ATT_NestMembers (33, "ATT_NestMembers", "NestMembers"), + // JEP 359 Record(Preview): class file 58.65535 + // Record_attribute { + // u2 attribute_name_index; + // u4 attribute_length; + // u2 components_count; + // component_info components[components_count]; + // } + ATT_Record (34, "ATT_Record", "Record"), + // JEP 360 (Sealed types): class file 59.65535 + // PermittedSubclasses_attribute { + // u2 attribute_name_index; + // u4 attribute_length; + // u2 number_of_classes; + // u2 classes[number_of_classes]; + // } + ATT_PermittedSubclasses (35, "ATT_PermittedSubclasses", "PermittedSubclasses"), + ATT_Preload (36, "ATT_Preload", "Preload"); + + private final Integer value; + private final String printval; + private final String parsekey; + + AttrTag(Integer val, String print, String parse) { + value = val; + printval = print; + parsekey = parse; + } + + public String printval() { + return printval; + } + + public String parsekey() { + return parsekey; + } + } + + private static void registerAttrtag(AttrTag tg) { + NameToAttrTag.put(tg.parsekey, tg); + AttrTags.put(tg.value, tg); + } + + public static AttrTag attrtag(int val) { + AttrTag tg = AttrTags.get(val); + if (tg == null) { + tg = AttrTag.ATT_Unrecognized; + } + return tg; + } + + public static AttrTag attrtag(String idValue) { + AttrTag tg = NameToAttrTag.get(idValue); + if (tg == null) { + tg = AttrTag.ATT_Unrecognized; + } + return tg; + } + + public static String attrtagName(int subtag) { + AttrTag tg = AttrTags.get(subtag); + return tg.parsekey; + } + + public static int attrtagValue(String idValue) { + AttrTag tg = attrtag(idValue); + return tg.value; + } + + + /*-------------------------------------------------------- */ + /** + * SubTag enums + */ + static public enum SubTag { + REF_GETFIELD (1, "REF_getField"), + REF_GETSTATIC (2, "REF_getStatic"), + REF_PUTFIELD (3, "REF_putField"), + REF_PUTSTATIC (4, "REF_putStatic"), + REF_INVOKEVIRTUAL (5, "REF_invokeVirtual"), + REF_INVOKESTATIC (6, "REF_invokeStatic"), + REF_INVOKESPECIAL (7, "REF_invokeSpecial"), + REF_NEWINVOKESPECIAL (8, "REF_newInvokeSpecial"), + REF_INVOKEINTERFACE (9, "REF_invokeInterface"); + + private final Integer value; + private final String printval; + + SubTag(Integer val, String print) { + value = val; + printval = print; + } + + public String printval() { + return printval; + } + + public Integer value() { + return value; + } + } + + private static void registerSubtag(SubTag tg) { + NameToSubTag.put(tg.printval, tg); + SubTags.put(tg.value, tg); + } + + public static SubTag subtag(String subtag) { + return NameToSubTag.get(subtag); + } + + public static SubTag subtag(int subtag) { + return SubTags.get(subtag); + } + + public static String subtagName(int subtag) { + String retval = null; + SubTag tg = SubTags.get(subtag); + if (tg != null) { + retval = tg.printval; + } + return retval; + } + + public static int subtagValue(String idValue) { + int retval = 0; + SubTag tg = NameToSubTag.get(idValue); + if (tg != null) { + retval = tg.value; + } + return retval; + } + + /*-------------------------------------------------------- */ + /** + * BasicType enums + */ + static public enum BasicType { + T_INT (0x0000000a, "int"), + T_LONG (0x0000000b, "long"), + T_FLOAT (0x00000006, "float"), + T_DOUBLE (0x00000007, "double"), + T_CLASS (0x00000002, "class"), + T_BOOLEAN (0x00000004, "boolean"), + T_CHAR (0x00000005, "char"), + T_BYTE (0x00000008, "byte"), + T_SHORT (0x00000009, "short"); + + private final Integer value; + private final String printval; + + BasicType(Integer val, String print) { + value = val; + printval = print; + } + + public String printval() { + return printval; + } + } + + private static void registerBasicType(BasicType typ) { + NameToBasicType.put(typ.printval, typ); + BasicTypes.put(typ.value, typ); + } + + public static BasicType basictype(String idValue) { + return NameToBasicType.get(idValue); + } + + public static BasicType basictype(int subtag) { + return BasicTypes.get(subtag); + } + + public static String basictypeName(int subtag) { + String retval = null; + BasicType tg = BasicTypes.get(subtag); + if (tg != null) { + retval = tg.printval; + } + return retval; + } + + public static int basictypeValue(String idValue) { + int retval = -1; + BasicType tg = NameToBasicType.get(idValue); + if (tg != null) { + retval = tg.value; + } + return retval; + } + + /*-------------------------------------------------------- */ + /** + * AnnotElemType enums + */ + static public enum AnnotElemType { + + AE_BYTE ('B', "byte"), + AE_CHAR ('C', "char"), + AE_SHORT ('S', "short"), + AE_INT ('I', "int"), + AE_LONG ('J', "long"), + AE_FLOAT ('F', "float"), + AE_DOUBLE ('D', "double"), + AE_BOOLEAN ('Z', "boolean"), + AE_STRING ('s', "string"), + AE_ENUM ('e', "enum"), + AE_CLASS ('c', "class"), + AE_ANNOTATION ('@', "annotation"), + AE_ARRAY ('[', "array"), + AE_UNKNOWN ((char)0, "unknown"); + + private char value; + private final String printval; + + AnnotElemType(char val, String print) { + value = val; + printval = print; + } + + public char val() { + return value; + } + + public String printval() { + return printval; + } + } + + private static void registerAnnotElemType(AnnotElemType typ) { + NameToAnnotElemType.put(typ.printval, typ); + AnnotElemTypes.put(typ.value, typ); + } + + public static AnnotElemType annotElemType(String idValue) { + return NameToAnnotElemType.get(idValue); + } + + public static AnnotElemType annotElemType(char subtag) { + AnnotElemType type = AnnotElemTypes.get(subtag); + if ( type == null ) { + type = AnnotElemType.AE_UNKNOWN; + } + return type; + } + + public static String annotElemTypeName(char subtag) { + String retval = null; + AnnotElemType tg = AnnotElemTypes.get(subtag); + if (tg != null) { + retval = tg.printval; + } + return retval; + } + + public static char annotElemTypeVal(String idValue) { + char retval = 0; + AnnotElemType tg = NameToAnnotElemType.get(idValue); + if (tg != null) { + retval = tg.value; + } + return retval; + } + + + /*-------------------------------------------------------- */ + /** + * MapTypes table. These constants are used in stackmap pseudo-instructions only. + */ + static public enum StackMapType { + /* Type codes for StackMap attribute */ + ITEM_Bogus (0, "bogus", "B"), // an unknown or uninitialized value + ITEM_Integer (1, "int", "I"), // a 32-bit integer + ITEM_Float (2, "float", "F"), // not used + ITEM_Double (3, "double", "D"), // not used + ITEM_Long (4, "long", "L"), // a 64-bit integer + ITEM_Null (5, "null", "N"), // the type of null + ITEM_InitObject (6, "this", "IO"), // "this" in constructor + ITEM_Object (7, "CP", "O"), // followed by 2-byte index of class name + ITEM_NewObject (8, "at", "NO"), // followed by 2-byte ref to "new" + ITEM_UNKNOWN (null, "UNKNOWN", "UNKNOWN"); // placeholder for wrong types + + private Integer value; + private final String printval; + private final String parsekey; + + StackMapType(Integer val, String print, String parse) { + value = val; + printval = print; + parsekey = parse; + } + + public String parsekey() { + return parsekey; + } + + public String printval() { + return printval; + } + + public Integer value() { + return value; + } + } + + private static void registerStackMapType(StackMapType typ) { + KeyToStackMapType.put(typ.parsekey, typ); + NameToStackMapType.put(typ.printval, typ); + StackMapTypes.put(typ.value, typ); + } + + public static StackMapType stackMapType(int subtag, PrintWriter out) { + StackMapType type = StackMapTypes.get(subtag); + if (type == null || type == StackMapType.ITEM_UNKNOWN) { + if (out != null) + out.println("// Unknown StackMap type " + subtag); + type = StackMapType.ITEM_UNKNOWN; + type.value = subtag; + } + return type; + } + + public static StackMapType stackMapType(String subtag) { + return NameToStackMapType.get(subtag); + } + + public static StackMapType stackMapTypeKey(String subtag) { + return KeyToStackMapType.get(subtag); + } + + public static String stackMapTypeName(int subtag) { + String retval = null; + StackMapType tg = StackMapTypes.get(subtag); + if (tg != null) { + retval = tg.printval; + } + return retval; + } + + public static int stackMapTypeValue(String idValue) { + int retval = 0; + StackMapType tg = NameToStackMapType.get(idValue); + if (tg != null) { + retval = tg.value; + } + return retval; + } + + + /*-------------------------------------------------------- */ + /** + * StackMap-FrameType table. These constants are used in stackmap pseudo-instructions + * only. + */ + static public enum StackMapFrameType { + /* Type codes for StackMapFrame attribute */ + SAME_FRAME (0, "same"), + SAME_LOCALS_1_STACK_ITEM_FRAME (64, "stack1"), + SAME_LOCALS_1_STACK_ITEM_EXTENDED_FRAME (247, "stack1_ex"), + CHOP_1_FRAME (250, "chop1"), + CHOP_2_FRAME (249, "chop2"), + CHOP_3_FRAME (248, "chop3"), + SAME_FRAME_EX (251, "same_ex"), + APPEND_FRAME (252, "append"), + FULL_FRAME (255, "full"); + + private final Integer value; + private final String parsekey; + + StackMapFrameType(Integer val, String print) { + value = val; + parsekey = print; + } + + public String parsekey() { + return parsekey; + } + + public Integer value() { + return value; + } + } + + private static void registerStackMapFrameType(StackMapFrameType typ) { + NameToStackMapFrameType.put(typ.parsekey, typ); + StackMapFrameTypes.put(typ.value, typ); + } + + public static StackMapFrameType stackMapFrameTypeVal(int subtag) { + return StackMapFrameTypes.get(subtag); + } + + public static String stackMapFrameTypeName(int subtag) { + String retval = null; + StackMapFrameType tg = StackMapFrameTypes.get(subtag); + if (tg != null) { + retval = tg.parsekey; + } + return retval; + } + + public static StackMapFrameType stackMapFrameType(int subtag) { + StackMapFrameType frametype; + if (subtag < StackMapFrameType.SAME_LOCALS_1_STACK_ITEM_FRAME.value()) { + // type is same_frame; + frametype = StackMapFrameType.SAME_FRAME; + } else if (subtag >= StackMapFrameType.SAME_LOCALS_1_STACK_ITEM_FRAME.value() + && subtag <= 127) { + // type is same_locals_1_stack_item_frame + frametype = StackMapFrameType.SAME_LOCALS_1_STACK_ITEM_FRAME; + + } else if (subtag >= StackMapFrameType.APPEND_FRAME.value() + && subtag < StackMapFrameType.FULL_FRAME.value()) { + // type is append_frame + frametype = StackMapFrameType.APPEND_FRAME; + } else { + frametype = StackMapFrameTypes.get(subtag); + } + return frametype; + } + + public static int stackMapFrameTypeValue(String idValue) { + int retval = 0; + StackMapFrameType tg = NameToStackMapFrameType.get(idValue); + if (tg != null) { + retval = tg.value; + } + return retval; + } + + /** + * CF_Context enums + */ + public enum CF_Context { + + CTX_CLASS (0, "class"), + CTX_FIELD (1, "field"), + CTX_METHOD (2, "method"), + CTX_INNERCLASS (3, "inner-class"), + CTX_MODULE (4, "module") ; + + private final int value; + private final String printval; + + CF_Context(int val, String print) { + value = val; + printval = print; + } + + boolean isOneOf(CF_Context... items) { + for(CF_Context item : items) { + if(item.value == value) { + return true; + } + } + return false; + } + + public int val() { + return value; + } + + public String printval() { + return printval; + } + } +} diff --git a/test/lib/org/openjdk/asmtools/jasm/TypeAnnotationData.java b/test/lib/org/openjdk/asmtools/jasm/TypeAnnotationData.java new file mode 100644 index 00000000000..4988362102b --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/TypeAnnotationData.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import org.openjdk.asmtools.jasm.TypeAnnotationTypes.ETargetType; +import org.openjdk.asmtools.jasm.TypeAnnotationTypes.TypePathEntry; + +import java.io.IOException; + +/** + * JVMS 4.7.20. + * type_annotation { + * u1 target_type; + * union { + * type_parameter_target; + * supertype_target; + * type_parameter_bound_target; + * empty_target; + * formal_parameter_target; + * throws_target; + * localvar_target; + * catch_target; + * offset_target; + * type_argument_target; + * } target_info; + * type_path target_path; + * u2 type_index; + * // + * // + * u2 num_element_value_pairs; + * { u2 element_name_index; + * element_value value; + * } element_value_pairs[num_element_value_pairs]; + * } + */ +public class TypeAnnotationData extends AnnotationData { + + protected ETargetType targetType; + protected TypeAnnotationTargetInfoData targetInfo; + protected TypeAnnotationTypePathData typePath; + + public TypeAnnotationData(Argument typeCPX, boolean invisible) { + super(typeCPX, invisible); + typePath = new TypeAnnotationTypePathData(); + } + + @Override + public int getLength() { + // lengthOf(annotations[]) + lengthOf(targetType) + lengthOf(targetInfo) + lengthOf(targetInfo) + return super.getLength() + 1 + targetInfo.getLength() + typePath.getLength(); + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeByte(targetType.value); + targetInfo.write(out); + typePath.write(out); + super.write(out); + } + + public void addTypePathEntry(TypePathEntry path) { + typePath.addTypePathEntry(path); + } + + @Override + public String toString() { + return toString(0); + } + + public String toString(int tabLevel) { + StringBuilder sb = new StringBuilder(tabString(tabLevel)); + sb.append(targetType.toString()). + append(' '). + append(targetInfo.toString(tabLevel)). + append(typePath.toString(tabLevel)); + return sb.toString(); + } +} diff --git a/test/lib/org/openjdk/asmtools/jasm/TypeAnnotationTargetInfoData.java b/test/lib/org/openjdk/asmtools/jasm/TypeAnnotationTargetInfoData.java new file mode 100644 index 00000000000..10db33ab7ee --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/TypeAnnotationTargetInfoData.java @@ -0,0 +1,546 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * TargetInfo (4.7.20.1. The target_info union) + * + * BaseClass for any Type Annotation Target-Info. + */ +public abstract class TypeAnnotationTargetInfoData implements Data { + + protected TypeAnnotationTypes.ETargetType targettype = null; + + public TypeAnnotationTargetInfoData(TypeAnnotationTypes.ETargetType tt) { + targettype = tt; + } + + public TypeAnnotationTypes.ETargetType getTargetType() { + return targettype; + } + + public void print(PrintWriter out, String tab) { + // print the TargetType and TargetInfo + out.print(tab + " {"); + targettype.print(out); + _print(out, tab); + out.print(tab + "} "); + } + + public abstract void _print(PrintWriter out, String tab); + + public abstract void write(CheckedDataOutputStream out) throws IOException; + + @Override + public String toString() { + return toString(0); + } + + protected abstract void _toString(StringBuilder sb, int tabLevel); + + public String toString(int tabLevel) { + StringBuilder sb = new StringBuilder(tabString(tabLevel)); + // first print the target info name ( + sb.append(targettype.targetInfo().printValue()).append("_target "); + // get the sub-classes parts + _toString(sb, tabLevel); + return sb.toString(); + } + + /** + * type_parameter_target (4.7.20.1. The target_info union) + * + * The type_parameter_target item indicates that an annotation appears on the declaration of the i'th type parameter + * of a generic class, generic interface, generic method, or generic constructor. + * + * type_parameter_target { + * u1 type_parameter_index; + * } + */ + public static class type_parameter_target extends TypeAnnotationTargetInfoData { + + int typeParamIndex; + + public type_parameter_target(TypeAnnotationTypes.ETargetType tt, int index) { + super(tt); + typeParamIndex = index; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeByte(typeParamIndex); + } + + @Override + public void _print(PrintWriter out, String tab) { + out.print(" "); + out.print(typeParamIndex); + } + + @Override + public int getLength() { + return 1; + } + + @Override + protected void _toString(StringBuilder sb, int tabLevel) { + sb.append(tabString(tabLevel)).append(String.format("{ type_parameter_index: %d; }",typeParamIndex)); + } + } + + /** + * supertype_target (4.7.20.1. The target_info union) + * + * The supertype_target item indicates that an annotation appears on a type in the extends or implements clause of + * a class or interface declaration. + * + * supertype_target { + * u2 supertype_index; + * } + */ + public static class supertype_target extends TypeAnnotationTargetInfoData { + + int superTypeIndex; + + public supertype_target(TypeAnnotationTypes.ETargetType tt, int index) { + super(tt); + superTypeIndex = index; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeShort(superTypeIndex); + } + + @Override + public void _print(PrintWriter out, String tab) { + out.print(" "); + out.print(superTypeIndex); + } + + @Override + public int getLength() { + return 2; + } + + @Override + protected void _toString(StringBuilder sb, int tabLevel) { + sb.append(tabString(tabLevel)).append(String.format("{ supertype_index: %d; }",superTypeIndex)); + } + } + + /** + * type_parameter_bound_target (4.7.20.1. The target_info union) + * + * The type_parameter_bound_target item indicates that an annotation appears on the i'th bound of the j'th type parameter + * declaration of a generic class, interface, method, or constructor. + * + * type_parameter_bound_target { + * u1 type_parameter_index; + * u1 bound_index; + * } + */ + public static class type_parameter_bound_target extends TypeAnnotationTargetInfoData { + + int typeParamIndex; + int boundIndex; + + public type_parameter_bound_target(TypeAnnotationTypes.ETargetType tt, int pindx, int bindx) { + super(tt); + typeParamIndex = pindx; + boundIndex = bindx; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeByte(typeParamIndex); + out.writeByte(boundIndex); + } + + @Override + public void _print(PrintWriter out, String tab) { + out.print(" "); + out.print(typeParamIndex); + out.print(" "); + out.print(boundIndex); + } + + @Override + public int getLength() { + return 2; + } + + @Override + protected void _toString(StringBuilder sb, int tabLevel) { + sb.append(tabString(tabLevel)).append(String.format("{ type_parameter_index: %d; bound_index: %d; }", + typeParamIndex, boundIndex)); + } + } + + /** + * empty_target (4.7.20.1. The target_info union) + * + * The empty_target item indicates that an annotation appears on either the type in a field declaration, + * the return type of a method, the type of a newly constructed object, or the receiver type of a method or constructor. + * + * empty_target { + * } + */ + public static class empty_target extends TypeAnnotationTargetInfoData { + + public empty_target(TypeAnnotationTypes.ETargetType tt) { + super(tt); + } + + @Override + public void _print(PrintWriter out, String tab) { + // do nothing + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + // do nothing + } + + @Override + public int getLength() { return 0; } + + @Override + protected void _toString(StringBuilder sb, int tabLevel) { + sb.append(tabString(tabLevel)).append("{ }"); + } + } + + /** + * formal_parameter_target (4.7.20.1. The target_info union) + * + * The formal_parameter_target item indicates that an annotation appears on the type in a formal parameter + * declaration of a method, constructor, or lambda expression. + * + * formal_parameter_target { + * u1 formal_parameter_index; + * } + */ + public static class formal_parameter_target extends TypeAnnotationTargetInfoData { + + int formalParamIndex; + + public formal_parameter_target(TypeAnnotationTypes.ETargetType tt, int index) { + super(tt); + formalParamIndex = index; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeByte(formalParamIndex); + } + + @Override + public void _print(PrintWriter out, String tab) { + out.print(" "); + out.print(formalParamIndex); + } + + @Override + public int getLength() { + return 1; + } + + @Override + protected void _toString(StringBuilder sb, int tabLevel) { + sb.append(tabString(tabLevel)).append(String.format("{ formal_parameter_index: %d; }",formalParamIndex)); + } + } + + /** + * throws_target (4.7.20.1. The target_info union) + * + * The throws_target item indicates that an annotation appears on the i'th type in the throws clause of a method or + * constructor declaration. + * + * throws_target { + * u2 throws_type_index; + * } + */ + public static class throws_target extends TypeAnnotationTargetInfoData { + + int throwsTypeIndex; + + public throws_target(TypeAnnotationTypes.ETargetType tt, int index) { + super(tt); + throwsTypeIndex = index; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeShort(throwsTypeIndex); + } + + @Override + public void _print(PrintWriter out, String tab) { + out.print(" "); + out.print(throwsTypeIndex); + } + + @Override + public int getLength() { + return 2; + } + + @Override + protected void _toString(StringBuilder sb, int tabLevel) { + sb.append(tabString(tabLevel)).append(String.format("{ throws_type_index: %d; }",throwsTypeIndex)); + } + } + + /** + * localvar_target (4.7.20.1. The target_info union) + * + * The localvar_target item indicates that an annotation appears on the type in a local variable declaration, + * including a variable declared as a resource in a try-with-resources statement. + * + * localvar_target { + * u2 table_length; + * { u2 start_pc; + * u2 length; + * u2 index; + * } table[table_length]; + * } + */ + public static class localvar_target extends TypeAnnotationTargetInfoData { + + public class LocalVar_Entry { + + public int startPC; + public int length; + public int cpx; + + public LocalVar_Entry(int st, int len, int index) { + startPC = st; + length = len; + cpx = index; + } + + void write(CheckedDataOutputStream out) throws IOException { + out.writeShort(startPC); + out.writeShort(length); + out.writeShort(cpx); + } + + public void _print(PrintWriter out, String tab) { + out.print(tab + "{"); + out.print(startPC); + out.print(" "); + out.print(length); + out.print(" "); + out.print(cpx); + out.print("}"); + } + + public String toString() { + return String.format("start_pc: %d, length: %d, index: %d", startPC, length, cpx); + } + } + + ArrayList table = null; + + public localvar_target(TypeAnnotationTypes.ETargetType tt, int size) { + super(tt); + table = new ArrayList<>(size); + } + + public void addEntry(int startPC, int length, int cpx) { + LocalVar_Entry entry = new LocalVar_Entry(startPC, length, cpx); + table.add(entry); + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeShort(table.size()); + for (LocalVar_Entry entry : table) { + entry.write(out); + } + } + + @Override + public void _print(PrintWriter out, String tab) { + String innerTab = tab + " "; + for (LocalVar_Entry entry : table) { + entry._print(out, innerTab); + } + out.print(tab); + } + + @Override + public int getLength() { + return 2 + // U2 for table size + (6 * table.size()); // (3 * U2) for each table entry + } + + @Override + protected void _toString(StringBuilder sb, int tabLevel) { + int i = 0; + sb.append(tabString(tabLevel)).append(String.format("{ %d {", table.size())); + for (LocalVar_Entry entry : table) { + sb.append(String.format(" [%d]: %s;", i++, entry.toString())); + } + sb.append(" } }"); + } + } + + /** + * catch_target (4.7.20.1. The target_info union) + * + * The catch_target item indicates that an annotation appears on the i'th type in an exception parameter declaration. + * + * catch_target { + * u2 exception_table_index; + * } + */ + public static class catch_target extends TypeAnnotationTargetInfoData { + + int exceptionTableIndex; + + public catch_target(TypeAnnotationTypes.ETargetType tt, int index) { + super(tt); + exceptionTableIndex = index; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeShort(exceptionTableIndex); + } + + @Override + public void _print(PrintWriter out, String tab) { + out.print(" "); + out.print(exceptionTableIndex); + } + + @Override + public int getLength() { + return 2; + } + + @Override + protected void _toString(StringBuilder sb, int tabLevel) { + sb.append(tabString(tabLevel)).append(String.format("{ exception_table_index: %d; }",exceptionTableIndex)); + } + } + + /** + * offset_target (4.7.20.1. The target_info union) + * + * The offset_target item indicates that an annotation appears on either the type in an instanceof expression or + * a new expression, or the type before the :: in a method reference expression. + * + * offset_target { + * u2 offset; + * } + */ + public static class offset_target extends TypeAnnotationTargetInfoData { + + int offset; + + public offset_target(TypeAnnotationTypes.ETargetType tt, int offset) { + super(tt); + this.offset = offset; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeShort(offset); + } + + @Override + public void _print(PrintWriter out, String tab) { + out.print(" "); + out.print(offset); + } + + @Override + public int getLength() { + return 2; + } + + @Override + protected void _toString(StringBuilder sb, int tabLevel) { + sb.append(tabString(tabLevel)).append(String.format("{ offset: %d; }", offset)); + } + } + + /** + * type_argument_target (4.7.20.1. The target_info union) + * + * The type_argument_target item indicates that an annotation appears either on the i'th type in a cast expression, + * or on the i'th type argument in the explicit type argument list for any of the following: a new expression, + * an explicit constructor invocation statement, a method invocation expression, or a method reference expression + * + * type_argument_target { + * u2 offset; + * u1 type_argument_index; + * } + */ + public static class type_argument_target extends TypeAnnotationTargetInfoData { + + int offset; + int typeArgumentIndex; + + public type_argument_target(TypeAnnotationTypes.ETargetType tt, int offset, int index) { + super(tt); + this.offset = offset; + typeArgumentIndex = index; + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeShort(offset); + out.writeByte(typeArgumentIndex); + } + + @Override + public void _print(PrintWriter out, String tab) { + out.print(" "); + out.print(offset); + out.print(" "); + out.print(typeArgumentIndex); + } + + @Override + public int getLength() { + return 3; + } + + @Override + protected void _toString(StringBuilder sb, int tabLevel) { + sb.append(tabString(tabLevel)).append(String.format("{ offset: %d; type_argument_index: %d; }", + offset, typeArgumentIndex)); + } + } + +} + diff --git a/test/lib/org/openjdk/asmtools/jasm/TypeAnnotationTypePathData.java b/test/lib/org/openjdk/asmtools/jasm/TypeAnnotationTypePathData.java new file mode 100644 index 00000000000..a4a3813661f --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/TypeAnnotationTypePathData.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import org.openjdk.asmtools.jasm.TypeAnnotationTypes.TypePathEntry; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * JVMS 4.7.20.2. The type_path structure + * + * type_path { + * u1 path_length; + * { u1 type_path_kind; + * u1 type_argument_index; + * } path[path_length]; + * } + */ +public class TypeAnnotationTypePathData implements Data { + + private ArrayList typePathEntries = new ArrayList<>(); + + public void addTypePathEntry( TypePathEntry entry) { + typePathEntries.add(entry); + } + + @Override + public void write(CheckedDataOutputStream out) throws IOException { + out.writeByte(typePathEntries.size()); + for (TypePathEntry entry : typePathEntries) { + out.writeByte(entry.getTypePathKind()); + out.writeByte(entry.getTypeArgumentIndex()); + } + } + + @Override + public int getLength() { + return 1 + typePathEntries.size() * 2; + } + + public String toString(int tabLevel) { + String buffer = ""; + if( typePathEntries.size() > 0 ) { + StringBuilder sb = new StringBuilder(tabString(tabLevel)); + sb.append(" [ "); + boolean first = true; + for (TypePathEntry entry : typePathEntries) { + if (!first) + sb.append(", "); + first = false; + sb.append(entry.toString()); + } + sb.append("]"); + buffer = sb.toString(); + } + return buffer; + } + + /** + * jdis: print the type_path structure + */ + public void print(PrintWriter out, String tab) { + if( typePathEntries.size() > 0 ) { + out.print(tab + " {"); + boolean first = true; + for (TypePathEntry entry : typePathEntries) { + if (!first) { + out.print(", "); + } + first = false; + out.print(entry.toString()); + } + out.print(tab + "} "); + } + } +} diff --git a/test/lib/org/openjdk/asmtools/jasm/TypeAnnotationTypes.java b/test/lib/org/openjdk/asmtools/jasm/TypeAnnotationTypes.java new file mode 100644 index 00000000000..f91703edd69 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/TypeAnnotationTypes.java @@ -0,0 +1,312 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jasm; + +import java.io.PrintWriter; + +/** + * Type annotation types: target_type, target_info && target_path + */ +public class TypeAnnotationTypes { + + /** + * Interpretation of type_path_kind values (Table 4.7.20.2-A) + */ + public enum EPathKind { + ARRAY(0), + INNER_TYPE(1), + WILDCARD(2), + TYPE_ARGUMENT(3); + + private final int tag; + public static final int maxLen = 3; + + EPathKind(int tag) { + this.tag = tag; + } + + public int tag() { + return tag; + } + + public String parseKey() { + return this.toString(); + } + + static EPathKind getPathKind(String token) { + for (EPathKind pk : values()) { + if( pk.parseKey().equals(token)) { + return pk; + } + } + return null; + } + } + + // will throw ArrayIndexOutOfBounds if i < 0 or i > 3 + static public EPathKind getPathKind(int i) { + return EPathKind.values()[i]; + } + + static public class TypePathEntry { + + private final EPathKind kind; + private final int typeArgumentIndex; + + public TypePathEntry(int kind, int typeArgumentIndex) { + this.kind = getPathKind(kind); + this.typeArgumentIndex = typeArgumentIndex; + } + + public TypePathEntry(EPathKind kind, int typeArgumentIndex) { + this.kind = kind; + this.typeArgumentIndex = typeArgumentIndex; + } + + public int getTypePathKind() { + return kind.tag(); + } + + public int getTypeArgumentIndex() { + return typeArgumentIndex; + } + + @Override + public String toString() { + // Chapter 4.7.20.2 The type_path structure + // if the value of the type_path_kind is 0,1, or 2, thebn the value of the + // type_argument_index item is 0. + return kind.parseKey() + ( kind.tag == 3 ? + JasmTokens.Token.LBRACE.parseKey() + typeArgumentIndex + JasmTokens.Token.RBRACE.parseKey() : + ""); + } + } + + /** + * union { + * type_parameter_target; + * supertype_target; + * type_parameter_bound_target; + * empty_target; + * method_formal_parameter_target; + * throws_target; + * localvar_target; + * catch_target; + * offset_target; + * type_argument_target; + * } target_info; + */ + public enum ETargetInfo { + TYPEPARAM ("TYPEPARAM", "type_parameter"), + SUPERTYPE ("SUPERTYPE", "supertype"), + TYPEPARAM_BOUND ("TYPEPARAM_BOUND", "type_parameter_bound"), + EMPTY ("EMPTY", "empty"), + METHODPARAM ("METHODPARAM", "formal_parameter"), + EXCEPTION ("EXCEPTION", "throws"), + LOCALVAR ("LOCALVAR", "localvar"), + CATCH ("CATCH", "catch"), + OFFSET ("OFFSET", "offset"), + TYPEARG ("TYPEARG", "type_argument"); + + private final String parseKey; + private final String printValue; + + ETargetInfo(String parse, String printValue) { + parseKey = parse; + this.printValue = printValue; + } + public String parseKey() { + return this.parseKey; + } + + public String printValue() { + return this.printValue; + } + } + + /** + * Interpretation of target_type values (Table 4.7.20-A./B.) + */ + static public enum ETargetType { + class_type_param (0x00, "CLASS_TYPE_PARAMETER", ETargetInfo.TYPEPARAM, "class/interface type parameter"), + meth_type_param (0x01, "METHOD_TYPE_PARAMETER", ETargetInfo.TYPEPARAM, "method/constructor type parameter"), + class_exts_impls (0x10, "CLASS_EXTENDS", ETargetInfo.SUPERTYPE, "class extends/implements"), + class_type_param_bnds (0x11, "CLASS_TYPE_PARAMETER_BOUND", ETargetInfo.TYPEPARAM_BOUND, "class/interface type parameter bounds"), + meth_type_param_bnds (0x12, "METHOD_TYPE_PARAMETER_BOUND", ETargetInfo.TYPEPARAM_BOUND, "method/constructor type parameter bounds"), + field (0x13, "FIELD", ETargetInfo.EMPTY, "field"), + meth_ret_type (0x14, "METHOD_RETURN", ETargetInfo.EMPTY, "method return type"), + meth_receiver (0x15, "METHOD_RECEIVER", ETargetInfo.EMPTY, "method receiver"), + meth_formal_param (0x16, "METHOD_FORMAL_PARAMETER", ETargetInfo.METHODPARAM, "method formal parameter type"), + throws_type (0x17, "THROWS", ETargetInfo.EXCEPTION, "exception type in throws"), + + local_var (0x40, "LOCAL_VARIABLE", ETargetInfo.LOCALVAR, "local variable"), + resource_var (0x41, "RESOURCE_VARIABLE", ETargetInfo.LOCALVAR, "resource variable"), + exception_param (0x42, "EXCEPTION_PARAM", ETargetInfo.CATCH, "exception parameter"), + type_test (0x43, "INSTANCEOF", ETargetInfo.OFFSET, "type test (instanceof)"), + obj_creat (0x44, "NEW", ETargetInfo.OFFSET, "object creation (new)"), + constr_ref_receiver (0x45, "CONSTRUCTOR_REFERENCE_RECEIVER", ETargetInfo.OFFSET, "constructor reference receiver"), + meth_ref_receiver (0x46, "METHOD_REFERENCE_RECEIVER", ETargetInfo.OFFSET, "method reference receiver"), + cast (0x47, "CAST", ETargetInfo.TYPEARG, "cast"), + constr_invoc_typearg (0x48, "CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT", ETargetInfo.TYPEARG, "type argument in constructor call"), + meth_invoc_typearg (0x49, "METHOD_INVOCATION_TYPE_ARGUMENT", ETargetInfo.TYPEARG, "type argument in method call"), + constr_ref_typearg (0x4A, "CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT", ETargetInfo.TYPEARG, "type argument in constructor reference"), + meth_ref_typearg (0x4B, "METHOD_REFERENCE_TYPE_ARGUMENT", ETargetInfo.TYPEARG, "type argument in method reference"); + + public static final int maxTag = 0x9A; + public static final int maxLen = 36; + + public final int value; + private final String parseKey; + private final ETargetInfo targetInfo; + private final String printVal; + + ETargetType(int val, String parse, ETargetInfo targetInfo, String printVal) { + value = val; + parseKey = parse; + this.targetInfo = targetInfo; + this.printVal = printVal; + } + + public String parseKey() { + return parseKey; + } + + public String infoKey() { + return targetInfo.parseKey(); + } + + public ETargetInfo targetInfo() { + return targetInfo; + } + + public void print(PrintWriter out) { + out.print(parseKey); + } + + @Override + public String toString() { + return String.format("%s[%#x]", parseKey, value); + } + + public static ETargetType getTargetType(int typeCode) { + for( ETargetType type: ETargetType.values() ) { + if (type.value == typeCode) { + return type; + } + } + return null; + } + + static public ETargetType getTargetType(String typeName) { + for( ETargetType type: ETargetType.values() ) { + if (type.parseKey.equals(typeName)) { + return type; + } + } + return null; + } + }; + + /* TypeAnnotationVisitor Methods */ + public static class TypeAnnotationTargetVisitor { + + public final void visit(ETargetType tt) { + switch (tt) { + case class_type_param: + case meth_type_param: + visit_type_param_target(tt); + break; + case class_exts_impls: + visit_supertype_target(tt); + break; + case class_type_param_bnds: + case meth_type_param_bnds: + visit_typeparam_bound_target(tt); + break; + case field: + case meth_ret_type: + case meth_receiver: + visit_empty_target(tt); + break; + case meth_formal_param: + visit_methodformalparam_target(tt); + break; + case throws_type: + visit_throws_target(tt); + break; + case local_var: + case resource_var: + visit_localvar_target(tt); + break; + case exception_param: + visit_catch_target(tt); + break; + case type_test: + case obj_creat: + case constr_ref_receiver: + case meth_ref_receiver: + visit_offset_target(tt); + break; + + case cast: + case constr_invoc_typearg: + case meth_invoc_typearg: + case constr_ref_typearg: + case meth_ref_typearg: + + visit_typearg_target(tt); + break; + } + } + + public void visit_type_param_target(ETargetType tt) { + } + + public void visit_supertype_target(ETargetType tt) { + } + + public void visit_typeparam_bound_target(ETargetType tt) { + } + + public void visit_empty_target(ETargetType tt) { + } + + public void visit_methodformalparam_target(ETargetType tt) { + } + + public void visit_throws_target(ETargetType tt) { + } + + public void visit_localvar_target(ETargetType tt) { + } + + public void visit_catch_target(ETargetType tt) { + } + + public void visit_offset_target(ETargetType tt) { + } + + public void visit_typearg_target(ETargetType tt) { + } + } +} diff --git a/test/lib/org/openjdk/asmtools/jasm/i18n.properties b/test/lib/org/openjdk/asmtools/jasm/i18n.properties new file mode 100644 index 00000000000..682d038ab5c --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jasm/i18n.properties @@ -0,0 +1,195 @@ +# Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved. +# 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. + +jasm.usage=\ +Usage: java -jar asmtools.jar jasm [options] file.jasm...\n\ +where possible options include: + +jasm.opt.d=\ +\ -d destdir directory to place resulting .class files +jasm.opt.v=\ +\ -v add trace information +jasm.opt.g=\ +\ -g add debug information +jasm.opt.version=\ +\ -version prints the program version +jasm.opt.nowrite=\ +\ -nowrite do not write resulting .class files +jasm.opt.strict=\ +\ -strict consider warnings as errors +jasm.opt.nowarn=\ +\ -nowarn do not print warnings +jasm.opt.cv=\ +\ -cv major.minor set operating class file version (by default {0}.{1}) + + +jasm.error.d_requires_argument=-d requires argument +jasm.error.does_not_exist={0} does not exist +jasm.error.cv_requires_arg=-cv requires argument +jasm.error.invalid_major_minor_param=invalid parameter major.minor +jasm.error.invalid_option=invalid option: {0} +jasm.error.cannot_read=cannot read {0} +jasm.error.cannot_write=cannot write {0} +jasm.error.fatal_error=fatal error +jasm.error.fatal_exception=fatal exception + + +# Scanner: +err.invalid.escape.char=Invalid escape character. +err.eof.in.comment=Comment not terminated at end of input. +err.invalid.number=Invalid character "{0}" in number. +err.invalid.octal.number=Invalid character in octal number. +err.overflow=Numeric overflow. +err.float.format=Invalid floating point format. +err.eof.in.string=String not terminated at end of input. +err.newline.in.string=String not terminated at end of line. +err.funny.char=Invalid character in input. +err.unbalanced.paren=Unbalanced parentheses. +# Parser: +err.package.repeated=Package statement repeated. +warn.intf.repeated=Interface {0} repeated. +warn.exc.repeated=Exception repeated in throws clause. +warn.record.repeated=Record attribute repeated. +err.multiple.inherit=Multiple inheritance is not supported. +err.toplevel.expected=Class, module or interface declaration expected. +err.const.def.expected=Constant declaration expected. +err.const.undecl=Constant #{0} not declared. +err.const.redecl=Constant {0} redeclared. +warn.const0.redecl=Re-declaration of Constant #0 cannot be written to the class file. +err.field.expected=Class member declaration expected. +err.token.expected={0} expected. +err.identifier.expected=Identifier expected. +err.extra.nesthost.attribute=There may be at most one NestHost attribute. +err.extra.nestmembers.attribute=There may be at most one NestMembers attribute. +err.extra.permittedsubclasses.attribute=There may be at most one PermittedSubclasses attribute. +err.extra.record.attribute=There may be at most one Record attribute. +err.extra.preload.attribute=There may be at most one Preload attribute. +err.grouped.component.expected=Either an annotation or a record component expected. +warn.no.components.in.record.attribute=Record should have at least one component. +err.one.of.two.token.expected=Either #{0} or #{1} token expected. + +err.both.nesthost.nestmembers.found=The attributes table of a ClassFile structure must not contain both a NestMembers attribute and a NestHost attribute. +err.name.expected=Name expected, got {0}. +err.module.name.expected=Module name expected, got {0}. +err.int.expected=Integer expected. +err.neg.forbidden=Negative integer is not allowed here. +err.value.large=Value doesn't fit in {0}. +err.value.expected=Value expected. +err.wrong.mnemocode=Invalid mnemocode ({0}). +err.default.redecl=Default statement already declared in this table. +err.long.switchtable=Switchtable too long: > {0}. +err.io.exception=I/O error in {0}. +warn.wrong.tag=Wrong tag: {0} expected. +err.wrong.tag=Wrong tag: {0} expected. +warn.wrong.tag2=Wrong tag: Either {0} or {1} expected. +err.wrong.tag2=Wrong tag: Either {0} or {1} expected. +# Code Gen: +err.locvar.redecl=Local variable {0} redeclared. +err.locvar.undecl=Local variable {0} not declared. +#err.locvar.expected=Local variable expected. +err.label.redecl=Label {0} redeclared. +err.label.undecl=Label {0} not declared. +err.label.expected=Label expected. +err.subtag.expected=Subtag expected. +err.type.expected=Type expected. +err.trap.tryredecl= redeclared. +err.trap.endtryredecl= redeclared. +err.trap.notry=No found. +err.trap.noendtry=No found. +warn.trap.notref=No declared. +err.cannot.write=Cannot write to {0}. +err.msig.malformed=Malformed method signature at char {0}. [err={1}] +err.no.classname=Class name not defined. +warn.msig.more255=Number of parameters too large ({0}>255). +warn.illslot=Local variable at Illegal slot {0}. +warn.repeated.modifier=Repeated modifier. +warn.invalid.modifier.init=invalid modifier for method \"{0}\". +warn.invalid.modifier.fiva=at most one of final and volatile modifiers can be used for a field +warn.invalid.modifier.intfield=interface field must be public static final only +warn.init.in_int= method cannot be placed in an interface. +warn.invalid.modifier.intmth=interface method must be abstract public only \"{0}\". +warn.invalid.modifier.abst=invalid modifier for abstract method. +# +warn.invalid.modifier.field=invalid modifier(s) for a field \"{0}\" +warn.invalid.modifier.mth=invalid modifier(s) for a method \"{0}\" +warn.invalid.modifier.innerclass=invalid modifier for an inner class \"{0}\" +# +warn.invalid.modifier.class=invalid modifier(s) for a class \"{0}\" +warn.invalid.modifier.int=invalid modifier(s) for an interface \"{0}\" +# +warn.invalid.modifier.acc=at most one of public, protected, and private modifiers can be used. +warn.invalid.modifier.int.abs=interface class must have abstract modifier. +warn.missing.modifier.class=class or enum declaration missing. +warn.invalid.modifier.class.finabs=class cannot be both abstract and final. +warn.invalid.modifier.class.intenum=cannot be both interface and enum. +err.itemtype.expected=StackMap item type expected instead of {0}. +err.localsmap.repeated=locals_map redeclared. +err.invalid.stack.frame.type=invalid stack frame type. +err.invalid.offset.same.frame=offset value more than 64 for the 'same_frame' type frame. +err.no.stack.map.same.locals=stack map element for the 'same_locals_1_stack_item_frame' type frame is absent. +err.should.be.only.one.stack.map.element=should be only one stack map element for the 'same_locals_1_stack_item_frame' type frame. +err.invalid.offset.same.locals=offset value more than 64 for the 'same_locals_1_stack_item_frame' type frame. +err.unexpected.stack.maps=there are unexpected stack maps. +err.unexpected.locals.maps=there are unexpected locals maps. +err.no.locals.map.append=locals map element for the 'append_frame' type frame is absent. +err.more.locals.map.elements=there are more than 3 locals map element for the 'append_frame' type frame. +err.stackmap.repeated=stack_map redeclared. +err.version.expected=class file version expected +err.invalid.innerclass=Invalid declaration of Inner Class +err.invalid.bootstrapmethod=Invalid declaration of BootstrapMethod Entry +err.frametype.repeated=Frametype repeated +err.invalid.paramnum=Invalid Parameter Number: {0}. +err.duplicate.paramnum=Duplicate Parameter Number: {0}. +err.paramname.constnum.invaltype=ParameterName CPX at {0} is not a ConstantString. +err.paramname.token.unexpected=Incorrect ParamName, unrecognized token: \"{0}\". +# +# annotations Errors +# +err.incorrect.annot.class=Incorrect Annotation (class), expected class name or CPX), got \"{0}\". +err.incorrect.annot.enum=Incorrect Annotation (enum), expected type field IDENT, \"{0}\". +err.incorrect.annot.enum.cpx==Incorrect Annotation (enum), expected type field CPX. +err.incorrect.annot.token=Incorrect Annotation, unrecognized token: \"{0}\". +err.incorrect.annot.bool=Incorrect Annotation (boolean), expected Integer), got \"{0}\". +err.incorrect.annot.byte=Incorrect Annotation (byte), expected Integer), got \"{0}\". +err.incorrect.annot.char=Incorrect Annotation (char), expected Integer), got \"{0}\". +err.incorrect.annot.short=Incorrect Annotation (short), expected Integer), got \"{0}\". +err.incorrect.annot.keyword=Incorrect Annotation keyword \"{0}\". +err.incorrect.typeannot.target=Incorrect TypeAnnotation target \"{0}\". +err.incorrect.typeannot.targtype.string=Incorrect TypeAnnotation \"{0}\" argument: (expected String), \"{1}\". +err.incorrect.typeannot.targtype.int=Incorrect TypeAnnotation \"{0}\" argument: (expected Integer), \"{1}\". +err.incorrect.typeannot.pathentry=Incorrect TypeAnnotation TargetPath PathEntry \"{0}\". +err.incorrect.typeannot.pathentry.argindex=Incorrect TypeAnnotation TargetPath PathEntry ArgIndex (expected Integer), \"{0}\". +# +# module Errors +err.module.statement.expected= Module statement expected. +err.requires.expected=Module statement \"requires [transitive] [static] ModuleName;\" expected. +warn.dot.will.be.converted=Forward slash \"/\" expected instead of dot \".\". The dot is replaced by \"/\". +# +# Compiler Errors +# +comperr.constcell.nullvalset="Cell without value in setCell" +comperr.constcell.nullvalhash="Cell without value in cpoolHashByValue" +comperr.constcell.invarg="Cell[{0}] has #{1}" +comperr.constcell.nullval="ConstCell.value=null??" +comperr.val.noteq="Values not eq" +comperr.instr.nullarg="null arg for {0}" +comperr.instr.arglong="Too long argument of {0}: {1}" +comperr.instr.opclen="Wrong opcLength({0})" diff --git a/test/lib/org/openjdk/asmtools/jcdec/Main.java b/test/lib/org/openjdk/asmtools/jcdec/Main.java new file mode 100644 index 00000000000..013cf25cbdc --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jcdec/Main.java @@ -0,0 +1,957 @@ +/* + * Copyright (c) 2009, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jcdec; + +import static org.openjdk.asmtools.jcoder.JcodTokens.*; +import org.openjdk.asmtools.jdis.uEscWriter; +import org.openjdk.asmtools.util.I18NResourceBundle; +import org.openjdk.asmtools.util.ProductInfo; +import java.io.DataInputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Main program of the JavaCard DeCoder + * + */ +public class Main { + + /*-------------------------------------------------------- */ + /* Main Fields */ + /** + * Name of the program. + */ + String program; + + public static final I18NResourceBundle i18n + = I18NResourceBundle.getBundleForClass(Main.class); + /** + * The stream where error message are printed. + */ + PrintWriter out; + boolean DebugFlag = false; + boolean printDetails = false; + int shift = 0; + private static final char hexTable[] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + /*-------------------------------------------------------- */ + + static String toHex(long val, int width) { + StringBuffer s = new StringBuffer(); + for (int i = width * 2 - 1; i >= 0; i--) { + s.append(hexTable[((int) (val >> (4 * i))) & 0xF]); + } + return "0x" + s.toString(); + } + + static String toHex(long val) { + int width; + for (width = 8; width > 0; width--) { + if ((val >> (width - 1) * 8) != 0) { + break; + } + } + return toHex(val, width); + } + + void printByteHex(PrintWriter out, int b) { + out.print(hexTable[(b >> 4) & 0xF]); + out.print(hexTable[b & 0xF]); + } + + /*========================================================*/ + void out_begin(String s) { + for (int i = 0; i < shift; i++) { + out.print(" "); + } + out.println(s); + shift++; + } + + void out_print(String s) { + for (int i = 0; i < shift; i++) { + out.print(" "); + } + out.print(s); + } + + void out_println(String s) { + for (int i = 0; i < shift; i++) { + out.print(" "); + } + out.println(s); + } + + void out_end(String s) { + shift--; + for (int i = 0; i < shift; i++) { + out.print(" "); + } + out.println(s); + } + + String startArray(int length) { + return "[" + (printDetails ? Integer.toString(length) : "") + "]"; + } + + void printBytes(DataInputStream in, int len) throws IOException { + try { + for (int i = 0; i < len; i++) { + if (i % 8 == 0) { + out_print("0x"); + } + printByteHex(out, in.readByte()); + if (i % 8 == 7) { + out.println(";"); + } + } + } finally { + if (len % 8 != 0) { + out.println(";"); + } + } + } + + /*========================================================*/ + static final int EXPORT_MAGIC = 0x00FACADE; + static final int HEADER_MAGIC = 0xDECAFFED; + static String[] compNames = { + "Header", + "Directory", + "Applet", + "Import", + "ConstantPool", + "Class", + "Method", + "StaticField", + "RefLocation", + "Export", + "Descriptor" + }; + + static String compName(int compNum) { + try { + return compNames[compNum - 1]; + } catch (ArrayIndexOutOfBoundsException e) { + return "tag " + compNum + "???"; + } + } + String[] cPoolStrings; + + void decodeAttr(DataInputStream in) throws IOException { + int name_cpx = in.readUnsignedShort(), len = in.readInt(); + String AttrName = null; + String endingComment = "Attr(#" + name_cpx + ")"; + try { + endingComment = AttrName = cPoolStrings[name_cpx]; + } catch (ArrayIndexOutOfBoundsException e) { + } + if (printDetails) { + out_begin("Attr(#" + name_cpx + ", " + len + ") { // " + AttrName); + } else { + out_begin("Attr(#" + name_cpx + ") { // " + AttrName); + } + if (AttrName == null) { + printBytes(in, len); + } else if (AttrName.equals("ConstantValue")) { + if (len != 2) { + out_println("// invalid length of ConstantValue attr: " + len + " (should be 2)"); + printBytes(in, len); + } else { + out_println("#" + in.readUnsignedShort() + ";"); + } + } else { + printBytes(in, len); + } + out_end("} // end " + endingComment); + } + + void decodeExp(String inpName) throws IOException { + DataInputStream in = new DataInputStream(new FileInputStream(inpName)); + out_println("file " + inpName); + out_begin("{ // export file"); + + int magic = in.readInt(); + out_print(toHex(magic, 4) + "; // "); + if (magic != EXPORT_MAGIC) { + out.print("wrong magic: 0x" + Integer.toString(EXPORT_MAGIC, 16) + " expected"); + } else { + out_print("magic"); + } + out.println(); + out_println(in.readUnsignedByte() + "b; // minor version"); + out_println(in.readUnsignedByte() + "b; // major version"); + + int cp_count = in.readUnsignedShort(); + cPoolStrings = new String[cp_count]; + out_begin(startArray(cp_count) + " { // Constant Pool"); + for (int i = 0; i < cp_count; i++) { + int tag = in.readUnsignedByte(); + ConstType tg = constType(tag); + switch (tg) { + case CONSTANT_UTF8: + out_print("Utf8 \""); + + StringBuffer sb = new StringBuffer(); + String s = in.readUTF(); + cPoolStrings[i] = s; + for (int k = 0; k < s.length(); k++) { + char c = s.charAt(k); + switch (c) { + case '\t': + sb.append('\\').append('t'); + break; + case '\n': + sb.append('\\').append('n'); + break; + case '\r': + sb.append('\\').append('r'); + break; + case '\"': + sb.append('\\').append('\"'); + break; + default: + sb.append(c); + } + } + out.println(sb.append("\"; // #").append(i).toString()); + break; + + case CONSTANT_INTEGER: + out_println("int " + toHex(in.readInt(), 4) + "; // #" + i); + break; + + case CONSTANT_CLASS: + out_println("class #" + in.readUnsignedShort() + "; // #" + i); + break; + + case CONSTANT_JAVACARD_PACKAGE: + out_begin("package { // #" + i); + out_println(toHex(in.readUnsignedByte(), 1) + "; // flags"); + out_println("#" + in.readUnsignedShort() + "; // name"); + out_println(in.readUnsignedByte() + "b; // minor version"); + out_println(in.readUnsignedByte() + "b; // major version"); + int aid_len = in.readUnsignedByte(); + out_begin("Bytes" + startArray(aid_len) + "b {"); + printBytes(in, aid_len); + out_end("};"); // Bytes[] + out_end("};"); // package info + break; + + default: + throw new Error("invalid constant type: " + (int) tag); + } + } + ; + out_end("} // Constant pool"); + out_println("#" + in.readUnsignedShort() + "; // this package"); + int class_count = in.readUnsignedByte(); + out_begin(startArray(class_count) + "b { // classes"); + for (int i = 0; i < class_count; i++) { + out_begin("{ // class " + i); + + out_println(in.readUnsignedByte() + "b; // token"); + + int flags = in.readUnsignedShort(); + out_print("0x"); + printByteHex(out, flags >> 8); + printByteHex(out, flags); + out.println("; // flags"); + + out_println("#" + in.readUnsignedShort() + "; // this class"); + + int sup_count = in.readUnsignedShort(); + out_begin(startArray(sup_count) + " { // supers"); + for (int k = 0; k < sup_count; k++) { + out_println("#" + in.readUnsignedShort() + ";"); + } + out_end("} // supers"); + + int int_count = in.readUnsignedByte(); + out_begin(startArray(int_count) + "b { // interfaces"); + for (int k = 0; k < int_count; k++) { + out_println("#" + in.readUnsignedShort() + ";"); + } + out_end("} // interfaces"); + + int field_count = in.readUnsignedShort(); + out_begin(startArray(field_count) + " { // fields"); + for (int k = 0; k < field_count; k++) { + out_begin("{ // field " + k); + out_println(in.readUnsignedByte() + "b; // token"); + + int f_flags = in.readUnsignedShort(); + out_print("0x"); + printByteHex(out, f_flags >> 8); + printByteHex(out, f_flags); + out.println("; // flags"); + + out_println("#" + in.readUnsignedShort() + "; // this field name"); + out_println("#" + in.readUnsignedShort() + "; // this field descriptor"); + + int attr_count = in.readUnsignedShort(); + out_begin(startArray(attr_count) + " { // Attributes"); + for (int ai = 0; ai < attr_count; ai++) { + decodeAttr(in); + } + out_end("} // Attributes"); + out_end("};"); + } + out_end("} // fields"); + + int mth_count = in.readUnsignedShort(); + out_begin(startArray(mth_count) + " { // methods"); + for (int k = 0; k < mth_count; k++) { + out_begin("{ // method " + k); + out_println(in.readUnsignedByte() + "b; // token"); + + int mth_flags = in.readUnsignedShort(); + out_print("0x"); + printByteHex(out, mth_flags >> 8); + printByteHex(out, mth_flags); + out.println("; // flags"); + + out_println("#" + in.readUnsignedShort() + "; // this method name"); + out_println("#" + in.readUnsignedShort() + "; // this method descriptor"); + out_end("};"); + } + out_end("} // methods"); + out_end("};"); + } + out_end("} // classes"); + endComponent(in); + } + + DataInputStream beginComponent(String inpName) throws IOException { + DataInputStream in = new DataInputStream(new FileInputStream(inpName)); + out_println("file " + inpName); + + int tag = in.readUnsignedByte(); + out_print("Component(" + tag); + int size = in.readUnsignedShort(); + if (printDetails) { + out.print(", " + size); + } + out_begin(") { // " + compName(tag)); + return in; + } + + void endComponent(DataInputStream in) throws IOException { + out_end("};"); // Component + int avail = in.available(); + if (avail > 0) { + out.println("=========== extra bytes:"); + for (int k = 0; k < 8; k++) { + printBytes(in, avail >= 8 ? 8 : avail); + avail = in.available(); + if (avail == 0) { + break; + } + } + if (avail > 0) { + out.println(" there is also " + avail + " bytes available"); + } + } + in.close(); + } + + ArrayList methodsLengths = null; + ArrayList methodsOffsets = null; + + void decodeHeader(String inpName) throws IOException { + DataInputStream in = beginComponent(inpName); + + int magic = in.readInt(); + out_print(toHex(magic, 4) + "; // "); + if (magic != HEADER_MAGIC) { + out.print("wrong magic: 0x" + Integer.toString(HEADER_MAGIC, 16) + " expected"); + } else { + out_print("magic"); + } + out.println(); + out_println(in.readUnsignedByte() + "b; // minor version"); + out_println(in.readUnsignedByte() + "b; // major version"); + out_println(toHex(in.readUnsignedByte(), 1) + "; // flags"); + + out_begin("{ // package info"); + out_println(in.readUnsignedByte() + "b; // minor version"); + out_println(in.readUnsignedByte() + "b; // major version"); + int aid_len = in.readUnsignedByte(); + out_begin("Bytes" + startArray(aid_len) + "b {"); + printBytes(in, aid_len); + out_end("};"); // Bytes[] + out_end("};"); // package info + endComponent(in); + } + + void decodeDirectory(String inpName) throws IOException { + DataInputStream in = beginComponent(inpName); + + int i; + out_begin("{ // component sizes"); + for (i = 0; i < 11; i++) { + out_println(in.readUnsignedShort() + "; // " + (i + 1)); + } + out_end("};"); + + out_begin("{ // static field size"); + out_println(in.readUnsignedShort() + "; // image size"); + out_println(in.readUnsignedShort() + "; // array init count"); + out_println(in.readUnsignedShort() + "; // array init size"); + out_end("};"); + + out_println(in.readUnsignedByte() + "b; // import count"); + out_println(in.readUnsignedByte() + "b; // applet count"); + + int custom_count = in.readUnsignedByte(); + out_begin(startArray(custom_count) + "b { // custom components"); + for (i = 0; i < custom_count; i++) { + out_print("Comp(" + in.readUnsignedByte()); // tag; + int size2 = in.readUnsignedShort(); + if (printDetails) { + out_print(", " + size2); + } + out_begin(") {"); + int aid_len = in.readUnsignedByte(); + out_begin("Bytes" + startArray(aid_len) + "b {"); + printBytes(in, aid_len); + out_end("};"); + out_end("};"); + } + out_end("};"); + + endComponent(in); + } + + void decodeApplet(String inpName) throws IOException { + DataInputStream in = beginComponent(inpName); + + int applet_count = in.readUnsignedByte(); + out_begin(startArray(applet_count) + "b { // applets"); + for (int i = 0; i < applet_count; i++) { + out_begin("{ // applet " + i); + int aid_len = in.readUnsignedByte(); + out_begin("Bytes" + startArray(aid_len) + "b {"); + printBytes(in, aid_len); + out_end("};"); // Bytes[] + out_println(in.readUnsignedShort() + "; // install method offset"); + out_end("};"); // applet + } + out_end("};"); // applets + endComponent(in); + } + + void decodeImport(String inpName) throws IOException { + DataInputStream in = beginComponent(inpName); + + int package_count = in.readUnsignedByte(); + out_begin(startArray(package_count) + "b { // packages"); + for (int i = 0; i < package_count; i++) { + out_begin("{ // package " + i); + out_println(in.readUnsignedByte() + "b; // minor version"); + out_println(in.readUnsignedByte() + "b; // major version"); + int aid_len = in.readUnsignedByte(); + out_begin("Bytes" + startArray(aid_len) + "b {"); + printBytes(in, aid_len); + out_end("};"); // Bytes[] + out_end("};"); // package info + } + out_end("};"); // package info + endComponent(in); + } + + static String[] refNames = { + "Classref", + "InstanceFieldref", + "VirtualMethodref", + "SuperMethodref", + "StaticFieldref", + "StaticMethodref" + }; + + void decodeConstantPool(String inpName) throws IOException { + DataInputStream in = beginComponent(inpName); + + int items_count = in.readUnsignedShort(); + out_begin(startArray(items_count) + " { // items"); + for (int i = 0; i < items_count; i++) { + int tag = in.readUnsignedByte(); + int info1 = in.readUnsignedByte(), + info2 = in.readUnsignedByte(), + info3 = in.readUnsignedByte(); + out_print(tag + "b "); + if ((tag > 0) && (tag <= 6)) { + if ((info1 & 0x80) == 0) { + if (tag <= 4) { + out_print(((info1 << 8) | info2) + " " + info3 + "b;"); + } else { + out_print(info1 + "b " + ((info2 << 8) | info3) + ";"); + } + out.print(" // internal "); + } else { + out.print(info1 + "b " + info2 + "b " + info3 + "b;"); + out.print(" // external "); + } + out.println(refNames[tag - 1]); + } else { + out.print(info1 + "b " + info2 + "b " + info3 + "b;"); + out.println(" // unknown tag "); + } + } + out_end("};"); // CP array + endComponent(in); + } + + void printClassref(DataInputStream in) throws IOException { + int info1 = in.readUnsignedByte(), + info2 = in.readUnsignedByte(); + if ((info1 & 0x80) == 0) { + out_print(((info1 << 8) | info2) + ";"); + out_print(" // internal "); + } else { + out_print(info1 + "b " + info2 + "b;"); + out_print(" // external "); + } + out_println(" Classref "); + } + + void decodeClass(String inpName) throws IOException { + DataInputStream in = beginComponent(inpName); + + for (int i = 0; in.available() > 0; i++) { + out_begin("{ // class " + i); + int bitfield = in.readUnsignedByte(); + int interface_count = bitfield & 0x0F; + out_print("0x"); + printByteHex(out, bitfield); + out.println("; // bitfield"); + if ((bitfield & 0x80) != 0) { + // interface + for (int k = 0; k < interface_count; k++) { + printClassref(in); + } + } else { + // class + printClassref(in); + out_println(in.readUnsignedByte() + "b; // declared instance size"); + out_println(in.readUnsignedByte() + "b; // first reference token"); + out_println(in.readUnsignedByte() + "b; // reference count"); + out_println(in.readUnsignedByte() + "b; // public method table base"); + int pumrc = in.readUnsignedByte(); + out_println(pumrc + "b; // public method table count"); + out_println(in.readUnsignedByte() + "b; // package method table base"); + int pamrc = in.readUnsignedByte(); + out_println(pamrc + "b; // package method table count"); + out_begin("{ // public method table"); + for (int k = 0; k < pumrc; k++) { + out_println(in.readUnsignedShort() + ";"); + } + out_end("};"); + out_begin("{ // package method table"); + for (int k = 0; k < pamrc; k++) { + out_println(in.readUnsignedShort() + ";"); + } + out_end("};"); + out_begin("{ // implemented interfaces"); + for (int k = 0; k < interface_count; k++) { + out_begin("{ // interface " + k); + printClassref(in); + int count = in.readUnsignedByte(); + out_begin("Bytes" + startArray(count) + "b {"); + printBytes(in, count); + out_end("};"); // Bytes[] + out_end("};"); + } + out_end("};"); + } + out_end("};"); + } + endComponent(in); + } + + void decodeDescriptor(String inpName) throws IOException { + DataInputStream in = beginComponent(inpName); + + methodsLengths = new ArrayList<>(); + methodsOffsets = new ArrayList<>(); + int class_count = in.readUnsignedByte(); + out_begin(startArray(class_count) + "b { // classes"); + for (int c = 0; c < class_count; c++) { + out_begin("{ // class " + c); + out_println(in.readUnsignedByte() + "b; // token"); + out_print("0x"); + printByteHex(out, in.readUnsignedByte()); + out.println("; // flags"); + printClassref(in); + int icount = in.readUnsignedByte(); + out_println(icount + "b; // interface count"); + int fcount = in.readUnsignedShort(); + out_println(fcount + "; // field count"); + int mcount = in.readUnsignedShort(); + out_println(mcount + "; // method count"); + if (icount != 0) { + out_begin("{ // interfaces"); + for (int i = 0; i < icount; i++) { + printClassref(in); + } + out_end("};"); + } + for (int i = 0; i < fcount; i++) { + out_begin("{ // field " + i); + out_println(in.readUnsignedByte() + "b; // token"); + int flags = in.readUnsignedByte(); + out_print("0x"); + printByteHex(out, flags); + out.println("; // flags"); + if ((flags & 0x08) == 0) { + printClassref(in); + out_println(in.readUnsignedByte() + "b; // token"); + } else { // static field + int info1 = in.readUnsignedByte(), + info2 = in.readUnsignedByte(), + info3 = in.readUnsignedByte(); + if ((info1 & 0x80) == 0) { + out_print(info1 + "b " + ((info2 << 8) | info3) + ";"); + out.println(" // internal field"); + } else { + out.print(info1 + "b " + info2 + "b " + info3 + "b;"); + out.println(" // external field"); + } + } + int type = in.readUnsignedShort(); + if ((type & 0x8000) == 0) { + out_println(type + "; // reference type"); + } else { + out_print("0x"); + printByteHex(out, type >> 8); + printByteHex(out, type); + out.println("; // primitive type"); + } + out_end("};"); + } + for (int i = 0; i < mcount; i++) { + int token = in.readUnsignedByte(); + int flags = in.readUnsignedByte(); + int m_offset = in.readUnsignedShort(); + int t_offset = in.readUnsignedShort(); + int bytecode_count = in.readUnsignedShort(); + if (m_offset != 0) { + out_begin("{ // method " + i + " (" + methodsLengths.size() + ")"); + methodsLengths.add(bytecode_count); + methodsOffsets.add(m_offset); + } else { + out_begin("{ // method " + i); + } + out_println(token + "b; // token"); + out_print("0x"); + printByteHex(out, flags); + out.println("; // flags"); + out_println(m_offset + "; // method offset"); + out_println(t_offset + "; // type offset"); + out_println(bytecode_count + "; // bytecode count"); + out_println(in.readUnsignedShort() + "; // exception handler count"); + out_println(in.readUnsignedShort() + "; // exception handler index"); + out_end("};"); + } + out_end("};"); // class i + } + out_end("}; // classes"); + + int cp_count = in.readUnsignedShort(); + out_begin(startArray(cp_count) + " { // constant pool types"); + for (int i = 0; i < cp_count; i++) { + int type = in.readUnsignedShort(); + if (type == 0xFFFF) { + out_println("0xFFFF;"); + } else { + out_println(type + "; "); + } + } + out_end("}; // constant pool types"); + + out_begin("{ // type descriptors"); + for (int i = 0; in.available() > 0; i++) { + int nibble_count = in.readUnsignedByte(); + out_print(nibble_count + "b; "); + printBytes(in, (nibble_count + 1) / 2); + } + out_end("}; // type descriptors"); + endComponent(in); + } + + void decodeMethod(String inpName) throws IOException { + DataInputStream in = beginComponent(inpName); + + int handler_count = in.readUnsignedByte(); + out_begin(startArray(handler_count) + "b { // exception handlers"); + for (int i = 0; i < handler_count; i++) { + out_print(in.readUnsignedShort() + ", "); + int bitfield = in.readUnsignedShort(); + out.print("0x"); + printByteHex(out, bitfield >> 8); + printByteHex(out, bitfield); + out.print(", " + in.readUnsignedShort() + ", "); + out.println(in.readUnsignedShort() + "; // handler " + i); + } + out_end("};"); // handlers + + if (methodsLengths == null) { + out.println("// Descriptor.cap absent - methods not printed"); + } else { + int f_offset = 1 + handler_count * 8; + for (int i = 0; i < methodsLengths.size(); i++) { + out_begin("{ // method " + i); + int m_offset = methodsOffsets.get(i); + if (m_offset != f_offset) { + out.println("file offset=" + f_offset + " but m_offset=" + m_offset); + break; + } + int bitfield = in.readUnsignedByte(); + if ((bitfield & 0x80) == 0) { + out_print("0x"); + printByteHex(out, bitfield); + out.println("; // flags, max_stack"); + out_print("0x"); + printByteHex(out, in.readUnsignedByte()); + out.println("; // nargs, max_locals"); + f_offset += 2; + } else { + out_print("0x"); + printByteHex(out, bitfield); + out.println("; // flags, padding"); + out_println(in.readUnsignedByte() + "b; // max_stack"); + out_println(in.readUnsignedByte() + "b; // nargs"); + out_println(in.readUnsignedByte() + "b; // max_locals"); + f_offset += 4; + } + int bytecode_count = methodsLengths.get(i); + out_begin("{ // bytecodes"); + printBytes(in, bytecode_count); + f_offset += bytecode_count; + out_end("};"); + out_end("};"); + } + } + + endComponent(in); + } + + void decodeStaticField(String inpName) throws IOException { + DataInputStream in = beginComponent(inpName); + + int image_size = in.readUnsignedShort(); + out_println(image_size + "; // image size"); + int reference_count = in.readUnsignedShort(); + out_println(reference_count + "; // reference count"); + int array_init_count = in.readUnsignedShort(); + out_begin(startArray(array_init_count) + " { // array_init_info"); + for (int i = 0; i < array_init_count; i++) { + out_println(in.readUnsignedByte() + "b // type "); + int count = in.readUnsignedShort(); + out_begin("Bytes" + startArray(count) + "s { // values"); + printBytes(in, count); + out_end("};"); // Bytes[] + } + out_end("};"); // array_init_info + int default_value_count = in.readUnsignedShort(); + out_println(default_value_count + "; // default value count"); + int non_default_value_count = in.readUnsignedShort(); + out_begin("Bytes" + startArray(non_default_value_count) + "s { // non default values"); + printBytes(in, non_default_value_count); + out_end("};"); // Bytes[] + + endComponent(in); + } + + void decodeRefLocation(String inpName) throws IOException { + DataInputStream in = beginComponent(inpName); + + int byte_index_count = in.readUnsignedShort(); + out_begin("Bytes" + startArray(byte_index_count) + "s { // offsets to byte indices"); + printBytes(in, byte_index_count); + out_end("};"); // Bytes[] + + byte_index_count = in.readUnsignedShort(); + out_begin("Bytes" + startArray(byte_index_count) + "s { // offsets to byte2 indices"); + printBytes(in, byte_index_count); + out_end("};"); // Bytes[] + + endComponent(in); + } + + void decodeExport(String inpName) throws IOException { + DataInputStream in = beginComponent(inpName); + int class_count = in.readUnsignedByte(); + out_begin(startArray(class_count) + "b { // classes"); + for (int i = 0; i < class_count; i++) { + out_begin("{ // class " + i); + out_println(in.readUnsignedShort() + "; // class offset"); + int fcount = in.readUnsignedByte(); + out_println(fcount + "b; // static field count"); + int mcount = in.readUnsignedByte(); + out_println(mcount + "b; // static method count"); + out_begin("{ // static field offsets"); + for (int j = 0; j < fcount; j++) { + out_println(in.readUnsignedShort() + "; // field " + j + " offset"); + } + out_end("};"); + out_begin("{ // static method offsets"); + for (int j = 0; j < mcount; j++) { + out_println(in.readUnsignedShort() + "; // method " + j + " offset"); + } + out_end("};"); + out_end("};"); // class i + } + out_end("};"); // classes + endComponent(in); + } + /*========================================================*/ + + /** + * Constructor. + */ + public Main(PrintWriter out, String program) { + this.out = out; + this.program = program; + } + + public void error(String msg) { + out.println(program + ": " + msg); + } + + /** + * Usage + */ + public void usage() { + out.println(i18n.getString("jcdec.usage")); + out.println(i18n.getString("jcdec.opt.g")); + out.println(i18n.getString("jcdec.opt.version")); + } + + /** + * Run the decoder + */ + public synchronized boolean decode(String argv[]) { +// int flags = F_WARNINGS; + long tm = System.currentTimeMillis(); + ArrayList vargs = new ArrayList<>(); + ArrayList vj = new ArrayList<>(); + boolean nowrite = false; + int addOptions = 0; + + // Parse arguments + for (int i = 0; i < argv.length; i++) { + String arg = argv[i]; + if (arg.equals("-g")) { + printDetails = true; + vargs.add(arg); + } else if (arg.equals("-v")) { + DebugFlag = true; + vargs.add(arg); + out.println("arg[" + i + "]=" + argv[i] + "/verbose"); + } else if (arg.equals("-version")) { + out.println(ProductInfo.FULL_VERSION); + } else if (arg.startsWith("-")) { +//out.println("arg["+i+"]="+argv[i]+"/invalid flag"); + error(i18n.getString("jcdec.error.invalid_flag", arg)); + usage(); + return false; + } else { + vargs.add(arg); + vj.add(arg); + } + } + + if (vj.isEmpty()) { + usage(); + return false; + } + +// String[] names = new String[vj.size()]; +// vj.copyInto(names); + String[] names = null; + names = vj.toArray(names); +decode: + for (int k = 0; k < names.length; k++) { + String inpname = names[k]; + try { + if (inpname.endsWith(".cap")) { + String shortName = inpname.substring(0, inpname.length() - 4); + if (shortName.endsWith("Header")) { + decodeHeader(inpname); + } else if (shortName.endsWith("Directory")) { + decodeDirectory(inpname); + } else if (shortName.endsWith("Applet")) { + decodeApplet(inpname); + } else if (shortName.endsWith("Import")) { + decodeImport(inpname); + } else if (shortName.endsWith("ConstantPool")) { + decodeConstantPool(inpname); + } else if (shortName.endsWith("Class")) { + decodeClass(inpname); + } else if (shortName.endsWith("Descriptor")) { + decodeDescriptor(inpname); + } else if (shortName.endsWith("Method")) { + decodeMethod(inpname); + } else if (shortName.endsWith("StaticField")) { + decodeStaticField(inpname); + } else if (shortName.endsWith("RefLocation")) { + decodeRefLocation(inpname); + } else if (shortName.endsWith("Export")) { + decodeExport(inpname); + } else { + continue decode; + } + out.println(""); + } else if (inpname.endsWith(".exp")) { + decodeExp(inpname); + out.println(""); + } + continue decode; + } catch (FileNotFoundException ee) { + error(i18n.getString("jcdec.error.cannot_read", inpname)); + } catch (Error ee) { + ee.printStackTrace(); + error(i18n.getString("jcdec.error.fatal_error")); + } catch (Exception ee) { + ee.printStackTrace(); + error(i18n.getString("jcdec.error.fatal_exception")); + } + return false; + } + return true; + } + + /** + * Main program + */ + public static void main(String argv[]) { + Main decoder = new Main(new PrintWriter(new uEscWriter(System.out)), "jcdec"); + System.exit(decoder.decode(argv) ? 0 : 1); + } +} diff --git a/test/lib/org/openjdk/asmtools/jcdec/i18n.properties b/test/lib/org/openjdk/asmtools/jcdec/i18n.properties new file mode 100644 index 00000000000..1faedfd18c9 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jcdec/i18n.properties @@ -0,0 +1,34 @@ +# Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. +# 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. + +jcdec.error.invalid_flag=invalid flag: " {0} +jcdec.error.fatal_error=fatal error +jcdec.error.fatal_exception=fatal exception +jcdec.error.cannot_read=cannot read {0} +jcdec.usage=\ +Usage: java -jar asmtools.jar jcdec [options] FILE.class... > FILE.jcod\n\ +where possible options include: + +jcdec.opt.g=\ +\ -g: detailed output format +jcdec.opt.version=\ +\ -version: print version number and date + diff --git a/test/lib/org/openjdk/asmtools/jcoder/ByteBuffer.java b/test/lib/org/openjdk/asmtools/jcoder/ByteBuffer.java new file mode 100644 index 00000000000..6f8142f5bf6 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jcoder/ByteBuffer.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2009, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jcoder; + +/** + * Compiles just 1 source file + */ +class ByteBuffer extends java.io.OutputStream { + + String myname; + /** + * The buffer where elements are stored. + */ + byte data[]; + /** + * The number of elements in the buffer. + */ + int length; + /** + * The size of the increment. If it is 0 the size of the the buffer is doubled + * everytime it needs to grow. + */ + protected int capacityIncrement; + + /** + * Constructs an empty vector with the specified storage capacity and the specified + * capacityIncrement. + * + * @param initialCapacity the initial storage capacity of the vector + * @param capacityIncrement how much to increase the element's size by. + */ + public ByteBuffer(int initialCapacity, int capacityIncrement) { +// super(); + this.data = new byte[initialCapacity]; + this.capacityIncrement = capacityIncrement; + } + + /** + * Constructs an empty vector with the specified storage capacity. + * + * @param initialCapacity the initial storage capacity of the vector + */ + public ByteBuffer(int initialCapacity) { + this(initialCapacity, 0); + } + + /** + * Constructs an empty vector. + */ + public ByteBuffer() { + this(30); + } + + /** + * Constructs a full vector. + */ + public ByteBuffer(byte data[], int capacityIncrement) { + this.length = data.length; + this.data = data; + this.capacityIncrement = capacityIncrement; + } + + /** + * Constructs a full vector. + */ + public ByteBuffer(byte data[]) { + this(data, 0); + } + + /** + * Returns the number of elements in the vector. Note that this is not the same as the + * vector's capacity. + */ + public final int size() { + return length; + } + + /** + * Ensures that the vector has at least the specified capacity. + * + * @param minCapacity the desired minimum capacity + */ + public final synchronized void ensureCapacity(int minCapacity) { + int oldCapacity = data.length; + if (minCapacity <= oldCapacity) { + return; + } + byte oldData[] = data; + int newCapacity = (capacityIncrement > 0) ? (oldCapacity + capacityIncrement) : (oldCapacity * 2); + if (newCapacity < minCapacity) { + newCapacity = minCapacity; + } + data = new byte[newCapacity]; + System.arraycopy(oldData, 0, data, 0, length); + } + + /*======================================*/ + public void write(int val) { + ensureCapacity(length + 1); + data[length++] = (byte) val; + } + + public void writeAt(int index, long val, int width) { + for (int i = 0; i < width; i++) { + data[index + i] = (byte) (val >> (width - 1 - i) * 8); + } + } + + public void append(long val, int width) { + ensureCapacity(length + width); + writeAt(length, val, width); + length += width; + } + + /*======================================================*/ +} // end ByteBuffer + diff --git a/test/lib/org/openjdk/asmtools/jcoder/ErrorMessage.java b/test/lib/org/openjdk/asmtools/jcoder/ErrorMessage.java new file mode 100644 index 00000000000..019ef8ebfd2 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jcoder/ErrorMessage.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jcoder; + +/** + * A sorted list of error messages + */ +final class ErrorMessage { + + int where; + String message; + ErrorMessage next; + + /** + * Constructor + */ + ErrorMessage(int where, String message) { + this.where = where; + this.message = message; + } +} diff --git a/test/lib/org/openjdk/asmtools/jcoder/JcodTokens.java b/test/lib/org/openjdk/asmtools/jcoder/JcodTokens.java new file mode 100644 index 00000000000..829eb1aa13d --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jcoder/JcodTokens.java @@ -0,0 +1,387 @@ +/* + * Copyright (c) 1996, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jcoder; + +import static org.openjdk.asmtools.jasm.Tables.*; + +import java.io.PrintWriter; +import java.util.HashMap; + +/** + * + * JcodTokens + * + * This class contains tokens specific to parsing JCOD syntax. + * + * The classes in JcodTokens are following a Singleton Pattern. These classes are Enums, + * and they are contained in private hash maps (lookup tables and reverse lookup tables). + * These hash maps all have public accessors, which clients use to look-up enums. + * + * Tokens in this table carry no external state, and are typically treated as constants. + * They do not need to be reset. + */ +public class JcodTokens { + + /*-------------------------------------------------------- */ + /* Marker: describes the type of Keyword */ + static public enum KeywordType { + + TOKEN(0, "TOKEN"), + KEYWORD(3, "KEYWORD"); + + private final Integer value; + private final String printval; + + KeywordType(Integer val, String print) { + value = val; + printval = print; + } + + public String printval() { + return printval; + } + } + + /*-------------------------------------------------------- */ + /* Marker - describes the type of token */ + /* this is rather cosmetic, no function currently. */ + static public enum TokenType { + VALUE (0, "Value"), + KEYWORDS (1, "Keywords"), + PUNCTUATION (2, "Punctuation"), + JDEC (3, "JDec"), + STACKMAP (4, "StackMap"), + MISC (5, "Misc"); + + private final Integer value; + private final String printval; + + TokenType(Integer val, String print) { + value = val; + printval = print; + } + + public String printval() { + return printval; + } + } + + /*-------------------------------------------------------- */ + /** Scanner Tokens (Definitive List) */ + static public enum Token { + EOF (-1, "EOF", "EOF", TokenType.MISC), + IDENT (60, "IDENT", "IDENT", TokenType.VALUE), + LONGSTRINGVAL (61, "LONGSTRINGVAL", "LONGSTRING", TokenType.VALUE), + INTVAL (65, "INTVAL", "INT", TokenType.VALUE), + LONGVAL (66, "LONGVAL", "LONG", TokenType.VALUE), + STRINGVAL (69, "STRINGVAL", "STRING", TokenType.VALUE), + + CLASS (70, "CLASS", "class", TokenType.KEYWORDS, KeywordType.KEYWORD), + INTERFACE (71, "INTERFACE", "interface", TokenType.KEYWORDS, KeywordType.KEYWORD), + DIV (72, "DIV", "div", TokenType.KEYWORDS), + EQ (73, "EQ", "eq", TokenType.KEYWORDS), + ASSIGN (74, "ASSIGN", "assign", TokenType.KEYWORDS), + MODULE (75, "MODULE", "module", TokenType.KEYWORDS, KeywordType.KEYWORD), + + COLON (134, "COLON", ":", TokenType.PUNCTUATION), + SEMICOLON (135, "SEMICOLON", ";", TokenType.PUNCTUATION, KeywordType.KEYWORD), + COMMA (0, "COMMA", ",", TokenType.PUNCTUATION, KeywordType.KEYWORD), + LBRACE (138, "LBRACE", "{", TokenType.PUNCTUATION, KeywordType.KEYWORD), + RBRACE (139, "RBRACE", "}", TokenType.PUNCTUATION, KeywordType.KEYWORD), + LPAREN (140, "LPAREN", "(", TokenType.PUNCTUATION, KeywordType.KEYWORD), + RPAREN (141, "RPAREN", ")", TokenType.PUNCTUATION, KeywordType.KEYWORD), + LSQBRACKET (142, "LSQBRACKET", "[", TokenType.PUNCTUATION, KeywordType.KEYWORD), + RSQBRACKET (143, "RSQBRACKET", "]", TokenType.PUNCTUATION, KeywordType.KEYWORD), + + + BYTEINDEX (156, "BYTEINDEX", "b", TokenType.JDEC, KeywordType.KEYWORD), + SHORTINDEX (157, "SHORTINDEX", "s", TokenType.JDEC, KeywordType.KEYWORD), + ATTR (158, "ATTR", "Attr", TokenType.JDEC, KeywordType.KEYWORD), + BYTES (159, "BYTES", "Bytes", TokenType.JDEC, KeywordType.KEYWORD), + MACRO (160, "MACRO", "Attr", TokenType.JDEC), + COMP (161, "COMP", "Component", TokenType.JDEC, KeywordType.KEYWORD), + FILE (162, "FILE", "file", TokenType.JDEC, KeywordType.KEYWORD), + + ZEROINDEX (163, "ZEROINDEX", "z", TokenType.STACKMAP, KeywordType.KEYWORD); + + private Integer value; + private String printval; + private String parsekey; + private TokenType tk_type; + private KeywordType key_type; + + // By default, if a KeywordType is not specified, it has the value 'TOKEN' + Token(Integer val, String print, String op) { + init(val, print, op, TokenType.VALUE, KeywordType.TOKEN); + } + + Token(Integer val, String print, String op, TokenType tt) { + init(val, print, op, tt, KeywordType.TOKEN); + } + + Token(Integer val, String print, String op, TokenType tt, KeywordType kt) { + init(val, print, op, tt, kt); + } + + private void init(Integer val, String print, String op, TokenType tt, KeywordType kt) { + value = val; + printval = print; + parsekey = op; + tk_type = tt; + key_type = kt; + } + + public String printval() { + return printval; + } + + public String parsekey() { + return parsekey; + } + + public int value() { + return value; + } + + @Override + public String toString() { + return "<" + printval + "> [" + value + "]"; + } + + } + + /** + * Initialized keyword and token Hash Maps (and Reverse Tables) + */ + protected static final int MaxTokens = 172; + private static HashMap TagToTokens = new HashMap<>(MaxTokens); + private static HashMap SymbolToTokens = new HashMap<>(MaxTokens); + private static HashMap ParsekeyToTokens = new HashMap<>(MaxTokens); + + protected static final int MaxKeywords = 40; + private static HashMap TagToKeywords = new HashMap<>(MaxKeywords); + private static HashMap SymbolToKeywords = new HashMap<>(MaxKeywords); + private static HashMap ParsekeyToKeywords = new HashMap<>(MaxKeywords); + + static { + + // register all of the tokens + for (Token tk : Token.values()) { + registerToken(tk); + } + + SymbolToKeywords.put(Token.INTVAL.printval(), Token.INTVAL); + ParsekeyToKeywords.put(Token.INTVAL.parsekey(), Token.INTVAL); + SymbolToKeywords.put(Token.STRINGVAL.printval(), Token.STRINGVAL); + ParsekeyToKeywords.put(Token.STRINGVAL.parsekey(), Token.STRINGVAL); + } + + private static void registerToken(Token tk) { + // Tag is a keyword + if (tk.key_type == KeywordType.KEYWORD) { + TagToKeywords.put(tk.value, tk); + SymbolToKeywords.put(tk.printval, tk); + if (tk.parsekey != null) { + ParsekeyToKeywords.put(tk.parsekey, tk); + } + } + + // Finally, register all tokens + TagToTokens.put(tk.value, tk); + SymbolToTokens.put(tk.printval, tk); + ParsekeyToTokens.put(tk.printval, tk); + } + + /* Token accessors */ + public static Token token(int tk) { + return TagToTokens.get(tk); + } + + public static Token keyword_token(int tk) { + return TagToKeywords.get(tk); + } + + /* Reverse lookup accessors */ + public static Token token(String parsekey) { + return ParsekeyToTokens.get(parsekey); + } + + public static Token keyword_token(String parsekey) { + return ParsekeyToKeywords.get(parsekey); + } + + /* Reverse lookup by ID accessors */ + public static Token token_ID(String ID) { + return ParsekeyToTokens.get(ID); + } + + public static Token keyword_token_ID(String ID) { + return ParsekeyToKeywords.get(ID); + } + + public static String keywordName(int token) { + String retval = ""; + if (token > TagToTokens.size()) { + retval = null; + } else { + Token tk = keyword_token(token); + if (tk != null) { + retval = tk.parsekey; + } + } + return retval; + } + + public static Token keyword_token_ident(String idValue) { + Token kwd = keyword_token(idValue); + + if (kwd == null) { + kwd = Token.IDENT; + } + return kwd; + } + + public static int keyword_token_int(String idValue) { + return keyword_token_ident(idValue).value(); + } + + private static HashMap NameToConstantType = new HashMap<>(ConstType.maxTag); + private static HashMap ConstantTypes = new HashMap<>(ConstType.maxTag); + + static { + // register all of the tokens + for (ConstType ct : ConstType.values()) { + registerConstantType(ct); + } + } + + /** + * ConstType + * + * A (typed) tag (constant) representing the type of Constant in the Constant Pool. + * + * This is more-or-less a copy of jasm.ConstType. Unfortunately, there's no way to + * sub-class (or slightly alter) the members of an enum. This enum set is slightly + * modified from the Jasm one. + */ + static public enum ConstType { +// CONSTANT_ZERO (-3, "CONSTANT_ZERO", ""), + CONSTANT_UTF8 (1, "CONSTANT_UTF8", "Asciz", "Utf8"), + CONSTANT_UNICODE (2, "CONSTANT_UNICODE", ""), + CONSTANT_INTEGER (3, "CONSTANT_INTEGER", "int", "u4"), + CONSTANT_FLOAT (4, "CONSTANT_FLOAT", "float"), + CONSTANT_LONG (5, "CONSTANT_LONG", "long"), + CONSTANT_DOUBLE (6, "CONSTANT_DOUBLE", "double"), + // Class is removed for JavaCard (???) + CONSTANT_CLASS (7, "CONSTANT_CLASS", "class"), + CONSTANT_STRING (8, "CONSTANT_STRING", "String"), + CONSTANT_FIELD (9, "CONSTANT_FIELD", "Field"), + CONSTANT_METHOD (10, "CONSTANT_METHOD", "Method"), + CONSTANT_INTERFACEMETHOD (11, "CONSTANT_INTERFACEMETHOD", "InterfaceMethod"), + CONSTANT_NAMEANDTYPE (12, "CONSTANT_NAMEANDTYPE", "NameAndType"), + // added for JavaCard + CONSTANT_JAVACARD_PACKAGE (13, "CONSTANT_PACKAGE", "package"), // in javacard export file + // Constant 14 reserved + CONSTANT_METHODHANDLE (15, "CONSTANT_METHODHANDLE", "MethodHandle"), + CONSTANT_METHODTYPE (16, "CONSTANT_METHODTYPE", "MethodType"), + CONSTANT_DYNAMIC (17, "CONSTANT_DYNAMIC", "Dynamic"), + CONSTANT_INVOKEDYNAMIC (18, "CONSTANT_INVOKEDYNAMIC", "InvokeDynamic"), + CONSTANT_MODULE (19, "CONSTANT_MODULE", "Module"), + CONSTANT_MODULE_PACKAGE (20, "CONSTANT_PACKAGE", "Package"); + + public static final int maxTag = 20; + + private final int value; + private final String parseKey; + private final String printval; + private final String alias; + + ConstType(int val, String print, String parse) { + value = val; + parseKey = parse; + printval = print; + alias = null; + } + + ConstType(int val, String print, String parse, String als) { + value = val; + parseKey = parse; + printval = print; + alias = als; + } + + public int value() { + return value; + } + + public String parseKey() { + return parseKey; + } + + public String printval() { + return printval; + } + + public void print(PrintWriter out) { + out.print(parseKey); + } + + @Override + public String toString() { + return "<" + printval + "> [" + Integer.toString(value) + "]"; + } + }; + + static public ConstType constType(int i) { + return ConstantTypes.get(i); + } + + static public ConstType constType(String parsekey) { + return NameToConstantType.get(parsekey); + } + + private static void registerConstantType(ConstType tt) { + NameToConstantType.put(tt.parseKey, tt); + if (tt.alias != null) { + NameToConstantType.put(tt.alias, tt); + } + ConstantTypes.put(tt.value, tt); + } + + public static int constValue(String stringValue) { + ConstType Val = constType(stringValue); + int val = -1; + + if (Val != null) { + val = Val.value(); + } else { + StackMapType smt = stackMapTypeKey(stringValue); + + if (smt != null) { + val = smt.value(); + } + } + return val; + } + +} diff --git a/test/lib/org/openjdk/asmtools/jcoder/Jcoder.java b/test/lib/org/openjdk/asmtools/jcoder/Jcoder.java new file mode 100644 index 00000000000..a8dccd858ba --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jcoder/Jcoder.java @@ -0,0 +1,766 @@ +/* + * Copyright (c) 2009, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jcoder; + +import java.io.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Stack; + +import static org.openjdk.asmtools.jcoder.JcodTokens.ConstType; +import static org.openjdk.asmtools.jcoder.JcodTokens.Token; + +/** + * Compiles just 1 source file + */ +class Jcoder { + + /*-------------------------------------------------------- */ + /* Jcoder Fields */ + private ArrayList Classes = new ArrayList<>(); + private ByteBuffer buf; + private DataOutputStream bufstream; + private int depth = 0; + private String tabStr = ""; + private Context context = null; + protected SourceFile env; + protected Scanner scanner; + + /*-------------------------------------------------------- */ + /* Jcoder inner classes */ + + /*-------------------------------------------------------- */ + /* ContextTag (marker) - describes the type of token */ + /* this is rather cosmetic, no function currently. */ + private enum ContextTag { + NULL ( ""), + CLASS ( "Class"), + CONSTANTPOOL ( "Constant-Pool"), + INTERFACES ( "Interfaces"), + INTERFACE ( "Interface"), + METHODS ( "Methods"), + METHOD ( "Method"), + FIELDS ( "Fields"), + FIELD ( "Field"), + ATTRIBUTE ( "Attribute"); + + private final String printValue; + + ContextTag(String value) { + printValue = value; + } + + public String printval() { + return printValue; + } + } + + /*-------------------------------------------------------- */ + /* ContextVal (marker) - Specific value on a context stack */ + private class ContextVal { + + public ContextTag tag; + int compCount; + ContextVal owner; + + ContextVal(ContextTag tg) { + tag = tg; + compCount = 0; + owner = null; + } + + ContextVal(ContextTag tg, ContextVal ownr) { + tag = tg; + compCount = 0; + owner = ownr; + } + } + + + /*-------------------------------------------------------- */ + /* Context - Context stack */ + public class Context { + + Stack stack; + + private boolean hasCP; + private boolean hasMethods; + private boolean hasInterfaces; + private boolean hasFields; + + Context() { + stack = new Stack<>(); + init(); + } + + boolean isConstantPool() { + return !stack.empty() && (stack.peek().tag == ContextTag.CONSTANTPOOL); + } + + public void init() { + stack.removeAllElements(); + hasCP = false; + hasMethods = false; + hasInterfaces = false; + hasFields = false; + } + + void update() { + if (stack.empty()) { + stack.push(new ContextVal(ContextTag.CLASS)); + return; + } + + ContextVal currentCtx = stack.peek(); + switch (currentCtx.tag) { + case CLASS: + if (!hasCP) { + stack.push(new ContextVal(ContextTag.CONSTANTPOOL)); + hasCP = true; + } else if (!hasInterfaces) { + stack.push(new ContextVal(ContextTag.INTERFACES)); + hasInterfaces = true; + } else if (!hasFields) { + stack.push(new ContextVal(ContextTag.FIELDS)); + hasFields = true; + } else if (!hasMethods) { + stack.push(new ContextVal(ContextTag.METHODS)); + hasMethods = true; + } else { + // must be class attributes + currentCtx.compCount += 1; + stack.push(new ContextVal(ContextTag.ATTRIBUTE, currentCtx)); + } + break; + case INTERFACES: + currentCtx.compCount += 1; + stack.push(new ContextVal(ContextTag.INTERFACE, currentCtx)); + break; + case FIELDS: + currentCtx.compCount += 1; + stack.push(new ContextVal(ContextTag.FIELD, currentCtx)); + break; + case METHODS: + currentCtx.compCount += 1; + stack.push(new ContextVal(ContextTag.METHOD, currentCtx)); + break; + case FIELD: + case METHOD: + case ATTRIBUTE: + currentCtx.compCount += 1; + stack.push(new ContextVal(ContextTag.ATTRIBUTE, currentCtx)); + break; + default: + break; + } + } + + void exit() { + if (!stack.isEmpty()) { + stack.pop(); + } + } + + public String toString() { + if (stack.isEmpty()) { + return ""; + } + ContextVal currentCtx = stack.peek(); + String retval = currentCtx.tag.printval(); + switch (currentCtx.tag) { + case INTERFACE: + case METHOD: + case FIELD: + case ATTRIBUTE: + if (currentCtx.owner != null) { + retval += "[" + currentCtx.owner.compCount + "]"; + } + } + + return retval; + } + } + + + /*-------------------------------------------------------- */ + /* Jcoder */ + /** + * Create a parser + */ + Jcoder(SourceFile sf, HashMap macros) throws IOException { + scanner = new Scanner(sf, macros); + env = sf; + context = new Context(); + + } + /*-------------------------------------------------------- */ + + /** + * Expect a token, return its value, scan the next token or throw an exception. + */ + private void expect(Token t) throws SyntaxError, IOException { + if (scanner.token != t) { + env.traceln("expect:" + t + " instead of " + scanner.token); + switch (t) { + case IDENT: + env.error(scanner.pos, "identifier.expected"); + break; + default: + env.error(scanner.pos, "token.expected", t.toString()); + break; + } + throw new SyntaxError(); + } + scanner.scan(); + } + + private void recoverField() throws SyntaxError, IOException { + while (true) { + switch (scanner.token) { + case LBRACE: + scanner.match(Token.LBRACE, Token.RBRACE); + scanner.scan(); + break; + + case LPAREN: + scanner.match(Token.LPAREN, Token.RPAREN); + scanner.scan(); + break; + + case LSQBRACKET: + scanner.match(Token.LSQBRACKET, Token.RSQBRACKET); + scanner.scan(); + break; + + case RBRACE: + case EOF: + case INTERFACE: + case CLASS: + // begin of something outside a class, panic more + throw new SyntaxError(); + + default: + // don't know what to do, skip + scanner.scan(); + break; + } + } + } + + /** + * Parse an array of struct. + */ + private void parseArray() throws IOException { + scanner.scan(); + int length0 = buf.length, pos0 = scanner.pos; + int num_expected; + if (scanner.token == Token.INTVAL) { + num_expected = scanner.intValue; + scanner.scan(); + } else { + num_expected = -1; + } + expect(Token.RSQBRACKET); + int numSize; + switch (scanner.token) { + case BYTEINDEX: + scanner.scan(); + numSize = 1; + break; + case SHORTINDEX: + scanner.scan(); + numSize = 2; + break; + case ZEROINDEX: + scanner.scan(); + numSize = 0; + break; + default: + numSize = 2; + } + + // skip array size + if (numSize > 0) { + buf.append(num_expected, numSize); + } + + int num_present = parseStruct(); + if (num_expected == -1) { + env.trace(" buf.writeAt(" + length0 + ", " + num_present + ", " + numSize + "); "); + // skip array size + if (numSize > 0) { + buf.writeAt(length0, num_present, numSize); + } + } else if ( num_expected != num_present) { + if (context.isConstantPool() && num_expected == num_present +1) return; + env.error(pos0, "warn.array.wronglength", num_expected, num_present); + } + } + + /** + * Parse a byte array. + */ + private void parseByteArray() throws IOException { + scanner.scan(); + expect(Token.LSQBRACKET); + int length0 = buf.length, pos0 = scanner.pos; + int len_expected; + if (scanner.token == Token.INTVAL) { + len_expected = scanner.intValue; + scanner.scan(); + } else { + len_expected = -1; + } + expect(Token.RSQBRACKET); + int lenSize; + switch (scanner.token) { + case BYTEINDEX: + scanner.scan(); + lenSize = 1; + break; + case SHORTINDEX: + scanner.scan(); + lenSize = 2; + break; + case ZEROINDEX: + scanner.scan(); + lenSize = 0; + break; + default: + lenSize = 4; + } + + // skip array size + if (lenSize > 0) { + buf.append(len_expected, lenSize); + } + int length1 = buf.length; + parseStruct(); + int len_present = buf.length - length1; + if (len_expected == -1) { + env.trace(" buf.writeAt(" + length0 + ", " + len_present + ", " + lenSize + "); "); + // skip array size + if (lenSize > 0) { + buf.writeAt(length0, len_present, lenSize); + } + } else if (len_expected != len_present) { + env.error(pos0, "warn.array.wronglength", len_expected, len_present); + } + } + + /** + * Parse an Attribute. + */ + private void parseAttr() throws IOException { + scanner.scan(); + expect(Token.LPAREN); + int cpx; // index int const. pool + if (scanner.token == Token.INTVAL) { + cpx = scanner.intValue; + scanner.scan(); + + /* } else if (token==STRINGVAL) { + Integer Val=(Integer)(CP_Strings.get(stringValue)); + if (Val == null) { + env.error(pos, "attrname.notfound", stringValue); + throw new SyntaxError(); + } + cpx=Val.intValue(); + */ } else { + env.error(scanner.pos, "attrname.expected"); + throw new SyntaxError(); + } + buf.append(cpx, 2); + int pos0 = scanner.pos, length0 = buf.length; + int len_expected; + if (scanner.token == Token.COMMA) { + scanner.scan(); + len_expected = scanner.intValue; + expect(Token.INTVAL); + } else { + len_expected = -1; + } + buf.append(len_expected, 4); + expect(Token.RPAREN); + parseStruct(); + int len_present = buf.length - (length0 + 4); + if (len_expected == -1) { + buf.writeAt(length0, len_present, 4); + } else if (len_expected != len_present) { + env.error(pos0, "warn.attr.wronglength", len_expected, len_present); + } + } // end parseAttr + + /** + * Parse a Component of JavaCard .cap file. + */ + private void parseComp() throws IOException { + scanner.scan(); + expect(Token.LPAREN); + int tag = scanner.intValue; // index int const. pool + expect(Token.INTVAL); + buf.append(tag, 1); + int pos0 = scanner.pos, length0 = buf.length; + int len_expected; + if (scanner.token == Token.COMMA) { + scanner.scan(); + len_expected = scanner.intValue; + expect(Token.INTVAL); + } else { + len_expected = -1; + } + buf.append(len_expected, 2); + expect(Token.RPAREN); + parseStruct(); + int len_present = buf.length - (length0 + 2); + if (len_expected == -1) { + buf.writeAt(length0, len_present, 2); + } else if (len_expected != len_present) { + env.error(pos0, "warn.attr.wronglength", len_expected, len_present); + } + } // end parseComp + + private void adjustDepth(boolean up) { + if (up) { + depth += 1; + context.update(); + scanner.setDebugCP(context.isConstantPool()); + } else { + depth -= 1; + context.exit(); + } + StringBuilder bldr = new StringBuilder(); + int tabAmt = 4; + int len = depth * tabAmt; + for (int i = 0; i < len; i++) { + bldr.append(" "); + } + tabStr = bldr.toString(); + } + + /** + * Parse a structure. + */ + private int parseStruct() throws IOException { + adjustDepth(true); + env.traceln(" "); + env.traceln(tabStr + "MapStruct { <" + context + "> "); + expect(Token.LBRACE); + int num = 0; + int addElem = 0; + while (true) { + try { + switch (scanner.token) { + case COMMA: // ignored + scanner.scan(); + break; + case SEMICOLON: + num++; + addElem = 0; + scanner.scan(); + break; + case CLASS: + scanner.addConstDebug(ConstType.CONSTANT_CLASS); + env.trace("class "); + scanner.longValue = ConstType.CONSTANT_CLASS.value(); + scanner.intSize = 1; + case INTVAL: + env.trace("int [" + scanner.longValue + "] "); + buf.append(scanner.longValue, scanner.intSize); + scanner.scan(); + addElem = 1; + break; + case STRINGVAL: + scanner.scan(); + scanner.addConstDebug(ConstType.CONSTANT_UTF8); + env.trace("UTF8 [\"" + scanner.stringValue + "\"] "); + bufstream.writeUTF(scanner.stringValue); + addElem = 1; + break; + case LONGSTRINGVAL: + scanner.scan(); + env.traceln("LongString [\"" + Arrays.toString(scanner.longStringValue.data) + "\"] "); + buf.write(scanner.longStringValue.data, 0, scanner.longStringValue.length); + addElem = 1; + break; + case LBRACE: + parseStruct(); + addElem = 1; + break; + case LSQBRACKET: + parseArray(); + addElem = 1; + break; + case BYTES: + env.trace("bytes "); + parseByteArray(); + addElem = 1; + break; + case ATTR: + env.trace("attr "); + parseAttr(); + addElem = 1; + break; + case COMP: + env.trace("comp "); + parseComp(); + addElem = 1; + break; + case RBRACE: + scanner.scan(); + env.traceln(" "); + env.traceln(tabStr + "} // MapStruct <" + context + "> ["); + adjustDepth(false); + return num + addElem; + default: + env.traceln("unexp token=" + scanner.token); + env.traceln(" scanner.stringval = \"" + scanner.stringValue + "\""); + env.error(scanner.pos, "element.expected"); + throw new SyntaxError(); + } + } catch (SyntaxError e) { + recoverField(); + } + } + } // end parseStruct + + /** + * Recover after a syntax error in the file. This involves discarding tokens until an + * EOF or a possible legal continuation is encountered. + */ + private void recoverFile() throws IOException { + while (true) { + switch (scanner.token) { + case CLASS: + case INTERFACE: + // Start of a new source file statement, continue + return; + + case LBRACE: + scanner.match(Token.LBRACE, Token.RBRACE); + scanner.scan(); + break; + + case LPAREN: + scanner.match(Token.LPAREN, Token.RPAREN); + scanner.scan(); + break; + + case LSQBRACKET: + scanner.match(Token.LSQBRACKET, Token.RSQBRACKET); + scanner.scan(); + break; + + case EOF: + return; + + default: + // Don't know what to do, skip + scanner.scan(); + break; + } + } + } + + /** + * Parse module declaration + */ + private void parseModule() throws IOException { + // skip module name as a redundant element + scanner.skipTill(Scanner.LBRACE); + buf = new ByteBuffer(); + bufstream = new DataOutputStream(buf); + buf.myname = "module-info.class"; + scanner.scan(); + env.traceln("starting " + buf.myname); + // Parse the clause + parseClause(); + env.traceln("ending " + buf.myname); + } + + /** + * Parse a class or interface declaration. + */ + private void parseClass(Token prev) throws IOException { + scanner.scan(); + buf = new ByteBuffer(); + bufstream = new DataOutputStream(buf); + // Parse the class name + switch (scanner.token) { + case STRINGVAL: + buf.myname = scanner.stringValue; + break; + case BYTEINDEX: + case SHORTINDEX: + case ATTR: + case BYTES: + case MACRO: + case COMP: + case FILE: + case IDENT: + if (prev == Token.FILE) { + buf.myname = scanner.stringValue; + } else { + buf.myname = scanner.stringValue + ".class"; + } + break; + default: + env.error(scanner.prevPos, "name.expected"); + throw new SyntaxError(); + } + scanner.scan(); + env.traceln("starting class " + buf.myname); + // Parse the clause + parseClause(); + env.traceln("ending class " + buf.myname); + + } // end parseClass + + private void parseClause() throws IOException { + switch (scanner.token) { + case LBRACE: + parseStruct(); + break; + case LSQBRACKET: + parseArray(); + break; + case BYTES: + parseByteArray(); + break; + case ATTR: + parseAttr(); + break; + case COMP: + parseComp(); + break; + default: + env.error(scanner.pos, "struct.expected"); + } + } + + /** + * Parse an Jcoder file. + */ + void parseFile() { + env.traceln("PARSER"); + context.init(); + try { + while (scanner.token != Token.EOF) { + try { + switch (scanner.token) { + case CLASS: + case MODULE: + case INTERFACE: + case FILE: + Token t = scanner.token; + if ( t == Token.MODULE) { + parseModule(); + } else { + parseClass(t); + } + // End of the class,interface or module + env.flushErrors(); + Classes.add(buf); + break; + case SEMICOLON: + // Bogus semi colon + scanner.scan(); + break; + + case EOF: + // The end + return; + + default: + env.traceln("unexpected token=" + scanner.token.toString()); + env.error(scanner.pos, "toplevel.expected"); + throw new SyntaxError(); + } + } catch (SyntaxError e) { + String msg = e.getMessage(); + env.traceln("SyntaxError " + (msg == null ? "" : msg)); + if( env.debugInfoFlag ) { + e.printStackTrace(); + } + recoverFile(); + } + } + } catch (IOException e) { + env.error(scanner.pos, "io.exception", env.getInputFileName()); + } + } //end parseFile + + /*---------------------------------------------*/ + private static char fileSeparator; //=System.getProperty("file.separator"); + + /** + * write to the directory passed with -d option + */ + public void write(ByteBuffer cls, File destdir) throws IOException { + String myname = cls.myname; + if (myname == null) { + env.error("cannot.write", null); + return; + } + + env.traceln("writing " + myname); + File outfile; + if (destdir == null) { + int startofname = myname.lastIndexOf('/'); + if (startofname != -1) { + myname = myname.substring(startofname + 1); + } + outfile = new File(myname); + } else { + env.traceln("writing -d " + destdir.getPath()); + if (fileSeparator == 0) { + fileSeparator = System.getProperty("file.separator").charAt(0); + } + if (fileSeparator != '/') { + myname = myname.replace('/', fileSeparator); + } + outfile = new File(destdir, myname); + File outdir = new File(outfile.getParent()); + if (!outdir.exists() && !outdir.mkdirs()) { + env.error("cannot.write", outdir.getPath()); + return; + } + } + + BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(outfile)); + out.write(cls.data, 0, cls.length); + try { + out.close(); + } catch (IOException ignored) { } + } + + /** + * Writes the classes + */ + public void write(File destdir) throws IOException { + for (ByteBuffer cls : Classes) { + write(cls, destdir); + } + } // end write() +} // end Jcoder diff --git a/test/lib/org/openjdk/asmtools/jcoder/Main.java b/test/lib/org/openjdk/asmtools/jcoder/Main.java new file mode 100644 index 00000000000..c348600d3e6 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jcoder/Main.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2009, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jcoder; + +import org.openjdk.asmtools.common.Tool; +import org.openjdk.asmtools.util.I18NResourceBundle; +import org.openjdk.asmtools.util.ProductInfo; + +import java.io.*; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * + * + */ +public class Main extends Tool { + + public static final I18NResourceBundle i18n + = I18NResourceBundle.getBundleForClass(Main.class); + + public Main(PrintWriter out, String programName) { + super(out, programName); + printCannotReadMsg = (fname) -> error(i18n.getString("jcoder.error.cannot_read", fname)); + } + + public Main(PrintStream out, String program) { + this(new PrintWriter(out), program); + } + + @Override + public void usage() { + println(i18n.getString("jcoder.usage")); + println(i18n.getString("jcoder.opt.nowrite")); + println(i18n.getString("jcoder.opt.ignore")); + println(i18n.getString("jcoder.opt.d")); + println(i18n.getString("jcoder.opt.version")); + } + + /** + * Run the compiler + */ + public synchronized boolean compile(String argv[]) { + File destDir = null; + boolean traceFlag = false; + DebugFlag = () -> false; + long tm = System.currentTimeMillis(); + ArrayList v = new ArrayList<>(); + boolean nowrite = false; + boolean ignore = false; + int nwarnings = 0; + HashMap macros = new HashMap<>(); + macros.put("VERSION", "3;45"); + + // Parse arguments + for (int i = 0; i < argv.length; i++) { + String arg = argv[i]; + if (!arg.startsWith("-")) { + v.add(arg); + } else if (arg.startsWith("-D")) { + int argLength = arg.length(); + if (argLength == 2) { + error(i18n.getString("jcoder.error.D_needs_macro")); + return false; + } + int index = arg.indexOf('='); + if (index == -1) { + error(i18n.getString("jcoder.error.D_needs_macro")); + return false; + } + String macroId = arg.substring(2, index); + index++; + if (argLength == index) { + error(i18n.getString("jcoder.error.D_needs_macro")); + return false; + } + String macro; + if (arg.charAt(index) == '"') { + index++; + if (argLength == index || arg.charAt(argLength - 1) != '"') { + error(i18n.getString("jcoder.error.no_closing_quota")); + return false; + } + macro = arg.substring(index, argLength - 1); + } else { + macro = arg.substring(index, argLength); + } + macros.put(macroId, macro); + } else if (arg.equals("-vv")) { + DebugFlag = () -> true; + traceFlag = true; + } else if (arg.equals("-v")) { + traceFlag = true; + } else if (arg.equals("-nowrite")) { + nowrite = true; + } else if (arg.equals("-ignore")) { + ignore = true; + } else if (arg.equals("-d")) { + if ((i + 1) == argv.length) { + error(i18n.getString("jcoder.error.d_requires_argument")); + usage(); + return false; + } + destDir = new File(argv[++i]); + if (!destDir.exists()) { + error(i18n.getString("jcoder.error.does_not_exist", destDir)); + return false; + } + } else if (arg.equals("-version")) { + println(ProductInfo.FULL_VERSION); + } else { + error(i18n.getString("jcoder.error.invalid_option", arg)); + usage(); + return false; + } + } + if (v.isEmpty()) { + usage(); + return false; + } + // compile all input files + try { + for (String inpname : v) { + SourceFile env; + Jcoder p; + + DataInputStream dataInputStream = getDataInputStream(inpname); + if( dataInputStream == null ) { + nerrors++; + continue; + } + env = new SourceFile(this, dataInputStream, inpname, out); + env.traceFlag = traceFlag; + env.debugInfoFlag = DebugFlag.getAsBoolean(); + p = new Jcoder(env, macros); + p.parseFile(); + env.traceln("END PARSER"); + env.closeInp(); + + nerrors += env.nerrors; + nwarnings += env.nwarnings; + if (nowrite || (nerrors > 0 & !ignore)) { + continue; + } + try { + env.traceln("WRITE"); + p.write(destDir); + } catch (FileNotFoundException ex) { + error(i18n.getString("jcoder.error.cannot_write", ex.getMessage())); + } + } + } catch (Error ee) { + ee.printStackTrace(); + error(i18n.getString("jcoder.error.fatal_error")); + } catch (Exception ee) { + ee.printStackTrace(); + error(i18n.getString("jcoder.error.fatal_exception")); + } + + boolean errs = nerrors > 0; + boolean warns = nwarnings > 0; + if (!errs && !warns) { + return true; + } + println(errs ? (nerrors > 1 ? (nerrors + " errors") : "1 error") + : "" + ((errs && warns) ? ", " : "") + (warns ? (nwarnings > 1 ? (nwarnings + " warnings") : "1 warning") : "")); + return !errs; + } + + /** + * main program + */ + public static void main(String[] argv) { + Main compiler = new Main(new PrintWriter(System.out), "jcoder"); + System.exit(compiler.compile(argv) ? 0 : 1); + } +} diff --git a/test/lib/org/openjdk/asmtools/jcoder/Scanner.java b/test/lib/org/openjdk/asmtools/jcoder/Scanner.java new file mode 100644 index 00000000000..86b8f27e47a --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jcoder/Scanner.java @@ -0,0 +1,905 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jcoder; + +import static org.openjdk.asmtools.jcoder.JcodTokens.*; + +import java.io.IOException; +import java.util.HashMap; + +/** + * A Scanner for Jcoder tokens. Errors are reported to the environment object.

    + * + * The scanner keeps track of the current token, the value of the current token (if any), + * and the start position of the current token.

    + * + * The scan() method advances the scanner to the next token in the input.

    + * + * The match() method is used to quickly match opening brackets (ie: '(', '{', or '[') + * with their closing counter part. This is useful during error recovery.

    + * + * The compiler treats either "\n", "\r" or "\r\n" as the end of a line.

    + */ +public class Scanner { + /*-------------------------------------------------------- */ + /* Scanner Fields */ + + /** + * End of input + */ + public static final int EOF = -1; + public static final int LBRACE = 123; // "{" + private boolean debugCP = false; + private int numCPentrs = 0; + + /** + * Where errors are reported + */ + protected SourceFile env; + + /** + * Input stream + */ + protected SourceFile in; + HashMap macros; + + /** + * The current character + */ + protected int ch, prevCh = -1; + protected String macro; + protected int indexMacro; + + /** + * Current token + */ + protected Token token; + + /** + * The position of the current token + */ + protected int pos; + + /** + * The position of the previous token + */ + protected int prevPos; + + /* Token values. */ + protected long longValue; + protected int intValue; + protected int intSize; + protected String stringValue; + protected ByteBuffer longStringValue; + protected int sign; // sign, when reading number + + /* A doc comment preceding the most recent token */ + protected String docComment; + + /** + * A growable character buffer. + */ + private int count; + private char[] buffer = new char[32]; + + /*-------------------------------------------------------- */ + /** + * Create a scanner to scan an input stream. + */ + protected Scanner(SourceFile sf, HashMap macros) + throws IOException { + this.env = sf; + this.in = sf; + this.macros = macros; + + ch = sf.read(); + prevPos = sf.pos; + + scan(); + } + + /** + * for use in jcfront. + */ + protected Scanner(SourceFile sf) + throws IOException { + this.env = sf; + this.in = sf; + this.macros = new HashMap<>(); + + ch = sf.read(); + prevPos = sf.pos; + + scan(); + } + + /* *********************************************** */ + void setDebugCP(boolean enable) { + if (enable) { + numCPentrs = 0; + } + debugCP = enable; + + } + + void addConstDebug(ConstType ct) { + numCPentrs += 1; + env.traceln("\n Const[" + numCPentrs + "] = " + ct.printval()); + } + + void setMacro(String macro) { + this.macro = macro; + indexMacro = 0; + prevCh = ch; + } + + void readCh() throws IOException { + if (macro != null) { + if (indexMacro < macro.length()) { + ch = macro.charAt(indexMacro); + } + macro = null; + } + if (prevCh >= 0) { + ch = prevCh; + prevCh = -1; + } else { + ch = in.read(); + } + } + + private void putc(int ch) { + if (count == buffer.length) { + char[] newBuffer = new char[buffer.length * 2]; + System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); + buffer = newBuffer; + } + buffer[count++] = (char) ch; + } + + private String bufferString() { + char[] buf = new char[count]; + System.arraycopy(buffer, 0, buf, 0, count); + return new String(buf); + } + + /** + * Scan a comment. This method should be called once the initial /, * and the next + * character have been read. + */ + private void skipComment() throws IOException { + while (true) { + switch (ch) { + case EOF: + env.error(pos, "eof.in.comment"); + return; + + case '*': + readCh(); + if (ch == '/') { + readCh(); + return; + } + break; + + default: + readCh(); + break; + } + } + } + + /** + * Scan a doc comment. This method should be called once the initial /, * and * have + * been read. It gathers the content of the comment (witout leading spaces and '*'s) + * in the string buffer. + */ + private String scanDocComment() throws IOException { + count = 0; + + if (ch == '*') { + do { + readCh(); + } while (ch == '*'); + if (ch == '/') { + readCh(); + return ""; + } + } + switch (ch) { + case '\n': + case ' ': + readCh(); + break; + } + + boolean seenstar = false; + int c = count; + while (true) { + switch (ch) { + case EOF: + env.error(pos, "eof.in.comment"); + return bufferString(); + + case '\n': + putc('\n'); + readCh(); + seenstar = false; + c = count; + break; + + case ' ': + case '\t': + putc(ch); + readCh(); + break; + + case '*': + if (seenstar) { + readCh(); + if (ch == '/') { + readCh(); + count = c; + return bufferString(); + } + putc('*'); + } else { + seenstar = true; + count = c; + do { + readCh(); + } while (ch == '*'); + switch (ch) { + case ' ': + readCh(); + break; + + case '/': + readCh(); + count = c; + return bufferString(); + } + } + break; + + default: + if (!seenstar) { + seenstar = true; + } + putc(ch); + readCh(); + c = count; + break; + } + } + } + + /** + * Scan a decimal number + */ + private void scanDecNumber() throws IOException { + boolean overflow = false; + long value = ch - '0'; + count = 0; + token = Token.INTVAL; + intSize = 2; // default + putc(ch); // save character in buffer +numberLoop: + for (;;) { + readCh(); + switch (ch) { + case '8': + case '9': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + putc(ch); + overflow = overflow || (value * 10) / 10 != value; + value = (value * 10) + (ch - '0'); + overflow = overflow || (value - 1 < -1); + break; + case 'b': + readCh(); + intSize = 1; + break numberLoop; + case 's': + readCh(); + intSize = 2; + break numberLoop; + case 'i': + readCh(); + intSize = 4; + break numberLoop; + case 'l': + readCh(); + intSize = 8; + break numberLoop; + default: + break numberLoop; + } + } + longValue = value; + intValue = (int) value; + // we have just finished reading the number. The next thing better + // not be a letter or digit. + if (Character.isJavaIdentifierPart((char) ch) || ch == '.') { + env.error(in.pos, "invalid.number", Character.toString((char)ch)); + do { + readCh(); + } while (Character.isJavaIdentifierPart((char) ch) || ch == '.'); + return; + } + if (overflow) { + env.error(pos, "overflow"); + } + } // scanNumber() + + /** + * Scan a hex number. + */ + private void scanHexNumber() throws IOException { + boolean overflow = false; + long value = 0; + int cypher; + count = 0; + token = Token.INTVAL; + intSize = 2; // default + putc(ch); // save character in buffer +numberLoop: + for (int k = 0;; k++) { + readCh(); + switch (ch) { + case '8': + case '9': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + cypher = (char) ch - '0'; + break; + case 'd': + case 'D': + case 'e': + case 'E': + case 'f': + case 'F': + case 'a': + case 'A': + case 'b': + case 'B': + case 'c': + case 'C': + cypher = 10 + Character.toLowerCase((char) ch) - 'a'; + break; + + default: + break numberLoop; + } + putc(ch); + overflow = overflow || ((value >>> 60) != 0); + value = (value << 4) + cypher; + intSize = (k + 1) / 2; + } + longValue = value; + intValue = (int) value; + // we have just finished reading the number. The next thing better + // not be a letter or digit. + if (Character.isJavaIdentifierPart((char) ch) || ch == '.') { + env.error(in.pos, "invalid.number", Character.toString((char)ch)); + do { + readCh(); + } while (Character.isJavaIdentifierPart((char) ch) || ch == '.'); + intValue = 0; +// } else if ( overflow || (intValue - 1 < -1) ) { + } else if (overflow) { + intValue = 0; // so we don't get second overflow in Parser + env.error(pos, "overflow"); + } + } // scanNumber() + + /** + * Scan an escape character. + * + * @return the character or -1 if it escaped an end-of-line. + */ + private int scanEscapeChar() throws IOException { + int p = in.pos; + + readCh(); + switch (ch) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': { + int n = ch - '0'; + for (int i = 2; i > 0; i--) { + readCh(); + switch (ch) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + n = (n << 3) + ch - '0'; + break; + + default: + if (n > 0xFF) { + env.error(p, "invalid.escape.char"); + } + return n; + } + } + readCh(); + if (n > 0xFF) { + env.error(p, "invalid.escape.char"); + } + return n; + } + case 'r': + readCh(); + return '\r'; + case 'n': + readCh(); + return '\n'; + case 'f': + readCh(); + return '\f'; + case 'b': + readCh(); + return '\b'; + case 't': + readCh(); + return '\t'; + case '\\': + readCh(); + return '\\'; + case '\"': + readCh(); + return '\"'; + case '\'': + readCh(); + return '\''; + } + + env.error(p, "invalid.escape.char"); + readCh(); + return -1; + } + + /** + * Scan a string. The current character should be the opening " of the string. + */ + private void scanString() throws IOException { + token = Token.STRINGVAL; + count = 0; + readCh(); + +loop: + for (;;) { + switch (ch) { + case EOF: + env.error(pos, "eof.in.string"); + break loop; + + case '\n': + readCh(); + env.error(pos, "newline.in.string"); + break loop; + + case '"': + readCh(); + break loop; + + case '\\': { + int c = scanEscapeChar(); + if (c >= 0) { + putc((char)c); + } + break; + } + + default: + putc(ch); + readCh(); + break; + } + } + stringValue = bufferString(); + } + + /** + * Scan a character array. The current character should be the opening ' of the array. + */ + private void scanCharArray() throws IOException { + token = Token.LONGSTRINGVAL; + ByteBuffer buf = new ByteBuffer(); + count = 0; + readCh(); + +loop: + for (;;) { + int c = ch; + switch (ch) { + case EOF: + env.error(pos, "eof.in.string"); + break loop; + + case '\n': + readCh(); + env.error(pos, "newline.in.string"); + break loop; + + case '\'': + readCh(); + break loop; + + case '\\': + c = scanEscapeChar(); + if (c < 0) { + break; + } + // no break - continue + default: + // see description of java.io.DataOutput.writeUTF() + if ((c > 0) && (c <= 0x7F)) { + buf.write(c); + } else if ((c == 0) || ((c >= 0x80) && (c <= 0x7FF))) { + buf.write(0xC0 | (0x1F & (c >> 6))); + buf.write(0x80 | (0x3f & c)); + } else { + buf.write(0xc0 | (0x0f & (c >> 12))); + buf.write(0x80 | (0x3f & (c >> 6))); + buf.write(0x80 | (0x3f & c)); + } + readCh(); + } + } + longStringValue = buf; + } + + /** + * Scan an Identifier. The current character should be the first character of the + * identifier. + */ + private void scanIdentifier() throws IOException { + count = 0; + boolean compound = false; + for (;;) { + putc(ch); + readCh(); + if ((ch == '/') || (ch == '.') || (ch == '-')) { + compound = true; + } else if (!Character.isJavaIdentifierPart((char) ch)) { + break; + } + } + stringValue = bufferString(); + if (compound) { + token = Token.IDENT; + } else { + token = keyword_token_ident(stringValue); + if (token == Token.IDENT) { + intValue = constValue(stringValue); + if (intValue != -1) { + // this is a constant + if (debugCP) { + ConstType ct = constType(stringValue); + if (ct != null) { + addConstDebug(ct); + } + } + token = Token.INTVAL; + intSize = 1; + longValue = intValue; + } + } + } + } // end scanIdentifier + + // skip till symbol + protected void skipTill(int sym) throws IOException { + while (true) { + if( ch == EOF ) { + env.error(pos, "eof.in.comment"); + return; + } else if (ch == sym) { + return; + } + readCh(); + } + } + + protected int xscan() throws IOException { + int retPos = pos; + prevPos = in.pos; + docComment = null; + sign = 1; + for (;;) { + pos = in.pos; + + switch (ch) { + case EOF: + token = Token.EOF; + return retPos; + + case '\n': + case ' ': + case '\t': + case '\f': + readCh(); + break; + + case '/': + readCh(); + switch (ch) { + case '/': + // Parse a // comment + do { + readCh(); + } while ((ch != EOF) && (ch != '\n')); + break; + + case '*': + readCh(); + if (ch == '*') { + docComment = scanDocComment(); + } else { + skipComment(); + } + break; + + default: + token = Token.DIV; + return retPos; + } + break; + + case '"': + scanString(); + return retPos; + + case '\'': + scanCharArray(); + return retPos; + + case '-': + sign = -sign; // hack: no check that numbers only are allowed after + case '+': + readCh(); + break; + + case '0': + readCh(); + token = Token.INTVAL; + longValue = intValue = 0; + switch (ch) { + case 'x': + case 'X': + scanHexNumber(); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + scanDecNumber(); + break; + case 'b': + readCh(); + intSize = 1; + break; + case 's': + readCh(); + intSize = 2; + break; + case 'i': + readCh(); + intSize = 4; + break; + case 'l': + readCh(); + intSize = 8; + break; + default: + intSize = 2; + } + return retPos; + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + scanDecNumber(); + return retPos; + + case '{': + readCh(); + token = Token.LBRACE; + return retPos; + + case '}': + readCh(); + token = Token.RBRACE; + return retPos; + + case '(': + readCh(); + token = Token.LPAREN; + return retPos; + + case ')': + readCh(); + token = Token.RPAREN; + return retPos; + + case '[': + readCh(); + token = Token.LSQBRACKET; + return retPos; + + case ']': + readCh(); + token = Token.RSQBRACKET; + return retPos; + + case ',': + readCh(); + token = Token.COMMA; + return retPos; + + case ';': + readCh(); + token = Token.SEMICOLON; + return retPos; + + case ':': + readCh(); + token = Token.COLON; + return retPos; + + case '=': + readCh(); + if (ch == '=') { + readCh(); + token = Token.EQ; + return retPos; + } + token = Token.ASSIGN; + return retPos; + + case '\u001a': + // Our one concession to DOS. + readCh(); + if (ch == EOF) { + token = Token.EOF; + return retPos; + } + env.error(pos, "funny.char"); + readCh(); + break; + + case '#': + readCh(); + scanDecNumber(); + return retPos; + + case '&': { + readCh(); + retPos = pos; + if (!Character.isJavaIdentifierStart((char) ch)) { + env.error(pos, "identifier.expected"); + } + scanIdentifier(); + String macroId = stringValue; + String macro = (String) macros.get(macroId); + if (macro == null) { + env.error(pos, "macro.undecl", macroId); + throw new SyntaxError(); + } + setMacro(macro); + readCh(); + } + break; + + default: + if (Character.isJavaIdentifierStart((char) ch)) { + scanIdentifier(); + return retPos; + } + env.error(pos, "funny.char"); + readCh(); + break; + } + } + } + + /** + * Scan to a matching '}', ']' or ')'. The current token must be a '{', '[' or '('; + */ + protected void match(Token open, Token close) throws IOException { + int depth = 1; + + while (true) { + scan(); + if (token == open) { + depth++; + } else if (token == close) { + if (--depth == 0) { + return; + } + } else if (token == Token.EOF) { + env.error(pos, "unbalanced.paren"); + return; + } + } + } + + /** + * Scan the next token. + * + * @return the position of the previous token. + */ + protected int scan() throws IOException { + int retPos = xscan(); +//env.traceln("scanned:"+token+" ("+keywordName(token)+")"); + return retPos; + } + + /** + * Scan the next token. + * + * @return the position of the previous token. + */ + protected int scanMacro() throws IOException { + int retPos = xscan(); +//env.traceln("scanned:"+token+" ("+keywordName(token)+")"); + return retPos; + } +} diff --git a/test/lib/org/openjdk/asmtools/jcoder/SourceFile.java b/test/lib/org/openjdk/asmtools/jcoder/SourceFile.java new file mode 100644 index 00000000000..a03a7fbcd2d --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jcoder/SourceFile.java @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jcoder; + +import java.io.*; +import java.util.Hashtable; + +import org.openjdk.asmtools.common.Tool; +import org.openjdk.asmtools.util.I18NResourceBundle; + +/** + * An input stream for java programs. The stream treats either "\n", "\r" or "\r\n" as the + * end of a line, it always returns \n. It also parses UNICODE characters expressed as + * \uffff. However, if it sees "\\", the second slash cannot begin a unicode sequence. It + * keeps track of the current position in the input stream. + * + * An position consists of: ((linenr << OFFSETBITS) | offset) this means that both + * the line number and the exact offset into the file are encoded in each postion + * value.

    + */ +public class SourceFile implements org.openjdk.asmtools.jasm.Constants { + + Tool tool; + + boolean traceFlag = false; + boolean debugInfoFlag = false; + /** + * The increment for each character. + */ + static final int OFFSETINC = 1; + /** + * The increment for each line. + */ + static final int LINEINC = 1 << OFFSETBITS; + String inputFileName; + InputStream in; + PrintWriter out; + int pos; + private int chpos; + private int pushBack = -1; + + public SourceFile(Tool tool, DataInputStream dataInputStream, String inputFileName, PrintWriter out) { + this.tool = tool; + this.inputFileName = inputFileName; + this.in = new BufferedInputStream(dataInputStream); + chpos = LINEINC; + this.out = out; + } + + public String getInputFileName() { + return inputFileName; + } + + public void closeInp() { + try { + in.close(); + } catch (IOException e) { + } + flushErrors(); + } + + public int read() throws IOException { + pos = chpos; + chpos += OFFSETINC; + + int c = pushBack; + if (c == -1) { + c = in.read(); + } else { + pushBack = -1; + } + + // parse special characters + switch (c) { + case -2: + // -2 is a special code indicating a pushback of a backslash that + // definitely isn't the start of a unicode sequence. + return '\\'; + + case '\\': + if ((c = in.read()) != 'u') { + pushBack = (c == '\\' ? -2 : c); + return '\\'; + } + // we have a unicode sequence + chpos += OFFSETINC; + while ((c = in.read()) == 'u') { + chpos += OFFSETINC; + } + + // unicode escape sequence + int d = 0; + for (int i = 0; i < 4; i++, chpos += OFFSETINC, c = in.read()) { + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + d = (d << 4) + c - '0'; + break; + + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + d = (d << 4) + 10 + c - 'a'; + break; + + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + d = (d << 4) + 10 + c - 'A'; + break; + + default: + error(pos, "invalid.escape.char"); + pushBack = c; + return d; + } + } + pushBack = c; + return d; + + case '\n': + chpos += LINEINC; + return '\n'; + + case '\r': + if ((c = in.read()) != '\n') { + pushBack = c; + } else { + chpos += OFFSETINC; + } + chpos += LINEINC; + return '\n'; + + default: + return c; + } + } + + public int lineNumber(int pos) { + return pos >>> OFFSETBITS; + } + + public int lineNumber() { + return pos >>> OFFSETBITS; + } + + /*============================================================== Environment */ + /** + * The number of errors and warnings + */ + public int nerrors; + public int nwarnings; + + public static final I18NResourceBundle i18n + = I18NResourceBundle.getBundleForClass(Main.class); + + /* + * Until place for jasm.properties is defind, + * I have to keep them right here + * + static Hashtable properties = new Hashtable(40); + + static { + // Scanner: + properties.put("err.eof.in.comment", "Comment not terminated at end of input."); + properties.put("err.invalid.number", "Invalid character \'%s\' in number."); + properties.put("err.invalid.octal.number", "Invalid character in octal number."); + properties.put("err.overflow", "Numeric overflow."); + properties.put("err.float.format", "Invalid floating point format."); + properties.put("err.eof.in.string", "String not terminated at end of input."); + properties.put("err.newline.in.string", "String not terminated at end of line."); + properties.put("err.funny.char", "Invalid character in input."); + properties.put("err.unbalanced.paren", "Unbalanced parentheses."); + // Parser: + properties.put("err.toplevel.expected", "Class or interface declaration expected."); + properties.put("err.token.expected", "'%s' expected."); + properties.put("err.identifier.expected", "Identifier expected."); + properties.put("err.name.expected", "Name expected."); + properties.put("err.io.exception", "I/O error in %s."); + properties.put("err.cannot.write", "Cannot write to %s."); + properties.put("warn.array.wronglength", "expected array length %s do not match real length %s; expected length written"); + properties.put("warn.attr.wronglength", "expected attribute length %s do not match real length %s; expected length written"); + properties.put("attrname.notfound", "Cannot find \"%s\" in constant pool"); + properties.put("err.attrname.expected", "Attribute's name or index expected."); + properties.put("err.element.expected", "Primary data item expected."); + properties.put("err.struct.expected", "Structured data item expected."); + properties.put("err.macro.undecl", "Macro %s undefined."); + } + static String getProperty(String nm) { + return (String) properties.get(nm); + } + */ + /** + * Error String + */ + String errorString(String err, Object arg1, Object arg2, Object arg3) { + String str = null; + + if (!err.startsWith("warn.")) { + err = "err." + err; + } + //str = getProperty(err); + str = i18n.getString(err); + + if (str == null) { + return "error message '" + err + "' not found"; + } + + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if ((c == '%') && (i + 1 < str.length())) { + switch (str.charAt(++i)) { + case 's': + String arg = arg1.toString(); + for (int j = 0; j < arg.length(); j++) { + switch (c = arg.charAt(j)) { + case ' ': + case '\t': + case '\n': + case '\r': + buf.append((char) c); + break; + + default: + if ((c > ' ') && (c <= 255)) { + buf.append((char) c); + } else { + buf.append('\\'); + buf.append('u'); + buf.append(Integer.toString(c, 16)); + } + } + } + arg1 = arg2; + arg2 = arg3; + break; + + case '%': + buf.append('%'); + break; + + default: + buf.append('?'); + break; + } + } else { + buf.append((char) c); + } + } + return buf.toString(); + } + + /** + * List of outstanding error messages + */ + ErrorMessage errors; + + /** + * Insert an error message in the list of outstanding error messages. The list is + * sorted on input position. + */ + void insertError(int where, String message) { + //output("ERR = " + message); + ErrorMessage msg = new ErrorMessage(where, message); + if (errors == null) { + errors = msg; + } else if (errors.where > where) { + msg.next = errors; + errors = msg; + } else { + ErrorMessage m = errors; + for (; (m.next != null) && (m.next.where <= where); m = m.next) { + ; + } + msg.next = m.next; + m.next = msg; + } + } + + /** + * Flush outstanding errors + */ + public void flushErrors() { + if (errors == null) { + return; + } + + try { + // Read the file + DataInputStream dataInputStream = tool.getDataInputStream(inputFileName); + if (dataInputStream == null) + return; + + byte data[] = new byte[dataInputStream.available()]; + dataInputStream.read(data); + dataInputStream.close(); + + // Report the errors + for (ErrorMessage msg = errors; msg != null; msg = msg.next) { + int ln = msg.where >>> OFFSETBITS; + int off = msg.where & ((1 << OFFSETBITS) - 1); + + int i, j; + for (i = off; (i > 0) && (data[i - 1] != '\n') && (data[i - 1] != '\r'); i--) { + ; + } + for (j = off; (j < data.length) && (data[j] != '\n') && (data[j] != '\r'); j++) { + ; + } + + String prefix = inputFileName + ":" + ln + ":"; + outputln(prefix + " " + msg.message); + outputln(new String(data, i, j - i)); + + char strdata[] = new char[(off - i) + 1]; + for (j = i; j < off; j++) { + strdata[j - i] = (data[j] == '\t') ? '\t' : ' '; + } + strdata[off - i] = '^'; + outputln(new String(strdata)); + } + } catch (IOException e) { + outputln("I/O exception"); + } + errors = null; + } + + /** + * Output a string. This can either be an error message or something for debugging. + * This should be used instead of print. + */ + public void output(String msg) { + int len = msg.length(); + for (int i = 0; i < len; i++) { + out.write(msg.charAt(i)); + } + out.flush(); + } + + /** + * Output a string. This can either be an error message or something for debugging. + * This should be used instead of println. + */ + public void outputln(String msg) { + output(msg); + out.write('\n'); + out.flush(); + } + + /** + * Issue an error. + * @param where Offset in the source for the error + * @param err Error number (as defined in this interface) + * @param arg1 Optional argument to the error (null if not applicable) + * @param arg2 Optional argument to the error (null if not applicable) + * @param arg3 Optional argument to the error (null if not applicable) + */ + /** + * Issue an error + */ + public void error(int where, String err, Object arg1, Object arg2, Object arg3) { + String msg = errorString(err, arg1, arg2, arg3); + if (err.startsWith("warn.")) { + nwarnings++; + } else { + nerrors++; + } + traceln("error:" + msg); + insertError(where, msg); + } + + public final void error(int where, String err, Object arg1, Object arg2) { + error(where, err, arg1, arg2, null); + } + + public final void error(int where, String err, Object arg1) { + error(where, err, arg1, null, null); + } + + public final void error(int where, String err) { + error(where, err, null, null, null); + } + + public final void error(String err) { + error(pos, err, null, null, null); + } + + public final void error(String err, Object arg1) { + error(pos, err, arg1, null, null); + } + + /*============================================================== trace */ + public void trace(String message) { + if (traceFlag) { + output(message); + } + } + + public void traceln(String message) { + if (traceFlag) { + outputln(message); + } + } +} // end SourceFile + diff --git a/test/lib/org/openjdk/asmtools/jcoder/SyntaxError.java b/test/lib/org/openjdk/asmtools/jcoder/SyntaxError.java new file mode 100644 index 00000000000..6d8784ed860 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jcoder/SyntaxError.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jcoder; + +/** + * Syntax errors, should always be caught inside the parser for error recovery. + */ +class SyntaxError extends Error { +} diff --git a/test/lib/org/openjdk/asmtools/jcoder/i18n.properties b/test/lib/org/openjdk/asmtools/jcoder/i18n.properties new file mode 100644 index 00000000000..99c1f032347 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jcoder/i18n.properties @@ -0,0 +1,67 @@ +# Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. +# 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. + +jcoder.usage=\ +Usage: java -jar asmtools.jar jcoder [options] file.jcod...\n\ +where possible options include: + +jcoder.opt.nowrite=\ +\ -nowrite do not write resulting .class files +jcoder.opt.ignore=\ +\ -ignore ingore non-fatal error(s) that suppress writing .class files +jcoder.opt.d=\ +\ -d destdir directory to place resulting .class files +jcoder.opt.version=\ +\ -version prints the program version + +jcoder.error.D_needs_macro=-D needs macro declaration +jcoder.error.no_closing_quota=no closing quota in macro definition +jcoder.error.d_requires_argument=-d requires argument +jcoder.error.does_not_exist={0} does not exist +jcoder.error.invalid_option=invalid option: {0} +jcoder.error.cannot_read=cannot read {0} +jcoder.error.cannot_write=cannot write {0} +jcoder.error.fatal_error=fatal error +jcoder.error.fatal_exception=fatal exception + +err.eof.in.comment=Comment not terminated at end of input. +err.invalid.number=Invalid character '%s' in number. +#err.invalid.octal.number=Invalid character in octal number. +err.overflow=Numeric overflow. +#err.float.format=Invalid floating point format. +err.eof.in.string=String not terminated at end of input. +err.newline.in.string=String not terminated at end of line. +err.funny.char=Invalid character in input. +err.unbalanced.paren=Unbalanced parentheses. +err.toplevel.expected=Class or interface declaration expected. +err.token.expected='{0}' expected. +err.identifier.expected=Identifier expected. +err.name.expected=Name expected. +err.io.exception=I/O error in {0}. +err.cannot.write=Cannot write to {0}. +warn.array.wronglength=expected array length %s do not match real length %s; expected length written +warn.attr.wronglength=expected attribute length %s do not match real length %s; expected length written +#attrname.notfound=Cannot find "{0}" in constant pool +err.attrname.expected=Attribute's name or index expected. +err.element.expected=Primary data item expected. +err.struct.expected=Structured data item expected. +err.macro.undecl=Macro {0} undefined. +err.invalid.escape.char=Invalid escape char diff --git a/test/lib/org/openjdk/asmtools/jdec/ClassData.java b/test/lib/org/openjdk/asmtools/jdec/ClassData.java new file mode 100644 index 00000000000..4616033cbfb --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdec/ClassData.java @@ -0,0 +1,1292 @@ +/* + * Copyright (c) 2009, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdec; + +import org.openjdk.asmtools.common.Module; +import org.openjdk.asmtools.asmutils.StringUtils; +import org.openjdk.asmtools.jasm.Modifiers; +import org.openjdk.asmtools.jcoder.JcodTokens; +import org.openjdk.asmtools.util.I18NResourceBundle; + +import java.awt.event.KeyEvent; +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.PrintWriter; + +import static java.lang.String.format; +import static org.openjdk.asmtools.jasm.Tables.*; +import static org.openjdk.asmtools.jasm.Tables.AnnotElemType.AE_UNKNOWN; +import static org.openjdk.asmtools.jasm.TypeAnnotationTypes.*; + +/** + * Class data of the Java Decoder + */ +class ClassData { + + private byte[] types; + private Object[] cpool; + private int CPlen; + private NestedByteArrayInputStream countedin; + private DataInputStream in; + private PrintWriter out; + private int[] cpe_pos; + private boolean printDetails; + private String entityType = ""; + private String entityName = ""; + + public static I18NResourceBundle i18n + = I18NResourceBundle.getBundleForClass(Main.class); + + ClassData(DataInputStream dis, int printFlags, PrintWriter out) throws IOException { + byte[] buf = new byte[dis.available()]; + try { + if (dis.read(buf) <= 0) + throw new IOException("The file is empty"); + } finally { + dis.close(); + } + countedin = new NestedByteArrayInputStream(buf); + in = new DataInputStream(countedin); + this.out = out; + printDetails = ((printFlags & 1) == 1); + } + + /*========================================================*/ + private static final char[] hexTable = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + + private String toHex(long val, int width) { + StringBuilder s = new StringBuilder(); + for (int i = width * 2 - 1; i >= 0; i--) { + s.append(hexTable[((int) (val >> (4 * i))) & 0xF]); + } + return "0x" + s.toString(); + } + + private String toHex(long val) { + int width; + for (width = 8; width > 0; width--) { + if ((val >> (width - 1) * 8) != 0) { + break; + } + } + return toHex(val, width); + } + + private void printByteHex(PrintWriter out, int b) { + out.print(hexTable[(b >> 4) & 0xF]); + out.print(hexTable[b & 0xF]); + } + + private void printBytes(PrintWriter out, DataInputStream in, int len) + throws IOException { + try { + for (int i = 0; i < len; i++) { + if (i % 8 == 0) { + out_print("0x"); + } + printByteHex(out, in.readByte()); + if (i % 8 == 7) { + out.println(";"); + } + } + } finally { + if (len % 8 != 0) { + out.println(";"); + } + } + } + + private void printRestOfBytes() { + for (int i = 0; ; i++) { + try { + byte b = in.readByte(); + if (i % 8 == 0) { + out_print("0x"); + } + printByteHex(out, b); + if (i % 8 == 7) { + out.print(";\n"); + } + } catch (IOException e) { + return; + } + } + } + + private void printUtf8InfoIndex(int index, String indexName) { + String name = (String) cpool[index]; + out_print("#" + index + "; // "); + if (printDetails) { + out.println(String.format("%-16s",indexName) + " : " + name); + } else { + out.println(indexName); + } + } + + /*========================================================*/ + private int shift = 0; + + private void out_begin(String s) { + for (int i = 0; i < shift; i++) { + out.print(" "); + } + out.println(s); + shift++; + } + + private void out_print(String s) { + for (int i = 0; i < shift; i++) { + out.print(" "); + } + out.print(s); + } + + private void out_println(String s) { + for (int i = 0; i < shift; i++) { + out.print(" "); + } + out.println(s); + } + + private void out_end(String s) { + shift--; + for (int i = 0; i < shift; i++) { + out.print(" "); + } + out.println(s); + } + + private String startArray(int length) { + return "[" + (printDetails ? Integer.toString(length) : "") + "]"; + } + + private void startArrayCmt(int length, String comment) { + out_begin(startArray(length) + format(" {%s", comment == null ? "" : " // " + comment)); + } + + private void startArrayCmtB(int length, String comment) { + out_begin(startArray(length) + format("b {%s", comment == null ? "" : " // " + comment)); + } + + /*========================================================*/ + private void readCP(DataInputStream in) throws IOException { + int length = in.readUnsignedShort(); + CPlen = length; + traceln(i18n.getString("jdec.trace.CP_len", length)); + types = new byte[length]; + cpool = new Object[length]; + cpe_pos = new int[length]; + for (int i = 1; i < length; i++) { + byte btag; + int v1; + long lv; + cpe_pos[i] = countedin.getPos(); + btag = in.readByte(); + traceln(i18n.getString("jdec.trace.CP_entry", i, btag)); + types[i] = btag; + ConstType tg = tag(btag); + switch (tg) { + case CONSTANT_UTF8: + cpool[i] = in.readUTF(); + break; + case CONSTANT_INTEGER: + v1 = in.readInt(); + cpool[i] = v1; + break; + case CONSTANT_FLOAT: + v1 = Float.floatToIntBits(in.readFloat()); + cpool[i] = v1; + break; + case CONSTANT_LONG: + lv = in.readLong(); + cpool[i] = lv; + i++; + break; + case CONSTANT_DOUBLE: + lv = Double.doubleToLongBits(in.readDouble()); + cpool[i] = lv; + i++; + break; + case CONSTANT_CLASS: + case CONSTANT_STRING: + case CONSTANT_MODULE: + case CONSTANT_PACKAGE: + v1 = in.readUnsignedShort(); + cpool[i] = v1; + break; + case CONSTANT_INTERFACEMETHOD: + case CONSTANT_FIELD: + case CONSTANT_METHOD: + case CONSTANT_NAMEANDTYPE: + cpool[i] = "#" + in.readUnsignedShort() + " #" + in.readUnsignedShort(); + break; + case CONSTANT_DYNAMIC: + case CONSTANT_INVOKEDYNAMIC: + cpool[i] = in.readUnsignedShort() + "s #" + in.readUnsignedShort(); + break; + case CONSTANT_METHODHANDLE: + cpool[i] = in.readUnsignedByte() + "b #" + in.readUnsignedShort(); + break; + case CONSTANT_METHODTYPE: + cpool[i] = "#" + in.readUnsignedShort(); + break; + default: + CPlen = i; + printCP(out); + out_println(toHex(btag, 1) + "; // invalid constant type: " + (int) btag + " for element " + i); + throw new ClassFormatError(); + } + } + } + + private void printCP(PrintWriter out) { + int length = CPlen; + startArrayCmt(length, "Constant Pool"); + out_println("; // first element is empty"); + try { + int size; + for (int i = 1; i < length; i = i + size) { + size = 1; + byte btag = types[i]; + ConstType tg = tag(btag); + int pos = cpe_pos[i]; + String tagstr; + String valstr; + int v1; + long lv; + if (tg != null) { + tagstr = tg.parseKey(); + } else { + throw new Error("Can't get a tg representing the type of Constant in the Constant Pool at: " + i); + } + switch (tg) { + case CONSTANT_UTF8: { + tagstr = "Utf8"; + valstr = StringUtils.Utf8ToString((String) cpool[i]); + } + break; + case CONSTANT_FLOAT: + case CONSTANT_INTEGER: + v1 = (Integer) cpool[i]; + valstr = toHex(v1, 4); + break; + case CONSTANT_DOUBLE: + case CONSTANT_LONG: + lv = (Long) cpool[i]; + valstr = toHex(lv, 8) + ";"; + size = 2; + break; + case CONSTANT_CLASS: + case CONSTANT_MODULE: + case CONSTANT_PACKAGE: + case CONSTANT_STRING: + v1 = (Integer) cpool[i]; + valstr = "#" + v1; + break; + case CONSTANT_INTERFACEMETHOD: + case CONSTANT_FIELD: + case CONSTANT_METHOD: + case CONSTANT_NAMEANDTYPE: + case CONSTANT_METHODHANDLE: + case CONSTANT_METHODTYPE: + case CONSTANT_DYNAMIC: + case CONSTANT_INVOKEDYNAMIC: + valstr = (String) cpool[i]; + break; + default: + throw new Error("invalid constant type: " + (int) btag); + } + out_print(tagstr + " " + valstr + "; // #" + i); + if (printDetails) { + out_println(" at " + toHex(pos)); + } else { + out.println(); + } + } + } finally { + out_end("} // Constant Pool"); + out.println(); + } + } + + private String getStringPos() { + return " at " + toHex(countedin.getPos()); + } + + private String getCommentPosCond() { + if (printDetails) { + return " // " + getStringPos(); + } else { + return ""; + } + } + + private void decodeCPXAttr(DataInputStream in, int len, String attrname, PrintWriter out) throws IOException { + decodeCPXAttrM(in, len, attrname, out, 1); + } + + private void decodeCPXAttrM(DataInputStream in, int len, String attrname, PrintWriter out, int expectedIndices) throws IOException { + if (len != expectedIndices * 2) { + out_println("// invalid length of " + attrname + " attr: " + len + " (should be " + (expectedIndices * 2) + ") > "); + printBytes(out, in, len); + } else { + StringBuilder outputString = new StringBuilder(); + for (int k = 1; k <= expectedIndices; k++) { + outputString.append("#").append(in.readUnsignedShort()).append("; "); + if (k % 16 == 0) { + out_println(outputString.toString().replaceAll("\\s+$","")); + outputString = new StringBuilder(); + } + } + if (outputString.length() > 0) { + out_println(outputString.toString().replaceAll("\\s+$","")); + } + } + } + + private void printStackMap(DataInputStream in, int elementsNum) throws IOException { + int num; + if (elementsNum > 0) { + num = elementsNum; + } else { + num = in.readUnsignedShort(); + } + out.print(startArray(num) + (elementsNum > 0 ? "z" : "") + "{"); + try { + for (int k = 0; k < num; k++) { + int maptype = in.readUnsignedByte(); + StackMapType mptyp = stackMapType(maptype, out); + String maptypeImg; + if (printDetails) { + maptypeImg = maptype + "b"; + } else { + try { + maptypeImg = mptyp.parsekey(); + } catch (ArrayIndexOutOfBoundsException e) { + maptypeImg = "/* BAD TYPE: */ " + maptype + "b"; + } + } + switch (mptyp) { + case ITEM_Object: + case ITEM_NewObject: + maptypeImg = maptypeImg + "," + in.readUnsignedShort(); + break; + case ITEM_UNKNOWN: + maptypeImg = maptype + "b"; + break; + default: + } + out.print(maptypeImg); + if (k < num - 1) { + out.print("; "); + } + } + } finally { + out.print("}"); + } + } + + /** + * Processes 4.7.20 The RuntimeVisibleTypeAnnotations Attribute, 4.7.21 The RuntimeInvisibleTypeAnnotations Attribute + * type_annotation structure. + */ + private void decodeTargetTypeAndRefInfo(DataInputStream in) throws IOException { + int tt = in.readUnsignedByte(); // [4.7.20] annotations[], type_annotation { u1 target_type; ...} + ETargetType targetType = ETargetType.getTargetType(tt); + if( targetType == null ) { + throw new Error("Type annotation: invalid target_type(u1) " + tt); + } + ETargetInfo targetInfo = targetType.targetInfo(); + out_println(toHex(tt, 1) + "; // target_type: " + targetType.parseKey()); + switch (targetInfo) { + case TYPEPARAM: //[3.3.1] meth_type_param, class_type_param: + out_println(toHex(in.readUnsignedByte(), 1) + "; // param_index"); + break; + case SUPERTYPE: //[3.3.2] class_exts_impls + out_println(toHex(in.readUnsignedShort(), 2) + "; // type_index"); + break; + case TYPEPARAM_BOUND: //[3.3.3] class_type_param_bnds, meth_type_param_bnds + out_println(toHex(in.readUnsignedByte(), 1) + "; // param_index"); + out_println(toHex(in.readUnsignedByte(), 1) + "; // bound_index"); + break; + case EMPTY: //[3.3.4] meth_receiver, meth_ret_type, field + // NOTE: reference_info is empty for this annotation's target + break; + case METHODPARAM: //[3.3.5] meth_formal_param: + out_println(toHex(in.readUnsignedByte(), 1) + "; // parameter_index"); + break; + case EXCEPTION: //[3.3.61] throws_type + //KTL: Updated index to UShort for JSR308 change + out_println(in.readUnsignedShort() + "; // type_index"); + break; + case LOCALVAR: //[3.3.7] local_var, resource_var + { + int lv_num = in.readUnsignedShort(); + startArrayCmt(lv_num, "local_variables"); + try { + for (int i = 0; i < lv_num; i++) { + out_println(in.readUnsignedShort() + " " + in.readUnsignedShort() + + " " + in.readUnsignedShort() + ";" + getCommentPosCond()); + } + } finally { + out_end("}"); + } + } + break; + case CATCH: //[3.3.8] exception_param + out_println(in.readUnsignedShort() + "; // exception_table_index"); + break; + case OFFSET: //[3.3.9] type_test (instanceof), obj_creat (new) + // constr_ref_receiver, meth_ref_receiver + out_println(in.readUnsignedShort() + "; // offset"); + break; + case TYPEARG: //[3.3.10] cast, constr_ref_typearg, meth_invoc_typearg + // constr_invoc_typearg, meth_ref_typearg + out_println(in.readUnsignedShort() + "; // offset"); + out_println(toHex(in.readUnsignedByte(), 1) + "; // type_index"); + break; + default: // should never happen + out_println(toHex(tt, 1) + "; // invalid target_info: " + tt); + throw new ClassFormatError(); + } + // [4.7.20.2] + int path_length = in.readUnsignedByte(); // type_path { u1 path_length; ...} + startArrayCmtB(path_length, "type_paths"); + try { + for (int i = 0; i < path_length; i++) { + // print the type_path elements + out_println("{ " + toHex(in.readUnsignedByte(), 1) // { u1 type_path_kind; + + "; " + toHex(in.readUnsignedByte(), 1) // u1 type_argument_index; } + + "; } // type_path[" + i + "]"); // path[i] + } + } finally { + out_end("}"); + } + } + + private void decodeElementValue(DataInputStream in, PrintWriter out) throws IOException { + out_begin("{ // element_value"); + try { + char tg = (char) in.readByte(); + AnnotElemType tag = annotElemType(tg); + if (tag != AE_UNKNOWN) { + out_println("'" + tg + "';"); + } + switch (tag) { + case AE_BYTE: + case AE_CHAR: + case AE_DOUBLE: + case AE_FLOAT: + case AE_INT: + case AE_LONG: + case AE_SHORT: + case AE_BOOLEAN: + case AE_STRING: + decodeCPXAttr(in, 2, "const_value_index", out); + break; + case AE_ENUM: + out_begin("{ // enum_const_value"); + decodeCPXAttr(in, 2, "type_name_index", out); + decodeCPXAttr(in, 2, "const_name_index", out); + out_end("} // enum_const_value"); + break; + case AE_CLASS: + decodeCPXAttr(in, 2, "class_info_index", out); + break; + case AE_ANNOTATION: + decodeAnnotation(in, out); + break; + case AE_ARRAY: + int ev_num = in.readUnsignedShort(); + startArrayCmt(ev_num, "array_value"); + try { + for (int i = 0; i < ev_num; i++) { + decodeElementValue(in, out); + if (i < ev_num - 1) { + out_println(";"); + } + } + } finally { + out_end("} // array_value"); + } + break; + case AE_UNKNOWN: + default: + String msg = "invalid element_value" + (isPrintableChar(tg) ? " tag type : " + tg : ""); + out_println(toHex(tg, 1) + "; // " + msg); + throw new ClassFormatError(msg); + } + } finally { + out_end("} // element_value"); + } + } + + public boolean isPrintableChar(char c) { + Character.UnicodeBlock block = Character.UnicodeBlock.of(c); + return (!Character.isISOControl(c)) && + c != KeyEvent.CHAR_UNDEFINED && + block != null && + block != Character.UnicodeBlock.SPECIALS; + } + + private void decodeAnnotation(DataInputStream in, PrintWriter out) throws IOException { + out_begin("{ // annotation"); + try { + decodeCPXAttr(in, 2, "field descriptor", out); + int evp_num = in.readUnsignedShort(); + decodeElementValuePairs(evp_num, in, out); + } finally { + out_end("} // annotation"); + } + } + + private void decodeElementValuePairs(int count, DataInputStream in, PrintWriter out) throws IOException { + startArrayCmt(count, "element_value_pairs"); + try { + for (int i = 0; i < count; i++) { + out_begin("{ // element value pair"); + try { + decodeCPXAttr(in, 2, "name of the annotation type element", out); + decodeElementValue(in, out); + } finally { + out_end("} // element value pair"); + if (i < count - 1) { + out_println(";"); + } + } + } + } finally { + out_end("} // element_value_pairs"); + } + } + + /** + * component_info { JEP 359 Record(Preview): class file 58.65535 + * u2 name_index; + * u2 descriptor_index; + * u2 attributes_count; + * attribute_info attributes[attributes_count]; + * } + * + * or + * field_info { + * u2 access_flags; + * u2 name_index; + * u2 descriptor_index; + * u2 attributes_count; + * attribute_info attributes[attributes_count]; + * } + * or + * method_info { + * u2 access_flags; + * u2 name_index; + * u2 descriptor_index; + * u2 attributes_count; + * attribute_info attributes[attributes_count]; + * } + * + */ + private void decodeInfo(DataInputStream in, PrintWriter out, String elementName, boolean hasAccessFlag) throws IOException { + out_begin("{ // " + elementName + (printDetails ? getStringPos() : "")); + try { + if(hasAccessFlag) { + // u2 access_flags; + out_println(toHex(in.readShort(), 2) + "; // access"); + } + // u2 name_index + printUtf8InfoIndex(in.readUnsignedShort(), "name_index"); + // u2 descriptor_index + printUtf8InfoIndex(in.readUnsignedShort(), "descriptor_index"); + // u2 attributes_count; + // attribute_info attributes[attributes_count] + decodeAttrs(in, out); + } finally { + out_end("}"); + } + } + + private void decodeTypeAnnotation(DataInputStream in, PrintWriter out) throws IOException { + out_begin("{ // type_annotation"); + try { + decodeTargetTypeAndRefInfo(in); + decodeCPXAttr(in, 2, "field descriptor", out); + int evp_num = in.readUnsignedShort(); + decodeElementValuePairs(evp_num, in, out); + } finally { + out_end("} // type_annotation"); + } + } + + private void decodeBootstrapMethod(DataInputStream in) throws IOException { + out_begin("{ // bootstrap_method"); + try { + out_println("#" + in.readUnsignedShort() + "; // bootstrap_method_ref"); + int bm_args_cnt = in.readUnsignedShort(); + startArrayCmt(bm_args_cnt, "bootstrap_arguments"); + try { + for (int i = 0; i < bm_args_cnt; i++) { + out_println("#" + in.readUnsignedShort() + ";" + getCommentPosCond()); + } + } finally { + out_end("} // bootstrap_arguments"); + } + } finally { + out_end("} // bootstrap_method"); + } + } + + private void decodeAttr(DataInputStream in, PrintWriter out) throws IOException { + // Read one attribute + String posComment = getStringPos(); + int name_cpx = in.readUnsignedShort(), btag, len; + + String AttrName = ""; + try { + btag = types[name_cpx]; + ConstType tag = tag(btag); + + if (tag == ConstType.CONSTANT_UTF8) { + AttrName = (String) cpool[name_cpx]; + } + } catch (ArrayIndexOutOfBoundsException ignored) { + } + AttrTag tg = attrtag(AttrName); + String endingComment = AttrName; + len = in.readInt(); + countedin.enter(len); + try { + if (printDetails) { + out_begin("Attr(#" + name_cpx + ", " + len + ") { // " + AttrName + posComment); + } else { + out_begin("Attr(#" + name_cpx + ") { // " + AttrName); + } + + switch (tg) { + case ATT_Code: + out_println(in.readUnsignedShort() + "; // max_stack"); + out_println(in.readUnsignedShort() + "; // max_locals"); + int code_len = in.readInt(); + out_begin("Bytes" + startArray(code_len) + "{"); + try { + printBytes(out, in, code_len); + } finally { + out_end("}"); + } + int trap_num = in.readUnsignedShort(); + startArrayCmt(trap_num, "Traps"); + try { + for (int i = 0; i < trap_num; i++) { + out_println(in.readUnsignedShort() + " " + + in.readUnsignedShort() + " " + + in.readUnsignedShort() + " " + + in.readUnsignedShort() + ";" + + getCommentPosCond()); + } + } finally { + out_end("} // end Traps"); + } + // Read the attributes + decodeAttrs(in, out); + break; + + case ATT_Exceptions: + int count = in.readUnsignedShort(); + startArrayCmt(count, AttrName); + try { + for (int i = 0; i < count; i++) { + out_println("#" + in.readUnsignedShort() + ";" + + getCommentPosCond()); + } + } finally { + out_end("}"); + } + break; + case ATT_LineNumberTable: + int ll_num = in.readUnsignedShort(); + startArrayCmt(ll_num, "line_number_table"); + try { + for (int i = 0; i < ll_num; i++) { + out_println(in.readUnsignedShort() + " " + + in.readUnsignedShort() + ";" + + getCommentPosCond()); + } + } finally { + out_end("}"); + } + break; + case ATT_LocalVariableTable: + case ATT_LocalVariableTypeTable: + int lvt_num = in.readUnsignedShort(); + startArrayCmt(lvt_num, AttrName); + try { + for (int i = 0; i < lvt_num; i++) { + out_println(in.readUnsignedShort() + " " + + in.readUnsignedShort() + " " + + in.readUnsignedShort() + " " + + in.readUnsignedShort() + " " + + in.readUnsignedShort() + ";" + + getCommentPosCond()); + } + } finally { + out_end("}"); + } + break; + case ATT_InnerClasses: + int ic_num = in.readUnsignedShort(); + startArrayCmt(ic_num, "classes"); + try { + for (int i = 0; i < ic_num; i++) { + out_println("#" + in.readUnsignedShort() + " #" + + in.readUnsignedShort() + " #" + + in.readUnsignedShort() + " " + + in.readUnsignedShort() + ";" + getCommentPosCond()); + } + } finally { + out_end("}"); + } + break; + case ATT_StackMap: + int e_num = in.readUnsignedShort(); + startArrayCmt(e_num, ""); + try { + for (int k = 0; k < e_num; k++) { + int start_pc = in.readUnsignedShort(); + out_print("" + start_pc + ", "); + printStackMap(in, 0); + out.print(", "); + printStackMap(in, 0); + out.println(";"); + } + } finally { + out_end("}"); + } + break; + case ATT_StackMapTable: + int et_num = in.readUnsignedShort(); + startArrayCmt(et_num, ""); + try { + for (int k = 0; k < et_num; k++) { + int frame_type = in.readUnsignedByte(); + StackMapFrameType ftype = stackMapFrameType(frame_type); + switch (ftype) { + case SAME_FRAME: + // type is same_frame; + out_print("" + frame_type + "b"); + out.println("; // same_frame"); + break; + case SAME_LOCALS_1_STACK_ITEM_FRAME: + // type is same_locals_1_stack_item_frame + out_print("" + frame_type + "b, "); + // read additional single stack element + printStackMap(in, 1); + out.println("; // same_locals_1_stack_item_frame"); + break; + case SAME_LOCALS_1_STACK_ITEM_EXTENDED_FRAME: + // type is same_locals_1_stack_item_frame_extended + int noffset = in.readUnsignedShort(); + out_print("" + frame_type + "b, " + noffset + ", "); + // read additional single stack element + printStackMap(in, 1); + out.println("; // same_locals_1_stack_item_frame_extended"); + break; + case CHOP_1_FRAME: + case CHOP_2_FRAME: + case CHOP_3_FRAME: + // type is chop_frame + int coffset = in.readUnsignedShort(); + out_print("" + frame_type + "b, " + coffset); + out.println("; // chop_frame " + (251 - frame_type)); + break; + case SAME_FRAME_EX: + // type is same_frame_extended; + int xoffset = in.readUnsignedShort(); + out_print("" + frame_type + "b, " + xoffset); + out.println("; // same_frame_extended"); + break; + case APPEND_FRAME: + // type is append_frame + int aoffset = in.readUnsignedShort(); + out_print("" + frame_type + "b, " + aoffset + ", "); + // read additional locals + printStackMap(in, frame_type - 251); + out.println("; // append_frame " + (frame_type - 251)); + break; + case FULL_FRAME: + // type is full_frame + int foffset = in.readUnsignedShort(); + out_print("" + frame_type + "b, " + foffset + ", "); + printStackMap(in, 0); + out.print(", "); + printStackMap(in, 0); + out.println("; // full_frame"); + break; + } + } + } finally { + out_end("}"); + } + break; + case ATT_EnclosingMethod: + decodeCPXAttrM(in, len, AttrName, out, 2); + break; + case ATT_AnnotationDefault: + decodeElementValue(in, out); + break; + case ATT_RuntimeInvisibleAnnotations: + case ATT_RuntimeVisibleAnnotations: + int an_num = in.readUnsignedShort(); + startArrayCmt(an_num, "annotations"); + try { + for (int i = 0; i < an_num; i++) { + decodeAnnotation(in, out); + if (i < an_num - 1) { + out_println(";"); + } + } + } finally { + out_end("}"); + } + break; + // 4.7.20 The RuntimeVisibleTypeAnnotations Attribute + // 4.7.21 The RuntimeInvisibleTypeAnnotations Attribute + case ATT_RuntimeInvisibleTypeAnnotations: + case ATT_RuntimeVisibleTypeAnnotations: + int ant_num = in.readUnsignedShort(); + startArrayCmt(ant_num, "annotations"); + try { + for (int i = 0; i < ant_num; i++) { + decodeTypeAnnotation(in, out); + if (i < ant_num - 1) { + out_println(";"); + } + } + } finally { + out_end("}"); + } + break; + case ATT_RuntimeInvisibleParameterAnnotations: + case ATT_RuntimeVisibleParameterAnnotations: + int pm_num = in.readUnsignedByte(); + startArrayCmtB(pm_num, "parameters"); + try { + for (int k = 0; k < pm_num; k++) { + int anp_num = in.readUnsignedShort(); + startArrayCmt(anp_num, "annotations"); + try { + for (int i = 0; i < anp_num; i++) { + decodeAnnotation(in, out); + if (k < anp_num - 1) { + out_println(";"); + } + } + } finally { + out_end("}"); + } + if (k < pm_num - 1) { + out_println(";"); + } + } + } finally { + out_end("}"); + } + break; + case ATT_BootstrapMethods: + int bm_num = in.readUnsignedShort(); + startArrayCmt(bm_num, "bootstrap_methods"); + try { + for (int i = 0; i < bm_num; i++) { + decodeBootstrapMethod(in); + if (i < bm_num - 1) { + out_println(";"); + } + } + } finally { + out_end("}"); + } + break; + case ATT_Module: + decodeModule(in); + break; + case ATT_TargetPlatform: + decodeCPXAttrM(in, len, AttrName, out, 3); + break; + case ATT_ModulePackages: + int p_num = in.readUnsignedShort(); + startArrayCmt(p_num, null); + try { + decodeCPXAttrM(in, len - 2, AttrName, out, p_num); + } finally { + out_end("}"); + } + break; + // MethodParameters_attribute { + // u2 attribute_name_index; + // u4 attribute_length; + // u1 parameters_count; + // { u2 name_index; + // u2 access_flags; + // } parameters[parameters_count]; + // } + case ATT_MethodParameters: + int pcount = in.readUnsignedByte(); + startArrayCmtB(pcount, AttrName); + try { + for (int i = 0; i < pcount; i++) { + out_println("#" + in.readUnsignedShort() + " " + + toHex(in.readUnsignedShort(), 2) + ";" + + getCommentPosCond()); + } + } finally { + out_end("}"); + } + break; + // JEP 359 Record(Preview): class file 58.65535 + // Record_attribute { + // u2 attribute_name_index; + // u4 attribute_length; + // u2 components_count; + // component_info components[components_count]; + // } + case ATT_Record: + int ncomps = in.readUnsignedShort(); + startArrayCmt(ncomps, "components"); + try { + for (int i = 0; i < ncomps; i++) { + decodeInfo(in,out,"component",false); + if (i < ncomps - 1) { + out_println(";"); + } + } + } finally { + out_end("}"); + } + break; + case ATT_ConstantValue: + case ATT_Signature: + case ATT_SourceFile: + decodeCPXAttr(in, len, AttrName, out); + break; + // JEP 181 (Nest-based Access Control): class file 55.0 + // NestHost_attribute { + // u2 attribute_name_index; + // u4 attribute_length; + // u2 host_class_index; + // } + case ATT_NestHost: + decodeTypes(in, out, 1); + break; + // JEP 181 (Nest-based Access Control): class file 55.0 + // NestMembers_attribute { + // u2 attribute_name_index; + // u4 attribute_length; + // u2 number_of_classes; + // u2 classes[number_of_classes]; + // } + case ATT_NestMembers: + // JEP 360 (Sealed types): class file 59.65535 + // PermittedSubclasses_attribute { + // u2 attribute_name_index; + // u4 attribute_length; + // u2 number_of_classes; + // u2 classes[number_of_classes]; + // } + case ATT_PermittedSubclasses: + // Preload attribute has same format + case ATT_Preload: + int nsubtypes = in.readUnsignedShort(); + startArrayCmt(nsubtypes, "classes"); + try { + decodeTypes(in, out, nsubtypes); + } finally { + out_end("}"); + } + break; + default: + printBytes(out, in, len); + if (AttrName == null) { + endingComment = "Attr(#" + name_cpx + ")"; + } + } + + } catch (EOFException e) { + out.println("// ======== unexpected end of attribute array"); + } finally { + int rest = countedin.available(); + if (rest > 0) { + out.println("// ======== attribute array started " + posComment + " has " + rest + " bytes more:"); + printBytes(out, in, rest); + } + out_end("} // end " + endingComment); + countedin.leave(); + } + } + + private void decodeModuleStatement(String statementName, DataInputStream in) throws IOException { + // u2 {exports|opens}_count + int count = in.readUnsignedShort(); + startArrayCmt(count, statementName); + try { + for (int i = 0; i < count; i++) { + // u2 {exports|opens}_index; u2 {exports|opens}_flags + int index = in.readUnsignedShort(); + int nFlags = in.readUnsignedShort(); + String sFlags = printDetails ? Module.Modifier.getStatementFlags(nFlags) : ""; + out_println("#" + index + " " + toHex(nFlags, 2) + (sFlags.isEmpty() ? "" : " // [ " + sFlags + " ]")); + int exports_to_count = in.readUnsignedShort(); + startArrayCmt(exports_to_count, null); + try { + for (int j = 0; j < exports_to_count; j++) { + out_println("#" + in.readUnsignedShort() + ";"); + } + } finally { + out_end("};"); + } + } + } finally { + out_end("} // " + statementName + "\n"); + } + } + + private void decodeModule(DataInputStream in) throws IOException { + //u2 module_name_index + int index = in.readUnsignedShort(); + entityName = (String) cpool[(Integer) cpool[index]]; + out_print("#" + index + "; // "); + if (printDetails) { + out.println(String.format("%-16s","name_index") + " : " + entityName); + } else { + out.println("name_index"); + } + + // u2 module_flags + int moduleFlags = in.readUnsignedShort(); + out_print(toHex(moduleFlags, 2) + "; // flags"); + if (printDetails) { + out_print(" " + Module.Modifier.getModuleFlags(moduleFlags)); + } + out.println(); + + //u2 module_version + int versionIndex = in.readUnsignedShort(); + out_println("#" + versionIndex + "; // version"); + + // u2 requires_count + int count = in.readUnsignedShort(); + startArrayCmt(count, "requires"); + try { + for (int i = 0; i < count; i++) { + // u2 requires_index; u2 requires_flags; u2 requires_version_index + index = in.readUnsignedShort(); + int nFlags = in.readUnsignedShort(); + versionIndex = in.readUnsignedShort(); + String sFlags = printDetails ? Module.Modifier.getStatementFlags(nFlags) : ""; + out_println("#" + index + " " + toHex(nFlags, 2) + " #" + versionIndex + ";" + (sFlags.isEmpty() ? "" : " // " + sFlags)); + } + } finally { + out_end("} // requires\n"); + } + + decodeModuleStatement("exports", in); + + decodeModuleStatement("opens", in); + // u2 uses_count + count = in.readUnsignedShort(); + startArrayCmt(count, "uses"); + try { + for (int i = 0; i < count; i++) { + // u2 uses_index + out_println("#" + in.readUnsignedShort() + ";"); + } + } finally { + out_end("} // uses\n"); + } + count = in.readUnsignedShort(); // u2 provides_count + startArrayCmt(count, "provides"); + try { + for (int i = 0; i < count; i++) { + // u2 provides_index + out_println("#" + in.readUnsignedShort()); + int provides_with_count = in.readUnsignedShort(); + // u2 provides_with_count + startArrayCmt(provides_with_count, null); + try { + for (int j = 0; j < provides_with_count; j++) { + // u2 provides_with_index; + out_println("#" + in.readUnsignedShort() + ";"); + } + } finally { + out_end("};"); + } + } + } finally { + out_end("} // provides\n"); + } + } + + private void decodeAttrs(DataInputStream in, PrintWriter out) throws IOException { + // Read the attributes + int attr_num = in.readUnsignedShort(); + startArrayCmt(attr_num, "Attributes"); + try { + for (int i = 0; i < attr_num; i++) { + decodeAttr(in, out); + if (i + 1 < attr_num) { + out_println(";"); + } + } + } finally { + out_end("} // Attributes"); + } + } + + private void decodeMembers(DataInputStream in, PrintWriter out, String groupName, String elementName) throws IOException { + int count = in.readUnsignedShort(); + traceln(groupName + "=" + count); + startArrayCmt(count, groupName); + try { + for (int i = 0; i < count; i++) { + decodeInfo(in,out,elementName,true); + if (i + 1 < count) { + out_println(";"); + } + } + } finally { + out_end("} // " + groupName); + out.println(); + } + } + + void decodeClass(String fileName) throws IOException { + // Read the header + try { + int magic = in.readInt(); + int min_version = in.readUnsignedShort(); + int version = in.readUnsignedShort(); + + // Read the constant pool + readCP(in); + short access = in.readShort(); // don't care about sign + int this_cpx = in.readUnsignedShort(); + + try { + entityName = (String) cpool[(Integer) cpool[this_cpx]]; + if (entityName.equals("module-info")) { + entityType = "module"; + entityName = ""; + } else { + entityType = "class"; + } + if (!entityName.isEmpty() && (JcodTokens.keyword_token_ident(entityName) != JcodTokens.Token.IDENT || JcodTokens.constValue(entityName) != -1)) { + // JCod can't parse a entityName matching a keyword or a constant value, + // then use the filename instead: + out_begin(String.format("file \"%s.class\" {", entityName)); + } else { + out_begin(format("%s %s {", entityType, entityName)); + } + } catch (Exception e) { + entityName = fileName; + out.println("// " + e.getMessage() + " while accessing entityName"); + out_begin(format("%s %s { // source file name", entityType, entityName)); + } + + out_print(toHex(magic, 4) + ";"); + if (magic != JAVA_MAGIC) { + out.print(" // wrong magic: 0x" + Integer.toString(JAVA_MAGIC, 16) + " expected"); + } + out.println(); + out_println(min_version + "; // minor version"); + out_println(version + "; // version"); + + // Print the constant pool + printCP(out); + out_println(toHex(access, 2) + "; // access" + + (printDetails ? " [" + (" " + Modifiers.accessString(access, CF_Context.CTX_CLASS).toUpperCase()).replaceAll(" (\\S)", " ACC_$1") + "]" : "")); + out_println("#" + this_cpx + ";// this_cpx"); + int super_cpx = in.readUnsignedShort(); + out_println("#" + super_cpx + ";// super_cpx"); + traceln(i18n.getString("jdec.trace.access_thisCpx_superCpx", access, this_cpx, super_cpx)); + out.println(); + + // Read the interfaces + int numinterfaces = in.readUnsignedShort(); + traceln(i18n.getString("jdec.trace.numinterfaces", numinterfaces)); + startArrayCmt(numinterfaces, "Interfaces"); + try { + decodeTypes(in, out, numinterfaces); + } finally { + out_end("} // Interfaces\n"); + } + // Read the fields + decodeMembers(in, out, "Fields", "field"); + + // Read the methods + decodeMembers(in, out, "Methods", "method"); + + // Read the attributes + decodeAttrs(in, out); + } catch (EOFException ignored) { + } catch (ClassFormatError err) { + String msg = err.getMessage(); + out.println("//------- ClassFormatError" + + (msg == null || msg.isEmpty() ? "" : ": " + msg)); + printRestOfBytes(); + } finally { + out_end(format("} // end %s %s", entityType, entityName)); + } + } // end decodeClass() + + private void decodeTypes(DataInputStream in, PrintWriter out, int count) throws IOException { + for (int i = 0; i < count; i++) { + int type_cpx = in.readUnsignedShort(); + traceln(i18n.getString("jdec.trace.type", i, type_cpx)); + out_print("#" + type_cpx + ";"); + if (printDetails) { + String name = (String) cpool[(int)cpool[type_cpx]]; + out.println(" // " + name + getStringPos()); + } else { + out.println(); + } + } + } + + /* ====================================================== */ + boolean DebugFlag = false; + + public void trace(String s) { + if (!DebugFlag) { + return; + } + System.out.print(s); + } + + public void traceln(String s) { + if (!DebugFlag) { + return; + } + System.out.println(s); + } +}// end class ClassData diff --git a/test/lib/org/openjdk/asmtools/jdec/Main.java b/test/lib/org/openjdk/asmtools/jdec/Main.java new file mode 100644 index 00000000000..e5dc8d5b7d7 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdec/Main.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2009, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdec; + +import org.openjdk.asmtools.common.Tool; +import org.openjdk.asmtools.jdis.uEscWriter; +import org.openjdk.asmtools.util.I18NResourceBundle; +import org.openjdk.asmtools.util.ProductInfo; + +import java.io.DataInputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Main program of the Java DECoder :: class to jcod + */ +public class Main extends Tool { + + int printFlags = 0; + + public static final I18NResourceBundle i18n + = I18NResourceBundle.getBundleForClass(Main.class); + + public Main(PrintWriter out, PrintWriter err, String programName) { + super(out, err, programName); + printCannotReadMsg = (fname) -> + error( i18n.getString("jdec.error.cannot_read", fname)); + } + + public Main(PrintStream out, String program) { + this(new PrintWriter(out), new PrintWriter(System.err), program); + } + + @Override + public void usage() { + println(i18n.getString("jdec.usage")); + println(i18n.getString("jdec.opt.g")); + println(i18n.getString("jdec.opt.version")); + } + + /** + * Run the decoder + */ + public synchronized boolean decode(String argv[]) { + long tm = System.currentTimeMillis(); + ArrayList vargs = new ArrayList<>(); + ArrayList vj = new ArrayList<>(); + boolean nowrite = false; + int addOptions = 0; + + // Parse arguments + int i = 0; + for (String arg : argv) { + // + if (arg.equals("-g")) { + printFlags = printFlags | 1; + vargs.add(arg); + } else if (arg.equals("-v")) { + DebugFlag = () -> true; + vargs.add(arg); + out.println("arg[" + i + "]=" + argv[i] + "/verbose"); + } else if (arg.equals("-version")) { + out.println(ProductInfo.FULL_VERSION); + } else if (arg.startsWith("-")) { + error(i18n.getString("jdec.error.invalid_flag", arg)); + usage(); + return false; + } else { + vargs.add(arg); + vj.add(arg); + } + i += 1; + } + + if (vj.isEmpty()) { + usage(); + return false; + } + + String[] names = new String[0]; + names = vj.toArray(names); + for (String inpname : names) { + try { + DataInputStream dataInputStream = getDataInputStream(inpname); + if( dataInputStream == null ) + return false; + ClassData cc = new ClassData(dataInputStream, printFlags, out); + cc.DebugFlag = DebugFlag.getAsBoolean(); + cc.decodeClass(inpname); + out.flush(); + continue; + } catch (Error ee) { + if (DebugFlag.getAsBoolean()) + ee.printStackTrace(); + error(i18n.getString("jdec.error.fatal_error")); + } catch (Exception ee) { + if (DebugFlag.getAsBoolean()) + ee.printStackTrace(); + error(i18n.getString("jdec.error.fatal_exception")); + } + return false; + } + return true; + } + + /** + * Main program + */ + public static void main(String argv[]) { + Main decoder = new Main(new PrintWriter(new uEscWriter(System.out)), new PrintWriter(System.err), "jdec"); + System.exit(decoder.decode(argv) ? 0 : 1); + } +} diff --git a/test/lib/org/openjdk/asmtools/jdec/NestedByteArrayInputStream.java b/test/lib/org/openjdk/asmtools/jdec/NestedByteArrayInputStream.java new file mode 100644 index 00000000000..c8e86d87e6e --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdec/NestedByteArrayInputStream.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2009, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdec; + +import java.io.ByteArrayInputStream; +import java.util.Stack; + +/** + * this class provides functionality needed to read class files: + *

      + *
    • methods to read unsigned integers of various length + *
    • counts bytes read so far + *
    + */ +public class NestedByteArrayInputStream extends ByteArrayInputStream { + + NestedByteArrayInputStream(byte buf[]) { + super(buf); + } + + NestedByteArrayInputStream(byte buf[], int offset, int length) { + super(buf, offset, length); + } + + public int getPos() { + return pos; + } + Stack savedStates = new Stack(); + + public void enter(int range) { + savedStates.push(count); + if (pos + range < count) { + count = pos + range; + } + } + + public void leave() { + pos = count; + count = ((Integer) savedStates.pop()).intValue(); + } +} // end class NestedByteArrayInputStream + diff --git a/test/lib/org/openjdk/asmtools/jdec/i18n.properties b/test/lib/org/openjdk/asmtools/jdec/i18n.properties new file mode 100644 index 00000000000..a27299c6e75 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdec/i18n.properties @@ -0,0 +1,41 @@ +# Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. + +jdec.error.invalid_flag=invalid flag: " {0} +jdec.error.fatal_error=fatal error +jdec.error.fatal_exception=fatal exception +jdec.error.cannot_read=cannot read {0} +jdec.usage=\ +Usage: java -jar asmtools.jar jdec [options] FILE.class... > FILE.jcod\n\ +where possible options include: + +jdec.opt.g=\ +\ -g: detailed output format +jdec.opt.version=\ +\ -version: print version number and date + + +jdec.trace.CP_len=CP len= {0} +jdec.trace.CP_entry=CP entry # {0} tag= {1} +jdec.trace.access_thisCpx_superCpx=access={0} this_cpx={1} super_cpx={2} +jdec.trace.numinterfaces=numinterfaces={0} +jdec.trace.type=\ +\ type_cpx[{0}]={1} diff --git a/test/lib/org/openjdk/asmtools/jdis/AnnotationData.java b/test/lib/org/openjdk/asmtools/jdis/AnnotationData.java new file mode 100644 index 00000000000..d21a2b505a6 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/AnnotationData.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; + +import static java.lang.String.format; + +/** + * + */ +public class AnnotationData { + /*-------------------------------------------------------- */ + /* AnnotData Fields */ + + protected String visAnnotToken = "@+"; + protected String invAnnotToken = "@-"; + protected String dataName = "AnnotationData"; + private boolean invisible = false; + private int type_cpx = 0; //an index into the constant pool indicating the annotation type for this annotation. + private ArrayList array = new ArrayList<>(); + private ClassData cls; + /*-------------------------------------------------------- */ + + public AnnotationData(boolean invisible, ClassData cls) { + this.cls = cls; + this.invisible = invisible; + } + + public void read(DataInputStream in) throws IOException { + type_cpx = in.readShort(); + int elemValueLength = in.readShort(); + TraceUtils.traceln(3, format(" %s: name[%d]=%s", dataName, type_cpx, cls.pool.getString(type_cpx)), + format(" %s: %s num_elems: %d", dataName, cls.pool.getString(type_cpx), elemValueLength)); + for (int evc = 0; evc < elemValueLength; evc++) { + AnnotationElement elem = new AnnotationElement(cls); + TraceUtils.traceln(3, format(" %s: %s reading [%d]", dataName, cls.pool.getString(type_cpx), evc)); + elem.read(in, invisible); + array.add(elem); + } + } + + public void print(PrintWriter out, String tab) { + printHeader(out, tab); + printBody(out, ""); + } + + protected void printHeader(PrintWriter out, String tab) { + //Print annotation Header, which consists of the + // Annotation Token ('@'), visibility ('+', '-'), + // and the annotation name (type index, CPX). + + // Mark whether it is invisible or not. + if (invisible) { + out.print(tab + invAnnotToken); + } else { + out.print(tab + visAnnotToken); + } + String annoName = cls.pool.getString(type_cpx); + + // converts class type to java class name + if (annoName.startsWith("L") && annoName.endsWith(";")) { + annoName = annoName.substring(1, annoName.length() - 1); + } + + out.print(annoName); + } + + protected void printBody(PrintWriter out, String tab) { + // For a standard annotation, print out brackets, + // and list the name/value pairs. + out.print(" { "); + int i = 0; + for (AnnotationElement elem : array) { + elem.print(out, tab); + if (i++ < array.size() - 1) { + out.print(", "); + } + } + out.print(" }"); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + String annoName = cls.pool.getString(type_cpx); + + // converts class type to java class name + if (annoName.startsWith("L") && annoName.endsWith(";")) { + annoName = annoName.substring(1, annoName.length() - 1); + } + + //Print annotation + // Mark whether it is invisible or not. + if (invisible) { + sb.append(invAnnotToken); + } else { + sb.append(visAnnotToken); + } + + sb.append(annoName); + sb.append(" { "); + + int i = 0; + for (AnnotationElement elem : array) { + sb.append(elem.toString()); + + if (i++ < array.size() - 1) { + sb.append(", "); + } + } + + _toString(sb); + + sb.append("}"); + return sb.toString(); + } + + protected void _toString(StringBuilder sb) { + // sub-classes override this + } +} + diff --git a/test/lib/org/openjdk/asmtools/jdis/AnnotationElement.java b/test/lib/org/openjdk/asmtools/jdis/AnnotationElement.java new file mode 100644 index 00000000000..c47492bd103 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/AnnotationElement.java @@ -0,0 +1,390 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import static java.lang.String.format; +import static org.openjdk.asmtools.jasm.Tables.*; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Base class of all AnnotationElement entries + */ +public class AnnotationElement { + + /** + * + * CPX_AnnotElem + * + * base class for an annotation value. + * + */ + public static class AnnotValue { + + /** + * tag the descriptor for the constant + */ + public AnnotElemType tag; + + // internal references + protected ClassData cls; + + public AnnotValue(AnnotElemType tagval, ClassData cls) { + tag = tagval; + this.cls = cls; + } + + public String stringVal() { + return ""; + } + + public void print(PrintWriter out, String tab) { + out.print(tag.val() + " "); + } + + @Override + public String toString() { + return ""; + } + } + + /** + * + * CPX_AnnotElem + * + * Annotation value which is described by a single CPX entry (ie. String, byte, char, + * int, short, boolean, float, long, double, class reference). + * + */ + public static class CPX_AnnotValue extends AnnotValue { + + /** + * tag the descriptor for the constant + */ + public int cpx; + + public CPX_AnnotValue(AnnotElemType tag, ClassData cls, int cpx) { + super(tag, cls); + this.cpx = cpx; + } + + @Override + public String stringVal() { + StringBuilder sb = new StringBuilder(); + switch (tag) { + case AE_STRING: // String + sb.append('"' + cls.pool.getString(cpx) + '"'); + break; + case AE_BYTE: // Byte + sb.append("byte " + cls.pool.getConst(cpx).stringVal()); + break; + case AE_CHAR: // Char + sb.append("char " + cls.pool.getConst(cpx).stringVal()); + break; + case AE_INT: // Int (no need to add keyword) + sb.append(cls.pool.getConst(cpx).stringVal()); + break; + case AE_SHORT: // Short + sb.append("short " + cls.pool.getConst(cpx).stringVal()); + break; + case AE_BOOLEAN: // Boolean + ConstantPool.CP_Int cns = (ConstantPool.CP_Int) cls.pool.getConst(cpx); + sb.append("boolean " + (cns.value == 0 ? "false" : "true")); + break; + case AE_FLOAT: // Float + sb.append(cls.pool.getConst(cpx).stringVal()); // + "f"); + break; + case AE_DOUBLE: // Double + sb.append(cls.pool.getConst(cpx).stringVal()); // + "d"); + break; + case AE_LONG: // Long + sb.append(cls.pool.getConst(cpx).stringVal()); // + "l"); + break; + case AE_CLASS: // Class + sb.append("class " + cls.pool.decodeClassDescriptor(cpx)); + break; + default: + break; + } + return sb.toString(); + } + + @Override + public void print(PrintWriter out, String tab) { + out.print(tab + stringVal()); + } + + @Override + public String toString() { + return ""; + } + } + + /** + * + * CPX_AnnotElem + * + * AnnotElements that contain 2 cpx indices (ie. enums). + * + */ + public static class CPX2_AnnotValue extends AnnotValue { + + /** + * tag the descriptor for the constant + */ + public int cpx1; + public int cpx2; + + public CPX2_AnnotValue(AnnotElemType tag, ClassData cls, int cpx1, int cpx2) { + super(tag, cls); + this.cpx1 = cpx1; + this.cpx2 = cpx2; + } + + @Override + public String stringVal() { + StringBuilder sb = new StringBuilder(); + switch (tag) { + + case AE_ENUM: // Enum + // print the enum type and constant name + sb.append("enum " + cls.pool.decodeClassDescriptor(cpx1) + + " " + cls.pool.getName(cpx2)); + break; + default: + break; + } + return sb.toString(); + } + + @Override + public void print(PrintWriter out, String tab) { + out.print(tab + stringVal()); + } + + @Override + public String toString() { + return ""; + } + } + + /** + * + * Array_AnnotElem + * + * Annotation value that is an array of annotation elements. + * + */ + public static class Array_AnnotValue extends AnnotValue { + + /** + * tag the descriptor for the constant + */ + public ArrayList array = new ArrayList<>(); + + public Array_AnnotValue(AnnotElemType tagval, ClassData cls) { + super(tagval, cls); + } + + @Override + public String stringVal() { + StringBuilder sb = new StringBuilder(); + sb.append(super.stringVal() + " = "); + sb.append("{"); + int i = 0; + int cnt = array.size(); + for (AnnotValue arrayelem : array) { + sb.append(arrayelem.toString()); + if (i < cnt - 1) { + sb.append(","); + } + } + sb.append("}"); + return sb.toString(); + } + + public void add(AnnotValue elem) { + array.add(elem); + } + + @Override + public void print(PrintWriter out, String tab) { + out.println("{"); + int i = 0; + int cnt = array.size(); + for (AnnotValue arrayelem : array) { + arrayelem.print(out, tab + " "); + if (i < cnt - 1) { + out.println(","); + } + i += 1; + } + out.println("}"); + } + + @Override + public String toString() { + return ""; + } + } + + /** + * + * Annot_AnnotValue + * + * Annotation value that is a reference to an annotation. + * + */ + public static class Annot_AnnotValue extends AnnotValue { + + /** + * tag the descriptor for the constant + */ + AnnotationData annot; + + public Annot_AnnotValue(AnnotElemType tagval, ClassData cls, AnnotationData annot) { + super(tagval, cls); + this.annot = annot; + } + + @Override + public String stringVal() { + return annot.toString(); + } + + @Override + public void print(PrintWriter out, String tab) { +// out.print(tag + "\t"); + annot.print(out, tab); + } + + @Override + public String toString() { + return ""; + } + } + + /*========================================================*/ + /* Factory Method */ + /** + * + * read + * + * Static factory - creates Annotation Elements. + * + */ + public static AnnotValue readValue(DataInputStream in, ClassData cls, boolean invisible) throws IOException { + AnnotValue val = null; + char tg = (char) in.readByte(); + AnnotElemType tag = annotElemType(tg); + + switch (tag) { + case AE_STRING: // String + case AE_BYTE: // Byte + case AE_CHAR: // Char + case AE_INT: // Int (no need to add keyword) + case AE_SHORT: // Short + case AE_BOOLEAN: // Boolean + case AE_FLOAT: // Float + case AE_DOUBLE: // Double + case AE_LONG: // Long + case AE_CLASS: // Class + // CPX based Annotation + int CPX = in.readShort(); + val = new CPX_AnnotValue(tag, cls, CPX); + break; + case AE_ENUM: // Enum + // CPX2 based Annotation + int CPX1 = in.readShort(); + int CPX2 = in.readShort(); + val = new CPX2_AnnotValue(tag, cls, CPX1, CPX2); + break; + case AE_ANNOTATION: // Annotation + AnnotationData ad = new AnnotationData(invisible, cls); + ad.read(in); + val = new Annot_AnnotValue(tag, cls, ad); + break; + case AE_ARRAY: // Array + Array_AnnotValue aelem = new Array_AnnotValue(tag, cls); + val = aelem; + int cnt = in.readShort(); + for (int i = 0; i < cnt; i++) { + aelem.add(readValue(in, cls, invisible)); + } + break; + default: + throw new IOException("Unknown tag in annotation '" + tg + "' [" + Integer.toHexString(tg) + "]"); + } + + return val; + } + + /*========================================================*/ + + /*-------------------------------------------------------- */ + /* AnnotElem Fields */ + /** + * constant pool index for the name of the Annotation Element + */ + public int name_cpx; + + public AnnotValue value = null; + + // internal references + protected ClassData cls; + /*-------------------------------------------------------- */ + + public AnnotationElement(ClassData cls) { + this.cls = cls; + } + + /** + * + * read + * + * read and resolve the method data called from ClassData. precondition: NumFields has + * already been read from the stream. + * + */ + public void read(DataInputStream in, boolean invisible) throws IOException { + name_cpx = in.readShort(); + value = readValue(in, cls, invisible); + TraceUtils.traceln(format(" AnnotElem: name[%d]=%s value=%s", name_cpx, cls.pool.getString(name_cpx), value.toString())); + } + + public String stringVal() { + return cls.pool.getName(name_cpx); + } + + public void print(PrintWriter out, String tab) { + out.print(stringVal() + " = "); + value.print(out, ""); + } + + @Override + public String toString() { + return ""; + } +} diff --git a/test/lib/org/openjdk/asmtools/jdis/AttrData.java b/test/lib/org/openjdk/asmtools/jdis/AttrData.java new file mode 100644 index 00000000000..b94ed013d11 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/AttrData.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import org.openjdk.asmtools.jasm.Tables; + +import java.io.DataInputStream; +import java.io.IOException; + +import static java.lang.String.format; + +/** + * + */ +public class AttrData { + + int name_cpx; + byte data[]; + ClassData cls; + public AttrData(ClassData cls) { + this.cls = cls; + } + + /** + * attributeTag + *

    + * returns either -1 (not found), or the hashed integer tag tag. + */ + public static int attributeTag(String tagname) { + int intgr = Tables.attrtagValue(tagname); + + if (intgr == 0) { + return -1; + } + + return intgr; + } + + public void read(int name_cpx, int attrlen, DataInputStream in) throws IOException { + this.name_cpx = name_cpx; + data = new byte[attrlen]; + TraceUtils.traceln(format("AttrData:#%d len=%d", name_cpx, attrlen)); + in.readFully(data); + } +} diff --git a/test/lib/org/openjdk/asmtools/jdis/BootstrapMethodData.java b/test/lib/org/openjdk/asmtools/jdis/BootstrapMethodData.java new file mode 100644 index 00000000000..67d2d6165e1 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/BootstrapMethodData.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import org.openjdk.asmtools.jasm.JasmTokens; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * + */ +public class BootstrapMethodData extends Indenter { + + int bsm_index; + ArrayList bsm_args_indexes; + + // internal references + private Options options = Options.OptionObject(); + private ClassData cls; + private PrintWriter out; + + public BootstrapMethodData(ClassData cls) { + this.cls = cls; + out = cls.out; + } + + + /*========================================================*/ + /* Read Methods */ + + /** + * + * read + * + * read and resolve the bootstrap method data called from ClassData. precondition: + * NumFields has already been read from the stream. + * + */ + public void read(DataInputStream in) throws IOException { + // read the Methods CP indexes + bsm_index = in.readUnsignedShort(); + int arg_num = in.readUnsignedShort(); + bsm_args_indexes = new ArrayList<>(arg_num); + for (int i = 0; i < arg_num; i++) { + bsm_args_indexes.add(in.readUnsignedShort()); + } + } + + + /*========================================================*/ + /* Print Methods */ + public void print() throws IOException { + out.print(getIndentString() + JasmTokens.Token.BOOTSTRAPMETHOD.parseKey() + " #" + bsm_index); + for (int i = 0; i < bsm_args_indexes.size(); i++) { + out.print(" #" + bsm_args_indexes.get(i)); + } + out.println(";"); + } +} diff --git a/test/lib/org/openjdk/asmtools/jdis/ClassArrayData.java b/test/lib/org/openjdk/asmtools/jdis/ClassArrayData.java new file mode 100644 index 00000000000..5b73fff72c0 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/ClassArrayData.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import java.io.DataInputStream; +import java.io.IOException; + +/** + * Base class of the "classes[]" data of attributes + *

    + * JEP 181 (Nest-based Access Control): class file 55.0 + * NestMembers_attribute { + * u2 attribute_name_index; + * u4 attribute_length; + * u2 number_of_classes; + * u2 classes[number_of_classes]; + * } + *

    + * JEP 360 (Sealed types): class file 59.65535 + * PermittedSubclasses_attribute { + * u2 attribute_name_index; + * u4 attribute_length; + * u2 number_of_classes; + * u2 classes[number_of_classes]; + * } + *

    + */ +public class ClassArrayData extends Indenter { + String name; + ClassData cls; + int[] classes; + private Options options = Options.OptionObject(); + + protected ClassArrayData(ClassData cls, String attrName) { + this.cls = cls; + this.name = attrName; + } + + public ClassArrayData read(DataInputStream in, int attribute_length) throws IOException, ClassFormatError { + int number_of_classes = in.readUnsignedShort(); + if (attribute_length != 2 + number_of_classes * 2) { + throw new ClassFormatError(name + "_attribute: Invalid attribute length"); + } + classes = new int[number_of_classes]; + for (int i = 0; i < number_of_classes; i++) { + classes[i] = in.readUnsignedShort(); + } + return this; + } + + public void print() { + String indexes = ""; + String names = ""; + boolean pr_cpx = options.contains(Options.PR.CPX); + cls.out.print(getIndentString() + name + " "); + for (int i = 0; i < classes.length; i++) { + if (pr_cpx) { + indexes += (indexes.isEmpty() ? "" : ", ") + "#" + classes[i]; + } + names += (names.isEmpty() ? "" : ", ") + cls.pool.StringValue(classes[i]); + } + if (pr_cpx) { + cls.out.print(indexes + "; // "); + } + cls.out.print(names); + if (pr_cpx) { + cls.out.println(); + } else { + cls.out.println(";"); + } + } + +} diff --git a/test/lib/org/openjdk/asmtools/jdis/ClassData.java b/test/lib/org/openjdk/asmtools/jdis/ClassData.java new file mode 100644 index 00000000000..a748e4c4f89 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/ClassData.java @@ -0,0 +1,539 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import org.openjdk.asmtools.asmutils.HexUtils; +import org.openjdk.asmtools.common.Tool; +import org.openjdk.asmtools.jasm.Modifiers; + +import java.io.*; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static java.lang.String.format; +import static org.openjdk.asmtools.jasm.RuntimeConstants.*; +import static org.openjdk.asmtools.jasm.Tables.*; + +/** + * Central class data for of the Java Disassembler + */ +public class ClassData extends MemberData { + + // Owner of this ClassData + protected Tool tool; + + // ----------------------------- + // Header Info + // ----------------------------- + // Version info + protected int minor_version, major_version; + + // Constant Pool index to this class + protected int this_cpx; + + // Constant Pool index to this classes parent (super) + protected int super_cpx; + + // Constant Pool index to a file reference to the Java source + protected int source_cpx = 0; + + // ----------------------------- + // The Constant Pool + // ----------------------------- + protected ConstantPool pool; + + // ----------------------------- + // Interfaces,Fields,Methods && Attributes + // ----------------------------- + // The interfaces this class implements + protected int[] interfaces; + + // The fields of this class + protected ArrayList fields; + + // The methods of this class + protected ArrayList methods; + + // The record attribute of this class (since class file 58.65535) + protected RecordData record; + + // The inner-classes of this class + protected ArrayList innerClasses; + + // The bootstrapmethods this class implements + protected ArrayList bootstrapMethods; + + //The module this class file presents + protected ModuleData moduleData; + + // The NestHost of this class (since class file: 55.0) + protected NestHostData nestHost; + + // The NestMembers of this class (since class file: 55.0) + protected NestMembersData nestMembers; + + // The PermittedSubclasses of this class (JEP 360 (Sealed types): class file 59.65535) + protected PermittedSubclassesData permittedSubclassesData; + + protected PreloadData preloadData; + + // other parsing fields + protected PrintWriter out; + protected String pkgPrefix = ""; + // source file data + private TextLines sourceLines = null; + private Path classFile = null; + + public ClassData(PrintWriter out, Tool tool) { + this.out = out; + this.tool = tool; + memberType = "ClassData"; + TraceUtils.traceln("printOptions=" + options.toString()); + pool = new ConstantPool(this); + init(this); + } + + public void read(File in) throws IOException { + try ( DataInputStream dis = new DataInputStream(new FileInputStream(in))){ + read(dis); + } + classFile = in.toPath(); + } + + public void read(String in) throws IOException { + try ( DataInputStream dis = new DataInputStream(new FileInputStream(in))){ + read(dis); + } + classFile = Paths.get(in); + } + + /** + * Read and resolve the field data + */ + protected void readFields(DataInputStream in) throws IOException { + int nfields = in.readUnsignedShort(); + TraceUtils.traceln("nfields=" + nfields); + fields = new ArrayList<>(nfields); + for (int k = 0; k < nfields; k++) { + FieldData field = new FieldData(this); + TraceUtils.traceln(" FieldData: #" + k); + field.read(in); + fields.add(field); + } + } + + /** + * Read and resolve the method data + */ + protected void readMethods(DataInputStream in) throws IOException { + int nmethods = in.readUnsignedShort(); + TraceUtils.traceln("nmethods=" + nmethods); + methods = new ArrayList<>(nmethods); + for (int k = 0; k < nmethods; k++) { + MethodData method = new MethodData(this); + TraceUtils.traceln(" MethodData: #" + k); + method.read(in); + methods.add(method); + } + } + + /** + * Read and resolve the interface data + */ + protected void readInterfaces(DataInputStream in) throws IOException { + // Read the interface names + int numinterfaces = in.readUnsignedShort(); + TraceUtils.traceln("numinterfaces=" + numinterfaces); + interfaces = new int[numinterfaces]; + for (int i = 0; i < numinterfaces; i++) { + int intrf_cpx = in.readShort(); + TraceUtils.traceln(" intrf_cpx[" + i + "]=" + intrf_cpx); + interfaces[i] = intrf_cpx; + } + } + + /** + * Read and resolve the attribute data + */ + @Override + protected boolean handleAttributes(DataInputStream in, AttrTag attrtag, int attrlen) throws IOException { + // Read the Attributes + boolean handled = true; + switch (attrtag) { + case ATT_SourceFile: + // Read SourceFile Attr + if (attrlen != 2) { + throw new ClassFormatError("ATT_SourceFile: Invalid attribute length"); + } + source_cpx = in.readUnsignedShort(); + break; + case ATT_InnerClasses: + // Read InnerClasses Attr + int num1 = in.readUnsignedShort(); + if (2 + num1 * 8 != attrlen) { + throw new ClassFormatError("ATT_InnerClasses: Invalid attribute length"); + } + innerClasses = new ArrayList<>(num1); + for (int j = 0; j < num1; j++) { + InnerClassData innerClass = new InnerClassData(this); + innerClass.read(in); + innerClasses.add(innerClass); + } + break; + case ATT_BootstrapMethods: + // Read BootstrapMethods Attr + int num2 = in.readUnsignedShort(); + bootstrapMethods = new ArrayList<>(num2); + for (int j = 0; j < num2; j++) { + BootstrapMethodData bsmData = new BootstrapMethodData(this); + bsmData.read(in); + bootstrapMethods.add(bsmData); + } + break; + case ATT_Module: + // Read Module Attribute + moduleData = new ModuleData(this); + moduleData.read(in); + break; + case ATT_NestHost: + // Read NestHost Attribute (since class file: 55.0) + nestHost = new NestHostData(this).read(in, attrlen); + break; + case ATT_NestMembers: + // Read NestMembers Attribute (since class file: 55.0) + nestMembers = new NestMembersData(this).read(in, attrlen); + break; + case ATT_Record: + record = new RecordData(this).read(in); + break; + case ATT_PermittedSubclasses: + // Read PermittedSubclasses Attribute (JEP 360 (Sealed types): class file 59.65535) + permittedSubclassesData = new PermittedSubclassesData(this).read(in, attrlen); + break; + case ATT_Preload: + preloadData = new PreloadData(this).read(in, attrlen); + break; + default: + handled = false; + break; + } + return handled; + } + + /** + * Read and resolve the class data + */ + private void read(DataInputStream in) throws IOException { + // Read the header + int magic = in.readInt(); + if (magic != JAVA_MAGIC) { + throw new ClassFormatError("wrong magic: " + HexUtils.toHex(magic) + ", expected " + HexUtils.toHex(JAVA_MAGIC)); + } + minor_version = in.readUnsignedShort(); + major_version = in.readUnsignedShort(); + + // Read the constant pool + pool.read(in); + access = in.readUnsignedShort(); // & MM_CLASS; // Q + this_cpx = in.readUnsignedShort(); + super_cpx = in.readUnsignedShort(); + TraceUtils.traceln("access=" + access + " " + Modifiers.accessString(access, CF_Context.CTX_INNERCLASS) + + " this_cpx=" + this_cpx + + " super_cpx=" + super_cpx); + + // Read the interfaces + readInterfaces(in); + + // Read the fields + readFields(in); + + // Read the methods + readMethods(in); + + // Read the attributes + readAttributes(in); + // + TraceUtils.traceln("", "<< Reading is done >>", ""); + } + + /** + * Read and resolve the attribute data + */ + public String getSrcLine(int lnum) { + if (sourceLines == null) { + return null; // impossible call + } + String line; + try { + line = sourceLines.getLine(lnum); + } catch (ArrayIndexOutOfBoundsException e) { + line = "Line number " + lnum + " is out of bounds"; + } + return line; + } + + private void printAnnotations(List annotations) { + if (annotations != null) { + for (T ad : annotations) { + ad.print(out, ""); + out.println(); + } + } + } + + @Override + public void print() throws IOException { + int k, l; + String className = ""; + String sourceName = null; + if( isModuleUnit() ) { + // Print the Annotations + printAnnotations(visibleAnnotations); + printAnnotations(invisibleAnnotations); + } else { + className = pool.getClassName(this_cpx); + int pkgPrefixLen = className.lastIndexOf("/") + 1; + // Write the header + // package-info compilation unit + if (className.endsWith("package-info")) { + // Print the Annotations + printAnnotations(visibleAnnotations); + printAnnotations(invisibleAnnotations); + printAnnotations(visibleTypeAnnotations); + printAnnotations(invisibleTypeAnnotations); + if (pkgPrefixLen != 0) { + pkgPrefix = className.substring(0, pkgPrefixLen); + out.print("package " + pkgPrefix.substring(0, pkgPrefixLen - 1) + " "); + out.print("version " + major_version + ":" + minor_version + ";"); + } + out.println(); + return; + } + if (pkgPrefixLen != 0) { + pkgPrefix = className.substring(0, pkgPrefixLen); + out.println("package " + pkgPrefix.substring(0, pkgPrefixLen - 1) + ";"); + className = pool.getShortClassName(this_cpx, pkgPrefix); + } + out.println(); + // Print the Annotations + printAnnotations(visibleAnnotations); + printAnnotations(invisibleAnnotations); + printAnnotations(visibleTypeAnnotations); + printAnnotations(invisibleTypeAnnotations); + if ((access & ACC_SUPER) != 0) { + out.print("super "); + access = access & ~ACC_SUPER; + } + } +// see if we are going to print: abstract interface class +// then replace it with just: interface +printHeader: + { +printSugar: + { + if ((access & ACC_ABSTRACT) == 0) { + break printSugar; + } + if ((access & ACC_INTERFACE) == 0) { + break printSugar; + } + if (options.contains(Options.PR.CPX)) { + break printSugar; + } + if (this_cpx == 0) { + break printSugar; + } + + // make sure the this_class is a valid class ref + ConstantPool.Constant this_const = pool.getConst(this_cpx); + if (this_const == null || this_const.tag != ConstantPool.TAG.CONSTANT_CLASS) { + break printSugar; + } + + // all conditions met, print syntactic sugar: + out.print(Modifiers.accessString(access & ~ACC_ABSTRACT, CF_Context.CTX_CLASS)); + if (isSynthetic) { + out.print("synthetic "); + } + if (isDeprecated) { + out.print("deprecated "); + } + out.print(" " + pool.getShortClassName(this_cpx, pkgPrefix)); + break printHeader; + } + + if(isModuleUnit()) { + out.print(moduleData.getModuleHeader()); + } else { + // not all conditions met, print header in ordinary way: + out.print(Modifiers.accessString(access, CF_Context.CTX_CLASS)); + if (isSynthetic) { + out.print("synthetic "); + } + if (isDeprecated) { + out.print("deprecated "); + } + if (options.contains(Options.PR.CPX)) { + out.print("\t#" + this_cpx + " //"); + } + pool.PrintConstant(out, this_cpx); + } + } + out.println(); + if(!isModuleUnit()) { + if (!pool.getClassName(super_cpx).equals("java/lang/Object")) { + out.print("\textends "); + pool.printlnClassId(out, super_cpx); + out.println(); + } + } + l = interfaces.length; + + if (l > 0) { + for (k = 0; k < l; k++) { + if (k == 0) { + out.print("\timplements "); + } else { + out.print("\t\t "); + } + boolean printComma = (l > 1 && k < (l - 1)); + pool.printlnClassId(out, interfaces[k], printComma); + out.println(); + } + } + out.println("\tversion " + major_version + ":" + minor_version); + out.println("{"); + + if ((options.contains(Options.PR.SRC)) && (source_cpx != 0)) { + sourceName = pool.getString(source_cpx); + if (sourceName != null) { + sourceLines = new TextLines(classFile.getParent(), sourceName); + } + } + + // Print the constant pool + if (options.contains(Options.PR.CP)) { + pool.print(out); + } + // Don't print fields, methods, inner classes and bootstrap methods if it is module-info entity + if ( !isModuleUnit() ) { + + // Print the fields + printMemberDataList(fields); + + // Print the methods + printMemberDataList(methods); + + // Print the Record (since class file 58.65535 JEP 359) + if( record != null ) { + record.print(); + } + + // Print PermittedSubclasses Attribute (JEP 360 (Sealed types): class file 59.65535) + if( permittedSubclassesData != null) { + permittedSubclassesData.print(); + } + // Print the NestHost (since class file: 55.0) + if(nestHost != null) { + nestHost.print(); + } + // Print the NestMembers (since class file: 55.0) + if( nestMembers != null) { + nestMembers.print(); + } + // Print the inner classes + if (innerClasses != null && !innerClasses.isEmpty()) { + for (InnerClassData icd : innerClasses) { + icd.print(); + } + out.println(); + } + + // Print Preload attribute + if (preloadData != null) { + preloadData.print(); + } + + // Print the BootstrapMethods + // + // Only print these if printing extended constants + if ((options.contains(Options.PR.CPX)) && bootstrapMethods != null && !bootstrapMethods.isEmpty()) { + for (BootstrapMethodData bsmdd : bootstrapMethods) { + bsmdd.print(); + } + out.println(); + } + out.println(format("} // end Class %s%s", + className, + sourceName != null ? " compiled from \"" + sourceName +"\"" : "")); + } else { + // Print module attributes + moduleData.print(); + out.print("} // end Module "); + out.print( moduleData.getModuleName()); + if(moduleData.getModuleVersion() != null) + out.print(" @" + moduleData.getModuleVersion()); + out.println(); + } + + List issues = getIssues(); + if( !issues.isEmpty() ) { + + throw issues.get(0); + } + } // end ClassData.print() + + // Gets the type of processed binary + private boolean isModuleUnit() { + return moduleData != null; + } + + private void printMemberDataList( List list) throws IOException { + if( list != null ) { + int count = list.size(); + if( count > 0 ) { + for( int i=0; i < count; i++ ) { + MemberData md = list.get(i); + md.setIndent(Options.BODY_INDENT); + if( i !=0 && md.getAnnotationsCount() > 0 ) + out.println(); + md.print(); + } + out.println(); + } + } + } + + private List getIssues() { + return this.pool.pool.stream(). + filter(Objects::nonNull). + filter(c->c.getIssue() != null). + map(ConstantPool.Constant::getIssue). + collect(Collectors.toList()); + } + +}// end class ClassData diff --git a/test/lib/org/openjdk/asmtools/jdis/CodeData.java b/test/lib/org/openjdk/asmtools/jdis/CodeData.java new file mode 100644 index 00000000000..155c334def2 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/CodeData.java @@ -0,0 +1,745 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import org.openjdk.asmtools.jasm.Tables; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; + +import static org.openjdk.asmtools.jasm.OpcodeTables.Opcode; +import static org.openjdk.asmtools.jasm.OpcodeTables.opcode; +import static org.openjdk.asmtools.jasm.Tables.*; +import static org.openjdk.asmtools.jasm.Tables.AttrTag.ATT_RuntimeInvisibleTypeAnnotations; +import static org.openjdk.asmtools.jasm.Tables.AttrTag.ATT_RuntimeVisibleTypeAnnotations; +import static org.openjdk.asmtools.jdis.Utils.commentString; + +/** + * Code data for a code attribute in method members in a class of the Java Disassembler + */ +public class CodeData extends Indenter { + + /** + * Raw byte array for the byte codes + */ + protected byte[] code; + /** + * Limit for the stack size + */ + protected int max_stack; + + /* CodeData Fields */ + /** + * Limit for the number of local vars + */ + protected int max_locals; + /** + * The remaining attributes of this class + */ + protected ArrayList attrs = new ArrayList<>(0); // AttrData + + // internal references + protected ClassData cls; + protected MethodData meth; + /** + * (parsed) Trap table, describes exceptions caught + */ + private ArrayList trap_table = new ArrayList<>(0); // TrapData + /** + * (parsed) Line Number table, describes source lines associated with ByteCode indexes + */ + private ArrayList lin_num_tb = new ArrayList<>(0); // LineNumData + /** + * (parsed) Local Variable table, describes variable scopes associated with ByteCode + * indexes + */ + private ArrayList loc_var_tb = new ArrayList<>(0); // LocVarData + /** + * (parsed) stack map table, describes compiler hints for stack rep, associated with + * ByteCode indexes + */ + private ArrayList stack_map = null; + /** + * The visible type annotations for this method + */ + private ArrayList visibleTypeAnnotations; + /** + * The invisible type annotations for this method + */ + private ArrayList invisibleTypeAnnotations; + + /** + * (parsed) reversed bytecode index hash, associates labels with ByteCode indexes + */ + private HashMap iattrs = new HashMap<>(); + private PrintWriter out; + public CodeData(MethodData meth) { + this.meth = meth; + this.cls = meth.cls; + this.out = cls.out; + } + + private static int align(int n) { + return (n + 3) & ~3; + } + /*-------------------------------------------------------- */ + + private int getbyte(int pc) { + return code[pc]; + } + + private int getUbyte(int pc) { + return code[pc] & 0xFF; + } + + private int getShort(int pc) { + return (code[pc] << 8) | (code[pc + 1] & 0xFF); + } + + private int getUShort(int pc) { + return ((code[pc] << 8) | (code[pc + 1] & 0xFF)) & 0xFFFF; + } + + private int getInt(int pc) { + return (getShort(pc) << 16) | (getShort(pc + 2) & 0xFFFF); + } + + protected iAtt get_iAtt(int pc) { + Integer PC = pc; + iAtt res = iattrs.get(PC); + if (res == null) { + res = new iAtt(this); + iattrs.put(PC, res); + } + return res; + } + + /*========================================================*/ + /* Read Methods */ + private void readLineNumTable(DataInputStream in) throws IOException { + int len = in.readInt(); // attr_length + int numlines = in.readUnsignedShort(); + lin_num_tb = new ArrayList<>(numlines); + TraceUtils.traceln(3, "CodeAttr: LineNumTable[" + numlines + "] len=" + len); + for (int l = 0; l < numlines; l++) { + lin_num_tb.add(new LineNumData(in)); + } + } + + private void readLocVarTable(DataInputStream in) throws IOException { + int len = in.readInt(); // attr_length + int numlines = in.readUnsignedShort(); + loc_var_tb = new ArrayList<>(numlines); + TraceUtils.traceln(3, "CodeAttr: LocalVariableTable[" + numlines + "] len=" + len); + for (int l = 0; l < numlines; l++) { + loc_var_tb.add(new LocVarData(in)); + } + } + + private void readTrapTable(DataInputStream in) throws IOException { + int trap_table_len = in.readUnsignedShort(); + TraceUtils.traceln(3, "CodeAttr: TrapTable[" + trap_table_len + "]"); + trap_table = new ArrayList<>(trap_table_len); + for (int l = 0; l < trap_table_len; l++) { + trap_table.add(new TrapData(in, l)); + } + } + + private void readStackMap(DataInputStream in) throws IOException { + int len = in.readInt(); // attr_length + int stack_map_len = in.readUnsignedShort(); + TraceUtils.traceln(3, "CodeAttr: Stack_Map: attrlen=" + len + " num=" + stack_map_len); + stack_map = new ArrayList<>(stack_map_len); + StackMapData.prevFramePC = 0; + for (int k = 0; k < stack_map_len; k++) { + stack_map.add(new StackMapData(this, in)); + } + } + + private void readStackMapTable(DataInputStream in) throws IOException { + int len = in.readInt(); // attr_length + int stack_map_len = in.readUnsignedShort(); + TraceUtils.traceln(3, "CodeAttr: Stack_Map_Table: attrlen=" + len + " num=" + stack_map_len); + stack_map = new ArrayList<>(stack_map_len); + StackMapData.prevFramePC = 0; + for (int k = 0; k < stack_map_len; k++) { + stack_map.add(new StackMapData(this, in, true)); + } + } + + private void readTypeAnnotations(DataInputStream in, boolean isInvisible) throws IOException { + int attrLength = in.readInt(); + // Read Type Annotations Attr + int count = in.readShort(); + ArrayList tannots = new ArrayList<>(count); + TraceUtils.traceln(3, "CodeAttr: Runtime" + + (isInvisible ? "Inv" : "V") + + "isibleTypeAnnotation: attrlen=" + + attrLength + " num=" + count); + for (int index = 0; index < count; index++) { + TraceUtils.traceln("\t\t\t[" + index +"]:"); + TypeAnnotationData tannot = new TypeAnnotationData(isInvisible, cls); + tannot.read(in); + tannots.add(tannot); + } + if (isInvisible) { + invisibleTypeAnnotations = tannots; + } else { + visibleTypeAnnotations = tannots; + } + } + + /** + * read + *

    + * read and resolve the code attribute data called from MethodData. precondition: + * NumFields has already been read from the stream. + */ + public void read(DataInputStream in, int codeattrlen) throws IOException { + + // Read the code in the Code Attribute + max_stack = in.readUnsignedShort(); + max_locals = in.readUnsignedShort(); + int codelen = in.readInt(); + TraceUtils.traceln(3, "CodeAttr: Codelen=" + codelen + + " fulllen=" + codeattrlen + + " max_stack=" + max_stack + + " max_locals=" + max_locals); + + // read the raw code bytes + code = new byte[codelen]; + in.read(code, 0, codelen); + + //read the trap table + readTrapTable(in); + + // Read any attributes of the Code Attribute + int nattr = in.readUnsignedShort(); + TraceUtils.traceln(3, "CodeAttr: add.attr:" + nattr); + for (int k = 0; k < nattr; k++) { + int name_cpx = in.readUnsignedShort(); + // verify the Attrs name + ConstantPool.Constant name_const = cls.pool.getConst(name_cpx); + if (name_const != null && name_const.tag == ConstantPool.TAG.CONSTANT_UTF8) { + String attrname = cls.pool.getString(name_cpx); + TraceUtils.traceln(3, "CodeAttr: attr: " + attrname); + // process the attr + AttrTag attrtag = attrtag(attrname); + switch (attrtag) { + case ATT_LineNumberTable: + readLineNumTable(in); + break; + case ATT_LocalVariableTable: + readLocVarTable(in); + break; + case ATT_StackMap: + readStackMap(in); + break; + case ATT_StackMapTable: + readStackMapTable(in); + break; + case ATT_RuntimeVisibleTypeAnnotations: + case ATT_RuntimeInvisibleTypeAnnotations: + readTypeAnnotations(in, attrtag == ATT_RuntimeInvisibleTypeAnnotations); + break; + default: + AttrData attr = new AttrData(cls); + int attrlen = in.readInt(); // attr_length + attr.read(name_cpx, attrlen, in); + attrs.add(attr); + break; + } + } + } + } + + /*========================================================*/ + /* Code Resolution Methods */ + private int checkForLabelRef(int pc) { + // throws IOException { + int opc = getUbyte(pc); + Opcode opcode = opcode(opc); + switch (opcode) { + case opc_tableswitch: { + int tb = align(pc + 1); + int default_skip = getInt(tb); /* default skip pamount */ + + int low = getInt(tb + 4); + int high = getInt(tb + 8); + int count = high - low; + for (int i = 0; i <= count; i++) { + get_iAtt(pc + getInt(tb + 12 + 4 * i)).referred = true; + } + get_iAtt(default_skip + pc).referred = true; + return tb - pc + 16 + count * 4; + } + case opc_lookupswitch: { + int tb = align(pc + 1); + int default_skip = getInt(tb); /* default skip pamount */ + + int npairs = getInt(tb + 4); + for (int i = 1; i <= npairs; i++) { + get_iAtt(pc + getInt(tb + 4 + i * 8)).referred = true; + } + get_iAtt(default_skip + pc).referred = true; + return tb - pc + (npairs + 1) * 8; + } + case opc_jsr: + case opc_goto: + case opc_ifeq: + case opc_ifge: + case opc_ifgt: + case opc_ifle: + case opc_iflt: + case opc_ifne: + case opc_if_icmpeq: + case opc_if_icmpne: + case opc_if_icmpge: + case opc_if_icmpgt: + case opc_if_icmple: + case opc_if_icmplt: + case opc_if_acmpeq: + case opc_if_acmpne: + case opc_ifnull: + case opc_ifnonnull: + get_iAtt(pc + getShort(pc + 1)).referred = true; + return 3; + case opc_jsr_w: + case opc_goto_w: + get_iAtt(pc + getInt(pc + 1)).referred = true; + return 5; + case opc_wide: + case opc_nonpriv: + case opc_priv: + int opc2 = (opcode.value() << 8) + getUbyte(pc + 1); + opcode = opcode(opc2); + } + try { + int opclen = opcode.length(); + return opclen == 0 ? 1 : opclen; // bugfix for 4614404 + } catch (ArrayIndexOutOfBoundsException e) { + return 1; + } + } // end checkForLabelRef + + private void loadLabelTable() { + for (int pc = 0; pc < code.length; ) { + pc = pc + checkForLabelRef(pc); + } + } + + private void loadLineNumTable() { + for (LineNumData entry : lin_num_tb) { + get_iAtt(entry.start_pc).lnum = entry.line_number; + } + } + + private void loadStackMap() { + for (StackMapData entry : stack_map) { + get_iAtt(entry.start_pc).stackMapEntry = entry; + } + } + + private void loadLocVarTable() { + for (LocVarData entry : loc_var_tb) { + get_iAtt(entry.start_pc).add_var(entry); + get_iAtt(entry.start_pc + entry.length).add_endvar(entry); + } + } + + private void loadTrapTable() { + for (TrapData entry : trap_table) { + get_iAtt(entry.start_pc).add_trap(entry); + get_iAtt(entry.end_pc).add_endtrap(entry); + get_iAtt(entry.handler_pc).add_handler(entry); + } + } + + /*========================================================*/ + /* Print Methods */ + private void PrintConstant(int cpx) { + out.print("\t"); + cls.pool.PrintConstant(out, cpx); + } + + private void PrintCommentedConstant(int cpx) { + out.print(commentString(cls.pool.ConstantStrValue(cpx))); + } + + private int printInstr(int pc) { + boolean pr_cpx = meth.options.contains(Options.PR.CPX); + int opc = getUbyte(pc); + int opc2; + Opcode opcode = opcode(opc); + Opcode opcode2; + String mnem; + switch (opcode) { + case opc_nonpriv: + case opc_priv: + opc2 = getUbyte(pc + 1); + int finalopc = (opc << 8) + opc2; + opcode2 = opcode(finalopc); + if (opcode2 == null) { +// assume all (even nonexistent) priv and nonpriv instructions +// are 2 bytes long + mnem = opcode.parsekey() + " " + opc2; + } else { + mnem = opcode2.parsekey(); + } + out.print(mnem); + return 2; + case opc_wide: { + opc2 = getUbyte(pc + 1); + int finalopcwide = (opc << 8) + opc2; + opcode2 = opcode(finalopcwide); + if (opcode2 == null) { +// nonexistent opcode - but we have to print something + out.print("bytecode " + opcode); + return 1; + } else { + mnem = opcode2.parsekey(); + } + out.print(mnem + " " + getUShort(pc + 2)); + if (opcode2 == Opcode.opc_iinc_w) { + out.print(", " + getShort(pc + 4)); + return 6; + } + return 4; + } + } + mnem = opcode.parsekey(); + if (mnem == null) { +// nonexistent opcode - but we have to print something + out.print("bytecode " + opcode); + return 1; + } + if (opcode.value() >= Opcode.opc_bytecode.value()) { +// pseudo opcodes should be printed as bytecodes + out.print("bytecode " + opcode); + return 1; + } + out.print(opcode.parsekey()); +// TraceUtils.traceln("****** [CodeData.printInstr]: got an '" + opcode.parseKey() + "' [" + opc + "] instruction ****** "); + switch (opcode) { + case opc_aload: + case opc_astore: + case opc_fload: + case opc_fstore: + case opc_iload: + case opc_istore: + case opc_lload: + case opc_lstore: + case opc_dload: + case opc_dstore: + case opc_ret: + out.print("\t" + getUbyte(pc + 1)); + return 2; + case opc_iinc: + out.print("\t" + getUbyte(pc + 1) + ", " + getbyte(pc + 2)); + return 3; + case opc_tableswitch: { + int tb = align(pc + 1); + int default_skip = getInt(tb); /* default skip pamount */ + + int low = getInt(tb + 4); + int high = getInt(tb + 8); + int count = high - low; + out.print("{ //" + low + " to " + high); + for (int i = 0; i <= count; i++) { + out.print("\n\t\t" + (i + low) + ": " + meth.lP + (pc + getInt(tb + 12 + 4 * i)) + ";"); + } + out.print("\n\t\tdefault: " + meth.lP + (default_skip + pc) + " }"); + return tb - pc + 16 + count * 4; + } + case opc_lookupswitch: { + int tb = align(pc + 1); + int default_skip = getInt(tb); + int npairs = getInt(tb + 4); + out.print("{ //" + npairs); + for (int i = 1; i <= npairs; i++) { + out.print("\n\t\t" + getInt(tb + i * 8) + ": " + meth.lP + (pc + getInt(tb + 4 + i * 8)) + ";"); + } + out.print("\n\t\tdefault: " + meth.lP + (default_skip + pc) + " }"); + return tb - pc + (npairs + 1) * 8; + } + case opc_newarray: + int tp = getUbyte(pc + 1); + BasicType type = basictype(tp); + switch (type) { + case T_BOOLEAN: + out.print(" boolean"); + break; + case T_BYTE: + out.print(" byte"); + break; + case T_CHAR: + out.print(" char"); + break; + case T_SHORT: + out.print(" short"); + break; + case T_INT: + out.print(" int"); + break; + case T_LONG: + out.print(" long"); + break; + case T_FLOAT: + out.print(" float"); + break; + case T_DOUBLE: + out.print(" double"); + break; + case T_CLASS: + out.print(" class"); + break; + default: + out.print(" BOGUS TYPE:" + type); + } + return 2; + case opc_ldc_w: + case opc_ldc2_w: { + // added printing of the tag: Method/Interface to clarify + // interpreting CONSTANT_MethodHandle_info:reference_kind + // Example: ldc_w Dynamic REF_invokeStatic:Method CondyIndy.condy_bsm + cls.pool.setPrintTAG(true); + int index = getUShort(pc + 1); + if (pr_cpx) { + out.print("\t#" + index + "; //"); + } + PrintConstant(index); + cls.pool.setPrintTAG(false); + return 3; + } + case opc_anewarray: + case opc_instanceof: + case opc_checkcast: + case opc_new: + case opc_aconst_init: + case opc_putstatic: + case opc_getstatic: + case opc_putfield: + case opc_getfield: + case opc_withfield: + case opc_invokevirtual: + case opc_invokespecial: + case opc_invokestatic: { + int index = getUShort(pc + 1); + if (pr_cpx) { + out.print("\t#" + index + "; //"); + } + PrintConstant(index); + return 3; + } + case opc_sipush: + out.print("\t" + getShort(pc + 1)); + return 3; + case opc_bipush: + out.print("\t" + getbyte(pc + 1)); + return 2; + case opc_ldc: { + // added printing of the tag: Method/Interface to clarify + // interpreting CONSTANT_MethodHandle_info:reference_kind + // Example: ldc Dynamic REF_invokeStatic:Method CondyIndy.condy_bsm + cls.pool.setPrintTAG(true); + int index = getUbyte(pc + 1); + if (pr_cpx) { + out.print("\t#" + index + "; //"); + } + PrintConstant(index); + cls.pool.setPrintTAG(false); + return 2; + } + case opc_invokeinterface: { + int index = getUShort(pc + 1), nargs = getUbyte(pc + 3); + if (pr_cpx) { + out.print("\t#" + index + ", " + nargs + "; //"); + PrintConstant(index); + } else { + PrintConstant(index); + out.print(", " + nargs); // args count + } + return 5; + } + case opc_invokedynamic: { // JSR-292 + cls.pool.setPrintTAG(true); + int index = getUShort(pc + 1); + // getUbyte(pc + 3); // reserved byte + // getUbyte(pc + 4); // reserved byte + if (pr_cpx) { + out.print("\t#" + index + ";\t"); + PrintCommentedConstant(index); + } else { + PrintConstant(index); + } + cls.pool.setPrintTAG(false); + return 5; + } + case opc_multianewarray: { + int index = getUShort(pc + 1), dimensions = getUbyte(pc + 3); + if (pr_cpx) { + out.print("\t#" + index + ", " + dimensions + "; //"); + PrintConstant(index); + } else { + PrintConstant(index); + out.print(", " + dimensions); // dimensions count + } + return 4; + } + case opc_jsr: + case opc_goto: + case opc_ifeq: + case opc_ifge: + case opc_ifgt: + case opc_ifle: + case opc_iflt: + case opc_ifne: + case opc_if_icmpeq: + case opc_if_icmpne: + case opc_if_icmpge: + case opc_if_icmpgt: + case opc_if_icmple: + case opc_if_icmplt: + case opc_if_acmpeq: + case opc_if_acmpne: + case opc_ifnull: + case opc_ifnonnull: + out.print("\t" + meth.lP + (pc + getShort(pc + 1))); + return 3; + case opc_jsr_w: + case opc_goto_w: + out.print("\t" + meth.lP + (pc + getInt(pc + 1))); + return 5; + default: + return 1; + } + } // end printInstr + + /** + * print + *

    + * prints the code data to the current output stream. called from MethodData. + */ + public void print() throws IOException { + if (!lin_num_tb.isEmpty()) { + loadLineNumTable(); + } + if (stack_map != null) { + loadStackMap(); + } + if (!meth.options.contains(Options.PR.PC)) { + loadLabelTable(); + } + loadTrapTable(); + if (!loc_var_tb.isEmpty()) { + loadLocVarTable(); + } + + out.println(); + out.println("\tstack " + max_stack + " locals " + max_locals); + + // Need to print ParamAnnotations here. + meth.printPAnnotations(); + + out.println(getIndentString() + "{"); + + iAtt iatt = iattrs.get(0); + for (int pc = 0; pc < code.length; ) { + if (iatt != null) { + iatt.printBegins(); // equ. print("\t"); + } else { + out.print("\t"); + } + if (meth.options.contains(Options.PR.PC)) { + out.print(pc + ":\t"); + } else if ((iatt != null) && iatt.referred) { + out.print(meth.lP + pc + ":\t"); + } else { + out.print("\t"); + } + if (iatt != null) { + iatt.printStackMap(); + } + pc = pc + printInstr(pc); + out.println(";"); + iatt = iattrs.get(pc); + if (iatt != null) { + iatt.printEnds(); + } + } + // the right brace can be labelled: + if (iatt != null) { + iatt.printBegins(); // equ. print("\t"); + if (iatt.referred) { + out.print(meth.lP + code.length + ":\t"); + } + iatt.printStackMap(); + out.println(); + } + // print TypeAnnotations + if (visibleTypeAnnotations != null) { + out.println(); + for (TypeAnnotationData visad : visibleTypeAnnotations) { + visad.print(out, getIndentString()); + out.println(); + } + } + if (invisibleTypeAnnotations != null) { + for (TypeAnnotationData invisad : invisibleTypeAnnotations) { + invisad.print(out, getIndentString()); + out.println(); + } + } + // end of code + out.println(getIndentString() + "}"); + } + + + public static class LocVarData { + + short start_pc, length, name_cpx, sig_cpx, slot; + + public LocVarData(DataInputStream in) throws IOException { + start_pc = in.readShort(); + length = in.readShort(); + name_cpx = in.readShort(); + sig_cpx = in.readShort(); + slot = in.readShort(); + } + } + + /* Code Data inner classes */ + class LineNumData { + + short start_pc, line_number; + + public LineNumData(DataInputStream in) throws IOException { + start_pc = in.readShort(); + line_number = in.readShort(); + } + } + +} diff --git a/test/lib/org/openjdk/asmtools/jdis/ConstantPool.java b/test/lib/org/openjdk/asmtools/jdis/ConstantPool.java new file mode 100644 index 00000000000..92dbb07a627 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/ConstantPool.java @@ -0,0 +1,1073 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import org.openjdk.asmtools.asmutils.HexUtils; +import org.openjdk.asmtools.asmutils.StringUtils; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Hashtable; +import java.util.stream.Collectors; + +import static java.lang.String.format; +import static org.openjdk.asmtools.jdis.Utils.commentString; + +/** + * + * ConstantPool + * + * Class representing the ConstantPool + */ +public class ConstantPool { + + private static final Hashtable taghash = new Hashtable<>(); + private static final Hashtable subtaghash = new Hashtable<>(); + + private boolean printTAG = false; + + public void setPrintTAG(boolean value) { + this.printTAG = value; + } + + public String getPrintedTAG(TAG tag) { + return (this.printTAG) ? tag.tagname + " " : "" ; + } + + class Indent { + private int length, offset, step; + + void inc() { length+=step; } + + void dec() { length-=step; } + + Indent(int offset, int step) { + this.length = 0; + this.step = step; + this.offset = offset; + } + + int size() { return offset + length; } + + /** + * Creates indent string based on current indent size. + */ + private String get() { + return Collections.nCopies(size(), "\t").stream().collect(Collectors.joining()); + } + } + + private final Indent indent = new Indent(2, 1); + + /** + * TAG + * + * A Tag descriptor of constants in the constant pool + * + */ + public enum TAG { + CONSTANT_UTF8 ((byte) 1, "Asciz", "CONSTANT_UTF8"), + CONSTANT_UNICODE ((byte) 2, "unicorn", "CONSTANT_UNICODE"), + CONSTANT_INTEGER ((byte) 3, "int", "CONSTANT_INTEGER"), + CONSTANT_FLOAT ((byte) 4, "float", "CONSTANT_FLOAT"), + CONSTANT_LONG ((byte) 5, "long", "CONSTANT_LONG"), + CONSTANT_DOUBLE ((byte) 6, "double", "CONSTANT_DOUBLE"), + CONSTANT_CLASS ((byte) 7, "class", "CONSTANT_CLASS"), + CONSTANT_STRING ((byte) 8, "String", "CONSTANT_STRING"), + CONSTANT_FIELD ((byte) 9, "Field", "CONSTANT_FIELD"), + CONSTANT_METHOD ((byte) 10, "Method", "CONSTANT_METHOD"), + CONSTANT_INTERFACEMETHOD ((byte) 11, "InterfaceMethod", "CONSTANT_INTERFACEMETHOD"), + CONSTANT_NAMEANDTYPE ((byte) 12, "NameAndType", "CONSTANT_NAMEANDTYPE"), + CONSTANT_METHODHANDLE ((byte) 15, "MethodHandle", "CONSTANT_METHODHANDLE"), + CONSTANT_METHODTYPE ((byte) 16, "MethodType", "CONSTANT_METHODTYPE"), + CONSTANT_DYNAMIC ((byte) 17, "Dynamic", "CONSTANT_DYNAMIC"), + CONSTANT_INVOKEDYNAMIC ((byte) 18, "InvokeDynamic", "CONSTANT_INVOKEDYNAMIC"), + CONSTANT_MODULE ((byte) 19, "Module", "CONSTANT_MODULE"), + CONSTANT_PACKAGE ((byte) 20, "Package", "CONSTANT_PACKAGE"); + + private final Byte value; + private final String tagname; + private final String printval; + + TAG(byte val, String tgname, String print) { + value = val; + tagname = tgname; + printval = print; + } + + public byte value() { + return value; + } + + public String tagname() { + return tagname; + } + + public String description() { + return printval; + } + + @Override + public String toString() { + return "<" + tagname + "> "; + } + + }; + + + /** + * SUBTAG + * + * A Tag descriptor of form method-handle constants + * + */ + static public enum SUBTAG { + REF_GETFIELD ((byte) 1, "REF_getField", "REF_GETFIELD"), + REF_GETSTATIC ((byte) 2, "REF_getStatic", "REF_GETSTATIC"), + REF_PUTFIELD ((byte) 3, "REF_putField", "REF_PUTFIELD"), + REF_PUTSTATIC ((byte) 4, "REF_putStatic", "REF_PUTSTATIC"), + REF_INVOKEVIRTUAL ((byte) 5, "REF_invokeVirtual", "REF_INVOKEVIRTUAL"), + REF_INVOKESTATIC ((byte) 6, "REF_invokeStatic", "REF_INVOKESTATIC"), + REF_INVOKESPECIAL ((byte) 7, "REF_invokeSpecial", "REF_INVOKESPECIAL"), + REF_NEWINVOKESPECIAL ((byte) 8, "REF_newInvokeSpecial", "REF_NEWINVOKESPECIAL"), + REF_INVOKEINTERFACE ((byte) 9, "REF_invokeInterface", "REF_INVOKEINTERFACE"); + + private final Byte value; + private final String tagname; + private final String printval; + + SUBTAG(byte val, String tgname, String print) { + value = val; + tagname = tgname; + printval = print; +// subtaghash.put(new Byte(val), this); + } + + public byte value() { + return value; + } + + public String tagname() { + return tagname; + } + + public String description() { + return printval; + } + + @Override + public String toString() { + return "<" + tagname + "> "; + } + }; + + static { + + // Class initializer Code + // + // Make sure all of the tags get initialized before being used. + taghash.put(TAG.CONSTANT_UTF8.value(), TAG.CONSTANT_UTF8); + taghash.put(TAG.CONSTANT_UNICODE.value(), TAG.CONSTANT_UNICODE); + taghash.put(TAG.CONSTANT_INTEGER.value(), TAG.CONSTANT_INTEGER); + taghash.put(TAG.CONSTANT_FLOAT.value(), TAG.CONSTANT_FLOAT); + taghash.put(TAG.CONSTANT_LONG.value(), TAG.CONSTANT_LONG); + taghash.put(TAG.CONSTANT_DOUBLE.value(), TAG.CONSTANT_DOUBLE); + taghash.put(TAG.CONSTANT_CLASS.value(), TAG.CONSTANT_CLASS); + taghash.put(TAG.CONSTANT_STRING.value(), TAG.CONSTANT_STRING); + taghash.put(TAG.CONSTANT_FIELD.value(), TAG.CONSTANT_FIELD); + taghash.put(TAG.CONSTANT_METHOD.value(), TAG.CONSTANT_METHOD); + taghash.put(TAG.CONSTANT_INTERFACEMETHOD.value(), TAG.CONSTANT_INTERFACEMETHOD); + taghash.put(TAG.CONSTANT_NAMEANDTYPE.value(), TAG.CONSTANT_NAMEANDTYPE); + taghash.put(TAG.CONSTANT_METHODHANDLE.value(), TAG.CONSTANT_METHODHANDLE); + taghash.put(TAG.CONSTANT_METHODTYPE.value(), TAG.CONSTANT_METHODTYPE); + taghash.put(TAG.CONSTANT_DYNAMIC.value(), TAG.CONSTANT_DYNAMIC); + taghash.put(TAG.CONSTANT_INVOKEDYNAMIC.value(), TAG.CONSTANT_INVOKEDYNAMIC); + taghash.put(TAG.CONSTANT_MODULE.value(), TAG.CONSTANT_MODULE); + taghash.put(TAG.CONSTANT_PACKAGE.value(), TAG.CONSTANT_PACKAGE); + + subtaghash.put(SUBTAG.REF_GETFIELD.value(), SUBTAG.REF_GETFIELD); + subtaghash.put(SUBTAG.REF_GETSTATIC.value(), SUBTAG.REF_GETSTATIC); + subtaghash.put(SUBTAG.REF_PUTFIELD.value(), SUBTAG.REF_PUTFIELD); + subtaghash.put(SUBTAG.REF_PUTSTATIC.value(), SUBTAG.REF_PUTSTATIC); + subtaghash.put(SUBTAG.REF_INVOKEVIRTUAL.value(), SUBTAG.REF_INVOKEVIRTUAL); + subtaghash.put(SUBTAG.REF_INVOKESTATIC.value(), SUBTAG.REF_INVOKESTATIC); + subtaghash.put(SUBTAG.REF_INVOKESPECIAL.value(), SUBTAG.REF_INVOKESPECIAL); + subtaghash.put(SUBTAG.REF_NEWINVOKESPECIAL.value(), SUBTAG.REF_NEWINVOKESPECIAL); + subtaghash.put(SUBTAG.REF_INVOKEINTERFACE.value(), SUBTAG.REF_INVOKEINTERFACE); + + } + + /** + * + * Constant + * + * Base class of all constant entries + * + */ + public class Constant { + + /** + * tag the descriptor for the constant + */ + public TAG tag; + + public Constant(TAG tagval) { + tag = tagval; + } + + public String stringVal() { + return ""; + } + + public void print(PrintWriter out) { + out.print(tag.tagname + "\t"); + } + + public int size() { + return 1; + } + + @Override + public String toString() { + return ""; + } + + private IOException issue; + + public IOException getIssue() { + return issue; + } + + public void setIssue(IOException value) { + issue = value; + } + + } + + /* -------------------------------------------------------- */ + /* Constant Sub-classes */ + /** + * + * CP_Str + * + * Constant entries that contain String data. usually is a CONSTANT_UTF8 + * + */ + class CP_Str extends Constant { + + String value; + + CP_Str(TAG tagval, String str) { + super(tagval); + this.value = str; + } + + @Override + public String stringVal() { + return StringUtils.Utf8ToString(value); + } + + @Override + public void print(PrintWriter out) { + super.print(out); + out.println(stringVal() + ";"); + } + } + + /** + * + * CP_Int + * + * Constant entries that contain Integer data. usually is a CONSTANT_INTEGER + * + */ + class CP_Int extends Constant { + + Integer value; + + CP_Int(TAG tagval, int intval) { + super(tagval); + this.value = intval; + } + + @Override + public String stringVal() { + if (cd.options.contains(Options.PR.HEX)) { + return HexUtils.toHex(value.intValue()); + } + return value.toString(); + } + + @Override + public void print(PrintWriter out) { + super.print(out); + out.println(stringVal() + ";"); + } + } + + /** + * + * CP_Long + * + * Constant entries that contain LongInteger data. usually is a CONSTANT_LONG + * + * These take up 2 slots in the constant pool. + * + */ + class CP_Long extends Constant { + + Long value; + + CP_Long(TAG tagval, long intval) { + super(tagval); + this.value = intval; + } + + @Override + public String stringVal() { + if (cd.options.contains(Options.PR.HEX)) { + return HexUtils.toHex(value.longValue()) + 'l'; + } + return value.toString() + 'l'; + } + + @Override + public void print(PrintWriter out) { + super.print(out); + out.println(stringVal() + ";"); + } + + @Override + public int size() { + return 2; + } + } + + /** + * + * CP_Float + * + * Constant entries that contain Float data. usually is a CONSTANT_FLOAT + * + */ + class CP_Float extends Constant { + + Float value; + + CP_Float(TAG tagval, float fltvl) { + super(tagval); + this.value = fltvl; + } + + @Override + public String stringVal() { + if (cd.options.contains(Options.PR.HEX)) { + return "bits " + HexUtils.toHex(Float.floatToIntBits(value.floatValue())); + } + String sf = (value).toString(); + if (value.isNaN() || value.isInfinite()) { + return sf; + } + return sf + "f"; + } + + @Override + public void print(PrintWriter out) { + super.print(out); + out.println(stringVal() + ";"); + } + } + + /** + * + * CP_Double + * + * Constant entries that contain double-precision float data. usually is a + * CONSTANT_DOUBLE + * + * These take up 2 slots in the constant pool. + * + */ + class CP_Double extends Constant { + + Double value; + + CP_Double(TAG tagval, double fltvl) { + super(tagval); + this.value = fltvl; + } + + @Override + public String stringVal() { + if (cd.options.contains(Options.PR.HEX)) { + return "bits " + HexUtils.toHex(Double.doubleToLongBits(value.doubleValue())) + 'l'; + } + String sd = value.toString(); + if (value.isNaN() || value.isInfinite()) { + return sd; + } + return sd + "d"; + } + + @Override + public void print(PrintWriter out) { + super.print(out); + out.println(stringVal() + ";"); + } + + @Override + public int size() { + return 2; + } + } + + /** + * + * CPX + * + * Constant entries that contain a single constant-pool index. Usually, this includes: + * CONSTANT_CLASS CONSTANT_METHODTYPE CONSTANT_STRING CONSTANT_MODULE CONSTANT_PACKAGE + * + */ + class CPX extends Constant { + + int value; + + CPX(TAG tagval, int cpx) { + super(tagval); + this.value = cpx; + } + + @Override + public String stringVal() { + String str = "UnknownTag"; + switch (tag) { + case CONSTANT_CLASS: + str = getShortClassName(getClassName(this), cd.pkgPrefix); + break; + case CONSTANT_PACKAGE: + case CONSTANT_MODULE: + str = getString(value); + break; + case CONSTANT_METHODTYPE: + case CONSTANT_STRING: + str = StringValue(value); + break; + default: + break; + } + return str; + } + + @Override + public void print(PrintWriter out) { + super.print(out); + switch (tag) { + case CONSTANT_CLASS: + case CONSTANT_STRING: + case CONSTANT_METHODTYPE: + case CONSTANT_PACKAGE: + case CONSTANT_MODULE: + out.println("#" + (value) + ";\t// " + stringVal()); + break; + } + } + } + + /** + * + * CPX2 + * + * Constant entries that contain two constant-pool indices. Usually, this includes: + * CONSTANT_FIELD CONSTANT_METHOD CONSTANT_INTERFACEMETHOD CONSTANT_NAMEANDTYPE + * CONSTANT_METHODHANDLE CONSTANT_DYNAMIC CONSTANT_INVOKEDYNAMIC + * + */ + class CPX2 extends Constant { + + int value1, value2; + + CPX2(TAG tagval, int cpx1, int cpx2) { + super(tagval); + this.value1 = cpx1; + this.value2 = cpx2; + } + + @Override + public String stringVal() { + + String str = "UnknownTag"; + switch (tag) { + case CONSTANT_FIELD: + // CODETOOLS-7902660: the tag Field is not necessary while printing static parameters of a bsm + // Example: MethodHandle REF_getField:ClassName.FieldName:"I" + str = getShortClassName(getClassName(value1), cd.pkgPrefix) + "." + StringValue(value2); + break; + case CONSTANT_METHOD: + case CONSTANT_INTERFACEMETHOD: + // CODETOOLS-7902648: added printing of the tag: Method/Interface to clarify + // interpreting CONSTANT_MethodHandle_info:reference_kind + // Example: invokedynamic InvokeDynamic REF_invokeStatic:Method java/lang/runtime/ObjectMethods.bootstrap + str = getPrintedTAG(tag) + getShortClassName(getClassName(value1), cd.pkgPrefix) + "." + StringValue(value2); + break; + case CONSTANT_NAMEANDTYPE: + str = getName(value1) + ":" + StringValue(value2); + break; + case CONSTANT_METHODHANDLE: + str = subtagToString(value1) + ":" + StringValue(value2); + break; + case CONSTANT_DYNAMIC: + case CONSTANT_INVOKEDYNAMIC: + int bsm_attr_idx = value1; + int nape_idx = value2; + BootstrapMethodData bsmData; + try { + bsmData = cd.bootstrapMethods.get(bsm_attr_idx); + } catch (NullPointerException npe) { + return ""; + } catch (IndexOutOfBoundsException ioob) { + return ""; + } + StringBuilder bsm_args_str = new StringBuilder(); + String offsetParm,offsetBrace; + int bsm_ref = bsmData.bsm_index; + int bsm_args_len = bsmData.bsm_args_indexes.size(); + if (bsm_args_len > 0) { + bsm_args_str.append(" {\n"); + offsetBrace = indent.get(); + indent.inc(); + offsetParm = indent.get(); + for (int i = 0; i < bsm_args_len; i++) { + int bsm_arg_idx = bsmData.bsm_args_indexes.get(i); + Constant cnt = pool.get(bsm_arg_idx); + if (cnt.equals(this)) { + String s = "circular reference to " + cnt.tag.tagname() + " #" + bsm_arg_idx; + bsm_args_str.append(offsetParm).append(" <").append(s).append(">"); + cnt.setIssue(new IOException(s)); + } else { + bsm_args_str.append(offsetParm).append(ConstantStrValue(bsm_arg_idx)); + if (i + 1 < bsm_args_len) { + bsm_args_str.append(","); + } + } + bsm_args_str.append('\n'); + } + indent.dec(); + bsm_args_str.append(offsetBrace).append("}"); + } + str = StringValue(bsm_ref) + ":" + StringValue(nape_idx) + bsm_args_str.toString(); + default: + break; + } + return str; + } + + + + @Override + public void print(PrintWriter out) { + super.print(out); + switch (tag) { + case CONSTANT_FIELD: + case CONSTANT_METHOD: + case CONSTANT_INTERFACEMETHOD: + out.println("#" + value1 + ".#" + value2 + ";\t// " + stringVal()); + break; + case CONSTANT_METHODHANDLE: + out.println(value1 + ":#" + value2 + ";\t// " + stringVal()); + break; + case CONSTANT_NAMEANDTYPE: + out.println("#" + value1 + ":#" + value2 + ";\t// " + stringVal()); + break; + case CONSTANT_DYNAMIC: + case CONSTANT_INVOKEDYNAMIC: + out.println(value1 + ":#" + value2 + ";\t" + commentString(stringVal())); + break; + default: + break; + } + } + + public boolean refersClassMember() { + return tag == TAG.CONSTANT_FIELD || tag == TAG.CONSTANT_METHOD || tag == TAG.CONSTANT_INTERFACEMETHOD; + } + } + + /* -------------------------------------------------------- */ + /* ConstantPool Fields */ + /** + * The actual pool of Constants + */ + public ArrayList pool; + /** + * Reference to the class data + */ + private ClassData cd; + + + /* -------------------------------------------------------- */ + /* ConstantPool Methods */ + + /* ConstantPool Constructors */ + public ConstantPool(ClassData cd) { + pool = null; + this.cd = cd; + } + + public ConstantPool(ClassData cd, int size) { + pool = new ArrayList<>(size); + this.cd = cd; + } + + /** + * + * read + * + * decodes a ConstantPool and it's constants from a data stream. + * + */ + void read(DataInputStream in) throws IOException { + int length = in.readUnsignedShort(); + pool = new ArrayList<>(length); + pool.add(0, null); + TraceUtils.traceln("CP len=" + length); + for (int i = 1; i < length; i++) { + byte tag = in.readByte(); + TAG tagobj = taghash.get(tag); + TraceUtils.traceln("CP entry #" + i + " + tagindex=" + tag + " tag=" + tagobj); + switch (tagobj) { + case CONSTANT_UTF8: + pool.add(i, new CP_Str(tagobj, in.readUTF())); + break; + case CONSTANT_INTEGER: + pool.add(i, new CP_Int(tagobj, in.readInt())); + break; + case CONSTANT_LONG: + pool.add(i, new CP_Long(tagobj, in.readLong())); + // handle null entry to account for Longs taking up 2 CP slots + i += 1; + pool.add(null); + break; + case CONSTANT_FLOAT: + pool.add(i, new CP_Float(tagobj, in.readFloat())); + break; + case CONSTANT_DOUBLE: + pool.add(i, new CP_Double(tagobj, in.readDouble())); + // handle null entry to account for Doubles taking up 2 CP slots + i += 1; + pool.add(null); + break; + case CONSTANT_CLASS: + case CONSTANT_STRING: + case CONSTANT_METHODTYPE: + case CONSTANT_PACKAGE: + case CONSTANT_MODULE: + pool.add(i, new CPX(tagobj, in.readUnsignedShort())); + break; + case CONSTANT_FIELD: + case CONSTANT_METHOD: + case CONSTANT_INTERFACEMETHOD: + case CONSTANT_NAMEANDTYPE: + case CONSTANT_DYNAMIC: + case CONSTANT_INVOKEDYNAMIC: + pool.add(i, new CPX2(tagobj, in.readUnsignedShort(), in.readUnsignedShort())); + break; + case CONSTANT_METHODHANDLE: + pool.add(i, new CPX2(tagobj, in.readUnsignedByte(), in.readUnsignedShort())); + break; + + default: + throw new ClassFormatError("invalid constant type: " + (int) tag); + } + } + } + + /** + * + * inbounds + * + * bounds-check a CP index. + * + */ + private boolean inbounds(int cpx) { + return !(cpx == 0 || cpx >= pool.size()); + } + + /** + * + * getConst + * + * Public getter - Safely gets a Constant from the CP at a given index. + * + */ + public Constant getConst(int cpx) { + if (inbounds(cpx)) { + return pool.get(cpx); + } else { + return null; + } + } + + /** + * + * StringTag + * + * Public string val - Safely gets the string-rep of a Constant from the CP at a given + * index. + * + */ + public String StringTag(int cpx) { + String str = "Incorrect CP index:" + cpx; + if (inbounds(cpx)) { + Constant cns = pool.get(cpx); + if (cns != null) { + str = cns.tag.tagname; + } + } + return str; + } + + /** + * + * getString + * + * Public string val - Safely gets the string-rep of a ConstantUTF8 from the CP at a + * given index. + * + * Returns either null (if invalid), or the string value of the UTF8 + * + */ + public String getString(int cpx) { + String str = null; + if (inbounds(cpx)) { + Constant cns = pool.get(cpx); + if (cns != null && cns.tag == TAG.CONSTANT_UTF8) { + CP_Str cns1 = (CP_Str) cns; + str = cns1.value; + } + } + return str; + } + + /** + * + * getModule + * + * Public string val - Safely gets the string-rep of a ConstantModule from the CP at a + * given index. + * + * Returns either null (if invalid), or the string value of the ConstantModule + * + */ + public String getModule(int cpx) { + String str = null; + if (inbounds(cpx)) { + Constant cns = pool.get(cpx); + if (cns != null && cns.tag == TAG.CONSTANT_MODULE) { + str = cns.stringVal(); + } + } + return str; + } + + /** + * + * getPackage + * + * Public string val - Safely gets the string-rep of a ConstantPackage from the CP at a + * given index. + * + * Returns either null (if invalid), or the string value of the ConstantPackage + * + */ + public String getPackage(int cpx) { + String str = null; + if (inbounds(cpx)) { + Constant cns = pool.get(cpx); + if (cns != null && cns.tag == TAG.CONSTANT_PACKAGE) { + str = cns.stringVal(); + } + } + return str; + } + + /** + * + * getTypeName + * + * Safely gets a Java name from a ConstantUTF8 from the CP at a given index. + * + * Returns either null (if invalid), or the Java name value of the UTF8 + * + */ + public String getName(int cpx) { + String str = getString(cpx); + if (str == null) { + return ""; + } + + return Utils.javaName(str); + } + + /** + * + * getClassName + * + * Safely gets a Java class name from a ConstantClass from the CP at a given index. + * + * Returns either the Java class name, or a CP index reference string. + * + */ + public String getClassName(int cpx) { + String res = "#" + cpx; + if (cpx == 0) { + return res; + } + if (!inbounds(cpx)) { + return res; + } + Constant cns = pool.get(cpx); + if (cns == null || cns.tag != TAG.CONSTANT_CLASS) { + return res; + } + + return getClassName((CPX) cns); + } + + /** + * + * getClassName + * + * Safely gets a Java class name from a ConstantClass from a CPX2 constant pool + * object. (eg. Method/Field/Interface Ref) + * + * Returns either the Java class name, or a CP index reference string. + * + */ + public String getClassName(CPX2 classConst) { + return _getClassName(classConst.value1); + } + + /** + * + * getClassName + * + * Safely gets a Java class name from a ConstantClass from a CPX constant pool object. + * (eg. Class Ref) + * + * Returns either the Java class name, or a CP index reference string. + * + */ + public String getClassName(CPX classConst) { + return _getClassName(classConst.value); + } + + /** + * + * _getClassName + * + * Helper for getting class name. Checks bounds, does name conversion. + * + */ + private String _getClassName(int nameIndex) { + String res = "#" + nameIndex; + if (!inbounds(nameIndex)) { + return res; + } + Constant nameconst = pool.get(nameIndex); + if (nameconst == null || nameconst.tag != TAG.CONSTANT_UTF8) { + return res; + } + CP_Str name = (CP_Str) nameconst; + + String classname = name.value; + + if (Utils.isClassArrayDescriptor(classname)) { + classname = "\"" + classname + "\""; + } + return classname; + } + + /** + * + * getShortClassName + * + * shortens a class name (if the class is in the given package). works with a + * string-encoded classname. + * + */ + public String getShortClassName(String className, String pkgPrefix) { + if (className.startsWith(pkgPrefix)) { + return className.substring(pkgPrefix.length()); + } + return className; + } + + /** + * + * getShortClassName + * + * shortens a class name (if the class is in the given package). works with a CP index + * to a ConstantClass. + * + */ + public String getShortClassName(int cpx, String pkgPrefix) { + String name = Utils.javaName(getClassName(cpx)); + return getShortClassName(name, pkgPrefix); + } + + /** + * + * decodeClassDescriptor + * + * Pulls the class name out of a string (at the CP index). (drops any array + * descriptors, and the class descriptors ("L" and ";") + * + */ + public String decodeClassDescriptor(int cpx) { + // enum type is encoded as a descriptor + // need to remove '"'s and L (class descriptor) + + // TODO: might have to count '['s at the beginning for Arrays + String rawEnumName = getName(cpx); + int len = rawEnumName.length(); + int begin = (rawEnumName.startsWith("\"L")) ? 2 : 0; + int end = (begin > 0) ? len - 2 : len; + return rawEnumName.substring(begin, end); + } + + /** + * + * subtagToString + * + * Getter that safely gets the string descriptor of a subtag + * + */ + private String subtagToString(int subtag) { + SUBTAG st = subtaghash.get((byte) subtag); + if (st == null) { + return "BOGUS_SUBTAG:" + subtag; + } + return st.tagname; + } + + /** + * + * StringValue + * + * Safely gets the string value of any Constant at any CP index. + * + */ + public String StringValue(int cpx) { + if (cpx == 0) { + return "#0"; + } + if (!inbounds(cpx)) { + return ""; + } + Constant cnst = pool.get(cpx); + if (cnst == null) { + return ""; + } + return cnst.stringVal(); + } + + /** + * ConstantStrValue + * + * Safely gets the string value of any Constant at any CP index. This string is either + * a Constant's String value, or a CP index reference string. The Constant string has + * a tag descriptor in the beginning. + * + */ + public String ConstantStrValue(int cpx) { + if (cpx == 0) { + return "#0"; + } + if (!inbounds(cpx)) { + return "#" + cpx; + } + Constant cns = pool.get(cpx); + if (cns == null) { + return "#" + cpx; + } + if (cns instanceof CPX2) { + CPX2 cns2 = (CPX2) cns; + if (cns2.value1 == cd.this_cpx && cns2.refersClassMember()) { + cpx = cns2.value2; + } + } + return cns.tag.tagname + " " + StringValue(cpx); + } + + /** + * prints the entire constant pool. + */ + public void print(PrintWriter out) throws IOException { + int cpx = 0; + for (Constant cns : pool) { + if (cpx == 0) { + cpx += 1; + continue; + } + + out.print("\tconst #" + cpx + " = "); + + if (cns == null) { + // do something + out.println("null"); + cpx += 1; + } else { + cns.print(out); + cpx += cns.size(); + } + } + } + + /** + * prints the Constant value at a given CP index. + */ + void PrintConstant(PrintWriter out, int cpx) { + out.print(ConstantStrValue(cpx)); + } + + /** + * prints a constant value, with the print format based on the print options. + */ + public void printlnClassId(PrintWriter out, int cpx) throws IOException { + printlnClassId(out, cpx, false); + } + + public void printlnClassId(PrintWriter out, int cpx, boolean addComma) throws IOException { + if (!cd.options.contains(Options.PR.CPX)) { + out.print(getShortClassName(cpx, cd.pkgPrefix) + (addComma ? "," : "")); + } else { + out.print("\t#" + cpx + (addComma ? "," : "") + " //"); + PrintConstant(out, cpx); + } + } + +} diff --git a/test/lib/org/openjdk/asmtools/jdis/FieldData.java b/test/lib/org/openjdk/asmtools/jdis/FieldData.java new file mode 100644 index 00000000000..13b663d7450 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/FieldData.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import org.openjdk.asmtools.jasm.JasmTokens; +import org.openjdk.asmtools.jasm.Modifiers; + +import java.io.DataInputStream; +import java.io.IOException; + +import static java.lang.String.format; +import static org.openjdk.asmtools.jasm.Tables.AttrTag; +import static org.openjdk.asmtools.jasm.Tables.CF_Context; +import static org.openjdk.asmtools.jdis.TraceUtils.traceln; + +/** + * Field data for field members in a class of the Java Disassembler + */ +public class FieldData extends MemberData { + + // CP index to the field name + protected int name_cpx; + // CP index to the field type + protected int type_cpx; + // CP index to the field value + protected int value_cpx = 0; + + public FieldData(ClassData cls) { + super(cls); + memberType = "FieldData"; + } + + @Override + protected boolean handleAttributes(DataInputStream in, AttrTag attrtag, int attrlen) throws IOException { + // Read the Attributes + boolean handled = true; + switch (attrtag) { + case ATT_Signature: + if( signature != null ) { + traceln("Record attribute: more than one attribute Signature are in component.attribute_info_attributes[attribute_count]"); + traceln("Last one will be used."); + } + signature = new SignatureData(cls).read(in, attrlen); + break; + case ATT_ConstantValue: + if (attrlen != 2) { + throw new ClassFormatError(format("%s: Invalid attribute length #%d", AttrTag.ATT_ConstantValue.printval(), attrlen)); + } + value_cpx = in.readUnsignedShort(); + break; + default: + handled = false; + break; + } + return handled; + } + + /** + * Read and resolve the field data called from ClassData. + * Precondition: NumFields has already been read from the stream. + */ + public void read(DataInputStream in) throws IOException { + // read the Fields CP indexes + access = in.readUnsignedShort(); + name_cpx = in.readUnsignedShort(); + type_cpx = in.readUnsignedShort(); + // Read the attributes + readAttributes(in); + // + TraceUtils.traceln(2, + format("FieldData: name[%d]=%s type[%d]=%s%s", + name_cpx, cls.pool.getString(name_cpx), + type_cpx, cls.pool.getString(type_cpx), + signature != null ? signature : "")); + } + + + /** + * Prints the field data to the current output stream. called from ClassData. + */ + @Override + public void print() throws IOException { + // Print annotations first + super.printAnnotations(getIndentString()); + + StringBuilder bodyPrefix = new StringBuilder(getIndentString()).append(Modifiers.accessString(access, CF_Context.CTX_FIELD)); + StringBuilder tailPrefix = new StringBuilder(); + + if (isSynthetic) { + bodyPrefix.append(JasmTokens.Token.SYNTHETIC.parseKey()).append(' '); + } + if (isDeprecated) { + bodyPrefix.append(JasmTokens.Token.DEPRECATED.parseKey()).append(' '); + } + + // field + bodyPrefix.append(JasmTokens.Token.FIELDREF.parseKey()).append(' '); + + if (value_cpx != 0) { + tailPrefix.append("\t= ").append(cls.pool.ConstantStrValue(value_cpx)); + } + + printVar(bodyPrefix, tailPrefix,name_cpx, type_cpx); + } +} // end FieldData + diff --git a/test/lib/org/openjdk/asmtools/jdis/Indenter.java b/test/lib/org/openjdk/asmtools/jdis/Indenter.java new file mode 100644 index 00000000000..512393d601c --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/Indenter.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +public class Indenter { + + private int indentLength; + + public Indenter(int indentLength) { + this.indentLength = indentLength; + } + + public Indenter() { + this.indentLength = Options.BODY_INDENT; + } + /** + * Returns current indentation length. + * + * @return current indentation length. + */ + public int indent() { + return indentLength; + } + + /** + * Increases indentation length. + * + * @param indentLength new indent length + * + * @throws IllegalArgumentException if indentLength is negative. + */ + public Indenter setIndent(int indentLength) { + if (indentLength < 0) { + throw new IllegalArgumentException("indent length can't be negative"); + } + this.indentLength = indentLength; + return this; + } + + /** + * Increases indentation length. + * + * @param increase length to increase by. + * + * @throws IllegalArgumentException if increase is negative. + */ + public Indenter increaseIndent(int increase) { + if (increase < 0) { + throw new IllegalArgumentException("indent length can't be negative"); + } + setIndent(indent() + increase); + return this; + } + + /** + * Decreases indentation length. + * + * @param decrease length to decrease by + * + * @throws IllegalArgumentException if decrease is negative, or if decrease is greater than + * {@link #indent() current indentation length}. + */ + public Indenter decreaseIndent(int decrease) { + if (decrease < 0) { + throw new IllegalArgumentException("decrease can't be negative"); + } + setIndent(indent() - decrease); + return this; + } + + /** + * Creates indent string based on current indent size. + */ + public String getIndentString() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < indent(); i++) { + sb.append(' '); + } + return sb.toString(); + } +} diff --git a/test/lib/org/openjdk/asmtools/jdis/InnerClassData.java b/test/lib/org/openjdk/asmtools/jdis/InnerClassData.java new file mode 100644 index 00000000000..ab9ea20e15b --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/InnerClassData.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import static org.openjdk.asmtools.jasm.Tables.*; +import org.openjdk.asmtools.jasm.Modifiers; +import java.io.DataInputStream; +import java.io.IOException; + +/** + * + */ +class InnerClassData extends Indenter { + + ClassData cls; + int inner_class_info_index; + int outer_class_info_index; + int inner_name_index; + int access; + /*-------------------------------------------------------- */ + + public InnerClassData(ClassData cls) { + this.cls = cls; + } + + public void read(DataInputStream in) throws IOException { + inner_class_info_index = in.readUnsignedShort(); + outer_class_info_index = in.readUnsignedShort(); + inner_name_index = in.readUnsignedShort(); + access = in.readUnsignedShort(); + } // end read + + public void print() throws IOException { + boolean pr_cpx = Options.OptionObject().contains(Options.PR.CPX); + cls.out.print(getIndentString() + Modifiers.accessString(access, CF_Context.CTX_INNERCLASS)); + cls.out.print("InnerClass "); + if (pr_cpx) { + if (inner_name_index != 0) { + cls.out.print("#" + inner_name_index + "= "); + } + cls.out.print("#" + inner_class_info_index); + if (outer_class_info_index != 0) { + cls.out.print(" of #" + outer_class_info_index); + } + cls.out.print("; // "); + } + if (inner_name_index != 0) { + cls.out.print(cls.pool.getName(inner_name_index) + "="); + } + if (inner_class_info_index != 0) { + cls.pool.PrintConstant(cls.out, inner_class_info_index); + } + if (outer_class_info_index != 0) { + cls.out.print(" of "); + cls.pool.PrintConstant(cls.out, outer_class_info_index); + } + if (pr_cpx) { + cls.out.println(); + } else { + cls.out.println(";"); + } + } +} // end InnerClassData + diff --git a/test/lib/org/openjdk/asmtools/jdis/Main.java b/test/lib/org/openjdk/asmtools/jdis/Main.java new file mode 100644 index 00000000000..9ebfd164262 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/Main.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import org.openjdk.asmtools.common.Tool; +import org.openjdk.asmtools.util.I18NResourceBundle; +import org.openjdk.asmtools.util.ProductInfo; + +import java.io.DataInputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Main program of the Java Disassembler :: class to jasm + */ +public class Main extends Tool { + + private Options options; + + public static final I18NResourceBundle i18n + = I18NResourceBundle.getBundleForClass(Main.class); + + public Main(PrintWriter out, PrintWriter err, String programName) { + super(out, err, programName); + // tool specific initialization + options = Options.OptionObject(); + DebugFlag = () -> options.contains(Options.PR.DEBUG); + printCannotReadMsg = (fname) -> error( i18n.getString("jdis.error.cannot_read", fname)); + } + + public Main(PrintStream out, String program) { + this(new PrintWriter(out), new PrintWriter(System.err), program); + } + + @Override + public void usage() { + println(i18n.getString("jdis.usage")); + println(i18n.getString("jdis.opt.g")); + println(i18n.getString("jdis.opt.sl")); + println(i18n.getString("jdis.opt.hx")); + println(i18n.getString("jdis.opt.v")); + println(i18n.getString("jdis.opt.version")); + } + + /** + * Run the disassembler + */ + public synchronized boolean disasm(String argv[]) { + ArrayList files = new ArrayList<>(); + + // Parse arguments + for (int i = 0; i < argv.length; i++) { + String arg = argv[i]; + switch (arg) { + case "-g": + options.setCodeOptions(); + break; + case "-v": + options.set(Options.PR.DEBUG); + break; + case "-sl": + options.set(Options.PR.SRC); + break; + case "-hx": + options.set(Options.PR.HEX); + break; + case "-version": + out.println(ProductInfo.FULL_VERSION); + break; + default: + if (arg.startsWith("-")) { + error(i18n.getString("jdis.error.invalid_option", arg)); + usage(); + return false; + } else { + files.add(arg); + } + break; + } + } + + if (files.isEmpty()) { + usage(); + return false; + } + + for (String fname : files) { + if (fname == null) { + continue; + } // cross out by CompilerChoice.compile + try { + ClassData cc = new ClassData(out, this); + cc.read(fname); + cc.print(); + out.flush(); + continue; + } catch (Error ee) { + if (DebugFlag.getAsBoolean()) + ee.printStackTrace(); + error(i18n.getString("jdis.error.fatal_error", fname)); + } catch (Exception ee) { + if (DebugFlag.getAsBoolean()) + ee.printStackTrace(); + error(i18n.getString("jdis.error.fatal_exception", fname)); + } + return false; + } + return true; + } + + /** + * Main program + */ + public static void main(String argv[]) { + Main disassembler = new Main(new PrintWriter(new uEscWriter(System.out)), new PrintWriter(System.err), "jdis"); + boolean result = disassembler.disasm(argv); + System.exit(result ? 0 : 1); + } +} diff --git a/test/lib/org/openjdk/asmtools/jdis/MemberData.java b/test/lib/org/openjdk/asmtools/jdis/MemberData.java new file mode 100644 index 00000000000..bea9ba80400 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/MemberData.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import org.openjdk.asmtools.jasm.Tables; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; + +import static java.lang.String.format; + +/** + * Base class for ClassData, MethodData, FieldData and RecordData(JEP 360) + */ +public abstract class MemberData extends Indenter { + + // access flags (modifiers) + protected int access; + + // flags + protected boolean isSynthetic = false; + protected boolean isDeprecated = false; + + // Signature can be located in ClassFile, field_info, method_info, and component_info + protected SignatureData signature; + + /** + * The visible annotations for this class, member( field or method) or record component + */ + protected ArrayList visibleAnnotations; + + /** + * The invisible annotations for this class, member( field or method) or record component + */ + protected ArrayList invisibleAnnotations; + + /** + * The visible annotations for this class, member( field or method) or record component + */ + protected ArrayList visibleTypeAnnotations; + + /** + * The invisible annotations for this class, member( field or method) or record component + */ + protected ArrayList invisibleTypeAnnotations; + + /** + * The remaining attributes of this class, member( field or method) or record component + */ + protected ArrayList attrs; + + // internal references + protected final Options options = Options.OptionObject(); + protected final boolean pr_cpx = options.contains(Options.PR.CPX);; + protected ClassData cls; + protected PrintWriter out; + protected String memberType = ""; + + public MemberData(ClassData cls) { + this(); + init(cls); + } + + public MemberData() { + } + + public void init(ClassData cls) { + this.out = cls.out; + this.cls = cls; + } + + protected boolean handleAttributes(DataInputStream in, Tables.AttrTag attrtag, int attrlen) throws IOException { + // sub-classes override + return false; + } + + protected abstract void print() throws IOException; + + final protected int getAnnotationsCount() { + return ((visibleAnnotations == null) ? 0 : visibleAnnotations.size()) + + ((invisibleAnnotations == null) ? 0 : invisibleAnnotations.size()) + + ((visibleTypeAnnotations == null) ? 0 : visibleTypeAnnotations.size()) + + ((invisibleTypeAnnotations == null) ? 0 : invisibleTypeAnnotations.size()); + + } + + final protected void printAnnotations(String initialTab) { + if( getAnnotationsCount() > 0 ) { + if (visibleAnnotations != null) { + for (AnnotationData visad : visibleAnnotations) { + // out.print(initialTab); + visad.print(out, initialTab); + out.println(); + } + } + if (invisibleAnnotations != null) { + for (AnnotationData invisad : invisibleAnnotations) { + invisad.print(out, initialTab); + out.println(); + } + } + + if (visibleTypeAnnotations != null) { + for (TypeAnnotationData visad : visibleTypeAnnotations) { + visad.print(out, initialTab); + out.println(); + } + } + if (invisibleTypeAnnotations != null) { + for (TypeAnnotationData invisad : invisibleTypeAnnotations) { + invisad.print(out, initialTab); + out.println(); + } + } + } + } + + protected void printVar(StringBuilder bodyPrefix, StringBuilder tailPrefix, int name_cpx, int type_cpx) { + if( pr_cpx ) { + bodyPrefix.append('#').append(name_cpx).append(":#").append(type_cpx); + tailPrefix.append(";\t // ").append(cls.pool.getName(name_cpx)).append(':').append(cls.pool.getName(type_cpx)); + + } else { + bodyPrefix.append(cls.pool.getName(name_cpx)).append(':').append(cls.pool.getName(type_cpx)); + tailPrefix.append(';'); + } + + if (signature != null) { + signature.print(bodyPrefix.append(':').toString(), tailPrefix.append( pr_cpx ? ":" : "" ).toString()); + } else { + out.print(bodyPrefix); + out.print(tailPrefix); + } + out.println(); + } + + protected void readAttributes(DataInputStream in) throws IOException { + // Read the Attributes + int natt = in.readUnsignedShort(); + attrs = new ArrayList<>(natt); + TraceUtils.traceln(format("%s - Attributes[%d]", memberType , natt)); + AttrData attr; + for (int k = 0; k < natt; k++) { + int name_cpx = in.readUnsignedShort(); + attr = new AttrData(cls); + attrs.add(attr); + String attr_name = cls.pool.getString(name_cpx); + TraceUtils.traceln(format(" #%d name[%d]=\"%s\"", k, name_cpx, attr_name)); + Tables.AttrTag tag = Tables.attrtag(attr_name); + int attrlen = in.readInt(); + switch (tag) { + case ATT_Synthetic: + // Read Synthetic Attr + if (attrlen != 0) { + throw new ClassFormatError("invalid Synthetic attr length"); + } + isSynthetic = true; + break; + case ATT_Deprecated: + // Read Deprecated Attr + if (attrlen != 0) { + throw new ClassFormatError("invalid Deprecated attr length"); + } + isDeprecated = true; + break; + case ATT_RuntimeVisibleAnnotations: + case ATT_RuntimeInvisibleAnnotations: + // Read Annotations Attr + int cnt = in.readShort(); + ArrayList annots = new ArrayList<>(cnt); + boolean invisible = (tag == Tables.AttrTag.ATT_RuntimeInvisibleAnnotations); + for (int i = 0; i < cnt; i++) { + TraceUtils.traceln(" AnnotationData: #" + i); + AnnotationData annot = new AnnotationData(invisible, cls); + annot.read(in); + annots.add(annot); + } + + if (invisible) { + invisibleAnnotations = annots; + } else { + visibleAnnotations = annots; + } + break; + case ATT_RuntimeVisibleTypeAnnotations: + case ATT_RuntimeInvisibleTypeAnnotations: + // Read Type Annotations Attr + int tcnt = in.readShort(); + ArrayList tannots = new ArrayList<>(tcnt); + boolean tinvisible = (tag == Tables.AttrTag.ATT_RuntimeInvisibleTypeAnnotations); + for (int tindex = 0; tindex < tcnt; tindex++) { + TraceUtils.traceln(" TypeAnnotationData: #" + tindex); + TypeAnnotationData tannot = new TypeAnnotationData(tinvisible, cls); + tannot.read(in); + tannots.add(tannot); + } + + if (tinvisible) { + invisibleTypeAnnotations = tannots; + } else { + visibleTypeAnnotations = tannots; + } + break; + default: + boolean handled = handleAttributes(in, tag, attrlen); + if (!handled) { + attr.read(name_cpx, attrlen, in); + } + break; + + } + } + } +} diff --git a/test/lib/org/openjdk/asmtools/jdis/MethodData.java b/test/lib/org/openjdk/asmtools/jdis/MethodData.java new file mode 100644 index 00000000000..e9a995de39c --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/MethodData.java @@ -0,0 +1,329 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import org.openjdk.asmtools.jasm.Modifiers; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.ArrayList; + +import static org.openjdk.asmtools.jasm.JasmTokens.Token; +import static org.openjdk.asmtools.jasm.Tables.AttrTag; +import static org.openjdk.asmtools.jasm.Tables.CF_Context; + +/** + * Method data for method members in a class of the Java Disassembler + */ +public class MethodData extends MemberData { + + /** + * CP index to the method name + */ + protected int name_cpx; + + /** + * CP index to the method type + */ + protected int sig_cpx; + protected String lP; // labelPrefix + /** + * The parameter names for this method + */ + protected ArrayList paramNames; + /** + * The visible parameter annotations for this method + */ + protected ParameterAnnotationData visibleParameterAnnotations; + /** + * The invisible parameter annotations for this method + */ + protected ParameterAnnotationData invisibleParameterAnnotations; + /** + * The invisible parameter annotations for this method + */ + protected AnnotationElement.AnnotValue defaultAnnotation; + /** + * The code data for this method. May be null + */ + private CodeData code; + /** + * The exception table (thrown exceptions) for this method. May be null + */ + private int[] exc_table = null; + + public MethodData(ClassData cls) { + super(cls); + memberType = "MethodData"; + lP = (options.contains(Options.PR.LABS)) ? "L" : ""; + paramNames = null; + } + + /*========================================================*/ + /* Read Methods */ + @Override + protected boolean handleAttributes(DataInputStream in, AttrTag attrtag, int attrlen) throws IOException { + // Read the Attributes + boolean handled = true; + switch (attrtag) { + case ATT_Code: + code = new CodeData(this); + code.read(in, attrlen); + break; + case ATT_Exceptions: + readExceptions(in); + break; + case ATT_MethodParameters: + readMethodParameters(in); + break; + case ATT_RuntimeVisibleParameterAnnotations: + case ATT_RuntimeInvisibleParameterAnnotations: + boolean invisible = (attrtag == AttrTag.ATT_RuntimeInvisibleParameterAnnotations); + ParameterAnnotationData pannots = new ParameterAnnotationData(cls, invisible); + pannots.read(in); + if (invisible) { + invisibleParameterAnnotations = pannots; + } else { + visibleParameterAnnotations = pannots; + } + break; + case ATT_AnnotationDefault: + defaultAnnotation = AnnotationElement.readValue(in, cls, false); + break; + default: + handled = false; + break; + } + return handled; + } + + /** + * read + * read and resolve the method data called from ClassData. + * Precondition: NumFields has already been read from the stream. + */ + public void read(DataInputStream in) throws IOException { + // read the Methods CP indexes + access = in.readUnsignedShort(); // & MM_METHOD; // Q + name_cpx = in.readUnsignedShort(); + sig_cpx = in.readUnsignedShort(); + TraceUtils.traceln(2,"MethodData: {modifiers}: " + Modifiers.toString(access, CF_Context.CTX_METHOD), + " MethodData: name[" + name_cpx + "]=" + cls.pool.getString(name_cpx) + " sig[" + sig_cpx + "]=" + cls.pool.getString(sig_cpx)); + // Read the attributes + readAttributes(in); + } + + private void readExceptions(DataInputStream in) throws IOException { + // this is not really a CodeAttr attribute, it's part of the CodeAttr + int exc_table_len = in.readUnsignedShort(); + TraceUtils.traceln(3,"ExceptionsAttr[" + exc_table_len + "]"); + exc_table = new int[exc_table_len]; + for (int l = 0; l < exc_table_len; l++) { + int exc = in.readShort(); + TraceUtils.traceln(4,"throws:#" + exc); + exc_table[l] = exc; + } + } + + private void readMethodParameters(DataInputStream in) throws IOException { + // this is not really a CodeAttr attribute, it's part of the CodeAttr + int num_params = in.readUnsignedByte(); + TraceUtils.traceln(3,"MethodParametersAttr[" + num_params + "]"); + paramNames = new ArrayList<>(num_params); + for (int l = 0; l < num_params; l++) { + short pname_cpx = (short) in.readUnsignedShort(); + int paccess = in.readUnsignedShort(); + TraceUtils.traceln(4,"P[" + l + "] ={ name[" + pname_cpx + "]: " + cls.pool.getString(pname_cpx) + + " modifiers [" + paccess + "]: " + Modifiers.toString(paccess, CF_Context.CTX_METHOD) + "}"); + paramNames.add(l, new ParamNameData(pname_cpx, paccess)); + } + } + + /** + * printPAnnotations + *

    + * prints the parameter annotations for this method. called from CodeAttr (since JASM + * code integrates the PAnnotation Syntax inside the method body). + */ + // This is called from the CodeAttr + public void printPAnnotations() throws IOException { + int visSize = 0; + int invisSize = 0; + int pNumSize = 0; + + if (visibleParameterAnnotations != null) { + visSize = visibleParameterAnnotations.numParams(); + } + if (invisibleParameterAnnotations != null) { + invisSize = invisibleParameterAnnotations.numParams(); + } + if (paramNames != null) { + pNumSize = paramNames.size(); + } + + int maxParams; + maxParams = (pNumSize > invisSize) ? pNumSize : invisSize; + maxParams = (visSize > maxParams) ? visSize : maxParams; + + for (int paramNum = 0; paramNum < maxParams; paramNum++) { + ArrayList visAnnots = null; + if (visibleParameterAnnotations != null && paramNum < visSize) { + visAnnots = visibleParameterAnnotations.get(paramNum); + } + ArrayList invisAnnots = null; + if (invisibleParameterAnnotations != null && paramNum < invisSize) { + invisAnnots = invisibleParameterAnnotations.get(paramNum); + } + ParamNameData pname = (paramNames == null) ? null : paramNames.get(paramNum); + + boolean nullAnnots = ((visAnnots == null) && (invisAnnots == null)); + if (pname != null && pname.name_cpx == 0) { + pname = null; + } + + // Print the Param number (header) + if ((pname != null) || !nullAnnots) { + out.print("\t" + paramNum + ": "); + } else { + continue; + } + + boolean firstTime = true; + + // Print the Parameter name + if (pname != null) { + out.print(Token.PARAM_NAME.parseKey()); + out.print(Token.LBRACE.parseKey()); + out.print(cls.pool.getString(pname.name_cpx)); + out.print(" "); + out.print(Modifiers.toString(pname.access, CF_Context.CTX_METHOD)); + out.print(Token.RBRACE.parseKey()); + out.print(" "); + } + + // Print any visible param annotations + if (visAnnots != null) { + for (AnnotationData annot : visAnnots) { + if (!firstTime) { + out.print("\t "); + } + annot.print(out, getIndentString()); +// out.println(); + firstTime = false; + } + } + + // Print any invisible param annotations + if (invisAnnots != null) { + for (AnnotationData annot : invisAnnots) { + if (!firstTime) { + out.print("\t "); + } + annot.print(out, getIndentString()); +// out.println(); + firstTime = false; + } + } + + // Reset the line, if there were parameters + if ((pname != null) || !nullAnnots) { + out.println(); + } + + } + + } + + /** + * Prints the method data to the current output stream. called from ClassData. + */ + @Override + public void print() throws IOException { + + printAnnotations(getIndentString()); + + out.print(getIndentString() + Modifiers.accessString(access, CF_Context.CTX_METHOD)); + + if (isSynthetic) { + out.print(Token.SYNTHETIC.parseKey() + " "); + } + if (isDeprecated) { + out.print(Token.DEPRECATED.parseKey() + " "); + } + out.print(Token.METHODREF.parseKey() + " "); + + if (pr_cpx) { + // print the CPX method descriptor + out.print("#" + name_cpx + ":#" + sig_cpx + + ((code == null && exc_table == null && defaultAnnotation == null) ? ";" : "") + + "\t // " + cls.pool.getName(name_cpx) + ":" + cls.pool.getName(sig_cpx)); + } else { + out.print(cls.pool.getName(name_cpx) + ":" + cls.pool.getName(sig_cpx) + + ((code == null && exc_table == null && defaultAnnotation == null) ? ";" : "")); + } + // followed by default annotation + if (defaultAnnotation != null) { + out.print(" default { "); + defaultAnnotation.print(out, getIndentString()); + out.print(" }" + ((code == null && exc_table == null) ? ";" : " ")); + } + // followed by exception table + printExceptionTable(); + + if (code != null) { + code.print(); + } else { + if( exc_table != null ) { + out.print(';'); + } + out.println(); + } + } + + private void printExceptionTable() { + if (exc_table != null) { + out.print("\n\tthrows "); + int len = exc_table.length; + for (int exceptNum = 0; exceptNum < len; exceptNum++) { + out.print(cls.pool.getClassName(exc_table[exceptNum])); + if (exceptNum < len - 1) { + out.print(", "); + } + } + } + } + + /** + * MethodParamData + */ + class ParamNameData { + + public int access; + public int name_cpx; + + public ParamNameData(int name, int access) { + this.access = access; + this.name_cpx = name; + } + } +} // end MethodData diff --git a/test/lib/org/openjdk/asmtools/jdis/ModuleData.java b/test/lib/org/openjdk/asmtools/jdis/ModuleData.java new file mode 100644 index 00000000000..a164b8f0ad3 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/ModuleData.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.*; + +import org.openjdk.asmtools.common.Module; +import org.openjdk.asmtools.common.Tool; +import org.openjdk.asmtools.jasm.JasmTokens; + +import static org.openjdk.asmtools.jdis.Main.i18n; + +/** + * The module attribute data. + */ +public class ModuleData { + + // internal references + private final Tool tool; + + private ConstantPool pool; + private PrintWriter out; + private Module module; + + public ModuleData(ClassData clsData) { + this.tool = clsData.tool; + this.pool = clsData.pool; + this.out = clsData.out; + } + + public String getModuleName() { + return module == null ? "N/A" : module.getModuleName(); + } + + public String getModuleVersion() { return module.getModuleVersion(); } + + public String getModuleHeader() { + if ( module == null ) { + return "N/A"; + } else { + StringBuilder sb = new StringBuilder(module.getModuleFlags()); + sb.append(JasmTokens.Token.MODULE.parseKey()).append(" "); + sb.append(module.getModuleName()); + if (module.getModuleVersion() != null) + sb.append("// @").append(module.getModuleVersion()); + return sb.toString(); + } + } + + /** + * Reads and resolve the method's attribute data called from ClassData. + */ + public void read(DataInputStream in) throws IOException { + int index, moduleFlags, versionIndex; + String moduleName, version; + Module.Builder builder; + try { + // u2 module_name_index; + index = in.readUnsignedShort(); + moduleName = pool.getModule(index); + // u2 module_flags; + moduleFlags = in.readUnsignedShort(); + // u2 module_version_index; + versionIndex = in.readUnsignedShort(); + version = pool.getString(versionIndex); + builder = new Module.Builder(moduleName, moduleFlags, version); + } catch (IOException ioe) { + tool.error(i18n.getString("jdis.error.invalid_header")); + throw ioe; + } + + try { + int requires_count = in.readUnsignedShort(); + for (int i = 0; i < requires_count; i++) { + index = in.readUnsignedShort(); + int requiresFlags = in.readUnsignedShort(); + versionIndex = in.readUnsignedShort(); + + moduleName = pool.getModule(index); + version = pool.getString(versionIndex); + builder.require(moduleName, requiresFlags, version); + } + } catch (IOException ioe) { + tool.error(i18n.getString("jdis.error.invalid_requires")); + throw ioe; + } + + try { + int exports_count = in.readUnsignedShort(); + if (exports_count > 0) { + for (int i = 0; i < exports_count; i++) { + index = in.readUnsignedShort(); + String packageName = pool.getPackage(index); + int exportsFlags = in.readUnsignedShort(); + int exports_to_count = in.readUnsignedShort(); + if (exports_to_count > 0) { + Set targets = new HashSet<>(exports_to_count); + for (int j = 0; j < exports_to_count; j++) { + int exports_to_index = in.readUnsignedShort(); + targets.add(pool.getModule(exports_to_index)); + } + builder.exports(packageName, exportsFlags, targets); + } else { + builder.exports(packageName, exportsFlags); + } + } + } + } catch (IOException ioe) { + tool.error(i18n.getString("jdis.error.invalid_exports")); + throw ioe; + } + + try { + int opens_count = in.readUnsignedShort(); + if (opens_count > 0) { + for (int i = 0; i < opens_count; i++) { + index = in.readUnsignedShort(); + String packageName = pool.getPackage(index); + int opensFlags = in.readUnsignedShort(); + int opens_to_count = in.readUnsignedShort(); + if (opens_to_count > 0) { + Set targets = new HashSet<>(opens_to_count); + for (int j = 0; j < opens_to_count; j++) { + int opens_to_index = in.readUnsignedShort(); + targets.add(pool.getModule(opens_to_index)); + } + builder.opens(packageName, opensFlags, targets); + } else { + builder.opens(packageName, opensFlags); + } + } + } + } catch (IOException ioe) { + tool.error(i18n.getString("jdis.error.invalid_opens")); + throw ioe; + } + + try { + int uses_count = in.readUnsignedShort(); + if (uses_count > 0) { + for (int i = 0; i < uses_count; i++) { + index = in.readUnsignedShort(); + String serviceName = pool.getClassName(index); + builder.uses(serviceName); + } + } + } catch (IOException ioe) { + tool.error(i18n.getString("jdis.error.invalid_uses")); + throw ioe; + } + + try { + int provides_count = in.readUnsignedShort(); + if (provides_count > 0) { + for (int i = 0; i < provides_count; i++) { + index = in.readUnsignedShort(); + String serviceName = pool.getClassName(index); + int provides_with_count = in.readUnsignedShort(); + Set implNames = new HashSet<>(provides_with_count); + for (int j = 0; j < provides_with_count; j++) { + int provides_with_index = in.readUnsignedShort(); + implNames.add(pool.getClassName(provides_with_index)); + } + builder.provides(serviceName, implNames); + } + } + } catch (IOException ioe) { + tool.error(i18n.getString("jdis.error.invalid_provides")); + throw ioe; + } + module = builder.build(); + } + + /* Print Methods */ + public void print() { + if (module != null) + out.println(module.toString()); + } +} diff --git a/test/lib/org/openjdk/asmtools/jdis/NestHostData.java b/test/lib/org/openjdk/asmtools/jdis/NestHostData.java new file mode 100644 index 00000000000..b47e8570290 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/NestHostData.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + + +import org.openjdk.asmtools.jasm.JasmTokens; + +import java.io.DataInputStream; +import java.io.IOException; + +/** + * The NestHost attribute data + *

    + * since class file 55.0 (JEP 181) + */ +public class NestHostData extends Indenter{ + ClassData cls; + int host_class_index; + private Options options = Options.OptionObject(); + + public NestHostData(ClassData cls) { + this.cls = cls; + } + + public NestHostData read(DataInputStream in, int attribute_length) throws IOException, ClassFormatError { + if (attribute_length != 2) { + throw new ClassFormatError("ATT_NestHost: Invalid attribute length"); + } + host_class_index = in.readUnsignedShort(); + return this; + } + + public void print() { + boolean pr_cpx = options.contains(Options.PR.CPX); + cls.out.print(getIndentString() + JasmTokens.Token.NESTHOST.parseKey() + " "); + if (pr_cpx) { + cls.out.print("#" + host_class_index + "; //"); + } + if (pr_cpx) { + cls.pool.PrintConstant(cls.out, host_class_index); + cls.out.println(); + } else { + cls.out.println(cls.pool.StringValue(host_class_index) + ";"); + } + } +} diff --git a/test/lib/org/openjdk/asmtools/jdis/NestMembersData.java b/test/lib/org/openjdk/asmtools/jdis/NestMembersData.java new file mode 100644 index 00000000000..7f4aa3d84cf --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/NestMembersData.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import org.openjdk.asmtools.jasm.JasmTokens; + +import java.io.DataInputStream; +import java.io.IOException; + +/** + * The NestMembers attribute data + *

    + * JEP 181 (Nest-based Access Control): class file 55.0 + * NestMembers_attribute { + * u2 attribute_name_index; + * u4 attribute_length; + * u2 number_of_classes; + * u2 classes[number_of_classes]; + * } + */ +public class NestMembersData extends ClassArrayData { + public NestMembersData(ClassData cls) { + super(cls, JasmTokens.Token.NESTMEMBERS.parseKey()); + } + + public NestMembersData read(DataInputStream in, int attribute_length) throws IOException, ClassFormatError { + return (NestMembersData) super.read(in, attribute_length); + } +} diff --git a/test/lib/org/openjdk/asmtools/jdis/Options.java b/test/lib/org/openjdk/asmtools/jdis/Options.java new file mode 100644 index 00000000000..de3fb7a7df3 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/Options.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import java.util.EnumSet; + +/** + * The singleton class to share global options among jdis classes. + */ +public class Options { + + public static final int BODY_INDENT = 2; + + /* Options Fields */ + private static Options ref; + + public enum PR { + + CP, // print Constant Pool + LNT, // print Line Number table + PC, // print Program Counter - for all instr + LABS, // print Labels (as identifiers) + CPX, // print CP index along with arguments + SRC, // print Source Line as comment + HEX, // print numbers as hexadecimals + VAR, // print local variables declarations + DEBUG; // Debug flag + }; + + static private final EnumSet JASM = EnumSet.of(PR.LABS); // default options + static private final EnumSet CODE = EnumSet.of( + PR.CP, + PR.LNT, + PR.PC, + PR.CPX, + PR.VAR + ); + + static private EnumSet printOptions = JASM; + /*-------------------------------------------------------- */ + + private Options() { + } + + public static Options OptionObject() { + if (ref == null) { + ref = new Options(); + } + return ref; + } + + public void set(PR val) { + printOptions.add(val); + } + + public void setCodeOptions() { + printOptions.addAll(CODE); + } + + public boolean contains(PR val) { + return printOptions.contains(val); + } + + public boolean debug() { + return printOptions.contains(PR.DEBUG); + } + + @Override + public String toString() { + return printOptions.toString(); + } + +} diff --git a/test/lib/org/openjdk/asmtools/jdis/ParameterAnnotationData.java b/test/lib/org/openjdk/asmtools/jdis/ParameterAnnotationData.java new file mode 100644 index 00000000000..717b8521695 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/ParameterAnnotationData.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * + */ +public class ParameterAnnotationData { + /*-------------------------------------------------------- */ + /* AnnotData Fields */ + + private boolean invisible = false; + private static final String initialTab = ""; + private ArrayList> array = null; + + private ClassData cls; + /*-------------------------------------------------------- */ + + public ParameterAnnotationData(ClassData cls, boolean invisible) { + this.cls = cls; + this.invisible = invisible; + } + + public int numParams() { + if (array == null) { + return 0; + } + + return array.size(); + } + + public ArrayList get(int i) { + return array.get(i); + } + + public void read(DataInputStream in) throws IOException { + int numParams = in.readByte(); + TraceUtils.traceln(" ParameterAnnotationData[" + numParams + "]"); + array = new ArrayList<>(numParams); + for (int paramNum = 0; paramNum < numParams; paramNum++) { + + int numAnnots = in.readShort(); + TraceUtils.traceln(" Param#[" + paramNum + "]: numAnnots=" + numAnnots); + + if (numAnnots > 0) { + // read annotation + ArrayList p_annots = new ArrayList<>(numAnnots); + for (int annotIndex = 0; annotIndex < numAnnots; annotIndex++) { + AnnotationData annot = new AnnotationData(invisible, cls); + annot.read(in); + p_annots.add(annot); + } + array.add(paramNum, p_annots); + } else { + array.add(paramNum, null); + } + } + } + + // Don't need to do this -- + // we need to print annotations (both vis and invisible) per each param number + public void print(PrintWriter out, String tab) { + if (array != null && array.size() > 0) { + out.println(); + int paramNum = 0; + for (ArrayList p_annot : array) { + if (p_annot != null && p_annot.size() > 0) { + out.print("\t" + paramNum + ": "); + boolean firstTime = true; + for (AnnotationData annot : p_annot) { + if (!firstTime) { + out.print("\t "); + } + annot.print(out, initialTab); + firstTime = false; + } + } + + paramNum += 1; + out.println(); + } + } + } + +} diff --git a/test/lib/org/openjdk/asmtools/jdis/PermittedSubclassesData.java b/test/lib/org/openjdk/asmtools/jdis/PermittedSubclassesData.java new file mode 100644 index 00000000000..f53e8bba20f --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/PermittedSubclassesData.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import org.openjdk.asmtools.jasm.JasmTokens; + +import java.io.DataInputStream; +import java.io.IOException; + +/** + * The PermittedSubclasses attribute data + *

    + * JEP 360 (Sealed types): class file 59.65535 + * PermittedSubclasses_attribute { + * u2 attribute_name_index; + * u4 attribute_length; + * u2 number_of_classes; + * u2 classes[number_of_classes]; + * } + */ +public class PermittedSubclassesData extends ClassArrayData { + public PermittedSubclassesData(ClassData cls) { + super(cls, JasmTokens.Token.PERMITTEDSUBCLASSES.parseKey()); + } + + public PermittedSubclassesData read(DataInputStream in, int attribute_length) throws IOException, ClassFormatError { + return (PermittedSubclassesData) super.read(in, attribute_length); + } +} diff --git a/test/lib/org/openjdk/asmtools/jdis/PreloadData.java b/test/lib/org/openjdk/asmtools/jdis/PreloadData.java new file mode 100644 index 00000000000..a3095a6536f --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/PreloadData.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import org.openjdk.asmtools.jasm.JasmTokens; + +import java.io.DataInputStream; +import java.io.IOException; + +public class PreloadData extends ClassArrayData { + public PreloadData(ClassData cls) { + super(cls, JasmTokens.Token.PRELOAD.parseKey()); + } + + public PreloadData read(DataInputStream in, int attribute_length) throws IOException, ClassFormatError { + return (PreloadData) super.read(in, attribute_length); + } +} diff --git a/test/lib/org/openjdk/asmtools/jdis/RecordData.java b/test/lib/org/openjdk/asmtools/jdis/RecordData.java new file mode 100644 index 00000000000..3bcb07eafdc --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/RecordData.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import org.openjdk.asmtools.jasm.JasmTokens; +import org.openjdk.asmtools.jasm.Tables; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static java.lang.String.format; +import static org.openjdk.asmtools.jasm.JasmTokens.Token.*; +import static org.openjdk.asmtools.jdis.TraceUtils.traceln; + +/** + * The Record attribute data + *

    + * since class file 58.65535 (JEP 359) + */ +public class RecordData extends Indenter { + + + private final ClassData cls; + private List components; + + public RecordData(ClassData cls) { + this.cls = cls; + } + + public RecordData read(DataInputStream in) throws IOException { + int count = in.readUnsignedShort(); + traceln("components=" + count); + components = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + components.add(new Component(cls).read(in)); + } + return this; + } + + /** + * Prints the record data to the current output stream. called from ClassData. + */ + public void print() throws IOException { + int count = components.size(); + if (count > 0) { + cls.out.println(getIndentString() + RECORD.parseKey() + getIndentString() + LBRACE.parseKey()); + for (int i = 0; i < count; i++) { + Component cn = components.get(i); + cn.setIndent(indent() * 2); + if (i != 0 && cn.getAnnotationsCount() > 0) + cn.out.println(); + cn.print(); + } + cls.out.println(getIndentString() + RBRACE.parseKey()); + cls.out.println(); + } + } + + private class Component extends MemberData { + // CP index to the name + private int name_cpx; + // CP index to the type descriptor + private int type_cpx; + + public Component(ClassData cls) { + super(cls); + memberType = "RecordData"; + } + + @Override + protected boolean handleAttributes(DataInputStream in, Tables.AttrTag attrtag, int attrlen) throws IOException { + // Read the Attributes + boolean handled = true; + switch (attrtag) { + case ATT_Signature: + if( signature != null ) { + traceln("Record attribute: more than one attribute Signature are in component.attribute_info_attributes[attribute_count]"); + traceln("Last one will be used."); + } + signature = new SignatureData(cls).read(in, attrlen); + break; + default: + handled = false; + break; + } + return handled; + } + + /** + * Read and resolve the component data called from ClassData. + */ + public Component read(DataInputStream in) throws IOException { + // read the Component CP indexes + name_cpx = in.readUnsignedShort(); + type_cpx = in.readUnsignedShort(); + traceln(2, "RecordComponent: name[" + name_cpx + "]=" + cls.pool.getString(name_cpx) + + " descriptor[" + type_cpx + "]=" + cls.pool.getString(type_cpx)); + // Read the attributes + readAttributes(in); + return this; + } + + /** + * Prints the component data to the current output stream. called from RecordData. + */ + public void print() throws IOException { + // print component's attributes + super.printAnnotations(getIndentString()); + // print component + StringBuilder bodyPrefix = new StringBuilder(getIndentString()); + StringBuilder tailPrefix = new StringBuilder(); + if (isSynthetic) { + bodyPrefix.append(JasmTokens.Token.SYNTHETIC.parseKey()).append(' '); + } + if (isDeprecated) { + bodyPrefix.append(JasmTokens.Token.DEPRECATED.parseKey()).append(' '); + } + // component + bodyPrefix.append(JasmTokens.Token.COMPONENT.parseKey()).append(' '); + + printVar(bodyPrefix, tailPrefix,name_cpx, type_cpx); + } + } +} diff --git a/test/lib/org/openjdk/asmtools/jdis/SignatureData.java b/test/lib/org/openjdk/asmtools/jdis/SignatureData.java new file mode 100644 index 00000000000..2d937137af5 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/SignatureData.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + + +import org.openjdk.asmtools.jasm.Tables; + +import java.io.DataInputStream; +import java.io.IOException; + +import static java.lang.String.format; + +/** + * The Signature attribute data + *

    + * since class file 49.0 + */ +public class SignatureData { + ClassData cls; + int signature_index; + private Options options = Options.OptionObject(); + + public SignatureData(ClassData cls) { + this.cls = cls; + } + + public SignatureData read(DataInputStream in, int attribute_length) throws IOException, ClassFormatError { + if (attribute_length != 2) { + throw new ClassFormatError(format("%s: Invalid attribute length #%d", Tables.AttrTag.ATT_Signature.printval(), attribute_length)); + } + signature_index = in.readUnsignedShort(); + return this; + } + + public void print(String bodyPrefix, String commentPrefix) { + boolean pr_cpx = options.contains(Options.PR.CPX); + if (pr_cpx) { + cls.out.print(format("%s#%d%s%s", bodyPrefix, signature_index, commentPrefix, cls.pool.StringValue(signature_index))); + } else { + cls.out.print(format("%s%s%s", bodyPrefix, cls.pool.getName(signature_index), commentPrefix)); + } + } + + @Override + public String toString() { + return format("signature[%d]=%s", signature_index, cls.pool.StringValue(signature_index)); + } +} diff --git a/test/lib/org/openjdk/asmtools/jdis/StackMapData.java b/test/lib/org/openjdk/asmtools/jdis/StackMapData.java new file mode 100644 index 00000000000..2d3b9e985db --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/StackMapData.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import static java.lang.String.format; +import static org.openjdk.asmtools.jasm.Tables.*; +import static org.openjdk.asmtools.jdis.TraceUtils.mapToHexString; +import static org.openjdk.asmtools.jdis.TraceUtils.traceln; + +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.IOException; + +/** + * represents one entry of StackMap attribute + */ +class StackMapData { + static int prevFramePC = 0; + boolean isStackMapTable = false; + StackMapFrameType stackFrameType = null; + int start_pc; + int[] lockMap; + int[] stackMap; + + public StackMapData(CodeData code, DataInputStream in) throws IOException { + start_pc = in.readUnsignedShort(); + lockMap = readMap(code, in); + stackMap = readMap(code, in); + traceln(2, format("stack_map_entry:pc=%d numloc=%s numstack=%s", + start_pc, mapToHexString(lockMap), mapToHexString(stackMap))); + } + + public StackMapData(CodeData code, DataInputStream in, + boolean isStackMapTable) throws IOException { + this.isStackMapTable = isStackMapTable; + int ft_val = in.readUnsignedByte(); + StackMapFrameType frame_type = stackMapFrameType(ft_val); + int offset = 0; + switch (frame_type) { + case SAME_FRAME: + // type is same_frame; + offset = ft_val; + traceln(2, format("same_frame=%d", ft_val)); + break; + case SAME_FRAME_EX: + // type is same_frame_extended; + offset = in.readUnsignedShort(); + traceln(2, format("same_frame_extended=%d, offset=%d", ft_val, offset)); + break; + case SAME_LOCALS_1_STACK_ITEM_FRAME: + // type is same_locals_1_stack_item_frame + offset = ft_val - 64; + stackMap = readMapElements(code, in, 1); + traceln(2, format("same_locals_1_stack_item_frame=%d, offset=%d, numstack=%s", + ft_val, offset, mapToHexString(stackMap))); + break; + case SAME_LOCALS_1_STACK_ITEM_EXTENDED_FRAME: + // type is same_locals_1_stack_item_frame_extended + offset = in.readUnsignedShort(); + stackMap = readMapElements(code, in, 1); + traceln(2, format("same_locals_1_stack_item_frame_extended=%d, offset=%d, numstack=%s", + ft_val, offset, mapToHexString(stackMap))); + break; + case CHOP_1_FRAME: + case CHOP_2_FRAME: + case CHOP_3_FRAME: + // type is chop_frame + offset = in.readUnsignedShort(); + traceln(2, format("chop_frame=%d offset=%d", ft_val, offset)); + break; + case APPEND_FRAME: + // type is append_frame + offset = in.readUnsignedShort(); + lockMap = readMapElements(code, in, ft_val - 251); + traceln(2, format("append_frame=%d offset=%d numlock=%s", + ft_val, offset, mapToHexString(lockMap))); + break; + case FULL_FRAME: + // type is full_frame + offset = in.readUnsignedShort(); + lockMap = readMap(code, in); + stackMap = readMap(code, in); + traceln(2, format("full_frame=%d offset=%d numloc=%s numstack=%s", + ft_val, offset, mapToHexString(lockMap), mapToHexString(stackMap))); + break; + default: + TraceUtils.traceln("incorrect frame_type argument"); + + } + stackFrameType = frame_type; + start_pc = prevFramePC == 0 ? offset : prevFramePC + offset + 1; + prevFramePC = start_pc; + } + + private int[] readMap(CodeData code, DataInputStream in) throws IOException { + int num = in.readUnsignedShort(); + return readMapElements(code, in, num); + } + + private int[] readMapElements(CodeData code, DataInputStream in, int num) throws IOException { + int[] map = new int[num]; + for (int k = 0; k < num; k++) { + int mt_val = 0; + try { + mt_val = in.readUnsignedByte(); + } catch (EOFException eofe) { + throw eofe; + } + StackMapType maptype = stackMapType(mt_val, null); + switch (maptype) { + case ITEM_Object: + mt_val = mt_val | (in.readUnsignedShort() << 8); + break; + case ITEM_NewObject: { + int pc = in.readUnsignedShort(); + code.get_iAtt(pc).referred = true; + mt_val = mt_val | (pc << 8); + break; + } + } + map[k] = mt_val; + } + return map; + } + +} diff --git a/test/lib/org/openjdk/asmtools/jdis/TextLines.java b/test/lib/org/openjdk/asmtools/jdis/TextLines.java new file mode 100644 index 00000000000..36de99a0208 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/TextLines.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +/** + * A container for the java sources tied to an jasm output when -sl in on + */ +public class TextLines { + + final Path file; + List lines; + + public TextLines(Path directory, String sourceFileName) { + file = directory == null ? Paths.get(sourceFileName) : directory.resolve(sourceFileName); + try { + lines = Files.readAllLines(file, StandardCharsets.UTF_8); + } catch (IOException ignore) {} + } + + public String getLine(int index) { + if( lines != null ) { + if (index < 1 || index >= lines.size()) { + return String.format("Line number %d is out of range in \"%s\"", index, file); + } + return lines.get(index - 1); + } + return String.format("\"%s\" not found", file); + } +} diff --git a/test/lib/org/openjdk/asmtools/jdis/TraceUtils.java b/test/lib/org/openjdk/asmtools/jdis/TraceUtils.java new file mode 100644 index 00000000000..3d3ec3ddca2 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/TraceUtils.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import org.openjdk.asmtools.asmutils.HexUtils; + +import java.util.Arrays; +import java.util.stream.Collectors; + +import static java.lang.String.format; + +public class TraceUtils { + + public static String prefixString = "\t"; + + public static void trace(String s) { + if (!(Options.OptionObject()).debug()) { + return; + } + System.out.print(s); + } + + public static void trace(int prefixLength, String s) { + if (!(Options.OptionObject()).debug()) { + return; + } + System.out.print((prefixLength > 0) ? new String(new char[prefixLength]).replace("\0", prefixString) + s : s); + } + + public static void traceln(String s) { + if (!(Options.OptionObject()).debug()) { + return; + } + System.out.println(s); + } + + public static void traceln(String... lines) { + if (!(Options.OptionObject()).debug()) { + return; + } + + if (lines.length == 0) { + System.out.println(); + } else { + for (String s : lines) { + System.out.println(s); + } + } + } + + public static void traceln(int prefixLength, String s) { + if (!(Options.OptionObject()).debug()) { + return; + } + System.out.println((prefixLength > 0) ? new String(new char[prefixLength]).replace("\0", prefixString) + s : s); + } + + public static void traceln(int prefixLength, String... lines) { + if (!(Options.OptionObject()).debug()) { + return; + } + if (lines.length == 0) { + System.out.println(); + } else { + String prefix = (prefixLength > 0) ? new String(new char[prefixLength]).replace("\0", prefixString) : ""; + for (String s : lines) { + System.out.println(prefix + s); + } + } + } + + public static String mapToHexString(int[] array) { + return format("%d %s", + array.length, + Arrays.stream(array). + mapToObj(val -> HexUtils.toHex(val)). + collect(Collectors.joining(" "))); + } +} diff --git a/test/lib/org/openjdk/asmtools/jdis/TrapData.java b/test/lib/org/openjdk/asmtools/jdis/TrapData.java new file mode 100644 index 00000000000..fd3691eaa15 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/TrapData.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 1996, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import java.io.DataInputStream; +import java.io.IOException; + +/** + * Exception table entry (JVMS 4.7.3 The Code Attribute) describes one exception handler + * in the code array {@link CodeData}. + */ +class TrapData { + + int num; + + // exception_table + int start_pc, // u2 + end_pc, // u2 + handler_pc, // u2 + catch_cpx; // u2 + + public TrapData(DataInputStream in, int num) throws IOException { + this.num = num; + start_pc = in.readUnsignedShort(); + end_pc = in.readUnsignedShort(); + handler_pc = in.readUnsignedShort(); + catch_cpx = in.readUnsignedShort(); + } + + /* returns recommended identifier + */ + public String ident() { + return "t" + num; + } +} diff --git a/test/lib/org/openjdk/asmtools/jdis/TypeAnnotationData.java b/test/lib/org/openjdk/asmtools/jdis/TypeAnnotationData.java new file mode 100644 index 00000000000..cab4e50edf2 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/TypeAnnotationData.java @@ -0,0 +1,283 @@ +/* + * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import org.openjdk.asmtools.jasm.TypeAnnotationTargetInfoData; +import org.openjdk.asmtools.jasm.TypeAnnotationTypePathData; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.PrintWriter; + +import static org.openjdk.asmtools.jasm.TypeAnnotationTargetInfoData.*; +import static org.openjdk.asmtools.jasm.TypeAnnotationTypes.*; + +/** + * Type Annotation data is a specific kind of AnnotationData. As well as the normal data + * items needed to present an annotation, Type annotations require a TargetInfo + * descriptor. This descriptor is based on a TargetType, and it optionally may contain a + * location descriptor (when the Type is embedded in a collection). + *

    + * The TypeAnnotationData class is based on JDis's AnnotationData class, and contains the + * (jasm) class for representing TargetInfo. + */ +public class TypeAnnotationData extends AnnotationData { + + private static TTVis TT_Visitor = new TTVis(); + private TypeAnnotationTargetInfoData targetInfo; + private TypeAnnotationTypePathData typePath; + + public TypeAnnotationData(boolean invisible, ClassData cls) { + super(invisible, cls); + targetInfo = null; + typePath = new TypeAnnotationTypePathData(); + visAnnotToken = "@T+"; + invAnnotToken = "@T-"; + dataName = "TypeAnnotationData"; + } + + @Override + public void read(DataInputStream in) throws IOException { + + int ttype = in.readUnsignedByte(); + ETargetType targetType = ETargetType.getTargetType(ttype); + + if (targetType == null) { + // Throw some kind of error for bad target type index + throw new IOException("Bad target type: " + ttype + " in TypeAnnotationData"); + } + + // read the target info + TT_Visitor.init(in); + TT_Visitor.visitExcept(targetType); + targetInfo = TT_Visitor.getTargetInfo(); + + // read the target path info + int len = in.readUnsignedByte(); + TraceUtils.traceln(4,"[TypeAnnotationData.read]: Reading Location (length = " + len + ")."); + TraceUtils.trace(4,"[TypeAnnotationData.read]: [ "); + for (int i = 0; i < len; i++) { + int pathType = in.readUnsignedByte(); + String pk = (getPathKind(pathType)).parseKey(); + char pathArgIndex = (char) in.readUnsignedByte(); + typePath.addTypePathEntry(new TypePathEntry(pathType, pathArgIndex)); + TraceUtils.trace(" " + pk + "(" + pathType + "," + pathArgIndex + "), "); + } + TraceUtils.traceln("] "); + super.read(in); + } + + @Override + protected void printBody(PrintWriter out, String tab) { + // For a type annotation, print out brackets, + // print out the (regular) annotation name/value pairs, + // then print out the target types. + out.print(" {"); + super.printBody(out, ""); + targetInfo.print(out, tab); + typePath.print(out, tab); + out.print(tab + "}"); + } + + /** + * TTVis + *

    + * Target Type visitor, used for constructing the target-info within a type + * annotation. visitExcept() is the entry point. ti is the constructed target info. + */ + private static class TTVis extends TypeAnnotationTargetVisitor { + + private TypeAnnotationTargetInfoData targetInfo = null; + private IOException IOProb = null; + private DataInputStream in; + + public void init(DataInputStream in) { + this.in = in; + } + + public int scanByteVal() { + int val = 0; + try { + val = in.readUnsignedByte(); + } catch (IOException e) { + IOProb = e; + } + return val; + } + + public int scanShortVal() { + int val = 0; + try { + val = in.readUnsignedShort(); + } catch (IOException e) { + IOProb = e; + } + return val; + } + + //This is the entry point for a visitor that tunnels exceptions + public void visitExcept(ETargetType tt) throws IOException { + IOProb = null; + targetInfo = null; + + TraceUtils.traceln(4,"Target Type: " + tt.parseKey()); + visit(tt); + + if (IOProb != null) { + throw IOProb; + } + } + + public TypeAnnotationTargetInfoData getTargetInfo() { + return targetInfo; + } + + private boolean error() { + return IOProb != null; + } + + @Override + public void visit_type_param_target(ETargetType tt) { + TraceUtils.trace(4,"Type Param Target: "); + int byteval = scanByteVal(); // param index + TraceUtils.traceln("{ param_index: " + byteval + "}"); + if (!error()) { + targetInfo = new type_parameter_target(tt, byteval); + } + } + + @Override + public void visit_supertype_target(ETargetType tt) { + TraceUtils.trace(4,"SuperType Target: "); + int shortval = scanShortVal(); // type index + TraceUtils.traceln("{ type_index: " + shortval + "}"); + if (!error()) { + targetInfo = new supertype_target(tt, shortval); + } + } + + @Override + public void visit_typeparam_bound_target(ETargetType tt) { + TraceUtils.trace(4,"TypeParam Bound Target: "); + int byteval1 = scanByteVal(); // param index + if (error()) { + return; + } + int byteval2 = scanByteVal(); // bound index + if (error()) { + return; + } + TraceUtils.traceln("{ param_index: " + byteval1 + " bound_index: " + byteval2 + "}"); + targetInfo = new type_parameter_bound_target(tt, byteval1, byteval2); + } + + @Override + public void visit_empty_target(ETargetType tt) { + TraceUtils.traceln(4,"Empty Target: "); + if (!error()) { + targetInfo = new empty_target(tt); + } + } + + @Override + public void visit_methodformalparam_target(ETargetType tt) { + TraceUtils.trace(4,"MethodFormalParam Target: "); + int byteval = scanByteVal(); // param index + TraceUtils.traceln("{ param_index: " + byteval + "}"); + if (!error()) { + targetInfo = new formal_parameter_target(tt, byteval); + } + } + + @Override + public void visit_throws_target(ETargetType tt) { + TraceUtils.trace(4,"Throws Target: "); + int shortval = scanShortVal(); // exception index + TraceUtils.traceln("{ exception_index: " + shortval + "}"); + if (!error()) { + targetInfo = new throws_target(tt, shortval); + } + } + + @Override + public void visit_localvar_target(ETargetType tt) { + TraceUtils.traceln(4,"LocalVar Target: "); + int tblsize = scanShortVal(); // table length (short) + if (error()) { + return; + } + localvar_target locvartab = new localvar_target(tt, tblsize); + targetInfo = locvartab; + + for (int i = 0; i < tblsize; i++) { + int shortval1 = scanShortVal(); // startPC + if (error()) { + return; + } + int shortval2 = scanShortVal(); // length + if (error()) { + return; + } + int shortval3 = scanShortVal(); // CPX + TraceUtils.trace(4,"LocalVar[" + i + "]: "); + TraceUtils.traceln("{ startPC: " + shortval1 + ", length: " + shortval2 + ", CPX: " + shortval3 + "}"); + locvartab.addEntry(shortval1, shortval2, shortval3); + } + } + + @Override + public void visit_catch_target(ETargetType tt) { + TraceUtils.trace(4,"Catch Target: "); + int shortval = scanShortVal(); // catch index + TraceUtils.traceln("{ catch_index: " + shortval + "}"); + if (!error()) { + targetInfo = new catch_target(tt, shortval); + } + } + + @Override + public void visit_offset_target(ETargetType tt) { + TraceUtils.trace(4,"Offset Target: "); + int shortval = scanShortVal(); // offset index + TraceUtils.traceln("{ offset_index: " + shortval + "}"); + if (!error()) { + targetInfo = new offset_target(tt, shortval); + } + } + + @Override + public void visit_typearg_target(ETargetType tt) { + TraceUtils.trace(4,"TypeArg Target: "); + int shortval = scanShortVal(); // offset + if (error()) { + return; + } + int byteval = scanByteVal(); // type index + if (error()) { + return; + } + TraceUtils.traceln("{ offset: " + shortval + " type_index: " + byteval + "}"); + targetInfo = new type_argument_target(tt, shortval, byteval); + } + } +} diff --git a/test/lib/org/openjdk/asmtools/jdis/Utils.java b/test/lib/org/openjdk/asmtools/jdis/Utils.java new file mode 100644 index 00000000000..dbbc222ff0a --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/Utils.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +/** + * + */ +public class Utils { + + static public String javaName(String name) { + if (name == null) { + return "null"; + } + int len = name.length(); + if (len == 0) { + return "\"\""; + } + char cc = '/'; +fullname: + { // xxx/yyy/zzz + for (int k = 0; k < len; k++) { + char c = name.charAt(k); + if (cc == '/') { + if (!Character.isJavaIdentifierStart(c) && c != '-') { + break fullname; + } + } else if (c != '/') { + if (!Character.isJavaIdentifierPart(c) && c != '-') { + break fullname; + } + } + cc = c; + } + return name; + } + return "\"" + name + "\""; + } + + static public boolean isClassArrayDescriptor(String name) { + boolean retval = false; + if (name != null) { + if (name.startsWith("[")) { + retval = true; + } + } + + return retval; + } + + static public String commentString(String str) { + return commentString(str,"// "); + } + + static public String commentString(String str, String prefix) { + return prefix + str.replace("\n", "\n" + prefix); + } + +} diff --git a/test/lib/org/openjdk/asmtools/jdis/i18n.properties b/test/lib/org/openjdk/asmtools/jdis/i18n.properties new file mode 100644 index 00000000000..37a6bfef87c --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/i18n.properties @@ -0,0 +1,47 @@ +# Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. +# 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. + +jdis.usage=\ +Usage: java -jar asmtools.jar jdis [options] FILE.class... > FILE.jasm\n\ +where possible options include: + +jdis.opt.g=\ +\ -g detailed output format +jdis.opt.sl=\ +\ -sl source lines in comments +jdis.opt.v=\ +\ -v debug mode +jdis.opt.hx=\ +\ -hx hexadecimal numbers +jdis.opt.version=\ +\ -version prints the version info + +jdis.error.invalid_option=invalid option: {0} +jdis.error.cannot_read=cannot read {0} +jdis.error.fatal_error=fatal error in file: {0} +jdis.error.fatal_exception=fatal exception in file: {0} + +jdis.error.invalid_header=Invalid Module's attributes table +jdis.error.invalid_requires=Invalid requires table +jdis.error.invalid_exports=Invalid exports table +jdis.error.invalid_opens=Invalid opens table +jdis.error.invalid_uses=Invalid uses table +jdis.error.invalid_provides=Invalid provides table diff --git a/test/lib/org/openjdk/asmtools/jdis/iAtt.java b/test/lib/org/openjdk/asmtools/jdis/iAtt.java new file mode 100644 index 00000000000..b3bcdb0a77a --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/iAtt.java @@ -0,0 +1,238 @@ +/* + * Copyright (c) 1996, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import org.openjdk.asmtools.jasm.RuntimeConstants; +import static org.openjdk.asmtools.jasm.Tables.*; +import static org.openjdk.asmtools.jasm.OpcodeTables.*; +import org.openjdk.asmtools.jasm.Tables; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * instruction attributes + */ +class iAtt { + + /*-------------------------------------------------------- */ + /* iAtt Fields */ + private Options options; + + short lnum = 0; + boolean referred = false; // from some other instruction + ArrayList vars; + ArrayList endvars; + ArrayList handlers; + ArrayList traps; + ArrayList endtraps; + StackMapData stackMapEntry; + CodeData code; + ClassData cls; + PrintWriter out; // =cls.out; + /*-------------------------------------------------------- */ + + public iAtt(CodeData code) { + this.code = code; + this.cls = code.meth.cls; + out = cls.out; + options = cls.options; + } + + void add_var(CodeData.LocVarData var) { + if (vars == null) { + vars = new ArrayList<>(4); + } + vars.add(var); + } + + void add_endvar(CodeData.LocVarData endvar) { + if (endvars == null) { + endvars = new ArrayList<>(4); + } + endvars.add(endvar); + } + + void add_trap(TrapData trap) { + if (traps == null) { + traps = new ArrayList<>(4); + } + traps.add(trap); + } + + void add_endtrap(TrapData endtrap) { + if (endtraps == null) { + endtraps = new ArrayList<>(4); + } + endtraps.add(endtrap); + } + + void add_handler(TrapData endtrap) { + if (handlers == null) { + handlers = new ArrayList<>(4); + } + handlers.add(endtrap); + } + + public void printEnds() throws IOException { +// prints additional information for instruction: +// end of local variable and trap scopes; + int len; + if ((endvars != null) && (options.contains(Options.PR.VAR))) { + len = endvars.size() - 1; + out.print("\t\tendvar"); + for (CodeData.LocVarData line : endvars) { + out.print(" " + line.slot); + if (len-- > 0) { + out.print(","); + } + } + out.println(";"); + } + + if (endtraps != null) { + len = endtraps.size() - 1; + out.print("\t\tendtry"); + for (TrapData line : endtraps) { + out.print(" " + line.ident()); + if (len-- > 0) { + out.print(","); + } + } + out.println(";"); + } + } + + public void printBegins() + throws IOException { +// prints additional information for instruction: +// source line number; +// start of exception handler; +// begin of locvar and trap scopes; + boolean eitherOpt = options.contains(Options.PR.LNT) || options.contains(Options.PR.SRC); + boolean bothOpt = options.contains(Options.PR.LNT) && options.contains(Options.PR.SRC); + int k; + + if ((lnum != 0) && eitherOpt) { + if (bothOpt) { + out.println("// " + lnum + ": " + cls.getSrcLine(lnum)); + } else if (options.contains(Options.PR.LNT)) { + out.print(lnum); + } else if (options.contains(Options.PR.SRC)) { + out.println("// " + cls.getSrcLine(lnum)); + } + } + out.print("\t"); + if (handlers != null) { + for (TrapData line : handlers) { + out.print("\tcatch " + line.ident()); + out.print(" " + cls.pool.getClassName(line.catch_cpx) + ";\n\t"); + } + } + if (traps != null) { + int len = traps.size() - 1; + out.print("\ttry"); + for (TrapData line : traps) { + out.print(" " + line.ident()); + if (len-- > 0) { + out.print(","); + } + } + out.print(";\n\t"); + } + if ((vars != null) && options.contains(Options.PR.VAR)) { + for (CodeData.LocVarData line : vars) { + out.println("\tvar " + line.slot + "; // " + cls.pool.getName(line.name_cpx) + ":" + cls.pool.getName(line.sig_cpx)); + out.print("\t"); + } + } + } + + public void printMapList(int[] map) throws IOException { + boolean pr_cpx = options.contains(Options.PR.CPX); + + for (int k = 0; k < map.length; k++) { + int fullmaptype = map[k]; + int mt_val = fullmaptype & 0xFF; + StackMapType maptype = stackMapType(mt_val, out); + int argument = fullmaptype >> 8; + switch (maptype) { + case ITEM_Object: + if (pr_cpx) { + out.print(" #" + argument); + } else { + out.print(" "); + cls.pool.PrintConstant(out, argument); + } + break; + case ITEM_NewObject: + if (pr_cpx) { + out.print(" " + mt_val); + } else { + out.print(" " + maptype.printval()); + } + out.print(" " + code.meth.lP + argument); + break; + default: + if (pr_cpx) { + out.print(" " + mt_val); + } else { + out.print(" " + maptype.printval()); + } + } + out.print((k == (map.length - 1) ? ';' : ',')); + } + } + + public void printStackMap() throws IOException { + if (stackMapEntry == null) { + return; + } + boolean printed = false; + if (stackMapEntry.stackFrameType != null) { + out.print(Opcode.opc_stack_frame_type.parsekey()); // opcNamesTab[opc_stackframetype]); + out.print(" " + stackMapEntry.stackFrameType.parsekey() + ';'); + out.print("\n\t\t"); + printed = true; + } + int[] map = stackMapEntry.lockMap; + if ((map != null) && (map.length > 0)) { + out.print(Opcode.opc_locals_map.parsekey()); + printMapList(map); + out.print("\n\t\t"); + printed = true; + } + map = stackMapEntry.stackMap; + if ((map != null) && (map.length > 0)) { + out.print(Opcode.opc_stack_map.parsekey()); + printMapList(map); + out.print("\n\t\t"); + printed = true; + } + if (!printed) { +// empty attribute should be printed anyway - it should not +// be eliminated after jdis/jasm cycle + out.print(Opcode.opc_locals_map.parsekey() + " ;\n\t\t"); + } + } +} diff --git a/test/lib/org/openjdk/asmtools/jdis/uEscWriter.java b/test/lib/org/openjdk/asmtools/jdis/uEscWriter.java new file mode 100644 index 00000000000..92d593572e1 --- /dev/null +++ b/test/lib/org/openjdk/asmtools/jdis/uEscWriter.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.jdis; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Writer; + +/** + * + */ +public class uEscWriter extends Writer { + /*-------------------------------------------------------- */ + /* uEscWriter Fields */ + + static final char[] hexTable = "0123456789ABCDEF".toCharArray(); + OutputStream out; + byte[] tmpl; + /*-------------------------------------------------------- */ + + public uEscWriter(OutputStream out) { + this.out = out; + tmpl = new byte[6]; + tmpl[0] = (byte) '\\'; + tmpl[1] = (byte) 'u'; + } + + @Override + public synchronized void write(int c) throws IOException { + if (c < 128) { + out.write(c); + return; + } + // write \udddd + byte[] tmpll = tmpl; + for (int k = 3; k >= 0; k--) { + tmpll[5 - k] = (byte) hexTable[(c >> 4 * k) & 0xF]; + } + out.write(tmpll, 0, 6); + } + + @Override + public synchronized void write(char[] cc, int ofs, int len) throws IOException { + for (int k = ofs; k < len; k++) { + write(cc[k]); + } + } + + @Override + public void flush() { + } + + @Override + public void close() { + } +} // end uEscWriter + diff --git a/test/lib/org/openjdk/asmtools/util/I18NResourceBundle.java b/test/lib/org/openjdk/asmtools/util/I18NResourceBundle.java new file mode 100644 index 00000000000..3350b6a58dd --- /dev/null +++ b/test/lib/org/openjdk/asmtools/util/I18NResourceBundle.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.util; + +import java.text.MessageFormat; +import java.util.Enumeration; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +/** + * A class that lazily opens a package-specific resource bundle containing localization + * data for a class. + */ +public class I18NResourceBundle extends ResourceBundle { + + /** + * Get a package-specific resource bundle for a class containing localization data. + * The bundle is named i18n.properties in the same package as the given class. + * + * @param c the class for which to obtain the resource bundle + * @return the appropriate resource bundle for the class + */ + public static I18NResourceBundle getBundleForClass(Class c) { + String cn = c.getName(); + int dot = cn.lastIndexOf('.'); + String rn = (dot == -1 ? "i18n" : cn.substring(0, dot) + ".i18n"); + return new I18NResourceBundle(rn, c.getClassLoader()); + } + + /** + * Create a resource bundle for the given name. The actual resource bundle will not be + * loaded until it is needed. + * + * @param name The name of the actual resource bundle to use. + */ + private I18NResourceBundle(String name, ClassLoader cl) { + this.name = name; + this.classLoader = cl; + } + + /** + * Get an entry from the resource bundle. If the resource cannot be found, a message + * is printed to the console and the result will be a string containing the method + * parameters. + * + * @param key the name of the entry to be returned + * @param arg an argument to be formatted into the result using + * {@link java.text.MessageFormat#format} + * @return the formatted string + */ + public String getString(String key, Object arg) { + return getString(key, new Object[]{arg}); + } + + /** + * Get an entry from the resource bundle. If the resource cannot be found, a message + * is printed to the console and the result will be a string containing the method + * parameters. + * + * @param key the name of the entry to be returned + * @param args an array of arguments to be formatted into the result using + * {@link java.text.MessageFormat#format} + * @return the formatted string + */ + public String getString(String key, Object... args) { + try { + return MessageFormat.format(getString(key), args); + } catch (MissingResourceException e) { + System.err.println("WARNING: missing resource: " + key + " for " + name); + StringBuffer sb = new StringBuffer(key); + for (int i = 0; i < args.length; i++) { + sb.append('\n'); + sb.append(args.toString()); + } + return sb.toString(); + } + } + + /** + * Get an entry from the bundle, returning null if it is not found. + * + * @param key the name of the entry to be returned + * @return the value of the entry, or null if it is not found. + */ + public String getOptionalString(String key) { + if (delegate == null) { + delegate = ResourceBundle.getBundle(name, Locale.getDefault(), classLoader); + } + try { + String s = (String) (delegate.getObject(key)); + if (s != null) { + System.out.println("i18n: " + key); + } + return s; + } catch (MissingResourceException e) { + return null; + } + } + + /** + * A required internal method for ResourceBundle. Load the actual resource bundle, if + * it has not yet been loaded, then hand the request off to that bundle. If the + * resource cannot be found, a message is printed to the console and the result will + * be the original tag. + */ + protected Object handleGetObject(String key) throws MissingResourceException { + try { + if (delegate == null) { + delegate = ResourceBundle.getBundle(name, Locale.getDefault(), classLoader); + } + return delegate.getObject(key); + } catch (MissingResourceException e) { + System.err.println("WARNING: missing resource: " + key + " for " + name); + return key; + } + } + + /** + * A required internal method for ResourceBundle. Load the actual resource bundle, if + * it has not yet been loaded, then hand the request off to that bundle. + */ + public Enumeration getKeys() { + if (delegate == null) { + delegate = ResourceBundle.getBundle(name); + } + return delegate.getKeys(); + } + + /** + * Returns the name of this bundle (useful for methods using bundle name instead of + * instance, such as Logger creation, + * + * @return the name of this resource bundle + */ + public String getName() { + return name; + } + + private String name; + private ResourceBundle delegate; + private ClassLoader classLoader; +} diff --git a/test/lib/org/openjdk/asmtools/util/ProductInfo.java b/test/lib/org/openjdk/asmtools/util/ProductInfo.java new file mode 100644 index 00000000000..77425c2588c --- /dev/null +++ b/test/lib/org/openjdk/asmtools/util/ProductInfo.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.asmtools.util; + +import java.util.ResourceBundle; + +/** + * Class providing an access to the product info. + * productinfo.properties will be generated during the build + */ +public class ProductInfo { + + static { + init(); + } + + ; + + /** + * Returns the value of the specified property + */ + public static String getProperty(String propName) { + try { + return bundle.getString(propName); + } catch (java.util.MissingResourceException e) { + return null; + } + } + + /** + * Version of the product in the short format, like 5.0 + */ + public static final String VERSION = version(); + + /** + * Full version of the product, including build number and date of creation + */ + public static final String FULL_VERSION = fullVersion(); + + private static final String BUNDLE_NAME = "org.openjdk.asmtools.util.productinfo"; + + private static ResourceBundle bundle; + + /** + * Initializes the bundle + */ + private static void init() { + bundle = ResourceBundle.getBundle(BUNDLE_NAME); + } + + private static String version() { + return getProperty("PRODUCT_VERSION"); + } + + private static String fullVersion() { + return getProperty("PRODUCT_NAME_LONG") + ", version " + version() + + " " + getProperty("PRODUCT_MILESTONE") + + " " + getProperty("PRODUCT_BUILDNUMBER") + + " (" + getProperty("PRODUCT_DATE") + ")"; + + } + +} diff --git a/test/lib/org/openjdk/asmtools/util/productinfo.properties b/test/lib/org/openjdk/asmtools/util/productinfo.properties new file mode 100644 index 00000000000..2286dbd656b --- /dev/null +++ b/test/lib/org/openjdk/asmtools/util/productinfo.properties @@ -0,0 +1,27 @@ +# Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. +# 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. + +PRODUCT_NAME = %%PRODUCT_NAME%% +PRODUCT_VERSION = %%PRODUCT_VERSION%% +PRODUCT_MILESTONE = %%PRODUCT_MILESTONE%% +PRODUCT_BUILDNUMBER = %%PRODUCT_BUILDNUMBER%% +PRODUCT_NAME_LONG = %%PRODUCT_NAME_LONG%% +PRODUCT_DATE = %%BUILD_DATE%% diff --git a/test/micro/org/openjdk/bench/java/lang/StringEquals.java b/test/micro/org/openjdk/bench/java/lang/StringEquals.java index b0db6a7037e..99c554456c9 100644 --- a/test/micro/org/openjdk/bench/java/lang/StringEquals.java +++ b/test/micro/org/openjdk/bench/java/lang/StringEquals.java @@ -38,8 +38,10 @@ public class StringEquals { public String test = new String("0123456789"); public String test2 = new String("tgntogjnrognagronagroangroarngorngaorng"); + @SuppressWarnings("initialization") public String test3 = new String(test); // equal to test, but not same public String test4 = new String("0123\u01FF"); + @SuppressWarnings("initialization") public String test5 = new String(test4); // equal to test4, but not same public String test6 = new String("0123456780"); public String test7 = new String("0123\u01FE"); diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/BulkOps.java b/test/micro/org/openjdk/bench/java/lang/foreign/BulkOps.java index 60f36d9f157..72c45f23c04 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/BulkOps.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/BulkOps.java @@ -68,6 +68,7 @@ public class BulkOps { final IntBuffer buffer = IntBuffer.allocate(ELEM_SIZE); final int[] ints = new int[ELEM_SIZE]; + @SuppressWarnings("initialization") final MemorySegment bytesSegment = MemorySegment.ofArray(ints); final long UNSAFE_INT_OFFSET = unsafe.arrayBaseOffset(int[].class); diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverPollutedBuffer.java b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverPollutedBuffer.java index c9060adc18b..dd3465f4a0d 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverPollutedBuffer.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverPollutedBuffer.java @@ -58,6 +58,7 @@ public class LoopOverPollutedBuffer { ByteBuffer dbb = ByteBuffer.allocateDirect(ALLOC_SIZE).order(ByteOrder.nativeOrder()); byte[] arr = new byte[ALLOC_SIZE]; + @SuppressWarnings("initialization") ByteBuffer hbb = ByteBuffer.wrap(arr).order(ByteOrder.nativeOrder()); FloatBuffer hfb = hbb.asFloatBuffer(); diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/StrLenTest.java b/test/micro/org/openjdk/bench/java/lang/foreign/StrLenTest.java index b54d5377377..890edc5ec37 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/StrLenTest.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/StrLenTest.java @@ -155,6 +155,7 @@ static class RingAllocator implements SegmentAllocator { SegmentAllocator current; long rem; + @SuppressWarnings("initialization") public RingAllocator(Arena session, int size) { this.segment = session.allocate(size, 1); reset(); diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/TestAdaptVarHandles.java b/test/micro/org/openjdk/bench/java/lang/foreign/TestAdaptVarHandles.java index 7ca70b31f49..7d79f667280 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/TestAdaptVarHandles.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/TestAdaptVarHandles.java @@ -90,6 +90,7 @@ int intValue() { static final MethodHandle MH_BOX_INT = MethodHandles.filterReturnValue(MH_INT, INT_TO_INTBOX); int[] base = new int[ELEM_SIZE]; + @SuppressWarnings("initialization") MemorySegment segment = MemorySegment.ofArray(base); @Setup diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/pointers/PointerBench.java b/test/micro/org/openjdk/bench/java/lang/foreign/pointers/PointerBench.java index 1bc7c23755b..c52ae46f24a 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/pointers/PointerBench.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/pointers/PointerBench.java @@ -52,8 +52,11 @@ public class PointerBench { final Arena arena = Arena.ofConfined(); static final int ELEM_SIZE = 1_000_000; + @SuppressWarnings("initialization") Pointer intPointer = Pointer.allocate(NativeType.C_INT, ELEM_SIZE, arena); + @SuppressWarnings("initialization") Pointer> intPointerPointer = Pointer.allocate(NativeType.C_INT_PTR, ELEM_SIZE, arena); + @SuppressWarnings("initialization") Pointer pointPointer = Pointer.allocate(Point.TYPE, ELEM_SIZE, arena); MemorySegment intSegment = intPointer.segment(); MemorySegment intPointerSegment = intPointerPointer.segment(); diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/points/support/BBPoint.java b/test/micro/org/openjdk/bench/java/lang/foreign/points/support/BBPoint.java index 7b112fad188..027ced75170 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/points/support/BBPoint.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/points/support/BBPoint.java @@ -33,6 +33,7 @@ public class BBPoint { private final ByteBuffer buff; + @SuppressWarnings("initialization") public BBPoint(int x, int y) { this.buff = ByteBuffer.allocateDirect(4 * 2).order(ByteOrder.nativeOrder()); setX(x); diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/points/support/PanamaPoint.java b/test/micro/org/openjdk/bench/java/lang/foreign/points/support/PanamaPoint.java index de7c823a917..e61502c6364 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/points/support/PanamaPoint.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/points/support/PanamaPoint.java @@ -60,6 +60,7 @@ public class PanamaPoint extends CLayouts implements AutoCloseable { Arena arena; private final MemorySegment segment; + @SuppressWarnings("initialization") public PanamaPoint(int x, int y) { this.arena = Arena.ofConfined(); this.segment = arena.allocate(LAYOUT); diff --git a/test/micro/org/openjdk/bench/java/lang/ref/CleanerChurn.java b/test/micro/org/openjdk/bench/java/lang/ref/CleanerChurn.java index eab4c0f59d0..df27f118a05 100644 --- a/test/micro/org/openjdk/bench/java/lang/ref/CleanerChurn.java +++ b/test/micro/org/openjdk/bench/java/lang/ref/CleanerChurn.java @@ -48,6 +48,7 @@ public Object test() { static class Target { private static final Cleaner CLEANER = Cleaner.create(); + @SuppressWarnings("initialization") public Target(boolean register) { if (register) { CLEANER.register(this, () -> {}); diff --git a/test/micro/org/openjdk/bench/java/lang/ref/CleanerGC.java b/test/micro/org/openjdk/bench/java/lang/ref/CleanerGC.java index 8d43f25c6c6..7c77e6b19fa 100644 --- a/test/micro/org/openjdk/bench/java/lang/ref/CleanerGC.java +++ b/test/micro/org/openjdk/bench/java/lang/ref/CleanerGC.java @@ -60,6 +60,7 @@ public void test() { static class Target { private static final Cleaner CLEANER = Cleaner.create(); + @SuppressWarnings("initialization") public Target() { CLEANER.register(this, () -> {}); } diff --git a/test/micro/org/openjdk/bench/java/nio/StringCharBufferBulkTransfer.java b/test/micro/org/openjdk/bench/java/nio/StringCharBufferBulkTransfer.java index 246116e4d43..ebf969c4dbe 100644 --- a/test/micro/org/openjdk/bench/java/nio/StringCharBufferBulkTransfer.java +++ b/test/micro/org/openjdk/bench/java/nio/StringCharBufferBulkTransfer.java @@ -45,6 +45,7 @@ public class StringCharBufferBulkTransfer { private static final int LENGTH = 16384; char[] buf = new char[LENGTH]; + @SuppressWarnings("initialization") CharBuffer cb = CharBuffer.wrap(new String(buf)); char[] dst = new char[LENGTH]; CharBuffer cbw = CharBuffer.allocate(LENGTH); diff --git a/test/micro/org/openjdk/bench/java/util/concurrent/CopyOnWriteArrayListBenchmark.java b/test/micro/org/openjdk/bench/java/util/concurrent/CopyOnWriteArrayListBenchmark.java index 0ce091d1724..77dd45bce94 100644 --- a/test/micro/org/openjdk/bench/java/util/concurrent/CopyOnWriteArrayListBenchmark.java +++ b/test/micro/org/openjdk/bench/java/util/concurrent/CopyOnWriteArrayListBenchmark.java @@ -67,11 +67,13 @@ private static byte[] getSerializedBytes(CopyOnWriteArrayList list) throws IO private Object[] oneItemArray = new Object[] { "" }; private CopyOnWriteArrayList emptyInstance = new CopyOnWriteArrayList<>(); + @SuppressWarnings("initialization") private CopyOnWriteArrayList oneItemInstance = new CopyOnWriteArrayList<>(oneItemArray); private byte[] emptyInstanceBytes; private byte[] oneInstanceBytes; + @SuppressWarnings("initialization") public CopyOnWriteArrayListBenchmark() { try { emptyInstanceBytes = getSerializedBytes(emptyInstance); diff --git a/test/micro/org/openjdk/bench/java/util/logging/LoggingRuntimeMicros.java b/test/micro/org/openjdk/bench/java/util/logging/LoggingRuntimeMicros.java index edd2b095dbe..f6a97691447 100644 --- a/test/micro/org/openjdk/bench/java/util/logging/LoggingRuntimeMicros.java +++ b/test/micro/org/openjdk/bench/java/util/logging/LoggingRuntimeMicros.java @@ -53,6 +53,7 @@ public static class TestHandler extends java.util.logging.Handler { private final java.util.logging.Logger logger; private volatile LogRecord record; + @SuppressWarnings("initialization") public TestHandler() { // Each instance uses its own logger logger = java.util.logging.Logger.getLogger("StackWalkBench" + serialNum.incrementAndGet()); diff --git a/test/micro/org/openjdk/bench/java/util/stream/tasks/IntegerDuplicate/IntegerDuplicateProblem.java b/test/micro/org/openjdk/bench/java/util/stream/tasks/IntegerDuplicate/IntegerDuplicateProblem.java index 718a7ed36a9..a9670fb10a0 100644 --- a/test/micro/org/openjdk/bench/java/util/stream/tasks/IntegerDuplicate/IntegerDuplicateProblem.java +++ b/test/micro/org/openjdk/bench/java/util/stream/tasks/IntegerDuplicate/IntegerDuplicateProblem.java @@ -30,6 +30,7 @@ public class IntegerDuplicateProblem { private final Integer[] data = new Integer[DATA_SIZE]; + @SuppressWarnings("initialization") public IntegerDuplicateProblem() { // use fixed seed to reduce run-to-run variance Random rand = new Random(0x30052012); diff --git a/test/micro/org/openjdk/bench/java/util/stream/tasks/IntegerMax/IntegerMaxProblem.java b/test/micro/org/openjdk/bench/java/util/stream/tasks/IntegerMax/IntegerMaxProblem.java index d7ae317ce0c..1413ab5480e 100644 --- a/test/micro/org/openjdk/bench/java/util/stream/tasks/IntegerMax/IntegerMaxProblem.java +++ b/test/micro/org/openjdk/bench/java/util/stream/tasks/IntegerMax/IntegerMaxProblem.java @@ -30,6 +30,7 @@ public class IntegerMaxProblem { private final Integer[] data = new Integer[DATA_SIZE]; + @SuppressWarnings("initialization") public IntegerMaxProblem() { // use fixed seed to reduce run-to-run variance Random rand = new Random(0x30052012); diff --git a/test/micro/org/openjdk/bench/java/util/stream/tasks/IntegerSum/IntegerSumProblem.java b/test/micro/org/openjdk/bench/java/util/stream/tasks/IntegerSum/IntegerSumProblem.java index 3eede68936d..9fafdca2a38 100644 --- a/test/micro/org/openjdk/bench/java/util/stream/tasks/IntegerSum/IntegerSumProblem.java +++ b/test/micro/org/openjdk/bench/java/util/stream/tasks/IntegerSum/IntegerSumProblem.java @@ -30,6 +30,7 @@ public class IntegerSumProblem { private final Integer[] data = new Integer[DATA_SIZE]; + @SuppressWarnings("initialization") public IntegerSumProblem() { // use fixed seed to reduce run-to-run variance Random rand = new Random(0x30052012); diff --git a/test/micro/org/openjdk/bench/java/util/stream/tasks/PrimesSieve/PrimesSieveProblem.java b/test/micro/org/openjdk/bench/java/util/stream/tasks/PrimesSieve/PrimesSieveProblem.java index 8e489e6bbca..a34c1929434 100644 --- a/test/micro/org/openjdk/bench/java/util/stream/tasks/PrimesSieve/PrimesSieveProblem.java +++ b/test/micro/org/openjdk/bench/java/util/stream/tasks/PrimesSieve/PrimesSieveProblem.java @@ -28,6 +28,7 @@ public class PrimesSieveProblem { private final Integer[] data = new Integer[DATA_SIZE]; + @SuppressWarnings("initialization") public PrimesSieveProblem() { for (int i = 0; i < data.length; i++) { data[i] = i; diff --git a/test/micro/org/openjdk/bench/javax/crypto/full/PolynomialP256Bench.java b/test/micro/org/openjdk/bench/javax/crypto/full/PolynomialP256Bench.java index 34a6bd761ff..def7d1f35b5 100644 --- a/test/micro/org/openjdk/bench/javax/crypto/full/PolynomialP256Bench.java +++ b/test/micro/org/openjdk/bench/javax/crypto/full/PolynomialP256Bench.java @@ -53,7 +53,9 @@ public class PolynomialP256Bench { final IntegerPolynomialP256 residueField = IntegerPolynomialP256.ONE; final BigInteger refx = new BigInteger("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16); + @SuppressWarnings("initialization") final ImmutableIntegerModuloP x = residueField.getElement(refx); + @SuppressWarnings("initialization") final ImmutableIntegerModuloP X = montField.getElement(refx); final ImmutableIntegerModuloP one = montField.get1(); diff --git a/test/micro/org/openjdk/bench/jdk/classfile/CodeAttributeTools.java b/test/micro/org/openjdk/bench/jdk/classfile/CodeAttributeTools.java index f16535ba3ae..cf2a85feee1 100644 --- a/test/micro/org/openjdk/bench/jdk/classfile/CodeAttributeTools.java +++ b/test/micro/org/openjdk/bench/jdk/classfile/CodeAttributeTools.java @@ -41,6 +41,7 @@ import jdk.internal.classfile.impl.SplitConstantPool; import jdk.internal.classfile.impl.StackCounter; import jdk.internal.classfile.impl.StackMapGenerator; +import jdk.internal.classfile.impl.WritableField; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -110,6 +111,7 @@ public void benchmarkStackMapsGenerator(Blackhole bh) { d.bytecode(), (SplitConstantPool)d.constantPool(), (ClassFileImpl)ClassFile.of(), + WritableField.UnsetField.EMPTY_ARRAY, d.handlers())); } diff --git a/test/micro/org/openjdk/bench/valhalla/ackermann/AckermannBase.java b/test/micro/org/openjdk/bench/valhalla/ackermann/AckermannBase.java new file mode 100644 index 00000000000..55e44dd8a33 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/ackermann/AckermannBase.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.ackermann; + +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@Fork(3) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public abstract class AckermannBase { + + // ackermann(1,1748) + ackermann(2,1897) + ackermann(3,8) == 9999999 invocations + // max depth - 3798 + public static final int X1 = 1; + public static final int Y1 = 1748; + public static final int X2 = 2; + public static final int Y2 = 1897; + public static final int X3 = 3; + public static final int Y3 = 8; + + public static final int OPI = 9999999; + +} diff --git a/test/micro/org/openjdk/bench/valhalla/ackermann/Identity.java b/test/micro/org/openjdk/bench/valhalla/ackermann/Identity.java new file mode 100644 index 00000000000..0ad0836fc23 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/ackermann/Identity.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.ackermann; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.OperationsPerInvocation; + +public class Identity extends AckermannBase { + + private static IdentityLong ack_identity(IdentityLong x, IdentityLong y) { + return x.value() == 0 ? + IdentityLong.valueOf(y.value() + 1) : + (y.value() == 0 ? + ack_identity(IdentityLong.valueOf(x.value() - 1), IdentityLong.valueOf(1)) : + ack_identity(IdentityLong.valueOf(x.value() - 1), ack_identity(x, IdentityLong.valueOf(y.value() - 1)))); + } + + @Benchmark + @OperationsPerInvocation(OPI) + public long ackermann() { + return ack_identity(IdentityLong.valueOf(X1), IdentityLong.valueOf(Y1)).value() + + ack_identity(IdentityLong.valueOf(X2), IdentityLong.valueOf(Y2)).value() + + ack_identity(IdentityLong.valueOf(X3), IdentityLong.valueOf(Y3)).value(); + } + + public static class IdentityLong { + + public final long v0; + + public IdentityLong(long v0) { + this.v0 = v0; + } + + public long value() { + return v0; + } + + public static IdentityLong valueOf(long value) { + return new IdentityLong(value); + } + + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/ackermann/Primitive.java b/test/micro/org/openjdk/bench/valhalla/ackermann/Primitive.java new file mode 100644 index 00000000000..85f0e326a3f --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/ackermann/Primitive.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.ackermann; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.OperationsPerInvocation; + +public class Primitive extends AckermannBase { + + private static long ack_primitive(long x, long y) { + return x == 0 ? + y + 1 : + (y == 0 ? + ack_primitive(x - 1, 1) : + ack_primitive(x - 1, ack_primitive(x, y - 1))); + } + + @Benchmark + @OperationsPerInvocation(OPI) + public long ackermann() { + return ack_primitive(X1, Y1) + + ack_primitive(X2, Y2) + + ack_primitive(X3, Y3); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/ackermann/Value.java b/test/micro/org/openjdk/bench/valhalla/ackermann/Value.java new file mode 100644 index 00000000000..384bd1b2e72 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/ackermann/Value.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.ackermann; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.OperationsPerInvocation; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value extends AckermannBase { + + private static ValueLong ack_value(ValueLong x, ValueLong y) { + return x.value() == 0 ? + ValueLong.valueOf(y.value() + 1) : + (y.value() == 0 ? + ack_value(ValueLong.valueOf(x.value() - 1), ValueLong.valueOf(1)) : + ack_value(ValueLong.valueOf(x.value() - 1), ack_value(x, ValueLong.valueOf(y.value() - 1)))); + } + + @Benchmark + @OperationsPerInvocation(OPI) + public long ackermann_value() { + return ack_value(ValueLong.valueOf(X1), ValueLong.valueOf(Y1)).value() + + ack_value(ValueLong.valueOf(X2), ValueLong.valueOf(Y2)).value() + + ack_value(ValueLong.valueOf(X3), ValueLong.valueOf(Y3)).value(); + } + + private static InterfaceLong ack_value_as_Int(InterfaceLong x, InterfaceLong y) { + return x.value() == 0 ? + ValueLong.valueOf(y.value() + 1) : + (y.value() == 0 ? + ack_value_as_Int(ValueLong.valueOf(x.value() - 1), ValueLong.valueOf(1)) : + ack_value_as_Int(ValueLong.valueOf(x.value() - 1), ack_value_as_Int(x, ValueLong.valueOf(y.value() - 1)))); + } + + @Benchmark + @OperationsPerInvocation(OPI) + public long ackermann_interface() { + return ack_value_as_Int(ValueLong.valueOf(X1), ValueLong.valueOf(Y1)).value() + + ack_value_as_Int(ValueLong.valueOf(X2), ValueLong.valueOf(Y2)).value() + + ack_value_as_Int(ValueLong.valueOf(X3), ValueLong.valueOf(Y3)).value(); + } + + public static interface InterfaceLong { + public long value(); + } + + public static value class ValueLong implements InterfaceLong { + + public final long v0; + + public ValueLong(long v0) { + this.v0 = v0; + } + + public long value() { + return v0; + } + + public static ValueLong valueOf(long value) { + return new ValueLong(value); + } + + } + + +} diff --git a/test/micro/org/openjdk/bench/valhalla/acmp/array/Identity.java b/test/micro/org/openjdk/bench/valhalla/acmp/array/Identity.java new file mode 100644 index 00000000000..1ced050a126 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/acmp/array/Identity.java @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.acmp.array; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.BitSet; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/* + * For proper results it should be executed: + * java -jar target/benchmarks.jar org.openjdk.bench.valhalla.acmp.array.Identity -wmb "org.openjdk.bench.valhalla.acmp.array.Identity.*050" + */ + +@Fork(3) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Identity { + + public static final int SIZE = 100; + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int cmp_branch_obj(Object[] objects1, Object[] objects2) { + int s = 0; + for (int i = 0; i < SIZE; i++) { + if (objects1[i] == objects2[i]) { + s += 1; + } else { + s -= 1; + } + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int cmp_branch_ref(IdentityInt[] objects1, IdentityInt[] objects2) { + int s = 0; + for (int i = 0; i < SIZE; i++) { + if (objects1[i] == objects2[i]) { + s += 1; + } else { + s -= 1; + } + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static boolean cmp_result_ref(IdentityInt[] objects1, IdentityInt[] objects2) { + boolean s = false; + for (int i = 0; i < SIZE; i++) { + s ^= objects1[i] == objects2[i]; + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static boolean cmp_result_obj(Object[] objects1, Object[] objects2) { + boolean s = false; + for (int i = 0; i < SIZE; i++) { + s ^= objects1[i] == objects2[i]; + } + return s; + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals000(ObjState00 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals025(ObjState25 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals050(ObjState50 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals075(ObjState75 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals100(ObjState100 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_ref_equals000(RefState00 st) { + return cmp_branch_ref(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_ref_equals025(RefState25 st) { + return cmp_branch_ref(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_ref_equals050(RefState50 st) { + return cmp_branch_ref(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_ref_equals075(RefState75 st) { + return cmp_branch_ref(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_ref_equals100(RefState100 st) { + return cmp_branch_ref(st.arr1, st.arr2); + } + + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals000(ObjState00 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals025(ObjState25 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals050(ObjState50 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals075(ObjState75 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals100(ObjState100 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_ref_equals000(RefState00 st) { + return cmp_result_ref(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_ref_equals025(RefState25 st) { + return cmp_result_ref(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_ref_equals050(RefState50 st) { + return cmp_result_ref(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_ref_equals075(RefState75 st) { + return cmp_result_ref(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_ref_equals100(RefState100 st) { + return cmp_result_ref(st.arr1, st.arr2); + } + + public static class IdentityInt { + + public final int v0; + + public IdentityInt(int v0) { + this.v0 = v0; + } + + public int value() { + return v0; + } + + } + + private static void populate(Object[] arr1, Object[] arr2, int eq) { + if (eq <= 0) { + arr1[0] = null; + arr2[0] = new IdentityInt(1); + arr1[1] = new IdentityInt(2); + arr2[1] = null; + for (int i = 2; i < SIZE; i++) { + arr1[i] = new IdentityInt(2 * i); + arr2[i] = new IdentityInt(2 * i + 1); + } + } else if (eq >= 100) { + arr2[0] = arr1[0] = null; + for (int i = 1; i < SIZE; i++) { + arr2[i] = arr1[i] = new IdentityInt(i); + } + } else { + BitSet eqset = new Random(42).ints(0, SIZE).distinct().limit(eq * SIZE / 100).collect(BitSet::new, BitSet::set, BitSet::or); + boolean samenulls = true; + int distinctnulls = 0; + for (int i = 0; i < SIZE; i++) { + if (eqset.get(i)) { + if(samenulls) { + arr2[i] = arr1[i] = null; + samenulls = false; + } else { + arr2[i] = arr1[i] = new IdentityInt(i); + } + } else { + switch (distinctnulls) { + case 0: + arr1[i] = null; + arr2[i] = new IdentityInt(2 * i + 1); + distinctnulls = 1; + break; + case 1: + arr1[i] = new IdentityInt(2 * i); + arr2[i] = null; + distinctnulls = 2; + break; + default: + arr1[i] = new IdentityInt(2 * i); + arr2[i] = new IdentityInt(2 * i + 1); + break; + } + } + } + + } + } + + @State(Scope.Thread) + public abstract static class ObjState { + Object[] arr1, arr2; + + public void setup(int eq) { + arr1 = new Object[SIZE]; + arr2 = new Object[SIZE]; + populate(arr1, arr2, eq); + } + } + + @State(Scope.Thread) + public abstract static class RefState { + IdentityInt[] arr1, arr2; + + public void setup(int eq) { + arr1 = new IdentityInt[SIZE]; + arr2 = new IdentityInt[SIZE]; + populate(arr1, arr2, eq); + } + } + + public static class ObjState00 extends ObjState { + @Setup + public void setup() { + setup(0); + } + } + + public static class ObjState25 extends ObjState { + @Setup + public void setup() { + setup(25); + } + } + + public static class ObjState50 extends ObjState { + @Setup + public void setup() { + setup(50); + } + } + + public static class ObjState75 extends ObjState { + @Setup + public void setup() { + setup(75); + } + } + + public static class ObjState100 extends ObjState { + @Setup + public void setup() { + setup(100); + } + } + + public static class RefState00 extends RefState { + @Setup + public void setup() { + setup(0); + } + } + + public static class RefState25 extends RefState { + @Setup + public void setup() { + setup(25); + } + } + + public static class RefState50 extends RefState { + @Setup + public void setup() { + setup(50); + } + } + + public static class RefState75 extends RefState { + @Setup + public void setup() { + setup(75); + } + } + + public static class RefState100 extends RefState { + @Setup + public void setup() { + setup(100); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/acmp/array/Value032.java b/test/micro/org/openjdk/bench/valhalla/acmp/array/Value032.java new file mode 100644 index 00000000000..b76b5184c91 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/acmp/array/Value032.java @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.acmp.array; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.BitSet; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/* + * For proper results it should be executed: + * java -jar target/benchmarks.jar org.openjdk.bench.valhalla.acmp.array.Value -wmb "org.openjdk.bench.valhalla.acmp.array.Value.*050" + */ + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Value032 { + + public static final int SIZE = 100; + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int cmp_branch_obj(Object[] objects1, Object[] objects2) { + int s = 0; + for (int i = 0; i < SIZE; i++) { + if (objects1[i] == objects2[i]) { + s += 1; + } else { + s -= 1; + } + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int cmp_branch_val(ValueInt[] objects1, ValueInt[] objects2) { + int s = 0; + for (int i = 0; i < SIZE; i++) { + if (objects1[i] == objects2[i]) { + s += 1; + } else { + s -= 1; + } + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static boolean cmp_result_val(ValueInt[] objects1, ValueInt[] objects2) { + boolean s = false; + for (int i = 0; i < SIZE; i++) { + s ^= objects1[i] == objects2[i]; + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static boolean cmp_result_obj(Object[] objects1, Object[] objects2) { + boolean s = false; + for (int i = 0; i < SIZE; i++) { + s ^= objects1[i] == objects2[i]; + } + return s; + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals000(ObjState00 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals025(ObjState25 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals050(ObjState50 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals075(ObjState75 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals100(ObjState100 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals000(ValState00 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals025(ValState25 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals050(ValState50 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals075(ValState75 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals100(ValState100 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals000(ObjState00 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals025(ObjState25 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals050(ObjState50 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals075(ObjState75 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals100(ObjState100 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals000(ValState00 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals025(ValState25 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals050(ValState50 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals075(ValState75 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals100(ValState100 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + public static value class ValueInt { + + public final int v0; + + public ValueInt(int v0) { + this.v0 = v0; + } + + public int value() { + return v0; + } + + } + + private static void populate(Object[] arr1, Object[] arr2, int eq) { + if (eq <= 0) { + arr1[0] = null; + arr2[0] = new ValueInt(1); + arr1[1] = new ValueInt(2); + arr2[1] = null; + for (int i = 2; i < SIZE; i++) { + arr1[i] = new ValueInt(2 * i); + arr2[i] = new ValueInt(2 * i + 1); + } + } else if (eq >= 100) { + arr2[0] = arr1[0] = null; + for (int i = 1; i < SIZE; i++) { + arr2[i] = arr1[i] = new ValueInt(i); + } + } else { + BitSet eqset = new Random(42).ints(0, SIZE).distinct().limit(eq * SIZE / 100).collect(BitSet::new, BitSet::set, BitSet::or); + boolean samenulls = true; + int distinctnulls = 0; + for (int i = 0; i < SIZE; i++) { + if (eqset.get(i)) { + if(samenulls) { + arr2[i] = arr1[i] = null; + samenulls = false; + } else { + arr2[i] = arr1[i] = new ValueInt(i); + } + } else { + switch (distinctnulls) { + case 0: + arr1[i] = null; + arr2[i] = new ValueInt(2 * i + 1); + distinctnulls = 1; + break; + case 1: + arr1[i] = new ValueInt(2 * i); + arr2[i] = null; + distinctnulls = 2; + break; + default: + arr1[i] = new ValueInt(2 * i); + arr2[i] = new ValueInt(2 * i + 1); + break; + } + } + } + + } + } + + @State(Scope.Thread) + public abstract static class ObjState { + Object[] arr1, arr2; + + public void setup(int eq) { + arr1 = new Object[SIZE]; + arr2 = new Object[SIZE]; + populate(arr1, arr2, eq); + } + } + + @State(Scope.Thread) + public abstract static class ValState { + ValueInt[] arr1, arr2; + + public void setup(int eq) { + arr1 = new ValueInt[SIZE]; + arr2 = new ValueInt[SIZE]; + populate(arr1, arr2, eq); + } + } + + public static class ObjState00 extends ObjState { + @Setup + public void setup() { + setup(0); + } + } + + public static class ObjState25 extends ObjState { + @Setup + public void setup() { + setup(25); + } + } + + public static class ObjState50 extends ObjState { + @Setup + public void setup() { + setup(50); + } + } + + public static class ObjState75 extends ObjState { + @Setup + public void setup() { + setup(75); + } + } + + public static class ObjState100 extends ObjState { + @Setup + public void setup() { + setup(100); + } + } + + public static class ValState00 extends ValState { + @Setup + public void setup() { + setup(0); + } + } + + public static class ValState25 extends ValState { + @Setup + public void setup() { + setup(25); + } + } + + public static class ValState50 extends ValState { + @Setup + public void setup() { + setup(50); + } + } + + public static class ValState75 extends ValState { + @Setup + public void setup() { + setup(75); + } + } + + public static class ValState100 extends ValState { + @Setup + public void setup() { + setup(100); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/acmp/array/Value032NullFree.java b/test/micro/org/openjdk/bench/valhalla/acmp/array/Value032NullFree.java new file mode 100644 index 00000000000..eeb66b6eeef --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/acmp/array/Value032NullFree.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.acmp.array; + +import jdk.internal.value.ValueClass; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.BitSet; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/* + * For proper results it should be executed: + * java -jar target/benchmarks.jar org.openjdk.bench.valhalla.acmp.array.Value -wmb "org.openjdk.bench.valhalla.acmp.array.Value.*050" + */ + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Value032NullFree { + + public static final int SIZE = 100; + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int cmp_branch_val(ValueInt[] objects1, ValueInt[] objects2) { + int s = 0; + for (int i = 0; i < SIZE; i++) { + if (objects1[i] == objects2[i]) { + s += 1; + } else { + s -= 1; + } + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static boolean cmp_result_val(ValueInt[] objects1, ValueInt[] objects2) { + boolean s = false; + for (int i = 0; i < SIZE; i++) { + s ^= objects1[i] == objects2[i]; + } + return s; + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals000(ValState00 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals025(ValState25 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals050(ValState50 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals075(ValState75 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals100(ValState100 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals000(ValState00 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals025(ValState25 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals050(ValState50 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals075(ValState75 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals100(ValState100 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + public static value class ValueInt { + + public final int v0; + + public ValueInt(int v0) { + this.v0 = v0; + } + + public int value() { + return v0; + } + + } + + private static void populate(Object[] arr1, Object[] arr2, int eq) { + if (eq <= 0) { + for (int i = 0; i < SIZE; i++) { + arr1[i] = new ValueInt(2 * i); + arr2[i] = new ValueInt(2 * i + 1); + } + } else if (eq >= 100) { + for (int i = 0; i < SIZE; i++) { + arr2[i] = arr1[i] = new ValueInt(i); + } + } else { + BitSet eqset = new Random(42).ints(0, SIZE).distinct().limit(eq * SIZE / 100).collect(BitSet::new, BitSet::set, BitSet::or); + for (int i = 0; i < SIZE; i++) { + if (eqset.get(i)) { + arr2[i] = arr1[i] = new ValueInt(i); + } else { + arr1[i] = new ValueInt(2 * i); + arr2[i] = new ValueInt(2 * i + 1); + } + } + } + } + + @State(Scope.Thread) + public abstract static class ValState { + ValueInt[] arr1, arr2; + + public void setup(int eq) { + arr1 = (ValueInt[]) ValueClass.newNullRestrictedAtomicArray(ValueInt.class, SIZE, new ValueInt(0)); + arr2 = (ValueInt[]) ValueClass.newNullRestrictedAtomicArray(ValueInt.class, SIZE, new ValueInt(0)); + populate(arr1, arr2, eq); + } + } + + public static class ValState00 extends ValState { + @Setup + public void setup() { + setup(0); + } + } + + public static class ValState25 extends ValState { + @Setup + public void setup() { + setup(25); + } + } + + public static class ValState50 extends ValState { + @Setup + public void setup() { + setup(50); + } + } + + public static class ValState75 extends ValState { + @Setup + public void setup() { + setup(75); + } + } + + public static class ValState100 extends ValState { + @Setup + public void setup() { + setup(100); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/acmp/array/Value032NullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/acmp/array/Value032NullFreeNonAtomic.java new file mode 100644 index 00000000000..cee0b51d527 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/acmp/array/Value032NullFreeNonAtomic.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.acmp.array; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.BitSet; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/* + * For proper results it should be executed: + * java -jar target/benchmarks.jar org.openjdk.bench.valhalla.acmp.array.Value -wmb "org.openjdk.bench.valhalla.acmp.array.Value.*050" + */ + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Value032NullFreeNonAtomic { + + public static final int SIZE = 100; + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int cmp_branch_val(ValueInt[] objects1, ValueInt[] objects2) { + int s = 0; + for (int i = 0; i < SIZE; i++) { + if (objects1[i] == objects2[i]) { + s += 1; + } else { + s -= 1; + } + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static boolean cmp_result_val(ValueInt[] objects1, ValueInt[] objects2) { + boolean s = false; + for (int i = 0; i < SIZE; i++) { + s ^= objects1[i] == objects2[i]; + } + return s; + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals000(ValState00 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals025(ValState25 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals050(ValState50 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals075(ValState75 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals100(ValState100 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals000(ValState00 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals025(ValState25 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals050(ValState50 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals075(ValState75 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals100(ValState100 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @LooselyConsistentValue + public static value class ValueInt { + + public final int v0; + + public ValueInt(int v0) { + this.v0 = v0; + } + + public int value() { + return v0; + } + + } + + private static void populate(Object[] arr1, Object[] arr2, int eq) { + if (eq <= 0) { + for (int i = 0; i < SIZE; i++) { + arr1[i] = new ValueInt(2 * i); + arr2[i] = new ValueInt(2 * i + 1); + } + } else if (eq >= 100) { + for (int i = 0; i < SIZE; i++) { + arr2[i] = arr1[i] = new ValueInt(i); + } + } else { + BitSet eqset = new Random(42).ints(0, SIZE).distinct().limit(eq * SIZE / 100).collect(BitSet::new, BitSet::set, BitSet::or); + for (int i = 0; i < SIZE; i++) { + if (eqset.get(i)) { + arr2[i] = arr1[i] = new ValueInt(i); + } else { + arr1[i] = new ValueInt(2 * i); + arr2[i] = new ValueInt(2 * i + 1); + } + } + } + } + + @State(Scope.Thread) + public abstract static class ValState { + ValueInt[] arr1, arr2; + + public void setup(int eq) { + arr1 = (ValueInt[]) ValueClass.newNullRestrictedNonAtomicArray(ValueInt.class, SIZE, new ValueInt(0)); + arr2 = (ValueInt[]) ValueClass.newNullRestrictedNonAtomicArray(ValueInt.class, SIZE, new ValueInt(0)); + populate(arr1, arr2, eq); + } + } + + public static class ValState00 extends ValState { + @Setup + public void setup() { + setup(0); + } + } + + public static class ValState25 extends ValState { + @Setup + public void setup() { + setup(25); + } + } + + public static class ValState50 extends ValState { + @Setup + public void setup() { + setup(50); + } + } + + public static class ValState75 extends ValState { + @Setup + public void setup() { + setup(75); + } + } + + public static class ValState100 extends ValState { + @Setup + public void setup() { + setup(100); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/acmp/array/Value128.java b/test/micro/org/openjdk/bench/valhalla/acmp/array/Value128.java new file mode 100644 index 00000000000..82a77606007 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/acmp/array/Value128.java @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.acmp.array; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.BitSet; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/* + * For proper results it should be executed: + * java -jar target/benchmarks.jar org.openjdk.bench.valhalla.acmp.array.Value -wmb "org.openjdk.bench.valhalla.acmp.array.Value.*050" + */ + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Value128 { + + public static final int SIZE = 100; + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int cmp_branch_obj(Object[] objects1, Object[] objects2) { + int s = 0; + for (int i = 0; i < SIZE; i++) { + if (objects1[i] == objects2[i]) { + s += 1; + } else { + s -= 1; + } + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int cmp_branch_val(ValueInt4[] objects1, ValueInt4[] objects2) { + int s = 0; + for (int i = 0; i < SIZE; i++) { + if (objects1[i] == objects2[i]) { + s += 1; + } else { + s -= 1; + } + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static boolean cmp_result_val(ValueInt4[] objects1, ValueInt4[] objects2) { + boolean s = false; + for (int i = 0; i < SIZE; i++) { + s ^= objects1[i] == objects2[i]; + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static boolean cmp_result_obj(Object[] objects1, Object[] objects2) { + boolean s = false; + for (int i = 0; i < SIZE; i++) { + s ^= objects1[i] == objects2[i]; + } + return s; + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals000(ObjState00 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals025(ObjState25 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals050(ObjState50 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals075(ObjState75 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals100(ObjState100 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals000(ValState00 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals025(ValState25 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals050(ValState50 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals075(ValState75 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals100(ValState100 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals000(ObjState00 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals025(ObjState25 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals050(ObjState50 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals075(ObjState75 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals100(ObjState100 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals000(ValState00 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals025(ValState25 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals050(ValState50 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals075(ValState75 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals100(ValState100 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + public static value class ValueInt4 { + + public final int v0; + public final int v1; + public final int v2; + public final int v3; + + public ValueInt4(int v) { + this.v0 = v; + this.v1 = v + 1; + this.v2 = v + 2; + this.v3 = v + 3; + } + + } + + private static void populate(Object[] arr1, Object[] arr2, int eq) { + if (eq <= 0) { + arr1[0] = null; + arr2[0] = new ValueInt4(1); + arr1[1] = new ValueInt4(2); + arr2[1] = null; + for (int i = 2; i < SIZE; i++) { + arr1[i] = new ValueInt4(2 * i); + arr2[i] = new ValueInt4(2 * i + 1); + } + } else if (eq >= 100) { + arr2[0] = arr1[0] = null; + for (int i = 1; i < SIZE; i++) { + arr2[i] = arr1[i] = new ValueInt4(i); + } + } else { + BitSet eqset = new Random(42).ints(0, SIZE).distinct().limit(eq * SIZE / 100).collect(BitSet::new, BitSet::set, BitSet::or); + boolean samenulls = true; + int distinctnulls = 0; + for (int i = 0; i < SIZE; i++) { + if (eqset.get(i)) { + if(samenulls) { + arr2[i] = arr1[i] = null; + samenulls = false; + } else { + arr2[i] = arr1[i] = new ValueInt4(i); + } + } else { + switch (distinctnulls) { + case 0: + arr1[i] = null; + arr2[i] = new ValueInt4(2 * i + 1); + distinctnulls = 1; + break; + case 1: + arr1[i] = new ValueInt4(2 * i); + arr2[i] = null; + distinctnulls = 2; + break; + default: + arr1[i] = new ValueInt4(2 * i); + arr2[i] = new ValueInt4(2 * i + 1); + break; + } + } + } + + } + } + + @State(Scope.Thread) + public abstract static class ObjState { + Object[] arr1, arr2; + + public void setup(int eq) { + arr1 = new Object[SIZE]; + arr2 = new Object[SIZE]; + populate(arr1, arr2, eq); + } + } + + @State(Scope.Thread) + public abstract static class ValState { + ValueInt4[] arr1, arr2; + + public void setup(int eq) { + arr1 = new ValueInt4[SIZE]; + arr2 = new ValueInt4[SIZE]; + populate(arr1, arr2, eq); + } + } + + public static class ObjState00 extends ObjState { + @Setup + public void setup() { + setup(0); + } + } + + public static class ObjState25 extends ObjState { + @Setup + public void setup() { + setup(25); + } + } + + public static class ObjState50 extends ObjState { + @Setup + public void setup() { + setup(50); + } + } + + public static class ObjState75 extends ObjState { + @Setup + public void setup() { + setup(75); + } + } + + public static class ObjState100 extends ObjState { + @Setup + public void setup() { + setup(100); + } + } + + public static class ValState00 extends ValState { + @Setup + public void setup() { + setup(0); + } + } + + public static class ValState25 extends ValState { + @Setup + public void setup() { + setup(25); + } + } + + public static class ValState50 extends ValState { + @Setup + public void setup() { + setup(50); + } + } + + public static class ValState75 extends ValState { + @Setup + public void setup() { + setup(75); + } + } + + public static class ValState100 extends ValState { + @Setup + public void setup() { + setup(100); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/acmp/array/Value128NullFree.java b/test/micro/org/openjdk/bench/valhalla/acmp/array/Value128NullFree.java new file mode 100644 index 00000000000..6e2c0ad0af7 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/acmp/array/Value128NullFree.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.acmp.array; + +import jdk.internal.value.ValueClass; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.BitSet; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/* + * For proper results it should be executed: + * java -jar target/benchmarks.jar org.openjdk.bench.valhalla.acmp.array.Value -wmb "org.openjdk.bench.valhalla.acmp.array.Value.*050" + */ + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Value128NullFree { + + public static final int SIZE = 100; + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int cmp_branch_val(ValueInt4[] objects1, ValueInt4[] objects2) { + int s = 0; + for (int i = 0; i < SIZE; i++) { + if (objects1[i] == objects2[i]) { + s += 1; + } else { + s -= 1; + } + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static boolean cmp_result_val(ValueInt4[] objects1, ValueInt4[] objects2) { + boolean s = false; + for (int i = 0; i < SIZE; i++) { + s ^= objects1[i] == objects2[i]; + } + return s; + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals000(ValState00 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals025(ValState25 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals050(ValState50 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals075(ValState75 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals100(ValState100 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals000(ValState00 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals025(ValState25 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals050(ValState50 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals075(ValState75 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals100(ValState100 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + public static value class ValueInt4 { + + public final int v0; + public final int v1; + public final int v2; + public final int v3; + + public ValueInt4(int v) { + this.v0 = v; + this.v1 = v + 1; + this.v2 = v + 2; + this.v3 = v + 3; + } + + } + + private static void populate(Object[] arr1, Object[] arr2, int eq) { + if (eq <= 0) { + for (int i = 0; i < SIZE; i++) { + arr1[i] = new ValueInt4(2 * i); + arr2[i] = new ValueInt4(2 * i + 1); + } + } else if (eq >= 100) { + for (int i = 0; i < SIZE; i++) { + arr2[i] = arr1[i] = new ValueInt4(i); + } + } else { + BitSet eqset = new Random(42).ints(0, SIZE).distinct().limit(eq * SIZE / 100).collect(BitSet::new, BitSet::set, BitSet::or); + for (int i = 0; i < SIZE; i++) { + if (eqset.get(i)) { + arr2[i] = arr1[i] = new ValueInt4(i); + } else { + arr1[i] = new ValueInt4(2 * i); + arr2[i] = new ValueInt4(2 * i + 1); + } + } + } + } + + @State(Scope.Thread) + public abstract static class ValState { + ValueInt4[] arr1, arr2; + + public void setup(int eq) { + arr1 = (ValueInt4[]) ValueClass.newNullRestrictedAtomicArray(ValueInt4.class, SIZE, new ValueInt4(0)); + arr2 = (ValueInt4[]) ValueClass.newNullRestrictedAtomicArray(ValueInt4.class, SIZE, new ValueInt4(0)); + populate(arr1, arr2, eq); + } + } + + public static class ValState00 extends ValState { + @Setup + public void setup() { + setup(0); + } + } + + public static class ValState25 extends ValState { + @Setup + public void setup() { + setup(25); + } + } + + public static class ValState50 extends ValState { + @Setup + public void setup() { + setup(50); + } + } + + public static class ValState75 extends ValState { + @Setup + public void setup() { + setup(75); + } + } + + public static class ValState100 extends ValState { + @Setup + public void setup() { + setup(100); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/acmp/array/Value128NullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/acmp/array/Value128NullFreeNonAtomic.java new file mode 100644 index 00000000000..6fee2a97ca2 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/acmp/array/Value128NullFreeNonAtomic.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.acmp.array; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.BitSet; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/* + * For proper results it should be executed: + * java -jar target/benchmarks.jar org.openjdk.bench.valhalla.acmp.array.Value -wmb "org.openjdk.bench.valhalla.acmp.array.Value.*050" + */ + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Value128NullFreeNonAtomic { + + public static final int SIZE = 100; + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int cmp_branch_val(ValueInt4[] objects1, ValueInt4[] objects2) { + int s = 0; + for (int i = 0; i < SIZE; i++) { + if (objects1[i] == objects2[i]) { + s += 1; + } else { + s -= 1; + } + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static boolean cmp_result_val(ValueInt4[] objects1, ValueInt4[] objects2) { + boolean s = false; + for (int i = 0; i < SIZE; i++) { + s ^= objects1[i] == objects2[i]; + } + return s; + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals000(ValState00 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals025(ValState25 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals050(ValState50 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals075(ValState75 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals100(ValState100 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals000(ValState00 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals025(ValState25 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals050(ValState50 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals075(ValState75 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals100(ValState100 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @LooselyConsistentValue + public static value class ValueInt4 { + + public final int v0; + public final int v1; + public final int v2; + public final int v3; + + public ValueInt4(int v) { + this.v0 = v; + this.v1 = v + 1; + this.v2 = v + 2; + this.v3 = v + 3; + } + + } + + private static void populate(Object[] arr1, Object[] arr2, int eq) { + if (eq <= 0) { + for (int i = 0; i < SIZE; i++) { + arr1[i] = new ValueInt4(2 * i); + arr2[i] = new ValueInt4(2 * i + 1); + } + } else if (eq >= 100) { + for (int i = 0; i < SIZE; i++) { + arr2[i] = arr1[i] = new ValueInt4(i); + } + } else { + BitSet eqset = new Random(42).ints(0, SIZE).distinct().limit(eq * SIZE / 100).collect(BitSet::new, BitSet::set, BitSet::or); + for (int i = 0; i < SIZE; i++) { + if (eqset.get(i)) { + arr2[i] = arr1[i] = new ValueInt4(i); + } else { + arr1[i] = new ValueInt4(2 * i); + arr2[i] = new ValueInt4(2 * i + 1); + } + } + } + } + + @State(Scope.Thread) + public abstract static class ValState { + ValueInt4[] arr1, arr2; + + public void setup(int eq) { + arr1 = (ValueInt4[]) ValueClass.newNullRestrictedNonAtomicArray(ValueInt4.class, SIZE, new ValueInt4(0)); + arr2 = (ValueInt4[]) ValueClass.newNullRestrictedNonAtomicArray(ValueInt4.class, SIZE, new ValueInt4(0)); + populate(arr1, arr2, eq); + } + } + + public static class ValState00 extends ValState { + @Setup + public void setup() { + setup(0); + } + } + + public static class ValState25 extends ValState { + @Setup + public void setup() { + setup(25); + } + } + + public static class ValState50 extends ValState { + @Setup + public void setup() { + setup(50); + } + } + + public static class ValState75 extends ValState { + @Setup + public void setup() { + setup(75); + } + } + + public static class ValState100 extends ValState { + @Setup + public void setup() { + setup(100); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/acmp/field/Identity.java b/test/micro/org/openjdk/bench/valhalla/acmp/field/Identity.java new file mode 100644 index 00000000000..d35f716c0e7 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/acmp/field/Identity.java @@ -0,0 +1,473 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.acmp.field; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.BitSet; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/* + * For proper results it should be executed: + * java -jar target/benchmarks.jar org.openjdk.bench.valhalla.acmp.field.Identity -wmb "org.openjdk.bench.valhalla.acmp.field.Identity.*050" + */ + +@Fork(3) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Identity { + + public static final int SIZE = 100; + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int cmp_branch_obj(ObjWrapper[] objects1, ObjWrapper[] objects2) { + int s = 0; + for (int i = 0; i < SIZE; i++) { + if (objects1[i].f == objects2[i].f) { + s += 1; + } else { + s -= 1; + } + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int cmp_branch_ref(RefWrapper[] objects1, RefWrapper[] objects2) { + int s = 0; + for (int i = 0; i < SIZE; i++) { + if (objects1[i].f == objects2[i].f) { + s += 1; + } else { + s -= 1; + } + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static boolean cmp_result_ref(RefWrapper[] objects1, RefWrapper[] objects2) { + boolean s = false; + for (int i = 0; i < SIZE; i++) { + s ^= objects1[i].f == objects2[i].f; + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static boolean cmp_result_obj(ObjWrapper[] objects1, ObjWrapper[] objects2) { + boolean s = false; + for (int i = 0; i < SIZE; i++) { + s ^= objects1[i].f == objects2[i].f; + } + return s; + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals000(ObjState00 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals025(ObjState25 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals050(ObjState50 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals075(ObjState75 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals100(ObjState100 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_ref_equals000(RefState00 st) { + return cmp_branch_ref(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_ref_equals025(RefState25 st) { + return cmp_branch_ref(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_ref_equals050(RefState50 st) { + return cmp_branch_ref(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_ref_equals075(RefState75 st) { + return cmp_branch_ref(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_ref_equals100(RefState100 st) { + return cmp_branch_ref(st.arr1, st.arr2); + } + + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals000(ObjState00 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals025(ObjState25 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals050(ObjState50 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals075(ObjState75 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals100(ObjState100 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_ref_equals000(RefState00 st) { + return cmp_result_ref(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_ref_equals025(RefState25 st) { + return cmp_result_ref(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_ref_equals050(RefState50 st) { + return cmp_result_ref(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_ref_equals075(RefState75 st) { + return cmp_result_ref(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_ref_equals100(RefState100 st) { + return cmp_result_ref(st.arr1, st.arr2); + } + + public static class IdentityInt { + + public final int v0; + + public IdentityInt(int v0) { + this.v0 = v0; + } + + public int value() { + return v0; + } + + } + + private static void populate(ObjWrapper[] arr1, ObjWrapper[] arr2, int eq) { + if (eq <= 0) { + arr1[0] = new ObjWrapper(null); + arr2[0] = new ObjWrapper(new IdentityInt(1)); + arr1[1] = new ObjWrapper(new IdentityInt(2)); + arr2[1] = new ObjWrapper(null); + for (int i = 2; i < SIZE; i++) { + arr1[i] = new ObjWrapper(new IdentityInt(2 * i)); + arr2[i] = new ObjWrapper(new IdentityInt(2 * i + 1)); + } + } else if (eq >= 100) { + arr2[0] = arr1[0] = new ObjWrapper(null); + for (int i = 1; i < SIZE; i++) { + IdentityInt x = new IdentityInt(i); + arr2[i] = new ObjWrapper(x); + arr1[i] = new ObjWrapper(x); + } + } else { + BitSet eqset = new Random(42).ints(0, SIZE).distinct().limit(eq * SIZE / 100).collect(BitSet::new, BitSet::set, BitSet::or); + boolean samenulls = true; + int distinctnulls = 0; + for (int i = 0; i < SIZE; i++) { + if (eqset.get(i)) { + if(samenulls) { + arr2[i] = new ObjWrapper(null); + arr1[i] = new ObjWrapper(null); + samenulls = false; + } else { + IdentityInt x = new IdentityInt(i); + arr2[i] = new ObjWrapper(x); + arr1[i] = new ObjWrapper(x); + } + } else { + switch (distinctnulls) { + case 0: + arr1[i] = new ObjWrapper(null); + arr2[i] = new ObjWrapper(new IdentityInt(2 * i + 1)); + distinctnulls = 1; + break; + case 1: + arr1[i] = new ObjWrapper(new IdentityInt(2 * i)); + arr2[i] = new ObjWrapper(null); + distinctnulls = 2; + break; + default: + arr1[i] = new ObjWrapper(new IdentityInt(2 * i)); + arr2[i] = new ObjWrapper(new IdentityInt(2 * i + 1)); + break; + } + } + } + } + } + + + private static void populate(RefWrapper[] arr1, RefWrapper[] arr2, int eq) { + if (eq <= 0) { + arr1[0] = new RefWrapper(null); + arr2[0] = new RefWrapper(new IdentityInt(1)); + arr1[1] = new RefWrapper(new IdentityInt(2)); + arr2[1] = new RefWrapper(null); + for (int i = 2; i < SIZE; i++) { + arr1[i] = new RefWrapper(new IdentityInt(2 * i)); + arr2[i] = new RefWrapper(new IdentityInt(2 * i + 1)); + } + } else if (eq >= 100) { + arr2[0] = arr1[0] = new RefWrapper(null); + for (int i = 1; i < SIZE; i++) { + IdentityInt x = new IdentityInt(i); + arr2[i] = new RefWrapper(x); + arr1[i] = new RefWrapper(x); + } + } else { + BitSet eqset = new Random(42).ints(0, SIZE).distinct().limit(eq * SIZE / 100).collect(BitSet::new, BitSet::set, BitSet::or); + boolean samenulls = true; + int distinctnulls = 0; + for (int i = 0; i < SIZE; i++) { + if (eqset.get(i)) { + if(samenulls) { + arr2[i] = new RefWrapper(null); + arr1[i] = new RefWrapper(null); + samenulls = false; + } else { + IdentityInt x = new IdentityInt(i); + arr2[i] = new RefWrapper(x); + arr1[i] = new RefWrapper(x); + } + } else { + switch (distinctnulls) { + case 0: + arr1[i] = new RefWrapper(null); + arr2[i] = new RefWrapper(new IdentityInt(2 * i + 1)); + distinctnulls = 1; + break; + case 1: + arr1[i] = new RefWrapper(new IdentityInt(2 * i)); + arr2[i] = new RefWrapper(null); + distinctnulls = 2; + break; + default: + arr1[i] = new RefWrapper(new IdentityInt(2 * i)); + arr2[i] = new RefWrapper(new IdentityInt(2 * i + 1)); + break; + } + } + } + } + } + + public static class ObjWrapper { + public final Object f; + + public ObjWrapper(Object f) { + this.f = f; + } + } + + public static class RefWrapper { + public final IdentityInt f; + + public RefWrapper(IdentityInt f) { + this.f = f; + } + } + + @State(Scope.Thread) + public abstract static class ObjState { + ObjWrapper[] arr1, arr2; + + public void setup(int eq) { + arr1 = new ObjWrapper[SIZE]; + arr2 = new ObjWrapper[SIZE]; + populate(arr1, arr2, eq); + } + } + + @State(Scope.Thread) + public abstract static class RefState { + RefWrapper[] arr1, arr2; + + public void setup(int eq) { + arr1 = new RefWrapper[SIZE]; + arr2 = new RefWrapper[SIZE]; + populate(arr1, arr2, eq); + } + } + + public static class ObjState00 extends ObjState { + @Setup + public void setup() { + setup(0); + } + } + + public static class ObjState25 extends ObjState { + @Setup + public void setup() { + setup(25); + } + } + + public static class ObjState50 extends ObjState { + @Setup + public void setup() { + setup(50); + } + } + + public static class ObjState75 extends ObjState { + @Setup + public void setup() { + setup(75); + } + } + + public static class ObjState100 extends ObjState { + @Setup + public void setup() { + setup(100); + } + } + + public static class RefState00 extends RefState { + @Setup + public void setup() { + setup(0); + } + } + + public static class RefState25 extends RefState { + @Setup + public void setup() { + setup(25); + } + } + + public static class RefState50 extends RefState { + @Setup + public void setup() { + setup(50); + } + } + + public static class RefState75 extends RefState { + @Setup + public void setup() { + setup(75); + } + } + + public static class RefState100 extends RefState { + @Setup + public void setup() { + setup(100); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/acmp/field/Value032.java b/test/micro/org/openjdk/bench/valhalla/acmp/field/Value032.java new file mode 100644 index 00000000000..e413d5bb7e7 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/acmp/field/Value032.java @@ -0,0 +1,473 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.acmp.field; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.BitSet; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/* + * For proper results it should be executed: + * java -jar target/benchmarks.jar org.openjdk.bench.valhalla.acmp.field.Value -wmb "org.openjdk.bench.valhalla.acmp.field.Value.*050" + */ + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Value032 { + + public static final int SIZE = 100; + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int cmp_branch_obj(ObjWrapper[] objects1, ObjWrapper[] objects2) { + int s = 0; + for (int i = 0; i < SIZE; i++) { + if (objects1[i].f == objects2[i].f) { + s += 1; + } else { + s -= 1; + } + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int cmp_branch_val(ValWrapper[] objects1, ValWrapper[] objects2) { + int s = 0; + for (int i = 0; i < SIZE; i++) { + if (objects1[i].f == objects2[i].f) { + s += 1; + } else { + s -= 1; + } + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static boolean cmp_result_val(ValWrapper[] objects1, ValWrapper[] objects2) { + boolean s = false; + for (int i = 0; i < SIZE; i++) { + s ^= objects1[i].f == objects2[i].f; + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static boolean cmp_result_obj(ObjWrapper[] objects1, ObjWrapper[] objects2) { + boolean s = false; + for (int i = 0; i < SIZE; i++) { + s ^= objects1[i].f == objects2[i].f; + } + return s; + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals000(ObjState00 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals025(ObjState25 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals050(ObjState50 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals075(ObjState75 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals100(ObjState100 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals000(ValState00 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals025(ValState25 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals050(ValState50 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals075(ValState75 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals100(ValState100 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals000(ObjState00 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals025(ObjState25 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals050(ObjState50 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals075(ObjState75 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals100(ObjState100 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals000(ValState00 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals025(ValState25 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals050(ValState50 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals075(ValState75 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals100(ValState100 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + public static value class ValueInt { + + public final int v0; + + public ValueInt(int v0) { + this.v0 = v0; + } + + public int value() { + return v0; + } + + } + + private static void populate(ObjWrapper[] arr1, ObjWrapper[] arr2, int eq) { + if (eq <= 0) { + arr1[0] = new ObjWrapper(null); + arr2[0] = new ObjWrapper(new ValueInt(1)); + arr1[1] = new ObjWrapper(new ValueInt(2)); + arr2[1] = new ObjWrapper(null); + for (int i = 2; i < SIZE; i++) { + arr1[i] = new ObjWrapper(new ValueInt(2 * i)); + arr2[i] = new ObjWrapper(new ValueInt(2 * i + 1)); + } + } else if (eq >= 100) { + arr2[0] = arr1[0] = new ObjWrapper(null); + for (int i = 1; i < SIZE; i++) { + ValueInt x = new ValueInt(i); + arr2[i] = new ObjWrapper(x); + arr1[i] = new ObjWrapper(x); + } + } else { + BitSet eqset = new Random(42).ints(0, SIZE).distinct().limit(eq * SIZE / 100).collect(BitSet::new, BitSet::set, BitSet::or); + boolean samenulls = true; + int distinctnulls = 0; + for (int i = 0; i < SIZE; i++) { + if (eqset.get(i)) { + if(samenulls) { + arr2[i] = new ObjWrapper(null); + arr1[i] = new ObjWrapper(null); + samenulls = false; + } else { + ValueInt x = new ValueInt(i); + arr2[i] = new ObjWrapper(x); + arr1[i] = new ObjWrapper(x); + } + } else { + switch (distinctnulls) { + case 0: + arr1[i] = new ObjWrapper(null); + arr2[i] = new ObjWrapper(new ValueInt(2 * i + 1)); + distinctnulls = 1; + break; + case 1: + arr1[i] = new ObjWrapper(new ValueInt(2 * i)); + arr2[i] = new ObjWrapper(null); + distinctnulls = 2; + break; + default: + arr1[i] = new ObjWrapper(new ValueInt(2 * i)); + arr2[i] = new ObjWrapper(new ValueInt(2 * i + 1)); + break; + } + } + } + } + } + + + private static void populate(ValWrapper[] arr1, ValWrapper[] arr2, int eq) { + if (eq <= 0) { + arr1[0] = new ValWrapper(null); + arr2[0] = new ValWrapper(new ValueInt(1)); + arr1[1] = new ValWrapper(new ValueInt(2)); + arr2[1] = new ValWrapper(null); + for (int i = 2; i < SIZE; i++) { + arr1[i] = new ValWrapper(new ValueInt(2 * i)); + arr2[i] = new ValWrapper(new ValueInt(2 * i + 1)); + } + } else if (eq >= 100) { + arr2[0] = arr1[0] = new ValWrapper(null); + for (int i = 1; i < SIZE; i++) { + ValueInt x = new ValueInt(i); + arr2[i] = new ValWrapper(x); + arr1[i] = new ValWrapper(x); + } + } else { + BitSet eqset = new Random(42).ints(0, SIZE).distinct().limit(eq * SIZE / 100).collect(BitSet::new, BitSet::set, BitSet::or); + boolean samenulls = true; + int distinctnulls = 0; + for (int i = 0; i < SIZE; i++) { + if (eqset.get(i)) { + if(samenulls) { + arr2[i] = new ValWrapper(null); + arr1[i] = new ValWrapper(null); + samenulls = false; + } else { + ValueInt x = new ValueInt(i); + arr2[i] = new ValWrapper(x); + arr1[i] = new ValWrapper(x); + } + } else { + switch (distinctnulls) { + case 0: + arr1[i] = new ValWrapper(null); + arr2[i] = new ValWrapper(new ValueInt(2 * i + 1)); + distinctnulls = 1; + break; + case 1: + arr1[i] = new ValWrapper(new ValueInt(2 * i)); + arr2[i] = new ValWrapper(null); + distinctnulls = 2; + break; + default: + arr1[i] = new ValWrapper(new ValueInt(2 * i)); + arr2[i] = new ValWrapper(new ValueInt(2 * i + 1)); + break; + } + } + } + } + } + + public static class ObjWrapper { + public Object f; + + public ObjWrapper(Object f) { + this.f = f; + } + } + + public static class ValWrapper { + public ValueInt f; + + public ValWrapper(ValueInt f) { + this.f = f; + } + } + + @State(Scope.Thread) + public abstract static class ObjState { + ObjWrapper[] arr1, arr2; + + public void setup(int eq) { + arr1 = new ObjWrapper[SIZE]; + arr2 = new ObjWrapper[SIZE]; + populate(arr1, arr2, eq); + } + } + + @State(Scope.Thread) + public abstract static class ValState { + ValWrapper[] arr1, arr2; + + public void setup(int eq) { + arr1 = new ValWrapper[SIZE]; + arr2 = new ValWrapper[SIZE]; + populate(arr1, arr2, eq); + } + } + + public static class ObjState00 extends ObjState { + @Setup + public void setup() { + setup(0); + } + } + + public static class ObjState25 extends ObjState { + @Setup + public void setup() { + setup(25); + } + } + + public static class ObjState50 extends ObjState { + @Setup + public void setup() { + setup(50); + } + } + + public static class ObjState75 extends ObjState { + @Setup + public void setup() { + setup(75); + } + } + + public static class ObjState100 extends ObjState { + @Setup + public void setup() { + setup(100); + } + } + + public static class ValState00 extends ValState { + @Setup + public void setup() { + setup(0); + } + } + + public static class ValState25 extends ValState { + @Setup + public void setup() { + setup(25); + } + } + + public static class ValState50 extends ValState { + @Setup + public void setup() { + setup(50); + } + } + + public static class ValState75 extends ValState { + @Setup + public void setup() { + setup(75); + } + } + + public static class ValState100 extends ValState { + @Setup + public void setup() { + setup(100); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/acmp/field/Value032NullFree.java b/test/micro/org/openjdk/bench/valhalla/acmp/field/Value032NullFree.java new file mode 100644 index 00000000000..bae5a039316 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/acmp/field/Value032NullFree.java @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.acmp.field; + +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.BitSet; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/* + * For proper results it should be executed: + * java -jar target/benchmarks.jar org.openjdk.bench.valhalla.acmp.field.Value -wmb "org.openjdk.bench.valhalla.acmp.field.Value.*050" + * */ + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Value032NullFree { + + public static final int SIZE = 100; + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int cmp_branch_val(ValWrapper[] objects1, ValWrapper[] objects2) { + int s = 0; + for (int i = 0; i < SIZE; i++) { + if (objects1[i].f == objects2[i].f) { + s += 1; + } else { + s -= 1; + } + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static boolean cmp_result_val(ValWrapper[] objects1, ValWrapper[] objects2) { + boolean s = false; + for (int i = 0; i < SIZE; i++) { + s ^= objects1[i].f == objects2[i].f; + } + return s; + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals000(ValState00 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals025(ValState25 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals050(ValState50 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals075(ValState75 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals100(ValState100 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals000(ValState00 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals025(ValState25 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals050(ValState50 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals075(ValState75 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals100(ValState100 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + public static value class ValueInt { + + public final int v0; + + public ValueInt(int v0) { + this.v0 = v0; + } + + public int value() { + return v0; + } + + } + + private static void populate(ValWrapper[] arr1, ValWrapper[] arr2, int eq) { + if (eq <= 0) { + for (int i = 0; i < SIZE; i++) { + arr1[i] = new ValWrapper(new ValueInt(2 * i)); + arr2[i] = new ValWrapper(new ValueInt(2 * i + 1)); + } + } else if (eq >= 100) { + for (int i = 0; i < SIZE; i++) { + ValueInt x = new ValueInt(i); + arr2[i] = new ValWrapper(x); + arr1[i] = new ValWrapper(x); + } + } else { + BitSet eqset = new Random(42).ints(0, SIZE).distinct().limit(eq * SIZE / 100).collect(BitSet::new, BitSet::set, BitSet::or); + for (int i = 0; i < SIZE; i++) { + if (eqset.get(i)) { + ValueInt x = new ValueInt(i); + arr2[i] = new ValWrapper(x); + arr1[i] = new ValWrapper(x); + } else { + arr1[i] = new ValWrapper(new ValueInt(2 * i)); + arr2[i] = new ValWrapper(new ValueInt(2 * i + 1)); + } + } + } + } + + public static class ValWrapper { + + @Strict + @NullRestricted + public final ValueInt f; + + public ValWrapper(ValueInt f) { + this.f = f; + } + } + + @State(Scope.Thread) + public abstract static class ValState { + ValWrapper[] arr1, arr2; + + public void setup(int eq) { + arr1 = new ValWrapper[SIZE]; + arr2 = new ValWrapper[SIZE]; + populate(arr1, arr2, eq); + } + } + + public static class ValState00 extends ValState { + @Setup + public void setup() { + setup(0); + } + } + + public static class ValState25 extends ValState { + @Setup + public void setup() { + setup(25); + } + } + + public static class ValState50 extends ValState { + @Setup + public void setup() { + setup(50); + } + } + + public static class ValState75 extends ValState { + @Setup + public void setup() { + setup(75); + } + } + + public static class ValState100 extends ValState { + @Setup + public void setup() { + setup(100); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/acmp/field/Value032NullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/acmp/field/Value032NullFreeNonAtomic.java new file mode 100644 index 00000000000..634cfe6b517 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/acmp/field/Value032NullFreeNonAtomic.java @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.acmp.field; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.BitSet; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/* + * For proper results it should be executed: + * java -jar target/benchmarks.jar org.openjdk.bench.valhalla.acmp.field.Value -wmb "org.openjdk.bench.valhalla.acmp.field.Value.*050" + */ + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Value032NullFreeNonAtomic { + + public static final int SIZE = 100; + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int cmp_branch_val(ValWrapper[] objects1, ValWrapper[] objects2) { + int s = 0; + for (int i = 0; i < SIZE; i++) { + if (objects1[i].f == objects2[i].f) { + s += 1; + } else { + s -= 1; + } + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static boolean cmp_result_val(ValWrapper[] objects1, ValWrapper[] objects2) { + boolean s = false; + for (int i = 0; i < SIZE; i++) { + s ^= objects1[i].f == objects2[i].f; + } + return s; + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals000(ValState00 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals025(ValState25 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals050(ValState50 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals075(ValState75 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals100(ValState100 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals000(ValState00 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals025(ValState25 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals050(ValState50 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals075(ValState75 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals100(ValState100 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @LooselyConsistentValue + public static value class ValueInt { + + public final int v0; + + public ValueInt(int v0) { + this.v0 = v0; + } + + public int value() { + return v0; + } + + } + + private static void populate(ValWrapper[] arr1, ValWrapper[] arr2, int eq) { + if (eq <= 0) { + for (int i = 0; i < SIZE; i++) { + arr1[i] = new ValWrapper(new ValueInt(2 * i)); + arr2[i] = new ValWrapper(new ValueInt(2 * i + 1)); + } + } else if (eq >= 100) { + for (int i = 0; i < SIZE; i++) { + ValueInt x = new ValueInt(i); + arr2[i] = new ValWrapper(x); + arr1[i] = new ValWrapper(x); + } + } else { + BitSet eqset = new Random(42).ints(0, SIZE).distinct().limit(eq * SIZE / 100).collect(BitSet::new, BitSet::set, BitSet::or); + for (int i = 0; i < SIZE; i++) { + if (eqset.get(i)) { + ValueInt x = new ValueInt(i); + arr2[i] = new ValWrapper(x); + arr1[i] = new ValWrapper(x); + } else { + arr1[i] = new ValWrapper(new ValueInt(2 * i)); + arr2[i] = new ValWrapper(new ValueInt(2 * i + 1)); + } + } + } + } + + public static class ValWrapper { + + @Strict + @NullRestricted + public final ValueInt f; + + public ValWrapper(ValueInt f) { + this.f = f; + } + } + + @State(Scope.Thread) + public abstract static class ValState { + ValWrapper[] arr1, arr2; + + public void setup(int eq) { + arr1 = new ValWrapper[SIZE]; + arr2 = new ValWrapper[SIZE]; + populate(arr1, arr2, eq); + } + } + + public static class ValState00 extends ValState { + @Setup + public void setup() { + setup(0); + } + } + + public static class ValState25 extends ValState { + @Setup + public void setup() { + setup(25); + } + } + + public static class ValState50 extends ValState { + @Setup + public void setup() { + setup(50); + } + } + + public static class ValState75 extends ValState { + @Setup + public void setup() { + setup(75); + } + } + + public static class ValState100 extends ValState { + @Setup + public void setup() { + setup(100); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/acmp/field/Value128.java b/test/micro/org/openjdk/bench/valhalla/acmp/field/Value128.java new file mode 100644 index 00000000000..3559dd3caed --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/acmp/field/Value128.java @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.acmp.field; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.BitSet; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/* + * For proper results it should be executed: + * java -jar target/benchmarks.jar org.openjdk.bench.valhalla.acmp.field.Value -wmb "org.openjdk.bench.valhalla.acmp.field.Value.*050" + */ + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Value128 { + + public static final int SIZE = 100; + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int cmp_branch_obj(ObjWrapper[] objects1, ObjWrapper[] objects2) { + int s = 0; + for (int i = 0; i < SIZE; i++) { + if (objects1[i].f == objects2[i].f) { + s += 1; + } else { + s -= 1; + } + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int cmp_branch_val(ValWrapper[] objects1, ValWrapper[] objects2) { + int s = 0; + for (int i = 0; i < SIZE; i++) { + if (objects1[i].f == objects2[i].f) { + s += 1; + } else { + s -= 1; + } + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static boolean cmp_result_val(ValWrapper[] objects1, ValWrapper[] objects2) { + boolean s = false; + for (int i = 0; i < SIZE; i++) { + s ^= objects1[i].f == objects2[i].f; + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static boolean cmp_result_obj(ObjWrapper[] objects1, ObjWrapper[] objects2) { + boolean s = false; + for (int i = 0; i < SIZE; i++) { + s ^= objects1[i].f == objects2[i].f; + } + return s; + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals000(ObjState00 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals025(ObjState25 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals050(ObjState50 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals075(ObjState75 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_obj_equals100(ObjState100 st) { + return cmp_branch_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals000(ValState00 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals025(ValState25 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals050(ValState50 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals075(ValState75 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals100(ValState100 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals000(ObjState00 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals025(ObjState25 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals050(ObjState50 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals075(ObjState75 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_obj_equals100(ObjState100 st) { + return cmp_result_obj(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals000(ValState00 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals025(ValState25 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals050(ValState50 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals075(ValState75 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals100(ValState100 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + public static value class ValueInt4 { + + public final int v0; + public final int v1; + public final int v2; + public final int v3; + + public ValueInt4(int v) { + this.v0 = v; + this.v1 = v + 1; + this.v2 = v + 2; + this.v3 = v + 3; + } + + } + + private static void populate(ObjWrapper[] arr1, ObjWrapper[] arr2, int eq) { + if (eq <= 0) { + arr1[0] = new ObjWrapper(null); + arr2[0] = new ObjWrapper(new ValueInt4(1)); + arr1[1] = new ObjWrapper(new ValueInt4(2)); + arr2[1] = new ObjWrapper(null); + for (int i = 2; i < SIZE; i++) { + arr1[i] = new ObjWrapper(new ValueInt4(2 * i)); + arr2[i] = new ObjWrapper(new ValueInt4(2 * i + 1)); + } + } else if (eq >= 100) { + arr2[0] = arr1[0] = new ObjWrapper(null); + for (int i = 1; i < SIZE; i++) { + ValueInt4 x = new ValueInt4(i); + arr2[i] = new ObjWrapper(x); + arr1[i] = new ObjWrapper(x); + } + } else { + BitSet eqset = new Random(42).ints(0, SIZE).distinct().limit(eq * SIZE / 100).collect(BitSet::new, BitSet::set, BitSet::or); + boolean samenulls = true; + int distinctnulls = 0; + for (int i = 0; i < SIZE; i++) { + if (eqset.get(i)) { + if(samenulls) { + arr2[i] = new ObjWrapper(null); + arr1[i] = new ObjWrapper(null); + samenulls = false; + } else { + ValueInt4 x = new ValueInt4(i); + arr2[i] = new ObjWrapper(x); + arr1[i] = new ObjWrapper(x); + } + } else { + switch (distinctnulls) { + case 0: + arr1[i] = new ObjWrapper(null); + arr2[i] = new ObjWrapper(new ValueInt4(2 * i + 1)); + distinctnulls = 1; + break; + case 1: + arr1[i] = new ObjWrapper(new ValueInt4(2 * i)); + arr2[i] = new ObjWrapper(null); + distinctnulls = 2; + break; + default: + arr1[i] = new ObjWrapper(new ValueInt4(2 * i)); + arr2[i] = new ObjWrapper(new ValueInt4(2 * i + 1)); + break; + } + } + } + } + } + + + private static void populate(ValWrapper[] arr1, ValWrapper[] arr2, int eq) { + if (eq <= 0) { + arr1[0] = new ValWrapper(null); + arr2[0] = new ValWrapper(new ValueInt4(1)); + arr1[1] = new ValWrapper(new ValueInt4(2)); + arr2[1] = new ValWrapper(null); + for (int i = 2; i < SIZE; i++) { + arr1[i] = new ValWrapper(new ValueInt4(2 * i)); + arr2[i] = new ValWrapper(new ValueInt4(2 * i + 1)); + } + } else if (eq >= 100) { + arr2[0] = arr1[0] = new ValWrapper(null); + for (int i = 1; i < SIZE; i++) { + ValueInt4 x = new ValueInt4(i); + arr2[i] = new ValWrapper(x); + arr1[i] = new ValWrapper(x); + } + } else { + BitSet eqset = new Random(42).ints(0, SIZE).distinct().limit(eq * SIZE / 100).collect(BitSet::new, BitSet::set, BitSet::or); + boolean samenulls = true; + int distinctnulls = 0; + for (int i = 0; i < SIZE; i++) { + if (eqset.get(i)) { + if(samenulls) { + arr2[i] = new ValWrapper(null); + arr1[i] = new ValWrapper(null); + samenulls = false; + } else { + ValueInt4 x = new ValueInt4(i); + arr2[i] = new ValWrapper(x); + arr1[i] = new ValWrapper(x); + } + } else { + switch (distinctnulls) { + case 0: + arr1[i] = new ValWrapper(null); + arr2[i] = new ValWrapper(new ValueInt4(2 * i + 1)); + distinctnulls = 1; + break; + case 1: + arr1[i] = new ValWrapper(new ValueInt4(2 * i)); + arr2[i] = new ValWrapper(null); + distinctnulls = 2; + break; + default: + arr1[i] = new ValWrapper(new ValueInt4(2 * i)); + arr2[i] = new ValWrapper(new ValueInt4(2 * i + 1)); + break; + } + } + } + } + } + + public static class ObjWrapper { + public Object f; + + public ObjWrapper(Object f) { + this.f = f; + } + } + + public static class ValWrapper { + public ValueInt4 f; + + public ValWrapper(ValueInt4 f) { + this.f = f; + } + } + + @State(Scope.Thread) + public abstract static class ObjState { + ObjWrapper[] arr1, arr2; + + public void setup(int eq) { + arr1 = new ObjWrapper[SIZE]; + arr2 = new ObjWrapper[SIZE]; + populate(arr1, arr2, eq); + } + } + + @State(Scope.Thread) + public abstract static class ValState { + ValWrapper[] arr1, arr2; + + public void setup(int eq) { + arr1 = new ValWrapper[SIZE]; + arr2 = new ValWrapper[SIZE]; + populate(arr1, arr2, eq); + } + } + + public static class ObjState00 extends ObjState { + @Setup + public void setup() { + setup(0); + } + } + + public static class ObjState25 extends ObjState { + @Setup + public void setup() { + setup(25); + } + } + + public static class ObjState50 extends ObjState { + @Setup + public void setup() { + setup(50); + } + } + + public static class ObjState75 extends ObjState { + @Setup + public void setup() { + setup(75); + } + } + + public static class ObjState100 extends ObjState { + @Setup + public void setup() { + setup(100); + } + } + + public static class ValState00 extends ValState { + @Setup + public void setup() { + setup(0); + } + } + + public static class ValState25 extends ValState { + @Setup + public void setup() { + setup(25); + } + } + + public static class ValState50 extends ValState { + @Setup + public void setup() { + setup(50); + } + } + + public static class ValState75 extends ValState { + @Setup + public void setup() { + setup(75); + } + } + + public static class ValState100 extends ValState { + @Setup + public void setup() { + setup(100); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/acmp/field/Value128NullFree.java b/test/micro/org/openjdk/bench/valhalla/acmp/field/Value128NullFree.java new file mode 100644 index 00000000000..b334ab2d778 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/acmp/field/Value128NullFree.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.acmp.field; + +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.BitSet; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/* + * For proper results it should be executed: + * java -jar target/benchmarks.jar org.openjdk.bench.valhalla.acmp.field.Value -wmb "org.openjdk.bench.valhalla.acmp.field.Value.*050" + */ + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Value128NullFree { + + public static final int SIZE = 100; + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int cmp_branch_val(ValWrapper[] objects1, ValWrapper[] objects2) { + int s = 0; + for (int i = 0; i < SIZE; i++) { + if (objects1[i].f == objects2[i].f) { + s += 1; + } else { + s -= 1; + } + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static boolean cmp_result_val(ValWrapper[] objects1, ValWrapper[] objects2) { + boolean s = false; + for (int i = 0; i < SIZE; i++) { + s ^= objects1[i].f == objects2[i].f; + } + return s; + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals000(ValState00 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals025(ValState25 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals050(ValState50 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals075(ValState75 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals100(ValState100 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals000(ValState00 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals025(ValState25 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals050(ValState50 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals075(ValState75 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals100(ValState100 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + public static value class ValueInt4 { + + public final int v0; + public final int v1; + public final int v2; + public final int v3; + + public ValueInt4(int v) { + this.v0 = v; + this.v1 = v + 1; + this.v2 = v + 2; + this.v3 = v + 3; + } + + } + + private static void populate(ValWrapper[] arr1, ValWrapper[] arr2, int eq) { + if (eq <= 0) { + for (int i = 0; i < SIZE; i++) { + arr1[i] = new ValWrapper(new ValueInt4(2 * i)); + arr2[i] = new ValWrapper(new ValueInt4(2 * i + 1)); + } + } else if (eq >= 100) { + for (int i = 0; i < SIZE; i++) { + ValueInt4 x = new ValueInt4(i); + arr2[i] = new ValWrapper(x); + arr1[i] = new ValWrapper(x); + } + } else { + BitSet eqset = new Random(42).ints(0, SIZE).distinct().limit(eq * SIZE / 100).collect(BitSet::new, BitSet::set, BitSet::or); + for (int i = 0; i < SIZE; i++) { + if (eqset.get(i)) { + ValueInt4 x = new ValueInt4(i); + arr2[i] = new ValWrapper(x); + arr1[i] = new ValWrapper(x); + } else { + arr1[i] = new ValWrapper(new ValueInt4(2 * i)); + arr2[i] = new ValWrapper(new ValueInt4(2 * i + 1)); + } + } + } + } + + public static class ValWrapper { + + @Strict + @NullRestricted + public final ValueInt4 f; + + public ValWrapper(ValueInt4 f) { + this.f = f; + } + } + + + @State(Scope.Thread) + public abstract static class ValState { + ValWrapper[] arr1, arr2; + + public void setup(int eq) { + arr1 = new ValWrapper[SIZE]; + arr2 = new ValWrapper[SIZE]; + populate(arr1, arr2, eq); + } + } + + public static class ValState00 extends ValState { + @Setup + public void setup() { + setup(0); + } + } + + public static class ValState25 extends ValState { + @Setup + public void setup() { + setup(25); + } + } + + public static class ValState50 extends ValState { + @Setup + public void setup() { + setup(50); + } + } + + public static class ValState75 extends ValState { + @Setup + public void setup() { + setup(75); + } + } + + public static class ValState100 extends ValState { + @Setup + public void setup() { + setup(100); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/acmp/field/Value128NullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/acmp/field/Value128NullFreeNonAtomic.java new file mode 100644 index 00000000000..7a9530a004c --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/acmp/field/Value128NullFreeNonAtomic.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.acmp.field; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.BitSet; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/* + * For proper results it should be executed: + * java -jar target/benchmarks.jar org.openjdk.bench.valhalla.acmp.field.Value -wmb "org.openjdk.bench.valhalla.acmp.field.Value.*050" + */ + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Value128NullFreeNonAtomic { + + public static final int SIZE = 100; + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int cmp_branch_val(ValWrapper[] objects1, ValWrapper[] objects2) { + int s = 0; + for (int i = 0; i < SIZE; i++) { + if (objects1[i].f == objects2[i].f) { + s += 1; + } else { + s -= 1; + } + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static boolean cmp_result_val(ValWrapper[] objects1, ValWrapper[] objects2) { + boolean s = false; + for (int i = 0; i < SIZE; i++) { + s ^= objects1[i].f == objects2[i].f; + } + return s; + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals000(ValState00 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals025(ValState25 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals050(ValState50 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals075(ValState75 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public int branch_val_equals100(ValState100 st) { + return cmp_branch_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals000(ValState00 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals025(ValState25 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals050(ValState50 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals075(ValState75 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.INLINE) + public boolean result_val_equals100(ValState100 st) { + return cmp_result_val(st.arr1, st.arr2); + } + + @LooselyConsistentValue + public static value class ValueInt4 { + + public final int v0; + public final int v1; + public final int v2; + public final int v3; + + public ValueInt4(int v) { + this.v0 = v; + this.v1 = v + 1; + this.v2 = v + 2; + this.v3 = v + 3; + } + + } + + private static void populate(ValWrapper[] arr1, ValWrapper[] arr2, int eq) { + if (eq <= 0) { + for (int i = 0; i < SIZE; i++) { + arr1[i] = new ValWrapper(new ValueInt4(2 * i)); + arr2[i] = new ValWrapper(new ValueInt4(2 * i + 1)); + } + } else if (eq >= 100) { + for (int i = 0; i < SIZE; i++) { + ValueInt4 x = new ValueInt4(i); + arr2[i] = new ValWrapper(x); + arr1[i] = new ValWrapper(x); + } + } else { + BitSet eqset = new Random(42).ints(0, SIZE).distinct().limit(eq * SIZE / 100).collect(BitSet::new, BitSet::set, BitSet::or); + for (int i = 0; i < SIZE; i++) { + if (eqset.get(i)) { + ValueInt4 x = new ValueInt4(i); + arr2[i] = new ValWrapper(x); + arr1[i] = new ValWrapper(x); + } else { + arr1[i] = new ValWrapper(new ValueInt4(2 * i)); + arr2[i] = new ValWrapper(new ValueInt4(2 * i + 1)); + } + } + } + } + + public static class ValWrapper { + + @Strict + @NullRestricted + public final ValueInt4 f; + + public ValWrapper(ValueInt4 f) { + this.f = f; + } + } + + @State(Scope.Thread) + public abstract static class ValState { + ValWrapper[] arr1, arr2; + + public void setup(int eq) { + arr1 = new ValWrapper[SIZE]; + arr2 = new ValWrapper[SIZE]; + populate(arr1, arr2, eq); + } + } + + public static class ValState00 extends ValState { + @Setup + public void setup() { + setup(0); + } + } + + public static class ValState25 extends ValState { + @Setup + public void setup() { + setup(25); + } + } + + public static class ValState50 extends ValState { + @Setup + public void setup() { + setup(50); + } + } + + public static class ValState75 extends ValState { + @Setup + public void setup() { + setup(75); + } + } + + public static class ValState100 extends ValState { + @Setup + public void setup() { + setup(100); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/acmp/trivial/Identity.java b/test/micro/org/openjdk/bench/valhalla/acmp/trivial/Identity.java new file mode 100644 index 00000000000..b690cfd84b1 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/acmp/trivial/Identity.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.acmp.trivial; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +/* + * to provide proper measurement the benchmark have to be executed in two modes: + * -wm INDI + * -wm BULK + */ +@Fork(1) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Identity { + + Object o1 = new IdentityLong(1); + Object o2 = new IdentityLong(2); + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static boolean cmpEquals(Object a, Object b) { + return a == b; } + + @Benchmark + public boolean isCmp_null_null() { + return cmpEquals(null, null); + } + + @Benchmark + public boolean isCmp_o1_null() { + return cmpEquals(o1, null); + } + + @Benchmark + public boolean isCmp_null_o1() { + return cmpEquals(null, o1); + } + + @Benchmark + public boolean isCmp_o1_o1() { + return cmpEquals(o1, o1); + } + + @Benchmark + public boolean isCmp_o1_o2() { + return cmpEquals(o1, o2); + } + + public static class IdentityLong { + + public final long v0; + + public IdentityLong(long v0) { + this.v0 = v0; + } + + public long value() { + return v0; + } + + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/acmp/trivial/Value.java b/test/micro/org/openjdk/bench/valhalla/acmp/trivial/Value.java new file mode 100644 index 00000000000..0e3b15a58fd --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/acmp/trivial/Value.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.acmp.trivial; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +/* + * to provide proper measurement the benchmark have to be executed in two modes: + * -wm INDI + * -wm BULK + */ +@Fork(value = 1, jvmArgsAppend = {"--enable-preview"}) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Value { + + Object o1 = new ValueLong(1); + Object o2 = new ValueLong(2); + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static boolean cmpEquals(Object a, Object b) { + return a == b; } + + @Benchmark + public boolean isCmp_null_null() { + return cmpEquals(null, null); + } + + @Benchmark + public boolean isCmp_o1_null() { + return cmpEquals(o1, null); + } + + @Benchmark + public boolean isCmp_null_o1() { + return cmpEquals(null, o1); + } + + @Benchmark + public boolean isCmp_o1_o1() { + return cmpEquals(o1, o1); + } + + @Benchmark + public boolean isCmp_o1_o2() { + return cmpEquals(o1, o2); + } + + public static value class ValueLong { + + public final long v0; + + public ValueLong(long v0) { + this.v0 = v0; + } + + public long value() { + return v0; + } + + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/copy/CopyBase.java b/test/micro/org/openjdk/bench/valhalla/array/copy/CopyBase.java new file mode 100644 index 00000000000..8ba6e27c84d --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/copy/CopyBase.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.copy; + + +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@Fork(3) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +public class CopyBase { + + @State(Scope.Thread) + public static abstract class SizeState { + + @Param({ + "100", // tiny size, to fit into all caches and check codegeneration quality + "1000000" // large size, to be out of all caches and memory effects + }) + public int size; + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/copy/Identity.java b/test/micro/org/openjdk/bench/valhalla/array/copy/Identity.java new file mode 100644 index 00000000000..8e298df4a6d --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/copy/Identity.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.copy; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Setup; + +public class Identity extends CopyBase { + + public interface InterfaceInt { + public int value(); + } + + public static class IdentityInt implements InterfaceInt { + public final int value; + public IdentityInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + + public static class RefState extends SizeState { + public IdentityInt[] arr; + + @Setup + public void setup() { + arr = new IdentityInt[size]; + for (int i = 0; i < size; i++) { + arr[i] = new IdentityInt(i); + } + } + } + + public static class IntState extends SizeState { + public InterfaceInt[] arr; + + @Setup + public void setup() { + arr = new InterfaceInt[size]; + for (int i = 0; i < size; i++) { + arr[i] = new IdentityInt(i); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_ref(IdentityInt[] dst, IdentityInt[] src) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_int(InterfaceInt[] dst, InterfaceInt[] src) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arraycopy_ref(IdentityInt[] dst, IdentityInt[] src) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arraycopy_int(InterfaceInt[] dst, InterfaceInt[] src) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Benchmark + public void copy_ref_as_ref(RefState st1, RefState st2) { + copy_ref(st1.arr, st2.arr); + } + + @Benchmark + public void arraycopy_ref_as_ref(RefState st1, RefState st2) { + arraycopy_ref(st1.arr, st2.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/copy/Primitive032.java b/test/micro/org/openjdk/bench/valhalla/array/copy/Primitive032.java new file mode 100644 index 00000000000..9c1eafe274e --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/copy/Primitive032.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.copy; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Setup; + +public class Primitive032 extends CopyBase { + + public static class PrimitiveState extends SizeState { + public int[] arr; + + @Setup + public void setup() { + arr = new int[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = i; + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static void copy_primitive(int[] dst, int[] src) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static void arraycopy_primitive(int[] dst, int[] src) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Benchmark + public void copy(PrimitiveState st1, PrimitiveState st2) { + copy_primitive(st1.arr, st2.arr); + } + + @Benchmark + public void arraycopy(PrimitiveState st1, PrimitiveState st2) { + arraycopy_primitive(st1.arr, st2.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/copy/Value032.java b/test/micro/org/openjdk/bench/valhalla/array/copy/Value032.java new file mode 100644 index 00000000000..fad494e0ece --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/copy/Value032.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.copy; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value032 extends CopyBase { + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt implements InterfaceInt { + + public final int value; + + public ValueInt(int value) { + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt[] arr; + + @Setup + public void setup() { + arr = new ValueInt[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt(i); + } + } + } + + public static class IntState extends SizeState { + public InterfaceInt[] arr; + + @Setup + public void setup() { + arr = new InterfaceInt[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt(i); + } + } + + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_val(ValueInt[] dst, ValueInt[] src) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_int(InterfaceInt[] dst, InterfaceInt[] src) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arraycopy_val(ValueInt[] dst, ValueInt[] src) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arraycopy_int(InterfaceInt[] dst, InterfaceInt[] src) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Benchmark + public void copy_val_as_val(ValState st1, ValState st2) { + copy_val(st1.arr, st2.arr); + } + + @Benchmark + public void arraycopy_val_as_val(ValState st1, ValState st2) { + arraycopy_val(st1.arr, st2.arr); + } + + @Benchmark + public void copy_int_as_int(IntState st1, IntState st2) { + copy_int(st1.arr, st2.arr); + } + + @Benchmark + public void arraycopy_int_as_int(IntState st1, IntState st2) { + arraycopy_int(st1.arr, st2.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/copy/Value032NullFree.java b/test/micro/org/openjdk/bench/valhalla/array/copy/Value032NullFree.java new file mode 100644 index 00000000000..d5310f60744 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/copy/Value032NullFree.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.copy; + +import jdk.internal.value.ValueClass; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class Value032NullFree extends CopyBase { + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt implements InterfaceInt { + + public final int value; + + public ValueInt(int value) { + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt[] arr; + + @Setup + public void setup() { + arr = (ValueInt[]) ValueClass.newNullRestrictedAtomicArray(ValueInt.class, size, new ValueInt(0)); + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt(i); + } + } + } + + public static class IntState extends SizeState { + public InterfaceInt[] arr; + + @Setup + public void setup() { + arr = new InterfaceInt[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt(i); + } + } + + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_val(ValueInt[] dst, ValueInt[] src) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_int(InterfaceInt[] dst, InterfaceInt[] src) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arraycopy_val(ValueInt[] dst, ValueInt[] src) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arraycopy_int(InterfaceInt[] dst, InterfaceInt[] src) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Benchmark + public void copy_val_as_val(ValState st1, ValState st2) { + copy_val(st1.arr, st2.arr); + } + + @Benchmark + public void arraycopy_val_as_val(ValState st1, ValState st2) { + arraycopy_val(st1.arr, st2.arr); + } + + @Benchmark + public void copy_int_as_int(IntState st1, IntState st2) { + copy_int(st1.arr, st2.arr); + } + + @Benchmark + public void arraycopy_int_as_int(IntState st1, IntState st2) { + arraycopy_int(st1.arr, st2.arr); + } + + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/copy/Value032NullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/array/copy/Value032NullFreeNonAtomic.java new file mode 100644 index 00000000000..dace774868a --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/copy/Value032NullFreeNonAtomic.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.copy; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class Value032NullFreeNonAtomic extends CopyBase { + + public interface InterfaceInt { + public int value(); + } + + @LooselyConsistentValue + public static value class ValueInt implements InterfaceInt { + + public final int value; + + public ValueInt(int value) { + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt[] arr; + + @Setup + public void setup() { + arr = (ValueInt[]) ValueClass.newNullRestrictedNonAtomicArray(ValueInt.class, size, new ValueInt(0)); + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt(i); + } + } + } + + public static class IntState extends SizeState { + public InterfaceInt[] arr; + + @Setup + public void setup() { + arr = new InterfaceInt[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt(i); + } + } + + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_val(ValueInt[] dst, ValueInt[] src) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_int(InterfaceInt[] dst, InterfaceInt[] src) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arraycopy_val(ValueInt[] dst, ValueInt[] src) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arraycopy_int(InterfaceInt[] dst, InterfaceInt[] src) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Benchmark + public void copy_val_as_val(ValState st1, ValState st2) { + copy_val(st1.arr, st2.arr); + } + + @Benchmark + public void arraycopy_val_as_val(ValState st1, ValState st2) { + arraycopy_val(st1.arr, st2.arr); + } + + @Benchmark + public void copy_int_as_int(IntState st1, IntState st2) { + copy_int(st1.arr, st2.arr); + } + + @Benchmark + public void arraycopy_int_as_int(IntState st1, IntState st2) { + arraycopy_int(st1.arr, st2.arr); + } + + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/copy/Value128.java b/test/micro/org/openjdk/bench/valhalla/array/copy/Value128.java new file mode 100644 index 00000000000..aeb828e9a97 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/copy/Value128.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.copy; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value128 extends CopyBase { + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt4[] arr; + + @Setup + public void setup() { + arr = new ValueInt4[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt4(i); + } + } + } + + public static class IntState extends SizeState { + public InterfaceInt[] arr; + + @Setup + public void setup() { + arr = new InterfaceInt[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt4(i); + } + } + + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_val(ValueInt4[] dst, ValueInt4[] src) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_int(InterfaceInt[] dst, InterfaceInt[] src) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arraycopy_val(ValueInt4[] dst, ValueInt4[] src) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arraycopy_int(InterfaceInt[] dst, InterfaceInt[] src) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Benchmark + public void copy_val_as_val(ValState st1, ValState st2) { + copy_val(st1.arr, st2.arr); + } + + @Benchmark + public void arraycopy_val_as_val(ValState st1, ValState st2) { + arraycopy_val(st1.arr, st2.arr); + } + + @Benchmark + public void copy_int_as_int(IntState st1, IntState st2) { + copy_int(st1.arr, st2.arr); + } + + @Benchmark + public void arraycopy_int_as_int(IntState st1, IntState st2) { + arraycopy_int(st1.arr, st2.arr); + } + + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/copy/Value128NullFree.java b/test/micro/org/openjdk/bench/valhalla/array/copy/Value128NullFree.java new file mode 100644 index 00000000000..0d771be401e --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/copy/Value128NullFree.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.copy; + +import jdk.internal.value.ValueClass; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class Value128NullFree extends CopyBase { + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt4[] arr; + + @Setup + public void setup() { + arr = (ValueInt4[]) ValueClass.newNullRestrictedAtomicArray(ValueInt4.class, size, new ValueInt4(0)); + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt4(i); + } + } + } + + public static class IntState extends SizeState { + public InterfaceInt[] arr; + + @Setup + public void setup() { + arr = new InterfaceInt[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt4(i); + } + } + + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_val(ValueInt4[] dst, ValueInt4[] src) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_int(InterfaceInt[] dst, InterfaceInt[] src) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arraycopy_val(ValueInt4[] dst, ValueInt4[] src) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arraycopy_int(InterfaceInt[] dst, InterfaceInt[] src) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Benchmark + public void copy_val_as_val(ValState st1, ValState st2) { + copy_val(st1.arr, st2.arr); + } + + @Benchmark + public void arraycopy_val_as_val(ValState st1, ValState st2) { + arraycopy_val(st1.arr, st2.arr); + } + + @Benchmark + public void copy_int_as_int(IntState st1, IntState st2) { + copy_int(st1.arr, st2.arr); + } + + @Benchmark + public void arraycopy_int_as_int(IntState st1, IntState st2) { + arraycopy_int(st1.arr, st2.arr); + } + + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/copy/Value128NullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/array/copy/Value128NullFreeNonAtomic.java new file mode 100644 index 00000000000..3a9d10519d6 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/copy/Value128NullFreeNonAtomic.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.copy; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class Value128NullFreeNonAtomic extends CopyBase { + + public interface InterfaceInt { + public int value(); + } + + @LooselyConsistentValue + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt4[] arr; + + @Setup + public void setup() { + arr = (ValueInt4[]) ValueClass.newNullRestrictedNonAtomicArray(ValueInt4.class, size, new ValueInt4(0)); + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt4(i); + } + } + } + + public static class IntState extends SizeState { + public InterfaceInt[] arr; + + @Setup + public void setup() { + arr = new InterfaceInt[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt4(i); + } + } + + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_val(ValueInt4[] dst, ValueInt4[] src) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_int(InterfaceInt[] dst, InterfaceInt[] src) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arraycopy_val(ValueInt4[] dst, ValueInt4[] src) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arraycopy_int(InterfaceInt[] dst, InterfaceInt[] src) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Benchmark + public void copy_val_as_val(ValState st1, ValState st2) { + copy_val(st1.arr, st2.arr); + } + + @Benchmark + public void arraycopy_val_as_val(ValState st1, ValState st2) { + arraycopy_val(st1.arr, st2.arr); + } + + @Benchmark + public void copy_int_as_int(IntState st1, IntState st2) { + copy_int(st1.arr, st2.arr); + } + + @Benchmark + public void arraycopy_int_as_int(IntState st1, IntState st2) { + arraycopy_int(st1.arr, st2.arr); + } + + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/copy/ValueOop.java b/test/micro/org/openjdk/bench/valhalla/array/copy/ValueOop.java new file mode 100644 index 00000000000..b6751aee438 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/copy/ValueOop.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.copy; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class ValueOop extends CopyBase { + + public static class IdentityInt { + public final int value; + public IdentityInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + public interface InterfaceOop { + public IdentityInt value(); + } + + public static value class ValueRef implements InterfaceOop { + + public final IdentityInt value; + + public ValueRef(IdentityInt value) { + this.value = value; + } + + public IdentityInt value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueRef[] arr; + + @Setup + public void setup() { + arr = new ValueRef[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueRef(new IdentityInt(i)); + } + } + } + + public static class IntState extends SizeState { + public InterfaceOop[] arr; + + @Setup + public void setup() { + arr = new InterfaceOop[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueRef(new IdentityInt(i)); + } + } + + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_val(ValueRef[] dst, ValueRef[] src) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_int(InterfaceOop[] dst, InterfaceOop[] src) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arraycopy_val(ValueRef[] dst, ValueRef[] src) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arraycopy_int(InterfaceOop[] dst, InterfaceOop[] src) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Benchmark + public void copy_val_as_val(ValState st1, ValState st2) { + copy_val(st1.arr, st2.arr); + } + + @Benchmark + public void arraycopy_val_as_val(ValState st1, ValState st2) { + arraycopy_val(st1.arr, st2.arr); + } + + @Benchmark + public void copy_int_as_int(IntState st1, IntState st2) { + copy_int(st1.arr, st2.arr); + } + + @Benchmark + public void arraycopy_int_as_int(IntState st1, IntState st2) { + arraycopy_int(st1.arr, st2.arr); + } + + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/copy/ValueOopNullFree.java b/test/micro/org/openjdk/bench/valhalla/array/copy/ValueOopNullFree.java new file mode 100644 index 00000000000..5cb271c86c4 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/copy/ValueOopNullFree.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.copy; + +import jdk.internal.value.ValueClass; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class ValueOopNullFree extends CopyBase { + + public static class IdentityInt { + public final int value; + public IdentityInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + public interface InterfaceOop { + public IdentityInt value(); + } + + public static value class ValueRef implements InterfaceOop { + + public final IdentityInt value; + + public ValueRef(IdentityInt value) { + this.value = value; + } + + public IdentityInt value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueRef[] arr; + + @Setup + public void setup() { + arr = (ValueRef[]) ValueClass.newNullRestrictedAtomicArray(ValueRef.class, size, new ValueRef(new IdentityInt(0))); + for (int i = 0; i < size; i++) { + arr[i] = new ValueRef(new IdentityInt(i)); + } + } + } + + public static class IntState extends SizeState { + public InterfaceOop[] arr; + + @Setup + public void setup() { + arr = new InterfaceOop[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueRef(new IdentityInt(i)); + } + } + + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_val(ValueRef[] dst, ValueRef[] src) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_int(InterfaceOop[] dst, InterfaceOop[] src) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arraycopy_val(ValueRef[] dst, ValueRef[] src) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arraycopy_int(InterfaceOop[] dst, InterfaceOop[] src) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Benchmark + public void copy_val_as_val(ValState st1, ValState st2) { + copy_val(st1.arr, st2.arr); + } + + @Benchmark + public void arraycopy_val_as_val(ValState st1, ValState st2) { + arraycopy_val(st1.arr, st2.arr); + } + + @Benchmark + public void copy_int_as_int(IntState st1, IntState st2) { + copy_int(st1.arr, st2.arr); + } + + @Benchmark + public void arraycopy_int_as_int(IntState st1, IntState st2) { + arraycopy_int(st1.arr, st2.arr); + } + + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/copy/ValueOopNullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/array/copy/ValueOopNullFreeNonAtomic.java new file mode 100644 index 00000000000..9dfb57c1d3f --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/copy/ValueOopNullFreeNonAtomic.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.copy; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class ValueOopNullFreeNonAtomic extends CopyBase { + + public static class IdentityInt { + public final int value; + public IdentityInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + public interface InterfaceOop { + public IdentityInt value(); + } + + @LooselyConsistentValue + public static value class ValueRef implements InterfaceOop { + + public final IdentityInt value; + + public ValueRef(IdentityInt value) { + this.value = value; + } + + public IdentityInt value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueRef[] arr; + + @Setup + public void setup() { + arr = (ValueRef[]) ValueClass.newNullRestrictedAtomicArray(ValueRef.class, size, new ValueRef(new IdentityInt(0))); + for (int i = 0; i < size; i++) { + arr[i] = new ValueRef(new IdentityInt(i)); + } + } + } + + public static class IntState extends SizeState { + public InterfaceOop[] arr; + + @Setup + public void setup() { + arr = new InterfaceOop[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueRef(new IdentityInt(i)); + } + } + + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_val(ValueRef[] dst, ValueRef[] src) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_int(InterfaceOop[] dst, InterfaceOop[] src) { + for (int i = 0; i < src.length; i++) { + dst[i] = src[i]; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arraycopy_val(ValueRef[] dst, ValueRef[] src) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arraycopy_int(InterfaceOop[] dst, InterfaceOop[] src) { + System.arraycopy(src, 0, dst, 0, src.length); + } + + @Benchmark + public void copy_val_as_val(ValState st1, ValState st2) { + copy_val(st1.arr, st2.arr); + } + + @Benchmark + public void arraycopy_val_as_val(ValState st1, ValState st2) { + arraycopy_val(st1.arr, st2.arr); + } + + @Benchmark + public void copy_int_as_int(IntState st1, IntState st2) { + copy_int(st1.arr, st2.arr); + } + + @Benchmark + public void arraycopy_int_as_int(IntState st1, IntState st2) { + arraycopy_int(st1.arr, st2.arr); + } + + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/fill/FillBase.java b/test/micro/org/openjdk/bench/valhalla/array/fill/FillBase.java new file mode 100644 index 00000000000..e70ee86d601 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/fill/FillBase.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.fill; + + +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@Fork(3) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +public class FillBase { + + @State(Scope.Thread) + public static abstract class SizeState { + + @Param({ + "100", // tiny size, to fit into all caches and check codegeneration quality + "1000000" // large size, to be out of all caches and memory effects + }) + public int size; + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/fill/Identity.java b/test/micro/org/openjdk/bench/valhalla/array/fill/Identity.java new file mode 100644 index 00000000000..971733cfc36 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/fill/Identity.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.fill; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import java.util.Arrays; + +public class Identity extends FillBase { + + public interface InterfaceInt { + public int value(); + } + + public static class IdentityInt implements InterfaceInt { + public final int value; + public IdentityInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + + public static class RefState extends SizeState { + public IdentityInt[] arr; + + @Setup + public void setup() { + arr = new IdentityInt[size]; + for (int i = 0; i < size; i++) { + arr[i] = new IdentityInt(i); + } + } + } + + public static class StaticHolder { + public static IdentityInt VALUE = new IdentityInt(42); + } + + @State(Scope.Thread) + public static class InstanceHolder { + public IdentityInt VALUE = new IdentityInt(42); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public IdentityInt get_ref(int i) { + return new IdentityInt(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_new_ref(IdentityInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new IdentityInt(42); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_new_ref(IdentityInt[] dst) { + Arrays.fill(dst, new IdentityInt(42)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_local_ref(IdentityInt[] dst) { + IdentityInt local = get_ref(42); + for (int i = 0; i < dst.length; i++) { + dst[i] = local; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_local_ref(IdentityInt[] dst) { + Arrays.fill(dst, get_ref(42)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_static_ref(IdentityInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = StaticHolder.VALUE; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_static_ref(IdentityInt[] dst) { + Arrays.fill(dst, StaticHolder.VALUE); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_instance_ref(IdentityInt[] dst, InstanceHolder ih) { + for (int i = 0; i < dst.length; i++) { + dst[i] = ih.VALUE; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_instance_ref(IdentityInt[] dst, InstanceHolder ih) { + Arrays.fill(dst, ih.VALUE); + } + + @Benchmark + public void fill_new(RefState st1) { + fill_new_ref(st1.arr); + } + + @Benchmark + public void arrayfill_new(RefState st1) { + arrayfill_new_ref(st1.arr); + } + + @Benchmark + public void fill_local(RefState st1) { + fill_local_ref(st1.arr); + } + + @Benchmark + public void arrayfill_local(RefState st1) { + arrayfill_local_ref(st1.arr); + } + + @Benchmark + public void fill_static(RefState st1) { + fill_static_ref(st1.arr); + } + + @Benchmark + public void arrayfill_static(RefState st1) { + arrayfill_static_ref(st1.arr); + } + + @Benchmark + public void fill_instance(RefState st1, InstanceHolder ih) { + fill_instance_ref(st1.arr, ih); + } + + @Benchmark + public void arrayfill_instance(RefState st1, InstanceHolder ih) { + arrayfill_instance_ref(st1.arr, ih); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/fill/Primitive032.java b/test/micro/org/openjdk/bench/valhalla/array/fill/Primitive032.java new file mode 100644 index 00000000000..81da99335bb --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/fill/Primitive032.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.fill; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Setup; + +import java.util.Arrays; + +public class Primitive032 extends FillBase { + + public static class PrimitiveState extends SizeState { + public int[] arr; + + @Setup + public void setup() { + arr = new int[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = i; + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static void fill_primitive(int[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = 42; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static void arrayfill_primitive(int[] dst) { + Arrays.fill(dst, 42); + } + + @Benchmark + public void fill(PrimitiveState st1) { + fill_primitive(st1.arr); + } + + @Benchmark + public void arrayfill(PrimitiveState st1) { + arrayfill_primitive(st1.arr); + } + + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/fill/Value032.java b/test/micro/org/openjdk/bench/valhalla/array/fill/Value032.java new file mode 100644 index 00000000000..8fda3d3d59f --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/fill/Value032.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.fill; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import java.util.Arrays; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value032 extends FillBase { + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt implements InterfaceInt { + + public final int value; + + public ValueInt(int value) { + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt[] arr; + + @Setup + public void setup() { + arr = new ValueInt[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt(i); + } + } + } + + public static class StaticHolder { + public static ValueInt VALUE = new ValueInt(42); + } + + @State(Scope.Thread) + public static class InstanceHolder { + public ValueInt VALUE = new ValueInt(42); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueInt get_val(int i) { + return new ValueInt(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_new_val(ValueInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueInt(42); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_new_val(ValueInt[] dst) { + Arrays.fill(dst, new ValueInt(42)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_local_val(ValueInt[] dst) { + ValueInt local = get_val(42); + for (int i = 0; i < dst.length; i++) { + dst[i] = local; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_local_val(ValueInt[] dst) { + Arrays.fill(dst, get_val(42)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_static_val(ValueInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = StaticHolder.VALUE; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_static_val(ValueInt[] dst) { + Arrays.fill(dst, StaticHolder.VALUE); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_instance_val(ValueInt[] dst, InstanceHolder ih) { + for (int i = 0; i < dst.length; i++) { + dst[i] = ih.VALUE; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_instance_val(ValueInt[] dst, InstanceHolder ih) { + Arrays.fill(dst, ih.VALUE); + } + + @Benchmark + public void fill_new(ValState st1) { + fill_new_val(st1.arr); + } + + @Benchmark + public void arrayfill_new(ValState st1) { + arrayfill_new_val(st1.arr); + } + + @Benchmark + public void fill_local(ValState st1) { + fill_local_val(st1.arr); + } + + @Benchmark + public void arrayfill_local(ValState st1) { + arrayfill_local_val(st1.arr); + } + + @Benchmark + public void fill_static(ValState st1) { + fill_static_val(st1.arr); + } + + @Benchmark + public void arrayfill_static(ValState st1) { + arrayfill_static_val(st1.arr); + } + + @Benchmark + public void fill_instance(ValState st1, InstanceHolder ih) { + fill_instance_val(st1.arr, ih); + } + + @Benchmark + public void arrayfill_instance(ValState st1, InstanceHolder ih) { + arrayfill_instance_val(st1.arr, ih); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/fill/Value032NullFree.java b/test/micro/org/openjdk/bench/valhalla/array/fill/Value032NullFree.java new file mode 100644 index 00000000000..08b04d987ec --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/fill/Value032NullFree.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.fill; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import java.util.Arrays; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class Value032NullFree extends FillBase { + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt implements InterfaceInt { + + public final int value; + + public ValueInt(int value) { + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt[] arr; + + @Setup + public void setup() { + arr = (ValueInt[]) ValueClass.newNullRestrictedAtomicArray(ValueInt.class, size, new ValueInt(0)); + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt(i); + } + } + } + + public static class StaticHolder { + @Strict + @NullRestricted + public static ValueInt VALUE = new ValueInt(42); + } + + @State(Scope.Thread) + public static class InstanceHolder { + @Strict + @NullRestricted + public ValueInt VALUE = new ValueInt(42); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueInt get_val(int i) { + return new ValueInt(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_new_val(ValueInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueInt(42); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_new_val(ValueInt[] dst) { + Arrays.fill(dst, new ValueInt(42)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_local_val(ValueInt[] dst) { + ValueInt local = get_val(42); + for (int i = 0; i < dst.length; i++) { + dst[i] = local; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_local_val(ValueInt[] dst) { + Arrays.fill(dst, get_val(42)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_static_val(ValueInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = StaticHolder.VALUE; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_static_val(ValueInt[] dst) { + Arrays.fill(dst, StaticHolder.VALUE); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_instance_val(ValueInt[] dst, InstanceHolder ih) { + for (int i = 0; i < dst.length; i++) { + dst[i] = ih.VALUE; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_instance_val(ValueInt[] dst, InstanceHolder ih) { + Arrays.fill(dst, ih.VALUE); + } + + @Benchmark + public void fill_new(ValState st1) { + fill_new_val(st1.arr); + } + + @Benchmark + public void arrayfill_new(ValState st1) { + arrayfill_new_val(st1.arr); + } + + @Benchmark + public void fill_local(ValState st1) { + fill_local_val(st1.arr); + } + + @Benchmark + public void arrayfill_local(ValState st1) { + arrayfill_local_val(st1.arr); + } + + @Benchmark + public void fill_static(ValState st1) { + fill_static_val(st1.arr); + } + + @Benchmark + public void arrayfill_static(ValState st1) { + arrayfill_static_val(st1.arr); + } + + @Benchmark + public void fill_instance(ValState st1, InstanceHolder ih) { + fill_instance_val(st1.arr, ih); + } + + @Benchmark + public void arrayfill_instance(ValState st1, InstanceHolder ih) { + arrayfill_instance_val(st1.arr, ih); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/fill/Value032NullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/array/fill/Value032NullFreeNonAtomic.java new file mode 100644 index 00000000000..2a888ad26ed --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/fill/Value032NullFreeNonAtomic.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.fill; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import java.util.Arrays; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class Value032NullFreeNonAtomic extends FillBase { + + public interface InterfaceInt { + public int value(); + } + + @LooselyConsistentValue + public static value class ValueInt implements InterfaceInt { + + public final int value; + + public ValueInt(int value) { + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt[] arr; + + @Setup + public void setup() { + arr = (ValueInt[]) ValueClass.newNullRestrictedNonAtomicArray(ValueInt.class, size, new ValueInt(0)); + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt(i); + } + } + } + + public static class StaticHolder { + @Strict + @NullRestricted + public static ValueInt VALUE = new ValueInt(42); + } + + @State(Scope.Thread) + public static class InstanceHolder { + @Strict + @NullRestricted + public ValueInt VALUE = new ValueInt(42); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueInt get_val(int i) { + return new ValueInt(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_new_val(ValueInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueInt(42); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_new_val(ValueInt[] dst) { + Arrays.fill(dst, new ValueInt(42)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_local_val(ValueInt[] dst) { + ValueInt local = get_val(42); + for (int i = 0; i < dst.length; i++) { + dst[i] = local; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_local_val(ValueInt[] dst) { + Arrays.fill(dst, get_val(42)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_static_val(ValueInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = StaticHolder.VALUE; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_static_val(ValueInt[] dst) { + Arrays.fill(dst, StaticHolder.VALUE); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_instance_val(ValueInt[] dst, InstanceHolder ih) { + for (int i = 0; i < dst.length; i++) { + dst[i] = ih.VALUE; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_instance_val(ValueInt[] dst, InstanceHolder ih) { + Arrays.fill(dst, ih.VALUE); + } + + @Benchmark + public void fill_new(ValState st1) { + fill_new_val(st1.arr); + } + + @Benchmark + public void arrayfill_new(ValState st1) { + arrayfill_new_val(st1.arr); + } + + @Benchmark + public void fill_local(ValState st1) { + fill_local_val(st1.arr); + } + + @Benchmark + public void arrayfill_local(ValState st1) { + arrayfill_local_val(st1.arr); + } + + @Benchmark + public void fill_static(ValState st1) { + fill_static_val(st1.arr); + } + + @Benchmark + public void arrayfill_static(ValState st1) { + arrayfill_static_val(st1.arr); + } + + @Benchmark + public void fill_instance(ValState st1, InstanceHolder ih) { + fill_instance_val(st1.arr, ih); + } + + @Benchmark + public void arrayfill_instance(ValState st1, InstanceHolder ih) { + arrayfill_instance_val(st1.arr, ih); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/fill/Value128.java b/test/micro/org/openjdk/bench/valhalla/array/fill/Value128.java new file mode 100644 index 00000000000..ca043c7abef --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/fill/Value128.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.fill; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import java.util.Arrays; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value128 extends FillBase { + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt4[] arr; + + @Setup + public void setup() { + arr = new ValueInt4[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt4(i); + } + } + } + + public static class StaticHolder { + public static ValueInt4 VALUE = new ValueInt4(42); + } + + @State(Scope.Thread) + public static class InstanceHolder { + public ValueInt4 VALUE = new ValueInt4(42); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueInt4 get_val(int i) { + return new ValueInt4(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_new_val(ValueInt4[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueInt4(42); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_new_val(ValueInt4[] dst) { + Arrays.fill(dst, new ValueInt4(42)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_local_val(ValueInt4[] dst) { + ValueInt4 local = get_val(42); + for (int i = 0; i < dst.length; i++) { + dst[i] = local; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_local_val(ValueInt4[] dst) { + Arrays.fill(dst, get_val(42)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_static_val(ValueInt4[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = StaticHolder.VALUE; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_static_val(ValueInt4[] dst) { + Arrays.fill(dst, StaticHolder.VALUE); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_instance_val(ValueInt4[] dst, InstanceHolder ih) { + for (int i = 0; i < dst.length; i++) { + dst[i] = ih.VALUE; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_instance_val(ValueInt4[] dst, InstanceHolder ih) { + Arrays.fill(dst, ih.VALUE); + } + + @Benchmark + public void fill_new(ValState st1) { + fill_new_val(st1.arr); + } + + @Benchmark + public void arrayfill_new(ValState st1) { + arrayfill_new_val(st1.arr); + } + + @Benchmark + public void fill_local(ValState st1) { + fill_local_val(st1.arr); + } + + @Benchmark + public void arrayfill_local(ValState st1) { + arrayfill_local_val(st1.arr); + } + + @Benchmark + public void fill_static(ValState st1) { + fill_static_val(st1.arr); + } + + @Benchmark + public void arrayfill_static(ValState st1) { + arrayfill_static_val(st1.arr); + } + + @Benchmark + public void fill_instance(ValState st1, InstanceHolder ih) { + fill_instance_val(st1.arr, ih); + } + + @Benchmark + public void arrayfill_instance(ValState st1, InstanceHolder ih) { + arrayfill_instance_val(st1.arr, ih); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/fill/Value128NullFree.java b/test/micro/org/openjdk/bench/valhalla/array/fill/Value128NullFree.java new file mode 100644 index 00000000000..98c396b21ac --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/fill/Value128NullFree.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.fill; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import java.util.Arrays; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class Value128NullFree extends FillBase { + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt4[] arr; + + @Setup + public void setup() { + arr = (ValueInt4[]) ValueClass.newNullRestrictedAtomicArray(ValueInt4.class, size, new ValueInt4(0)); + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt4(i); + } + } + } + + public static class StaticHolder { + @Strict + @NullRestricted + public static ValueInt4 VALUE = new ValueInt4(42); + } + + @State(Scope.Thread) + public static class InstanceHolder { + @Strict + @NullRestricted + public ValueInt4 VALUE = new ValueInt4(42); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueInt4 get_val(int i) { + return new ValueInt4(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_new_val(ValueInt4[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueInt4(42); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_new_val(ValueInt4[] dst) { + Arrays.fill(dst, new ValueInt4(42)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_local_val(ValueInt4[] dst) { + ValueInt4 local = get_val(42); + for (int i = 0; i < dst.length; i++) { + dst[i] = local; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_local_val(ValueInt4[] dst) { + Arrays.fill(dst, get_val(42)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_static_val(ValueInt4[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = StaticHolder.VALUE; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_static_val(ValueInt4[] dst) { + Arrays.fill(dst, StaticHolder.VALUE); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_instance_val(ValueInt4[] dst, InstanceHolder ih) { + for (int i = 0; i < dst.length; i++) { + dst[i] = ih.VALUE; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_instance_val(ValueInt4[] dst, InstanceHolder ih) { + Arrays.fill(dst, ih.VALUE); + } + + @Benchmark + public void fill_new(ValState st1) { + fill_new_val(st1.arr); + } + + @Benchmark + public void arrayfill_new(ValState st1) { + arrayfill_new_val(st1.arr); + } + + @Benchmark + public void fill_local(ValState st1) { + fill_local_val(st1.arr); + } + + @Benchmark + public void arrayfill_local(ValState st1) { + arrayfill_local_val(st1.arr); + } + + @Benchmark + public void fill_static(ValState st1) { + fill_static_val(st1.arr); + } + + @Benchmark + public void arrayfill_static(ValState st1) { + arrayfill_static_val(st1.arr); + } + + @Benchmark + public void fill_instance(ValState st1, InstanceHolder ih) { + fill_instance_val(st1.arr, ih); + } + + @Benchmark + public void arrayfill_instance(ValState st1, InstanceHolder ih) { + arrayfill_instance_val(st1.arr, ih); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/fill/Value128NullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/array/fill/Value128NullFreeNonAtomic.java new file mode 100644 index 00000000000..ef57a63bbf9 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/fill/Value128NullFreeNonAtomic.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.fill; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import java.util.Arrays; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class Value128NullFreeNonAtomic extends FillBase { + + public interface InterfaceInt { + public int value(); + } + + @LooselyConsistentValue + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt4[] arr; + + @Setup + public void setup() { + arr = (ValueInt4[]) ValueClass.newNullRestrictedNonAtomicArray(ValueInt4.class, size, new ValueInt4(0)); + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt4(i); + } + } + } + + public static class StaticHolder { + @Strict + @NullRestricted + public static ValueInt4 VALUE = new ValueInt4(42); + } + + @State(Scope.Thread) + public static class InstanceHolder { + @Strict + @NullRestricted + public ValueInt4 VALUE = new ValueInt4(42); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueInt4 get_val(int i) { + return new ValueInt4(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_new_val(ValueInt4[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueInt4(42); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_new_val(ValueInt4[] dst) { + Arrays.fill(dst, new ValueInt4(42)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_local_val(ValueInt4[] dst) { + ValueInt4 local = get_val(42); + for (int i = 0; i < dst.length; i++) { + dst[i] = local; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_local_val(ValueInt4[] dst) { + Arrays.fill(dst, get_val(42)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_static_val(ValueInt4[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = StaticHolder.VALUE; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_static_val(ValueInt4[] dst) { + Arrays.fill(dst, StaticHolder.VALUE); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_instance_val(ValueInt4[] dst, InstanceHolder ih) { + for (int i = 0; i < dst.length; i++) { + dst[i] = ih.VALUE; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_instance_val(ValueInt4[] dst, InstanceHolder ih) { + Arrays.fill(dst, ih.VALUE); + } + + @Benchmark + public void fill_new(ValState st1) { + fill_new_val(st1.arr); + } + + @Benchmark + public void arrayfill_new(ValState st1) { + arrayfill_new_val(st1.arr); + } + + @Benchmark + public void fill_local(ValState st1) { + fill_local_val(st1.arr); + } + + @Benchmark + public void arrayfill_local(ValState st1) { + arrayfill_local_val(st1.arr); + } + + @Benchmark + public void fill_static(ValState st1) { + fill_static_val(st1.arr); + } + + @Benchmark + public void arrayfill_static(ValState st1) { + arrayfill_static_val(st1.arr); + } + + @Benchmark + public void fill_instance(ValState st1, InstanceHolder ih) { + fill_instance_val(st1.arr, ih); + } + + @Benchmark + public void arrayfill_instance(ValState st1, InstanceHolder ih) { + arrayfill_instance_val(st1.arr, ih); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/fill/ValueOop.java b/test/micro/org/openjdk/bench/valhalla/array/fill/ValueOop.java new file mode 100644 index 00000000000..8ecc5b4cf9e --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/fill/ValueOop.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.fill; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import java.util.Arrays; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class ValueOop extends FillBase { + + public static class IdentityInt { + public final int value; + public IdentityInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + public interface InterfaceOop { + public IdentityInt value(); + } + + public static value class ValueRef implements InterfaceOop { + + public final IdentityInt value; + + public ValueRef(IdentityInt value) { + this.value = value; + } + + public IdentityInt value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueRef[] arr; + + @Setup + public void setup() { + arr = new ValueRef[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueRef(new IdentityInt(i)); + } + } + } + + public static class StaticHolder { + public static ValueRef VALUE = new ValueRef(new IdentityInt(42)); + } + + @State(Scope.Thread) + public static class InstanceHolder { + public ValueRef VALUE = new ValueRef(new IdentityInt(42)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueRef get_val(int i) { + return new ValueRef(new IdentityInt(i)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_new_val(ValueRef[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueRef(new IdentityInt(42)); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_new_val(ValueRef[] dst) { + Arrays.fill(dst, new ValueRef(new IdentityInt(42))); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_local_val(ValueRef[] dst) { + ValueRef local = get_val(42); + for (int i = 0; i < dst.length; i++) { + dst[i] = local; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_local_val(ValueRef[] dst) { + Arrays.fill(dst, get_val(42)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_static_val(ValueRef[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = StaticHolder.VALUE; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_static_val(ValueRef[] dst) { + Arrays.fill(dst, StaticHolder.VALUE); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_instance_val(ValueRef[] dst, InstanceHolder ih) { + for (int i = 0; i < dst.length; i++) { + dst[i] = ih.VALUE; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_instance_val(ValueRef[] dst, InstanceHolder ih) { + Arrays.fill(dst, ih.VALUE); + } + + @Benchmark + public void fill_new(ValState st1) { + fill_new_val(st1.arr); + } + + @Benchmark + public void arrayfill_new(ValState st1) { + arrayfill_new_val(st1.arr); + } + + @Benchmark + public void fill_local(ValState st1) { + fill_local_val(st1.arr); + } + + @Benchmark + public void arrayfill_local(ValState st1) { + arrayfill_local_val(st1.arr); + } + + @Benchmark + public void fill_static(ValState st1) { + fill_static_val(st1.arr); + } + + @Benchmark + public void arrayfill_static(ValState st1) { + arrayfill_static_val(st1.arr); + } + + @Benchmark + public void fill_instance(ValState st1, InstanceHolder ih) { + fill_instance_val(st1.arr, ih); + } + + @Benchmark + public void arrayfill_instance(ValState st1, InstanceHolder ih) { + arrayfill_instance_val(st1.arr, ih); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/fill/ValueOopNullFree.java b/test/micro/org/openjdk/bench/valhalla/array/fill/ValueOopNullFree.java new file mode 100644 index 00000000000..3218a1d21dc --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/fill/ValueOopNullFree.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.fill; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import java.util.Arrays; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class ValueOopNullFree extends FillBase { + + public static class IdentityInt { + public final int value; + public IdentityInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + public interface InterfaceOop { + public IdentityInt value(); + } + + public static value class ValueRef implements InterfaceOop { + + public final IdentityInt value; + + public ValueRef(IdentityInt value) { + this.value = value; + } + + public IdentityInt value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueRef[] arr; + + @Setup + public void setup() { + arr = (ValueRef[]) ValueClass.newNullRestrictedAtomicArray(ValueRef.class, size, new ValueRef(new IdentityInt(0))); + for (int i = 0; i < size; i++) { + arr[i] = new ValueRef(new IdentityInt(i)); + } + } + } + + public static class StaticHolder { + @Strict + @NullRestricted + public static ValueRef VALUE = new ValueRef(new IdentityInt(42)); + } + + @State(Scope.Thread) + public static class InstanceHolder { + @Strict + @NullRestricted + public ValueRef VALUE = new ValueRef(new IdentityInt(42)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueRef get_val(int i) { + return new ValueRef(new IdentityInt(i)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_new_val(ValueRef[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueRef(new IdentityInt(42)); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_new_val(ValueRef[] dst) { + Arrays.fill(dst, new ValueRef(new IdentityInt(42))); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_local_val(ValueRef[] dst) { + ValueRef local = get_val(42); + for (int i = 0; i < dst.length; i++) { + dst[i] = local; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_local_val(ValueRef[] dst) { + Arrays.fill(dst, get_val(42)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_static_val(ValueRef[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = StaticHolder.VALUE; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_static_val(ValueRef[] dst) { + Arrays.fill(dst, StaticHolder.VALUE); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_instance_val(ValueRef[] dst, InstanceHolder ih) { + for (int i = 0; i < dst.length; i++) { + dst[i] = ih.VALUE; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_instance_val(ValueRef[] dst, InstanceHolder ih) { + Arrays.fill(dst, ih.VALUE); + } + + @Benchmark + public void fill_new(ValState st1) { + fill_new_val(st1.arr); + } + + @Benchmark + public void arrayfill_new(ValState st1) { + arrayfill_new_val(st1.arr); + } + + @Benchmark + public void fill_local(ValState st1) { + fill_local_val(st1.arr); + } + + @Benchmark + public void arrayfill_local(ValState st1) { + arrayfill_local_val(st1.arr); + } + + @Benchmark + public void fill_static(ValState st1) { + fill_static_val(st1.arr); + } + + @Benchmark + public void arrayfill_static(ValState st1) { + arrayfill_static_val(st1.arr); + } + + @Benchmark + public void fill_instance(ValState st1, InstanceHolder ih) { + fill_instance_val(st1.arr, ih); + } + + @Benchmark + public void arrayfill_instance(ValState st1, InstanceHolder ih) { + arrayfill_instance_val(st1.arr, ih); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/fill/ValueOopNullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/array/fill/ValueOopNullFreeNonAtomic.java new file mode 100644 index 00000000000..69a8daf4709 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/fill/ValueOopNullFreeNonAtomic.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.fill; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import java.util.Arrays; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class ValueOopNullFreeNonAtomic extends FillBase { + + public static class IdentityInt { + public final int value; + public IdentityInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + public interface InterfaceOop { + public IdentityInt value(); + } + + @LooselyConsistentValue + public static value class ValueRef implements InterfaceOop { + + public final IdentityInt value; + + public ValueRef(IdentityInt value) { + this.value = value; + } + + public IdentityInt value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueRef[] arr; + + @Setup + public void setup() { + arr = (ValueRef[]) ValueClass.newNullRestrictedAtomicArray(ValueRef.class, size, new ValueRef(new IdentityInt(0))); + for (int i = 0; i < size; i++) { + arr[i] = new ValueRef(new IdentityInt(i)); + } + } + } + + public static class StaticHolder { + @Strict + @NullRestricted + public static ValueRef VALUE = new ValueRef(new IdentityInt(42)); + } + + @State(Scope.Thread) + public static class InstanceHolder { + @Strict + @NullRestricted + public ValueRef VALUE = new ValueRef(new IdentityInt(42)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueRef get_val(int i) { + return new ValueRef(new IdentityInt(i)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_new_val(ValueRef[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueRef(new IdentityInt(42)); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_new_val(ValueRef[] dst) { + Arrays.fill(dst, new ValueRef(new IdentityInt(42))); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_local_val(ValueRef[] dst) { + ValueRef local = get_val(42); + for (int i = 0; i < dst.length; i++) { + dst[i] = local; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_local_val(ValueRef[] dst) { + Arrays.fill(dst, get_val(42)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_static_val(ValueRef[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = StaticHolder.VALUE; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_static_val(ValueRef[] dst) { + Arrays.fill(dst, StaticHolder.VALUE); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void fill_instance_val(ValueRef[] dst, InstanceHolder ih) { + for (int i = 0; i < dst.length; i++) { + dst[i] = ih.VALUE; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void arrayfill_instance_val(ValueRef[] dst, InstanceHolder ih) { + Arrays.fill(dst, ih.VALUE); + } + + @Benchmark + public void fill_new(ValState st1) { + fill_new_val(st1.arr); + } + + @Benchmark + public void arrayfill_new(ValState st1) { + arrayfill_new_val(st1.arr); + } + + @Benchmark + public void fill_local(ValState st1) { + fill_local_val(st1.arr); + } + + @Benchmark + public void arrayfill_local(ValState st1) { + arrayfill_local_val(st1.arr); + } + + @Benchmark + public void fill_static(ValState st1) { + fill_static_val(st1.arr); + } + + @Benchmark + public void arrayfill_static(ValState st1) { + arrayfill_static_val(st1.arr); + } + + @Benchmark + public void fill_instance(ValState st1, InstanceHolder ih) { + fill_instance_val(st1.arr, ih); + } + + @Benchmark + public void arrayfill_instance(ValState st1, InstanceHolder ih) { + arrayfill_instance_val(st1.arr, ih); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/read/Identity.java b/test/micro/org/openjdk/bench/valhalla/array/read/Identity.java new file mode 100644 index 00000000000..c5cd3261339 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/read/Identity.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.read; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +public class Identity extends ReadBase { + + public interface InterfaceInt { + public int value(); + } + + public static class IdentityInt implements InterfaceInt { + public final int value; + public IdentityInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + + public static class RefState extends SizeState { + public IdentityInt[] arr; + + @Setup + public void setup() { + arr = new IdentityInt[size]; + for (int i = 0; i < size; i++) { + arr[i] = new IdentityInt(i); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_ref(IdentityInt[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i]); + } + } + + + @Benchmark + public void read(RefState st1, Blackhole bh) { + read_ref(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/read/Primitive032.java b/test/micro/org/openjdk/bench/valhalla/array/read/Primitive032.java new file mode 100644 index 00000000000..1d78186d8ce --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/read/Primitive032.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.read; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +public class Primitive032 extends ReadBase { + + public static class PrimitiveState extends SizeState { + public int[] arr; + + @Setup + public void setup() { + arr = new int[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = i; + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static void read_primitive(int[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i]); + } + } + + @Benchmark + public void read(PrimitiveState st1, Blackhole bh) { + read_primitive(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/read/ReadBase.java b/test/micro/org/openjdk/bench/valhalla/array/read/ReadBase.java new file mode 100644 index 00000000000..b8bae66cffd --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/read/ReadBase.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.read; + + +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@Fork(3) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +public class ReadBase { + + @State(Scope.Thread) + public static abstract class SizeState { + + @Param({ + "100", // tiny size, to fit into all caches and check codegeneration quality + "1000000" // large size, to be out of all caches and memory effects + }) + public int size; + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/read/Value032.java b/test/micro/org/openjdk/bench/valhalla/array/read/Value032.java new file mode 100644 index 00000000000..d3a8f19e1c8 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/read/Value032.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.read; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value032 extends ReadBase { + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt implements InterfaceInt { + + public final int value; + + public ValueInt(int value) { + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt[] arr; + + @Setup + public void setup() { + arr = new ValueInt[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt(i); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_val(ValueInt[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i]); + } + } + + @Benchmark + public void read(ValState st1, Blackhole bh) { + read_val(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/read/Value032NullFree.java b/test/micro/org/openjdk/bench/valhalla/array/read/Value032NullFree.java new file mode 100644 index 00000000000..698df68f1fb --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/read/Value032NullFree.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.read; + +import jdk.internal.value.ValueClass; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class Value032NullFree extends ReadBase { + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt implements InterfaceInt { + + public final int value; + + public ValueInt(int value) { + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt[] arr; + + @Setup + public void setup() { + arr = (ValueInt[]) ValueClass.newNullRestrictedAtomicArray(ValueInt.class, size, new ValueInt(0)); + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt(i); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_val(ValueInt[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i]); + } + } + + @Benchmark + public void read(ValState st1, Blackhole bh) { + read_val(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/read/Value032NullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/array/read/Value032NullFreeNonAtomic.java new file mode 100644 index 00000000000..e9ce85ab804 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/read/Value032NullFreeNonAtomic.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.read; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class Value032NullFreeNonAtomic extends ReadBase { + + public interface InterfaceInt { + public int value(); + } + + @LooselyConsistentValue + public static value class ValueInt implements InterfaceInt { + + public final int value; + + public ValueInt(int value) { + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt[] arr; + + @Setup + public void setup() { + arr = (ValueInt[]) ValueClass.newNullRestrictedNonAtomicArray(ValueInt.class, size, new ValueInt(0)); + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt(i); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_val(ValueInt[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i]); + } + } + + @Benchmark + public void read(ValState st1, Blackhole bh) { + read_val(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/read/Value128.java b/test/micro/org/openjdk/bench/valhalla/array/read/Value128.java new file mode 100644 index 00000000000..071297109c3 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/read/Value128.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.read; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value128 extends ReadBase { + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt4[] arr; + + @Setup + public void setup() { + arr = new ValueInt4[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt4(i); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_val(ValueInt4[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i]); + } + } + + @Benchmark + public void read(ValState st1, Blackhole bh) { + read_val(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/read/Value128NullFree.java b/test/micro/org/openjdk/bench/valhalla/array/read/Value128NullFree.java new file mode 100644 index 00000000000..bfe4f0bd12b --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/read/Value128NullFree.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.read; + +import jdk.internal.value.ValueClass; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class Value128NullFree extends ReadBase { + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt4[] arr; + + @Setup + public void setup() { + arr = (ValueInt4[]) ValueClass.newNullRestrictedAtomicArray(ValueInt4.class, size, new ValueInt4(0)); + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt4(i); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_val(ValueInt4[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i]); + } + } + + @Benchmark + public void read(ValState st1, Blackhole bh) { + read_val(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/read/Value128NullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/array/read/Value128NullFreeNonAtomic.java new file mode 100644 index 00000000000..e01c311a6a2 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/read/Value128NullFreeNonAtomic.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.read; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class Value128NullFreeNonAtomic extends ReadBase { + + public interface InterfaceInt { + public int value(); + } + + @LooselyConsistentValue + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt4[] arr; + + @Setup + public void setup() { + arr = (ValueInt4[]) ValueClass.newNullRestrictedNonAtomicArray(ValueInt4.class, size, new ValueInt4(0)); + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt4(i); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_val(ValueInt4[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i]); + } + } + + @Benchmark + public void read(ValState st1, Blackhole bh) { + read_val(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/read/ValueOop.java b/test/micro/org/openjdk/bench/valhalla/array/read/ValueOop.java new file mode 100644 index 00000000000..105de0b13b1 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/read/ValueOop.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.read; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class ValueOop extends ReadBase { + + public static class IdentityInt { + public final int value; + public IdentityInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + public interface InterfaceOop { + public IdentityInt value(); + } + + public static value class ValueRef implements InterfaceOop { + + public final IdentityInt value; + + public ValueRef(IdentityInt value) { + this.value = value; + } + + public IdentityInt value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueRef[] arr; + + @Setup + public void setup() { + arr = new ValueRef[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueRef(new IdentityInt(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_val(ValueRef[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i]); + } + } + + @Benchmark + public void read(ValState st1, Blackhole bh) { + read_val(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/read/ValueOopNullFree.java b/test/micro/org/openjdk/bench/valhalla/array/read/ValueOopNullFree.java new file mode 100644 index 00000000000..6e66f180410 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/read/ValueOopNullFree.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.read; + +import jdk.internal.value.ValueClass; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class ValueOopNullFree extends ReadBase { + + public static class IdentityInt { + public final int value; + public IdentityInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + public interface InterfaceOop { + public IdentityInt value(); + } + + public static value class ValueRef implements InterfaceOop { + + public final IdentityInt value; + + public ValueRef(IdentityInt value) { + this.value = value; + } + + public IdentityInt value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueRef[] arr; + + @Setup + public void setup() { + arr = (ValueRef[]) ValueClass.newNullRestrictedAtomicArray(ValueRef.class, size, new ValueRef(new IdentityInt(0))); + for (int i = 0; i < size; i++) { + arr[i] = new ValueRef(new IdentityInt(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_val(ValueRef[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i]); + } + } + + @Benchmark + public void read(ValState st1, Blackhole bh) { + read_val(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/read/ValueOopNullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/array/read/ValueOopNullFreeNonAtomic.java new file mode 100644 index 00000000000..7d4015fc20f --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/read/ValueOopNullFreeNonAtomic.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.read; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class ValueOopNullFreeNonAtomic extends ReadBase { + + public static class IdentityInt { + public final int value; + public IdentityInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + public interface InterfaceOop { + public IdentityInt value(); + } + + @LooselyConsistentValue + public static value class ValueRef implements InterfaceOop { + + public final IdentityInt value; + + public ValueRef(IdentityInt value) { + this.value = value; + } + + public IdentityInt value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueRef[] arr; + + @Setup + public void setup() { + arr = (ValueRef[]) ValueClass.newNullRestrictedAtomicArray(ValueRef.class, size, new ValueRef(new IdentityInt(0))); + for (int i = 0; i < size; i++) { + arr[i] = new ValueRef(new IdentityInt(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_val(ValueRef[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i]); + } + } + + @Benchmark + public void read(ValState st1, Blackhole bh) { + read_val(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/readpartial/Identity.java b/test/micro/org/openjdk/bench/valhalla/array/readpartial/Identity.java new file mode 100644 index 00000000000..1b7bfa196f9 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/readpartial/Identity.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.readpartial; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +public class Identity extends ReadBase { + + public interface InterfaceInt { + public int value(); + } + + public static class IdentityInt4 implements InterfaceInt { + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + public final int value; + public IdentityInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + public int value() { + return value; + } + } + + + public static class RefState extends SizeState { + public IdentityInt4[] arr; + + @Setup + public void setup() { + arr = new IdentityInt4[size]; + for (int i = 0; i < size; i++) { + arr[i] = new IdentityInt4(i); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_ref(IdentityInt4[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i].value); + } + } + + @Benchmark + public void read(RefState st1, Blackhole bh) { + read_ref(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/readpartial/Primitive128.java b/test/micro/org/openjdk/bench/valhalla/array/readpartial/Primitive128.java new file mode 100644 index 00000000000..de95bac7f7a --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/readpartial/Primitive128.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.readpartial; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +public class Primitive128 extends ReadBase { + + public static class PrimitiveState extends SizeState { + public int[] arr; + + @Setup + public void setup() { + arr = new int[size * 4]; + for (int i = 0; i < arr.length; i++) { + arr[i] = i; + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static void read_primitive(int[] src, Blackhole bh) { + for (int i = 0; i < src.length; i += 4) { + bh.consume(src[i + 3]); + } + } + + @Benchmark + public void read(PrimitiveState st1, Blackhole bh) { + read_primitive(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/readpartial/ReadBase.java b/test/micro/org/openjdk/bench/valhalla/array/readpartial/ReadBase.java new file mode 100644 index 00000000000..e8818239ed7 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/readpartial/ReadBase.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.readpartial; + + +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@Fork(3) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +public class ReadBase { + + @State(Scope.Thread) + public static abstract class SizeState { + + @Param({ + "100", // tiny size, to fit into all caches and check codegeneration quality + "1000000" // large size, to be out of all caches and memory effects + }) + public int size; + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/readpartial/Value128.java b/test/micro/org/openjdk/bench/valhalla/array/readpartial/Value128.java new file mode 100644 index 00000000000..f7eaf45bdd5 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/readpartial/Value128.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.readpartial; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value128 extends ReadBase { + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt4[] arr; + + @Setup + public void setup() { + arr = new ValueInt4[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt4(i); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_val(ValueInt4[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i].value); + } + } + + @Benchmark + public void read(ValState st1, Blackhole bh) { + read_val(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/readpartial/Value128NullFree.java b/test/micro/org/openjdk/bench/valhalla/array/readpartial/Value128NullFree.java new file mode 100644 index 00000000000..f6d912167e1 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/readpartial/Value128NullFree.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.readpartial; + +import jdk.internal.value.ValueClass; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class Value128NullFree extends ReadBase { + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt4[] arr; + + @Setup + public void setup() { + arr = (ValueInt4[]) ValueClass.newNullRestrictedAtomicArray(ValueInt4.class, size, new ValueInt4(0)); + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt4(i); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_val(ValueInt4[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i].value); + } + } + + @Benchmark + public void read(ValState st1, Blackhole bh) { + read_val(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/readpartial/Value128NullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/array/readpartial/Value128NullFreeNonAtomic.java new file mode 100644 index 00000000000..b8b450dc52e --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/readpartial/Value128NullFreeNonAtomic.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.readpartial; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class Value128NullFreeNonAtomic extends ReadBase { + + public interface InterfaceInt { + public int value(); + } + + @LooselyConsistentValue + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt4[] arr; + + @Setup + public void setup() { + arr = (ValueInt4[]) ValueClass.newNullRestrictedNonAtomicArray(ValueInt4.class, size, new ValueInt4(0)); + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt4(i); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_val(ValueInt4[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i]); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_int(InterfaceInt[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i].value()); + } + } + + @Benchmark + public void read(ValState st1, Blackhole bh) { + read_val(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/readpartial/ValueOop.java b/test/micro/org/openjdk/bench/valhalla/array/readpartial/ValueOop.java new file mode 100644 index 00000000000..f2241674aea --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/readpartial/ValueOop.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.readpartial; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class ValueOop extends ReadBase { + + public static class IdentityInt { + public final int value; + public IdentityInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + public interface InterfaceOop { + public IdentityInt value(); + } + + public static value class ValueRef4 implements InterfaceOop { + public final IdentityInt prevalue0; + public final IdentityInt prevalue1; + public final IdentityInt prevalue2; + public final IdentityInt value; + + public ValueRef4(IdentityInt value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public IdentityInt value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueRef4[] arr; + + @Setup + public void setup() { + arr = new ValueRef4[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueRef4(new IdentityInt(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_val(ValueRef4[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i].value); + } + } + + @Benchmark + public void read(ValState st1, Blackhole bh) { + read_val(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/readpartial/ValueOopNullFree.java b/test/micro/org/openjdk/bench/valhalla/array/readpartial/ValueOopNullFree.java new file mode 100644 index 00000000000..f62d517e910 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/readpartial/ValueOopNullFree.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.readpartial; + +import jdk.internal.value.ValueClass; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class ValueOopNullFree extends ReadBase { + + public static class IdentityInt { + public final int value; + public IdentityInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + public interface InterfaceOop { + public IdentityInt value(); + } + + public static value class ValueRef4 implements InterfaceOop { + public final IdentityInt prevalue0; + public final IdentityInt prevalue1; + public final IdentityInt prevalue2; + public final IdentityInt value; + + public ValueRef4(IdentityInt value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public IdentityInt value() { + return value; + } + + } + + + public static class ValState extends SizeState { + public ValueRef4[] arr; + + @Setup + public void setup() { + arr = (ValueRef4[]) ValueClass.newNullRestrictedAtomicArray(ValueRef4.class, size, new ValueRef4(new IdentityInt(0))); + for (int i = 0; i < size; i++) { + arr[i] = new ValueRef4(new IdentityInt(i)); + } + } + } + + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_val(ValueRef4[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i].value); + } + } + + @Benchmark + public void read(ValState st1, Blackhole bh) { + read_val(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/readpartial/ValueOopNullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/array/readpartial/ValueOopNullFreeNonAtomic.java new file mode 100644 index 00000000000..fd0fe34eb15 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/readpartial/ValueOopNullFreeNonAtomic.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.readpartial; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class ValueOopNullFreeNonAtomic extends ReadBase { + + public static class IdentityInt { + public final int value; + public IdentityInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + public interface InterfaceOop { + public IdentityInt value(); + } + + @LooselyConsistentValue + public static value class ValueRef4 implements InterfaceOop { + public final IdentityInt prevalue0; + public final IdentityInt prevalue1; + public final IdentityInt prevalue2; + public final IdentityInt value; + + public ValueRef4(IdentityInt value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public IdentityInt value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueRef4[] arr; + + @Setup + public void setup() { + arr = (ValueRef4[]) ValueClass.newNullRestrictedAtomicArray(ValueRef4.class, size, new ValueRef4(new IdentityInt(0))); + for (int i = 0; i < size; i++) { + arr[i] = new ValueRef4(new IdentityInt(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_val(ValueRef4[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i].value); + } + } + + @Benchmark + public void read(ValState st1, Blackhole bh) { + read_val(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/set/Identity.java b/test/micro/org/openjdk/bench/valhalla/array/set/Identity.java new file mode 100644 index 00000000000..2a341776f52 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/set/Identity.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.set; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Setup; + +public class Identity extends SetBase { + + public interface InterfaceInt { + public int value(); + } + + public static class IdentityInt implements InterfaceInt { + public final int value; + public IdentityInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + + public static class RefState extends SizeState { + public IdentityInt[] arr; + + @Setup + public void setup() { + arr = new IdentityInt[size]; + for (int i = 0; i < size; i++) { + arr[i] = new IdentityInt(i); + } + } + } + + public static class IntState extends SizeState { + public InterfaceInt[] arr; + + @Setup + public void setup() { + arr = new InterfaceInt[size]; + for (int i = 0; i < size; i++) { + arr[i] = new IdentityInt(i); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public IdentityInt get_ref(int i) { + return new IdentityInt(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public InterfaceInt get_int(int i) { + return new IdentityInt(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new_ref(IdentityInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new IdentityInt(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new_int(InterfaceInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new IdentityInt(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call_ref(IdentityInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = get_ref(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call_int(InterfaceInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = get_int(i); + } + } + + @Benchmark + public void set_new_ref_as_ref(RefState st1) { + set_new_ref(st1.arr); + } + + @Benchmark + public void set_new_int_as_int(IntState st1) { + set_new_int(st1.arr); + } + + @Benchmark + public void set_call_ref_as_ref(RefState st1) { + set_call_ref(st1.arr); + } + + @Benchmark + public void set_call_int_as_int(IntState st1) { + set_call_int(st1.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/set/Primitive032.java b/test/micro/org/openjdk/bench/valhalla/array/set/Primitive032.java new file mode 100644 index 00000000000..805f686d5cb --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/set/Primitive032.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.set; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Setup; + +public class Primitive032 extends SetBase { + + public static class PrimitiveState extends SizeState { + public int[] arr; + + @Setup + public void setup() { + arr = new int[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = i; + } + } + } + + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int get_primitive(int i) { + return i; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static void set_primitive(int[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = i; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static void set_call_primitive(int[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = get_primitive(i); + } + } + + @Benchmark + public void set(PrimitiveState st1) { + set_primitive(st1.arr); + } + + @Benchmark + public void set_call(PrimitiveState st1) { + set_call_primitive(st1.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/set/SetBase.java b/test/micro/org/openjdk/bench/valhalla/array/set/SetBase.java new file mode 100644 index 00000000000..274452493f0 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/set/SetBase.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.set; + + +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@Fork(3) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +public class SetBase { + + @State(Scope.Thread) + public static abstract class SizeState { + + @Param({ + "100", // tiny size, to fit into all caches and check codegeneration quality + "1000000" // large size, to be out of all caches and memory effects + }) + public int size; + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/set/Value032.java b/test/micro/org/openjdk/bench/valhalla/array/set/Value032.java new file mode 100644 index 00000000000..a8376a9af3f --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/set/Value032.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.set; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value032 extends SetBase { + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt implements InterfaceInt { + + public final int value; + + public ValueInt(int value) { + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt[] arr; + + @Setup + public void setup() { + arr = new ValueInt[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt(i); + } + } + } + + public static class IntState extends SizeState { + public InterfaceInt[] arr; + + @Setup + public void setup() { + arr = new InterfaceInt[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt(i); + } + } + + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueInt get_val(int i) { + return new ValueInt(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public InterfaceInt get_int(int i) { + return new ValueInt(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new_val(ValueInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueInt(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new_int(InterfaceInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueInt(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call_val(ValueInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = get_val(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call_int(InterfaceInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = get_int(i); + } + } + + @Benchmark + public void set_new_val_as_val(ValState st1) { + set_new_val(st1.arr); + } + + @Benchmark + public void set_new_int_as_int(IntState st1) { + set_new_int(st1.arr); + } + + @Benchmark + public void set_call_val_as_val(ValState st1) { + set_call_val(st1.arr); + } + + @Benchmark + public void set_call_int_as_int(IntState st1) { + set_call_int(st1.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/set/Value032NullFree.java b/test/micro/org/openjdk/bench/valhalla/array/set/Value032NullFree.java new file mode 100644 index 00000000000..33152c1c306 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/set/Value032NullFree.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.set; + +import jdk.internal.value.ValueClass; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class Value032NullFree extends SetBase { + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt implements InterfaceInt { + + public final int value; + + public ValueInt(int value) { + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt[] arr; + + @Setup + public void setup() { + arr = (ValueInt[]) ValueClass.newNullRestrictedAtomicArray(ValueInt.class, size, new ValueInt(0)); + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt(i); + } + } + } + + public static class IntState extends SizeState { + public InterfaceInt[] arr; + + @Setup + public void setup() { + arr = new InterfaceInt[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt(i); + } + } + + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueInt get_val(int i) { + return new ValueInt(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public InterfaceInt get_int(int i) { + return new ValueInt(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new_val(ValueInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueInt(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new_int(InterfaceInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueInt(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call_val(ValueInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = get_val(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call_int(InterfaceInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = get_int(i); + } + } + + @Benchmark + public void set_new_val_as_val(ValState st1) { + set_new_val(st1.arr); + } + + @Benchmark + public void set_new_int_as_int(IntState st1) { + set_new_int(st1.arr); + } + + @Benchmark + public void set_call_val_as_val(ValState st1) { + set_call_val(st1.arr); + } + + @Benchmark + public void set_call_int_as_int(IntState st1) { + set_call_int(st1.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/set/Value032NullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/array/set/Value032NullFreeNonAtomic.java new file mode 100644 index 00000000000..84508db7b73 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/set/Value032NullFreeNonAtomic.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.set; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class Value032NullFreeNonAtomic extends SetBase { + + public interface InterfaceInt { + public int value(); + } + + @LooselyConsistentValue + public static value class ValueInt implements InterfaceInt { + + public final int value; + + public ValueInt(int value) { + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt[] arr; + + @Setup + public void setup() { + arr = (ValueInt[]) ValueClass.newNullRestrictedNonAtomicArray(ValueInt.class, size, new ValueInt(0)); + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt(i); + } + } + } + + public static class IntState extends SizeState { + public InterfaceInt[] arr; + + @Setup + public void setup() { + arr = new InterfaceInt[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt(i); + } + } + + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueInt get_val(int i) { + return new ValueInt(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public InterfaceInt get_int(int i) { + return new ValueInt(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new_val(ValueInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueInt(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new_int(InterfaceInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueInt(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call_val(ValueInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = get_val(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call_int(InterfaceInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = get_int(i); + } + } + + @Benchmark + public void set_new_val_as_val(ValState st1) { + set_new_val(st1.arr); + } + + @Benchmark + public void set_new_int_as_int(IntState st1) { + set_new_int(st1.arr); + } + + @Benchmark + public void set_call_val_as_val(ValState st1) { + set_call_val(st1.arr); + } + + @Benchmark + public void set_call_int_as_int(IntState st1) { + set_call_int(st1.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/set/Value128.java b/test/micro/org/openjdk/bench/valhalla/array/set/Value128.java new file mode 100644 index 00000000000..e0b7ea9262b --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/set/Value128.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.set; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value128 extends SetBase { + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt4[] arr; + + @Setup + public void setup() { + arr = new ValueInt4[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt4(i); + } + } + } + + public static class IntState extends SizeState { + public InterfaceInt[] arr; + + @Setup + public void setup() { + arr = new InterfaceInt[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt4(i); + } + } + + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueInt4 get_val(int i) { + return new ValueInt4(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public InterfaceInt get_int(int i) { + return new ValueInt4(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new_val(ValueInt4[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueInt4(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new_int(InterfaceInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueInt4(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call_val(ValueInt4[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = get_val(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call_int(InterfaceInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = get_int(i); + } + } + + @Benchmark + public void set_new_val_as_val(ValState st1) { + set_new_val(st1.arr); + } + + @Benchmark + public void set_new_int_as_int(IntState st1) { + set_new_int(st1.arr); + } + + @Benchmark + public void set_call_val_as_val(ValState st1) { + set_call_val(st1.arr); + } + + @Benchmark + public void set_call_int_as_int(IntState st1) { + set_call_int(st1.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/set/Value128NullFree.java b/test/micro/org/openjdk/bench/valhalla/array/set/Value128NullFree.java new file mode 100644 index 00000000000..6c69c36c05f --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/set/Value128NullFree.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.set; + +import jdk.internal.value.ValueClass; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class Value128NullFree extends SetBase { + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt4[] arr; + + @Setup + public void setup() { + arr = (ValueInt4[]) ValueClass.newNullRestrictedAtomicArray(ValueInt4.class, size, new ValueInt4(0)); + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt4(i); + } + } + } + + public static class IntState extends SizeState { + public InterfaceInt[] arr; + + @Setup + public void setup() { + arr = new InterfaceInt[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt4(i); + } + } + + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueInt4 get_val(int i) { + return new ValueInt4(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public InterfaceInt get_int(int i) { + return new ValueInt4(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new_val(ValueInt4[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueInt4(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new_int(InterfaceInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueInt4(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call_val(ValueInt4[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = get_val(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call_int(InterfaceInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = get_int(i); + } + } + + @Benchmark + public void set_new_val_as_val(ValState st1) { + set_new_val(st1.arr); + } + + @Benchmark + public void set_new_int_as_int(IntState st1) { + set_new_int(st1.arr); + } + + @Benchmark + public void set_call_val_as_val(ValState st1) { + set_call_val(st1.arr); + } + + @Benchmark + public void set_call_int_as_int(IntState st1) { + set_call_int(st1.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/set/Value128NullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/array/set/Value128NullFreeNonAtomic.java new file mode 100644 index 00000000000..1d9840c8250 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/set/Value128NullFreeNonAtomic.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.set; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class Value128NullFreeNonAtomic extends SetBase { + + public interface InterfaceInt { + public int value(); + } + + @LooselyConsistentValue + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt4[] arr; + + @Setup + public void setup() { + arr = (ValueInt4[]) ValueClass.newNullRestrictedNonAtomicArray(ValueInt4.class, size, new ValueInt4(0)); + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt4(i); + } + } + } + + public static class IntState extends SizeState { + public InterfaceInt[] arr; + + @Setup + public void setup() { + arr = new InterfaceInt[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueInt4(i); + } + } + + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueInt4 get_val(int i) { + return new ValueInt4(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public InterfaceInt get_int(int i) { + return new ValueInt4(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new_val(ValueInt4[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueInt4(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new_int(InterfaceInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueInt4(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call_val(ValueInt4[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = get_val(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call_int(InterfaceInt[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = get_int(i); + } + } + + @Benchmark + public void set_new_val_as_val(ValState st1) { + set_new_val(st1.arr); + } + + @Benchmark + public void set_new_int_as_int(IntState st1) { + set_new_int(st1.arr); + } + + @Benchmark + public void set_call_val_as_val(ValState st1) { + set_call_val(st1.arr); + } + + @Benchmark + public void set_call_int_as_int(IntState st1) { + set_call_int(st1.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/set/ValueOop.java b/test/micro/org/openjdk/bench/valhalla/array/set/ValueOop.java new file mode 100644 index 00000000000..213b1517ce8 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/set/ValueOop.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.set; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class ValueOop extends SetBase { + + public static class IdentityInt { + public final int value; + public IdentityInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + public interface InterfaceOop { + public IdentityInt value(); + } + + public static value class ValueRef implements InterfaceOop { + + public final IdentityInt value; + + public ValueRef(IdentityInt value) { + this.value = value; + } + + public IdentityInt value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueRef[] arr; + + @Setup + public void setup() { + arr = new ValueRef[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueRef(new IdentityInt(i)); + } + } + } + + public static class IntState extends SizeState { + public InterfaceOop[] arr; + + @Setup + public void setup() { + arr = new InterfaceOop[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueRef(new IdentityInt(i)); + } + } + + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueRef get_val(int i) { + return new ValueRef(new IdentityInt(i)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public InterfaceOop get_int(int i) { + return new ValueRef(new IdentityInt(i)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new_val(ValueRef[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueRef(new IdentityInt(i)); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new_int(InterfaceOop[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueRef(new IdentityInt(i)); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call_val(ValueRef[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = get_val(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call_int(InterfaceOop[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = get_int(i); + } + } + + @Benchmark + public void set_new_val_as_val(ValState st1) { + set_new_val(st1.arr); + } + + @Benchmark + public void set_new_int_as_int(IntState st1) { + set_new_int(st1.arr); + } + + @Benchmark + public void set_call_val_as_val(ValState st1) { + set_call_val(st1.arr); + } + + @Benchmark + public void set_call_int_as_int(IntState st1) { + set_call_int(st1.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/set/ValueOopNullFree.java b/test/micro/org/openjdk/bench/valhalla/array/set/ValueOopNullFree.java new file mode 100644 index 00000000000..2c29fb250f6 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/set/ValueOopNullFree.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.set; + +import jdk.internal.value.ValueClass; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class ValueOopNullFree extends SetBase { + + public static class IdentityInt { + public final int value; + public IdentityInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + public interface InterfaceOop { + public IdentityInt value(); + } + + public static value class ValueRef implements InterfaceOop { + + public final IdentityInt value; + + public ValueRef(IdentityInt value) { + this.value = value; + } + + public IdentityInt value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueRef[] arr; + + @Setup + public void setup() { + arr = (ValueRef[]) ValueClass.newNullRestrictedAtomicArray(ValueRef.class, size, new ValueRef(new IdentityInt(0))); + for (int i = 0; i < size; i++) { + arr[i] = new ValueRef(new IdentityInt(i)); + } + } + } + + public static class IntState extends SizeState { + public InterfaceOop[] arr; + + @Setup + public void setup() { + arr = new InterfaceOop[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueRef(new IdentityInt(i)); + } + } + + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueRef get_val(int i) { + return new ValueRef(new IdentityInt(i)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public InterfaceOop get_int(int i) { + return new ValueRef(new IdentityInt(i)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new_val(ValueRef[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueRef(new IdentityInt(i)); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new_int(InterfaceOop[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueRef(new IdentityInt(i)); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call_val(ValueRef[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = get_val(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call_int(InterfaceOop[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = get_int(i); + } + } + + @Benchmark + public void set_new_val_as_val(ValState st1) { + set_new_val(st1.arr); + } + + @Benchmark + public void set_new_int_as_int(IntState st1) { + set_new_int(st1.arr); + } + + @Benchmark + public void set_call_val_as_val(ValState st1) { + set_call_val(st1.arr); + } + + @Benchmark + public void set_call_int_as_int(IntState st1) { + set_call_int(st1.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/set/ValueOopNullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/array/set/ValueOopNullFreeNonAtomic.java new file mode 100644 index 00000000000..ce038ef46aa --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/set/ValueOopNullFreeNonAtomic.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.set; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class ValueOopNullFreeNonAtomic extends SetBase { + + public static class IdentityInt { + public final int value; + public IdentityInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + public interface InterfaceOop { + public IdentityInt value(); + } + + @LooselyConsistentValue + public static value class ValueRef implements InterfaceOop { + + public final IdentityInt value; + + public ValueRef(IdentityInt value) { + this.value = value; + } + + public IdentityInt value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueRef[] arr; + + @Setup + public void setup() { + arr = (ValueRef[]) ValueClass.newNullRestrictedAtomicArray(ValueRef.class, size, new ValueRef(new IdentityInt(0))); + for (int i = 0; i < size; i++) { + arr[i] = new ValueRef(new IdentityInt(i)); + } + } + } + + public static class IntState extends SizeState { + public InterfaceOop[] arr; + + @Setup + public void setup() { + arr = new InterfaceOop[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValueRef(new IdentityInt(i)); + } + } + + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueRef get_val(int i) { + return new ValueRef(new IdentityInt(i)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public InterfaceOop get_int(int i) { + return new ValueRef(new IdentityInt(i)); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new_val(ValueRef[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueRef(new IdentityInt(i)); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new_int(InterfaceOop[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = new ValueRef(new IdentityInt(i)); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call_val(ValueRef[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = get_val(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call_int(InterfaceOop[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i] = get_int(i); + } + } + + @Benchmark + public void set_new_val_as_val(ValState st1) { + set_new_val(st1.arr); + } + + @Benchmark + public void set_new_int_as_int(IntState st1) { + set_new_int(st1.arr); + } + + @Benchmark + public void set_call_val_as_val(ValState st1) { + set_call_val(st1.arr); + } + + @Benchmark + public void set_call_int_as_int(IntState st1) { + set_call_int(st1.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/walk/Identity.java b/test/micro/org/openjdk/bench/valhalla/array/walk/Identity.java new file mode 100644 index 00000000000..9ece262c815 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/walk/Identity.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.walk; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Setup; + +public class Identity extends WalkBase { + + public interface InterfaceInt { + public int value(); + } + + public static class IdentityInt implements InterfaceInt { + + public final int value; + + public IdentityInt(int value) { + this.value = value; + } + + public int value() { + return value; + } + + } + + + public static class RefState extends SizeState { + public IdentityInt[] arr; + + @Setup + public void setup() { + arr = new IdentityInt[size]; + int[] a = makeRandomRing(arr.length); + for (int i = 0; i < a.length; i++) { + arr[i] = new IdentityInt(a[i]); + } + } + } + + public static class IntState extends SizeState { + public InterfaceInt[] arr; + + @Setup + public void setup() { + arr = new InterfaceInt[size]; + int[] a = makeRandomRing(arr.length); + for (int i = 0; i < a.length; i++) { + arr[i] = new IdentityInt(a[i]); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int walk_ref(IdentityInt[] values) { + int steps = 0; + for (int i = values[0].value(); i != 0; i = values[i].value()) steps++; + return steps; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int walk_int(InterfaceInt[] values) { + int steps = 0; + for (int i = values[0].value(); i != 0; i = values[i].value()) steps++; + return steps; + } + + @Benchmark + public int walk_ref_as_ref(RefState st) { + return walk_ref(st.arr); + } + + @Benchmark + public int walk_int_as_int(IntState st) { + return walk_int(st.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/walk/Primitive.java b/test/micro/org/openjdk/bench/valhalla/array/walk/Primitive.java new file mode 100644 index 00000000000..098f45f134e --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/walk/Primitive.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.walk; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Setup; + +public class Primitive extends WalkBase { + + public static class PrimState extends SizeState { + public int[] arr; + + @Setup + public void setup() { + arr = makeRandomRing(size); + } + + } + + @Benchmark + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int walk(PrimState s) { + int steps = 0; + int[] values = s.arr; + for (int i = values[0]; i != 0; i = values[i]) steps++; + return steps; + } + +} + diff --git a/test/micro/org/openjdk/bench/valhalla/array/walk/Value032.java b/test/micro/org/openjdk/bench/valhalla/array/walk/Value032.java new file mode 100644 index 00000000000..f02e19fddb8 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/walk/Value032.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.walk; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value032 extends WalkBase { + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt implements InterfaceInt { + + public final int value; + + public ValueInt(int value) { + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt[] arr; + + @Setup + public void setup() { + arr = new ValueInt[size]; + int[] a = makeRandomRing(arr.length); + for (int i = 0; i < a.length; i++) { + arr[i] = new ValueInt(a[i]); + } + } + } + + public static class IntState extends SizeState { + public InterfaceInt[] arr; + + @Setup + public void setup() { + arr = new InterfaceInt[size]; + int[] a = makeRandomRing(arr.length); + for (int i = 0; i < a.length; i++) { + arr[i] = new ValueInt(a[i]); + } + } + + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int walk_val(ValueInt[] values) { + int steps = 0; + for (int i = values[0].value(); i != 0; i = values[i].value()) steps++; + return steps; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int walk_int(InterfaceInt[] values) { + int steps = 0; + for (int i = values[0].value(); i != 0; i = values[i].value()) steps++; + return steps; + } + + @Benchmark + public int walk_val_as_val(ValState st) { + return walk_val(st.arr); + } + + @Benchmark + public int walk_int_as_int(IntState st) { + return walk_int(st.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/walk/Value032NullFree.java b/test/micro/org/openjdk/bench/valhalla/array/walk/Value032NullFree.java new file mode 100644 index 00000000000..96dee5bea6d --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/walk/Value032NullFree.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.walk; + +import jdk.internal.value.ValueClass; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class Value032NullFree extends WalkBase { + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt implements InterfaceInt { + + public final int value; + + public ValueInt(int value) { + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt[] arr; + + @Setup + public void setup() { + arr = (ValueInt[]) ValueClass.newNullRestrictedAtomicArray(ValueInt.class, size, new ValueInt(0)); + int[] a = makeRandomRing(arr.length); + for (int i = 0; i < a.length; i++) { + arr[i] = new ValueInt(a[i]); + } + } + } + + public static class IntState extends SizeState { + public InterfaceInt[] arr; + + @Setup + public void setup() { + arr = new InterfaceInt[size]; + int[] a = makeRandomRing(arr.length); + for (int i = 0; i < a.length; i++) { + arr[i] = new ValueInt(a[i]); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int walk_val(ValueInt[] values) { + int steps = 0; + for (int i = values[0].value(); i != 0; i = values[i].value()) steps++; + return steps; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int walk_int(InterfaceInt[] values) { + int steps = 0; + for (int i = values[0].value(); i != 0; i = values[i].value()) steps++; + return steps; + } + + @Benchmark + public int walk_val_as_val(ValState st) { + return walk_val(st.arr); + } + + @Benchmark + public int walk_int_as_int(IntState st) { + return walk_int(st.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/walk/Value032NullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/array/walk/Value032NullFreeNonAtomic.java new file mode 100644 index 00000000000..33aa7f80d18 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/walk/Value032NullFreeNonAtomic.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.walk; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class Value032NullFreeNonAtomic extends WalkBase { + + public interface InterfaceInt { + public int value(); + } + + @LooselyConsistentValue + public static value class ValueInt implements InterfaceInt { + + public final int value; + + public ValueInt(int value) { + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt[] arr; + + @Setup + public void setup() { + arr = (ValueInt[]) ValueClass.newNullRestrictedNonAtomicArray(ValueInt.class, size, new ValueInt(0)); + int[] a = makeRandomRing(arr.length); + for (int i = 0; i < a.length; i++) { + arr[i] = new ValueInt(a[i]); + } + } + } + + public static class IntState extends SizeState { + public InterfaceInt[] arr; + + @Setup + public void setup() { + arr = new InterfaceInt[size]; + int[] a = makeRandomRing(arr.length); + for (int i = 0; i < a.length; i++) { + arr[i] = new ValueInt(a[i]); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int walk_val(ValueInt[] values) { + int steps = 0; + for (int i = values[0].value(); i != 0; i = values[i].value()) steps++; + return steps; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int walk_int(InterfaceInt[] values) { + int steps = 0; + for (int i = values[0].value(); i != 0; i = values[i].value()) steps++; + return steps; + } + + @Benchmark + public int walk_val_as_val(ValState st) { + return walk_val(st.arr); + } + + @Benchmark + public int walk_int_as_int(IntState st) { + return walk_int(st.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/walk/Value128.java b/test/micro/org/openjdk/bench/valhalla/array/walk/Value128.java new file mode 100644 index 00000000000..f336ef9aa06 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/walk/Value128.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.walk; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value128 extends WalkBase { + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt4[] arr; + + @Setup + public void setup() { + arr = new ValueInt4[size]; + int[] a = makeRandomRing(arr.length); + for (int i = 0; i < a.length; i++) { + arr[i] = new ValueInt4(a[i]); + } + } + } + + public static class IntState extends SizeState { + public InterfaceInt[] arr; + + @Setup + public void setup() { + arr = new InterfaceInt[size]; + int[] a = makeRandomRing(arr.length); + for (int i = 0; i < a.length; i++) { + arr[i] = new ValueInt4(a[i]); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int walk_val(ValueInt4[] values) { + int steps = 0; + for (int i = values[0].value(); i != 0; i = values[i].value()) steps++; + return steps; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int walk_int(InterfaceInt[] values) { + int steps = 0; + for (int i = values[0].value(); i != 0; i = values[i].value()) steps++; + return steps; + } + + @Benchmark + public int walk_val_as_val(ValState st) { + return walk_val(st.arr); + } + + @Benchmark + public int walk_int_as_int(IntState st) { + return walk_int(st.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/walk/Value128NullFree.java b/test/micro/org/openjdk/bench/valhalla/array/walk/Value128NullFree.java new file mode 100644 index 00000000000..f42633ce3eb --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/walk/Value128NullFree.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.walk; + +import jdk.internal.value.ValueClass; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class Value128NullFree extends WalkBase { + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt4[] arr; + + @Setup + public void setup() { + arr = (ValueInt4[]) ValueClass.newNullRestrictedAtomicArray(ValueInt4.class, size, new ValueInt4(0)); + int[] a = makeRandomRing(arr.length); + for (int i = 0; i < a.length; i++) { + arr[i] = new ValueInt4(a[i]); + } + } + } + + public static class IntState extends SizeState { + public InterfaceInt[] arr; + + @Setup + public void setup() { + arr = new InterfaceInt[size]; + int[] a = makeRandomRing(arr.length); + for (int i = 0; i < a.length; i++) { + arr[i] = new ValueInt4(a[i]); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int walk_val(ValueInt4[] values) { + int steps = 0; + for (int i = values[0].value(); i != 0; i = values[i].value()) steps++; + return steps; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int walk_int(InterfaceInt[] values) { + int steps = 0; + for (int i = values[0].value(); i != 0; i = values[i].value()) steps++; + return steps; + } + + @Benchmark + public int walk_val_as_val(ValState st) { + return walk_val(st.arr); + } + + @Benchmark + public int walk_int_as_int(IntState st) { + return walk_int(st.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/walk/Value128NullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/array/walk/Value128NullFreeNonAtomic.java new file mode 100644 index 00000000000..4a90370ce93 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/walk/Value128NullFreeNonAtomic.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.walk; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class Value128NullFreeNonAtomic extends WalkBase { + + public interface InterfaceInt { + public int value(); + } + + @LooselyConsistentValue + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValueInt4[] arr; + + @Setup + public void setup() { + arr = (ValueInt4[]) ValueClass.newNullRestrictedNonAtomicArray(ValueInt4.class, size, new ValueInt4(0)); + int[] a = makeRandomRing(arr.length); + for (int i = 0; i < a.length; i++) { + arr[i] = new ValueInt4(a[i]); + } + } + } + + public static class IntState extends SizeState { + public InterfaceInt[] arr; + + @Setup + public void setup() { + arr = new InterfaceInt[size]; + int[] a = makeRandomRing(arr.length); + for (int i = 0; i < a.length; i++) { + arr[i] = new ValueInt4(a[i]); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int walk_val(ValueInt4[] values) { + int steps = 0; + for (int i = values[0].value(); i != 0; i = values[i].value()) steps++; + return steps; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int walk_int(InterfaceInt[] values) { + int steps = 0; + for (int i = values[0].value(); i != 0; i = values[i].value()) steps++; + return steps; + } + + @Benchmark + public int walk_val_as_val(ValState st) { + return walk_val(st.arr); + } + + @Benchmark + public int walk_int_as_int(IntState st) { + return walk_int(st.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/array/walk/WalkBase.java b/test/micro/org/openjdk/bench/valhalla/array/walk/WalkBase.java new file mode 100644 index 00000000000..0ad521a7481 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/array/walk/WalkBase.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.array.walk; + + +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +@Fork(3) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +public class WalkBase { + + @State(Scope.Thread) + public static abstract class SizeState { + + @Param({ + "100", // tiny size, to fit into all caches and check codegeneration quality + "1000000" // large size, to be out of all caches and memory effects + }) + public int size; + } + + public static void shuffle(int[] a, Random rnd) { + for (int i = a.length; i > 1; i--) { + int idx = rnd.nextInt(i); + int tmp = a[i - 1]; + a[i - 1] = a[idx]; + a[idx] = tmp; + } + + } + + public static int[] makeRandomRing(int size) { + int[] A = new int[size - 1]; + for (int i = 0; i < A.length; i++) { + A[i] = i + 1; + } + shuffle(A, new Random(42)); + int[] a = new int[size]; + int x = 0; + for (int i = 0; i < A.length; i++) { + x = a[x] = A[i]; + } + a[x] = 0; + return a; + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/callconv/Primitive1.java b/test/micro/org/openjdk/bench/valhalla/callconv/Primitive1.java new file mode 100644 index 00000000000..8fbda154758 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/callconv/Primitive1.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.callconv; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +@Fork(3) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Primitive1 { + + public static final int SIZE = 96; // must be divisible by 2 and 3 and around 100 + + public abstract static class InvocationLogic { + public abstract int compute(int v1); + } + + public static class InvokeImpl1 extends InvocationLogic { + @Override + public int compute(int v1) { + return v1; + } + } + + public static class InvokeImpl2 extends InvocationLogic { + @Override + public int compute(int v1) { + return v1; + } + } + + public static class InvokeImpl3 extends InvocationLogic { + @Override + public int compute(int v1) { + return v1; + } + } + + @State(Scope.Thread) + public static class StateTargets { + InvocationLogic[] arr; + + @Setup + public void setup() { + arr = new InvocationLogic[SIZE]; + Arrays.setAll(arr, i -> getImpl(i, 3)); + } + + private InvocationLogic getImpl(int i, int targets) { + return switch (i % targets) { + case 0 -> new InvokeImpl1(); + case 1 -> new InvokeImpl2(); + default -> new InvokeImpl3(); + }; + } + } + + int a0 = 42; + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void primitive_args1(Blackhole bh, StateTargets st) { + InvocationLogic[] arr = st.arr; + for (InvocationLogic t : arr) { + bh.consume(t.compute(a0)); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/callconv/Primitive2.java b/test/micro/org/openjdk/bench/valhalla/callconv/Primitive2.java new file mode 100644 index 00000000000..ae7065b1391 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/callconv/Primitive2.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.callconv; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +@Fork(3) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Primitive2 { + + public static final int SIZE = 96; // must be divisible by 2 and 3 and around 100 + + public abstract static class InvocationLogic { + public abstract int compute(int v1, int v2); + } + + public static class InvokeImpl1 extends InvocationLogic { + @Override + public int compute(int v1, int v2) { + return v1; + } + } + + public static class InvokeImpl2 extends InvocationLogic { + @Override + public int compute(int v1, int v2) { + return v1; + } + } + + public static class InvokeImpl3 extends InvocationLogic { + @Override + public int compute(int v1, int v2) { + return v1; + } + } + + @State(Scope.Thread) + public static class StateTargets { + InvocationLogic[] arr; + + @Setup + public void setup() { + arr = new InvocationLogic[SIZE]; + Arrays.setAll(arr, i -> getImpl(i, 3)); + } + + private InvocationLogic getImpl(int i, int targets) { + return switch (i % targets) { + case 0 -> new InvokeImpl1(); + case 1 -> new InvokeImpl2(); + default -> new InvokeImpl3(); + }; + } + } + + int a0 = 42; + int a1 = 43; + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void primitive_args2(Blackhole bh, StateTargets st) { + InvocationLogic[] arr = st.arr; + for (InvocationLogic t : arr) { + bh.consume(t.compute(a0, a1)); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/callconv/Primitive4.java b/test/micro/org/openjdk/bench/valhalla/callconv/Primitive4.java new file mode 100644 index 00000000000..d31471b5e8c --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/callconv/Primitive4.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.callconv; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +@Fork(3) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Primitive4 { + + public static final int SIZE = 96; // must be divisible by 2 and 3 and around 100 + + public abstract static class InvocationLogic { + public abstract int compute(int v1, int v2, int v3, int v4); + } + + public static class InvokeImpl1 extends InvocationLogic { + @Override + public int compute(int v1, int v2, int v3, int v4) { + return v1; + } + } + + public static class InvokeImpl2 extends InvocationLogic { + @Override + public int compute(int v1, int v2, int v3, int v4) { + return v1; + } + } + + public static class InvokeImpl3 extends InvocationLogic { + @Override + public int compute(int v1, int v2, int v3, int v4) { + return v1; + } + } + + @State(Scope.Thread) + public static class StateTargets { + InvocationLogic[] arr; + + @Setup + public void setup() { + arr = new InvocationLogic[SIZE]; + Arrays.setAll(arr, i -> getImpl(i, 3)); + } + + private InvocationLogic getImpl(int i, int targets) { + return switch (i % targets) { + case 0 -> new InvokeImpl1(); + case 1 -> new InvokeImpl2(); + default -> new InvokeImpl3(); + }; + } + } + + int a0 = 42; + int a1 = 43; + int a2 = 44; + int a3 = 45; + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void primitive_args4(Blackhole bh, StateTargets st) { + InvocationLogic[] arr = st.arr; + for (InvocationLogic t : arr) { + bh.consume(t.compute(a0, a1, a2, a3)); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/callconv/Primitive8.java b/test/micro/org/openjdk/bench/valhalla/callconv/Primitive8.java new file mode 100644 index 00000000000..0eddbc37f2f --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/callconv/Primitive8.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.callconv; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +@Fork(3) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Primitive8 { + + public static final int SIZE = 96; // must be divisible by 2 and 3 and around 100 + + public abstract static class InvocationLogic { + public abstract int compute(int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8); + } + + public static class InvokeImpl1 extends InvocationLogic { + @Override + public int compute(int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8) { + return v1; + } + } + + public static class InvokeImpl2 extends InvocationLogic { + @Override + public int compute(int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8) { + return v1; + } + } + + public static class InvokeImpl3 extends InvocationLogic { + @Override + public int compute(int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8) { + return v1; + } + } + + @State(Scope.Thread) + public static class StateTargets { + InvocationLogic[] arr; + + @Setup + public void setup() { + arr = new InvocationLogic[SIZE]; + Arrays.setAll(arr, i -> getImpl(i, 3)); + } + + private InvocationLogic getImpl(int i, int targets) { + return switch (i % targets) { + case 0 -> new InvokeImpl1(); + case 1 -> new InvokeImpl2(); + default -> new InvokeImpl3(); + }; + } + } + + int a0 = 42; + int a1 = 43; + int a2 = 44; + int a3 = 45; + int a4 = 46; + int a5 = 47; + int a6 = 48; + int a7 = 49; + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void primitive_args8(Blackhole bh, StateTargets st) { + InvocationLogic[] arr = st.arr; + for (InvocationLogic t : arr) { + bh.consume(t.compute(a0, a1, a2, a3, a4, a5, a6, a7)); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/callconv/Value1.java b/test/micro/org/openjdk/bench/valhalla/callconv/Value1.java new file mode 100644 index 00000000000..b4875646cc8 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/callconv/Value1.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.callconv; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Value1 { + + public static final int SIZE = 96; // must be divisible by 2 and 3 and around 100 + + public static value class ValueInt1 { + + public final int v0; + + public ValueInt1(int v0) { + this.v0 = v0; + } + + public int value() { + return v0; + } + + public static ValueInt1 valueOf(int value) { + return new ValueInt1(value); + } + } + + public abstract static class InvocationLogic { + public abstract ValueInt1 compute(ValueInt1 v1); + } + + public static class InvokeImpl1 extends InvocationLogic { + @Override + public ValueInt1 compute(ValueInt1 v1) { + return v1; + } + } + + public static class InvokeImpl2 extends InvocationLogic { + @Override + public ValueInt1 compute(ValueInt1 v1) { + return v1; + } + } + + public static class InvokeImpl3 extends InvocationLogic { + @Override + public ValueInt1 compute(ValueInt1 v1) { + return v1; + } + } + + @State(Scope.Thread) + public static class StateTargets { + InvocationLogic[] arr; + + @Setup + public void setup() { + arr = new InvocationLogic[SIZE]; + Arrays.setAll(arr, i -> getImpl(i, 3)); + } + + private InvocationLogic getImpl(int i, int targets) { + return switch (i % targets) { + case 0 -> new InvokeImpl1(); + case 1 -> new InvokeImpl2(); + default -> new InvokeImpl3(); + }; + } + } + + ValueInt1 a0 = ValueInt1.valueOf(42); + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void value_args1(Blackhole bh, StateTargets st) { + ValueInt1 v0 = a0; + InvocationLogic[] arr = st.arr; + for (InvocationLogic t : arr) { + bh.consume(t.compute(v0)); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/callconv/Value2.java b/test/micro/org/openjdk/bench/valhalla/callconv/Value2.java new file mode 100644 index 00000000000..c80bce3e00e --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/callconv/Value2.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.callconv; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Value2 { + + public static final int SIZE = 96; // must be divisible by 2 and 3 and around 100 + + public static value class ValueInt1 { + + public final int v0; + + public ValueInt1(int v0) { + this.v0 = v0; + } + + public int value() { + return v0; + } + + public static ValueInt1 valueOf(int value) { + return new ValueInt1(value); + } + } + + public static value class ValueInt2 { + + public final int v0, v1; + + public ValueInt2(int v0, int v1) { + this.v0 = v0; + this.v1 = v1; + } + + public int value0() { + return v0; + } + + public static ValueInt2 valueOf(int v0, int v1) { + return new ValueInt2(v0, v1); + } + } + + public abstract static class InvocationLogic { + public abstract ValueInt1 compute(ValueInt1 v1, ValueInt1 v2); + public abstract ValueInt2 compute(ValueInt2 v1); + } + + public static class InvokeImpl1 extends InvocationLogic { + @Override + public ValueInt1 compute(ValueInt1 v1, ValueInt1 v2) { + return v1; + } + @Override + public ValueInt2 compute(ValueInt2 v1) { + return v1; + } + } + + public static class InvokeImpl2 extends InvocationLogic { + @Override + public ValueInt1 compute(ValueInt1 v1, ValueInt1 v2) { + return v1; + } + @Override + public ValueInt2 compute(ValueInt2 v1) { + return v1; + } + } + + public static class InvokeImpl3 extends InvocationLogic { + @Override + public ValueInt1 compute(ValueInt1 v1, ValueInt1 v2) { + return v1; + } + @Override + public ValueInt2 compute(ValueInt2 v1) { + return v1; + } + } + + @State(Scope.Thread) + public static class StateTargets { + InvocationLogic[] arr; + + @Setup + public void setup() { + arr = new InvocationLogic[SIZE]; + Arrays.setAll(arr, i -> getImpl(i, 3)); + } + + private InvocationLogic getImpl(int i, int targets) { + return switch (i % targets) { + case 0 -> new InvokeImpl1(); + case 1 -> new InvokeImpl2(); + default -> new InvokeImpl3(); + }; + } + } + + ValueInt1 a0 = ValueInt1.valueOf(42); + ValueInt1 a1 = ValueInt1.valueOf(43); + ValueInt2 b0 = ValueInt2.valueOf(42, 43); + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void value_args2(Blackhole bh, StateTargets st) { + ValueInt1 v0 = a0; + ValueInt1 v1 = a1; + InvocationLogic[] arr = st.arr; + for (InvocationLogic t : arr) { + bh.consume(t.compute(v0, v1)); + } + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void value_args1(Blackhole bh, StateTargets st) { + ValueInt2 v0 = b0; + InvocationLogic[] arr = st.arr; + for (InvocationLogic t : arr) { + bh.consume(t.compute(v0)); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/callconv/Value4.java b/test/micro/org/openjdk/bench/valhalla/callconv/Value4.java new file mode 100644 index 00000000000..2958459f889 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/callconv/Value4.java @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.callconv; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Value4 { + + public static final int SIZE = 96; // must be divisible by 2 and 3 and around 100 + + public static value class ValueInt1 { + + public final int v0; + + public ValueInt1(int v0) { + this.v0 = v0; + } + + public int value() { + return v0; + } + + public static ValueInt1 valueOf(int value) { + return new ValueInt1(value); + } + } + + public static value class ValueInt2 { + + public final int v0, v1; + + public ValueInt2(int v0, int v1) { + this.v0 = v0; + this.v1 = v1; + } + + public int value0() { + return v0; + } + + public static ValueInt2 valueOf(int v0, int v1) { + return new ValueInt2(v0, v1); + } + } + + public static value class ValueInt4 { + + public final int v0, v1, v2, v3; + + public ValueInt4(int v0, int v1, int v2, int v3) { + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + } + + public int value0() { + return v0; + } + + public static ValueInt4 valueOf(int v0, int v1, int v2, int v3) { + return new ValueInt4(v0, v1, v2, v3); + } + } + + public abstract static class InvocationLogic { + public abstract ValueInt1 compute(ValueInt1 v1, ValueInt1 v2, ValueInt1 v3, ValueInt1 v4); + public abstract ValueInt2 compute(ValueInt2 v1, ValueInt2 v2); + public abstract ValueInt4 compute(ValueInt4 v1); + } + + public static class InvokeImpl1 extends InvocationLogic { + @Override + public ValueInt1 compute(ValueInt1 v1, ValueInt1 v2, ValueInt1 v3, ValueInt1 v4) { + return v1; + } + @Override + public ValueInt2 compute(ValueInt2 v1, ValueInt2 v2) { + return v1; + } + @Override + public ValueInt4 compute(ValueInt4 v1) { + return v1; + } + } + + public static class InvokeImpl2 extends InvocationLogic { + @Override + public ValueInt1 compute(ValueInt1 v1, ValueInt1 v2, ValueInt1 v3, ValueInt1 v4) { + return v1; + } + @Override + public ValueInt2 compute(ValueInt2 v1, ValueInt2 v2) { + return v1; + } + @Override + public ValueInt4 compute(ValueInt4 v1) { + return v1; + } + } + + public static class InvokeImpl3 extends InvocationLogic { + @Override + public ValueInt1 compute(ValueInt1 v1, ValueInt1 v2, ValueInt1 v3, ValueInt1 v4) { + return v1; + } + @Override + public ValueInt2 compute(ValueInt2 v1, ValueInt2 v2) { + return v1; + } + @Override + public ValueInt4 compute(ValueInt4 v1) { + return v1; + } + } + + @State(Scope.Thread) + public static class StateTargets { + InvocationLogic[] arr; + + @Setup + public void setup() { + arr = new InvocationLogic[SIZE]; + Arrays.setAll(arr, i -> getImpl(i, 3)); + } + + private InvocationLogic getImpl(int i, int targets) { + return switch (i % targets) { + case 0 -> new InvokeImpl1(); + case 1 -> new InvokeImpl2(); + default -> new InvokeImpl3(); + }; + } + } + + ValueInt1 a0 = ValueInt1.valueOf(42); + ValueInt1 a1 = ValueInt1.valueOf(43); + ValueInt1 a2 = ValueInt1.valueOf(44); + ValueInt1 a3 = ValueInt1.valueOf(45); + + ValueInt2 b0 = ValueInt2.valueOf(42, 43); + ValueInt2 b1 = ValueInt2.valueOf(44, 45); + + ValueInt4 c0 = ValueInt4.valueOf(42, 43, 44, 45); + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void value_args4(Blackhole bh, StateTargets st) { + ValueInt1 v0 = a0; + ValueInt1 v1 = a1; + ValueInt1 v2 = a2; + ValueInt1 v3 = a3; + InvocationLogic[] arr = st.arr; + for (InvocationLogic t : arr) { + bh.consume(t.compute(v0, v1, v2, v3)); + } + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void value_args2(Blackhole bh, StateTargets st) { + ValueInt2 v0 = b0; + ValueInt2 v1 = b1; + InvocationLogic[] arr = st.arr; + for (InvocationLogic t : arr) { + bh.consume(t.compute(v0, v1)); + } + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void value_args1(Blackhole bh, StateTargets st) { + ValueInt4 v0 = c0; + InvocationLogic[] arr = st.arr; + for (InvocationLogic t : arr) { + bh.consume(t.compute(v0)); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/callconv/Value8.java b/test/micro/org/openjdk/bench/valhalla/callconv/Value8.java new file mode 100644 index 00000000000..ce1735c41d3 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/callconv/Value8.java @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.callconv; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Value8 { + + public static final int SIZE = 96; // must be divisible by 2 and 3 and around 100 + + public static value class ValueInt1 { + + public final int v0; + + public ValueInt1(int v0) { + this.v0 = v0; + } + + public int value() { + return v0; + } + + public static ValueInt1 valueOf(int value) { + return new ValueInt1(value); + } + } + + public static value class ValueInt2 { + + public final int v0, v1; + + public ValueInt2(int v0, int v1) { + this.v0 = v0; + this.v1 = v1; + } + + public int value0() { + return v0; + } + + public static ValueInt2 valueOf(int v0, int v1) { + return new ValueInt2(v0, v1); + } + } + + public static value class ValueInt4 { + + public final int v0, v1, v2, v3; + + public ValueInt4(int v0, int v1, int v2, int v3) { + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + } + + public int value0() { + return v0; + } + + public static ValueInt4 valueOf(int v0, int v1, int v2, int v3) { + return new ValueInt4(v0, v1, v2, v3); + } + } + + public static value class ValueInt8 { + + public final int v0, v1, v2, v3, v4, v5, v6, v7; + + public ValueInt8(int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7) { + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + } + + public int value0() { + return v0; + } + + public static ValueInt8 valueOf(int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7) { + return new ValueInt8(v0, v1, v2, v3, v4, v5, v6, v7); + } + } + + public abstract static class InvocationLogic { + public abstract ValueInt1 compute(ValueInt1 v1, ValueInt1 v2, ValueInt1 v3, ValueInt1 v4, ValueInt1 v5, ValueInt1 v6, ValueInt1 v7, ValueInt1 v8); + public abstract ValueInt2 compute(ValueInt2 v1, ValueInt2 v2, ValueInt2 v3, ValueInt2 v4); + public abstract ValueInt4 compute(ValueInt4 v1, ValueInt4 v2); + public abstract ValueInt8 compute(ValueInt8 v1); + } + + public static class InvokeImpl1 extends InvocationLogic { + @Override + public ValueInt1 compute(ValueInt1 v1, ValueInt1 v2, ValueInt1 v3, ValueInt1 v4, ValueInt1 v5, ValueInt1 v6, ValueInt1 v7, ValueInt1 v8) { + return v1; + } + @Override + public ValueInt2 compute(ValueInt2 v1, ValueInt2 v2, ValueInt2 v3, ValueInt2 v4) { + return v1; + } + @Override + public ValueInt4 compute(ValueInt4 v1, ValueInt4 v2) { + return v1; + } + @Override + public ValueInt8 compute(ValueInt8 v1) { + return v1; + } + } + + public static class InvokeImpl2 extends InvocationLogic { + @Override + public ValueInt1 compute(ValueInt1 v1, ValueInt1 v2, ValueInt1 v3, ValueInt1 v4, ValueInt1 v5, ValueInt1 v6, ValueInt1 v7, ValueInt1 v8) { + return v1; + } + @Override + public ValueInt2 compute(ValueInt2 v1, ValueInt2 v2, ValueInt2 v3, ValueInt2 v4) { + return v1; + } + @Override + public ValueInt4 compute(ValueInt4 v1, ValueInt4 v2) { + return v1; + } + @Override + public ValueInt8 compute(ValueInt8 v1) { + return v1; + } + } + + public static class InvokeImpl3 extends InvocationLogic { + @Override + public ValueInt1 compute(ValueInt1 v1, ValueInt1 v2, ValueInt1 v3, ValueInt1 v4, ValueInt1 v5, ValueInt1 v6, ValueInt1 v7, ValueInt1 v8) { + return v1; + } + @Override + public ValueInt2 compute(ValueInt2 v1, ValueInt2 v2, ValueInt2 v3, ValueInt2 v4) { + return v1; + } + @Override + public ValueInt4 compute(ValueInt4 v1, ValueInt4 v2) { + return v1; + } + @Override + public ValueInt8 compute(ValueInt8 v1) { + return v1; + } + } + + @State(Scope.Thread) + public static class StateTargets { + InvocationLogic[] arr; + + @Setup + public void setup() { + arr = new InvocationLogic[SIZE]; + Arrays.setAll(arr, i -> getImpl(i, 3)); + } + + private InvocationLogic getImpl(int i, int targets) { + return switch (i % targets) { + case 0 -> new InvokeImpl1(); + case 1 -> new InvokeImpl2(); + default -> new InvokeImpl3(); + }; + } + } + + ValueInt1 a0 = ValueInt1.valueOf(42); + ValueInt1 a1 = ValueInt1.valueOf(43); + ValueInt1 a2 = ValueInt1.valueOf(44); + ValueInt1 a3 = ValueInt1.valueOf(45); + ValueInt1 a4 = ValueInt1.valueOf(46); + ValueInt1 a5 = ValueInt1.valueOf(47); + ValueInt1 a6 = ValueInt1.valueOf(48); + ValueInt1 a7 = ValueInt1.valueOf(49); + + ValueInt2 b0 = ValueInt2.valueOf(42, 43); + ValueInt2 b1 = ValueInt2.valueOf(44, 45); + ValueInt2 b2 = ValueInt2.valueOf(46, 47); + ValueInt2 b3 = ValueInt2.valueOf(48, 49); + + ValueInt4 c0 = ValueInt4.valueOf(42, 43, 44, 45); + ValueInt4 c1 = ValueInt4.valueOf(46, 47, 48, 49); + + ValueInt8 d0 = ValueInt8.valueOf(42, 43, 44, 45, 46, 47, 48, 49); + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void value_args8(Blackhole bh, StateTargets st) { + ValueInt1 v0 = a0; + ValueInt1 v1 = a1; + ValueInt1 v2 = a2; + ValueInt1 v3 = a3; + ValueInt1 v4 = a4; + ValueInt1 v5 = a5; + ValueInt1 v6 = a6; + ValueInt1 v7 = a7; + InvocationLogic[] arr = st.arr; + for (InvocationLogic t : arr) { + bh.consume(t.compute(v0, v1, v2, v3, v4, v5, v6, v7)); + } + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void value_args4(Blackhole bh, StateTargets st) { + ValueInt2 v0 = b0; + ValueInt2 v1 = b1; + ValueInt2 v2 = b2; + ValueInt2 v3 = b3; + InvocationLogic[] arr = st.arr; + for (InvocationLogic t : arr) { + bh.consume(t.compute(v0, v1, v2, v3)); + } + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void value_args2(Blackhole bh, StateTargets st) { + ValueInt4 v0 = c0; + ValueInt4 v1 = c1; + InvocationLogic[] arr = st.arr; + for (InvocationLogic t : arr) { + bh.consume(t.compute(v0, v1)); + } + } + + @Benchmark + @OperationsPerInvocation(SIZE) + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void value_args1(Blackhole bh, StateTargets st) { + ValueInt8 v0 = d0; + InvocationLogic[] arr = st.arr; + for (InvocationLogic t : arr) { + bh.consume(t.compute(v0)); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/copy/CopyBase.java b/test/micro/org/openjdk/bench/valhalla/field/copy/CopyBase.java new file mode 100644 index 00000000000..68d7028eef7 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/copy/CopyBase.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.copy; + + +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@Fork(3) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +public class CopyBase { + + @State(Scope.Thread) + public static abstract class SizeState { + + @Param({ + "100", // tiny size, to fit into all caches and check codegeneration quality + "1000000" // large size, to be out of all caches and memory effects + }) + public int size; + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/copy/Identity.java b/test/micro/org/openjdk/bench/valhalla/field/copy/Identity.java new file mode 100644 index 00000000000..eb2cc8fc8bb --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/copy/Identity.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.copy; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Setup; + +public class Identity extends CopyBase { + + public static class RefWrapper { + public IdentityInt f; + + public RefWrapper(IdentityInt f) { + this.f = f; + } + } + + public static class IntWrapper { + public InterfaceInt f; + + public IntWrapper(IdentityInt f) { + this.f = f; + } + } + + + public interface InterfaceInt { + public int value(); + } + + public static class IdentityInt implements InterfaceInt { + public final int value; + public IdentityInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + + public static class RefState extends SizeState { + public RefWrapper[] arr; + + @Setup + public void setup() { + arr = new RefWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new RefWrapper(new IdentityInt(i)); + } + } + } + + public static class IntState extends SizeState { + public IntWrapper[] arr; + + @Setup + public void setup() { + arr = new IntWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new IntWrapper(new IdentityInt(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy(RefWrapper[] dst, RefWrapper[] src) { + for (int i = 0; i < src.length; i++) { + dst[i].f = src[i].f; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_to_int(IntWrapper[] dst, RefWrapper[] src) { + for (int i = 0; i < src.length; i++) { + dst[i].f = src[i].f; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_to_ref(RefWrapper[] dst, IntWrapper[] src) { + for (int i = 0; i < src.length; i++) { + dst[i].f = (IdentityInt) src[i].f; + } + } + + @Benchmark + public void copy_ref(RefState st1, RefState st2) { + copy(st1.arr, st2.arr); + } + + @Benchmark + public void copy_ref_to_int(IntState st1, RefState st2) { + copy_to_int(st1.arr, st2.arr); + } + + @Benchmark + public void copy_int_to_ref(RefState st1, IntState st2) { + copy_to_ref(st1.arr, st2.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/copy/Value032.java b/test/micro/org/openjdk/bench/valhalla/field/copy/Value032.java new file mode 100644 index 00000000000..ddbc41f52b0 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/copy/Value032.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.copy; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value032 extends CopyBase { + + public static class ValWrapper { + public ValueInt f; + + public ValWrapper(ValueInt f) { + this.f = f; + } + } + + public static class IntWrapper { + public InterfaceInt f; + + public IntWrapper(ValueInt f) { + this.f = f; + } + } + + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt implements InterfaceInt { + public final int value; + public ValueInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + + public static class ValState extends SizeState { + public ValWrapper[] arr; + + @Setup + public void setup() { + arr = new ValWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValWrapper(new ValueInt(i)); + } + } + } + + public static class IntState extends SizeState { + public IntWrapper[] arr; + + @Setup + public void setup() { + arr = new IntWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new IntWrapper(new ValueInt(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy(ValWrapper[] dst, ValWrapper[] src) { + for (int i = 0; i < src.length; i++) { + dst[i].f = src[i].f; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_to_int(IntWrapper[] dst, ValWrapper[] src) { + for (int i = 0; i < src.length; i++) { + dst[i].f = src[i].f; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_to_val(ValWrapper[] dst, IntWrapper[] src) { + for (int i = 0; i < src.length; i++) { + dst[i].f = (ValueInt) src[i].f; + } + } + + @Benchmark + public void copy_val(ValState st1, ValState st2) { + copy(st1.arr, st2.arr); + } + + @Benchmark + public void copy_val_to_int(IntState st1, ValState st2) { + copy_to_int(st1.arr, st2.arr); + } + + @Benchmark + public void copy_int_to_val(ValState st1, IntState st2) { + copy_to_val(st1.arr, st2.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/copy/Value032NullFree.java b/test/micro/org/openjdk/bench/valhalla/field/copy/Value032NullFree.java new file mode 100644 index 00000000000..a0e98105c72 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/copy/Value032NullFree.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.copy; + +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value032NullFree extends CopyBase { + + public static class ValWrapper { + @Strict + @NullRestricted + public ValueInt f; + + public ValWrapper(ValueInt f) { + this.f = f; + } + } + + public static class IntWrapper { + public InterfaceInt f; + + public IntWrapper(ValueInt f) { + this.f = f; + } + } + + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt implements InterfaceInt { + public final int value; + public ValueInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + + public static class ValState extends SizeState { + public ValWrapper[] arr; + + @Setup + public void setup() { + arr = new ValWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValWrapper(new ValueInt(i)); + } + } + } + + public static class IntState extends SizeState { + public IntWrapper[] arr; + + @Setup + public void setup() { + arr = new IntWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new IntWrapper(new ValueInt(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy(ValWrapper[] dst, ValWrapper[] src) { + for (int i = 0; i < src.length; i++) { + dst[i].f = src[i].f; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_to_int(IntWrapper[] dst, ValWrapper[] src) { + for (int i = 0; i < src.length; i++) { + dst[i].f = src[i].f; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_to_val(ValWrapper[] dst, IntWrapper[] src) { + for (int i = 0; i < src.length; i++) { + dst[i].f = (ValueInt) src[i].f; + } + } + + @Benchmark + public void copy_val(ValState st1, ValState st2) { + copy(st1.arr, st2.arr); + } + + @Benchmark + public void copy_val_to_int(IntState st1, ValState st2) { + copy_to_int(st1.arr, st2.arr); + } + + @Benchmark + public void copy_int_to_val(ValState st1, IntState st2) { + copy_to_val(st1.arr, st2.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/copy/Value032NullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/field/copy/Value032NullFreeNonAtomic.java new file mode 100644 index 00000000000..811742b8f53 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/copy/Value032NullFreeNonAtomic.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.copy; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value032NullFreeNonAtomic extends CopyBase { + + public static class ValWrapper { + @Strict + @NullRestricted + public ValueInt f; + + public ValWrapper(ValueInt f) { + this.f = f; + } + } + + public static class IntWrapper { + public InterfaceInt f; + + public IntWrapper(ValueInt f) { + this.f = f; + } + } + + + public interface InterfaceInt { + public int value(); + } + + @LooselyConsistentValue + public static value class ValueInt implements InterfaceInt { + public final int value; + public ValueInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + + public static class ValState extends SizeState { + public ValWrapper[] arr; + + @Setup + public void setup() { + arr = new ValWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValWrapper(new ValueInt(i)); + } + } + } + + public static class IntState extends SizeState { + public IntWrapper[] arr; + + @Setup + public void setup() { + arr = new IntWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new IntWrapper(new ValueInt(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy(ValWrapper[] dst, ValWrapper[] src) { + for (int i = 0; i < src.length; i++) { + dst[i].f = src[i].f; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_to_int(IntWrapper[] dst, ValWrapper[] src) { + for (int i = 0; i < src.length; i++) { + dst[i].f = src[i].f; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_to_val(ValWrapper[] dst, IntWrapper[] src) { + for (int i = 0; i < src.length; i++) { + dst[i].f = (ValueInt) src[i].f; + } + } + + @Benchmark + public void copy_val(ValState st1, ValState st2) { + copy(st1.arr, st2.arr); + } + + @Benchmark + public void copy_val_to_int(IntState st1, ValState st2) { + copy_to_int(st1.arr, st2.arr); + } + + @Benchmark + public void copy_int_to_val(ValState st1, IntState st2) { + copy_to_val(st1.arr, st2.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/copy/Value128.java b/test/micro/org/openjdk/bench/valhalla/field/copy/Value128.java new file mode 100644 index 00000000000..4b51da1edd5 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/copy/Value128.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.copy; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value128 extends CopyBase { + + public static class ValWrapper { + public ValueInt4 f; + + public ValWrapper(ValueInt4 f) { + this.f = f; + } + } + + public static class IntWrapper { + public InterfaceInt f; + + public IntWrapper(ValueInt4 f) { + this.f = f; + } + } + + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValWrapper[] arr; + + @Setup + public void setup() { + arr = new ValWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValWrapper(new ValueInt4(i)); + } + } + } + + public static class IntState extends SizeState { + public IntWrapper[] arr; + + @Setup + public void setup() { + arr = new IntWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new IntWrapper(new ValueInt4(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy(ValWrapper[] dst, ValWrapper[] src) { + for (int i = 0; i < src.length; i++) { + dst[i].f = src[i].f; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_to_int(IntWrapper[] dst, ValWrapper[] src) { + for (int i = 0; i < src.length; i++) { + dst[i].f = src[i].f; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_to_val(ValWrapper[] dst, IntWrapper[] src) { + for (int i = 0; i < src.length; i++) { + dst[i].f = (ValueInt4) src[i].f; + } + } + + @Benchmark + public void copy_val(ValState st1, ValState st2) { + copy(st1.arr, st2.arr); + } + + @Benchmark + public void copy_val_to_int(IntState st1, ValState st2) { + copy_to_int(st1.arr, st2.arr); + } + + @Benchmark + public void copy_int_to_val(ValState st1, IntState st2) { + copy_to_val(st1.arr, st2.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/copy/Value128NullFree.java b/test/micro/org/openjdk/bench/valhalla/field/copy/Value128NullFree.java new file mode 100644 index 00000000000..fc72f034323 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/copy/Value128NullFree.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.copy; + +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value128NullFree extends CopyBase { + + public static class ValWrapper { + @Strict + @NullRestricted + public ValueInt4 f; + + public ValWrapper(ValueInt4 f) { + this.f = f; + } + } + + public static class IntWrapper { + public InterfaceInt f; + + public IntWrapper(ValueInt4 f) { + this.f = f; + } + } + + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValWrapper[] arr; + + @Setup + public void setup() { + arr = new ValWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValWrapper(new ValueInt4(i)); + } + } + } + + public static class IntState extends SizeState { + public IntWrapper[] arr; + + @Setup + public void setup() { + arr = new IntWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new IntWrapper(new ValueInt4(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy(ValWrapper[] dst, ValWrapper[] src) { + for (int i = 0; i < src.length; i++) { + dst[i].f = src[i].f; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_to_int(IntWrapper[] dst, ValWrapper[] src) { + for (int i = 0; i < src.length; i++) { + dst[i].f = src[i].f; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_to_val(ValWrapper[] dst, IntWrapper[] src) { + for (int i = 0; i < src.length; i++) { + dst[i].f = (ValueInt4) src[i].f; + } + } + + @Benchmark + public void copy_val(ValState st1, ValState st2) { + copy(st1.arr, st2.arr); + } + + @Benchmark + public void copy_val_to_int(IntState st1, ValState st2) { + copy_to_int(st1.arr, st2.arr); + } + + @Benchmark + public void copy_int_to_val(ValState st1, IntState st2) { + copy_to_val(st1.arr, st2.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/copy/Value128NullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/field/copy/Value128NullFreeNonAtomic.java new file mode 100644 index 00000000000..0cc2ddfef73 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/copy/Value128NullFreeNonAtomic.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.copy; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value128NullFreeNonAtomic extends CopyBase { + + public static class ValWrapper { + @Strict + @NullRestricted + public ValueInt4 f; + + public ValWrapper(ValueInt4 f) { + this.f = f; + } + } + + public static class IntWrapper { + public InterfaceInt f; + + public IntWrapper(ValueInt4 f) { + this.f = f; + } + } + + + public interface InterfaceInt { + public int value(); + } + + @LooselyConsistentValue + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class ValState extends SizeState { + public ValWrapper[] arr; + + @Setup + public void setup() { + arr = new ValWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValWrapper(new ValueInt4(i)); + } + } + } + + public static class IntState extends SizeState { + public IntWrapper[] arr; + + @Setup + public void setup() { + arr = new IntWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new IntWrapper(new ValueInt4(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy(ValWrapper[] dst, ValWrapper[] src) { + for (int i = 0; i < src.length; i++) { + dst[i].f = src[i].f; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_to_int(IntWrapper[] dst, ValWrapper[] src) { + for (int i = 0; i < src.length; i++) { + dst[i].f = src[i].f; + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void copy_to_val(ValWrapper[] dst, IntWrapper[] src) { + for (int i = 0; i < src.length; i++) { + dst[i].f = (ValueInt4) src[i].f; + } + } + + @Benchmark + public void copy_val(ValState st1, ValState st2) { + copy(st1.arr, st2.arr); + } + + @Benchmark + public void copy_val_to_int(IntState st1, ValState st2) { + copy_to_int(st1.arr, st2.arr); + } + + @Benchmark + public void copy_int_to_val(ValState st1, IntState st2) { + copy_to_val(st1.arr, st2.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/read/Identity.java b/test/micro/org/openjdk/bench/valhalla/field/read/Identity.java new file mode 100644 index 00000000000..f84b1e81348 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/read/Identity.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.read; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +public class Identity extends ReadBase { + + public static class RefWrapper { + public IdentityInt f; + + public RefWrapper(IdentityInt f) { + this.f = f; + } + } + + public interface InterfaceInt { + public int value(); + } + + public static class IdentityInt implements InterfaceInt { + public final int value; + public IdentityInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + + public static class RefState extends SizeState { + public RefWrapper[] arr; + + @Setup + public void setup() { + arr = new RefWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new RefWrapper(new IdentityInt(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_ref(RefWrapper[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i].f); + } + } + + + @Benchmark + public void read(RefState st1, Blackhole bh) { + read_ref(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/read/ReadBase.java b/test/micro/org/openjdk/bench/valhalla/field/read/ReadBase.java new file mode 100644 index 00000000000..451571c295b --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/read/ReadBase.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.read; + + +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@Fork(3) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +public class ReadBase { + + @State(Scope.Thread) + public static abstract class SizeState { + + @Param({ + "100", // tiny size, to fit into all caches and check codegeneration quality + "1000000" // large size, to be out of all caches and memory effects + }) + public int size; + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/read/Value032.java b/test/micro/org/openjdk/bench/valhalla/field/read/Value032.java new file mode 100644 index 00000000000..f252de20932 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/read/Value032.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.read; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value032 extends ReadBase { + + public static class ValWrapper { + public ValueInt f; + + public ValWrapper(ValueInt f) { + this.f = f; + } + } + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt implements InterfaceInt { + public final int value; + public ValueInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + + public static class ValState extends SizeState { + public ValWrapper[] arr; + + @Setup + public void setup() { + arr = new ValWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValWrapper(new ValueInt(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_val(ValWrapper[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i].f); + } + } + + + @Benchmark + public void read(ValState st1, Blackhole bh) { + read_val(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/read/Value032NullFree.java b/test/micro/org/openjdk/bench/valhalla/field/read/Value032NullFree.java new file mode 100644 index 00000000000..21d4a8b751d --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/read/Value032NullFree.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.read; + +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value032NullFree extends ReadBase { + + public static class ValWrapper { + @Strict + @NullRestricted + public ValueInt f; + + public ValWrapper(ValueInt f) { + this.f = f; + } + } + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt implements InterfaceInt { + public final int value; + + public ValueInt(int value) { + this.value = value; + } + + public int value() { + return value; + } + } + + + public static class ValState extends SizeState { + public ValWrapper[] arr; + + @Setup + public void setup() { + arr = new ValWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValWrapper(new ValueInt(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_val(ValWrapper[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i].f); + } + } + + + @Benchmark + public void read(ValState st1, Blackhole bh) { + read_val(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/read/Value032NullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/field/read/Value032NullFreeNonAtomic.java new file mode 100644 index 00000000000..0f374892f41 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/read/Value032NullFreeNonAtomic.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.read; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value032NullFreeNonAtomic extends ReadBase { + + public static class ValWrapper { + @Strict + @NullRestricted + public ValueInt f; + + public ValWrapper(ValueInt f) { + this.f = f; + } + } + + public interface InterfaceInt { + public int value(); + } + + @LooselyConsistentValue + public static value class ValueInt implements InterfaceInt { + public final int value; + + public ValueInt(int value) { + this.value = value; + } + + public int value() { + return value; + } + } + + + public static class ValState extends SizeState { + public ValWrapper[] arr; + + @Setup + public void setup() { + arr = new ValWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValWrapper(new ValueInt(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_val(ValWrapper[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i].f); + } + } + + + @Benchmark + public void read(ValState st1, Blackhole bh) { + read_val(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/read/Value128.java b/test/micro/org/openjdk/bench/valhalla/field/read/Value128.java new file mode 100644 index 00000000000..b3019b64f5a --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/read/Value128.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.read; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value128 extends ReadBase { + + public static class ValWrapper { + public ValueInt4 f; + + public ValWrapper(ValueInt4 f) { + this.f = f; + } + } + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + + public static class ValState extends SizeState { + public ValWrapper[] arr; + + @Setup + public void setup() { + arr = new ValWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValWrapper(new ValueInt4(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_val(ValWrapper[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i].f); + } + } + + + @Benchmark + public void read(ValState st1, Blackhole bh) { + read_val(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/read/Value128NullFree.java b/test/micro/org/openjdk/bench/valhalla/field/read/Value128NullFree.java new file mode 100644 index 00000000000..ef07eae3436 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/read/Value128NullFree.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.read; + +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value128NullFree extends ReadBase { + + public static class ValWrapper { + @Strict + @NullRestricted + public ValueInt4 f; + + public ValWrapper(ValueInt4 f) { + this.f = f; + } + } + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + + public static class ValState extends SizeState { + public ValWrapper[] arr; + + @Setup + public void setup() { + arr = new ValWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValWrapper(new ValueInt4(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_val(ValWrapper[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i].f); + } + } + + + @Benchmark + public void read(ValState st1, Blackhole bh) { + read_val(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/read/Value128NullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/field/read/Value128NullFreeNonAtomic.java new file mode 100644 index 00000000000..987ba1f7f6e --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/read/Value128NullFreeNonAtomic.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.read; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.infra.Blackhole; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value128NullFreeNonAtomic extends ReadBase { + + public static class ValWrapper { + @Strict + @NullRestricted + public ValueInt4 f; + + public ValWrapper(ValueInt4 f) { + this.f = f; + } + } + + public interface InterfaceInt { + public int value(); + } + + @LooselyConsistentValue + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + + public static class ValState extends SizeState { + public ValWrapper[] arr; + + @Setup + public void setup() { + arr = new ValWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValWrapper(new ValueInt4(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void read_val(ValWrapper[] src, Blackhole bh) { + for (int i = 0; i < src.length; i++) { + bh.consume(src[i].f); + } + } + + + @Benchmark + public void read(ValState st1, Blackhole bh) { + read_val(st1.arr, bh); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/set/Identity.java b/test/micro/org/openjdk/bench/valhalla/field/set/Identity.java new file mode 100644 index 00000000000..9c1116e441d --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/set/Identity.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.set; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Setup; + +public class Identity extends SetBase { + + public static class RefWrapper { + public IdentityInt f; + + public RefWrapper(IdentityInt f) { + this.f = f; + } + } + + public interface InterfaceInt { + public int value(); + } + + public static class IdentityInt implements InterfaceInt { + public final int value; + public IdentityInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + + public static class RefState extends SizeState { + public RefWrapper[] arr; + + @Setup + public void setup() { + arr = new RefWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new RefWrapper(new IdentityInt(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public IdentityInt get_ref(int i) { + return new IdentityInt(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new(RefWrapper[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i].f = new IdentityInt(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call(RefWrapper[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i].f = get_ref(i); + } + } + + @Benchmark + public void set_new_ref(RefState st1) { + set_new(st1.arr); + } + + @Benchmark + public void set_call_ref(RefState st1) { + set_call(st1.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/set/SetBase.java b/test/micro/org/openjdk/bench/valhalla/field/set/SetBase.java new file mode 100644 index 00000000000..d6ac8ca67d0 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/set/SetBase.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.set; + + +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@Fork(3) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +public class SetBase { + + @State(Scope.Thread) + public static abstract class SizeState { + + @Param({ + "100", // tiny size, to fit into all caches and check codegeneration quality + "1000000" // large size, to be out of all caches and memory effects + }) + public int size; + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/set/Value032.java b/test/micro/org/openjdk/bench/valhalla/field/set/Value032.java new file mode 100644 index 00000000000..643d278f196 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/set/Value032.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.set; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value032 extends SetBase { + + public static class ValWrapper { + public ValueInt f; + + public ValWrapper(ValueInt f) { + this.f = f; + } + } + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt implements InterfaceInt { + public final int value; + public ValueInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + + public static class RefState extends SizeState { + public ValWrapper[] arr; + + @Setup + public void setup() { + arr = new ValWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValWrapper(new ValueInt(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueInt get_val(int i) { + return new ValueInt(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new(ValWrapper[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i].f = new ValueInt(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call(ValWrapper[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i].f = get_val(i); + } + } + + @Benchmark + public void set_new_val(RefState st1) { + set_new(st1.arr); + } + + @Benchmark + public void set_call_val(RefState st1) { + set_call(st1.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/set/Value032NullFree.java b/test/micro/org/openjdk/bench/valhalla/field/set/Value032NullFree.java new file mode 100644 index 00000000000..8b19567c934 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/set/Value032NullFree.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.set; + +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value032NullFree extends SetBase { + + public static class ValWrapper { + @Strict + @NullRestricted + public ValueInt f; + + public ValWrapper(ValueInt f) { + this.f = f; + } + } + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt implements InterfaceInt { + public final int value; + public ValueInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + + public static class RefState extends SizeState { + public ValWrapper[] arr; + + @Setup + public void setup() { + arr = new ValWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValWrapper(new ValueInt(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueInt get_val(int i) { + return new ValueInt(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new(ValWrapper[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i].f = new ValueInt(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call(ValWrapper[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i].f = get_val(i); + } + } + + @Benchmark + public void set_new_val(RefState st1) { + set_new(st1.arr); + } + + @Benchmark + public void set_call_val(RefState st1) { + set_call(st1.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/set/Value032NullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/field/set/Value032NullFreeNonAtomic.java new file mode 100644 index 00000000000..7897edbff29 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/set/Value032NullFreeNonAtomic.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.set; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value032NullFreeNonAtomic extends SetBase { + + public static class ValWrapper { + @Strict + @NullRestricted + public ValueInt f; + + public ValWrapper(ValueInt f) { + this.f = f; + } + } + + public interface InterfaceInt { + public int value(); + } + + @LooselyConsistentValue + public static value class ValueInt implements InterfaceInt { + public final int value; + public ValueInt(int value) { + this.value = value; + } + public int value() { + return value; + } + } + + + public static class RefState extends SizeState { + public ValWrapper[] arr; + + @Setup + public void setup() { + arr = new ValWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValWrapper(new ValueInt(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueInt get_val(int i) { + return new ValueInt(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new(ValWrapper[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i].f = new ValueInt(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call(ValWrapper[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i].f = get_val(i); + } + } + + @Benchmark + public void set_new_val(RefState st1) { + set_new(st1.arr); + } + + @Benchmark + public void set_call_val(RefState st1) { + set_call(st1.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/set/Value128.java b/test/micro/org/openjdk/bench/valhalla/field/set/Value128.java new file mode 100644 index 00000000000..a3b69b95ed6 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/set/Value128.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.set; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value128 extends SetBase { + + public static class ValWrapper { + public ValueInt4 f; + + public ValWrapper(ValueInt4 f) { + this.f = f; + } + } + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class RefState extends SizeState { + public ValWrapper[] arr; + + @Setup + public void setup() { + arr = new ValWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValWrapper(new ValueInt4(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueInt4 get_val(int i) { + return new ValueInt4(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new(ValWrapper[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i].f = new ValueInt4(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call(ValWrapper[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i].f = get_val(i); + } + } + + @Benchmark + public void set_new_val(RefState st1) { + set_new(st1.arr); + } + + @Benchmark + public void set_call_val(RefState st1) { + set_call(st1.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/set/Value128NullFree.java b/test/micro/org/openjdk/bench/valhalla/field/set/Value128NullFree.java new file mode 100644 index 00000000000..9a9ea763663 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/set/Value128NullFree.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.set; + +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value128NullFree extends SetBase { + + public static class ValWrapper { + @Strict + @NullRestricted + public ValueInt4 f; + + public ValWrapper(ValueInt4 f) { + this.f = f; + } + } + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class RefState extends SizeState { + public ValWrapper[] arr; + + @Setup + public void setup() { + arr = new ValWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValWrapper(new ValueInt4(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueInt4 get_val(int i) { + return new ValueInt4(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new(ValWrapper[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i].f = new ValueInt4(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call(ValWrapper[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i].f = get_val(i); + } + } + + @Benchmark + public void set_new_val(RefState st1) { + set_new(st1.arr); + } + + @Benchmark + public void set_call_val(RefState st1) { + set_call(st1.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/field/set/Value128NullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/field/set/Value128NullFreeNonAtomic.java new file mode 100644 index 00000000000..0ed783f5e02 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/field/set/Value128NullFreeNonAtomic.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.field.set; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value128NullFreeNonAtomic extends SetBase { + + public static class ValWrapper { + @Strict + @NullRestricted + public ValueInt4 f; + + public ValWrapper(ValueInt4 f) { + this.f = f; + } + } + + public interface InterfaceInt { + public int value(); + } + + @LooselyConsistentValue + public static value class ValueInt4 implements InterfaceInt { + + public final int prevalue0; + public final int prevalue1; + public final int prevalue2; + + public final int value; + + public ValueInt4(int value) { + this.prevalue0 = value; + this.prevalue1 = value; + this.prevalue2 = value; + this.value = value; + } + + public int value() { + return value; + } + + } + + public static class RefState extends SizeState { + public ValWrapper[] arr; + + @Setup + public void setup() { + arr = new ValWrapper[size]; + for (int i = 0; i < size; i++) { + arr[i] = new ValWrapper(new ValueInt4(i)); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueInt4 get_val(int i) { + return new ValueInt4(i); + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_new(ValWrapper[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i].f = new ValueInt4(i); + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public void set_call(ValWrapper[] dst) { + for (int i = 0; i < dst.length; i++) { + dst[i].f = get_val(i); + } + } + + @Benchmark + public void set_new_val(RefState st1) { + set_new(st1.arr); + } + + @Benchmark + public void set_call_val(RefState st1) { + set_call(st1.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/hash/Value1.java b/test/micro/org/openjdk/bench/valhalla/hash/Value1.java new file mode 100644 index 00000000000..06f321ac13c --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/hash/Value1.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.hash; + + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Value1 { + + public static value class ValueInt1Hash { + + public final int v0; + + public ValueInt1Hash(int v0) { + this.v0 = v0; + } + + public int value() { + return v0; + } + + @Override + public int hashCode() { + return v0; + } + + } + + public static value class ValueInt1 { + + public final int v0; + + public ValueInt1(int v0) { + this.v0 = v0; + } + + public int value() { + return v0; + } + + } + + + @Benchmark + public int explicit() { + return new ValueInt1Hash(42).hashCode(); + } + + @Benchmark + public int implicit() { + return new ValueInt1(42).hashCode(); + } + + @Benchmark + public int direct() { + return System.identityHashCode(new ValueInt1(42)); + } +} diff --git a/test/micro/org/openjdk/bench/valhalla/hash/Value8.java b/test/micro/org/openjdk/bench/valhalla/hash/Value8.java new file mode 100644 index 00000000000..204aa4afdb9 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/hash/Value8.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.hash; + + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Value8 { + + public static value class ValueInt8 { + + public final int v0, v1, v2, v3, v4, v5, v6, v7; + + public ValueInt8(int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7) { + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + } + + public int value0() { + return v0; + } + + } + + public static value class ValueInt8Hash { + + public final int v0, v1, v2, v3, v4, v5, v6, v7; + + public ValueInt8Hash(int v0, int v1, int v2, int v3, int v4, int v5, int v6, int v7) { + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + this.v5 = v5; + this.v6 = v6; + this.v7 = v7; + } + + public int value0() { + return v0; + } + + @Override + public int hashCode() { + return (((((((v0 * 31) + v1) * 31 + v2) * 31 + v3) * 31 + v4) * 31 + v5) * 31 + v6) * 31 + v7; + } + } + + + @Benchmark + public int explicit() { + return new ValueInt8Hash(42, 43, 44, 45, 46, 47, 48, 49).hashCode(); + } + + @Benchmark + public int implicit() { + return new ValueInt8(42, 43, 44, 45, 46, 47, 48, 49).hashCode(); + } + + @Benchmark + public int direct() { + return System.identityHashCode(new ValueInt8(42, 43, 44, 45, 46, 47, 48, 49)); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/intrinsics/IsFlatArray.java b/test/micro/org/openjdk/bench/valhalla/intrinsics/IsFlatArray.java new file mode 100644 index 00000000000..a8e79750cba --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/intrinsics/IsFlatArray.java @@ -0,0 +1,95 @@ +/* + * 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. + */ + +package org.openjdk.bench.valhalla.intrinsics; + +import org.openjdk.jmh.annotations.*; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.util.concurrent.TimeUnit; +import jdk.internal.value.ValueClass; + +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@Fork(value = 1, + jvmArgsAppend = {"--add-opens", "java.base/jdk.internal.misc=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.value=ALL-UNNAMED", + "--enable-preview"}) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 1) +public class IsFlatArray { + + private static final VarHandle objectArrayVarHandle = + MethodHandles.arrayElementVarHandle(Object[].class); + + @State(Scope.Benchmark) + public static class ClassState { + public Object[] flatArray = new Point[10]; + public Object[] nonFlatArray = new String[10]; + + public Object[] objectArray = new Object[10]; + public Object objectElement = new Object(); + public int arrayIndex = 0; + } + + @Benchmark + public boolean testKnownFlatClass() { + return ValueClass.isFlatArray(new Point[8]); + } + + @Benchmark + public boolean testKnownNonFlatClass() { + return ValueClass.isFlatArray(new String[8]); + } + + @Benchmark + public boolean testUnknownFlatClass(ClassState state) { + return ValueClass.isFlatArray(state.flatArray); + } + + @Benchmark + public boolean testUnknownNonFlatClass(ClassState state) { + return ValueClass.isFlatArray(state.nonFlatArray); + } + + @Benchmark + public void setArrayElement(ClassState state) { + objectArrayVarHandle.set(state.objectArray, state.arrayIndex, state.objectElement); + } + + @Benchmark + public VarHandle makeArrayVarHandle() { + return MethodHandles.arrayElementVarHandle(Object[].class); + } + +} + +value class Point { + int x; + int y; + public Point(int x, int y) { + this.x = x; + this.y = y; + } +} diff --git a/test/micro/org/openjdk/bench/valhalla/invoke/array/Identity.java b/test/micro/org/openjdk/bench/valhalla/invoke/array/Identity.java new file mode 100644 index 00000000000..34565b22e98 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/invoke/array/Identity.java @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.invoke.array; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@Fork(3) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Identity { + + /* + virtual method invocations: + target0 - statically known target method. + target1 - the single invoked method (should be inlined) + target2 - two invoked method (should be inlined, cache-inline) + target3 - thee invoked method (shouldn't be inlined) + + */ + + + public static final int SIZE = 128; + + public interface InterfaceInt { + public int value(); + } + + public static class IdentityInt0 implements InterfaceInt { + public final int value; + public IdentityInt0(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + public static class IdentityInt1 implements InterfaceInt { + public final int value; + public IdentityInt1(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + public static class IdentityInt2 implements InterfaceInt { + public final int value; + public IdentityInt2(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + @State(Scope.Thread) + public static class Int0State { + public InterfaceInt[] arr; + @Setup + public void setup() { + arr = new InterfaceInt[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IdentityInt0(i); + } + } + } + + @State(Scope.Thread) + public static class Int1State { + public InterfaceInt[] arr; + @Setup + public void setup() { + arr = new InterfaceInt[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IdentityInt1(i); + } + } + } + + @State(Scope.Thread) + public static class Int2State { + public InterfaceInt[] arr; + @Setup + public void setup() { + arr = new InterfaceInt[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IdentityInt2(i); + } + } + } + + @State(Scope.Thread) + public static class Ref0State { + public IdentityInt0[] arr; + @Setup + public void setup() { + arr = new IdentityInt0[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IdentityInt0(i); + } + } + } + + @State(Scope.Thread) + public static class Ref1State { + public IdentityInt1[] arr; + @Setup + public void setup() { + arr = new IdentityInt1[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IdentityInt1(i); + } + } + } + + @State(Scope.Thread) + public static class Ref2State { + public IdentityInt2[] arr; + @Setup + public void setup() { + arr = new IdentityInt2[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IdentityInt2(i); + } + } + } + + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int reduce_int(InterfaceInt[] arr) { + int r = 0; + for (int i = 0; i < arr.length; i++) { + r += arr[i].value(); + } + return r; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int reduce_ref(IdentityInt0[] arr) { + int r = 0; + for (int i = 0; i < arr.length; i++) { + r += arr[i].value(); + } + return r; + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target0(Ref0State st0, Ref0State st1, Ref0State st2, Ref0State st3, Ref0State st4, Ref0State st5) { + return reduce_ref(st0.arr) + + reduce_ref(st1.arr) + + reduce_ref(st2.arr) + + reduce_ref(st3.arr) + + reduce_ref(st4.arr) + + reduce_ref(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target1_r(Ref0State st0, Ref0State st1, Ref0State st2, Ref0State st3, Ref0State st4, Ref0State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target1_i(Int0State st0, Int0State st1, Int0State st2, Int0State st3, Int0State st4, Int0State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target1_ri(Ref0State st0, Int0State st1, Ref0State st2, Int0State st3, Ref0State st4, Int0State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target2_r(Ref0State st0, Ref0State st1, Ref0State st2, Ref1State st3, Ref1State st4, Ref1State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target2_i(Int0State st0, Int0State st1, Int0State st2, Int1State st3, Int1State st4, Int1State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target2_ri(Ref0State st0, Int0State st1, Ref0State st2, Int1State st3, Ref1State st4, Int1State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target3_r(Ref0State st0, Ref0State st1, Ref1State st2, Ref1State st3, Ref2State st4, Ref2State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target3_i(Int0State st0, Int0State st1, Int1State st2, Int1State st3, Int2State st4, Int2State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target3_ri(Ref0State st0, Int0State st1, Ref1State st2, Int1State st3, Ref2State st4, Int2State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + +} \ No newline at end of file diff --git a/test/micro/org/openjdk/bench/valhalla/invoke/array/Value.java b/test/micro/org/openjdk/bench/valhalla/invoke/array/Value.java new file mode 100644 index 00000000000..97d54dd5494 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/invoke/array/Value.java @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.invoke.array; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Value { + + /* + virtual method invocations: + target0 - statically known target method. + target1 - the single invoked method (should be inlined) + target2 - two invoked method (should be inlined, cache-inline) + target3 - thee invoked method (shouldn't be inlined) + + */ + + + public static final int SIZE = 128; + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt0 implements InterfaceInt { + public final int value; + public ValueInt0(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + public static value class ValueInt1 implements InterfaceInt { + public final int value; + public ValueInt1(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + public static value class ValueInt2 implements InterfaceInt { + public final int value; + public ValueInt2(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + @State(Scope.Thread) + public static class Int0State { + public InterfaceInt[] arr; + @Setup + public void setup() { + arr = new InterfaceInt[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt0(i); + } + } + } + + @State(Scope.Thread) + public static class Int1State { + public InterfaceInt[] arr; + @Setup + public void setup() { + arr = new InterfaceInt[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt1(i); + } + } + } + + @State(Scope.Thread) + public static class Int2State { + public InterfaceInt[] arr; + @Setup + public void setup() { + arr = new InterfaceInt[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt2(i); + } + } + } + + @State(Scope.Thread) + public static class Val0State { + public ValueInt0[] arr; + @Setup + public void setup() { + arr = new ValueInt0[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt0(i); + } + } + } + + @State(Scope.Thread) + public static class Val1State { + public ValueInt1[] arr; + @Setup + public void setup() { + arr = new ValueInt1[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt1(i); + } + } + } + + @State(Scope.Thread) + public static class Val22State { + public ValueInt2[] arr; + @Setup + public void setup() { + arr = new ValueInt2[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt2(i); + } + } + } + + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int reduce_int(InterfaceInt[] arr) { + int r = 0; + for (int i = 0; i < arr.length; i++) { + r += arr[i].value(); + } + return r; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int reduce_val(ValueInt0[] arr) { + int r = 0; + for (int i = 0; i < arr.length; i++) { + r += arr[i].value(); + } + return r; + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target0(Val0State st0, Val0State st1, Val0State st2, Val0State st3, Val0State st4, Val0State st5) { + return reduce_val(st0.arr) + + reduce_val(st1.arr) + + reduce_val(st2.arr) + + reduce_val(st3.arr) + + reduce_val(st4.arr) + + reduce_val(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target1_v(Val0State st0, Val0State st1, Val0State st2, Val0State st3, Val0State st4, Val0State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target1_i(Int0State st0, Int0State st1, Int0State st2, Int0State st3, Int0State st4, Int0State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target1_vi(Val0State st0, Int0State st1, Val0State st2, Int0State st3, Val0State st4, Int0State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target2_v(Val0State st0, Val0State st1, Val0State st2, Val1State st3, Val1State st4, Val1State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target2_i(Int0State st0, Int0State st1, Int0State st2, Int1State st3, Int1State st4, Int1State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target2_vi(Val0State st0, Int0State st1, Val0State st2, Int1State st3, Val1State st4, Int1State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target3_v(Val0State st0, Val0State st1, Val1State st2, Val1State st3, Val22State st4, Val22State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target3_i(Int0State st0, Int0State st1, Int1State st2, Int1State st3, Int2State st4, Int2State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target3_vi(Val0State st0, Int0State st1, Val1State st2, Int1State st3, Val22State st4, Int2State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + +} \ No newline at end of file diff --git a/test/micro/org/openjdk/bench/valhalla/invoke/array/ValueNullFree.java b/test/micro/org/openjdk/bench/valhalla/invoke/array/ValueNullFree.java new file mode 100644 index 00000000000..493af9ba755 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/invoke/array/ValueNullFree.java @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.invoke.array; + +import jdk.internal.value.ValueClass; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class ValueNullFree { + + /* + virtual method invocations: + target0 - statically known target method. + target1 - the single invoked method (should be inlined) + target2 - two invoked method (should be inlined, cache-inline) + target3 - thee invoked method (shouldn't be inlined) + + */ + + + public static final int SIZE = 128; + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt0 implements InterfaceInt { + public final int value; + public ValueInt0(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + public static value class ValueInt1 implements InterfaceInt { + public final int value; + public ValueInt1(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + public static value class ValueInt2 implements InterfaceInt { + public final int value; + public ValueInt2(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + @State(Scope.Thread) + public static class Int0State { + public InterfaceInt[] arr; + @Setup + public void setup() { + arr = new InterfaceInt[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt0(i); + } + } + } + + @State(Scope.Thread) + public static class Int1State { + public InterfaceInt[] arr; + @Setup + public void setup() { + arr = new InterfaceInt[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt1(i); + } + } + } + + @State(Scope.Thread) + public static class Int2State { + public InterfaceInt[] arr; + @Setup + public void setup() { + arr = new InterfaceInt[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt2(i); + } + } + } + + @State(Scope.Thread) + public static class Val0State { + public ValueInt0[] arr; + @Setup + public void setup() { + arr = (ValueInt0[]) ValueClass.newNullRestrictedAtomicArray(ValueInt0.class, SIZE, new ValueInt0(0)); + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt0(i); + } + } + } + + @State(Scope.Thread) + public static class Val1State { + public ValueInt1[] arr; + @Setup + public void setup() { + arr = (ValueInt1[]) ValueClass.newNullRestrictedAtomicArray(ValueInt1.class, SIZE, new ValueInt1(0)); + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt1(i); + } + } + } + + @State(Scope.Thread) + public static class Val22State { + public ValueInt2[] arr; + @Setup + public void setup() { + arr = (ValueInt2[]) ValueClass.newNullRestrictedAtomicArray(ValueInt2.class, SIZE, new ValueInt2(0)); + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt2(i); + } + } + } + + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int reduce_int(InterfaceInt[] arr) { + int r = 0; + for (int i = 0; i < arr.length; i++) { + r += arr[i].value(); + } + return r; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int reduce_val(ValueInt0[] arr) { + int r = 0; + for (int i = 0; i < arr.length; i++) { + r += arr[i].value(); + } + return r; + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target0(Val0State st0, Val0State st1, Val0State st2, Val0State st3, Val0State st4, Val0State st5) { + return reduce_val(st0.arr) + + reduce_val(st1.arr) + + reduce_val(st2.arr) + + reduce_val(st3.arr) + + reduce_val(st4.arr) + + reduce_val(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target1_v(Val0State st0, Val0State st1, Val0State st2, Val0State st3, Val0State st4, Val0State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target1_i(Int0State st0, Int0State st1, Int0State st2, Int0State st3, Int0State st4, Int0State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target1_vi(Val0State st0, Int0State st1, Val0State st2, Int0State st3, Val0State st4, Int0State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target2_v(Val0State st0, Val0State st1, Val0State st2, Val1State st3, Val1State st4, Val1State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target2_i(Int0State st0, Int0State st1, Int0State st2, Int1State st3, Int1State st4, Int1State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target2_vi(Val0State st0, Int0State st1, Val0State st2, Int1State st3, Val1State st4, Int1State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target3_v(Val0State st0, Val0State st1, Val1State st2, Val1State st3, Val22State st4, Val22State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target3_i(Int0State st0, Int0State st1, Int1State st2, Int1State st3, Int2State st4, Int2State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target3_vi(Val0State st0, Int0State st1, Val1State st2, Int1State st3, Val22State st4, Int2State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + +} \ No newline at end of file diff --git a/test/micro/org/openjdk/bench/valhalla/invoke/array/ValueNullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/invoke/array/ValueNullFreeNonAtomic.java new file mode 100644 index 00000000000..eb17228d517 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/invoke/array/ValueNullFreeNonAtomic.java @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.invoke.array; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class ValueNullFreeNonAtomic { + + /* + virtual method invocations: + target0 - statically known target method. + target1 - the single invoked method (should be inlined) + target2 - two invoked method (should be inlined, cache-inline) + target3 - thee invoked method (shouldn't be inlined) + + */ + + + public static final int SIZE = 128; + + public interface InterfaceInt { + public int value(); + } + + @LooselyConsistentValue + public static value class ValueInt0 implements InterfaceInt { + public final int value; + public ValueInt0(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + @LooselyConsistentValue + public static value class ValueInt1 implements InterfaceInt { + public final int value; + public ValueInt1(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + @LooselyConsistentValue + public static value class ValueInt2 implements InterfaceInt { + public final int value; + public ValueInt2(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + @State(Scope.Thread) + public static class Int0State { + public InterfaceInt[] arr; + @Setup + public void setup() { + arr = new InterfaceInt[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt0(i); + } + } + } + + @State(Scope.Thread) + public static class Int1State { + public InterfaceInt[] arr; + @Setup + public void setup() { + arr = new InterfaceInt[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt1(i); + } + } + } + + @State(Scope.Thread) + public static class Int2State { + public InterfaceInt[] arr; + @Setup + public void setup() { + arr = new InterfaceInt[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt2(i); + } + } + } + + @State(Scope.Thread) + public static class Val0State { + public ValueInt0[] arr; + @Setup + public void setup() { + arr = (ValueInt0[]) ValueClass.newNullRestrictedNonAtomicArray(ValueInt0.class, SIZE, new ValueInt0(0)); + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt0(i); + } + } + } + + @State(Scope.Thread) + public static class Val1State { + public ValueInt1[] arr; + @Setup + public void setup() { + arr = (ValueInt1[]) ValueClass.newNullRestrictedNonAtomicArray(ValueInt1.class, SIZE, new ValueInt1(0)); + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt1(i); + } + } + } + + @State(Scope.Thread) + public static class Val22State { + public ValueInt2[] arr; + @Setup + public void setup() { + arr = (ValueInt2[]) ValueClass.newNullRestrictedNonAtomicArray(ValueInt2.class, SIZE, new ValueInt2(0)); + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt2(i); + } + } + } + + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int reduce_int(InterfaceInt[] arr) { + int r = 0; + for (int i = 0; i < arr.length; i++) { + r += arr[i].value(); + } + return r; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int reduce_val(ValueInt0[] arr) { + int r = 0; + for (int i = 0; i < arr.length; i++) { + r += arr[i].value(); + } + return r; + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target0(Val0State st0, Val0State st1, Val0State st2, Val0State st3, Val0State st4, Val0State st5) { + return reduce_val(st0.arr) + + reduce_val(st1.arr) + + reduce_val(st2.arr) + + reduce_val(st3.arr) + + reduce_val(st4.arr) + + reduce_val(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target1_v(Val0State st0, Val0State st1, Val0State st2, Val0State st3, Val0State st4, Val0State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target1_i(Int0State st0, Int0State st1, Int0State st2, Int0State st3, Int0State st4, Int0State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target1_vi(Val0State st0, Int0State st1, Val0State st2, Int0State st3, Val0State st4, Int0State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target2_v(Val0State st0, Val0State st1, Val0State st2, Val1State st3, Val1State st4, Val1State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target2_i(Int0State st0, Int0State st1, Int0State st2, Int1State st3, Int1State st4, Int1State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target2_vi(Val0State st0, Int0State st1, Val0State st2, Int1State st3, Val1State st4, Int1State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target3_v(Val0State st0, Val0State st1, Val1State st2, Val1State st3, Val22State st4, Val22State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target3_i(Int0State st0, Int0State st1, Int1State st2, Int1State st3, Int2State st4, Int2State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target3_vi(Val0State st0, Int0State st1, Val1State st2, Int1State st3, Val22State st4, Int2State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + +} \ No newline at end of file diff --git a/test/micro/org/openjdk/bench/valhalla/invoke/field/Identity.java b/test/micro/org/openjdk/bench/valhalla/invoke/field/Identity.java new file mode 100644 index 00000000000..ff03fdd6b87 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/invoke/field/Identity.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.invoke.field; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@Fork(3) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Identity { + + /* + virtual method invocations: + target0 - statically known target method. + target1 - the single invoked method (should be inlined) + target2 - two invoked method (should be inlined, cache-inline) + target3 - thee invoked method (shouldn't be inlined) + + */ + + + public static final int SIZE = 128; + + public interface InterfaceInt { + public int value(); + } + + public static class IdentityInt0 implements InterfaceInt { + public final int value; + public IdentityInt0(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + public static class IdentityInt1 implements InterfaceInt { + public final int value; + public IdentityInt1(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + public static class IdentityInt2 implements InterfaceInt { + public final int value; + public IdentityInt2(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + public static class IntWrapper { + final InterfaceInt f; + + public IntWrapper(InterfaceInt f) { + this.f = f; + } + } + + public static class RefWrapper { + final IdentityInt0 f; + + public RefWrapper(IdentityInt0 f) { + this.f = f; + } + } + + @State(Scope.Thread) + public static class Int0State { + public IntWrapper[] arr; + @Setup + public void setup() { + arr = new IntWrapper[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IntWrapper(new IdentityInt0(i)); + } + } + } + + @State(Scope.Thread) + public static class Int1State { + public IntWrapper[] arr; + @Setup + public void setup() { + arr = new IntWrapper[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IntWrapper(new IdentityInt1(i)); + } + } + } + + @State(Scope.Thread) + public static class Int2State { + public IntWrapper[] arr; + @Setup + public void setup() { + arr = new IntWrapper[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IntWrapper(new IdentityInt2(i)); + } + } + } + + @State(Scope.Thread) + public static class Ref0State { + public RefWrapper[] arr; + @Setup + public void setup() { + arr = new RefWrapper[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new RefWrapper(new IdentityInt0(i)); + } + } + } + + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int reduce_int(IntWrapper[] arr) { + int r = 0; + for (int i = 0; i < arr.length; i++) { + r += arr[i].f.value(); + } + return r; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int reduce_ref(RefWrapper[] arr) { + int r = 0; + for (int i = 0; i < arr.length; i++) { + r += arr[i].f.value(); + } + return r; + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target0(Ref0State st0, Ref0State st1, Ref0State st2, Ref0State st3, Ref0State st4, Ref0State st5) { + return reduce_ref(st0.arr) + + reduce_ref(st1.arr) + + reduce_ref(st2.arr) + + reduce_ref(st3.arr) + + reduce_ref(st4.arr) + + reduce_ref(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target1(Int0State st0, Int0State st1, Int0State st2, Int0State st3, Int0State st4, Int0State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target2(Int0State st0, Int0State st1, Int0State st2, Int1State st3, Int1State st4, Int1State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target3(Int0State st0, Int0State st1, Int1State st2, Int1State st3, Int2State st4, Int2State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + +} \ No newline at end of file diff --git a/test/micro/org/openjdk/bench/valhalla/invoke/field/Value.java b/test/micro/org/openjdk/bench/valhalla/invoke/field/Value.java new file mode 100644 index 00000000000..620e7474f08 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/invoke/field/Value.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.invoke.field; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class Value { + + /* + virtual method invocations: + target0 - statically known target method. + target1 - the single invoked method (should be inlined) + target2 - two invoked method (should be inlined, cache-inline) + target3 - thee invoked method (shouldn't be inlined) + + */ + + + public static final int SIZE = 128; + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt0 implements InterfaceInt { + public final int value; + public ValueInt0(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + public static value class ValueInt1 implements InterfaceInt { + public final int value; + public ValueInt1(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + public static value class ValueInt2 implements InterfaceInt { + public final int value; + public ValueInt2(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + public static class IntWrapper { + final InterfaceInt f; + + public IntWrapper(InterfaceInt f) { + this.f = f; + } + } + + public static class ValWrapper { + final ValueInt0 f; + + public ValWrapper(ValueInt0 f) { + this.f = f; + } + } + + @State(Scope.Thread) + public static class Int0State { + public IntWrapper[] arr; + @Setup + public void setup() { + arr = new IntWrapper[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IntWrapper(new ValueInt0(i)); + } + } + } + + @State(Scope.Thread) + public static class Int1State { + public IntWrapper[] arr; + @Setup + public void setup() { + arr = new IntWrapper[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IntWrapper(new ValueInt1(i)); + } + } + } + + @State(Scope.Thread) + public static class Int2State { + public IntWrapper[] arr; + @Setup + public void setup() { + arr = new IntWrapper[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IntWrapper(new ValueInt2(i)); + } + } + } + + @State(Scope.Thread) + public static class Ref0State { + public ValWrapper[] arr; + @Setup + public void setup() { + arr = new ValWrapper[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValWrapper(new ValueInt0(i)); + } + } + } + + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int reduce_int(IntWrapper[] arr) { + int r = 0; + for (int i = 0; i < arr.length; i++) { + r += arr[i].f.value(); + } + return r; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int reduce_val(ValWrapper[] arr) { + int r = 0; + for (int i = 0; i < arr.length; i++) { + r += arr[i].f.value(); + } + return r; + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target0(Ref0State st0, Ref0State st1, Ref0State st2, Ref0State st3, Ref0State st4, Ref0State st5) { + return reduce_val(st0.arr) + + reduce_val(st1.arr) + + reduce_val(st2.arr) + + reduce_val(st3.arr) + + reduce_val(st4.arr) + + reduce_val(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target1(Int0State st0, Int0State st1, Int0State st2, Int0State st3, Int0State st4, Int0State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target2(Int0State st0, Int0State st1, Int0State st2, Int1State st3, Int1State st4, Int1State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target3(Int0State st0, Int0State st1, Int1State st2, Int1State st3, Int2State st4, Int2State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + +} \ No newline at end of file diff --git a/test/micro/org/openjdk/bench/valhalla/invoke/field/ValueNullFree.java b/test/micro/org/openjdk/bench/valhalla/invoke/field/ValueNullFree.java new file mode 100644 index 00000000000..15bcfd320ec --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/invoke/field/ValueNullFree.java @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.invoke.field; + +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class ValueNullFree { + + /* + virtual method invocations: + target0 - statically known target method. + target1 - the single invoked method (should be inlined) + target2 - two invoked method (should be inlined, cache-inline) + target3 - thee invoked method (shouldn't be inlined) + + */ + + + public static final int SIZE = 128; + + public interface InterfaceInt { + public int value(); + } + + public static value class ValueInt0 implements InterfaceInt { + public final int value; + public ValueInt0(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + public static value class ValueInt1 implements InterfaceInt { + public final int value; + public ValueInt1(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + public static value class ValueInt2 implements InterfaceInt { + public final int value; + public ValueInt2(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + public static class IntWrapper { + final InterfaceInt f; + + public IntWrapper(InterfaceInt f) { + this.f = f; + } + } + + public static class ValWrapper { + @Strict + @NullRestricted + final ValueInt0 f; + + public ValWrapper(ValueInt0 f) { + this.f = f; + } + } + + @State(Scope.Thread) + public static class Int0State { + public IntWrapper[] arr; + @Setup + public void setup() { + arr = new IntWrapper[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IntWrapper(new ValueInt0(i)); + } + } + } + + @State(Scope.Thread) + public static class Int1State { + public IntWrapper[] arr; + @Setup + public void setup() { + arr = new IntWrapper[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IntWrapper(new ValueInt1(i)); + } + } + } + + @State(Scope.Thread) + public static class Int2State { + public IntWrapper[] arr; + @Setup + public void setup() { + arr = new IntWrapper[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IntWrapper(new ValueInt2(i)); + } + } + } + + @State(Scope.Thread) + public static class Ref0State { + public ValWrapper[] arr; + @Setup + public void setup() { + arr = new ValWrapper[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValWrapper(new ValueInt0(i)); + } + } + } + + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int reduce_int(IntWrapper[] arr) { + int r = 0; + for (int i = 0; i < arr.length; i++) { + r += arr[i].f.value(); + } + return r; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int reduce_val(ValWrapper[] arr) { + int r = 0; + for (int i = 0; i < arr.length; i++) { + r += arr[i].f.value(); + } + return r; + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target0(Ref0State st0, Ref0State st1, Ref0State st2, Ref0State st3, Ref0State st4, Ref0State st5) { + return reduce_val(st0.arr) + + reduce_val(st1.arr) + + reduce_val(st2.arr) + + reduce_val(st3.arr) + + reduce_val(st4.arr) + + reduce_val(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target1(Int0State st0, Int0State st1, Int0State st2, Int0State st3, Int0State st4, Int0State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target2(Int0State st0, Int0State st1, Int0State st2, Int1State st3, Int1State st4, Int1State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target3(Int0State st0, Int0State st1, Int1State st2, Int1State st3, Int2State st4, Int2State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + +} \ No newline at end of file diff --git a/test/micro/org/openjdk/bench/valhalla/invoke/field/ValueNullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/invoke/field/ValueNullFreeNonAtomic.java new file mode 100644 index 00000000000..30bad0164af --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/invoke/field/ValueNullFreeNonAtomic.java @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.invoke.field; + +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class ValueNullFreeNonAtomic { + + /* + virtual method invocations: + target0 - statically known target method. + target1 - the single invoked method (should be inlined) + target2 - two invoked method (should be inlined, cache-inline) + target3 - thee invoked method (shouldn't be inlined) + + */ + + + public static final int SIZE = 128; + + public interface InterfaceInt { + public int value(); + } + + @LooselyConsistentValue + public static value class ValueInt0 implements InterfaceInt { + public final int value; + public ValueInt0(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + @LooselyConsistentValue + public static value class ValueInt1 implements InterfaceInt { + public final int value; + public ValueInt1(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + @LooselyConsistentValue + public static value class ValueInt2 implements InterfaceInt { + public final int value; + public ValueInt2(int value) { + this.value = value; + } + @Override + public int value() { + return value; + } + } + + public static class IntWrapper { + final InterfaceInt f; + + public IntWrapper(InterfaceInt f) { + this.f = f; + } + } + + public static class ValWrapper { + @Strict + @NullRestricted + final ValueInt0 f; + + public ValWrapper(ValueInt0 f) { + this.f = f; + } + } + + @State(Scope.Thread) + public static class Int0State { + public IntWrapper[] arr; + @Setup + public void setup() { + arr = new IntWrapper[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IntWrapper(new ValueInt0(i)); + } + } + } + + @State(Scope.Thread) + public static class Int1State { + public IntWrapper[] arr; + @Setup + public void setup() { + arr = new IntWrapper[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IntWrapper(new ValueInt1(i)); + } + } + } + + @State(Scope.Thread) + public static class Int2State { + public IntWrapper[] arr; + @Setup + public void setup() { + arr = new IntWrapper[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IntWrapper(new ValueInt2(i)); + } + } + } + + @State(Scope.Thread) + public static class Ref0State { + public ValWrapper[] arr; + @Setup + public void setup() { + arr = new ValWrapper[SIZE]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValWrapper(new ValueInt0(i)); + } + } + } + + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int reduce_int(IntWrapper[] arr) { + int r = 0; + for (int i = 0; i < arr.length; i++) { + r += arr[i].f.value(); + } + return r; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int reduce_val(ValWrapper[] arr) { + int r = 0; + for (int i = 0; i < arr.length; i++) { + r += arr[i].f.value(); + } + return r; + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target0(Ref0State st0, Ref0State st1, Ref0State st2, Ref0State st3, Ref0State st4, Ref0State st5) { + return reduce_val(st0.arr) + + reduce_val(st1.arr) + + reduce_val(st2.arr) + + reduce_val(st3.arr) + + reduce_val(st4.arr) + + reduce_val(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target1(Int0State st0, Int0State st1, Int0State st2, Int0State st3, Int0State st4, Int0State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target2(Int0State st0, Int0State st1, Int0State st2, Int1State st3, Int1State st4, Int1State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + + @Benchmark + @OperationsPerInvocation(SIZE * 6) + @CompilerControl(CompilerControl.Mode.INLINE) + public int target3(Int0State st0, Int0State st1, Int1State st2, Int1State st3, Int2State st4, Int2State st5) { + return reduce_int(st0.arr) + + reduce_int(st1.arr) + + reduce_int(st2.arr) + + reduce_int(st3.arr) + + reduce_int(st4.arr) + + reduce_int(st5.arr); + } + +} \ No newline at end of file diff --git a/test/micro/org/openjdk/bench/valhalla/loops/osr/LarvalDetectionAboveOSR.java b/test/micro/org/openjdk/bench/valhalla/loops/osr/LarvalDetectionAboveOSR.java new file mode 100644 index 00000000000..eac68be92bd --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/loops/osr/LarvalDetectionAboveOSR.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.bench.valhalla.loops.osr; + +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; + +@BenchmarkMode(Mode.SingleShotTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Fork(value = 1, jvmArgsAppend = {"--enable-preview"}) +@Warmup(iterations = 0) +@Measurement(iterations = 10) +public class LarvalDetectionAboveOSR { + @Benchmark + public long test() { + return MyNumber.loop(); + } +} + +value class MyNumber { + static int MANY = 1_000_000_000; + private long d0; + + MyNumber(long d0) { + this.d0 = d0; + } + + MyNumber add(long v) { + return new MyNumber(d0 + v); + } + + public static long loop() { + MyNumber dec = new MyNumber(123); + for (int i = 0; i < MANY; ++i) { + dec = dec.add(i); + } + return dec.d0; + } +} + diff --git a/test/micro/org/openjdk/bench/valhalla/mandelbrot/Identity.java b/test/micro/org/openjdk/bench/valhalla/mandelbrot/Identity.java new file mode 100644 index 00000000000..d8fbd7d7c08 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/mandelbrot/Identity.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.mandelbrot; + +import org.openjdk.jmh.annotations.Benchmark; + +public class Identity extends MandelbrotBase { + + @Benchmark + public int[][] mandelbrot() { + for (int x = 0; x < size; x++) { + for (int y = 0; y < size; y++) { + points[x][y] = count(coordToComplex(x, y, size, size)); + } + } + return points; + } + + private IdentityComplex coordToComplex(int x, int y, int width, int height) { + double cx = ((double) x) / (((double) width) / 2.0) - 1.0; + double cy = ((double) y) / (((double) height) / 2.0) - 1.0; + return new IdentityComplex(cy * SCALE, cx * SCALE); + } + + private static int count(IdentityComplex c) { + IdentityComplex z = c; + for (int i = 1; i < MAX_ITER; i++) { + if (z.length() >= 2.0) return i; + z = z.mul(z).add(c); + } + return MAX_ITER; + } + + public static class IdentityComplex { + + public final double re; + public final double im; + + public IdentityComplex(double re, double im) { + this.re = re; + this.im = im; + } + + public double re() { return re; } + + public double im() { return im; } + + public IdentityComplex add(IdentityComplex that) { + return new IdentityComplex(this.re + that.re, this.im + that.im); + } + + public IdentityComplex mul(IdentityComplex that) { + return new IdentityComplex(this.re * that.re - this.im * that.im, + this.re * that.im + this.im * that.re); + } + + public double length() { + return Math.sqrt(re * re + im * im); + } + + } +} diff --git a/test/micro/org/openjdk/bench/valhalla/mandelbrot/MandelbrotBase.java b/test/micro/org/openjdk/bench/valhalla/mandelbrot/MandelbrotBase.java new file mode 100644 index 00000000000..39a7c93dec0 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/mandelbrot/MandelbrotBase.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.mandelbrot; + +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@Fork(3) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.SECONDS) +@BenchmarkMode(Mode.Throughput) +@State(Scope.Thread) +public abstract class MandelbrotBase { + + @Param("500") + int size; + + public static final double SCALE = 2.0; + public static final int MAX_ITER = 400; + + int[][] points; // color points + + @Setup() + public void setup() { + points = new int[size][size]; + } + + +} diff --git a/test/micro/org/openjdk/bench/valhalla/mandelbrot/Primitive.java b/test/micro/org/openjdk/bench/valhalla/mandelbrot/Primitive.java new file mode 100644 index 00000000000..6835316dd65 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/mandelbrot/Primitive.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.mandelbrot; + +import org.openjdk.jmh.annotations.Benchmark; + +public class Primitive extends MandelbrotBase { + + @Benchmark + public int[][] mandelbrot() { + for (int x = 0; x < size; x++) { + for (int y = 0; y < size; y++) { + points[x][y] = count(x, y, size, size); + } + } + return points; + } + + private int count(int x, int y, int width, int height) { + double cx = (((double) x) / (((double) width) / 2.0) - 1.0) * SCALE; + double cy = (((double) y) / (((double) height) / 2.0) - 1.0) * SCALE; + double zx = cx; + double zy = cy; + for (int i = 1; i < MAX_ITER; i++) { + if (Math.sqrt(zx * zx + zy * zy) >= 2.0) return i; + double tx = zx * zx - zy * zy + cx; + double ty = 2 * zx * zy + cy; + zx = tx; + zy = ty; + } + return MAX_ITER; + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/mandelbrot/Value.java b/test/micro/org/openjdk/bench/valhalla/mandelbrot/Value.java new file mode 100644 index 00000000000..3abb5b1ac8c --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/mandelbrot/Value.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.mandelbrot; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Fork; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value extends MandelbrotBase { + + private ValueComplex coordToComplex(int x, int y, int width, int height) { + double cx = ((double) x) / (((double) width) / 2.0) - 1.0; + double cy = ((double) y) / (((double) height) / 2.0) - 1.0; + return new ValueComplex(cy * SCALE, cx * SCALE); + } + + private static int count(ValueComplex c) { + ValueComplex z = c; + for (int i = 1; i < MAX_ITER; i++) { + if (z.length() >= 2.0) return i; + z = z.mul(z).add(c); + } + return MAX_ITER; + } + + @Benchmark + public int[][] mandelbrot_value() { + for (int x = 0; x < size; x++) { + for (int y = 0; y < size; y++) { + points[x][y] = count(coordToComplex(x, y, size, size)); + } + } + return points; + } + + private Complex coordToComplex_interface(int x, int y, int width, int height) { + double cx = ((double) x) / (((double) width) / 2.0) - 1.0; + double cy = ((double) y) / (((double) height) / 2.0) - 1.0; + return new ValueComplex(cy * SCALE, cx * SCALE); + } + + private static int count_interface(Complex c) { + Complex z = c; + for (int i = 1; i < MAX_ITER; i++) { + if (z.length() >= 2.0) return i; + z = z.mul(z).add(c); + } + return MAX_ITER; + } + + @Benchmark + public int[][] mandelbrot_interface() { + for (int x = 0; x < size; x++) { + for (int y = 0; y < size; y++) { + points[x][y] = count_interface(coordToComplex_interface(x, y, size, size)); + } + } + return points; + } + + public static interface Complex { + public Complex add(Complex that); + public Complex mul(Complex that); + public double length(); + public double re(); + public double im(); + } + + public static value class ValueComplex implements Complex { + + public final double re; + public final double im; + + public ValueComplex(double re, double im) { + this.re = re; + this.im = im; + } + + @Override + public double re() { return re; } + + @Override + public double im() { return im; } + + public ValueComplex add(ValueComplex that) { + return new ValueComplex(this.re + that.re, this.im + that.im); + } + + @Override + public Complex add(Complex that) { + return new ValueComplex(this.re + that.re(), this.im + that.im()); + } + + public ValueComplex mul(ValueComplex that) { + return new ValueComplex(this.re * that.re - this.im * that.im, + this.re * that.im + this.im * that.re); + } + + @Override + public Complex mul(Complex that) { + return new ValueComplex(this.re * that.re() - this.im * that.im(), + this.re * that.im() + this.im * that.re()); + } + + public double length() { + return Math.sqrt(re * re + im * im); + } + + } +} diff --git a/test/micro/org/openjdk/bench/valhalla/matrix/Identity.java b/test/micro/org/openjdk/bench/valhalla/matrix/Identity.java new file mode 100644 index 00000000000..96d119e9afd --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/matrix/Identity.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.matrix; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Setup; + +import java.util.concurrent.ThreadLocalRandom; + + +public class Identity extends MatrixBase { + + public static IdentityComplex[][] create_matrix_ref(int size) { + return new IdentityComplex[size][size]; + } + + public static Complex[][] create_matrix_int(int size) { + return new Complex[size][size]; + } + + public static abstract class RefState extends SizeState { + IdentityComplex[][] A; + IdentityComplex[][] B; + + static void populate(IdentityComplex[][] m) { + int size = m.length; + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + m[i][j] = new IdentityComplex(ThreadLocalRandom.current().nextDouble(), ThreadLocalRandom.current().nextDouble()); + } + } + } + } + + public static abstract class IntState extends SizeState { + Complex[][] A; + Complex[][] B; + + static void populate(Complex[][] m) { + int size = m.length; + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + m[i][j] = new IdentityComplex(ThreadLocalRandom.current().nextDouble(), ThreadLocalRandom.current().nextDouble()); + } + } + } + } + + public static class Ref_as_Ref extends RefState { + @Setup + public void setup() { + populate(A = create_matrix_ref(size)); + populate(B = create_matrix_ref(size)); + } + } + + public static class Ref_as_Int extends IntState { + @Setup + public void setup() { + populate(A = create_matrix_ref(size)); + populate(B = create_matrix_ref(size)); + } + } + + public static class Int_as_Int extends IntState { + @Setup + public void setup() { + populate(A = create_matrix_int(size)); + populate(B = create_matrix_int(size)); + } + } + + @Benchmark + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public IdentityComplex[][] mult_ref_as_ref(Ref_as_Ref st) { + IdentityComplex[][] A = st.A; + IdentityComplex[][] B = st.B; + int size = st.size; + IdentityComplex[][] R = create_matrix_ref(size); + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + IdentityComplex s = new IdentityComplex(0,0); + for (int k = 0; k < size; k++) { + s = s.add(A[i][k].mul(B[k][j])); + } + R[i][j] = s; + } + } + return R; + } + + @Benchmark + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public Complex[][] mult_ref_as_int(Ref_as_Int st) { + Complex[][] A = st.A; + Complex[][] B = st.B; + int size = st.size; + Complex[][] R = create_matrix_ref(size); + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + Complex s = new IdentityComplex(0,0); + for (int k = 0; k < size; k++) { + s = s.add(A[i][k].mul(B[k][j])); + } + R[i][j] = s; + } + } + return R; + } + + @Benchmark + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public Complex[][] mult_int_as_int(Int_as_Int st) { + Complex[][] A = st.A; + Complex[][] B = st.B; + int size = st.size; + Complex[][] R = create_matrix_int(size); + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + Complex s = new IdentityComplex(0,0); + for (int k = 0; k < size; k++) { + s = s.add(A[i][k].mul(B[k][j])); + } + R[i][j] = s; + } + } + return R; + } + + public interface Complex { + double re(); + double im(); + Complex add(Complex that); + Complex mul(Complex that); + } + + public static class IdentityComplex implements Complex { + + private final double re; + private final double im; + + public IdentityComplex(double re, double im) { + this.re = re; + this.im = im; + } + + @Override + public double re() { return re; } + + @Override + public double im() { return im; } + + @Override + public IdentityComplex add(Complex that) { + return new IdentityComplex(this.re + that.re(), this.im + that.im()); + } + + public IdentityComplex add(IdentityComplex that) { + return new IdentityComplex(this.re + that.re, this.im + that.im); + } + + @Override + public IdentityComplex mul(Complex that) { + return new IdentityComplex(this.re * that.re() - this.im * that.im(), + this.re * that.im() + this.im * that.re()); + } + + public IdentityComplex mul(IdentityComplex that) { + return new IdentityComplex(this.re * that.re - this.im * that.im, + this.re * that.im + this.im * that.re); + } + + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/matrix/MatrixBase.java b/test/micro/org/openjdk/bench/valhalla/matrix/MatrixBase.java new file mode 100644 index 00000000000..cbfb536b498 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/matrix/MatrixBase.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.matrix; + +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@Fork(3) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@Warmup(iterations = 5, time = 3) +@Measurement(iterations = 3, time = 8) +@BenchmarkMode(Mode.AverageTime) +public class MatrixBase { + + @State(Scope.Thread) + public static abstract class SizeState { + @Param({ + "20", + "360", + }) + public int size; + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/matrix/Primitive.java b/test/micro/org/openjdk/bench/valhalla/matrix/Primitive.java new file mode 100644 index 00000000000..743044e004f --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/matrix/Primitive.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.matrix; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Setup; + +import java.util.concurrent.ThreadLocalRandom; + +public class Primitive extends MatrixBase { + + public static class PrimState extends SizeState { + double[][] A; + double[][] B; + + @Setup + public void setup() { + A = populate(new double[size][size * 2]); + B = populate(new double[size][size * 2]); + } + + private double[][] populate(double[][] m) { + int size = m.length; + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + m[i][j * 2] = ThreadLocalRandom.current().nextDouble(); + m[i][j * 2 + 1] = ThreadLocalRandom.current().nextDouble(); + } + } + return m; + } + + } + + @Benchmark + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public double[][] multiply(PrimState st) { + double[][] A = st.A; + double[][] B = st.B; + int size = st.size; + double[][] R = new double[size][size * 2]; + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + double s_re = 0; + double s_im = 0; + for (int k = 0; k < size; k++) { + double are = A[i][k * 2]; + double aim = A[i][k * 2 + 1]; + double bre = B[k][j * 2]; + double bim = B[k][j * 2 + 1]; + s_re += are * bre - aim * bim; + s_im += are * bim + bre * aim; + } + R[i][j * 2] = s_re; + R[i][j * 2 + 1] = s_im; + } + } + return R; + } + +// @Benchmark +// public double[][] multiplyCacheFriendly() { +// int size = A.length; +// double[][] R = new double[size][size * 2]; +// for (int i = 0; i < size; i++) { +// for (int k = 0; k < size; k++) { +// double are = A[i][k * 2 + 0]; +// double aim = A[i][k * 2 + 1]; +// for (int j = 0; j < size; j++) { +// double bre = B[k][j * 2 + 0]; +// double bim = B[k][j * 2 + 1]; +// R[i][j * 2 + 0] += are * bre - aim * bim; +// R[i][j * 2 + 1] += are * bim + bre * aim; +// } +// } +// } +// return R; +// } + + +} diff --git a/test/micro/org/openjdk/bench/valhalla/matrix/Value.java b/test/micro/org/openjdk/bench/valhalla/matrix/Value.java new file mode 100644 index 00000000000..fbc37ee1212 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/matrix/Value.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.matrix; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +import java.util.concurrent.ThreadLocalRandom; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value extends MatrixBase { + + public static ValueComplex[][] create_matrix_val(int size) { + return new ValueComplex[size][size]; + } + + public static Complex[][] create_matrix_int(int size) { + return new Complex[size][size]; + } + + public static abstract class ValState extends SizeState { + ValueComplex[][] A; + ValueComplex[][] B; + + static void populate(ValueComplex[][] m) { + int size = m.length; + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + m[i][j] = new ValueComplex(ThreadLocalRandom.current().nextDouble(), ThreadLocalRandom.current().nextDouble()); + } + } + } + } + + public static abstract class IntState extends SizeState { + Complex[][] A; + Complex[][] B; + + static void populate(Complex[][] m) { + int size = m.length; + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + m[i][j] = new ValueComplex(ThreadLocalRandom.current().nextDouble(), ThreadLocalRandom.current().nextDouble()); + } + } + } + } + + public static class Val_as_Val extends ValState { + @Setup + public void setup() { + populate(A = create_matrix_val(size)); + populate(B = create_matrix_val(size)); + } + } + + public static class Val_as_Int extends IntState { + @Setup + public void setup() { + populate(A = create_matrix_val(size)); + populate(B = create_matrix_val(size)); + } + } + + public static class Int_as_Int extends IntState { + @Setup + public void setup() { + populate(A = create_matrix_int(size)); + populate(B = create_matrix_int(size)); + } + } + + @Benchmark + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueComplex[][] mult_val_as_val(Val_as_Val st) { + ValueComplex[][] A = st.A; + ValueComplex[][] B = st.B; + int size = st.size; + ValueComplex[][] R = create_matrix_val(size); + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + ValueComplex s = new ValueComplex(0,0); + for (int k = 0; k < size; k++) { + s = s.add(A[i][k].mul(B[k][j])); + } + R[i][j] = s; + } + } + return R; + } + + @Benchmark + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public Complex[][] mult_val_as_int(Val_as_Int st) { + Complex[][] A = st.A; + Complex[][] B = st.B; + int size = st.size; + Complex[][] R = create_matrix_val(size); + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + Complex s = new ValueComplex(0,0); + for (int k = 0; k < size; k++) { + s = s.add(A[i][k].mul(B[k][j])); + } + R[i][j] = s; + } + } + return R; + } + + @Benchmark + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public Complex[][] mult_int_as_int(Int_as_Int st) { + Complex[][] A = st.A; + Complex[][] B = st.B; + int size = st.size; + Complex[][] R = create_matrix_int(size); + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + Complex s = new ValueComplex(0,0); + for (int k = 0; k < size; k++) { + s = s.add(A[i][k].mul(B[k][j])); + } + R[i][j] = s; + } + } + return R; + } + + public interface Complex { + double re(); + double im(); + Complex add(Complex that); + Complex mul(Complex that); + } + + public static value class ValueComplex implements Complex { + + private final double re; + private final double im; + + public ValueComplex(double re, double im) { + this.re = re; + this.im = im; + } + + @Override + public double re() { return re; } + + @Override + public double im() { return im; } + + @Override + public ValueComplex add(Complex that) { + return new ValueComplex(this.re + that.re(), this.im + that.im()); + } + + public ValueComplex add(ValueComplex that) { + return new ValueComplex(this.re + that.re, this.im + that.im); + } + + @Override + public ValueComplex mul(Complex that) { + return new ValueComplex(this.re * that.re() - this.im * that.im(), + this.re * that.im() + this.im * that.re()); + } + + public ValueComplex mul(ValueComplex that) { + return new ValueComplex(this.re * that.re - this.im * that.im, + this.re * that.im + this.im * that.re); + } + + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/matrix/ValueNullFree.java b/test/micro/org/openjdk/bench/valhalla/matrix/ValueNullFree.java new file mode 100644 index 00000000000..5d484035f20 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/matrix/ValueNullFree.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.matrix; + +import jdk.internal.value.ValueClass; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +import java.util.concurrent.ThreadLocalRandom; + + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class ValueNullFree extends MatrixBase { + + public static ValueComplex[][] create_matrix_val(int size) { + ValueComplex[][] x; + x = new ValueComplex[size][]; + for (int i = 0; i < size; i++) { + x[i] = (ValueComplex[]) ValueClass.newNullRestrictedAtomicArray(ValueComplex.class, size, new ValueComplex(0, 0)); + } + return x; + } + + public static Complex[][] create_matrix_int(int size) { + return new Complex[size][size]; + } + + public static abstract class ValState extends SizeState { + ValueComplex[][] A; + ValueComplex[][] B; + + static void populate(ValueComplex[][] m) { + int size = m.length; + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + m[i][j] = new ValueComplex(ThreadLocalRandom.current().nextDouble(), ThreadLocalRandom.current().nextDouble()); + } + } + } + } + + public static abstract class IntState extends SizeState { + Complex[][] A; + Complex[][] B; + + static void populate(Complex[][] m) { + int size = m.length; + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + m[i][j] = new ValueComplex(ThreadLocalRandom.current().nextDouble(), ThreadLocalRandom.current().nextDouble()); + } + } + } + } + + public static class Val_as_Val extends ValState { + @Setup + public void setup() { + populate(A = create_matrix_val(size)); + populate(B = create_matrix_val(size)); + } + } + + public static class Val_as_Int extends IntState { + @Setup + public void setup() { + populate(A = create_matrix_val(size)); + populate(B = create_matrix_val(size)); + } + } + + public static class Int_as_Int extends IntState { + @Setup + public void setup() { + populate(A = create_matrix_int(size)); + populate(B = create_matrix_int(size)); + } + } + + @Benchmark + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueComplex[][] mult_val_as_val(Val_as_Val st) { + ValueComplex[][] A = st.A; + ValueComplex[][] B = st.B; + int size = st.size; + ValueComplex[][] R = create_matrix_val(size); + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + ValueComplex s = new ValueComplex(0,0); + for (int k = 0; k < size; k++) { + s = s.add(A[i][k].mul(B[k][j])); + } + R[i][j] = s; + } + } + return R; + } + + @Benchmark + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public Complex[][] mult_val_as_int(Val_as_Int st) { + Complex[][] A = st.A; + Complex[][] B = st.B; + int size = st.size; + Complex[][] R = create_matrix_val(size); + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + Complex s = new ValueComplex(0,0); + for (int k = 0; k < size; k++) { + s = s.add(A[i][k].mul(B[k][j])); + } + R[i][j] = s; + } + } + return R; + } + + @Benchmark + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public Complex[][] mult_int_as_int(Int_as_Int st) { + Complex[][] A = st.A; + Complex[][] B = st.B; + int size = st.size; + Complex[][] R = create_matrix_int(size); + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + Complex s = new ValueComplex(0,0); + for (int k = 0; k < size; k++) { + s = s.add(A[i][k].mul(B[k][j])); + } + R[i][j] = s; + } + } + return R; + } + + public interface Complex { + double re(); + double im(); + Complex add(Complex that); + Complex mul(Complex that); + } + + public static value class ValueComplex implements Complex { + + private final double re; + private final double im; + + public ValueComplex(double re, double im) { + this.re = re; + this.im = im; + } + + @Override + public double re() { return re; } + + @Override + public double im() { return im; } + + @Override + public ValueComplex add(Complex that) { + return new ValueComplex(this.re + that.re(), this.im + that.im()); + } + + public ValueComplex add(ValueComplex that) { + return new ValueComplex(this.re + that.re, this.im + that.im); + } + + @Override + public ValueComplex mul(Complex that) { + return new ValueComplex(this.re * that.re() - this.im * that.im(), + this.re * that.im() + this.im * that.re()); + } + + public ValueComplex mul(ValueComplex that) { + return new ValueComplex(this.re * that.re - this.im * that.im, + this.re * that.im + this.im * that.re); + } + + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/matrix/ValueNullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/matrix/ValueNullFreeNonAtomic.java new file mode 100644 index 00000000000..513765822df --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/matrix/ValueNullFreeNonAtomic.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.matrix; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +import java.util.concurrent.ThreadLocalRandom; + + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class ValueNullFreeNonAtomic extends MatrixBase { + + public static ValueComplex[][] create_matrix_val(int size) { + ValueComplex[][] x; + x = new ValueComplex[size][]; + for (int i = 0; i < size; i++) { + x[i] = (ValueComplex[]) ValueClass.newNullRestrictedNonAtomicArray(ValueComplex.class, size, new ValueComplex(0, 0)); + } + return x; + } + + public static Complex[][] create_matrix_int(int size) { + return new Complex[size][size]; + } + + public static abstract class ValState extends SizeState { + ValueComplex[][] A; + ValueComplex[][] B; + + static void populate(ValueComplex[][] m) { + int size = m.length; + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + m[i][j] = new ValueComplex(ThreadLocalRandom.current().nextDouble(), ThreadLocalRandom.current().nextDouble()); + } + } + } + } + + public static abstract class IntState extends SizeState { + Complex[][] A; + Complex[][] B; + + static void populate(Complex[][] m) { + int size = m.length; + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + m[i][j] = new ValueComplex(ThreadLocalRandom.current().nextDouble(), ThreadLocalRandom.current().nextDouble()); + } + } + } + } + + public static class Val_as_Val extends ValState { + @Setup + public void setup() { + populate(A = create_matrix_val(size)); + populate(B = create_matrix_val(size)); + } + } + + public static class Val_as_Int extends IntState { + @Setup + public void setup() { + populate(A = create_matrix_val(size)); + populate(B = create_matrix_val(size)); + } + } + + public static class Int_as_Int extends IntState { + @Setup + public void setup() { + populate(A = create_matrix_int(size)); + populate(B = create_matrix_int(size)); + } + } + + @Benchmark + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public ValueComplex[][] mult_val_as_val(Val_as_Val st) { + ValueComplex[][] A = st.A; + ValueComplex[][] B = st.B; + int size = st.size; + ValueComplex[][] R = create_matrix_val(size); + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + ValueComplex s = new ValueComplex(0,0); + for (int k = 0; k < size; k++) { + s = s.add(A[i][k].mul(B[k][j])); + } + R[i][j] = s; + } + } + return R; + } + + @Benchmark + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public Complex[][] mult_val_as_int(Val_as_Int st) { + Complex[][] A = st.A; + Complex[][] B = st.B; + int size = st.size; + Complex[][] R = create_matrix_val(size); + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + Complex s = new ValueComplex(0,0); + for (int k = 0; k < size; k++) { + s = s.add(A[i][k].mul(B[k][j])); + } + R[i][j] = s; + } + } + return R; + } + + @Benchmark + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public Complex[][] mult_int_as_int(Int_as_Int st) { + Complex[][] A = st.A; + Complex[][] B = st.B; + int size = st.size; + Complex[][] R = create_matrix_int(size); + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + Complex s = new ValueComplex(0,0); + for (int k = 0; k < size; k++) { + s = s.add(A[i][k].mul(B[k][j])); + } + R[i][j] = s; + } + } + return R; + } + + public interface Complex { + double re(); + double im(); + Complex add(Complex that); + Complex mul(Complex that); + } + + @LooselyConsistentValue + public static value class ValueComplex implements Complex { + + private final double re; + private final double im; + + public ValueComplex(double re, double im) { + this.re = re; + this.im = im; + } + + @Override + public double re() { return re; } + + @Override + public double im() { return im; } + + @Override + public ValueComplex add(Complex that) { + return new ValueComplex(this.re + that.re(), this.im + that.im()); + } + + public ValueComplex add(ValueComplex that) { + return new ValueComplex(this.re + that.re, this.im + that.im); + } + + @Override + public ValueComplex mul(Complex that) { + return new ValueComplex(this.re * that.re() - this.im * that.im(), + this.re * that.im() + this.im * that.re()); + } + + public ValueComplex mul(ValueComplex that) { + return new ValueComplex(this.re * that.re - this.im * that.im, + this.re * that.im + this.im * that.re); + } + + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/ArrayListInt.java b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/ArrayListInt.java new file mode 100644 index 00000000000..d74e455fe9b --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/ArrayListInt.java @@ -0,0 +1,640 @@ +/* + * 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 + * 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 org.openjdk.bench.valhalla.sandbox.corelibs; + +import java.util.Arrays; +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.IntConsumer; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; + +/** + * Resizable-array implementation like {@code ArrayList}. + */ +public class ArrayListInt +// extends AbstractList +// implements List, RandomAccess, Cloneable, java.io.Serializable +{ + @java.io.Serial + private static final long serialVersionUID = 8683452581122892189L; + + /** + *new () initial capacity. + */ + private static final int DEFAULT_CAPACITY = 10; + + /** + * Shared empty array instance used for empty instances. + */ + private static final int[] EMPTY_ELEMENTDATA = {}; + + /** + * Shared empty array instance used for default sized empty instances. We + * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when + * first element is added. + */ + private static final int[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new int[0]; + + /** + * The array buffer into which the elements of the ArrayList are stored. + * The capacity of the ArrayList is the length of this array buffer. Any + * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA + * will be expanded to DEFAULT_CAPACITY when the first element is added. + */ + transient int[] elementData; // non-private to simplify nested class access + + protected transient int modCount = 0; + + /** + * The size of the ArrayList (the number of elements it contains). + * + * @serial + */ + private int size; + + /** + * Constructs an empty list with the specified initial capacity. + * + * @param initialCapacity the initial capacity of the list + * @throws IllegalArgumentException if the specified initial capacity + * is negative + */ + public ArrayListInt(int initialCapacity) { + if (initialCapacity > 0) { + this.elementData = new int[initialCapacity]; + } else if (initialCapacity == 0) { + this.elementData = EMPTY_ELEMENTDATA; + } else { + throw new IllegalArgumentException("Illegal Capacity: "+ + initialCapacity); + } + } + + /** + * Constructs an empty list with an initial capacity of ten. + */ + public ArrayListInt() { + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; + } + + /** + * Trims the capacity of this {@code ArrayList} instance to be the + * list's current size. An application can use this operation to minimize + * the storage of an {@code ArrayList} instance. + */ + public void trimToSize() { + modCount++; + if (size < elementData.length) { + elementData = (size == 0) + ? EMPTY_ELEMENTDATA + : Arrays.copyOf(elementData, size); + } + } + + /** + * Increases the capacity of this {@code ArrayList} instance, if + * necessary, to ensure that it can hold at least the number of elements + * specified by the minimum capacity argument. + * + * @param minCapacity the desired minimum capacity + */ + public void ensureCapacity(int minCapacity) { + if (minCapacity > elementData.length + && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA + && minCapacity <= DEFAULT_CAPACITY)) { + modCount++; + grow(minCapacity); + } + } + + /** + * Increases the capacity to ensure that it can hold at least the + * number of elements specified by the minimum capacity argument. + * + * @param minCapacity the desired minimum capacity + * @throws OutOfMemoryError if minCapacity is less than zero + */ + private int[] grow(int minCapacity) { + int oldCapacity = elementData.length; + if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { + int newCapacity = newLength(oldCapacity, + minCapacity - oldCapacity, /* minimum growth */ + oldCapacity >> 1 /* preferred growth */); + return elementData = Arrays.copyOf(elementData, newCapacity); + } else { + return elementData = new int[Math.max(DEFAULT_CAPACITY, minCapacity)]; + } + } + + private int[] grow() { + return grow(size + 1); + } + + /** + * Returns the number of elements in this list. + * + * @return the number of elements in this list + */ + public int size() { + return size; + } + + /** + * Returns {@code true} if this list contains no elements. + * + * @return {@code true} if this list contains no elements + */ + public boolean isEmpty() { + return size == 0; + } + + /** + * Returns {@code true} if this list contains the specified element. + * More formally, returns {@code true} if and only if this list contains + * at least one element {@code e} such that + * {@code Objects.equals(o, e)}. + * + * @param o element whose presence in this list is to be tested + * @return {@code true} if this list contains the specified element + */ + public boolean contains(int o) { + return indexOf(o) >= 0; + } + + /** + * Returns the index of the first occurrence of the specified element + * in this list, or -1 if this list does not contain the element. + * More formally, returns the lowest index {@code i} such that + * {@code Objects.equals(o, get(i))}, + * or -1 if there is no such index. + */ + public int indexOf(int o) { + return indexOfRange(o, 0, size); + } + + int indexOfRange(int o, int start, int end) { + int[] es = elementData; + for (int i = start; i < end; i++) { + if (o == es[i]) { + return i; + } + } + return -1; + } + + /** + * Returns the index of the last occurrence of the specified element + * in this list, or -1 if this list does not contain the element. + * More formally, returns the highest index {@code i} such that + * {@code Objects.equals(o, get(i))}, + * or -1 if there is no such index. + */ + public int lastIndexOf(int o) { + return lastIndexOfRange(o, 0, size); + } + + int lastIndexOfRange(int o, int start, int end) { + int[] es = elementData; + { + for (int i = end - 1; i >= start; i--) { + if (o == es[i]) { + return i; + } + } + } + return -1; + } + + /** + * Returns an array containing all of the elements in this list + * in proper sequence (from first to last element). + * + *

    The returned array will be "safe" in that no references to it are + * maintained by this list. (In other words, this method must allocate + * a new array). The caller is thus free to modify the returned array. + * + *

    This method acts as bridge between array-based and collection-based + * APIs. + * + * @return an array containing all of the elements in this list in + * proper sequence + */ + public int[] toArray() { + return Arrays.copyOf(elementData, size); + } + + // Positional Access Operations + + @SuppressWarnings("unchecked") + int elementData(int index) { + return (int) elementData[index]; + } + + @SuppressWarnings("unchecked") + static int elementAt(int[] es, int index) { + return es[index]; + } + + /** + * Returns the element at the specified position in this list. + * + * @param index index of the element to return + * @return the element at the specified position in this list + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public int get(int index) { + Objects.checkIndex(index, size); + return elementData(index); + } + + /** + * Replaces the element at the specified position in this list with + * the specified element. + * + * @param index index of the element to replace + * @param element element to be stored at the specified position + * @return the element previously at the specified position + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public int set(int index, int element) { + Objects.checkIndex(index, size); + int oldValue = elementData(index); + elementData[index] = element; + return oldValue; + } + + /** + * This helper method split out from add(int) to keep method + * bytecode size under 35 (the -XX:MaxInlineSize default value), + * which helps when add(int) is called in a C1-compiled loop. + */ + private void add(int e, int[] elementData, int s) { + if (s == elementData.length) + elementData = grow(); + elementData[s] = e; + size = s + 1; + } + + /** + * Appends the specified element to the end of this list. + * + * @param e element to be appended to this list + * @return {@code true} (as specified by {@link Collection#add}) + */ + public boolean add(int e) { + modCount++; + add(e, elementData, size); + return true; + } + + /** + * Inserts the specified element at the specified position in this + * list. Shifts the element currently at that position (if any) and + * any subsequent elements to the right (adds one to their indices). + * + * @param index index at which the specified element is to be inserted + * @param element element to be inserted + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public void add(int index, int element) { + rangeCheckForAdd(index); + modCount++; + final int s; + int[] elementData; + if ((s = size) == (elementData = this.elementData).length) + elementData = grow(); + System.arraycopy(elementData, index, + elementData, index + 1, + s - index); + elementData[index] = element; + size = s + 1; + } + + /** + * Removes the element at the specified position in this list. + * Shifts any subsequent elements to the left (subtracts one from their + * indices). + * + * @param index the index of the element to be removed + * @return the element that was removed from the list + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public int remove(int index) { + Objects.checkIndex(index, size); + final int[] es = elementData; + + int oldValue = (int) es[index]; + fastRemove(es, index); + + return oldValue; + } + + /** + * {@inheritDoc} + */ + public boolean equals(Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof ArrayListInt)) { + return false; + } + + final int expectedModCount = modCount; + // ArrayList can be subclassed and given arbitrary behavior, but we can + // still deal with the common case where o is ArrayList precisely + boolean equal = equalsArrayList((ArrayListInt) o); + + checkForComodification(expectedModCount); + return equal; + } + + private boolean equalsArrayList(ArrayListInt other) { + final int otherModCount = other.modCount; + final int s = size; + boolean equal; + if (equal = (s == other.size)) { + final int[] otherEs = other.elementData; + final int[] es = elementData; + if (s > es.length || s > otherEs.length) { + throw new ConcurrentModificationException(); + } + for (int i = 0; i < s; i++) { + if (es[i] != otherEs[i]) { + equal = false; + break; + } + } + } + other.checkForComodification(otherModCount); + return equal; + } + + private void checkForComodification(final int expectedModCount) { + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + } + + /** + * {@inheritDoc} + */ + public int hashCode() { + int expectedModCount = modCount; + int hash = hashCodeRange(0, size); + checkForComodification(expectedModCount); + return hash; + } + + int hashCodeRange(int from, int to) { + final int[] es = elementData; + if (to > es.length) { + throw new ConcurrentModificationException(); + } + int hashCode = 1; + for (int i = from; i < to; i++) { + int e = es[i]; + hashCode = 31 * hashCode + e; + } + return hashCode; + } + + /** + * Removes the first occurrence of the specified element from this list, + * if it is present. If the list does not contain the element, it is + * unchanged. More formally, removes the element with the lowest index + * {@code i} such that + * {@code Objects.equals(o, get(i))} + * (if such an element exists). Returns {@code true} if this list + * contained the specified element (or equivalently, if this list + * changed as a result of the call). + * + * @param o element to be removed from this list, if present + * @return {@code true} if this list contained the specified element + */ + public boolean removeC(int o) { + final int[] es = elementData; + final int size = this.size; + int i = 0; + found: + { +// if (o == null) { +// for (; i < size; i++) +// if (es[i] == null) +// break found; +// } else + { + for (; i < size; i++) + if (o == es[i]) + break found; + } + return false; + } + fastRemove(es, i); + return true; + } + + /** + * Private remove method that skips bounds checking and does not + * return the value removed. + */ + private void fastRemove(int[] es, int i) { + modCount++; + final int newSize; + if ((newSize = size - 1) > i) + System.arraycopy(es, i + 1, es, i, newSize - i); + es[size = newSize] = 0; + } + + /** + * Removes all of the elements from this list. The list will + * be empty after this call returns. + */ + public void clear() { + modCount++; + final int[] es = elementData; + for (int to = size, i = size = 0; i < to; i++) + es[i] = 0; + } + + /** + * Removes from this list all of the elements whose index is between + * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive. + * Shifts any succeeding elements to the left (reduces their index). + * This call shortens the list by {@code (toIndex - fromIndex)} elements. + * (If {@code toIndex==fromIndex}, this operation has no effect.) + * + * @throws IndexOutOfBoundsException if {@code fromIndex} or + * {@code toIndex} is out of range + * ({@code fromIndex < 0 || + * toIndex > size() || + * toIndex < fromIndex}) + */ + protected void removeRange(int fromIndex, int toIndex) { + if (fromIndex > toIndex) { + throw new IndexOutOfBoundsException( + outOfBoundsMsg(fromIndex, toIndex)); + } + modCount++; + shiftTailOverGap(elementData, fromIndex, toIndex); + } + + /** Erases the gap from lo to hi, by sliding down following elements. */ + private void shiftTailOverGap(int[] es, int lo, int hi) { + System.arraycopy(es, hi, es, lo, size - hi); + for (int to = size, i = (size -= hi - lo); i < to; i++) + es[i] = 0; + } + + /** + * A version of rangeCheck used by add and addAll. + */ + private void rangeCheckForAdd(int index) { + if (index > size || index < 0) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); + } + + /** + * Constructs an IndexOutOfBoundsException detail message. + * Of the many possible refactorings of the error handling code, + * this "outlining" performs best with both server and client VMs. + */ + private String outOfBoundsMsg(int index) { + return "Index: "+index+", Size: "+size; + } + + /** + * A version used in checking (fromIndex > toIndex) condition + */ + private static String outOfBoundsMsg(int fromIndex, int toIndex) { + return "From Index: " + fromIndex + " > To Index: " + toIndex; + } + + /** + * Returns an iterator over the elements in this list in proper sequence. + * + *

    The returned iterator is fail-fast. + * + * @return an iterator over the elements in this list in proper sequence + */ + public IntItr iterator() { + return new IntItr(); + } + + /** + * An optimized version of AbstractList.Itr + */ + private class IntItr { + int cursor; // index of next element to return + int lastRet = -1; // index of last element returned; -1 if no such + int expectedModCount = modCount; + + // prevent creating a synthetic constructor + IntItr() {} + + public boolean hasNext() { + return cursor != size; + } + + @SuppressWarnings("unchecked") + public int next() { + checkForComodification(); + int i = cursor; + if (i >= size) + throw new NoSuchElementException(); + int[] elementData = ArrayListInt.this.elementData; + if (i >= elementData.length) + throw new ConcurrentModificationException(); + cursor = i + 1; + return (int) elementData[lastRet = i]; + } + + public void remove() { + if (lastRet < 0) + throw new IllegalStateException(); + checkForComodification(); + + try { + ArrayListInt.this.remove(lastRet); + cursor = lastRet; + lastRet = -1; + expectedModCount = modCount; + } catch (IndexOutOfBoundsException ex) { + throw new ConcurrentModificationException(); + } + } + + public void forEachRemaining(IntConsumer action) { + Objects.requireNonNull(action); + final int size = ArrayListInt.this.size; + int i = cursor; + if (i < size) { + final int[] es = elementData; + if (i >= es.length) + throw new ConcurrentModificationException(); + for (; i < size && modCount == expectedModCount; i++) + action.accept(elementAt(es, i)); + // update once at end to reduce heap write traffic + cursor = i; + lastRet = i - 1; + checkForComodification(); + } + } + + final void checkForComodification() { + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + } + } + + static final int MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8; + + static int newLength(int oldLength, int minGrowth, int prefGrowth) { + // assert oldLength >= 0 + // assert minGrowth > 0 + + int newLength = Math.max(minGrowth, prefGrowth) + oldLength; + if (newLength - MAX_ARRAY_LENGTH <= 0) { + return newLength; + } + return hugeLength(oldLength, minGrowth); + } + + private static int hugeLength(int oldLength, int minGrowth) { + int minLength = oldLength + minGrowth; + if (minLength < 0) { // overflow + throw new OutOfMemoryError("Required array length too large"); + } + if (minLength <= MAX_ARRAY_LENGTH) { + return MAX_ARRAY_LENGTH; + } + return Integer.MAX_VALUE; + } +} diff --git a/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/ArrayListOfIntBench.java b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/ArrayListOfIntBench.java new file mode 100644 index 00000000000..ed9b923444e --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/ArrayListOfIntBench.java @@ -0,0 +1,239 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.bench.valhalla.sandbox.corelibs; + +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.TearDown; + +import org.openjdk.jmh.infra.Blackhole; + +import java.util.ArrayList; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/** + * Measure performance of List of Integer operations. + * - Set all of int from a set of random numbers (with a seed) + * - Get all + * - Shuffle the array + * - Sort the array + */ + + +@Fork(value = 3, jvmArgsAppend = "--enable-preview") +@Warmup(iterations = 5, time = 2) +@Measurement(iterations = 5, time = 3) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class ArrayListOfIntBench { + + @Param({ + "100", + "1000000", + }) + public int size; + + ArrayListInt arrayListInt; + ArrayListPrimitiveInt arrayListPrimitiveInt; + ArrayList arrayListOfInteger; + ArrayList arrayListOfPrimitiveInt; + Random random; + + @Setup + public void setup() { + arrayListInt = new ArrayListInt(size); + for (int i = 0; i < size; i++) { + arrayListInt.add(i, i); + } + + arrayListPrimitiveInt = new ArrayListPrimitiveInt(size); + for (int i = 0; i < size; i++) { + arrayListPrimitiveInt.add(i, new PrimitiveInt(i)); + } + + arrayListOfInteger = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + arrayListOfInteger.add(i, i); + } + + arrayListOfPrimitiveInt = new ArrayList(size); + for (int i = 0; i < size; i++) { + arrayListOfPrimitiveInt.add(i, new PrimitiveInt(i)); + } + + random = new Random(42); + } + + @Benchmark + public Object appendListInt() { + ArrayListInt list = new ArrayListInt(size); + for (int i = 0; i < size; i++) { + list.add(i); + } + return list; + } + + @Benchmark + public Object appendListPrimitiveInt() { + ArrayListPrimitiveInt list = new ArrayListPrimitiveInt(size); + for (int i = 0; i < size; i++) { + list.add(new PrimitiveInt(i)); + } + return list; + } + + @Benchmark + public Object appendListOfInteger() { + ArrayList list = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + list.add(i); + } + return list; + } + + @Benchmark + public Object appendListOfPrimitiveInt() { + ArrayList list = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + list.add(new PrimitiveInt(i)); + } + return list; + } + + + @Benchmark + public int sumListInt() { + int sum = 0; + for (int i = 0; i < size; i++) { + sum += arrayListInt.get(i); + } + return sum; + } + + @Benchmark + public int sumListOfInteger() { + int sum = 0; + for (int i = 0; i < size; i++) { + sum += arrayListOfInteger.get(i); + } + return sum; + } + + + @Benchmark + public int sumListPrimitiveInt() { + int sum = 0; + for (int i = 0; i < size; i++) { + sum += arrayListPrimitiveInt.get(i).value(); + } + return sum; + } + + @Benchmark + public int sumListOfPrimitiveInt() { + int sum = 0; + for (int i = 0; i < size; i++) { + sum += arrayListOfPrimitiveInt.get(i).value(); + } + return sum; + } + + @Benchmark + public int thrashListInt() { + final ArrayListInt list = arrayListInt; + + int sum = 0; + for (int i = 0; i < 1000; i++) { + int ndx = (random.nextInt() & 0x7fffffff) % list.size(); // positive + if (list.size() == size) { + list.remove(ndx); + } else { + list.add(ndx); + } + sum += ndx; + } + return sum; + } + + @Benchmark + public int thrashListPrimitiveInt() { + final ArrayListPrimitiveInt list = arrayListPrimitiveInt; + int sum = 0; + for (int i = 0; i < 1000; i++) { + int ndx = (random.nextInt() & 0x7fffffff) % list.size(); // positive + if (list.size() == size) { + list.remove(ndx); + } else { + list.add(ndx, new PrimitiveInt(ndx)); + } + sum += ndx; + } + return sum; + } + + @Benchmark + public int thrashListOfInteger() { + final ArrayList list = arrayListOfInteger; + int sum = 0; + for (int i = 0; i < 1000; i++) { + int ndx = (random.nextInt() & 0x7fffffff) % list.size(); // positive + if (list.size() == size) { + list.remove(ndx); + } else { + list.add(ndx); + } + sum += ndx; + } + return sum; + } + + + @Benchmark + public int thrashListOfPrimitiveInt() { + final ArrayList list = arrayListOfPrimitiveInt; + int sum = 0; + + for (int i = 0; i < 1000; i++) { + int ndx = (random.nextInt() & 0x7fffffff) % list.size(); // positive + if (list.size() == size) { + list.remove(ndx); + } else { + list.add(ndx, new PrimitiveInt(ndx)); + } + sum += ndx; + } + return sum; + } +} diff --git a/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/ArrayListPrimitiveInt.java b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/ArrayListPrimitiveInt.java new file mode 100644 index 00000000000..edf9fe3b300 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/ArrayListPrimitiveInt.java @@ -0,0 +1,678 @@ +/* + * 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 + * 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 org.openjdk.bench.valhalla.sandbox.corelibs; + +import java.util.Arrays; +import java.util.ConcurrentModificationException; +import java.util.Objects; +import java.util.NoSuchElementException; +import java.util.function.Consumer; +import java.util.function.IntConsumer; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; + +/** + * Resizable-array implementation like {@code ArrayList}. + */ +public class ArrayListPrimitiveInt +// extends AbstractList +// implements List, RandomAccess, Cloneable, java.io.Serializable +{ + @java.io.Serial + private static final long serialVersionUID = 8683452581122892189L; + + /** + * Default initial capacity. + */ + private static final int DEFAULT_CAPACITY = 10; + + /** + * Shared empty array instance used for empty instances. + */ + private static final PrimitiveInt[] EMPTY_ELEMENTDATA = {}; + + /** + * Shared empty array instance used for default sized empty instances. We + * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when + * first element is added. + */ + private static final PrimitiveInt[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new PrimitiveInt[0]; + + /** + * The array buffer into which the elements of the ArrayList are stored. + * The capacity of the ArrayList is the length of this array buffer. Any + * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA + * will be expanded to DEFAULT_CAPACITY when the first element is added. + */ + transient PrimitiveInt[] elementData; // non-private to simplify nested class access + + protected transient int modCount = 0; + + /** + * The size of the ArrayList (the number of elements it contains). + * + * @serial + */ + private int size; + + /** + * Constructs an empty list with the specified initial capacity. + * + * @param initialCapacity the initial capacity of the list + * @throws IllegalArgumentException if the specified initial capacity + * is negative + */ + public ArrayListPrimitiveInt(int initialCapacity) { + if (initialCapacity > 0) { + this.elementData = new PrimitiveInt[initialCapacity]; + } else if (initialCapacity == 0) { + this.elementData = EMPTY_ELEMENTDATA; + } else { + throw new IllegalArgumentException("Illegal Capacity: "+ + initialCapacity); + } + } + + /** + * Constructs an empty list with an initial capacity of ten. + */ + public ArrayListPrimitiveInt() { + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; + } + + /** + * Trims the capacity of this {@code ArrayList} instance to be the + * list's current size. An application can use this operation to minimize + * the storage of an {@code ArrayList} instance. + */ + public void trimToSize() { + modCount++; + if (size < elementData.length) { + elementData = (size == 0) + ? EMPTY_ELEMENTDATA + : (PrimitiveInt[])Arrays.copyOf(elementData, size); + } + } + + /** + * Increases the capacity of this {@code ArrayList} instance, if + * necessary, to ensure that it can hold at least the number of elements + * specified by the minimum capacity argument. + * + * @param minCapacity the desired minimum capacity + */ + public void ensureCapacity(int minCapacity) { + if (minCapacity > elementData.length + && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA + && minCapacity <= DEFAULT_CAPACITY)) { + modCount++; + grow(minCapacity); + } + } + + /** + * Increases the capacity to ensure that it can hold at least the + * number of elements specified by the minimum capacity argument. + * + * @param minCapacity the desired minimum capacity + * @throws OutOfMemoryError if minCapacity is less than zero + */ + private PrimitiveInt[] grow(int minCapacity) { + int oldCapacity = elementData.length; + if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { + int newCapacity = newLength(oldCapacity, + minCapacity - oldCapacity, /* minimum growth */ + oldCapacity >> 1 /* preferred growth */); + return elementData = (PrimitiveInt[])Arrays.copyOf(elementData, newCapacity); + } else { + return elementData = new PrimitiveInt[Math.max(DEFAULT_CAPACITY, minCapacity)]; + } + } + + private PrimitiveInt[] grow() { + return grow(size + 1); + } + + /** + * Returns the number of elements in this list. + * + * @return the number of elements in this list + */ + public int size() { + return size; + } + + /** + * Returns {@code true} if this list contains no elements. + * + * @return {@code true} if this list contains no elements + */ + public boolean isEmpty() { + return size == 0; + } + + /** + * Returns {@code true} if this list contains the specified element. + * More formally, returns {@code true} if and only if this list contains + * at least one element {@code e} such that + * {@code Objects.equals(o, e)}. + * + * @param o element whose presence in this list is to be tested + * @return {@code true} if this list contains the specified element + */ + public boolean contains(PrimitiveInt o) { + return indexOf(o) >= 0; + } + + /** + * Returns the index of the first occurrence of the specified element + * in this list, or -1 if this list does not contain the element. + * More formally, returns the lowest index {@code i} such that + * {@code Objects.equals(o, get(i))}, + * or -1 if there is no such index. + */ + public int indexOf(PrimitiveInt o) { + return indexOfRange(o, 0, size); + } + + int indexOfRange(PrimitiveInt o, int start, int end) { + PrimitiveInt[] es = elementData; + { + for (int i = start; i < end; i++) { + if (o == es[i]) { + return i; + } + } + } + return -1; + } + + /** + * Returns the index of the last occurrence of the specified element + * in this list, or -1 if this list does not contain the element. + * More formally, returns the highest index {@code i} such that + * {@code Objects.equals(o, get(i))}, + * or -1 if there is no such index. + */ + public int lastIndexOf(PrimitiveInt o) { + return lastIndexOfRange(o, 0, size); + } + + int lastIndexOfRange(PrimitiveInt o, int start, int end) { + PrimitiveInt[] es = elementData; + { + for (int i = end - 1; i >= start; i--) { + if (o == es[i]) { + return i; + } + } + } + return -1; + } + + /** + * Returns an array containing all of the elements in this list + * in proper sequence (from first to last element). + * + *

    The returned array will be "safe" in that no references to it are + * maintained by this list. (In other words, this method must allocate + * a new array). The caller is thus free to modify the returned array. + * + *

    This method acts as bridge between array-based and collection-based + * APIs. + * + * @return an array containing all of the elements in this list in + * proper sequence + */ + public PrimitiveInt[] toArray() { + return (PrimitiveInt[])Arrays.copyOf(elementData, size); + } + + /** + * Returns an array containing all of the elements in this list in proper + * sequence (from first to last element); the runtime type of the returned + * array is that of the specified array. If the list fits in the + * specified array, it is returned therein. Otherwise, a new array is + * allocated with the runtime type of the specified array and the size of + * this list. + * + *

    If the list fits in the specified array with room to spare + * (i.e., the array has more elements than the list), the element in + * the array immediately following the end of the collection is set to + * {@code null}. (This is useful in determining the length of the + * list only if the caller knows that the list does not contain + * any null elements.) + * + * @param a the array into which the elements of the list are to + * be stored, if it is big enough; otherwise, a new array of the + * same runtime type is allocated for this purpose. + * @return an array containing the elements of the list + * @throws ArrayStoreException if the runtime type of the specified array + * is not a supertype of the runtime type of every element in + * this list + * @throws NullPointerException if the specified array is null + */ +// @SuppressWarnings("unchecked") +// public T[] toArray(T[] a) { +// if (a.length < size) +// // Make a new array of a's runtime type, but my contents: +// return (T[]) Arrays.copyOf(elementData, size, a.getClass()); +// System.arraycopy(elementData, 0, a, 0, size); +// if (a.length > size) +// a[size] = null; +// return a; +// } + + // Positional Access Operations + + @SuppressWarnings("unchecked") + PrimitiveInt elementData(int index) { + return (PrimitiveInt) elementData[index]; + } + + @SuppressWarnings("unchecked") + static PrimitiveInt elementAt(PrimitiveInt[] es, int index) { + return es[index]; + } + + /** + * Returns the element at the specified position in this list. + * + * @param index index of the element to return + * @return the element at the specified position in this list + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public PrimitiveInt get(int index) { + Objects.checkIndex(index, size); + return elementData(index); + } + + /** + * Replaces the element at the specified position in this list with + * the specified element. + * + * @param index index of the element to replace + * @param element element to be stored at the specified position + * @return the element previously at the specified position + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public PrimitiveInt set(int index, PrimitiveInt element) { + Objects.checkIndex(index, size); + PrimitiveInt oldValue = elementData(index); + elementData[index] = element; + return oldValue; + } + + /** + * This helper method split out from add(int) to keep method + * bytecode size under 35 (the -XX:MaxInlineSize default value), + * which helps when add(int) is called in a C1-compiled loop. + */ + private void add(PrimitiveInt e, PrimitiveInt[] elementData, int s) { + if (s == elementData.length) + elementData = grow(); + elementData[s] = e; + size = s + 1; + } + + /** + * Appends the specified element to the end of this list. + * + * @param e element to be appended to this list + * @return {@code true} (as specified by {@link Collection#add}) + */ + public boolean add(PrimitiveInt e) { + modCount++; + add(e, elementData, size); + return true; + } + + /** + * Inserts the specified element at the specified position in this + * list. Shifts the element currently at that position (if any) and + * any subsequent elements to the right (adds one to their indices). + * + * @param index index at which the specified element is to be inserted + * @param element element to be inserted + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public void add(int index, PrimitiveInt element) { + rangeCheckForAdd(index); + modCount++; + final int s; + PrimitiveInt[] elementData; + if ((s = size) == (elementData = this.elementData).length) + elementData = grow(); + System.arraycopy(elementData, index, + elementData, index + 1, + s - index); + elementData[index] = element; + size = s + 1; + } + + /** + * Removes the element at the specified position in this list. + * Shifts any subsequent elements to the left (subtracts one from their + * indices). + * + * @param index the index of the element to be removed + * @return the element that was removed from the list + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public PrimitiveInt remove(int index) { + Objects.checkIndex(index, size); + final PrimitiveInt[] es = elementData; + + PrimitiveInt oldValue = (PrimitiveInt) es[index]; + fastRemove(es, index); + + return oldValue; + } + + /** + * {@inheritDoc} + */ + public boolean equals(Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof ArrayListPrimitiveInt)) { + return false; + } + + final int expectedModCount = modCount; + // ArrayList can be subclassed and given arbitrary behavior, but we can + // still deal with the common case where o is ArrayList precisely + boolean equal = equalsArrayList((ArrayListPrimitiveInt) o); + + checkForComodification(expectedModCount); + return equal; + } + + private boolean equalsArrayList(ArrayListPrimitiveInt other) { + final int otherModCount = other.modCount; + final int s = size; + boolean equal; + if (equal = (s == other.size)) { + final PrimitiveInt[] otherEs = other.elementData; + final PrimitiveInt[] es = elementData; + if (s > es.length || s > otherEs.length) { + throw new ConcurrentModificationException(); + } + for (int i = 0; i < s; i++) { + if (es[i] != otherEs[i]) { + equal = false; + break; + } + } + } + other.checkForComodification(otherModCount); + return equal; + } + + private void checkForComodification(final int expectedModCount) { + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + } + + /** + * {@inheritDoc} + */ + public int hashCode() { + int expectedModCount = modCount; + int hash = hashCodeRange(0, size); + checkForComodification(expectedModCount); + return hash; + } + + int hashCodeRange(int from, int to) { + final PrimitiveInt[] es = elementData; + if (to > es.length) { + throw new ConcurrentModificationException(); + } + int hashCode = 1; + for (int i = from; i < to; i++) { + PrimitiveInt e = es[i]; + hashCode = 31 * hashCode + e.hashCode(); + } + return hashCode; + } + + /** + * Removes the first occurrence of the specified element from this list, + * if it is present. If the list does not contain the element, it is + * unchanged. More formally, removes the element with the lowest index + * {@code i} such that + * {@code Objects.equals(o, get(i))} + * (if such an element exists). Returns {@code true} if this list + * contained the specified element (or equivalently, if this list + * changed as a result of the call). + * + * @param o element to be removed from this list, if present + * @return {@code true} if this list contained the specified element + */ + public boolean removeC(PrimitiveInt o) { + final PrimitiveInt[] es = elementData; + final int size = this.size; + int i = 0; + found: + { +// if (o == null) { +// for (; i < size; i++) +// if (es[i] == null) +// break found; +// } else + { + for (; i < size; i++) + if (o == es[i]) + break found; + } + return false; + } + fastRemove(es, i); + return true; + } + + /** + * Private remove method that skips bounds checking and does not + * return the value removed. + */ + private void fastRemove(PrimitiveInt[] es, int i) { + modCount++; + final int newSize; + if ((newSize = size - 1) > i) + System.arraycopy(es, i + 1, es, i, newSize - i); + es[size = newSize] = new PrimitiveInt(); + } + + /** + * Removes all of the elements from this list. The list will + * be empty after this call returns. + */ + public void clear() { + modCount++; + final PrimitiveInt[] es = elementData; + for (int to = size, i = size = 0; i < to; i++) + es[i] = new PrimitiveInt(); + } + + /** + * Removes from this list all of the elements whose index is between + * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive. + * Shifts any succeeding elements to the left (reduces their index). + * This call shortens the list by {@code (toIndex - fromIndex)} elements. + * (If {@code toIndex==fromIndex}, this operation has no effect.) + * + * @throws IndexOutOfBoundsException if {@code fromIndex} or + * {@code toIndex} is out of range + * ({@code fromIndex < 0 || + * toIndex > size() || + * toIndex < fromIndex}) + */ + protected void removeRange(int fromIndex, int toIndex) { + if (fromIndex > toIndex) { + throw new IndexOutOfBoundsException( + outOfBoundsMsg(fromIndex, toIndex)); + } + modCount++; + shiftTailOverGap(elementData, fromIndex, toIndex); + } + + /** Erases the gap from lo to hi, by sliding down following elements. */ + private void shiftTailOverGap(PrimitiveInt[] es, int lo, int hi) { + System.arraycopy(es, hi, es, lo, size - hi); + for (int to = size, i = (size -= hi - lo); i < to; i++) + es[i] = new PrimitiveInt(); + } + + /** + * A version of rangeCheck used by add and addAll. + */ + private void rangeCheckForAdd(int index) { + if (index > size || index < 0) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); + } + + /** + * Constructs an IndexOutOfBoundsException detail message. + * Of the many possible refactorings of the error handling code, + * this "outlining" performs best with both server and client VMs. + */ + private String outOfBoundsMsg(int index) { + return "Index: "+index+", Size: "+size; + } + + /** + * A version used in checking (fromIndex > toIndex) condition + */ + private static String outOfBoundsMsg(int fromIndex, int toIndex) { + return "From Index: " + fromIndex + " > To Index: " + toIndex; + } + + /** + * Returns an iterator over the elements in this list in proper sequence. + * + *

    The returned iterator is fail-fast. + * + * @return an iterator over the elements in this list in proper sequence + */ + public PrimitiveIntItr iterator() { + return new PrimitiveIntItr(); + } + + /** + * An optimized version of AbstractList.Itr + */ + private class PrimitiveIntItr { + int cursor; // index of next element to return + int lastRet = -1; // index of last element returned; -1 if no such + int expectedModCount = modCount; + + // prevent creating a synthetic constructor + PrimitiveIntItr() {} + + public boolean hasNext() { + return cursor != size; + } + + @SuppressWarnings("unchecked") + public PrimitiveInt next() { + checkForComodification(); + int i = cursor; + if (i >= size) + throw new NoSuchElementException(); + PrimitiveInt[] elementData = ArrayListPrimitiveInt.this.elementData; + if (i >= elementData.length) + throw new ConcurrentModificationException(); + cursor = i + 1; + return (PrimitiveInt) elementData[lastRet = i]; + } + + public void remove() { + if (lastRet < 0) + throw new IllegalStateException(); + checkForComodification(); + + try { + ArrayListPrimitiveInt.this.remove(lastRet); + cursor = lastRet; + lastRet = -1; + expectedModCount = modCount; + } catch (IndexOutOfBoundsException ex) { + throw new ConcurrentModificationException(); + } + } + + public void forEachRemaining(Consumer action) { + Objects.requireNonNull(action); + final int size = ArrayListPrimitiveInt.this.size; + int i = cursor; + if (i < size) { + final PrimitiveInt[] es = elementData; + if (i >= es.length) + throw new ConcurrentModificationException(); + for (; i < size && modCount == expectedModCount; i++) + action.accept(elementAt(es, i)); + // update once at end to reduce heap write traffic + cursor = i; + lastRet = i - 1; + checkForComodification(); + } + } + + final void checkForComodification() { + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + } + } + + static final int MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8; + + static int newLength(int oldLength, int minGrowth, int prefGrowth) { + // assert oldLength >= 0 + // assert minGrowth > 0 + + int newLength = Math.max(minGrowth, prefGrowth) + oldLength; + if (newLength - MAX_ARRAY_LENGTH <= 0) { + return newLength; + } + return hugeLength(oldLength, minGrowth); + } + + private static int hugeLength(int oldLength, int minGrowth) { + int minLength = oldLength + minGrowth; + if (minLength < 0) { // overflow + throw new OutOfMemoryError("Required array length too large"); + } + if (minLength <= MAX_ARRAY_LENGTH) { + return MAX_ARRAY_LENGTH; + } + return Integer.MAX_VALUE; + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/InlineCursor.java b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/InlineCursor.java new file mode 100644 index 00000000000..64bc1fd4c68 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/InlineCursor.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + */ + +package org.openjdk.bench.valhalla.sandbox.corelibs.corelibs; + +import java.util.ConcurrentModificationException; +import java.util.NoSuchElementException; + +/** + * An inline cursor is a reference to an existing or non-existent element + * of a collection. + *

    + * Cursor values are immutable, the reference to an element + * does not change but the state of the collection can change + * so the element is no longer accessible. + * Calling {@link #get()} throws a {@link ConcurrentModificationException}. + * Iterating through a Collection proceeds by creating new Cursor + * from the Collection or advancing to the next or retreating to previous elements. + * Advancing past the end of the Collection or retreating before the beginning + * results in Cursor values that are non-existent. + * A Cursor for an empty Collection does not refer to an element and + * throws {@link NoSuchElementException}. + * Modifications to the Collection invalidate every Cursor that was created + * before the modification. + * The typical traversal pattern is: + *

    {@code
    + *  Collection c = ...;
    + *  for (var cursor = c.cursor(); cursor.exists(); cursor = cursor.advance()) {
    + *      var el = cursor.get();
    + *  }
    + * }
    + * 
    + *

    + * Cursors can be used to {@link #remove()} remove an element from the collection. + * Removing an element modifies the collection making that cursor invalid. + * The cursor returned from the {@link #remove()} method is a placeholder + * for the position, the element occupied, between the next and previous elements. + * It can be moved to the next or previous element to continue the iteration. + *

    + * The typical traversal and remove pattern follows; when an element is + * removed, the cursor returned from the remove is used to continue the iteration: + *

    {@code
    + *  Collection c = ...;
    + *  for (var cursor = c.cursor(); cursor.exists(); cursor = cursor.advance()) {
    + *      var el = cursor.get();
    + *      if (el.equals(...)) {
    + *          cursor = cursor.remove();
    + *      }
    + *  }
    + * }
    + * 
    + *

    + * @param the type of the element. + */ +public interface InlineCursor { + /** + * Return true if the Cursor refers to an element. + * + * If the collection has been modified since the Cursor was created + * the element can not be known to exist. + * This method does not throw {@link ConcurrentModificationException} + * if the collection has been modified but returns false. + * + * @return true if this Cursor refers to an element in the collection and + * the collection has not been modified since the cursor was created; + * false otherwise + */ + boolean exists(); + + /** + * Return a Cursor for the next element after the current element. + * If there is no element following this element the returned + * Cursor will be non-existent. To wit: {@code Cursor.exists() == false}. + * + * @return return a cursor for the next element after this element + * @throws ConcurrentModificationException if the collection + * has been modified since this Cursor was created + */ + InlineCursor advance(); + + /** + * Return the current element referred to by the Cursor. + * + * The behavior must be consistent with {@link #exists()} + * as long as the collection has not been modified. + * + * @return return the element in the collection if the collection + * has not been modified since the cursor was created + * @throws NoSuchElementException if the referenced element does not exist + * or no longer exists + * @throws ConcurrentModificationException if the collection + * has been modified since this Cursor was created + */ + T get(); +} diff --git a/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/PrimitiveInt.java b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/PrimitiveInt.java new file mode 100644 index 00000000000..9e58d41c2a4 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/PrimitiveInt.java @@ -0,0 +1,44 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.bench.valhalla.sandbox.corelibs; + +public value class PrimitiveInt { + int value; + + PrimitiveInt() { + this.value = 0; + } + + PrimitiveInt(int value) { + this.value = value; + } + + int value() { + return value; + } + + public String toString() { + return Integer.toString(value); + } +} diff --git a/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/XArrayList.java b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/XArrayList.java new file mode 100644 index 00000000000..76273801d1f --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/XArrayList.java @@ -0,0 +1,1908 @@ +/* + * 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 + * 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 org.openjdk.bench.valhalla.sandbox.corelibs.corelibs; + +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.RandomAccess; +import java.util.Spliterator; + +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; +//import jdk.internal.access.SharedSecrets; +//import jdk.internal.util.ArraysSupport; + +/** + * Resizable-array implementation of the {@code List} interface. Implements + * all optional list operations, and permits all elements, including + * {@code null}. In addition to implementing the {@code List} interface, + * this class provides methods to manipulate the size of the array that is + * used internally to store the list. (This class is roughly equivalent to + * {@code Vector}, except that it is unsynchronized.) + * + *

    The {@code size}, {@code isEmpty}, {@code get}, {@code set}, + * {@code iterator}, and {@code listIterator} operations run in constant + * time. The {@code add} operation runs in amortized constant time, + * that is, adding n elements requires O(n) time. All of the other operations + * run in linear time (roughly speaking). The constant factor is low compared + * to that for the {@code LinkedList} implementation. + * + *

    Each {@code XArrayList} instance has a capacity. The capacity is + * the size of the array used to store the elements in the list. It is always + * at least as large as the list size. As elements are added to an XArrayList, + * its capacity grows automatically. The details of the growth policy are not + * specified beyond the fact that adding an element has constant amortized + * time cost. + * + *

    An application can increase the capacity of an {@code XArrayList} instance + * before adding a large number of elements using the {@code ensureCapacity} + * operation. This may reduce the amount of incremental reallocation. + * + *

    Note that this implementation is not synchronized. + * If multiple threads access an {@code XArrayList} instance concurrently, + * and at least one of the threads modifies the list structurally, it + * must be synchronized externally. (A structural modification is + * any operation that adds or deletes one or more elements, or explicitly + * resizes the backing array; merely setting the value of an element is not + * a structural modification.) This is typically accomplished by + * synchronizing on some object that naturally encapsulates the list. + * + * If no such object exists, the list should be "wrapped" using the + * {@link Collections#synchronizedList Collections.synchronizedList} + * method. This is best done at creation time, to prevent accidental + * unsynchronized access to the list:

    + *   List list = Collections.synchronizedList(new XArrayList(...));
    + * + *

    + * The iterators returned by this class's {@link #iterator() iterator} and + * {@link #listIterator(int) listIterator} methods are fail-fast: + * if the list is structurally modified at any time after the iterator is + * created, in any way except through the iterator's own + * {@link ListIterator#remove() remove} or + * {@link ListIterator#add(Object) add} methods, the iterator will throw a + * {@link ConcurrentModificationException}. Thus, in the face of + * concurrent modification, the iterator fails quickly and cleanly, rather + * than risking arbitrary, non-deterministic behavior at an undetermined + * time in the future. + * + *

    Note that the fail-fast behavior of an iterator cannot be guaranteed + * as it is, generally speaking, impossible to make any hard guarantees in the + * presence of unsynchronized concurrent modification. Fail-fast iterators + * throw {@code ConcurrentModificationException} on a best-effort basis. + * Therefore, it would be wrong to write a program that depended on this + * exception for its correctness: the fail-fast behavior of iterators + * should be used only to detect bugs. + * + *

    This class is a member of the + * + * Java Collections Framework. + * + * @param the type of elements in this list + * + * @author Josh Bloch + * @author Neal Gafter + * @see Collection + * @see List + * @see LinkedList + * @see Vector + * @since 1.2 + */ +public class XArrayList extends AbstractList + implements List, RandomAccess, Cloneable, java.io.Serializable +{ + private static final long serialVersionUID = 8683452581122892189L; + + /** + * Default initial capacity. + */ + private static final int DEFAULT_CAPACITY = 10; + + /** + * Shared empty array instance used for empty instances. + */ + private static final Object[] EMPTY_ELEMENTDATA = {}; + + /** + * Shared empty array instance used for default sized empty instances. We + * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when + * first element is added. + */ + private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; + + /** + * The array buffer into which the elements of the ArrayList are stored. + * The capacity of the ArrayList is the length of this array buffer. Any + * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA + * will be expanded to DEFAULT_CAPACITY when the first element is added. + */ + transient Object[] elementData; // non-private to simplify nested class access + + /** + * The size of the ArrayList (the number of elements it contains). + * + * @serial + */ + int size; + + /** + * Constructs an empty list with the specified initial capacity. + * + * @param initialCapacity the initial capacity of the list + * @throws IllegalArgumentException if the specified initial capacity + * is negative + */ + public XArrayList(int initialCapacity) { + if (initialCapacity > 0) { + this.elementData = new Object[initialCapacity]; + } else if (initialCapacity == 0) { + this.elementData = EMPTY_ELEMENTDATA; + } else { + throw new IllegalArgumentException("Illegal Capacity: "+ + initialCapacity); + } + } + + /** + * Constructs an empty list with an initial capacity of ten. + */ + public XArrayList() { + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; + } + + /** + * Constructs a list containing the elements of the specified + * collection, in the order they are returned by the collection's + * iterator. + * + * @param c the collection whose elements are to be placed into this list + * @throws NullPointerException if the specified collection is null + */ + public XArrayList(Collection c) { + elementData = c.toArray(); + if ((size = elementData.length) != 0) { + // defend against c.toArray (incorrectly) not returning Object[] + // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652) + if (elementData.getClass() != Object[].class) + elementData = Arrays.copyOf(elementData, size, Object[].class); + } else { + // replace with empty array. + this.elementData = EMPTY_ELEMENTDATA; + } + } + + /** + * Trims the capacity of this {@code XArrayList} instance to be the + * list's current size. An application can use this operation to minimize + * the storage of an {@code XArrayList} instance. + */ + public void trimToSize() { + modCount++; + if (size < elementData.length) { + elementData = (size == 0) + ? EMPTY_ELEMENTDATA + : Arrays.copyOf(elementData, size); + } + } + + /** + * Increases the capacity of this {@code XArrayList} instance, if + * necessary, to ensure that it can hold at least the number of elements + * specified by the minimum capacity argument. + * + * @param minCapacity the desired minimum capacity + */ + public void ensureCapacity(int minCapacity) { + if (minCapacity > elementData.length + && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA + && minCapacity <= DEFAULT_CAPACITY)) { + modCount++; + grow(minCapacity); + } + } + + /** + * Increases the capacity to ensure that it can hold at least the + * number of elements specified by the minimum capacity argument. + * + * @param minCapacity the desired minimum capacity + * @throws OutOfMemoryError if minCapacity is less than zero + */ + private Object[] grow(int minCapacity) { + int oldCapacity = elementData.length; + if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { + int newCapacity = newLength(oldCapacity, + minCapacity - oldCapacity, /* minimum growth */ + oldCapacity >> 1 /* preferred growth */); + return elementData = Arrays.copyOf(elementData, newCapacity); + } else { + return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)]; + } + } + + private Object[] grow() { + return grow(size + 1); + } + + /** + * Returns the number of elements in this list. + * + * @return the number of elements in this list + */ + public int size() { + return size; + } + + /** + * Returns {@code true} if this list contains no elements. + * + * @return {@code true} if this list contains no elements + */ + public boolean isEmpty() { + return size == 0; + } + + /** + * Returns {@code true} if this list contains the specified element. + * More formally, returns {@code true} if and only if this list contains + * at least one element {@code e} such that + * {@code Objects.equals(o, e)}. + * + * @param o element whose presence in this list is to be tested + * @return {@code true} if this list contains the specified element + */ + public boolean contains(Object o) { + return indexOf(o) >= 0; + } + + /** + * Returns the index of the first occurrence of the specified element + * in this list, or -1 if this list does not contain the element. + * More formally, returns the lowest index {@code i} such that + * {@code Objects.equals(o, get(i))}, + * or -1 if there is no such index. + */ + public int indexOf(Object o) { + return indexOfRange(o, 0, size); + } + + int indexOfRange(Object o, int start, int end) { + Object[] es = elementData; + if (o == null) { + for (int i = start; i < end; i++) { + if (es[i] == null) { + return i; + } + } + } else { + for (int i = start; i < end; i++) { + if (o.equals(es[i])) { + return i; + } + } + } + return -1; + } + + /** + * Returns the index of the last occurrence of the specified element + * in this list, or -1 if this list does not contain the element. + * More formally, returns the highest index {@code i} such that + * {@code Objects.equals(o, get(i))}, + * or -1 if there is no such index. + */ + public int lastIndexOf(Object o) { + return lastIndexOfRange(o, 0, size); + } + + int lastIndexOfRange(Object o, int start, int end) { + Object[] es = elementData; + if (o == null) { + for (int i = end - 1; i >= start; i--) { + if (es[i] == null) { + return i; + } + } + } else { + for (int i = end - 1; i >= start; i--) { + if (o.equals(es[i])) { + return i; + } + } + } + return -1; + } + + /** + * Returns a shallow copy of this {@code XArrayList} instance. (The + * elements themselves are not copied.) + * + * @return a clone of this {@code XArrayList} instance + */ + public Object clone() { + try { + XArrayList v = (XArrayList) super.clone(); + v.elementData = Arrays.copyOf(elementData, size); + v.modCount = 0; + return v; + } catch (CloneNotSupportedException e) { + // this shouldn't happen, since we are Cloneable + throw new InternalError(e); + } + } + + /** + * Returns an array containing all of the elements in this list + * in proper sequence (from first to last element). + * + *

    The returned array will be "safe" in that no references to it are + * maintained by this list. (In other words, this method must allocate + * a new array). The caller is thus free to modify the returned array. + * + *

    This method acts as bridge between array-based and collection-based + * APIs. + * + * @return an array containing all of the elements in this list in + * proper sequence + */ + public Object[] toArray() { + return Arrays.copyOf(elementData, size); + } + + /** + * Returns an array containing all of the elements in this list in proper + * sequence (from first to last element); the runtime type of the returned + * array is that of the specified array. If the list fits in the + * specified array, it is returned therein. Otherwise, a new array is + * allocated with the runtime type of the specified array and the size of + * this list. + * + *

    If the list fits in the specified array with room to spare + * (i.e., the array has more elements than the list), the element in + * the array immediately following the end of the collection is set to + * {@code null}. (This is useful in determining the length of the + * list only if the caller knows that the list does not contain + * any null elements.) + * + * @param a the array into which the elements of the list are to + * be stored, if it is big enough; otherwise, a new array of the + * same runtime type is allocated for this purpose. + * @return an array containing the elements of the list + * @throws ArrayStoreException if the runtime type of the specified array + * is not a supertype of the runtime type of every element in + * this list + * @throws NullPointerException if the specified array is null + */ + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + if (a.length < size) + // Make a new array of a's runtime type, but my contents: + return (T[]) Arrays.copyOf(elementData, size, a.getClass()); + System.arraycopy(elementData, 0, a, 0, size); + if (a.length > size) + a[size] = null; + return a; + } + + // Positional Access Operations + + @SuppressWarnings("unchecked") + E elementData(int index) { + return (E) elementData[index]; + } + + @SuppressWarnings("unchecked") + static E elementAt(Object[] es, int index) { + return (E) es[index]; + } + + /** + * Returns the element at the specified position in this list. + * + * @param index index of the element to return + * @return the element at the specified position in this list + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public E get(int index) { + Objects.checkIndex(index, size); + return elementData(index); + } + + /** + * Replaces the element at the specified position in this list with + * the specified element. + * + * @param index index of the element to replace + * @param element element to be stored at the specified position + * @return the element previously at the specified position + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public E set(int index, E element) { + Objects.checkIndex(index, size); + E oldValue = elementData(index); + elementData[index] = element; + return oldValue; + } + + /** + * This helper method split out from add(E) to keep method + * bytecode size under 35 (the -XX:MaxInlineSize default value), + * which helps when add(E) is called in a C1-compiled loop. + */ + private void add(E e, Object[] elementData, int s) { + if (s == elementData.length) + elementData = grow(); + elementData[s] = e; + size = s + 1; + } + + /** + * Appends the specified element to the end of this list. + * + * @param e element to be appended to this list + * @return {@code true} (as specified by {@link Collection#add}) + */ + public boolean add(E e) { + modCount++; + add(e, elementData, size); + return true; + } + + /** + * Inserts the specified element at the specified position in this + * list. Shifts the element currently at that position (if any) and + * any subsequent elements to the right (adds one to their indices). + * + * @param index index at which the specified element is to be inserted + * @param element element to be inserted + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public void add(int index, E element) { + rangeCheckForAdd(index); + modCount++; + final int s; + Object[] elementData; + if ((s = size) == (elementData = this.elementData).length) + elementData = grow(); + System.arraycopy(elementData, index, + elementData, index + 1, + s - index); + elementData[index] = element; + size = s + 1; + } + + /** + * Removes the element at the specified position in this list. + * Shifts any subsequent elements to the left (subtracts one from their + * indices). + * + * @param index the index of the element to be removed + * @return the element that was removed from the list + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public E remove(int index) { + Objects.checkIndex(index, size); + final Object[] es = elementData; + + @SuppressWarnings("unchecked") E oldValue = (E) es[index]; + fastRemove(es, index); + + return oldValue; + } + + /** + * {@inheritDoc} + */ + public boolean equals(Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof List)) { + return false; + } + + final int expectedModCount = modCount; + // XArrayList can be subclassed and given arbitrary behavior, but we can + // still deal with the common case where o is XArrayList precisely + boolean equal = (o.getClass() == XArrayList.class) + ? equalsArrayList((XArrayList) o) + : equalsRange((List) o, 0, size); + + checkForComodification(expectedModCount); + return equal; + } + + boolean equalsRange(List other, int from, int to) { + final Object[] es = elementData; + if (to > es.length) { + throw new ConcurrentModificationException(); + } + var oit = other.iterator(); + for (; from < to; from++) { + if (!oit.hasNext() || !Objects.equals(es[from], oit.next())) { + return false; + } + } + return !oit.hasNext(); + } + + private boolean equalsArrayList(XArrayList other) { + final int otherModCount = other.modCount; + final int s = size; + boolean equal; + if (equal = (s == other.size)) { + final Object[] otherEs = other.elementData; + final Object[] es = elementData; + if (s > es.length || s > otherEs.length) { + throw new ConcurrentModificationException(); + } + for (int i = 0; i < s; i++) { + if (!Objects.equals(es[i], otherEs[i])) { + equal = false; + break; + } + } + } + other.checkForComodification(otherModCount); + return equal; + } + + private void checkForComodification(final int expectedModCount) { + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + } + + /** + * {@inheritDoc} + */ + public int hashCode() { + int expectedModCount = modCount; + int hash = hashCodeRange(0, size); + checkForComodification(expectedModCount); + return hash; + } + + int hashCodeRange(int from, int to) { + final Object[] es = elementData; + if (to > es.length) { + throw new ConcurrentModificationException(); + } + int hashCode = 1; + for (int i = from; i < to; i++) { + Object e = es[i]; + hashCode = 31 * hashCode + (e == null ? 0 : e.hashCode()); + } + return hashCode; + } + + /** + * Removes the first occurrence of the specified element from this list, + * if it is present. If the list does not contain the element, it is + * unchanged. More formally, removes the element with the lowest index + * {@code i} such that + * {@code Objects.equals(o, get(i))} + * (if such an element exists). Returns {@code true} if this list + * contained the specified element (or equivalently, if this list + * changed as a result of the call). + * + * @param o element to be removed from this list, if present + * @return {@code true} if this list contained the specified element + */ + public boolean remove(Object o) { + final Object[] es = elementData; + final int size = this.size; + int i = 0; + found: { + if (o == null) { + for (; i < size; i++) + if (es[i] == null) + break found; + } else { + for (; i < size; i++) + if (o.equals(es[i])) + break found; + } + return false; + } + fastRemove(es, i); + return true; + } + + /** + * Private remove method that skips bounds checking and does not + * return the value removed. + */ + private void fastRemove(Object[] es, int i) { + modCount++; + final int newSize; + if ((newSize = size - 1) > i) + System.arraycopy(es, i + 1, es, i, newSize - i); + es[size = newSize] = null; + } + + /** + * Removes all of the elements from this list. The list will + * be empty after this call returns. + */ + public void clear() { + modCount++; + final Object[] es = elementData; + for (int to = size, i = size = 0; i < to; i++) + es[i] = null; + } + + /** + * Appends all of the elements in the specified collection to the end of + * this list, in the order that they are returned by the + * specified collection's Iterator. The behavior of this operation is + * undefined if the specified collection is modified while the operation + * is in progress. (This implies that the behavior of this call is + * undefined if the specified collection is this list, and this + * list is nonempty.) + * + * @param c collection containing elements to be added to this list + * @return {@code true} if this list changed as a result of the call + * @throws NullPointerException if the specified collection is null + */ + public boolean addAll(Collection c) { + Object[] a = c.toArray(); + modCount++; + int numNew = a.length; + if (numNew == 0) + return false; + Object[] elementData; + final int s; + if (numNew > (elementData = this.elementData).length - (s = size)) + elementData = grow(s + numNew); + System.arraycopy(a, 0, elementData, s, numNew); + size = s + numNew; + return true; + } + + /** + * Inserts all of the elements in the specified collection into this + * list, starting at the specified position. Shifts the element + * currently at that position (if any) and any subsequent elements to + * the right (increases their indices). The new elements will appear + * in the list in the order that they are returned by the + * specified collection's iterator. + * + * @param index index at which to insert the first element from the + * specified collection + * @param c collection containing elements to be added to this list + * @return {@code true} if this list changed as a result of the call + * @throws IndexOutOfBoundsException {@inheritDoc} + * @throws NullPointerException if the specified collection is null + */ + public boolean addAll(int index, Collection c) { + rangeCheckForAdd(index); + + Object[] a = c.toArray(); + modCount++; + int numNew = a.length; + if (numNew == 0) + return false; + Object[] elementData; + final int s; + if (numNew > (elementData = this.elementData).length - (s = size)) + elementData = grow(s + numNew); + + int numMoved = s - index; + if (numMoved > 0) + System.arraycopy(elementData, index, + elementData, index + numNew, + numMoved); + System.arraycopy(a, 0, elementData, index, numNew); + size = s + numNew; + return true; + } + + /** + * Removes from this list all of the elements whose index is between + * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive. + * Shifts any succeeding elements to the left (reduces their index). + * This call shortens the list by {@code (toIndex - fromIndex)} elements. + * (If {@code toIndex==fromIndex}, this operation has no effect.) + * + * @throws IndexOutOfBoundsException if {@code fromIndex} or + * {@code toIndex} is out of range + * ({@code fromIndex < 0 || + * toIndex > size() || + * toIndex < fromIndex}) + */ + protected void removeRange(int fromIndex, int toIndex) { + if (fromIndex > toIndex) { + throw new IndexOutOfBoundsException( + outOfBoundsMsg(fromIndex, toIndex)); + } + modCount++; + shiftTailOverGap(elementData, fromIndex, toIndex); + } + + /** Erases the gap from lo to hi, by sliding down following elements. */ + private void shiftTailOverGap(Object[] es, int lo, int hi) { + System.arraycopy(es, hi, es, lo, size - hi); + for (int to = size, i = (size -= hi - lo); i < to; i++) + es[i] = null; + } + + /** + * A version of rangeCheck used by add and addAll. + */ + private void rangeCheckForAdd(int index) { + if (index > size || index < 0) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); + } + + /** + * Constructs an IndexOutOfBoundsException detail message. + * Of the many possible refactorings of the error handling code, + * this "outlining" performs best with both server and client VMs. + */ + private String outOfBoundsMsg(int index) { + return "Index: "+index+", Size: "+size; + } + + /** + * A version used in checking (fromIndex > toIndex) condition + */ + private static String outOfBoundsMsg(int fromIndex, int toIndex) { + return "From Index: " + fromIndex + " > To Index: " + toIndex; + } + + /** + * Removes from this list all of its elements that are contained in the + * specified collection. + * + * @param c collection containing elements to be removed from this list + * @return {@code true} if this list changed as a result of the call + * @throws ClassCastException if the class of an element of this list + * is incompatible with the specified collection + * (optional) + * @throws NullPointerException if this list contains a null element and the + * specified collection does not permit null elements + * (optional), + * or if the specified collection is null + * @see Collection#contains(Object) + */ + public boolean removeAll(Collection c) { + return batchRemove(c, false, 0, size); + } + + /** + * Retains only the elements in this list that are contained in the + * specified collection. In other words, removes from this list all + * of its elements that are not contained in the specified collection. + * + * @param c collection containing elements to be retained in this list + * @return {@code true} if this list changed as a result of the call + * @throws ClassCastException if the class of an element of this list + * is incompatible with the specified collection + * (optional) + * @throws NullPointerException if this list contains a null element and the + * specified collection does not permit null elements + * (optional), + * or if the specified collection is null + * @see Collection#contains(Object) + */ + public boolean retainAll(Collection c) { + return batchRemove(c, true, 0, size); + } + + boolean batchRemove(Collection c, boolean complement, + final int from, final int end) { + Objects.requireNonNull(c); + final Object[] es = elementData; + int r; + // Optimize for initial run of survivors + for (r = from;; r++) { + if (r == end) + return false; + if (c.contains(es[r]) != complement) + break; + } + int w = r++; + try { + for (Object e; r < end; r++) + if (c.contains(e = es[r]) == complement) + es[w++] = e; + } catch (Throwable ex) { + // Preserve behavioral compatibility with AbstractCollection, + // even if c.contains() throws. + System.arraycopy(es, r, es, w, end - r); + w += end - r; + throw ex; + } finally { + modCount += end - w; + shiftTailOverGap(es, w, end); + } + return true; + } + + /** + * Saves the state of the {@code XArrayList} instance to a stream + * (that is, serializes it). + * + * @param s the stream + * @throws java.io.IOException if an I/O error occurs + * @serialData The length of the array backing the {@code XArrayList} + * instance is emitted (int), followed by all of its elements + * (each an {@code Object}) in the proper order. + */ + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + // Write out element count, and any hidden stuff + int expectedModCount = modCount; + s.defaultWriteObject(); + + // Write out size as capacity for behavioral compatibility with clone() + s.writeInt(size); + + // Write out all elements in the proper order. + for (int i=0; i

    The returned list iterator is fail-fast. + * + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public ListIterator listIterator(int index) { + rangeCheckForAdd(index); + return new ListItr(index); + } + + /** + * Returns a list iterator over the elements in this list (in proper + * sequence). + * + *

    The returned list iterator is fail-fast. + * + * @see #listIterator(int) + */ + public ListIterator listIterator() { + return new ListItr(0); + } + + /** + * Returns an iterator over the elements in this list in proper sequence. + * + *

    The returned iterator is fail-fast. + * + * @return an iterator over the elements in this list in proper sequence + */ + public Iterator iterator() { + return new Itr(); + } + + /** + * An optimized version of AbstractList.Itr + */ + private class Itr implements Iterator { + int cursor; // index of next element to return + int lastRet = -1; // index of last element returned; -1 if no such + int expectedModCount = modCount; + + // prevent creating a synthetic constructor + Itr() {} + + public boolean hasNext() { + return cursor != size; + } + + @SuppressWarnings("unchecked") + public E next() { + checkForComodification(); + int i = cursor; + if (i >= size) + throw new NoSuchElementException(); + Object[] elementData = XArrayList.this.elementData; + if (i >= elementData.length) + throw new ConcurrentModificationException(); + cursor = i + 1; + return (E) elementData[lastRet = i]; + } + + public void remove() { + if (lastRet < 0) + throw new IllegalStateException(); + checkForComodification(); + + try { + XArrayList.this.remove(lastRet); + cursor = lastRet; + lastRet = -1; + expectedModCount = modCount; + } catch (IndexOutOfBoundsException ex) { + throw new ConcurrentModificationException(); + } + } + + @Override + public void forEachRemaining(Consumer action) { + Objects.requireNonNull(action); + final int size = XArrayList.this.size; + int i = cursor; + if (i < size) { + final Object[] es = elementData; + if (i >= es.length) + throw new ConcurrentModificationException(); + for (; i < size && modCount == expectedModCount; i++) + action.accept(elementAt(es, i)); + // update once at end to reduce heap write traffic + cursor = i; + lastRet = i - 1; + checkForComodification(); + } + } + + final void checkForComodification() { + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + } + } + + /** + * An optimized version of AbstractList.ListItr + */ + private class ListItr extends Itr implements ListIterator { + ListItr(int index) { + super(); + cursor = index; + } + + public boolean hasPrevious() { + return cursor != 0; + } + + public int nextIndex() { + return cursor; + } + + public int previousIndex() { + return cursor - 1; + } + + @SuppressWarnings("unchecked") + public E previous() { + checkForComodification(); + int i = cursor - 1; + if (i < 0) + throw new NoSuchElementException(); + Object[] elementData = XArrayList.this.elementData; + if (i >= elementData.length) + throw new ConcurrentModificationException(); + cursor = i; + return (E) elementData[lastRet = i]; + } + + public void set(E e) { + if (lastRet < 0) + throw new IllegalStateException(); + checkForComodification(); + + try { + XArrayList.this.set(lastRet, e); + } catch (IndexOutOfBoundsException ex) { + throw new ConcurrentModificationException(); + } + } + + public void add(E e) { + checkForComodification(); + + try { + int i = cursor; + XArrayList.this.add(i, e); + cursor = i + 1; + lastRet = -1; + expectedModCount = modCount; + } catch (IndexOutOfBoundsException ex) { + throw new ConcurrentModificationException(); + } + } + } + + /** + * Return a new cursor for this XArrayList. + * @return a cursor + */ + public InlineCursor cursor() { + return new AListCursor<>(0); + } + + /** + * Create an inline cursor for this XArrayList. + */ + private value class AListCursor implements InlineCursor { + // Inner class field 'this' is initialized + int index; + int expectedModCount; + + /** + * Create a new Cursor for this XArrayList. + * + * @param cursor index + */ + public AListCursor(int cursor) { + this.index = cursor; + this.expectedModCount = XArrayList.this.modCount; + } + + @Override + public boolean exists() { + return index < XArrayList.this.size; + } + + @SuppressWarnings("unchecked") + public E get() { + if (exists()) { + checkForComodification(); + try { + return (E) XArrayList.this.elementData[index]; + } catch (ArrayIndexOutOfBoundsException aioobe) { + throw new ConcurrentModificationException(); + } + } + throw new NoSuchElementException(); + } + + @Override + public AListCursor advance() { + // new Cursor will have a current expectedModCount + // TBD: Saturate index? So calling adv, adv, adv, prev == last + return new AListCursor<>(Math.min(index + 1, size)); + } + + final void checkForComodification() { + if (XArrayList.this.modCount != expectedModCount) + throw new ConcurrentModificationException(); + } + } + + /** + * Returns a iterator (Using an InlineCursor) over the elements in this list in proper sequence. + * + *

    The returned iterator is fail-fast. + * + * @return an iterator over the elements in this list in proper sequence + */ + public Iterator iteratorCurs() { + return new CurItr(); + } + + /** + * Iterate using a Cursor. + */ + private class CurItr implements Iterator { + AListCursor cursor; + AListCursor lastRet; + + // prevent creating a synthetic constructor + CurItr() { + this.cursor = new AListCursor(0); + this.lastRet = this.cursor; + } + + public boolean hasNext() { + return cursor.advance().exists(); + } + + @SuppressWarnings("unchecked") + public E next() { + E val = cursor.get(); + lastRet = cursor; + cursor = cursor.advance(); + return val; + } + + @Override + public void forEachRemaining(Consumer action) { + Objects.requireNonNull(action); + + AListCursor cur = cursor; + while (cur.exists()) { + E val = cur.get(); + action.accept(val); + cur = cur.advance(); + } + cursor = cur; + } + + public String toString() { + return "cur: " + cursor; + } + } + + + /** + * Returns a view of the portion of this list between the specified + * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive. (If + * {@code fromIndex} and {@code toIndex} are equal, the returned list is + * empty.) The returned list is backed by this list, so non-structural + * changes in the returned list are reflected in this list, and vice-versa. + * The returned list supports all of the optional list operations. + * + *

    This method eliminates the need for explicit range operations (of + * the sort that commonly exist for arrays). Any operation that expects + * a list can be used as a range operation by passing a subList view + * instead of a whole list. For example, the following idiom + * removes a range of elements from a list: + *

    +     *      list.subList(from, to).clear();
    +     * 
    + * Similar idioms may be constructed for {@link #indexOf(Object)} and + * {@link #lastIndexOf(Object)}, and all of the algorithms in the + * {@link Collections} class can be applied to a subList. + * + *

    The semantics of the list returned by this method become undefined if + * the backing list (i.e., this list) is structurally modified in + * any way other than via the returned list. (Structural modifications are + * those that change the size of this list, or otherwise perturb it in such + * a fashion that iterations in progress may yield incorrect results.) + * + * @throws IndexOutOfBoundsException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} + */ + public List subList(int fromIndex, int toIndex) { + subListRangeCheck(fromIndex, toIndex, size); + return new SubList<>(this, fromIndex, toIndex); + } + + private static class SubList extends AbstractList implements RandomAccess { + private final XArrayList root; + private final SubList parent; + private final int offset; + private int size; + + /** + * Constructs a sublist of an arbitrary XArrayList. + */ + @SuppressWarnings("initialization") + public SubList(XArrayList root, int fromIndex, int toIndex) { + this.root = root; + this.parent = null; + this.offset = fromIndex; + this.size = toIndex - fromIndex; + this.modCount = root.modCount; + } + + /** + * Constructs a sublist of another SubList. + */ + @SuppressWarnings("initialization") + private SubList(SubList parent, int fromIndex, int toIndex) { + this.root = parent.root; + this.parent = parent; + this.offset = parent.offset + fromIndex; + this.size = toIndex - fromIndex; + this.modCount = root.modCount; + } + + public E set(int index, E element) { + Objects.checkIndex(index, size); + checkForComodification(); + E oldValue = root.elementData(offset + index); + root.elementData[offset + index] = element; + return oldValue; + } + + public E get(int index) { + Objects.checkIndex(index, size); + checkForComodification(); + return root.elementData(offset + index); + } + + public int size() { + checkForComodification(); + return size; + } + + public void add(int index, E element) { + rangeCheckForAdd(index); + checkForComodification(); + root.add(offset + index, element); + updateSizeAndModCount(1); + } + + public E remove(int index) { + Objects.checkIndex(index, size); + checkForComodification(); + E result = root.remove(offset + index); + updateSizeAndModCount(-1); + return result; + } + + protected void removeRange(int fromIndex, int toIndex) { + checkForComodification(); + root.removeRange(offset + fromIndex, offset + toIndex); + updateSizeAndModCount(fromIndex - toIndex); + } + + public boolean addAll(Collection c) { + return addAll(this.size, c); + } + + public boolean addAll(int index, Collection c) { + rangeCheckForAdd(index); + int cSize = c.size(); + if (cSize==0) + return false; + checkForComodification(); + root.addAll(offset + index, c); + updateSizeAndModCount(cSize); + return true; + } + + public void replaceAll(UnaryOperator operator) { + root.replaceAllRange(operator, offset, offset + size); + } + + public boolean removeAll(Collection c) { + return batchRemove(c, false); + } + + public boolean retainAll(Collection c) { + return batchRemove(c, true); + } + + private boolean batchRemove(Collection c, boolean complement) { + checkForComodification(); + int oldSize = root.size; + boolean modified = + root.batchRemove(c, complement, offset, offset + size); + if (modified) + updateSizeAndModCount(root.size - oldSize); + return modified; + } + + public boolean removeIf(Predicate filter) { + checkForComodification(); + int oldSize = root.size; + boolean modified = root.removeIf(filter, offset, offset + size); + if (modified) + updateSizeAndModCount(root.size - oldSize); + return modified; + } + + public Object[] toArray() { + checkForComodification(); + return Arrays.copyOfRange(root.elementData, offset, offset + size); + } + + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + checkForComodification(); + if (a.length < size) + return (T[]) Arrays.copyOfRange( + root.elementData, offset, offset + size, a.getClass()); + System.arraycopy(root.elementData, offset, a, 0, size); + if (a.length > size) + a[size] = null; + return a; + } + + public boolean equals(Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof List)) { + return false; + } + + boolean equal = root.equalsRange((List)o, offset, offset + size); + checkForComodification(); + return equal; + } + + public int hashCode() { + int hash = root.hashCodeRange(offset, offset + size); + checkForComodification(); + return hash; + } + + public int indexOf(Object o) { + int index = root.indexOfRange(o, offset, offset + size); + checkForComodification(); + return index >= 0 ? index - offset : -1; + } + + public int lastIndexOf(Object o) { + int index = root.lastIndexOfRange(o, offset, offset + size); + checkForComodification(); + return index >= 0 ? index - offset : -1; + } + + public boolean contains(Object o) { + return indexOf(o) >= 0; + } + + public Iterator iterator() { + return listIterator(); + } + + public ListIterator listIterator(int index) { + checkForComodification(); + rangeCheckForAdd(index); + + return new ListIterator() { + int cursor = index; + int lastRet = -1; + int expectedModCount = root.modCount; + + public boolean hasNext() { + return cursor != SubList.this.size; + } + + @SuppressWarnings("unchecked") + public E next() { + checkForComodification(); + int i = cursor; + if (i >= SubList.this.size) + throw new NoSuchElementException(); + Object[] elementData = root.elementData; + if (offset + i >= elementData.length) + throw new ConcurrentModificationException(); + cursor = i + 1; + return (E) elementData[offset + (lastRet = i)]; + } + + public boolean hasPrevious() { + return cursor != 0; + } + + @SuppressWarnings("unchecked") + public E previous() { + checkForComodification(); + int i = cursor - 1; + if (i < 0) + throw new NoSuchElementException(); + Object[] elementData = root.elementData; + if (offset + i >= elementData.length) + throw new ConcurrentModificationException(); + cursor = i; + return (E) elementData[offset + (lastRet = i)]; + } + + public void forEachRemaining(Consumer action) { + Objects.requireNonNull(action); + final int size = SubList.this.size; + int i = cursor; + if (i < size) { + final Object[] es = root.elementData; + if (offset + i >= es.length) + throw new ConcurrentModificationException(); + for (; i < size && modCount == expectedModCount; i++) + action.accept(elementAt(es, offset + i)); + // update once at end to reduce heap write traffic + cursor = i; + lastRet = i - 1; + checkForComodification(); + } + } + + public int nextIndex() { + return cursor; + } + + public int previousIndex() { + return cursor - 1; + } + + public void remove() { + if (lastRet < 0) + throw new IllegalStateException(); + checkForComodification(); + + try { + SubList.this.remove(lastRet); + cursor = lastRet; + lastRet = -1; + expectedModCount = root.modCount; + } catch (IndexOutOfBoundsException ex) { + throw new ConcurrentModificationException(); + } + } + + public void set(E e) { + if (lastRet < 0) + throw new IllegalStateException(); + checkForComodification(); + + try { + root.set(offset + lastRet, e); + } catch (IndexOutOfBoundsException ex) { + throw new ConcurrentModificationException(); + } + } + + public void add(E e) { + checkForComodification(); + + try { + int i = cursor; + SubList.this.add(i, e); + cursor = i + 1; + lastRet = -1; + expectedModCount = root.modCount; + } catch (IndexOutOfBoundsException ex) { + throw new ConcurrentModificationException(); + } + } + + final void checkForComodification() { + if (root.modCount != expectedModCount) + throw new ConcurrentModificationException(); + } + }; + } + + public List subList(int fromIndex, int toIndex) { + subListRangeCheck(fromIndex, toIndex, size); + return new SubList<>(this, fromIndex, toIndex); + } + + private void rangeCheckForAdd(int index) { + if (index < 0 || index > this.size) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); + } + + private String outOfBoundsMsg(int index) { + return "Index: "+index+", Size: "+this.size; + } + + private void checkForComodification() { + if (root.modCount != modCount) + throw new ConcurrentModificationException(); + } + + private void updateSizeAndModCount(int sizeChange) { + SubList slist = this; + do { + slist.size += sizeChange; + slist.modCount = root.modCount; + slist = slist.parent; + } while (slist != null); + } + + public Spliterator spliterator() { + checkForComodification(); + + // ArrayListSpliterator not used here due to late-binding + return new Spliterator() { + private int index = offset; // current index, modified on advance/split + private int fence = -1; // -1 until used; then one past last index + private int expectedModCount; // initialized when fence set + + private int getFence() { // initialize fence to size on first use + int hi; // (a specialized variant appears in method forEach) + if ((hi = fence) < 0) { + expectedModCount = modCount; + hi = fence = offset + size; + } + return hi; + } + + public XArrayList.ArrayListSpliterator trySplit() { + int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; + // ArrayListSpliterator can be used here as the source is already bound + return (lo >= mid) ? null : // divide range in half unless too small + root.new ArrayListSpliterator(lo, index = mid, expectedModCount); + } + + public boolean tryAdvance(Consumer action) { + Objects.requireNonNull(action); + int hi = getFence(), i = index; + if (i < hi) { + index = i + 1; + @SuppressWarnings("unchecked") E e = (E)root.elementData[i]; + action.accept(e); + if (root.modCount != expectedModCount) + throw new ConcurrentModificationException(); + return true; + } + return false; + } + + public void forEachRemaining(Consumer action) { + Objects.requireNonNull(action); + int i, hi, mc; // hoist accesses and checks from loop + XArrayList lst = root; + Object[] a; + if ((a = lst.elementData) != null) { + if ((hi = fence) < 0) { + mc = modCount; + hi = offset + size; + } + else + mc = expectedModCount; + if ((i = index) >= 0 && (index = hi) <= a.length) { + for (; i < hi; ++i) { + @SuppressWarnings("unchecked") E e = (E) a[i]; + action.accept(e); + } + if (lst.modCount == mc) + return; + } + } + throw new ConcurrentModificationException(); + } + + public long estimateSize() { + return getFence() - index; + } + + public int characteristics() { + return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED; + } + }; + } + } + + /** + * @throws NullPointerException {@inheritDoc} + */ + @Override + public void forEach(Consumer action) { + Objects.requireNonNull(action); + final int expectedModCount = modCount; + final Object[] es = elementData; + final int size = this.size; + for (int i = 0; modCount == expectedModCount && i < size; i++) + action.accept(elementAt(es, i)); + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + } + + /** + * Creates a late-binding + * and fail-fast {@link Spliterator} over the elements in this + * list. + * + *

    The {@code Spliterator} reports {@link Spliterator#SIZED}, + * {@link Spliterator#SUBSIZED}, and {@link Spliterator#ORDERED}. + * Overriding implementations should document the reporting of additional + * characteristic values. + * + * @return a {@code Spliterator} over the elements in this list + * @since 1.8 + */ + @Override + public Spliterator spliterator() { + return new ArrayListSpliterator(0, -1, 0); + } + + /** Index-based split-by-two, lazily initialized Spliterator */ + final class ArrayListSpliterator implements Spliterator { + + /* + * If XArrayLists were immutable, or structurally immutable (no + * adds, removes, etc), we could implement their spliterators + * with Arrays.spliterator. Instead we detect as much + * interference during traversal as practical without + * sacrificing much performance. We rely primarily on + * modCounts. These are not guaranteed to detect concurrency + * violations, and are sometimes overly conservative about + * within-thread interference, but detect enough problems to + * be worthwhile in practice. To carry this out, we (1) lazily + * initialize fence and expectedModCount until the latest + * point that we need to commit to the state we are checking + * against; thus improving precision. (This doesn't apply to + * SubLists, that create spliterators with current non-lazy + * values). (2) We perform only a single + * ConcurrentModificationException check at the end of forEach + * (the most performance-sensitive method). When using forEach + * (as opposed to iterators), we can normally only detect + * interference after actions, not before. Further + * CME-triggering checks apply to all other possible + * violations of assumptions for example null or too-small + * elementData array given its size(), that could only have + * occurred due to interference. This allows the inner loop + * of forEach to run without any further checks, and + * simplifies lambda-resolution. While this does entail a + * number of checks, note that in the common case of + * list.stream().forEach(a), no checks or other computation + * occur anywhere other than inside forEach itself. The other + * less-often-used methods cannot take advantage of most of + * these streamlinings. + */ + + private int index; // current index, modified on advance/split + private int fence; // -1 until used; then one past last index + private int expectedModCount; // initialized when fence set + + /** Creates new spliterator covering the given range. */ + ArrayListSpliterator(int origin, int fence, int expectedModCount) { + this.index = origin; + this.fence = fence; + this.expectedModCount = expectedModCount; + } + + private int getFence() { // initialize fence to size on first use + int hi; // (a specialized variant appears in method forEach) + if ((hi = fence) < 0) { + expectedModCount = modCount; + hi = fence = size; + } + return hi; + } + + public ArrayListSpliterator trySplit() { + int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; + return (lo >= mid) ? null : // divide range in half unless too small + new ArrayListSpliterator(lo, index = mid, expectedModCount); + } + + public boolean tryAdvance(Consumer action) { + if (action == null) + throw new NullPointerException(); + int hi = getFence(), i = index; + if (i < hi) { + index = i + 1; + @SuppressWarnings("unchecked") E e = (E)elementData[i]; + action.accept(e); + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + return true; + } + return false; + } + + public void forEachRemaining(Consumer action) { + int i, hi, mc; // hoist accesses and checks from loop + Object[] a; + if (action == null) + throw new NullPointerException(); + if ((a = elementData) != null) { + if ((hi = fence) < 0) { + mc = modCount; + hi = size; + } + else + mc = expectedModCount; + if ((i = index) >= 0 && (index = hi) <= a.length) { + for (; i < hi; ++i) { + @SuppressWarnings("unchecked") E e = (E) a[i]; + action.accept(e); + } + if (modCount == mc) + return; + } + } + throw new ConcurrentModificationException(); + } + + public long estimateSize() { + return getFence() - index; + } + + public int characteristics() { + return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED; + } + } + + // A tiny bit set implementation + + private static long[] nBits(int n) { + return new long[((n - 1) >> 6) + 1]; + } + private static void setBit(long[] bits, int i) { + bits[i >> 6] |= 1L << i; + } + private static boolean isClear(long[] bits, int i) { + return (bits[i >> 6] & (1L << i)) == 0; + } + + /** + * @throws NullPointerException {@inheritDoc} + */ + @Override + public boolean removeIf(Predicate filter) { + return removeIf(filter, 0, size); + } + + /** + * Removes all elements satisfying the given predicate, from index + * i (inclusive) to index end (exclusive). + */ + boolean removeIf(Predicate filter, int i, final int end) { + Objects.requireNonNull(filter); + int expectedModCount = modCount; + final Object[] es = elementData; + // Optimize for initial run of survivors + for (; i < end && !filter.test(elementAt(es, i)); i++) + ; + // Tolerate predicates that reentrantly access the collection for + // read (but writers still get CME), so traverse once to find + // elements to delete, a second pass to physically expunge. + if (i < end) { + final int beg = i; + final long[] deathRow = nBits(end - beg); + deathRow[0] = 1L; // set bit 0 + for (i = beg + 1; i < end; i++) + if (filter.test(elementAt(es, i))) + setBit(deathRow, i - beg); + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + modCount++; + int w = beg; + for (i = beg; i < end; i++) + if (isClear(deathRow, i - beg)) + es[w++] = es[i]; + shiftTailOverGap(es, w, end); + return true; + } else { + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + return false; + } + } + + @Override + public void replaceAll(UnaryOperator operator) { + replaceAllRange(operator, 0, size); + // TODO(8203662): remove increment of modCount from ... + modCount++; + } + + private void replaceAllRange(UnaryOperator operator, int i, int end) { + Objects.requireNonNull(operator); + final int expectedModCount = modCount; + final Object[] es = elementData; + for (; modCount == expectedModCount && i < end; i++) + es[i] = operator.apply(elementAt(es, i)); + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + } + + @Override + @SuppressWarnings("unchecked") + public void sort(Comparator c) { + final int expectedModCount = modCount; + Arrays.sort((E[]) elementData, 0, size, c); + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + modCount++; + } + + void checkInvariants() { + // assert size >= 0; + // assert size == elementData.length || elementData[size] == null; + } + + /** + * Calculates a new array length given an array's current length, a preferred + * growth value, and a minimum growth value. If the preferred growth value + * is less than the minimum growth value, the minimum growth value is used in + * its place. If the sum of the current length and the preferred growth + * value does not exceed {@link #MAX_ARRAY_LENGTH}, that sum is returned. + * If the sum of the current length and the minimum growth value does not + * exceed {@code MAX_ARRAY_LENGTH}, then {@code MAX_ARRAY_LENGTH} is returned. + * If the sum does not overflow an int, then {@code Integer.MAX_VALUE} is + * returned. Otherwise, {@code OutOfMemoryError} is thrown. + * + * @param oldLength current length of the array (must be non negative) + * @param minGrowth minimum required growth of the array length (must be + * positive) + * @param prefGrowth preferred growth of the array length (ignored, if less + * then {@code minGrowth}) + * @return the new length of the array + * @throws OutOfMemoryError if increasing {@code oldLength} by + * {@code minGrowth} overflows. + */ + private static int newLength(int oldLength, int minGrowth, int prefGrowth) { + // assert oldLength >= 0 + // assert minGrowth > 0 + + int newLength = Math.max(minGrowth, prefGrowth) + oldLength; + if (newLength - MAX_ARRAY_LENGTH <= 0) { + return newLength; + } + return hugeLength(oldLength, minGrowth); + } + + private static int hugeLength(int oldLength, int minGrowth) { + int minLength = oldLength + minGrowth; + if (minLength < 0) { // overflow + throw new OutOfMemoryError("Required array length too large"); + } + if (minLength <= MAX_ARRAY_LENGTH) { + return MAX_ARRAY_LENGTH; + } + return Integer.MAX_VALUE; + } + + private static final int MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8; + + private static void subListRangeCheck(int fromIndex, int toIndex, int size) { + if (fromIndex < 0) + throw new IndexOutOfBoundsException("fromIndex = " + fromIndex); + if (toIndex > size) + throw new IndexOutOfBoundsException("toIndex = " + toIndex); + if (fromIndex > toIndex) + throw new IllegalArgumentException("fromIndex(" + fromIndex + + ") > toIndex(" + toIndex + ")"); + } +} diff --git a/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/XArrayListCursorTest.java b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/XArrayListCursorTest.java new file mode 100644 index 00000000000..9b586c0e422 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/XArrayListCursorTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + */ +package org.openjdk.bench.valhalla.sandbox.corelibs.corelibs; + +import java.util.List; +import java.util.Iterator; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +@Fork(1) +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 3) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class XArrayListCursorTest { + @Param({"100000"}) + public static int size; + + private static final String constantString = "abc"; + + private static XArrayList list; + + @Setup + public void setup() { + list = new XArrayList<>(); + for (int i = 0; i < size; i++) { + list.add(constantString); + } + } + + @Benchmark + public void getViaCursorWhileLoop(Blackhole blackhole) { + InlineCursor cur = list.cursor(); + while (cur.exists()) { + blackhole.consume(cur.get()); + cur = cur.advance(); + } + } + + @Benchmark + public void getViaCursorForLoop(Blackhole blackhole) { + for (InlineCursor cur = list.cursor(); + cur.exists(); + cur = cur.advance()) { + blackhole.consume(cur.get()); + } + } + + @Benchmark + public void getViaIterator(Blackhole blackhole) { + Iterator it = list.iterator(); + while (it.hasNext()) { + blackhole.consume(it.next()); + } + } + + @Benchmark + public void getViaIteratorCurs(Blackhole blackhole) { + Iterator it = list.iteratorCurs(); + while (it.hasNext()) { + blackhole.consume(it.next()); + } + } + + @Benchmark + public void getViaArray(Blackhole blackhole) { + for (int i = 0; i < list.size(); i++) { + blackhole.consume(list.get(i)); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/GetX.java b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/GetX.java new file mode 100644 index 00000000000..527440db067 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/GetX.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.sandbox.corelibs.corelibs.mapprotos; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.TearDown; + +import org.openjdk.jmh.infra.Blackhole; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.IntFunction; + +public class GetX extends MapBase { + + IntFunction> mapSupplier; + Map map; + Integer[] mixed; + + @Setup + public void setup() { + super.init(size); + try { + Class mapClass = Class.forName(mapType); + mapSupplier = (s) -> newInstance(mapClass, s); + } catch (Exception ex) { + System.out.printf("%s: %s%n", mapType, ex.getMessage()); + return; + } + + map = mapSupplier.apply(size); + for (Integer k : keys) { + map.put(k, k); + } + + mixed = new Integer[size]; + System.arraycopy(keys, 0, mixed, 0, size / 2); + System.arraycopy(nonKeys, 0, mixed, size / 2, size / 2); + Collections.shuffle(Arrays.asList(mixed), rnd); + } + + @TearDown + public void teardown() { + super.TearDown(map); + } + + Map newInstance(Class mapClass, int size) { + try { + return (Map)mapClass.getConstructor(int.class).newInstance(size); + } catch (Exception ex) { + throw new RuntimeException("failed", ex); + } + } + + @Benchmark + public void getHit(Blackhole bh) { + Integer[] keys = this.keys; + Map map = this.map; + for (Integer k : keys) { + bh.consume(map.get(k)); + } + } + + @Benchmark + public void getMix(Blackhole bh) { + Integer[] keys = this.mixed; + Map map = this.map; + for (Integer k : keys) { + bh.consume(map.get(k)); + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/HashMap.java b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/HashMap.java new file mode 100644 index 00000000000..0ed1df7b534 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/HashMap.java @@ -0,0 +1,1891 @@ +/* + * 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 + * 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 org.openjdk.bench.valhalla.sandbox.corelibs.corelibs.mapprotos; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.PrintStream; +import java.io.Serializable; +import java.lang.Boolean; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.ConcurrentModificationException; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.TreeMap; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.Spliterator; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * HashMap using hashing and "open addressing". + * Hash entries are inline class instances. + * As described in Introduction to Algorithms, 3rd Edition (The MIT Press), + * Section 11 Hash tables and Section 11.4 Open addressing. + * + * Open addressing is used to locate other entries for keys with the same hash. + * If multiple keys have the same hashcode, a rehashing mechanism + * is used to place the 2nd and subsequent + * key/value pairs at a non-optimal index in the table. Therefore, + * finding the entry for a desired key must rehash and examine subsequent + * entries until the value is value or it encounters an empty entry. + * When an entry is removed, the entry is marked as deleted, (not empty) + * to allow the search algorithm to keep looking; otherwise it would terminate + * the scan on the deleted entry, when it might be the case for some (other) key + * would have that same entry as part of its chain of possible locations for its hash. + * The default load factor (.75) should be re-evaluated in light of the open addressing + * computations. A higher number would reduce unused (wasted) space at the cost of + * increased search times, a lower number would increase unused (wasted) space but + * improve search times (assuming even hashcode distributions). + * Badly distributed hash values will result in incremental table growth and + * linear search performance. + *

    + * During insertion the Robin Hood hash algorithm does a small optimization + * to reduce worst case rehash lengths. + * Removal of entries, does a compaction of the following entries to fill + * in free entries and reduce entry rehashling lengths based on + * "On Deletions in Open Addressing Hashing", by Rosa M. Jimenez and Conrado Martinz. + * + *

    + * The only allocation that occurs during put operations is for the resizing of the entry table. + * + *

    + * Hash table based implementation of the {@code Map} interface. This + * implementation provides all of the optional map operations, and permits + * {@code null} values and the {@code null} key. (The {@code HashMap} + * class is roughly equivalent to {@code Hashtable}, except that it is + * unsynchronized and permits nulls.) This class makes no guarantees as to + * the order of the map; in particular, it does not guarantee that the order + * will remain constant over time. + * + *

    This implementation provides constant-time performance for the basic + * operations ({@code get} and {@code put}), assuming the hash function + * disperses the elements properly among the buckets. Iteration over + * collection views requires time proportional to the "capacity" of the + * {@code HashMap} instance (the number of buckets) plus its size (the number + * of key-value mappings). Thus, it's very important not to set the initial + * capacity too high (or the load factor too low) if iteration performance is + * important. + * + *

    An instance of {@code HashMap} has two parameters that affect its + * performance: initial capacity and load factor. The + * capacity is the number of buckets in the hash table, and the initial + * capacity is simply the capacity at the time the hash table is created. The + * load factor is a measure of how full the hash table is allowed to + * get before its capacity is automatically increased. When the number of + * entries in the hash table exceeds the product of the load factor and the + * current capacity, the hash table is rehashed (that is, internal data + * structures are rebuilt) so that the hash table has approximately twice the + * number of buckets. + * + *

    As a general rule, the default load factor (.75) offers a good + * tradeoff between time and space costs. Higher values decrease the + * space overhead but increase the lookup cost (reflected in most of + * the operations of the {@code HashMap} class, including + * {@code get} and {@code put}). The expected number of entries in + * the map and its load factor should be taken into account when + * setting its initial capacity, so as to minimize the number of + * rehash operations. If the initial capacity is greater than the + * maximum number of entries divided by the load factor, no rehash + * operations will ever occur. + * + *

    If many mappings are to be stored in a {@code HashMap} + * instance, creating it with a sufficiently large capacity will allow + * the mappings to be stored more efficiently than letting it perform + * automatic rehashing as needed to grow the table. Note that using + * many keys with the same {@code hashCode()} is a sure way to slow + * down performance of any hash table. + * TBD: To ameliorate impact, when keys + * are {@link Comparable}, this class may use comparison order among + * keys to help break ties. + * + *

    Note that this implementation is not synchronized. + * If multiple threads access a hash map concurrently, and at least one of + * the threads modifies the map structurally, it must be + * synchronized externally. (A structural modification is any operation + * that adds or deletes one or more mappings; merely changing the value + * associated with a key that an instance already contains is not a + * structural modification.) This is typically accomplished by + * synchronizing on some object that naturally encapsulates the map. + * + * If no such object exists, the map should be "wrapped" using the + * {@link Collections#synchronizedMap Collections.synchronizedMap} + * method. This is best done at creation time, to prevent accidental + * unsynchronized access to the map:

    + *   Map m = Collections.synchronizedMap(new HashMap(...));
    + * + *

    The iterators returned by all of this class's "collection view methods" + * are fail-fast: if the map is structurally modified at any time after + * the iterator is created, in any way except through the iterator's own + * {@code remove} method, the iterator will throw a + * {@link ConcurrentModificationException}. Thus, in the face of concurrent + * modification, the iterator fails quickly and cleanly, rather than risking + * arbitrary, non-deterministic behavior at an undetermined time in the + * future. + * + *

    Note that the fail-fast behavior of an iterator cannot be guaranteed + * as it is, generally speaking, impossible to make any hard guarantees in the + * presence of unsynchronized concurrent modification. Fail-fast iterators + * throw {@code ConcurrentModificationException} on a best-effort basis. + * Therefore, it would be wrong to write a program that depended on this + * exception for its correctness: the fail-fast behavior of iterators + * should be used only to detect bugs. + * + *

    This class is a member of the + * + * Java Collections Framework. + * + * @param the type of keys maintained by this map + * @param the type of mapped values + * + * @author Doug Lea + * @author Josh Bloch + * @author Arthur van Hoff + * @author Neal Gafter + * @see Object#hashCode() + * @see Collection + * @see Map + * @see TreeMap + * @see Hashtable + * @since 1.2 + */ +public class HashMap extends XAbstractMap + implements Map, Cloneable, Serializable { + + private static final long serialVersionUID = 362498820763181265L; + + private static final boolean DEBUG = Boolean.getBoolean("DEBUG"); + private static final boolean VERIFYTABLEOK = Boolean.getBoolean("VERIFYTABLEOK"); + + /* + * Implementation notes. + * + * This map usually acts as a binned (bucketed) hash table. + * The concurrent-programming-like SSA-based coding style helps + * avoid aliasing errors amid all of the twisty pointer operations. + */ + + /** + * The default initial capacity - MUST be a power of two. + */ + static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 + + /** + * The maximum capacity, used if a higher value is implicitly specified + * by either of the constructors with arguments. + * MUST be a power of two <= 1<<30. + */ + static final int MAXIMUM_CAPACITY = 1 << 30; + + /** + * The load factor used when none specified in constructor. + */ + static final float DEFAULT_LOAD_FACTOR = 0.75f; + + private final int REHASH_HASH = 2003; // Odd and small (a medium-small prime) + + /** + * Basic hash bin node, used for most entries. + */ + static value class YNode implements Map.Entry { + final int hash; + final short probes; // maybe only a byte + final K key; + final V value; + + YNode() { + this.hash = 0; + this.probes = 0; + this.key = null; + this.value = null; + } + + YNode(int hash, K key, V value, int probes) { + this.hash = hash; + this.key = key; + this.value = value; + this.probes = (short)probes; + } + + boolean isEmpty() { + return probes == 0; + } + + boolean isValue() { + return probes > 0; + } + + public final K getKey() { return key; } + public final V getValue() { return value; } + public final String toString() { return key + "=" + value + ", probes: " + probes + + ", hash: " + Integer.toString(hash, 16) + + ", hashCode: " + hashCode(); } + public final int hashCode() { + return Objects.hashCode(key) ^ Objects.hashCode(value); + } + + public final V setValue(V newValue) { + throw new IllegalStateException("YNode cannot set a value"); + } + + public final boolean equals(Object o) { + if (o == this) + return true; + if (o instanceof Map.Entry) { + Map.Entry e = (Map.Entry)o; + if (Objects.equals(key, e.getKey()) && + Objects.equals(value, e.getValue())) + return true; + } + return false; + } + } + + value class YNodeWrapper implements Map.Entry { + final int index; + final YNode entry; + + YNodeWrapper(int index) { + this.index = index; + this.entry = table[index]; + } + + public K getKey() { + return entry.key; + } + + public V getValue() { + return entry.value; + } + + public String toString() { + return entry.toString(); + } + + public int hashCode() { + return entry.hashCode(); + } + + public boolean equals(Object o) { + return entry.equals(o); + } + /** + * Replaces the value corresponding to this entry with the specified + * value (optional operation). (Writes through to the map.) The + * behavior of this call is undefined if the mapping has already been + * removed from the map (by the iterator's {@code remove} operation). + * + * @param value new value to be stored in this entry + * @return old value corresponding to the entry + * @throws UnsupportedOperationException if the {@code put} operation + * is not supported by the backing map + * @throws ClassCastException if the class of the specified value + * prevents it from being stored in the backing map + * @throws NullPointerException if the backing map does not permit + * null values, and the specified value is null + * @throws IllegalArgumentException if some property of this value + * prevents it from being stored in the backing map + * @throws IllegalStateException implementations may, but are not + * required to, throw this exception if the entry has been + * removed from the backing map. + */ + public V setValue(V value) { + table[index] = new YNode<>(entry.hash, entry.key, value, entry.probes); + return entry.value; + } + } + /* ---------------- Static utilities -------------- */ + + /** + * Computes key.hashCode() and spreads (XORs) higher bits of hash + * to lower. Because the table uses power-of-two masking, sets of + * hashes that vary only in bits above the current mask will + * always collide. (Among known examples are sets of Float keys + * holding consecutive whole numbers in small tables.) So we + * apply a transform that spreads the impact of higher bits + * downward. There is a tradeoff between speed, utility, and + * quality of bit-spreading. Because many common sets of hashes + * are already reasonably distributed (so don't benefit from + * spreading), and because we use trees to handle large sets of + * collisions in bins, we just XOR some shifted bits in the + * cheapest possible way to reduce systematic lossage, as well as + * to incorporate impact of the highest bits that would otherwise + * never be used in index calculations because of table bounds. + */ + static final int hash(Object key) { + int h; + return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); + } + + /** + * Returns a power of two size for the given target capacity. + */ + static final int tableSizeFor(int cap) { + int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1); + return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; + } + + /* ---------------- Fields -------------- */ + + /** + * The table, initialized on first use, and resized as + * necessary. When allocated, length is always a power of two. + * (We also tolerate length zero in some operations to allow + * bootstrapping mechanics that are currently not needed.) + */ + transient YNode[] table; + + /** + * Holds cached entrySet(). Note that AbstractMap fields are used + * for keySet() and values(). + */ + transient Set> entrySet; + + /** + * The number of key-value mappings contained in this map. + */ + transient int size; + + /** + * The number of times this HashMap has been structurally modified + * Structural modifications are those that change the number of mappings in + * the HashMap or otherwise modify its internal structure (e.g., + * rehash). This field is used to make iterators on Collection-views of + * the HashMap fail-fast. (See ConcurrentModificationException). + */ + transient int modCount; + + /** + * The next size value at which to resize (capacity * load factor). + * + * @serial + */ + // (The javadoc description is true upon serialization. + // Additionally, if the table array has not been allocated, this + // field holds the initial array capacity, or zero signifying + // DEFAULT_INITIAL_CAPACITY.) + int threshold; + + /** + * The load factor for the hash table. + * + * @serial + */ + final float loadFactor; + + private transient int[] getProbes = new int[16]; + private transient int[] putProbes = new int[16]; + private transient int[] notFoundProbes = new int[16]; + + + /* ---------------- Public operations -------------- */ + + /** + * Constructs an empty {@code HashMap} with the specified initial + * capacity and load factor. + * + * @param initialCapacity the initial capacity + * @param loadFactor the load factor + * @throws IllegalArgumentException if the initial capacity is negative + * or the load factor is nonpositive + */ + public HashMap(int initialCapacity, float loadFactor) { + if (initialCapacity < 0) + throw new IllegalArgumentException("Illegal initial capacity: " + + initialCapacity); + if (initialCapacity > MAXIMUM_CAPACITY) + initialCapacity = MAXIMUM_CAPACITY; + if (loadFactor <= 0 || Float.isNaN(loadFactor)) + throw new IllegalArgumentException("Illegal load factor: " + + loadFactor); + this.loadFactor = loadFactor; + this.threshold = tableSizeFor(initialCapacity); + } + + /** + * Constructs an empty {@code HashMap} with the specified initial + * capacity and the default load factor (0.75). + * + * @param initialCapacity the initial capacity. + * @throws IllegalArgumentException if the initial capacity is negative. + */ + public HashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR); + } + + /** + * Constructs an empty {@code HashMap} with the default initial capacity + * (16) and the default load factor (0.75). + */ + public HashMap() { + this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted + } + + /** + * Constructs a new {@code HashMap} with the same mappings as the + * specified {@code Map}. The {@code HashMap} is created with + * default load factor (0.75) and an initial capacity sufficient to + * hold the mappings in the specified {@code Map}. + * + * @param m the map whose mappings are to be placed in this map + * @throws NullPointerException if the specified map is null + */ + @SuppressWarnings("initialization") + public HashMap(Map m) { + this.loadFactor = DEFAULT_LOAD_FACTOR; + putMapEntries(m, false); + } + + /** + * Implements Map.putAll and Map constructor. + * + * @param m the map + * @param evict false when initially constructing this map, else true. + */ + final void putMapEntries(Map m, boolean evict) { + int s = m.size(); + if (s > 0) { + if (table == null) { // pre-size + float ft = ((float)s / loadFactor) + 1.0F; + int t = ((ft < (float)MAXIMUM_CAPACITY) ? + (int)ft : MAXIMUM_CAPACITY); + if (t > threshold) + threshold = tableSizeFor(t); + } else { + // Because of linked-list bucket constraints, we cannot + // expand all at once, but can reduce total resize + // effort by repeated doubling now vs later + while (s > threshold && table.length < MAXIMUM_CAPACITY) + resize(); + } + + for (Map.Entry e : m.entrySet()) { + K key = e.getKey(); + V value = e.getValue(); + putVal(hash(key), key, value, false); + } + } + } + + /** + * Returns the number of key-value mappings in this map. + * + * @return the number of key-value mappings in this map + */ + public int size() { + return size; + } + + /** + * Returns {@code true} if this map contains no key-value mappings. + * + * @return {@code true} if this map contains no key-value mappings + */ + public boolean isEmpty() { + return size == 0; + } + + /** + * Returns the value to which the specified key is mapped, + * or {@code null} if this map contains no mapping for the key. + * + *

    More formally, if this map contains a mapping from a key + * {@code k} to a value {@code v} such that {@code (key==null ? k==null : + * key.equals(k))}, then this method returns {@code v}; otherwise + * it returns {@code null}. (There can be at most one such mapping.) + * + *

    A return value of {@code null} does not necessarily + * indicate that the map contains no mapping for the key; it's also + * possible that the map explicitly maps the key to {@code null}. + * The {@link #containsKey containsKey} operation may be used to + * distinguish these two cases. + * + * @see #put(Object, Object) + */ + public V get(Object key) { + final YNode[] tab; + final int mask; +// int probes = 0; + if ((tab = table) != null && (mask = tab.length - 1) >= 0) { + final int hash = hash(key); + int h = hash; + YNode entry; + while ((entry = tab[(mask & h)]).isValue()) { +// probes++; + K k; + if (entry.hash == hash && + ((k = entry.key) == key || (key != null && key.equals(k)))) { +// getProbes = incProbeCount(getProbes, probes); + return entry.value; + } else { + h += REHASH_HASH; + } + } + } +// notFoundProbes = incProbeCount(notFoundProbes, 0); + return null; // not found; empty table + } + + /** + * Same as Get caching the entry. + * @param key the key + * @return a value, or null + */ + public V get1(Object key) { + final int hash = hash(key); + final YNode[] tab; + int n; + if ((tab = table) != null && (n = tab.length) > 0) { + int h = hash; + int index = (n - 1) & h; + YNode entry = tab[index]; + for (; //int probes = 1 + entry.isValue(); + h += REHASH_HASH, index = (n - 1) & h, entry = tab[index]) { //, probes++ + K k; + if (entry.hash == hash && + ((k = entry.key) == key || (key != null && key.equals(k)))) { +// getProbes = incProbeCount(getProbes, probes); + return entry.value; + } + } + } +// notFoundProbes = incProbeCount(notFoundProbes, 0); + return null; // not found; empty table + } + + /** + * Implements Map.get and related methods. + * + * @param hash hash for key + * @param key the key + * @return the index of a matching node or -1 + */ + private final int getNode(final int hash, Object key) { + YNode[] tab; + int n; + if ((tab = table) != null && (n = tab.length) > 0) { + final YNode first; + final int i = (n - 1) & hash; + K k; + if ((first = tab[i]).isValue() && + first.hash == hash && + ((k = first.key) == key || (key != null && key.equals(k)))) { +// getProbes = incProbeCount(getProbes, 1); + return i; + } + // non-empty table and not first entry + int h = hash; + for (int probes = 1; probes <= tab.length; probes++, h += REHASH_HASH) { + final int index = (n - 1) & h; + final YNode entry = tab[index]; + if (!entry.isValue()) { +// notFoundProbes = incProbeCount(notFoundProbes, probes); + return -1; // search ended without finding the key + } else if (entry.hash == hash && + ((k = entry.key) == key || (key != null && key.equals(k)))) { +// getProbes = incProbeCount(getProbes, probes); + return index; + } + } + } +// notFoundProbes = incProbeCount(notFoundProbes, 0); + return -1; // not found; empty table + } + + /** + * Returns {@code true} if this map contains a mapping for the + * specified key. + * + * @param key The key whose presence in this map is to be tested + * @return {@code true} if this map contains a mapping for the specified + * key. + */ + public boolean containsKey(Object key) { + return getNode(hash(key), key) >= 0; + } + + /** + * Associates the specified value with the specified key in this map. + * If the map previously contained a mapping for the key, the old + * value is replaced. + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key}. + * (A {@code null} return can also indicate that the map + * previously associated {@code null} with {@code key}.) + */ + public V put(K key, V value) { + return putVal(hash(key), key, value, false); + } + + /** + * Implements Map.put and related methods. + * + * @param hash hash for key + * @param key the key + * @param value the value to put + * @param onlyIfAbsent if true, don't change existing value + * @return previous value, or null if none + */ + private final V putVal(final int hash, final K key, final V value, boolean onlyIfAbsent) { + YNode[] tab; + YNode tp; + int n, i; + if ((tab = table) == null || (n = tab.length) == 0) + n = (tab = resize()).length; + debug(" putval", -1, new YNode(hash, key, value, -1)); + + int h = hash; + int insert = -1; // insertion point if not already present + int insertProbes = -1; + for (int probes = 1; ; probes++, h += REHASH_HASH) { + if (probes > tab.length) + throw new IllegalStateException("No empty entries in table"); + final int index; + final YNode entry = tab[(index = ((n - 1) & h))]; +// debug(" looking at", index, entry); + if (entry.isEmpty()) { + // Absent; insert in the first place it could be added + // TBD: should it check onlyIfAbsent? + if (insert < 0) { + // no better place to insert than here + tab[index] = new YNode<>(hash, key, value, probes); + debug(" setting", index, tab[index]); +// putProbes = incProbeCount(putProbes, probes); + } else { + // The new entry is more needy than the current one + final YNode tmp = tab[insert]; + tab[insert] = new YNode<>(hash, key, value, insertProbes); + debug(" robin-hood inserted", index, tab[index]); +// putProbes = incProbeCount(putProbes, insertProbes); + putReinsert(insert, tmp); + } + break; // break to update modCount and size + } + + if (entry.isValue() && entry.hash == hash && + (entry.key == key || (key != null && key.equals(entry.key)))) { + // TBD: consider if updated entry should be moved closer to the front + if (!onlyIfAbsent || entry.value == null) { + tab[index] = new YNode<>(hash, key, value, entry.probes); + } + debug(" oldvalue", index, entry); + debug(" updating", index, tab[index]); +// putProbes = incProbeCount(putProbes, probes); + return entry.value; + } + + // Save first possible insert index + if (insert < 0 && probes > entry.probes) { + insert = index; + insertProbes = probes; + } + } + ++modCount; + ++size; + isTableOk(tab, "table not ok, putval"); + if (size >= threshold) + resize(); // Ensure there is at least 1 empty available + return null; + } + + /** + * Re-insert the entry in the table starting at the entry beyond the index. + * Insert it at an empty slot. + * Replace an entry with a lower probe count and repeat to reinsert that entry. + * @param oldIndex the index just replaced + * @param rEntry the entry to be replaced + */ + private void putReinsert(final int oldIndex, YNode rEntry) { + final YNode[] tab = table; + final int n = tab.length; + + int h = oldIndex + REHASH_HASH; + for (int probes = rEntry.probes + 1; probes <= n; probes++, h += REHASH_HASH) { + isTableOk(tab, "bubble down loop"); + final int index = (n - 1) & h; + final YNode entry = tab[index]; + if (entry.isEmpty()) { + // Insert in the empty slot + tab[index] = new YNode<>(rEntry.hash, rEntry.key, rEntry.value, probes); + debug(" reinserted", index, tab[index]); + return; + } else if (probes > entry.probes) { + // Replace a less deserving entry + tab[index] = new YNode<>(rEntry.hash, rEntry.key, rEntry.value, probes); + debug(" robin-hood bubble down", index, tab[index]); + rEntry = entry; + probes = rEntry.probes; + debug(" robin-hood moving", index, rEntry); + } else { + debug(" robin-hood skipping", index, entry); + } + } + throw new RuntimeException("bubble down failed"); + } + + private void debug(String msg, int index, YNode entry) { + if (DEBUG && System.out != null) { + System.out.println(System.identityHashCode(this) + ": " + msg + ": index: " + index + ", node: " + Objects.toString(entry)); + } + } + private void debug2(String msg, int index, YNode entry) { + if (System.out != null) { + System.out.println(System.identityHashCode(this) + ": " + msg + ": index: " + index + + ", " + "node: " + entry); + } + } + + /** + * Initializes or doubles table size. If null, allocates in + * accord with initial capacity target held in field threshold. + * Otherwise, because we are using power-of-two expansion, the + * elements from each bin must either stay at same index, or move + * with a power of two offset in the new table. + * + * @return the table + */ + final YNode[] resize() { + YNode[] oldTab = table; + int oldCap = (oldTab == null) ? 0 : oldTab.length; + int oldThr = threshold; + int newCap, newThr = 0; + if (oldCap > 0) { + if (oldCap >= MAXIMUM_CAPACITY) { + threshold = Integer.MAX_VALUE; + return oldTab; + } + else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && + oldCap >= DEFAULT_INITIAL_CAPACITY) + newThr = oldThr << 1; // double threshold + } + else if (oldThr > 0) // initial capacity was placed in threshold + newCap = oldThr; + else { // zero initial threshold signifies using defaults + newCap = DEFAULT_INITIAL_CAPACITY; + newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); + } + if (newThr == 0) { + float ft = (float)newCap * loadFactor; + newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? + (int)ft : Integer.MAX_VALUE); + } + isTableOk(oldTab, "oldTab bad before resize"); + if (getProbes != null) + Arrays.fill(getProbes, 0); + if (putProbes != null) + Arrays.fill(putProbes, 0); + if (notFoundProbes != null) + Arrays.fill(notFoundProbes, 0); + + // There must always be an empty entry, resize when it gets to capacity. + threshold = (newThr > newCap) ? newCap : newThr; + @SuppressWarnings({"rawtypes","unchecked"}) + YNode[] newTab = (YNode[])new YNode[newCap]; + table = newTab; + if (oldTab != null) { + for (int i = 0; i < oldCap; ++i) { + YNode e; + if ((e = oldTab[i]).isValue()) { + final int ii; + if (newTab[ii = (newCap - 1) & e.hash].isEmpty()) { + newTab[ii] = new YNode<>(e.hash, e.key, e.value, 1); + putProbes = incProbeCount(putProbes, 1); + } else { + int h = e.hash + REHASH_HASH; + for (int probes = 2; ; probes++, h += REHASH_HASH) { + final int index; + if (newTab[(index = ((newCap - 1) & h))].isEmpty()) { + newTab[index] = new YNode<>(e.hash, e.key, e.value, probes); + putProbes = incProbeCount(putProbes, probes); + break; + } + // TBD: Consider Robin-hood insert + if (probes > newTab.length) + throw new IllegalStateException("NYI resize: no support for overflow"); + } + } + } + } + } + + debug("resized", newTab.length, new YNode()); + isTableOk(newTab, "newTab bad after resize"); + return newTab; + } + + /** + * Dump the hashtable. + */ + public void dumpTable() { + dumpTable(table, "dumpTable"); + } + + /** + * Dump the hashtable + * @param table the table + * @param msg a message + */ + private void dumpTable(YNode[] table, String msg) { + if (System.out == null || table == null) + return; + System.out.println(msg + ", size: " + size + ", len: " + table.length); + for (int i = 0; i < table.length; ++i) { + if (table[i].isValue()) + System.out.println(" [" + i + "] " + table[i]); + } + } + + /** + * Copies all of the mappings from the specified map to this map. + * These mappings will replace any mappings that this map had for + * any of the keys currently in the specified map. + * + * @param m mappings to be stored in this map + * @throws NullPointerException if the specified map is null + */ + public void putAll(Map m) { + putMapEntries(m, true); + } + + /** + * Removes the mapping for the specified key from this map if present. + * + * @param key key whose mapping is to be removed from the map + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key}. + * (A {@code null} return can also indicate that the map + * previously associated {@code null} with {@code key}.) + */ + public V remove(Object key) { + YNode entry = removeNode(hash(key), key, null, false, true); + return entry.isValue() ? entry.value : null; + } + + /** + * Implements Map.remove and related methods. + * + * @param hash hash for key + * @param key the key + * @param value the value to match if matchValue, else ignored + * @param matchValue if true only remove if value is equal + * @param movable if false do not move other nodes while removing + * @return the node, or null if none + */ + @SuppressWarnings("unchecked") + private final YNode removeNode(final int hash, final Object key, final Object value, + boolean matchValue, boolean movable) { + YNode[] tab; + YNode entry; + V v = null; + int curr; + int n; + debug(" removeNode", -2, new YNode(hash, (K)key, (V)value, -2)); + + if ((tab = table) != null && (n = tab.length) > 0 && + (curr = getNode(hash, key)) >= 0 && + (entry = tab[curr]).isValue() && + ((!matchValue || (v = entry.value) == value || + (value != null && value.equals(v))))) { + // found entry; free and compress + removeNode(curr); + return entry; + } + return new YNode(); + } + + @SuppressWarnings("unchecked") + private void removeNode(final int curr) { + final YNode[] tab = table;; + final int n = tab.length; + + ++modCount; + --size; + int free = curr; // The entry to be cleared, if not replaced + int h = curr; + int probes = 0; + do { + probes++; + h += REHASH_HASH; + final int index = (n - 1) & h; + final YNode entry = tab[index]; + if (entry.probes == 0) { + // Search ended at empty entry, clear the free entry + debug(" clearing", index, entry); + tab[free] = new YNode<>(); + return; + } + if (entry.probes > probes) { + // move up the entry, skip if it is already in the best spot + debug(" replacing", free, entry); + tab[free] = new YNode<>(entry.hash, entry.key, entry.value, entry.probes - probes); + debug(" with", free, tab[free]); + free = index; + probes = 0; + } + } while (((n - 1) & h) != curr); + isTableOk(tab, "table not ok, not found"); + throw new RuntimeException("removeNode too many probes"); + } + + /** + * Removes all of the mappings from this map. + * The map will be empty after this call returns. + */ + @SuppressWarnings({"rawtypes","unchecked"}) + public void clear() { + YNode[] tab; + modCount++; + if ((tab = table) != null && size > 0) { + size = 0; + for (int i = 0; i < tab.length; i++) + tab[i] = new YNode(); + } + } + + /** + * Returns {@code true} if this map maps one or more keys to the + * specified value. + * + * @param value value whose presence in this map is to be tested + * @return {@code true} if this map maps one or more keys to the + * specified value + */ + public boolean containsValue(Object value) { + YNode[] tab; V v; + if ((tab = table) != null && size > 0) { + for (YNode te : tab) { + if (te.isValue()) { + if ((v = te.value) == value || + (value != null && value.equals(v))) + return true; + } + } + } + return false; + } + + /** + * Returns a {@link Set} view of the keys contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. If the map is modified + * while an iteration over the set is in progress (except through + * the iterator's own {@code remove} operation), the results of + * the iteration are undefined. The set supports element removal, + * which removes the corresponding mapping from the map, via the + * {@code Iterator.remove}, {@code Set.remove}, + * {@code removeAll}, {@code retainAll}, and {@code clear} + * operations. It does not support the {@code add} or {@code addAll} + * operations. + * + * @return a set view of the keys contained in this map + */ + public Set keySet() { + Set ks = keySet; + if (ks == null) { + ks = new KeySet(); + keySet = ks; + } + return ks; + } + + /** + * Prepares the array for {@link Collection#toArray(Object[])} implementation. + * If supplied array is smaller than this map size, a new array is allocated. + * If supplied array is bigger than this map size, a null is written at size index. + * + * @param a an original array passed to {@code toArray()} method + * @param type of array elements + * @return an array ready to be filled and returned from {@code toArray()} method. + */ + @SuppressWarnings("unchecked") + final T[] prepareArray(T[] a) { + int size = this.size; + if (a.length < size) { + return (T[]) java.lang.reflect.Array + .newInstance(a.getClass().getComponentType(), size); + } + if (a.length > size) { + a[size] = null; + } + return a; + } + + /** + * Fills an array with this map keys and returns it. This method assumes + * that input array is big enough to fit all the keys. Use + * {@link #prepareArray(Object[])} to ensure this. + * + * @param a an array to fill + * @param type of array elements + * @return supplied array + */ + T[] keysToArray(T[] a) { + Object[] r = a; + YNode[] tab; + int idx = 0; + if (size > 0 && (tab = table) != null) { + for (YNode te : tab) { + if (te.isValue()) { + r[idx++] = te.key; + } + } + } + return a; + } + + /** + * Fills an array with this map values and returns it. This method assumes + * that input array is big enough to fit all the values. Use + * {@link #prepareArray(Object[])} to ensure this. + * + * @param a an array to fill + * @param type of array elements + * @return supplied array + */ + T[] valuesToArray(T[] a) { + Object[] r = a; + YNode[] tab; + int idx = 0; + if (size > 0 && (tab = table) != null) { + for (YNode te : tab) { + if (te.isValue()) { + r[idx++] = te.value; + } + } + } + return a; + } + + final class KeySet extends AbstractSet { + public final int size() { return size; } + public final void clear() { HashMap.this.clear(); } + public final Iterator iterator() { return new KeyIterator(); } + public final boolean contains(Object o) { return containsKey(o); } + public final boolean remove(Object key) { + return removeNode(hash(key), key, null, false, true).isValue(); + } + + public Object[] toArray() { + return keysToArray(new Object[size]); + } + + public T[] toArray(T[] a) { + return keysToArray(prepareArray(a)); + } + + public final void forEach(Consumer action) { + YNode[] tab; + if (action == null) + throw new NullPointerException(); + if (size > 0 && (tab = table) != null) { + int mc = modCount; + for (YNode te : tab) { + if (te.isValue()) { + action.accept(te.key); + } + } + if (modCount != mc) + throw new ConcurrentModificationException(); + } + } + } + + /** + * Returns a {@link Collection} view of the values contained in this map. + * The collection is backed by the map, so changes to the map are + * reflected in the collection, and vice-versa. If the map is + * modified while an iteration over the collection is in progress + * (except through the iterator's own {@code remove} operation), + * the results of the iteration are undefined. The collection + * supports element removal, which removes the corresponding + * mapping from the map, via the {@code Iterator.remove}, + * {@code Collection.remove}, {@code removeAll}, + * {@code retainAll} and {@code clear} operations. It does not + * support the {@code add} or {@code addAll} operations. + * + * @return a view of the values contained in this map + */ + public Collection values() { + Collection vs = values; + if (vs == null) { + vs = new Values(); + values = vs; + } + return vs; + } + + final class Values extends AbstractCollection { + public final int size() { return size; } + public final void clear() { HashMap.this.clear(); } + public final Iterator iterator() { return new ValueIterator(); } + public final boolean contains(Object o) { return containsValue(o); } + + public Object[] toArray() { + return valuesToArray(new Object[size]); + } + + public T[] toArray(T[] a) { + return valuesToArray(prepareArray(a)); + } + + public final void forEach(Consumer action) { + YNode[] tab; + if (action == null) + throw new NullPointerException(); + if (size > 0 && (tab = table) != null) { + int mc = modCount; + for (YNode te : tab) { + if (te.isValue()) { + action.accept(te.value); + } + } + if (modCount != mc) + throw new ConcurrentModificationException(); + } + } + } + + /** + * Returns a {@link Set} view of the mappings contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. If the map is modified + * while an iteration over the set is in progress (except through + * the iterator's own {@code remove} operation, or through the + * {@code setValue} operation on a map entry returned by the + * iterator) the results of the iteration are undefined. The set + * supports element removal, which removes the corresponding + * mapping from the map, via the {@code Iterator.remove}, + * {@code Set.remove}, {@code removeAll}, {@code retainAll} and + * {@code clear} operations. It does not support the + * {@code add} or {@code addAll} operations. + * + * @return a set view of the mappings contained in this map + */ + public Set> entrySet() { + Set> es; + return (es = entrySet) == null ? (entrySet = new EntrySet()) : es; + } + + final class EntrySet extends AbstractSet> { + public final int size() { return size; } + public final void clear() { HashMap.this.clear(); } + public final Iterator> iterator() { + return new EntryIterator(); + } + public final boolean contains(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry) o; + Object key = e.getKey(); + int index = getNode(hash(key), key); + return index >= 0 && table[index].equals(e); + } + public final boolean remove(Object o) { + if (o instanceof Map.Entry) { + Map.Entry e = (Map.Entry) o; + Object key = e.getKey(); + Object value = e.getValue(); + return removeNode(hash(key), key, value, true, true).isValue(); + } + return false; + } + + public final void forEach(Consumer> action) { + YNode[] tab; + if (action == null) + throw new NullPointerException(); + if (size > 0 && (tab = table) != null) { + int mc = modCount; + for (YNode te : tab) { + if (te.isValue()) { + action.accept(new YNodeWrapper(te.hash & (tab.length - 1))); + } + } + if (modCount != mc) + throw new ConcurrentModificationException(); + } + } + } + + // Overrides of JDK8 Map extension methods + + @Override + public V getOrDefault(Object key, V defaultValue) { + final int index; + return (index = getNode(hash(key), key)) < 0 ? defaultValue : table[index].value; + } + + @Override + public V putIfAbsent(K key, V value) { + return putVal(hash(key), key, value, true); + } + + @Override + public boolean remove(Object key, Object value) { + return removeNode(hash(key), key, value, true, true).isValue(); + } + + @Override + public boolean replace(K key, V oldValue, V newValue) { + int hash, index; + V v; + if ((index = getNode((hash = hash(key)), key)) >= 0 && + ((v = table[index].value) == oldValue || (v != null && v.equals(oldValue)))) { + table[index] = new YNode<>(hash, key, newValue, table[index].probes); + return true; + } + return false; + } + + @Override + public V replace(K key, V value) { + int hash, index; + V v; + if ((index = getNode((hash = hash(key)), key)) >= 0) { + V oldValue = table[index].value; + table[index] = new YNode<>(hash, key, value, table[index].probes); + return oldValue; + } + return null; + } + + /** + * {@inheritDoc} + * + *

    This method will, on a best-effort basis, throw a + * {@link ConcurrentModificationException} if it is detected that the + * mapping function modifies this map during computation. + * + * @throws ConcurrentModificationException if it is detected that the + * mapping function modified this map + */ + @Override + public V computeIfAbsent(K key, + Function mappingFunction) { + if (mappingFunction == null) + throw new NullPointerException(); + int hash = hash(key); + YNode[] tab = table; + YNode entry; + int index; + + index = getNode(hash, key); + if (index >= 0 && (entry = tab[index]).value != null) { + return entry.value; + } + int mc = modCount; + V v = mappingFunction.apply(key); + if (mc != modCount) { throw new ConcurrentModificationException(); } + if (v == null) { + return null; + } + putVal(hash, key, v, false); + return v; + } + + /** + * {@inheritDoc} + * + *

    This method will, on a best-effort basis, throw a + * {@link ConcurrentModificationException} if it is detected that the + * remapping function modifies this map during computation. + * + * @throws ConcurrentModificationException if it is detected that the + * remapping function modified this map + */ + @Override + public V computeIfPresent(K key, + BiFunction remappingFunction) { + if (remappingFunction == null) + throw new NullPointerException(); + YNode oldValue; + int hash = hash(key); + int index = getNode(hash, key); + if (index >= 0 && (oldValue = table[index]).value != null) { + int mc = modCount; + V v = remappingFunction.apply(key, oldValue.value); + if (mc != modCount) { throw new ConcurrentModificationException(); } + if (v != null) { + table[index] = new YNode<>(hash, key, v, oldValue.probes); + return v; + } else + removeNode(hash, key, null, false, true); + } + return null; + } + + /** + * {@inheritDoc} + * + *

    This method will, on a best-effort basis, throw a + * {@link ConcurrentModificationException} if it is detected that the + * remapping function modifies this map during computation. + * + * @throws ConcurrentModificationException if it is detected that the + * remapping function modified this map + */ + @Override + @SuppressWarnings("unchecked") + public V compute(K key, + BiFunction remappingFunction) { + if (remappingFunction == null) + throw new NullPointerException(); + + int hash = hash(key); + int index = getNode(hash, key); + YNode oldValue = (index >= 0) ? table[index] : new YNode(); + + int mc = modCount; + V v = remappingFunction.apply(key, oldValue.value); + if (mc != modCount) { throw new ConcurrentModificationException(); } + if (v != null) { + if (index >= 0) { + table[index] = new YNode<>(hash, key, v, oldValue.probes); +// modCount++; + } else + putVal(hash, key, v, false); + } else + // TBD: 2nd lookup to remove even though we have index + removeNode(hash, key, null, false, true); + return v; + } + + /** + * {@inheritDoc} + * + *

    This method will, on a best-effort basis, throw a + * {@link ConcurrentModificationException} if it is detected that the + * remapping function modifies this map during computation. + * + * @throws ConcurrentModificationException if it is detected that the + * remapping function modified this map + */ + @Override + public V merge(K key, V value, + BiFunction remappingFunction) { + if (remappingFunction == null) + throw new NullPointerException(); + + final int hash = hash(key); + final int index = getNode(hash, key); + if (index >= 0) { + int mc = modCount; + V v = remappingFunction.apply(table[index].value, value); + if (mc != modCount) { throw new ConcurrentModificationException(); } + if (v != null) { + if (index >= 0) { + table[index] = new YNode<>(hash, key, v, table[index].probes); +// modCount++; + } else + putVal(hash, key, v, false); + } else { + // TBD: 2nd lookup to remove even though we have index + removeNode(hash, key, null, false, true); + } + return v; + } else { + // put new key/value (even if null) + putVal(hash, key, value, false); + } + return value; + } + + @Override + public void forEach(BiConsumer action) { + YNode[] tab; + if (action == null) + throw new NullPointerException(); + if (size > 0 && (tab = table) != null) { + int mc = modCount; + for (YNode te : tab) { + if (te.isValue()) { + action.accept(te.key, te.value); + } + } + if (modCount != mc) + throw new ConcurrentModificationException(); + } + } + + @Override + public void replaceAll(BiFunction function) { + super.replaceAll(function); + } + + /* ------------------------------------------------------------ */ + // Cloning and serialization + + /** + * Returns a shallow copy of this {@code HashMap} instance: the keys and + * values themselves are not cloned. + * + * @return a shallow copy of this map + */ + @SuppressWarnings("unchecked") + @Override + public Object clone() { + HashMap result; + try { + result = (HashMap)super.clone(); + } catch (CloneNotSupportedException e) { + // this shouldn't happen, since we are Cloneable + throw new InternalError(e); + } + result.reinitialize(); + result.putMapEntries(this, false); + return result; + } + + // These methods are also used when serializing HashSets + final float loadFactor() { return loadFactor; } + final int capacity() { + return (table != null) ? table.length : + (threshold > 0) ? threshold : + DEFAULT_INITIAL_CAPACITY; + } + + /** + * Saves this map to a stream (that is, serializes it). + * + * @param s the stream + * @throws IOException if an I/O error occurs + * @serialData The capacity of the HashMap (the length of the + * bucket array) is emitted (int), followed by the + * size (an int, the number of key-value + * mappings), followed by the key (Object) and value (Object) + * for each key-value mapping. The key-value mappings are + * emitted in no particular order. + */ + private void writeObject(java.io.ObjectOutputStream s) + throws IOException { + int buckets = capacity(); + // Write out the threshold, loadfactor, and any hidden stuff + s.defaultWriteObject(); + s.writeInt(buckets); + s.writeInt(size); + internalWriteEntries(s); + } + + /** + * Reconstitutes this map from a stream (that is, deserializes it). + * @param s the stream + * @throws ClassNotFoundException if the class of a serialized object + * could not be found + * @throws IOException if an I/O error occurs + */ + private void readObject(java.io.ObjectInputStream s) + throws IOException, ClassNotFoundException { + // Read in the threshold (ignored), loadfactor, and any hidden stuff + s.defaultReadObject(); + reinitialize(); + if (loadFactor <= 0 || Float.isNaN(loadFactor)) + throw new InvalidObjectException("Illegal load factor: " + + loadFactor); + s.readInt(); // Read and ignore number of buckets + int mappings = s.readInt(); // Read number of mappings (size) + if (mappings < 0) + throw new InvalidObjectException("Illegal mappings count: " + + mappings); + else if (mappings > 0) { // (if zero, use defaults) + // Size the table using given load factor only if within + // range of 0.25...4.0 + float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f); + float fc = (float)mappings / lf + 1.0f; + int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ? + DEFAULT_INITIAL_CAPACITY : + (fc >= MAXIMUM_CAPACITY) ? + MAXIMUM_CAPACITY : + tableSizeFor((int)fc)); + float ft = (float)cap * lf; + threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ? + (int)ft : Integer.MAX_VALUE); + + // Check Map.Entry[].class since it's the nearest public type to + // what we're actually creating. +// SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Map.Entry[].class, cap); + @SuppressWarnings({"rawtypes","unchecked"}) + YNode[] tab = (YNode[])new YNode[cap]; + table = tab; + + // Read the keys and values, and put the mappings in the HashMap + for (int i = 0; i < mappings; i++) { + @SuppressWarnings("unchecked") + K key = (K) s.readObject(); + @SuppressWarnings("unchecked") + V value = (V) s.readObject(); + putVal(hash(key), key, value, false); + } + } + } + + /* ------------------------------------------------------------ */ + // iterators + + abstract class HashIterator { + int next; // next entry to return + int current; // current entry + int expectedModCount; // for fast-fail + int count; + + @SuppressWarnings("initialization") + HashIterator() { + expectedModCount = modCount; + current = -1; + next = (size > 0 && table != null) ? findNext(0) : -1; + count = 0; + } + + public final boolean hasNext() { + return next >= 0; + } + + final Entry nextNode() { + if (modCount != expectedModCount) + throw new ConcurrentModificationException("ex: " + expectedModCount + " != " + modCount); + if (!hasNext()) + throw new NoSuchElementException(); + current = next; + assert current >= 0; + + next = (current + REHASH_HASH) & (table.length - 1); + next = (next == 0) ? -1 : findNext(next); + return new YNodeWrapper(current); + } + + public final void remove() { + if (current < 0 || current > table.length) + throw new IllegalStateException(); + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + YNode p = table[current]; + removeNode(p.hash, p.key, null, false, false); + if (table[current].isValue()) { + // a node was moved into current + next = current; + } + current = -1; + expectedModCount = modCount; + } + + /** + * Find the next value entry in the rehash sequence. + */ + private final int findNext(int index) { + final YNode[] t = table; + final int lowbitmask = table.length - 1; + index = index & lowbitmask; + int count = 0; + while (!t[index].isValue()) { + count ++; + index = (index + REHASH_HASH) & lowbitmask; + if (index == 0) + return -1; + } + return index; + } + } + + final class KeyIterator extends HashIterator + implements Iterator { + public final K next() { return nextNode().getKey(); } + } + + final class ValueIterator extends HashIterator + implements Iterator { + public final V next() { return nextNode().getValue(); } + } + + final class EntryIterator extends HashIterator + implements Iterator> { + public final Map.Entry next() { return nextNode(); } + } + + /** + * Reset to initial default state. Called by clone and readObject. + */ + void reinitialize() { + table = null; + entrySet = null; + keySet = null; + values = null; + modCount = 0; + threshold = 0; + size = 0; + } + + // Called only from writeObject, to ensure compatible ordering. + void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException { + YNode[] tab; + if (size > 0 && (tab = table) != null) { + for (YNode te : tab) { + if (te.isValue()) { + s.writeObject(te.key); + s.writeObject(te.value); + } + } + } + } + + + /** + * Check each entry in the table. + * - FindNode will find it from the key. + * - the probes value is correct. + */ + boolean isTableOk(final YNode[] tab, String msg) { + int n; + if (!VERIFYTABLEOK || tab == null || (n = tab.length) == 0) + return true; + boolean ok = true; + int valueEntries = 0; + for (int index = 0; index < tab.length; index++) { + final YNode te = tab[index]; + if (te.isValue()) { + valueEntries++; + if (te.key == this || te.value == this) + continue; // skip self referential entries + int hash = hash(te.key); + int th = hash(te.key); + if (th != te.hash) { + ok = false; + debug2("ERROR: computed hash not equal stored hash: th: " + th, index, te); + } + + int findex = getNode(hash, te.key); + if (findex < index) { + ok = false; + debug2("ERROR: getNode entry not found/found at wrong index: " + findex, + index , te); + } + if (findex >= 0) { + int h = hash; + for (int probes = 1; probes <= tab.length; probes++, h += REHASH_HASH) { + int i = (n - 1) & h; + if (i == findex) { + if (probes != te.probes) { + ok = false; + debug2("ERROR: computed probes not equal recorded " + + "probes: " + probes, findex, te); + } + break; + } + if (probes == 500) { + debug2("probes > 500: " + probes, findex, te); + } + } + } + // Check for duplicate entry + for (int j = index + 1; j < tab.length; j++) { + if (te.hash == tab[j].hash && + te.key.equals(tab[j].key)) { + debug2("ERROR: YNode at index ", index, te); + debug2("ERROR: duplicate YNode", j, tab[j]); + } + } + } + } + if (valueEntries != size()) { + debug2("ERROR: size wrong: " + valueEntries, size(), new YNode()); + ok = false; + } + if (!ok) { + if (System.out != null) { + Thread.dumpStack(); + dumpTable(table, msg); + } + } + return ok; + } + + /** + * Print stats of the table to the a stream. + * @param out a stream + */ + public void dumpStats(PrintStream out) { + out.printf("%s instance: size: %d%n", this.getClass().getName(), this.size()); + long size = heapSize(); + long bytesPer = (this.size != 0) ? size / this.size() : 0; + out.printf(" heap size: %d(bytes), avg bytes per entry: %d, table len: %d%n", + size, bytesPer, (table != null) ? table.length : 0); + long[] types = entryTypes(); + out.printf(" values: %d, empty: %d%n", + types[0], types[1]); + printStats(out, "hash collisions", entryRehashes()); + printStats(out, "getProbes ", minCounts(getProbes)); + printStats(out, "putProbes ", minCounts(putProbes)); + printStats(out, "notFoundProbes ", minCounts(notFoundProbes)); + + isTableOk(table, "dumpStats"); + } + + private void printStats(PrintStream out, String label, int[] hist) { + if (hist.length > 1) { + out.printf(" %s: max: %d, mean: %3.2f, stddev: %3.2f, %s%n", + label, hist.length - 1, + computeMean(hist), computeStdDev(hist), + Arrays.toString(hist)); + } else if (hist.length > 0) { + out.printf(" %s: max: %d, %s%n", + label, hist.length - 1, + Arrays.toString(hist)); + } else { + out.printf(" %s: n/a%n", label); + } + } + + private double computeStdDev(int[] hist) { + double mean = computeMean(hist); + double sum = 0.0f; + long count = 0L; + for (int i = 1; i < hist.length; i++) { + count += hist[i]; + sum += (i - mean) * (i - mean) * hist[i]; + } + return Math.sqrt(sum / (count - 1)); + } + + private double computeMean(int[] hist) { + long sum = 0L; + long count = 0; + for (int i = 1; i < hist.length; i++) { + count += hist[i]; + sum += i * hist[i]; + } + return (double)sum / (double)count; + } + + private long[] entryTypes() { + long[] counts = new long[2]; + if (table == null) + return counts; + for (YNode te : table) { + if (te.isEmpty()) + counts[1]++; + else + counts[0]++; + } + return counts; + } + + private int[] incProbeCount(int[] counters, int probes) { + if (counters == null) + counters = new int[Math.max(probes + 1, 16)]; + else if (probes >= counters.length) + counters = Arrays.copyOf(counters, Math.max(probes + 1, counters.length * 2)); + counters[probes]++; + return counters; + } + + + // Returns a histogram array of the number of rehashs needed to find each key. + private int[] entryRehashes() { + if (table == null) + return new int[0]; + int[] counts = new int[table.length + 1]; + YNode[] tab = table; + int n = tab.length; + for (YNode te : tab) { + + if (!te.isValue()) + continue; + int h = te.hash; + int count; + final K key = te.key; + K k; + for (count = 1; count < tab.length; count++, h += REHASH_HASH) { + final YNode entry; + if ((entry = tab[(n - 1) & h]).isValue() && + entry.hash == te.hash && + ((k = entry.key) == key || (k != null && k.equals(key)))) { + break; + } + } + + counts[count]++; + } + + int i; + for (i = counts.length - 1; i >= 0 && counts[i] == 0; i--) { + } + counts = Arrays.copyOf(counts, i + 1); + return counts; + } + + private int[] minCounts(int[] counts) { + int i; + for (i = counts.length - 1; i >= 0 && counts[i] == 0; i--) { + } + counts = Arrays.copyOf(counts, i + 1); + return counts; + } + + private long heapSize() { + long acc = objectSizeMaybe(this); + return acc + objectSizeMaybe(table); + } + + private long objectSizeMaybe(Object o) { + try { + return (mObjectSize != null) + ? (long)mObjectSize.invoke(null, o) + : 0L; + } catch (IllegalAccessException | InvocationTargetException e) { + return 0L; + } + } + + private static boolean hasObjectSize = false; + private static Method mObjectSize = getObjectSizeMethod(); + + private static Method getObjectSizeMethod() { + try { + Method m = Objects.class.getDeclaredMethod("getObjectSize", Object.class); + hasObjectSize = true; + return m; + } catch (NoSuchMethodException nsme) { + return null; + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/HashMapBench.java b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/HashMapBench.java new file mode 100644 index 00000000000..4c049e5b8a4 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/HashMapBench.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2018, Red Hat, Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.bench.valhalla.sandbox.corelibs.corelibs.mapprotos; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import java.util.function.IntFunction; +import java.util.stream.IntStream; + +import static java.util.stream.Collectors.toMap; + +/** + * Benchmark (mapType) (size) Mode Cnt Score Error Units + * XHashMapBench.put HASH_MAP 1000000 avgt 5 214.470 +/- 44.063 ms/op + * XHashMapBench.put XHASH_MAP 1000000 avgt 5 215.772 +/- 31.595 ms/op + * XHashMapBench.putAllWithBigMapToEmptyMap HASH_MAP 1000000 avgt 5 126.472 +/- 38.452 ms/op + * XHashMapBench.putAllWithBigMapToEmptyMap XHASH_MAP 1000000 avgt 5 117.741 +/- 10.460 ms/op + * XHashMapBench.putAllWithBigMapToNonEmptyMap HASH_MAP 1000000 avgt 5 136.112 +/- 36.712 ms/op + * XHashMapBench.putAllWithBigMapToNonEmptyMap XHASH_MAP 1000000 avgt 5 144.681 +/- 8.755 ms/op + * Finished running test 'micro:valhalla.corelibs.XHashMapBench' + */ +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Fork(1) +@State(Scope.Thread) +public class HashMapBench { + private IntFunction> mapSupplier; + private Map bigMapToAdd; + + @Param("1000000") + private int size; + + @Param(value = { + "org.openjdk.bench.valhalla.corelibs.mapprotos.HashMap", +// "org.openjdk.bench.valhalla.corelibs.mapprotos.XHashMap", + "java.util.HashMap", + }) + private String mapType; + + @Setup + public void setup() { + try { + Class mapClass = Class.forName(mapType); + mapSupplier = (size) -> newInstance(mapClass, size); + } catch (Exception ex) { + System.out.printf("%s: %s%n", mapType, ex.getMessage()); + return; + } + + ThreadLocalRandom rnd = ThreadLocalRandom.current(); + this.bigMapToAdd = IntStream.range(0, size).boxed() + .collect(toMap(i -> 7 + i * 128, i -> rnd.nextInt())); + } + + Map newInstance(Class mapClass, int size) { + try { + return (Map)mapClass.getConstructor(int.class).newInstance(size); + } catch (Exception ex) { + throw new RuntimeException("failed", ex); + } + } + + @Benchmark + public int putAllWithBigMapToNonEmptyMap() { + Map map = mapSupplier.apply(16); + map.put(-1, -1); + map.putAll(bigMapToAdd); + return map.size(); + } + + @Benchmark + public int putAllWithBigMapToEmptyMap() { + Map map = mapSupplier.apply(16); + map.putAll(bigMapToAdd); + return map.size(); + } + + @Benchmark + public int put() { + Map map = mapSupplier.apply(16); + for (int k : bigMapToAdd.keySet()) { + map.put(k, bigMapToAdd.get(k)); + } + return map.size(); + } +} diff --git a/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/HashMapToArray.java b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/HashMapToArray.java new file mode 100644 index 00000000000..302a16da3a2 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/HashMapToArray.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.sandbox.corelibs.corelibs.mapprotos; + + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.IntFunction; +import java.util.concurrent.TimeUnit; + +/** + * Simple benchmark of toArray. + */ + +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@Fork(1) +@State(Scope.Thread) +public class HashMapToArray { + + private IntFunction> mapSupplier; + Map map; + + + @Param(value = { + "org.openjdk.bench.valhalla.corelibs.mapprotos.HashMap", +// "org.openjdk.bench.valhalla.corelibs.mapprotos.XHashMap", + "java.util.HashMap", + }) + private String mapType; + + @Param({"1", "10", "1000", "100000"}) + public int size; + + @Setup + public void setup() { + try { + Class mapClass = Class.forName(mapType); + mapSupplier = (size) -> newInstance(mapClass, size); + } catch (Exception ex) { + System.out.printf("%s: %s%n", mapType, ex.getMessage()); + return; + } + + map = mapSupplier.apply(0); + for (int i = 0; i < size; i++) { + map.put(i, i * i); + } + } + + Map newInstance(Class mapClass, int size) { + try { + return (Map)mapClass.getConstructor(int.class).newInstance(size); + } catch (Exception ex) { + throw new RuntimeException("failed", ex); + } + } + + @Benchmark + public Object[] testKeySetToArray() { + return map.keySet().toArray(); + } + + @Benchmark + public Object[] testKeySetToArrayTyped() { + return map.keySet().toArray(new Integer[0]); + } + + @Benchmark + public Object[] testValuesToArray() { + return map.values().toArray(); + } + + @Benchmark + public Object[] testValuesToArrayTyped() { + return map.values().toArray(new Integer[0]); + } +} diff --git a/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/MapBase.java b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/MapBase.java new file mode 100644 index 00000000000..a6b2072f3af --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/MapBase.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.sandbox.corelibs.corelibs.mapprotos; + +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; + +@Fork(1) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Thread) +public class MapBase { + + @Param({ + "11", + "767", + "1572863", + }) + public int size; + + @Param({ +// "0", + "42", + }) + public int seed; + + @Param(value = { + "java.util.HashMap", + "org.openjdk.bench.valhalla.corelibs.mapprotos.HashMap", +// "org.openjdk.bench.valhalla.corelibs.mapprotos.XHashMap", +// "java.util.HashMap0", + }) + public String mapType; + + public Random rnd; + public Integer[] keys; + public Integer[] nonKeys; + + public void init(int size) { + Integer[] all; + if (seed != 0) { + rnd = new Random(seed); + all = rnd.ints().distinct().limit(size * 2).boxed().toArray(Integer[]::new); + Collections.shuffle(Arrays.asList(all), rnd); + } else { + rnd = new Random(); + all = IntStream.range(0, size * 2).boxed().toArray(Integer[]::new); + Collections.shuffle(Arrays.asList(all)); + } + keys = Arrays.copyOfRange(all, 0, size); + nonKeys = Arrays.copyOfRange(all, size, size * 2); + } + + void TearDown(Map map) { + try { + Method m = map.getClass().getMethod("dumpStats", java.io.PrintStream.class); + m.invoke(map, System.out); + } catch (Throwable nsme) { + System.out.println("Stats not available:"); + } + } +} diff --git a/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/PutX.java b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/PutX.java new file mode 100644 index 00000000000..6fab343d9f3 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/PutX.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.sandbox.corelibs.corelibs.mapprotos; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.IntFunction; + + +public class PutX extends MapBase { + IntFunction> mapSupplier; + Map lastMap; + + @Setup + public void setup() { + super.init(size); + try { + Class mapClass = Class.forName(mapType); + mapSupplier = (size) -> newInstance(mapClass, size); + } catch (Exception ex) { + System.out.printf("%s: %s%n", mapType, ex.getMessage()); + return; + } + } + + Map newInstance(Class mapClass, int size) { + try { + return (Map)mapClass.getConstructor(int.class).newInstance(size); + } catch (Exception ex) { + throw new RuntimeException("failed", ex); + } + } + + @TearDown + public void teardown() { + super.TearDown(lastMap); + } + + @Benchmark + public Map put() { + Integer[] keys = this.keys; + Map map = mapSupplier.apply(0); + for (Integer k : keys) { + map.put(k, k); + } + lastMap = map; + return map; + } + + @Benchmark + public Map putSized() { + Integer[] keys = this.keys; + Map map = mapSupplier.apply(size * 2); + for (Integer k : keys) { + map.put(k, k); + } + lastMap = map; + return map; + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/README.md b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/README.md new file mode 100644 index 00000000000..28da448fc41 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/README.md @@ -0,0 +1,31 @@ +# Prototype implementations of HashMaps using inline classes for the table entries. + + **NOTE: The implementations have NOT been optimized or tuned.** + +## HashMap uses Open Addressing to store all entries in a table of inline classes +The hash of the key is used as the first index into the table. +If there is a collision, double hashing (with a static offset) is used to probe subsequent locations for available storage. +The Robin Hood hashing variation on insertion is used to reduce worst case lookup times. +On key removal, the subsequent double-hashed entries are compressed into the entry being removed. + +### HashMap Storage requirements +Typical storage usage for a table near its load factor is 22 bytes per entry. + +Inserting entries into the HashMap may resize the table but otherwise does +not use any additional memory on each get or put. + +## XHashMap stores the initial entry in a table of inline classes +The hash of the key is used as the first index into the table. +If there is a collision, subsequent entries add the familiar link list of Nodes. +On key removal, direct entries in the table are replaced by the first linked node; +for Nodes in the link list, the Node is unlinked. + +### XHashMap Storage requirements: +Typical storage usage for a table near its load factor is 32 bytes per entry. + +## java.util.HashMap (the original) +HashMap uses a table of references to the initial Node entries, collisions are handled by +linked Nodes. + +### HashMap Storage requirements: +Typical storage usage for a table near its load factor is 37 bytes per entry. diff --git a/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/ReplX.java b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/ReplX.java new file mode 100644 index 00000000000..dd8a1a0f33d --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/ReplX.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.sandbox.corelibs.corelibs.mapprotos; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Setup; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.IntFunction; + +public class ReplX extends MapBase { + IntFunction> mapSupplier; + Integer[] mixed; + Map map; + + @Setup + public void setup() { + super.init(size); + try { + Class mapClass = Class.forName(mapType); + mapSupplier = (size) -> newInstance(mapClass, size); + + map = mapSupplier.apply(0); + for (Integer k : keys) { + map.put(k, k); + } + mixed = new Integer[size]; + System.arraycopy(keys, 0, mixed, 0, size / 2); + System.arraycopy(nonKeys, 0, mixed, size / 2, size / 2); + Collections.shuffle(Arrays.asList(mixed), rnd); + } catch (Exception ex) { + System.out.printf("%s: %s%n", mapType, ex.getMessage()); + return; + } + } + + Map newInstance(Class mapClass, int size) { + try { + return (Map)mapClass.getConstructor(int.class).newInstance(size); + } catch (Exception ex) { + throw new RuntimeException("failed", ex); + } + } + + @Benchmark + public Map replace() { + Integer[] keys = this.keys; + for (Integer k : mixed) { + if (map.size() < size) { + map.put(k, k); + } else { + map.remove(k); + } + } + return map; + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/WalkX.java b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/WalkX.java new file mode 100644 index 00000000000..19f5c32b942 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/WalkX.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.sandbox.corelibs.corelibs.mapprotos; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import java.util.Iterator; +import java.util.HashMap; +import java.util.Map; +import java.util.function.IntFunction; + +public class WalkX extends MapBase { + + IntFunction> mapSupplier; + Map map; + + @Setup + public void setup() { + super.init(size); + try { + Class mapClass = Class.forName(mapType); + mapSupplier = (size) -> newInstance(mapClass, size); + } catch (Exception ex) { + System.out.printf("%s: %s%n", mapType, ex.getMessage()); + return; + } + + map = mapSupplier.apply(0); + for (Integer k : keys) { + map.put(k, k); + } + } + + Map newInstance(Class mapClass, int size) { + try { + return (Map)mapClass.getConstructor(int.class).newInstance(size); + } catch (Exception ex) { + throw new RuntimeException("failed", ex); + } + } + + @Benchmark + public int sumIterator() { + int s = 0; + for (Iterator iterator = map.keySet().iterator(); iterator.hasNext(); ) { + s += iterator.next(); + } + return s; + } + + @Benchmark + public int sumIteratorHidden() { + int s = 0; + for (Iterator iterator = hide(map.keySet().iterator()); iterator.hasNext(); ) { + s += iterator.next(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static Iterator hide(Iterator it) { + return it; + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/XAbstractMap.java b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/XAbstractMap.java new file mode 100644 index 00000000000..e7dd65f430d --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/XAbstractMap.java @@ -0,0 +1,863 @@ +/* + * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved. + * 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 org.openjdk.bench.valhalla.sandbox.corelibs.corelibs.mapprotos; + +import java.util.AbstractCollection; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * This class provides a skeletal implementation of the {@code Map} + * interface, to minimize the effort required to implement this interface. + * + *

    To implement an unmodifiable map, the programmer needs only to extend this + * class and provide an implementation for the {@code entrySet} method, which + * returns a set-view of the map's mappings. Typically, the returned set + * will, in turn, be implemented atop {@code AbstractSet}. This set should + * not support the {@code add} or {@code remove} methods, and its iterator + * should not support the {@code remove} method. + * + *

    To implement a modifiable map, the programmer must additionally override + * this class's {@code put} method (which otherwise throws an + * {@code UnsupportedOperationException}), and the iterator returned by + * {@code entrySet().iterator()} must additionally implement its + * {@code remove} method. + * + *

    The programmer should generally provide a void (no argument) and map + * constructor, as per the recommendation in the {@code Map} interface + * specification. + * + *

    The documentation for each non-abstract method in this class describes its + * implementation in detail. Each of these methods may be overridden if the + * map being implemented admits a more efficient implementation. + * + *

    This class is a member of the + * + * Java Collections Framework. + * + * @param the type of keys maintained by this map + * @param the type of mapped values + * + * @author Josh Bloch + * @author Neal Gafter + * @see Map + * @see Collection + * @since 1.2 + */ + +public abstract class XAbstractMap implements Map { + /** + * Sole constructor. (For invocation by subclass constructors, typically + * implicit.) + */ + protected XAbstractMap() { + } + + // Query Operations + + /** + * {@inheritDoc} + * + * @implSpec + * This implementation returns {@code entrySet().size()}. + */ + public int size() { + return entrySet().size(); + } + + /** + * {@inheritDoc} + * + * @implSpec + * This implementation returns {@code size() == 0}. + */ + public boolean isEmpty() { + return size() == 0; + } + + /** + * {@inheritDoc} + * + * @implSpec + * This implementation iterates over {@code entrySet()} searching + * for an entry with the specified value. If such an entry is found, + * {@code true} is returned. If the iteration terminates without + * finding such an entry, {@code false} is returned. Note that this + * implementation requires linear time in the size of the map. + * + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + */ + public boolean containsValue(Object value) { + Iterator> i = entrySet().iterator(); + if (value==null) { + while (i.hasNext()) { + Entry e = i.next(); + if (e.getValue()==null) + return true; + } + } else { + while (i.hasNext()) { + Entry e = i.next(); + if (value.equals(e.getValue())) + return true; + } + } + return false; + } + + /** + * {@inheritDoc} + * + * @implSpec + * This implementation iterates over {@code entrySet()} searching + * for an entry with the specified key. If such an entry is found, + * {@code true} is returned. If the iteration terminates without + * finding such an entry, {@code false} is returned. Note that this + * implementation requires linear time in the size of the map; many + * implementations will override this method. + * + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + */ + public boolean containsKey(Object key) { + Iterator> i = entrySet().iterator(); + if (key==null) { + while (i.hasNext()) { + Entry e = i.next(); + if (e.getKey()==null) + return true; + } + } else { + while (i.hasNext()) { + Entry e = i.next(); + if (key.equals(e.getKey())) + return true; + } + } + return false; + } + + /** + * {@inheritDoc} + * + * @implSpec + * This implementation iterates over {@code entrySet()} searching + * for an entry with the specified key. If such an entry is found, + * the entry's value is returned. If the iteration terminates without + * finding such an entry, {@code null} is returned. Note that this + * implementation requires linear time in the size of the map; many + * implementations will override this method. + * + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + */ + public V get(Object key) { + Iterator> i = entrySet().iterator(); + if (key==null) { + while (i.hasNext()) { + Entry e = i.next(); + if (e.getKey()==null) + return e.getValue(); + } + } else { + while (i.hasNext()) { + Entry e = i.next(); + if (key.equals(e.getKey())) + return e.getValue(); + } + } + return null; + } + + + // Modification Operations + + /** + * {@inheritDoc} + * + * @implSpec + * This implementation always throws an + * {@code UnsupportedOperationException}. + * + * @throws UnsupportedOperationException {@inheritDoc} + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} + */ + public V put(K key, V value) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * + * @implSpec + * This implementation iterates over {@code entrySet()} searching for an + * entry with the specified key. If such an entry is found, its value is + * obtained with its {@code getValue} operation, the entry is removed + * from the collection (and the backing map) with the iterator's + * {@code remove} operation, and the saved value is returned. If the + * iteration terminates without finding such an entry, {@code null} is + * returned. Note that this implementation requires linear time in the + * size of the map; many implementations will override this method. + * + *

    Note that this implementation throws an + * {@code UnsupportedOperationException} if the {@code entrySet} + * iterator does not support the {@code remove} method and this map + * contains a mapping for the specified key. + * + * @throws UnsupportedOperationException {@inheritDoc} + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + */ + public V remove(Object key) { + Iterator> i = entrySet().iterator(); + Entry correctEntry = null; + if (key==null) { + while (correctEntry==null && i.hasNext()) { + Entry e = i.next(); + if (e.getKey()==null) + correctEntry = e; + } + } else { + while (correctEntry==null && i.hasNext()) { + Entry e = i.next(); + if (key.equals(e.getKey())) + correctEntry = e; + } + } + + V oldValue = null; + if (correctEntry !=null) { + oldValue = correctEntry.getValue(); + i.remove(); + } + return oldValue; + } + + + // Bulk Operations + + /** + * {@inheritDoc} + * + * @implSpec + * This implementation iterates over the specified map's + * {@code entrySet()} collection, and calls this map's {@code put} + * operation once for each entry returned by the iteration. + * + *

    Note that this implementation throws an + * {@code UnsupportedOperationException} if this map does not support + * the {@code put} operation and the specified map is nonempty. + * + * @throws UnsupportedOperationException {@inheritDoc} + * @throws ClassCastException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} + */ + public void putAll(Map m) { + for (Map.Entry e : m.entrySet()) + put(e.getKey(), e.getValue()); + } + + /** + * {@inheritDoc} + * + * @implSpec + * This implementation calls {@code entrySet().clear()}. + * + *

    Note that this implementation throws an + * {@code UnsupportedOperationException} if the {@code entrySet} + * does not support the {@code clear} operation. + * + * @throws UnsupportedOperationException {@inheritDoc} + */ + public void clear() { + entrySet().clear(); + } + + + // Views + + /** + * Each of these fields are initialized to contain an instance of the + * appropriate view the first time this view is requested. The views are + * stateless, so there's no reason to create more than one of each. + * + *

    Since there is no synchronization performed while accessing these fields, + * it is expected that java.util.Map view classes using these fields have + * no non-final fields (or any fields at all except for outer-this). Adhering + * to this rule would make the races on these fields benign. + * + *

    It is also imperative that implementations read the field only once, + * as in: + * + *

     {@code
    +     * public Set keySet() {
    +     *   Set ks = keySet;  // single racy read
    +     *   if (ks == null) {
    +     *     ks = new KeySet();
    +     *     keySet = ks;
    +     *   }
    +     *   return ks;
    +     * }
    +     *}
    + */ + transient Set keySet; + transient Collection values; + + /** + * {@inheritDoc} + * + * @implSpec + * This implementation returns a set that subclasses {@link AbstractSet}. + * The subclass's iterator method returns a "wrapper object" over this + * map's {@code entrySet()} iterator. The {@code size} method + * delegates to this map's {@code size} method and the + * {@code contains} method delegates to this map's + * {@code containsKey} method. + * + *

    The set is created the first time this method is called, + * and returned in response to all subsequent calls. No synchronization + * is performed, so there is a slight chance that multiple calls to this + * method will not all return the same set. + */ + public Set keySet() { + Set ks = keySet; + if (ks == null) { + ks = new AbstractSet() { + public Iterator iterator() { + return new Iterator() { + private Iterator> i = entrySet().iterator(); + + public boolean hasNext() { + return i.hasNext(); + } + + public K next() { + return i.next().getKey(); + } + + public void remove() { + i.remove(); + } + }; + } + + public int size() { + return XAbstractMap.this.size(); + } + + public boolean isEmpty() { + return XAbstractMap.this.isEmpty(); + } + + public void clear() { + XAbstractMap.this.clear(); + } + + public boolean contains(Object k) { + return XAbstractMap.this.containsKey(k); + } + }; + keySet = ks; + } + return ks; + } + + /** + * {@inheritDoc} + * + * @implSpec + * This implementation returns a collection that subclasses {@link + * AbstractCollection}. The subclass's iterator method returns a + * "wrapper object" over this map's {@code entrySet()} iterator. + * The {@code size} method delegates to this map's {@code size} + * method and the {@code contains} method delegates to this map's + * {@code containsValue} method. + * + *

    The collection is created the first time this method is called, and + * returned in response to all subsequent calls. No synchronization is + * performed, so there is a slight chance that multiple calls to this + * method will not all return the same collection. + */ + public Collection values() { + Collection vals = values; + if (vals == null) { + vals = new AbstractCollection() { + public Iterator iterator() { + return new Iterator() { + private Iterator> i = entrySet().iterator(); + + public boolean hasNext() { + return i.hasNext(); + } + + public V next() { + return i.next().getValue(); + } + + public void remove() { + i.remove(); + } + }; + } + + public int size() { + return XAbstractMap.this.size(); + } + + public boolean isEmpty() { + return XAbstractMap.this.isEmpty(); + } + + public void clear() { + XAbstractMap.this.clear(); + } + + public boolean contains(Object v) { + return XAbstractMap.this.containsValue(v); + } + }; + values = vals; + } + return vals; + } + + public abstract Set> entrySet(); + + + // Comparison and hashing + + /** + * Compares the specified object with this map for equality. Returns + * {@code true} if the given object is also a map and the two maps + * represent the same mappings. More formally, two maps {@code m1} and + * {@code m2} represent the same mappings if + * {@code m1.entrySet().equals(m2.entrySet())}. This ensures that the + * {@code equals} method works properly across different implementations + * of the {@code Map} interface. + * + * @implSpec + * This implementation first checks if the specified object is this map; + * if so it returns {@code true}. Then, it checks if the specified + * object is a map whose size is identical to the size of this map; if + * not, it returns {@code false}. If so, it iterates over this map's + * {@code entrySet} collection, and checks that the specified map + * contains each mapping that this map contains. If the specified map + * fails to contain such a mapping, {@code false} is returned. If the + * iteration completes, {@code true} is returned. + * + * @param o object to be compared for equality with this map + * @return {@code true} if the specified object is equal to this map + */ + public boolean equals(Object o) { + if (o == this) + return true; + + if (!(o instanceof Map)) + return false; + Map m = (Map) o; + if (m.size() != size()) + return false; + + try { + for (Entry e : entrySet()) { + K key = e.getKey(); + V value = e.getValue(); + if (value == null) { + if (!(m.get(key) == null && m.containsKey(key))) + return false; + } else { + if (!value.equals(m.get(key))) + return false; + } + } + } catch (ClassCastException unused) { + return false; + } catch (NullPointerException unused) { + return false; + } + + return true; + } + + /** + * Returns the hash code value for this map. The hash code of a map is + * defined to be the sum of the hash codes of each entry in the map's + * {@code entrySet()} view. This ensures that {@code m1.equals(m2)} + * implies that {@code m1.hashCode()==m2.hashCode()} for any two maps + * {@code m1} and {@code m2}, as required by the general contract of + * {@link Object#hashCode}. + * + * @implSpec + * This implementation iterates over {@code entrySet()}, calling + * {@link Map.Entry#hashCode hashCode()} on each element (entry) in the + * set, and adding up the results. + * + * @return the hash code value for this map + * @see Map.Entry#hashCode() + * @see Object#equals(Object) + * @see Set#equals(Object) + */ + public int hashCode() { + int h = 0; + for (Entry entry : entrySet()) + h += entry.hashCode(); + return h; + } + + /** + * Returns a string representation of this map. The string representation + * consists of a list of key-value mappings in the order returned by the + * map's {@code entrySet} view's iterator, enclosed in braces + * ({@code "{}"}). Adjacent mappings are separated by the characters + * {@code ", "} (comma and space). Each key-value mapping is rendered as + * the key followed by an equals sign ({@code "="}) followed by the + * associated value. Keys and values are converted to strings as by + * {@link String#valueOf(Object)}. + * + * @return a string representation of this map + */ + public String toString() { + Iterator> i = entrySet().iterator(); + if (! i.hasNext()) + return "{}"; + + StringBuilder sb = new StringBuilder(); + sb.append('{'); + for (;;) { + Entry e = i.next(); + K key = e.getKey(); + V value = e.getValue(); + sb.append(key == this ? "(this Map)" : key); + sb.append('='); + sb.append(value == this ? "(this Map)" : value); + if (! i.hasNext()) + return sb.append('}').toString(); + sb.append(',').append(' '); + } + } + + /** + * Returns a shallow copy of this {@code AbstractMap} instance: the keys + * and values themselves are not cloned. + * + * @return a shallow copy of this map + */ + protected Object clone() throws CloneNotSupportedException { + XAbstractMap result = (XAbstractMap)super.clone(); + result.keySet = null; + result.values = null; + return result; + } + + /** + * Utility method for SimpleEntry and SimpleImmutableEntry. + * Test for equality, checking for nulls. + * + * NB: Do not replace with Object.equals until JDK-8015417 is resolved. + */ + private static boolean eq(Object o1, Object o2) { + return o1 == null ? o2 == null : o1.equals(o2); + } + + // Implementation Note: SimpleEntry and SimpleImmutableEntry + // are distinct unrelated classes, even though they share + // some code. Since you can't add or subtract final-ness + // of a field in a subclass, they can't share representations, + // and the amount of duplicated code is too small to warrant + // exposing a common abstract class. + + + /** + * An Entry maintaining a key and a value. The value may be + * changed using the {@code setValue} method. This class + * facilitates the process of building custom map + * implementations. For example, it may be convenient to return + * arrays of {@code SimpleEntry} instances in method + * {@code Map.entrySet().toArray}. + * + * @since 1.6 + */ + public static class SimpleEntry + implements Entry, java.io.Serializable + { + private static final long serialVersionUID = -8499721149061103585L; + + private final K key; + private V value; + + /** + * Creates an entry representing a mapping from the specified + * key to the specified value. + * + * @param key the key represented by this entry + * @param value the value represented by this entry + */ + public SimpleEntry(K key, V value) { + this.key = key; + this.value = value; + } + + /** + * Creates an entry representing the same mapping as the + * specified entry. + * + * @param entry the entry to copy + */ + public SimpleEntry(Entry entry) { + this.key = entry.getKey(); + this.value = entry.getValue(); + } + + /** + * Returns the key corresponding to this entry. + * + * @return the key corresponding to this entry + */ + public K getKey() { + return key; + } + + /** + * Returns the value corresponding to this entry. + * + * @return the value corresponding to this entry + */ + public V getValue() { + return value; + } + + /** + * Replaces the value corresponding to this entry with the specified + * value. + * + * @param value new value to be stored in this entry + * @return the old value corresponding to the entry + */ + public V setValue(V value) { + V oldValue = this.value; + this.value = value; + return oldValue; + } + + /** + * Compares the specified object with this entry for equality. + * Returns {@code true} if the given object is also a map entry and + * the two entries represent the same mapping. More formally, two + * entries {@code e1} and {@code e2} represent the same mapping + * if

    +         *   (e1.getKey()==null ?
    +         *    e2.getKey()==null :
    +         *    e1.getKey().equals(e2.getKey()))
    +         *   &&
    +         *   (e1.getValue()==null ?
    +         *    e2.getValue()==null :
    +         *    e1.getValue().equals(e2.getValue()))
    + * This ensures that the {@code equals} method works properly across + * different implementations of the {@code Map.Entry} interface. + * + * @param o object to be compared for equality with this map entry + * @return {@code true} if the specified object is equal to this map + * entry + * @see #hashCode + */ + public boolean equals(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry)o; + return eq(key, e.getKey()) && eq(value, e.getValue()); + } + + /** + * Returns the hash code value for this map entry. The hash code + * of a map entry {@code e} is defined to be:
    +         *   (e.getKey()==null   ? 0 : e.getKey().hashCode()) ^
    +         *   (e.getValue()==null ? 0 : e.getValue().hashCode())
    + * This ensures that {@code e1.equals(e2)} implies that + * {@code e1.hashCode()==e2.hashCode()} for any two Entries + * {@code e1} and {@code e2}, as required by the general + * contract of {@link Object#hashCode}. + * + * @return the hash code value for this map entry + * @see #equals + */ + public int hashCode() { + return (key == null ? 0 : key.hashCode()) ^ + (value == null ? 0 : value.hashCode()); + } + + /** + * Returns a String representation of this map entry. This + * implementation returns the string representation of this + * entry's key followed by the equals character ("{@code =}") + * followed by the string representation of this entry's value. + * + * @return a String representation of this map entry + */ + public String toString() { + return key + "=" + value; + } + + } + + /** + * An Entry maintaining an immutable key and value. This class + * does not support method {@code setValue}. This class may be + * convenient in methods that return thread-safe snapshots of + * key-value mappings. + * + * @since 1.6 + */ + public static class SimpleImmutableEntry + implements Entry, java.io.Serializable + { + private static final long serialVersionUID = 7138329143949025153L; + + private final K key; + private final V value; + + /** + * Creates an entry representing a mapping from the specified + * key to the specified value. + * + * @param key the key represented by this entry + * @param value the value represented by this entry + */ + public SimpleImmutableEntry(K key, V value) { + this.key = key; + this.value = value; + } + + /** + * Creates an entry representing the same mapping as the + * specified entry. + * + * @param entry the entry to copy + */ + public SimpleImmutableEntry(Entry entry) { + this.key = entry.getKey(); + this.value = entry.getValue(); + } + + /** + * Returns the key corresponding to this entry. + * + * @return the key corresponding to this entry + */ + public K getKey() { + return key; + } + + /** + * Returns the value corresponding to this entry. + * + * @return the value corresponding to this entry + */ + public V getValue() { + return value; + } + + /** + * Replaces the value corresponding to this entry with the specified + * value (optional operation). This implementation simply throws + * {@code UnsupportedOperationException}, as this class implements + * an immutable map entry. + * + * @param value new value to be stored in this entry + * @return (Does not return) + * @throws UnsupportedOperationException always + */ + public V setValue(V value) { + throw new UnsupportedOperationException(); + } + + /** + * Compares the specified object with this entry for equality. + * Returns {@code true} if the given object is also a map entry and + * the two entries represent the same mapping. More formally, two + * entries {@code e1} and {@code e2} represent the same mapping + * if
    +         *   (e1.getKey()==null ?
    +         *    e2.getKey()==null :
    +         *    e1.getKey().equals(e2.getKey()))
    +         *   &&
    +         *   (e1.getValue()==null ?
    +         *    e2.getValue()==null :
    +         *    e1.getValue().equals(e2.getValue()))
    + * This ensures that the {@code equals} method works properly across + * different implementations of the {@code Map.Entry} interface. + * + * @param o object to be compared for equality with this map entry + * @return {@code true} if the specified object is equal to this map + * entry + * @see #hashCode + */ + public boolean equals(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry)o; + return eq(key, e.getKey()) && eq(value, e.getValue()); + } + + /** + * Returns the hash code value for this map entry. The hash code + * of a map entry {@code e} is defined to be:
    +         *   (e.getKey()==null   ? 0 : e.getKey().hashCode()) ^
    +         *   (e.getValue()==null ? 0 : e.getValue().hashCode())
    + * This ensures that {@code e1.equals(e2)} implies that + * {@code e1.hashCode()==e2.hashCode()} for any two Entries + * {@code e1} and {@code e2}, as required by the general + * contract of {@link Object#hashCode}. + * + * @return the hash code value for this map entry + * @see #equals + */ + public int hashCode() { + return (key == null ? 0 : key.hashCode()) ^ + (value == null ? 0 : value.hashCode()); + } + + /** + * Returns a String representation of this map entry. This + * implementation returns the string representation of this + * entry's key followed by the equals character ("{@code =}") + * followed by the string representation of this entry's value. + * + * @return a String representation of this map entry + */ + public String toString() { + return key + "=" + value; + } + + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/XHashMap.java b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/XHashMap.java new file mode 100644 index 00000000000..71f52725112 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sandbox/corelibs/mapprotos/XHashMap.java @@ -0,0 +1,2376 @@ +/* + * 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 + * 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 org.openjdk.bench.valhalla.sandbox.corelibs.corelibs.mapprotos; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.PrintStream; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.AbstractCollection; +//import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.ConcurrentModificationException; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.TreeMap; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Hash map implementation that uses inline class entries in the initial table + * and maintains a link list of separate Node entries for key/value pairs + * that have the same hash value. The handling of the link list is the same + * as the original java.util.HashMap. + * The primary entry array is larger than in HashMap due to the inline storage + * of the entries but since it replaces the separate Node instance for the first + * Node, the overall memory usage is less for a reasonably full table. + * The TreeNode organization is not yet implemented. + *

    + * Hash table based implementation of the {@code Map} interface. This + * implementation provides all of the optional map operations, and permits + * {@code null} values and the {@code null} key. (The {@code HashMap} + * class is roughly equivalent to {@code Hashtable}, except that it is + * unsynchronized and permits nulls.) This class makes no guarantees as to + * the order of the map; in particular, it does not guarantee that the order + * will remain constant over time. + * + *

    This implementation provides constant-time performance for the basic + * operations ({@code get} and {@code put}), assuming the hash function + * disperses the elements properly among the buckets. Iteration over + * collection views requires time proportional to the "capacity" of the + * {@code HashMap} instance (the number of buckets) plus its size (the number + * of key-value mappings). Thus, it's very important not to set the initial + * capacity too high (or the load factor too low) if iteration performance is + * important. + * + *

    An instance of {@code HashMap} has two parameters that affect its + * performance: initial capacity and load factor. The + * capacity is the number of buckets in the hash table, and the initial + * capacity is simply the capacity at the time the hash table is created. The + * load factor is a measure of how full the hash table is allowed to + * get before its capacity is automatically increased. When the number of + * entries in the hash table exceeds the product of the load factor and the + * current capacity, the hash table is rehashed (that is, internal data + * structures are rebuilt) so that the hash table has approximately twice the + * number of buckets. + * + *

    As a general rule, the default load factor (.75) offers a good + * tradeoff between time and space costs. Higher values decrease the + * space overhead but increase the lookup cost (reflected in most of + * the operations of the {@code HashMap} class, including + * {@code get} and {@code put}). The expected number of entries in + * the map and its load factor should be taken into account when + * setting its initial capacity, so as to minimize the number of + * rehash operations. If the initial capacity is greater than the + * maximum number of entries divided by the load factor, no rehash + * operations will ever occur. + * + *

    If many mappings are to be stored in a {@code HashMap} + * instance, creating it with a sufficiently large capacity will allow + * the mappings to be stored more efficiently than letting it perform + * automatic rehashing as needed to grow the table. Note that using + * many keys with the same {@code hashCode()} is a sure way to slow + * down performance of any hash table. To ameliorate impact, when keys + * are {@link Comparable}, this class may use comparison order among + * keys to help break ties. + * + *

    Note that this implementation is not synchronized. + * If multiple threads access a hash map concurrently, and at least one of + * the threads modifies the map structurally, it must be + * synchronized externally. (A structural modification is any operation + * that adds or deletes one or more mappings; merely changing the value + * associated with a key that an instance already contains is not a + * structural modification.) This is typically accomplished by + * synchronizing on some object that naturally encapsulates the map. + * + * If no such object exists, the map should be "wrapped" using the + * {@link Collections#synchronizedMap Collections.synchronizedMap} + * method. This is best done at creation time, to prevent accidental + * unsynchronized access to the map:

    + *   Map m = Collections.synchronizedMap(new HashMap(...));
    + * + *

    The iterators returned by all of this class's "collection view methods" + * are fail-fast: if the map is structurally modified at any time after + * the iterator is created, in any way except through the iterator's own + * {@code remove} method, the iterator will throw a + * {@link ConcurrentModificationException}. Thus, in the face of concurrent + * modification, the iterator fails quickly and cleanly, rather than risking + * arbitrary, non-deterministic behavior at an undetermined time in the + * future. + * + *

    Note that the fail-fast behavior of an iterator cannot be guaranteed + * as it is, generally speaking, impossible to make any hard guarantees in the + * presence of unsynchronized concurrent modification. Fail-fast iterators + * throw {@code ConcurrentModificationException} on a best-effort basis. + * Therefore, it would be wrong to write a program that depended on this + * exception for its correctness: the fail-fast behavior of iterators + * should be used only to detect bugs. + * + *

    This class is a member of the + * + * Java Collections Framework. + * + * @param the type of keys maintained by this map + * @param the type of mapped values + * + * @author Doug Lea + * @author Josh Bloch + * @author Arthur van Hoff + * @author Neal Gafter + * @see Object#hashCode() + * @see Collection + * @see Map + * @see TreeMap + * @see Hashtable + * @since 1.2 + */ +public class XHashMap extends XAbstractMap + implements Map, Cloneable, Serializable { + + private static final long serialVersionUID = 362498820763181265L; + + /* + * Implementation notes. + * + * This map usually acts as a binned (bucketed) hash table, but + * when bins get too large, they are transformed into bins of + * TreeNodes, each structured similarly to those in + * java.util.TreeMap. Most methods try to use normal bins, but + * relay to TreeNode methods when applicable (simply by checking + * instanceof a node). Bins of TreeNodes may be traversed and + * used like any others, but additionally support faster lookup + * when overpopulated. However, since the vast majority of bins in + * normal use are not overpopulated, checking for existence of + * tree bins may be delayed in the course of table methods. + * + * Tree bins (i.e., bins whose elements are all TreeNodes) are + * ordered primarily by hashCode, but in the case of ties, if two + * elements are of the same "class C implements Comparable", + * type then their compareTo method is used for ordering. (We + * conservatively check generic types via reflection to validate + * this -- see method comparableClassFor). The added complexity + * of tree bins is worthwhile in providing worst-case O(log n) + * operations when keys either have distinct hashes or are + * orderable, Thus, performance degrades gracefully under + * accidental or malicious usages in which hashCode() methods + * return values that are poorly distributed, as well as those in + * which many keys share a hashCode, so long as they are also + * Comparable. (If neither of these apply, we may waste about a + * factor of two in time and space compared to taking no + * precautions. But the only known cases stem from poor user + * programming practices that are already so slow that this makes + * little difference.) + * + * Because TreeNodes are about twice the size of regular nodes, we + * use them only when bins contain enough nodes to warrant use + * (see TREEIFY_THRESHOLD). And when they become too small (due to + * removal or resizing) they are converted back to plain bins. In + * usages with well-distributed user hashCodes, tree bins are + * rarely used. Ideally, under random hashCodes, the frequency of + * nodes in bins follows a Poisson distribution + * (http://en.wikipedia.org/wiki/Poisson_distribution) with a + * parameter of about 0.5 on average for the default resizing + * threshold of 0.75, although with a large variance because of + * resizing granularity. Ignoring variance, the expected + * occurrences of list size k are (exp(-0.5) * pow(0.5, k) / + * factorial(k)). The first values are: + * + * 0: 0.60653066 + * 1: 0.30326533 + * 2: 0.07581633 + * 3: 0.01263606 + * 4: 0.00157952 + * 5: 0.00015795 + * 6: 0.00001316 + * 7: 0.00000094 + * 8: 0.00000006 + * more: less than 1 in ten million + * + * The root of a tree bin is normally its first node. However, + * sometimes (currently only upon Iterator.remove), the root might + * be elsewhere, but can be recovered following parent links + * (method TreeNode.root()). + * + * All applicable internal methods accept a hash code as an + * argument (as normally supplied from a public method), allowing + * them to call each other without recomputing user hashCodes. + * Most internal methods also accept a "tab" argument, that is + * normally the current table, but may be a new or old one when + * resizing or converting. + * + * When bin lists are treeified, split, or untreeified, we keep + * them in the same relative access/traversal order (i.e., field + * Node.next) to better preserve locality, and to slightly + * simplify handling of splits and traversals that invoke + * iterator.remove. When using comparators on insertion, to keep a + * total ordering (or as close as is required here) across + * rebalancings, we compare classes and identityHashCodes as + * tie-breakers. + * + * The use and transitions among plain vs tree modes is + * complicated by the existence of subclass LinkedHashMap. See + * below for hook methods defined to be invoked upon insertion, + * removal and access that allow LinkedHashMap internals to + * otherwise remain independent of these mechanics. (This also + * requires that a map instance be passed to some utility methods + * that may create new nodes.) + * + * The concurrent-programming-like SSA-based coding style helps + * avoid aliasing errors amid all of the twisty pointer operations. + */ + + /** + * The default initial capacity - MUST be a power of two. + */ + static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 + + /** + * The maximum capacity, used if a higher value is implicitly specified + * by either of the constructors with arguments. + * MUST be a power of two <= 1<<30. + */ + static final int MAXIMUM_CAPACITY = 1 << 30; + + /** + * The load factor used when none specified in constructor. + */ + static final float DEFAULT_LOAD_FACTOR = 0.75f; + + /** + * The bin count threshold for using a tree rather than list for a + * bin. Bins are converted to trees when adding an element to a + * bin with at least this many nodes. The value must be greater + * than 2 and should be at least 8 to mesh with assumptions in + * tree removal about conversion back to plain bins upon + * shrinkage. + */ + static final int TREEIFY_THRESHOLD = 8; + + /** + * The bin count threshold for untreeifying a (split) bin during a + * resize operation. Should be less than TREEIFY_THRESHOLD, and at + * most 6 to mesh with shrinkage detection under removal. + */ + static final int UNTREEIFY_THRESHOLD = 6; + + /** + * The smallest table capacity for which bins may be treeified. + * (Otherwise the table is resized if too many nodes in a bin.) + * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts + * between resizing and treeification thresholds. + */ + static final int MIN_TREEIFY_CAPACITY = 64; + + private XNode emptyXNode() { + return new XNode(); + } + /** + * Basic hash bin node, used for most entries. (See below for + * TreeNode subclass, and in LinkedHashMap for its Entry subclass.) + */ + static value class XNode implements Map.Entry { + final int hash; + final K key; + V value; + Node next; + + XNode() { + this.hash = 0; + this.key = null; + this.value = null; + this.next = null; + } + + XNode(int hash, K key, V value, Node next) { + this.hash = hash; + this.key = key; + this.value = value; + this.next = next; + } + + boolean isEmpty() { + return hash == 0 && key == null && value == null; + } + public final K getKey() { return key; } + public final V getValue() { return value; } + public final String toString() { return key + "=" + value; } + + public final int hashCode() { + return Objects.hashCode(key) ^ Objects.hashCode(value); + } + + public final V setValue(V newValue) { + throw new IllegalStateException("XNode cannot set a value"); +// V oldValue = value; +// value = newValue; +// return oldValue; + } + + public final boolean equals(Object o) { + if (o instanceof Map.Entry) { + Map.Entry e = (Map.Entry)o; + if (Objects.equals(key, e.getKey()) && + Objects.equals(value, e.getValue())) + return true; + } + return false; + } + } + + /** + * Basic hash bin node, used for overflow entries. (See below for + * TreeNode subclass, and in LinkedHashMap for its Entry subclass.) + */ + static class Node implements Map.Entry { + final int hash; + final K key; + V value; + Node next; + + Node(int hash, K key, V value, Node next) { + this.hash = hash; + this.key = key; + this.value = value; + this.next = next; + } + + public final K getKey() { return key; } + public final V getValue() { return value; } + public final String toString() { return key + "=" + value; } + public final int hashCode() { + return Objects.hashCode(key) ^ Objects.hashCode(value); + } + + public final V setValue(V newValue) { + V oldValue = value; + value = newValue; + return oldValue; + } + + public final boolean equals(Object o) { + if (o == this) + return true; + if (o instanceof Map.Entry) { + Map.Entry e = (Map.Entry)o; + if (Objects.equals(key, e.getKey()) && + Objects.equals(value, e.getValue())) + return true; + } + return false; + } + } + + value class XNodeWrapper implements Map.Entry { + int index; + + XNodeWrapper(int index) { + this.index = index; + } + + public K getKey() { + XNode e = table[index]; + return e.isEmpty() ? null : e.key; + } + + public V getValue() { + XNode e = table[index]; + return e.isEmpty() ? null : e.value; + } + + /** + * Replaces the value corresponding to this entry with the specified + * value (optional operation). (Writes through to the map.) The + * behavior of this call is undefined if the mapping has already been + * removed from the map (by the iterator's {@code remove} operation). + * + * @param value new value to be stored in this entry + * @return old value corresponding to the entry + * @throws UnsupportedOperationException if the {@code put} operation + * is not supported by the backing map + * @throws ClassCastException if the class of the specified value + * prevents it from being stored in the backing map + * @throws NullPointerException if the backing map does not permit + * null values, and the specified value is null + * @throws IllegalArgumentException if some property of this value + * prevents it from being stored in the backing map + * @throws IllegalStateException implementations may, but are not + * required to, throw this exception if the entry has been + * removed from the backing map. + */ + public V setValue(V value) { + XNode e = table[index]; + assert !e.isEmpty(); + table[index] = new XNode(e.hash, e.key, value, e.next); + return e.value; + } + } + /* ---------------- Static utilities -------------- */ + + /** + * Computes key.hashCode() and spreads (XORs) higher bits of hash + * to lower. Because the table uses power-of-two masking, sets of + * hashes that vary only in bits above the current mask will + * always collide. (Among known examples are sets of Float keys + * holding consecutive whole numbers in small tables.) So we + * apply a transform that spreads the impact of higher bits + * downward. There is a tradeoff between speed, utility, and + * quality of bit-spreading. Because many common sets of hashes + * are already reasonably distributed (so don't benefit from + * spreading), and because we use trees to handle large sets of + * collisions in bins, we just XOR some shifted bits in the + * cheapest possible way to reduce systematic lossage, as well as + * to incorporate impact of the highest bits that would otherwise + * never be used in index calculations because of table bounds. + */ + static final int hash(Object key) { + int h; + return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); + } + + /** + * Returns x's Class if it is of the form "class C implements + * Comparable", else null. + */ + static Class comparableClassFor(Object x) { + if (x instanceof Comparable) { + Class c; Type[] ts, as; ParameterizedType p; + if ((c = x.getClass()) == String.class) // bypass checks + return c; + if ((ts = c.getGenericInterfaces()) != null) { + for (Type t : ts) { + if ((t instanceof ParameterizedType) && + ((p = (ParameterizedType) t).getRawType() == + Comparable.class) && + (as = p.getActualTypeArguments()) != null && + as.length == 1 && as[0] == c) // type arg is c + return c; + } + } + } + return null; + } + + /** + * Returns k.compareTo(x) if x matches kc (k's screened comparable + * class), else 0. + */ + @SuppressWarnings({"rawtypes","unchecked"}) // for cast to Comparable + static int compareComparables(Class kc, Object k, Object x) { + return (x == null || x.getClass() != kc ? 0 : + ((Comparable)k).compareTo(x)); + } + + /** + * Returns a power of two size for the given target capacity. + */ + static final int tableSizeFor(int cap) { + int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1); + return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; + } + + /* ---------------- Fields -------------- */ + + /** + * The table, initialized on first use, and resized as + * necessary. When allocated, length is always a power of two. + * (We also tolerate length zero in some operations to allow + * bootstrapping mechanics that are currently not needed.) + */ + transient XNode[] table; + + /** + * Holds cached entrySet(). Note that AbstractMap fields are used + * for keySet() and values(). + */ + transient Set> entrySet; + + /** + * The number of key-value mappings contained in this map. + */ + transient int size; + + /** + * The number of times this HashMap has been structurally modified + * Structural modifications are those that change the number of mappings in + * the HashMap or otherwise modify its internal structure (e.g., + * rehash). This field is used to make iterators on Collection-views of + * the HashMap fail-fast. (See ConcurrentModificationException). + */ + transient int modCount; + + /** + * The next size value at which to resize (capacity * load factor). + * + * @serial + */ + // (The javadoc description is true upon serialization. + // Additionally, if the table array has not been allocated, this + // field holds the initial array capacity, or zero signifying + // DEFAULT_INITIAL_CAPACITY.) + int threshold; + + /** + * The load factor for the hash table. + * + * @serial + */ + final float loadFactor; + + /* ---------------- Public operations -------------- */ + + /** + * Constructs an empty {@code HashMap} with the specified initial + * capacity and load factor. + * + * @param initialCapacity the initial capacity + * @param loadFactor the load factor + * @throws IllegalArgumentException if the initial capacity is negative + * or the load factor is nonpositive + */ + public XHashMap(int initialCapacity, float loadFactor) { + if (initialCapacity < 0) + throw new IllegalArgumentException("Illegal initial capacity: " + + initialCapacity); + if (initialCapacity > MAXIMUM_CAPACITY) + initialCapacity = MAXIMUM_CAPACITY; + if (loadFactor <= 0 || Float.isNaN(loadFactor)) + throw new IllegalArgumentException("Illegal load factor: " + + loadFactor); + this.loadFactor = loadFactor; + this.threshold = tableSizeFor(initialCapacity); + } + + /** + * Constructs an empty {@code HashMap} with the specified initial + * capacity and the default load factor (0.75). + * + * @param initialCapacity the initial capacity. + * @throws IllegalArgumentException if the initial capacity is negative. + */ + public XHashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR); + } + + /** + * Constructs an empty {@code HashMap} with the default initial capacity + * (16) and the default load factor (0.75). + */ + public XHashMap() { + this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted + } + + /** + * Constructs a new {@code HashMap} with the same mappings as the + * specified {@code Map}. The {@code HashMap} is created with + * default load factor (0.75) and an initial capacity sufficient to + * hold the mappings in the specified {@code Map}. + * + * @param m the map whose mappings are to be placed in this map + * @throws NullPointerException if the specified map is null + */ + @SuppressWarnings("initialization") + public XHashMap(Map m) { + this.loadFactor = DEFAULT_LOAD_FACTOR; + putMapEntries(m, false); + } + + /** + * Implements Map.putAll and Map constructor. + * + * @param m the map + * @param evict false when initially constructing this map, else true. + */ + final void putMapEntries(Map m, boolean evict) { + int s = m.size(); + if (s > 0) { + if (table == null) { // pre-size + float ft = ((float)s / loadFactor) + 1.0F; + int t = ((ft < (float)MAXIMUM_CAPACITY) ? + (int)ft : MAXIMUM_CAPACITY); + if (t > threshold) + threshold = tableSizeFor(t); + } else { + // Because of linked-list bucket constraints, we cannot + // expand all at once, but can reduce total resize + // effort by repeated doubling now vs later + while (s > threshold && table.length < MAXIMUM_CAPACITY) + resize(); + } + + for (Map.Entry e : m.entrySet()) { + K key = e.getKey(); + V value = e.getValue(); + putVal(hash(key), key, value, false, evict); + } + } + } + + /** + * Returns the number of key-value mappings in this map. + * + * @return the number of key-value mappings in this map + */ + public int size() { + return size; + } + + /** + * Returns {@code true} if this map contains no key-value mappings. + * + * @return {@code true} if this map contains no key-value mappings + */ + public boolean isEmpty() { + return size == 0; + } + + /** + * Returns the value to which the specified key is mapped, + * or {@code null} if this map contains no mapping for the key. + * + *

    More formally, if this map contains a mapping from a key + * {@code k} to a value {@code v} such that {@code (key==null ? k==null : + * key.equals(k))}, then this method returns {@code v}; otherwise + * it returns {@code null}. (There can be at most one such mapping.) + * + *

    A return value of {@code null} does not necessarily + * indicate that the map contains no mapping for the key; it's also + * possible that the map explicitly maps the key to {@code null}. + * The {@link #containsKey containsKey} operation may be used to + * distinguish these two cases. + * + * @see #put(Object, Object) + */ + public V get(Object key) { + int hash = hash(key); + Node e; + XNode n = getXNode(hash, key); + return (!n.isEmpty()) ? n.value + : (e = getNode(hash, key)) == null ? null : e.value; + } + + /** + * Implements Map.get and related methods. + * + * @param hash hash for key + * @param key the key + * @return the node, or emptyXNode() if not at top level. + */ + final XNode getXNode(int hash, Object key) { + XNode[] tab; + XNode first; + int n; + K k; + if ((tab = table) != null && (n = tab.length) > 0 && + !(first = tab[(n - 1) & hash]).isEmpty()) { + if (first.hash == hash && // always check first node + ((k = first.key) == key || (key != null && key.equals(k)))) + return first; + } + return emptyXNode(); + } + + /** + * Implements Map.get and related methods when the key is not found in the primary entry. + * + * @param hash hash for key + * @param key the key + * @return the node, or null if none + */ + final Node getNode(int hash, Object key) { + XNode[] tab; XNode first; Node e; int n; K k; + if ((tab = table) != null && (n = tab.length) > 0 && + !(first = tab[(n - 1) & hash]).isEmpty()) { + if ((e = first.next) != null) { + if (e instanceof TreeNode) + return ((TreeNode)e).getTreeNode(hash, key); + do { + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) + return e; + } while ((e = e.next) != null); + } + } + return null; + } + + /** + * Returns {@code true} if this map contains a mapping for the + * specified key. + * + * @param key The key whose presence in this map is to be tested + * @return {@code true} if this map contains a mapping for the specified + * key. + */ + public boolean containsKey(Object key) { + int hash = hash(key); + Node e; + XNode n = getXNode(hash, key); + return !n.isEmpty() || (e = getNode(hash, key)) != null; + } + + /** + * Associates the specified value with the specified key in this map. + * If the map previously contained a mapping for the key, the old + * value is replaced. + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key}. + * (A {@code null} return can also indicate that the map + * previously associated {@code null} with {@code key}.) + */ + public V put(K key, V value) { + return putVal(hash(key), key, value, false, true); + } + + /** + * Implements Map.put and related methods. + * + * @param hash hash for key + * @param key the key + * @param value the value to put + * @param onlyIfAbsent if true, don't change existing value + * @param evict if false, the table is in creation mode. + * @return previous value, or null if none + */ + final V putVal(int hash, K key, V value, boolean onlyIfAbsent, + boolean evict) { + XNode[] tab; XNode tp; int n, i; + if ((tab = table) == null || (n = tab.length) == 0) + n = (tab = resize()).length; + if ((tp = tab[i = (n - 1) & hash]).isEmpty()) { + tab[i] = new XNode(hash, key, value, null); + } else { + Node e; K k; + if (tp.hash == hash && + ((k = tp.key) == key || (key != null && key.equals(k)))) { + if (!onlyIfAbsent || tp.value == null) { + tab[i] = new XNode(hash, k, value, tp.next); + } + return tp.value; + } else if ((e = tp.next) == null) { + Node x = newNode(hash, key, value, null); + tab[i] = new XNode(tp.hash, tp.key, tp.value, x); + } else if (e instanceof TreeNode) { + e = ((TreeNode) e).putTreeVal(this, tab, hash, key, value); + } else { + for (int binCount = 0; ; ++binCount) { + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) + break; + Node p = e; + if ((e = p.next) == null) { + p.next = newNode(hash, key, value, null); + if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st + treeifyBin(tab, hash); + break; + } + } + } + if (e != null) { // existing mapping for key + V oldValue = e.value; + if (!onlyIfAbsent || oldValue == null) + e.value = value; + return oldValue; + } + } + + ++modCount; + if (++size > threshold) + resize(); + return null; + } + + /** + * Initializes or doubles table size. If null, allocates in + * accord with initial capacity target held in field threshold. + * Otherwise, because we are using power-of-two expansion, the + * elements from each bin must either stay at same index, or move + * with a power of two offset in the new table. + * + * @return the table + */ + final XNode[] resize() { + XNode[] oldTab = table; + int oldCap = (oldTab == null) ? 0 : oldTab.length; + int oldThr = threshold; + int newCap, newThr = 0; + if (oldCap > 0) { + if (oldCap >= MAXIMUM_CAPACITY) { + threshold = Integer.MAX_VALUE; + return oldTab; + } + else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && + oldCap >= DEFAULT_INITIAL_CAPACITY) + newThr = oldThr << 1; // double threshold + } + else if (oldThr > 0) // initial capacity was placed in threshold + newCap = oldThr; + else { // zero initial threshold signifies using defaults + newCap = DEFAULT_INITIAL_CAPACITY; + newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); + } + if (newThr == 0) { + float ft = (float)newCap * loadFactor; + newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? + (int)ft : Integer.MAX_VALUE); + } + threshold = newThr; + @SuppressWarnings({"rawtypes","unchecked"}) + XNode[] newTab = (XNode[])new XNode[newCap]; + table = newTab; + if (oldTab != null) { + for (int j = 0; j < oldCap; ++j) { + XNode x; + Node e; + if (!(x = oldTab[j]).isEmpty()) { + oldTab[j] = emptyXNode(); + if ((e = x.next) == null) + newTab[x.hash & (newCap - 1)] = new XNode(x.hash, x.key, x.value, null); + else if (e instanceof TreeNode) + ((TreeNode)e).split(this, newTab, j, oldCap); + else { // preserve order + Node loHead = null, loTail = null; + Node hiHead = null, hiTail = null; + Node next; + do { + next = e.next; + if ((e.hash & oldCap) == 0) { + if (loTail == null) + loHead = e; + else + loTail.next = e; + loTail = e; + } + else { + if (hiTail == null) + hiHead = e; + else + hiTail.next = e; + hiTail = e; + } + } while ((e = next) != null); + if (loTail != null) + loTail.next = null; + if (hiTail != null) + hiTail.next = null; + + newTab[j] = (j == (x.hash & (newCap - 1))) + ? new XNode(x.hash, x.key, x.value, loHead) + : ((loHead != null) + ? new XNode(loHead.hash, loHead.key, loHead.value, loHead.next) : + emptyXNode()); + + newTab[j + oldCap] = ((j + oldCap) == (x.hash & (newCap - 1))) + ? new XNode(x.hash, x.key, x.value, hiHead) + : ((hiHead != null) + ? new XNode(hiHead.hash, hiHead.key, hiHead.value, hiHead.next) : + emptyXNode()); + } + } + } + } + return newTab; + } + + /** + * Replaces all linked nodes in bin at index for given hash unless + * table is too small, in which case resizes instead. + */ + final void treeifyBin(XNode[] tab, int hash) { +// int n, index; Node e; +// if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) +// resize(); +// else if ((e = tab[index = (n - 1) & hash]) != null) { +// TreeNode hd = null, tl = null; +// do { +// TreeNode p = replacementTreeNode(e, null); +// if (tl == null) +// hd = p; +// else { +// p.prev = tl; +// tl.next = p; +// } +// tl = p; +// } while ((e = e.next) != null); +// if ((tab[index] = hd) != null) +// hd.treeify(tab); +// } + } + + /** + * Copies all of the mappings from the specified map to this map. + * These mappings will replace any mappings that this map had for + * any of the keys currently in the specified map. + * + * @param m mappings to be stored in this map + * @throws NullPointerException if the specified map is null + */ + public void putAll(Map m) { + putMapEntries(m, true); + } + + /** + * Removes the mapping for the specified key from this map if present. + * + * @param key key whose mapping is to be removed from the map + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key}. + * (A {@code null} return can also indicate that the map + * previously associated {@code null} with {@code key}.) + */ + public V remove(Object key) { + Optional o = removeNode(hash(key), key, null, false, true); + return o.orElse(null); + } + + /** + * Implements Map.remove and related methods. + * + * @param hash hash for key + * @param key the key + * @param value the value to match if matchValue, else ignored + * @param matchValue if true only remove if value is equal + * @param movable if false do not move other nodes while removing + * @return the node, or null if none + */ + final Optional removeNode(int hash, Object key, Object value, + boolean matchValue, boolean movable) { + XNode[] tab; XNode te; int n, index; + if ((tab = table) != null && (n = tab.length) > 0 && + !(te = tab[index = (n - 1) & hash]).isEmpty()) { + Node node = null, e; K k; V v = null; + if (te.hash == hash && + ((k = te.key) == key || (key != null && key.equals(k)))) { + if ((!matchValue || (v = te.value) == value || + (value != null && value.equals(v)))) { + tab[index] = ((e = te.next) == null) + ? emptyXNode() + : new XNode(hash, e.key, e.value, e.next); + ++modCount; + --size; + return Optional.ofNullable(v); + } + } else if ((e = te.next) != null) { + Node p = null; + if (e instanceof TreeNode) + node = ((TreeNode)e).getTreeNode(hash, key); + else { + do { + if (e.hash == hash && + ((k = e.key) == key || + (key != null && key.equals(k)))) { + node = e; + break; + } + p = e; + } while ((e = e.next) != null); + } + + if (node != null && (!matchValue || (v = node.value) == value || + (value != null && value.equals(v)))) { + if (node instanceof TreeNode) + ((TreeNode)node).removeTreeNode(this, tab, movable); + else if (p == null) + tab[index] = new XNode(hash, node.key, node.value, node.next); + else + p.next = node.next; + ++modCount; + --size; + return Optional.of(node.value); + } + } + } + return Optional.empty(); + } + + /** + * Removes all of the mappings from this map. + * The map will be empty after this call returns. + */ + public void clear() { + modCount++; + if (table != null && size > 0) { + size = 0; + table = null; + threshold = 0; + } + } + + /** + * Returns {@code true} if this map maps one or more keys to the + * specified value. + * + * @param value value whose presence in this map is to be tested + * @return {@code true} if this map maps one or more keys to the + * specified value + */ + public boolean containsValue(Object value) { + XNode[] tab; V v; + if ((tab = table) != null && size > 0) { + for (XNode te : tab) { + if (!te.isEmpty()) { + if ((v = te.value) == value || + (value != null && value.equals(v))) + return true; + for (Node e = te.next; e != null; e = e.next) { + if ((v = e.value) == value || + (value != null && value.equals(v))) + return true; + } + } + } + } + return false; + } + + /** + * Returns a {@link Set} view of the keys contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. If the map is modified + * while an iteration over the set is in progress (except through + * the iterator's own {@code remove} operation), the results of + * the iteration are undefined. The set supports element removal, + * which removes the corresponding mapping from the map, via the + * {@code Iterator.remove}, {@code Set.remove}, + * {@code removeAll}, {@code retainAll}, and {@code clear} + * operations. It does not support the {@code add} or {@code addAll} + * operations. + * + * @return a set view of the keys contained in this map + */ + public Set keySet() { + Set ks = keySet; + if (ks == null) { + ks = new KeySet(); + keySet = ks; + } + return ks; + } + + /** + * Prepares the array for {@link Collection#toArray(Object[])} implementation. + * If supplied array is smaller than this map size, a new array is allocated. + * If supplied array is bigger than this map size, a null is written at size index. + * + * @param a an original array passed to {@code toArray()} method + * @param type of array elements + * @return an array ready to be filled and returned from {@code toArray()} method. + */ + @SuppressWarnings("unchecked") + final T[] prepareArray(T[] a) { + int size = this.size; + if (a.length < size) { + return (T[]) java.lang.reflect.Array + .newInstance(a.getClass().getComponentType(), size); + } + if (a.length > size) { + a[size] = null; + } + return a; + } + + /** + * Fills an array with this map keys and returns it. This method assumes + * that input array is big enough to fit all the keys. Use + * {@link #prepareArray(Object[])} to ensure this. + * + * @param a an array to fill + * @param type of array elements + * @return supplied array + */ + T[] keysToArray(T[] a) { + Object[] r = a; + XNode[] tab; + int idx = 0; + int i = 0; + if (size > 0 && (tab = table) != null) { + for (XNode te : tab) { + if (!te.isEmpty()) { + r[idx++] = te.key; + for (Node e = te.next; e != null; e = e.next) { + r[idx++] = e.key; + } + } + } + } + return a; + } + + /** + * Fills an array with this map values and returns it. This method assumes + * that input array is big enough to fit all the values. Use + * {@link #prepareArray(Object[])} to ensure this. + * + * @param a an array to fill + * @param type of array elements + * @return supplied array + */ + T[] valuesToArray(T[] a) { + Object[] r = a; + XNode[] tab; + int idx = 0; + if (size > 0 && (tab = table) != null) { + for (XNode te : tab) { + if (!te.isEmpty()) { + r[idx++] = te.value; + for (Node e = te.next; e != null; e = e.next) { + r[idx++] = e.value; + } + } + } + } + return a; + } + + final class KeySet extends AbstractSet { + public final int size() { return size; } + public final void clear() { XHashMap.this.clear(); } + public final Iterator iterator() { return new KeyIterator(); } + public final boolean contains(Object o) { return containsKey(o); } + public final boolean remove(Object key) { + return removeNode(hash(key), key, null, false, true).isPresent(); + } + + public Object[] toArray() { + return keysToArray(new Object[size]); + } + + public T[] toArray(T[] a) { + return keysToArray(prepareArray(a)); + } + + public final void forEach(Consumer action) { + XNode[] tab; + if (action == null) + throw new NullPointerException(); + if (size > 0 && (tab = table) != null) { + int mc = modCount; + for (XNode te : tab) { + if (!te.isEmpty()) { + action.accept(te.key); + for (Node e = te.next; e != null; e = e.next) + action.accept(e.key); + } + } + if (modCount != mc) + throw new ConcurrentModificationException(); + } + } + } + + /** + * Returns a {@link Collection} view of the values contained in this map. + * The collection is backed by the map, so changes to the map are + * reflected in the collection, and vice-versa. If the map is + * modified while an iteration over the collection is in progress + * (except through the iterator's own {@code remove} operation), + * the results of the iteration are undefined. The collection + * supports element removal, which removes the corresponding + * mapping from the map, via the {@code Iterator.remove}, + * {@code Collection.remove}, {@code removeAll}, + * {@code retainAll} and {@code clear} operations. It does not + * support the {@code add} or {@code addAll} operations. + * + * @return a view of the values contained in this map + */ + public Collection values() { + Collection vs = values; + if (vs == null) { + vs = new Values(); + values = vs; + } + return vs; + } + + final class Values extends AbstractCollection { + public final int size() { return size; } + public final void clear() { XHashMap.this.clear(); } + public final Iterator iterator() { return new ValueIterator(); } + public final boolean contains(Object o) { return containsValue(o); } + + public Object[] toArray() { + return valuesToArray(new Object[size]); + } + + public T[] toArray(T[] a) { + return valuesToArray(prepareArray(a)); + } + + public final void forEach(Consumer action) { + XNode[] tab; + if (action == null) + throw new NullPointerException(); + if (size > 0 && (tab = table) != null) { + int mc = modCount; + for (XNode te : tab) { + if (!te.isEmpty()) { + action.accept(te.value); + for (Node e = te.next; e != null; e = e.next) + action.accept(e.value); + } + } + if (modCount != mc) + throw new ConcurrentModificationException(); + } + } + } + + /** + * Returns a {@link Set} view of the mappings contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. If the map is modified + * while an iteration over the set is in progress (except through + * the iterator's own {@code remove} operation, or through the + * {@code setValue} operation on a map entry returned by the + * iterator) the results of the iteration are undefined. The set + * supports element removal, which removes the corresponding + * mapping from the map, via the {@code Iterator.remove}, + * {@code Set.remove}, {@code removeAll}, {@code retainAll} and + * {@code clear} operations. It does not support the + * {@code add} or {@code addAll} operations. + * + * @return a set view of the mappings contained in this map + */ + public Set> entrySet() { + Set> es; + return (es = entrySet) == null ? (entrySet = new EntrySet()) : es; + } + + final class EntrySet extends AbstractSet> { + public final int size() { return size; } + public final void clear() { XHashMap.this.clear(); } + public final Iterator> iterator() { + return new EntryIterator(); + } + public final boolean contains(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry) o; + Object key = e.getKey(); + Node candidate = getNode(hash(key), key); + return candidate != null && candidate.equals(e); + } + public final boolean remove(Object o) { + if (o instanceof Map.Entry) { + Map.Entry e = (Map.Entry) o; + Object key = e.getKey(); + Object value = e.getValue(); + return removeNode(hash(key), key, value, true, true).isPresent(); + } + return false; + } + public final void forEach(Consumer> action) { + XNode[] tab; + if (action == null) + throw new NullPointerException(); + if (size > 0 && (tab = table) != null) { + int mc = modCount; + for (XNode te : tab) { + if (!te.isEmpty()) { + action.accept(new XNodeWrapper(te.hash & (tab.length - 1))); + for (Node e = te.next; e != null; e = e.next) + action.accept(e); + } + } + if (modCount != mc) + throw new ConcurrentModificationException(); + } + } + } + + // Overrides of JDK8 Map extension methods + + @Override + public V getOrDefault(Object key, V defaultValue) { + Node e; + return (e = getNode(hash(key), key)) == null ? defaultValue : e.value; + } + + @Override + public V putIfAbsent(K key, V value) { + return putVal(hash(key), key, value, true, true); + } + + @Override + public boolean remove(Object key, Object value) { + return removeNode(hash(key), key, value, true, true).isPresent(); + } + + @Override + public boolean replace(K key, V oldValue, V newValue) { + Node e; V v; + if ((e = getNode(hash(key), key)) != null && + ((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) { + e.value = newValue; + return true; + } + return false; + } + + @Override + public V replace(K key, V value) { + Node e; + if ((e = getNode(hash(key), key)) != null) { + V oldValue = e.value; + e.value = value; + return oldValue; + } + return null; + } + + /** + * {@inheritDoc} + * + *

    This method will, on a best-effort basis, throw a + * {@link ConcurrentModificationException} if it is detected that the + * mapping function modifies this map during computation. + * + * @throws ConcurrentModificationException if it is detected that the + * mapping function modified this map + */ + @Override + public V computeIfAbsent(K key, + Function mappingFunction) { + if (mappingFunction == null) + throw new NullPointerException(); + int hash = hash(key); + XNode[] tab; XNode first; int n, i; + int binCount = 0; + TreeNode t = null; + Node old = null; + if (size > threshold || (tab = table) == null || + (n = tab.length) == 0) + n = (tab = resize()).length; + if (!(first = tab[i = (n - 1) & hash]).isEmpty()) { + K k; + if (first.hash == hash && + ((k = first.key) == key || (key != null && key.equals(k)))) { + return first.value; + } + Node e = first.next; + if (e instanceof TreeNode) + old = (t = (TreeNode)e).getTreeNode(hash, key); + else { + do { + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) { + old = e; + break; + } + ++binCount; + } while ((e = e.next) != null); + } + + V oldValue; + if (old != null && (oldValue = old.value) != null) { + return oldValue; + } + } + int mc = modCount; + V v = mappingFunction.apply(key); + if (mc != modCount) { throw new ConcurrentModificationException(); } + if (v == null) { + return null; + } else if (old != null) { + old.value = v; + return v; + } + else if (t != null) + t.putTreeVal(this, tab, hash, key, v); + else { + Node x = (tab[i].isEmpty()) ? null : newNode(hash, key, v, null); + tab[i] = new XNode(hash, key, v, x); + if (binCount >= TREEIFY_THRESHOLD - 1) + treeifyBin(tab, hash); + } + modCount = mc + 1; + ++size; + return v; + } + + /** + * {@inheritDoc} + * + *

    This method will, on a best-effort basis, throw a + * {@link ConcurrentModificationException} if it is detected that the + * remapping function modifies this map during computation. + * + * @throws ConcurrentModificationException if it is detected that the + * remapping function modified this map + */ + @Override + public V computeIfPresent(K key, + BiFunction remappingFunction) { + if (remappingFunction == null) + throw new NullPointerException(); + Node e; V oldValue; + int hash = hash(key); + if ((e = getNode(hash, key)) != null && + (oldValue = e.value) != null) { + int mc = modCount; + V v = remappingFunction.apply(key, oldValue); + if (mc != modCount) { throw new ConcurrentModificationException(); } + if (v != null) { + e.value = v; + return v; + } + else + removeNode(hash, key, null, false, true); + } + return null; + } + + /** + * {@inheritDoc} + * + *

    This method will, on a best-effort basis, throw a + * {@link ConcurrentModificationException} if it is detected that the + * remapping function modifies this map during computation. + * + * @throws ConcurrentModificationException if it is detected that the + * remapping function modified this map + */ + @Override + public V compute(K key, + BiFunction remappingFunction) { + if (remappingFunction == null) + throw new NullPointerException(); + int hash = hash(key); + XNode[] tab; XNode first; int n, i; + int binCount = 0; + TreeNode t = null; + Node old = null; + if (size > threshold || (tab = table) == null || + (n = tab.length) == 0) + n = (tab = resize()).length; + if (!(first = tab[i = (n - 1) & hash]).isEmpty()) { + Node e = first.next;K k; + if (first.hash == hash && + ((k = first.key) == key || (key != null && key.equals(k)))) { + V v = remappingFunction.apply(k, first.value); + tab[i] = new XNode(hash, k, v, e); + return v; + } + if (e instanceof TreeNode) + old = (t = (TreeNode)e).getTreeNode(hash, key); + else { + do { + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) { + old = e; + break; + } + ++binCount; + } while ((e = e.next) != null); + } + } + V oldValue = (old == null) ? null : old.value; + int mc = modCount; + V v = remappingFunction.apply(key, oldValue); + if (mc != modCount) { throw new ConcurrentModificationException(); } + if (old != null) { + if (v != null) { + old.value = v; + } + else + removeNode(hash, key, null, false, true); + } + else if (v != null) { + if (t != null) + t.putTreeVal(this, tab, hash, key, v); + else { + Node x = (tab[i].isEmpty()) ? null : newNode(hash, key, v, null); + tab[i] = new XNode(hash, key, v, x); + if (binCount >= TREEIFY_THRESHOLD - 1) + treeifyBin(tab, hash); + } + modCount = mc + 1; + ++size; + } + return v; + } + + /** + * {@inheritDoc} + * + *

    This method will, on a best-effort basis, throw a + * {@link ConcurrentModificationException} if it is detected that the + * remapping function modifies this map during computation. + * + * @throws ConcurrentModificationException if it is detected that the + * remapping function modified this map + */ + @Override + public V merge(K key, V value, + BiFunction remappingFunction) { + if (value == null || remappingFunction == null) + throw new NullPointerException(); + int hash = hash(key); + XNode[] tab; XNode first; int n, i; + int binCount = 0; + TreeNode t = null; + Node old = null; + if (size > threshold || (tab = table) == null || + (n = tab.length) == 0) + n = (tab = resize()).length; + if (!(first = tab[i = (n - 1) & hash]).isEmpty()) { + Node e = first.next;K k; + if (first.hash == hash && + ((k = first.key) == key || (key != null && key.equals(k)))) { + V v = remappingFunction.apply(first.value, value); + tab[i] = new XNode(hash, k, v, e); + return v; + } + if (e instanceof TreeNode) + old = (t = (TreeNode)e).getTreeNode(hash, key); + else { + do { + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) { + old = e; + break; + } + ++binCount; + } while ((e = e.next) != null); + } + } + if (old != null) { + V v; + if (old.value != null) { + int mc = modCount; + v = remappingFunction.apply(old.value, value); + if (mc != modCount) { + throw new ConcurrentModificationException(); + } + } else { + v = value; + } + if (v != null) { + old.value = v; + } + else + removeNode(hash, key, null, false, true); + return v; + } else { + if (t != null) + t.putTreeVal(this, tab, hash, key, value); + else { + Node x = (tab[i].isEmpty()) ? null + : newNode(hash, tab[i].key, tab[i].value, null); + tab[i] = new XNode(hash, key, value, x); + if (binCount >= TREEIFY_THRESHOLD - 1) + treeifyBin(tab, hash); + } + ++modCount; + ++size; + return value; + } + } + + @Override + public void forEach(BiConsumer action) { + XNode[] tab; + if (action == null) + throw new NullPointerException(); + if (size > 0 && (tab = table) != null) { + int mc = modCount; + for (XNode te : tab) { + if (!te.isEmpty()) { + action.accept(te.key, te.value); + for (Node e = te.next; e != null; e = e.next) + action.accept(e.key, e.value); + } + } + if (modCount != mc) + throw new ConcurrentModificationException(); + } + } + + @Override + public void replaceAll(BiFunction function) { + XNode[] tab; + if (function == null) + throw new NullPointerException(); + if (size > 0 && (tab = table) != null) { + int mc = modCount; + for (XNode te : tab) { + if (!te.isEmpty()) { + V v = function.apply(te.key, te.value); + tab[te.hash & (tab.length -1)] = new XNode(te.hash, te.key, v, te.next); + for (Node e = te.next; e != null; e = e.next) + e.value = function.apply(e.key, e.value); + } + } + if (modCount != mc) + throw new ConcurrentModificationException(); + } + } + + /* ------------------------------------------------------------ */ + // Cloning and serialization + + /** + * Returns a shallow copy of this {@code HashMap} instance: the keys and + * values themselves are not cloned. + * + * @return a shallow copy of this map + */ + @SuppressWarnings("unchecked") + @Override + public Object clone() { + XHashMap result; + try { + result = (XHashMap)super.clone(); + } catch (CloneNotSupportedException e) { + // this shouldn't happen, since we are Cloneable + throw new InternalError(e); + } + result.reinitialize(); + result.putMapEntries(this, false); + return result; + } + + // These methods are also used when serializing HashSets + final float loadFactor() { return loadFactor; } + final int capacity() { + return (table != null) ? table.length : + (threshold > 0) ? threshold : + DEFAULT_INITIAL_CAPACITY; + } + + /** + * Saves this map to a stream (that is, serializes it). + * + * @param s the stream + * @throws IOException if an I/O error occurs + * @serialData The capacity of the HashMap (the length of the + * bucket array) is emitted (int), followed by the + * size (an int, the number of key-value + * mappings), followed by the key (Object) and value (Object) + * for each key-value mapping. The key-value mappings are + * emitted in no particular order. + */ + private void writeObject(java.io.ObjectOutputStream s) + throws IOException { + int buckets = capacity(); + // Write out the threshold, loadfactor, and any hidden stuff + s.defaultWriteObject(); + s.writeInt(buckets); + s.writeInt(size); + internalWriteEntries(s); + } + + /** + * Reconstitutes this map from a stream (that is, deserializes it). + * @param s the stream + * @throws ClassNotFoundException if the class of a serialized object + * could not be found + * @throws IOException if an I/O error occurs + */ + private void readObject(java.io.ObjectInputStream s) + throws IOException, ClassNotFoundException { + // Read in the threshold (ignored), loadfactor, and any hidden stuff + s.defaultReadObject(); + reinitialize(); + if (loadFactor <= 0 || Float.isNaN(loadFactor)) + throw new InvalidObjectException("Illegal load factor: " + + loadFactor); + s.readInt(); // Read and ignore number of buckets + int mappings = s.readInt(); // Read number of mappings (size) + if (mappings < 0) + throw new InvalidObjectException("Illegal mappings count: " + + mappings); + else if (mappings > 0) { // (if zero, use defaults) + // Size the table using given load factor only if within + // range of 0.25...4.0 + float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f); + float fc = (float)mappings / lf + 1.0f; + int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ? + DEFAULT_INITIAL_CAPACITY : + (fc >= MAXIMUM_CAPACITY) ? + MAXIMUM_CAPACITY : + tableSizeFor((int)fc)); + float ft = (float)cap * lf; + threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ? + (int)ft : Integer.MAX_VALUE); + + // Check Map.Entry[].class since it's the nearest public type to + // what we're actually creating. + @SuppressWarnings({"rawtypes","unchecked"}) + XNode[] tab = (XNode[])new XNode[cap]; + table = tab; + + // Read the keys and values, and put the mappings in the HashMap + for (int i = 0; i < mappings; i++) { + @SuppressWarnings("unchecked") + K key = (K) s.readObject(); + @SuppressWarnings("unchecked") + V value = (V) s.readObject(); + putVal(hash(key), key, value, false, false); + } + } + } + + /* ------------------------------------------------------------ */ + // iterators + + static final Node START_INDEX = new Node(0, null, null, null); + + abstract class HashIterator { + Node next; // next entry to return + Node current; // current entry + int expectedModCount; // for fast-fail + int index; // current slot + + + HashIterator() { + expectedModCount = modCount; + XNode[] t = table; + current = next = null; + index = 0; + if (t != null && size > 0) { // advance to first entry + XNode n = emptyXNode(); + for (; index < t.length && (n = t[index]).isEmpty(); index++) { + } + next = (Node)START_INDEX; + } + } + + public final boolean hasNext() { + return next != null; + } + + final Entry nextNode() { + XNode[] t; + Node e = next; + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + if (e == null) + throw new NoSuchElementException(); + if ((next = (current = e).next) == null && (t = table) != null) { + var ret = (e == START_INDEX) ? new XNodeWrapper(index++) : e; + for (; index < t.length && (t[index]).isEmpty(); index++) { } + next = (index < t.length) ? (Node) START_INDEX : null; + return ret; + } + return e; + } + + public final void remove() { + Node p = current; + if (p == null) + throw new IllegalStateException(); + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + current = null; + removeNode(p.hash, p.key, null, false, false); + expectedModCount = modCount; + } + } + + final class KeyIterator extends HashIterator + implements Iterator { + public final K next() { return nextNode().getKey(); } + } + + final class ValueIterator extends HashIterator + implements Iterator { + public final V next() { return nextNode().getValue(); } + } + + final class EntryIterator extends HashIterator + implements Iterator> { + public final Map.Entry next() { return nextNode(); } + } + + /* + * The following package-protected methods are designed to be + * overridden by LinkedHashMap, but not by any other subclass. + * Nearly all other internal methods are also package-protected + * but are declared final, so can be used by LinkedHashMap, view + * classes, and HashSet. + */ + + // Create a regular (non-tree) node + Node newNode(int hash, K key, V value, Node next) { + return new Node<>(hash, key, value, next); + } + + // For conversion from TreeNodes to plain nodes + Node replacementNode(Node p, Node next) { + return new Node<>(p.hash, p.key, p.value, next); + } + + // Create a tree bin node + TreeNode newTreeNode(int hash, K key, V value, Node next) { + return new TreeNode<>(hash, key, value, next); + } + + // For treeifyBin + TreeNode replacementTreeNode(Node p, Node next) { + return new TreeNode<>(p.hash, p.key, p.value, next); + } + + /** + * Reset to initial default state. Called by clone and readObject. + */ + void reinitialize() { + table = null; + entrySet = null; + keySet = null; + values = null; + modCount = 0; + threshold = 0; + size = 0; + } + + // Called only from writeObject, to ensure compatible ordering. + void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException { + XNode[] tab; + if (size > 0 && (tab = table) != null) { + for (XNode te : tab) { + if (!te.isEmpty()) { + s.writeObject(te.key); + s.writeObject(te.value); + + for (Node e = te.next; e != null; e = e.next) { + s.writeObject(e.key); + s.writeObject(e.value); + } + } + } + } + } + + /* ------------------------------------------------------------ */ + // Tree bins + + /** + * Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn + * extends Node) so can be used as extension of either regular or + * linked node. + */ + static final class TreeNode extends Node { + TreeNode parent; // red-black tree links + TreeNode left; + TreeNode right; + TreeNode prev; // needed to unlink next upon deletion + boolean red; + TreeNode(int hash, K key, V val, Node next) { + super(hash, key, val, next); + } + + /** + * Returns root of tree containing this node. + */ + final TreeNode root() { + for (TreeNode r = this, p;;) { + if ((p = r.parent) == null) + return r; + r = p; + } + } + + /** + * Ensures that the given root is the first node of its bin. + */ + static void moveRootToFront(Node[] tab, TreeNode root) { + int n; + if (root != null && tab != null && (n = tab.length) > 0) { + int index = (n - 1) & root.hash; + TreeNode first = (TreeNode)tab[index]; + if (root != first) { + Node rn; + tab[index] = root; + TreeNode rp = root.prev; + if ((rn = root.next) != null) + ((TreeNode)rn).prev = rp; + if (rp != null) + rp.next = rn; + if (first != null) + first.prev = root; + root.next = first; + root.prev = null; + } + assert checkInvariants(root); + } + } + + /** + * Finds the node starting at root p with the given hash and key. + * The kc argument caches comparableClassFor(key) upon first use + * comparing keys. + */ + final TreeNode find(int h, Object k, Class kc) { + TreeNode p = this; + do { + int ph, dir; K pk; + TreeNode pl = p.left, pr = p.right, q; + if ((ph = p.hash) > h) + p = pl; + else if (ph < h) + p = pr; + else if ((pk = p.key) == k || (k != null && k.equals(pk))) + return p; + else if (pl == null) + p = pr; + else if (pr == null) + p = pl; + else if ((kc != null || + (kc = comparableClassFor(k)) != null) && + (dir = compareComparables(kc, k, pk)) != 0) + p = (dir < 0) ? pl : pr; + else if ((q = pr.find(h, k, kc)) != null) + return q; + else + p = pl; + } while (p != null); + return null; + } + + /** + * Calls find for root node. + */ + final TreeNode getTreeNode(int h, Object k) { + return ((parent != null) ? root() : this).find(h, k, null); + } + + /** + * Tie-breaking utility for ordering insertions when equal + * hashCodes and non-comparable. We don't require a total + * order, just a consistent insertion rule to maintain + * equivalence across rebalancings. Tie-breaking further than + * necessary simplifies testing a bit. + */ + static int tieBreakOrder(Object a, Object b) { + int d; + if (a == null || b == null || + (d = a.getClass().getName(). + compareTo(b.getClass().getName())) == 0) + d = (System.identityHashCode(a) <= System.identityHashCode(b) ? + -1 : 1); + return d; + } + + /** + * Forms tree of the nodes linked from this node. + */ + final void treeify(XNode[] tab) { +// TreeNode root = null; +// for (TreeNode x = this, next; x != null; x = next) { +// next = (TreeNode)x.next; +// x.left = x.right = null; +// if (root == null) { +// x.parent = null; +// x.red = false; +// root = x; +// } +// else { +// K k = x.key; +// int h = x.hash; +// Class kc = null; +// for (TreeNode p = root;;) { +// int dir, ph; +// K pk = p.key; +// if ((ph = p.hash) > h) +// dir = -1; +// else if (ph < h) +// dir = 1; +// else if ((kc == null && +// (kc = comparableClassFor(k)) == null) || +// (dir = compareComparables(kc, k, pk)) == 0) +// dir = tieBreakOrder(k, pk); +// +// TreeNode xp = p; +// if ((p = (dir <= 0) ? p.left : p.right) == null) { +// x.parent = xp; +// if (dir <= 0) +// xp.left = x; +// else +// xp.right = x; +// root = balanceInsertion(root, x); +// break; +// } +// } +// } +// } +// moveRootToFront(tab, root); + } + + /** + * Returns a list of non-TreeNodes replacing those linked from + * this node. + */ + final Node untreeify(XHashMap map) { + Node hd = null, tl = null; + for (Node q = this; q != null; q = q.next) { + Node p = map.replacementNode(q, null); + if (tl == null) + hd = p; + else + tl.next = p; + tl = p; + } + return hd; + } + + /** + * Tree version of putVal. + */ + final TreeNode putTreeVal(XHashMap map, XNode[] tab, + int h, K k, V v) { +// Class kc = null; +// boolean searched = false; +// TreeNode root = (parent != null) ? root() : this; +// for (TreeNode p = root;;) { +// int dir, ph; K pk; +// if ((ph = p.hash) > h) +// dir = -1; +// else if (ph < h) +// dir = 1; +// else if ((pk = p.key) == k || (k != null && k.equals(pk))) +// return p; +// else if ((kc == null && +// (kc = comparableClassFor(k)) == null) || +// (dir = compareComparables(kc, k, pk)) == 0) { +// if (!searched) { +// TreeNode q, ch; +// searched = true; +// if (((ch = p.left) != null && +// (q = ch.find(h, k, kc)) != null) || +// ((ch = p.right) != null && +// (q = ch.find(h, k, kc)) != null)) +// return q; +// } +// dir = tieBreakOrder(k, pk); +// } +// +// TreeNode xp = p; +// if ((p = (dir <= 0) ? p.left : p.right) == null) { +// Node xpn = xp.next; +// TreeNode x = map.newTreeNode(h, k, v, xpn); +// if (dir <= 0) +// xp.left = x; +// else +// xp.right = x; +// xp.next = x; +// x.parent = x.prev = xp; +// if (xpn != null) +// ((TreeNode)xpn).prev = x; +// moveRootToFront(tab, balanceInsertion(root, x)); +// return null; +// } +// } + return null; + } + + /** + * Removes the given node, that must be present before this call. + * This is messier than typical red-black deletion code because we + * cannot swap the contents of an interior node with a leaf + * successor that is pinned by "next" pointers that are accessible + * independently during traversal. So instead we swap the tree + * linkages. If the current tree appears to have too few nodes, + * the bin is converted back to a plain bin. (The test triggers + * somewhere between 2 and 6 nodes, depending on tree structure). + */ + final void removeTreeNode(XHashMap map, XNode[] tab, + boolean movable) { +// int n; +// if (tab == null || (n = tab.length) == 0) +// return; +// int index = (n - 1) & hash; +// TreeNode first = (TreeNode)tab[index], root = first, rl; +// TreeNode succ = (TreeNode)next, pred = prev; +// if (pred == null) +// tab[index] = first = succ; +// else +// pred.next = succ; +// if (succ != null) +// succ.prev = pred; +// if (first == null) +// return; +// if (root.parent != null) +// root = root.root(); +// if (root == null +// || (movable +// && (root.right == null +// || (rl = root.left) == null +// || rl.left == null))) { +// tab[index] = first.untreeify(map); // too small +// return; +// } +// TreeNode p = this, pl = left, pr = right, replacement; +// if (pl != null && pr != null) { +// TreeNode s = pr, sl; +// while ((sl = s.left) != null) // find successor +// s = sl; +// boolean c = s.red; s.red = p.red; p.red = c; // swap colors +// TreeNode sr = s.right; +// TreeNode pp = p.parent; +// if (s == pr) { // p was s's direct parent +// p.parent = s; +// s.right = p; +// } +// else { +// TreeNode sp = s.parent; +// if ((p.parent = sp) != null) { +// if (s == sp.left) +// sp.left = p; +// else +// sp.right = p; +// } +// if ((s.right = pr) != null) +// pr.parent = s; +// } +// p.left = null; +// if ((p.right = sr) != null) +// sr.parent = p; +// if ((s.left = pl) != null) +// pl.parent = s; +// if ((s.parent = pp) == null) +// root = s; +// else if (p == pp.left) +// pp.left = s; +// else +// pp.right = s; +// if (sr != null) +// replacement = sr; +// else +// replacement = p; +// } +// else if (pl != null) +// replacement = pl; +// else if (pr != null) +// replacement = pr; +// else +// replacement = p; +// if (replacement != p) { +// TreeNode pp = replacement.parent = p.parent; +// if (pp == null) +// (root = replacement).red = false; +// else if (p == pp.left) +// pp.left = replacement; +// else +// pp.right = replacement; +// p.left = p.right = p.parent = null; +// } +// +// TreeNode r = p.red ? root : balanceDeletion(root, replacement); +// +// if (replacement == p) { // detach +// TreeNode pp = p.parent; +// p.parent = null; +// if (pp != null) { +// if (p == pp.left) +// pp.left = null; +// else if (p == pp.right) +// pp.right = null; +// } +// } +// if (movable) +// moveRootToFront(tab, r); + } + + /** + * Splits nodes in a tree bin into lower and upper tree bins, + * or untreeifies if now too small. Called only from resize; + * see above discussion about split bits and indices. + * + * @param map the map + * @param tab the table for recording bin heads + * @param index the index of the table being split + * @param bit the bit of hash to split on + */ + final void split(XHashMap map, XNode[] tab, int index, int bit) { +// TreeNode b = this; +// // Relink into lo and hi lists, preserving order +// TreeNode loHead = null, loTail = null; +// TreeNode hiHead = null, hiTail = null; +// int lc = 0, hc = 0; +// for (TreeNode e = b, next; e != null; e = next) { +// next = (TreeNode)e.next; +// e.next = null; +// if ((e.hash & bit) == 0) { +// if ((e.prev = loTail) == null) +// loHead = e; +// else +// loTail.next = e; +// loTail = e; +// ++lc; +// } +// else { +// if ((e.prev = hiTail) == null) +// hiHead = e; +// else +// hiTail.next = e; +// hiTail = e; +// ++hc; +// } +// } +// +// if (loHead != null) { +// if (lc <= UNTREEIFY_THRESHOLD) +// tab[index] = loHead.untreeify(map); +// else { +// tab[index] = loHead; +// if (hiHead != null) // (else is already treeified) +// loHead.treeify(tab); +// } +// } +// if (hiHead != null) { +// if (hc <= UNTREEIFY_THRESHOLD) +// tab[index + bit] = hiHead.untreeify(map); +// else { +// tab[index + bit] = hiHead; +// if (loHead != null) +// hiHead.treeify(tab); +// } +// } + } + + /** + * Recursive invariant check + */ + static boolean checkInvariants(TreeNode t) { + TreeNode tp = t.parent, tl = t.left, tr = t.right, + tb = t.prev, tn = (TreeNode)t.next; + if (tb != null && tb.next != t) + return false; + if (tn != null && tn.prev != t) + return false; + if (tp != null && t != tp.left && t != tp.right) + return false; + if (tl != null && (tl.parent != t || tl.hash > t.hash)) + return false; + if (tr != null && (tr.parent != t || tr.hash < t.hash)) + return false; + if (t.red && tl != null && tl.red && tr != null && tr.red) + return false; + if (tl != null && !checkInvariants(tl)) + return false; + if (tr != null && !checkInvariants(tr)) + return false; + return true; + } + } + + + public void dumpStats(PrintStream out) { + out.printf("%s instance: size: %d%n", this.getClass().getName(), this.size()); + long size = heapSize(); + long bytesPer = size / this.size(); + out.printf(" heap size: %d(bytes), avg bytes per entry: %d, table len: %d%n", + size, bytesPer, table.length); + long[] types = entryTypes(); + out.printf(" values: %d, empty: %d%n", types[0], types[1]); + int[] rehashes = entryRehashes(); + out.printf(" hash collision histogram: max: %d, %s%n", + rehashes.length - 1, Arrays.toString(rehashes)); + } + + private long[] entryTypes() { + long[] counts = new long[3]; + for (XNode te : table) { + counts[te.isEmpty() ? 1 : 0]++; + } + return counts; + } + + // Returns a histogram array of the number of rehashs needed to find each key. + private int[] entryRehashes() { + int[] counts = new int[table.length + 1]; + XNode[] tab = table; + for (XNode te : tab) { + if (!te.isEmpty()) { + int count = 0; + for (Node e = te.next; e != null; e = e.next) + count++; + counts[count]++; + } + } + + int i; + for (i = counts.length - 1; i >= 0 && counts[i] == 0; i--) { + } + counts = Arrays.copyOf(counts, i + 1); + return counts; + } + + private long heapSize() { + long acc = objectSizeMaybe(this); + acc += objectSizeMaybe(table); + + XNode[] tab = table; + for (XNode te : tab) { + if (!te.isEmpty()) { + for (Node e = te.next; e != null; e = e.next) + acc += objectSizeMaybe(e); + } + } + return acc; + } + + private long objectSizeMaybe(Object o) { + try { + return (mObjectSize != null) + ? (long)mObjectSize.invoke(null, o) + : 0L; + } catch (IllegalAccessException | InvocationTargetException e) { + return 0L; + } + } + + private static boolean hasObjectSize = false; + private static Method mObjectSize = getObjectSizeMethod(); + + private static Method getObjectSizeMethod() { + try { + Method m = Objects.class.getDeclaredMethod("getObjectSize", Object.class); + hasObjectSize = true; + return m; + } catch (NoSuchMethodException nsme) { + return null; + } + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/sum/Identity.java b/test/micro/org/openjdk/bench/valhalla/sum/Identity.java new file mode 100644 index 00000000000..3c574668b63 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sum/Identity.java @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.sum; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Setup; + +public class Identity extends SumBase { + + public interface InterfaceSum { + public int sum(); + } + + public static class IdentityInt implements InterfaceSum { + public final int v0; + public IdentityInt(int value) { + this.v0 = value; + } + public int sum() { + return v0; + } + } + + public static class IdentityInt2_w2_d0 implements InterfaceSum { + public final int v0, v1; + + public IdentityInt2_w2_d0(int v0, int v1) { + this.v0 = v0; + this.v1 = v1; + } + + public int sum() { + return v0 + v1; + } + } + + public static class IdentityInt2_w2_d1 implements InterfaceSum { + public final IdentityInt v0, v1; + + public IdentityInt2_w2_d1(IdentityInt v0, IdentityInt v1) { + this.v0 = v0; + this.v1 = v1; + } + + public IdentityInt2_w2_d1(int v0, int v1) { + this(new IdentityInt(v0), new IdentityInt(v1)); + } + + public int sum() { + return v0.sum() + v1.sum(); + } + } + + public static class IdentityInt4_w4_d0 implements InterfaceSum { + public final int v0, v1, v2, v3; + + public IdentityInt4_w4_d0(int v0, int v1, int v2, int v3) { + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + } + + public int sum() { + return v0 + v1 + v2 + v3; + } + } + + public static class IdentityInt4_w4_d1 implements InterfaceSum { + public final IdentityInt v0, v1, v2, v3; + + public IdentityInt4_w4_d1(IdentityInt v0, IdentityInt v1, IdentityInt v2, IdentityInt v3) { + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + } + + public IdentityInt4_w4_d1(int v0, int v1, int v2, int v3) { + this(new IdentityInt(v0), new IdentityInt(v1), new IdentityInt(v2), new IdentityInt(v3)); + } + + public int sum() { + return v0.sum() + v1.sum() + v2.sum() + v3.sum(); + } + } + + public static class IdentityInt4_w2_d1 implements InterfaceSum { + public final IdentityInt2_w2_d0 v0, v1; + + public IdentityInt4_w2_d1(IdentityInt2_w2_d0 v0, IdentityInt2_w2_d0 v1) { + this.v0 = v0; + this.v1 = v1; + } + + public IdentityInt4_w2_d1(int v0, int v1, int v2, int v3) { + this(new IdentityInt2_w2_d0(v0, v1), new IdentityInt2_w2_d0(v2, v3)); + } + + public int sum() { + return v0.sum() + v1.sum(); + } + } + + public static class IdentityInt4_w2_d2 implements InterfaceSum { + + public final IdentityInt2_w2_d1 v0, v1; + + public IdentityInt4_w2_d2(IdentityInt2_w2_d1 v0, IdentityInt2_w2_d1 v1) { + this.v0 = v0; + this.v1 = v1; + } + + public IdentityInt4_w2_d2(int v0, int v1, int v2, int v3) { + this(new IdentityInt2_w2_d1(v0, v1), new IdentityInt2_w2_d1(v2, v3)); + } + public int sum() { + return v0.sum() + v1.sum(); + } + + } + + public static class RefState_of_Int extends SizeState { + public IdentityInt[] arr; + @Setup + public void setup() { + arr = new IdentityInt[size * 4]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IdentityInt(i); + } + } + } + + public static class IntState_of_Int extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size * 4]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IdentityInt(i); + } + } + } + + public static class RefState_of_Int2_w2_d0 extends SizeState { + public IdentityInt2_w2_d0[] arr; + @Setup + public void setup() { + arr = new IdentityInt2_w2_d0[size * 2]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IdentityInt2_w2_d0(2 * i, 2 * i + 1); + } + } + } + + public static class IntState_of_Int2_w2_d0 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size * 2]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IdentityInt2_w2_d0(2 * i, 2 * i + 1); + } + } + } + + public static class RefState_of_Int2_w2_d1 extends SizeState { + public IdentityInt2_w2_d1[] arr; + @Setup + public void setup() { + arr = new IdentityInt2_w2_d1[size * 2]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IdentityInt2_w2_d1(2 * i, 2 * i + 1); + } + } + } + + public static class IntState_of_Int2_w2_d1 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size * 2]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IdentityInt2_w2_d1(2 * i, 2 * i + 1); + } + } + } + + public static class RefState_of_Int4_w4_d0 extends SizeState { + public IdentityInt4_w4_d0[] arr; + @Setup + public void setup() { + arr = new IdentityInt4_w4_d0[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IdentityInt4_w4_d0(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class IntState_of_Int4_w4_d0 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IdentityInt4_w4_d0(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class RefState_of_Int4_w4_d1 extends SizeState { + public IdentityInt4_w4_d1[] arr; + @Setup + public void setup() { + arr = new IdentityInt4_w4_d1[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IdentityInt4_w4_d1(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class IntState_of_Int4_w4_d1 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IdentityInt4_w4_d1(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class RefState_of_Int4_w2_d1 extends SizeState { + public IdentityInt4_w2_d1[] arr; + @Setup + public void setup() { + arr = new IdentityInt4_w2_d1[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IdentityInt4_w2_d1(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class IntState_of_Int4_w2_d1 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IdentityInt4_w2_d1(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class RefState_of_Int4_w2_d2 extends SizeState { + public IdentityInt4_w2_d2[] arr; + @Setup + public void setup() { + arr = new IdentityInt4_w2_d2[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IdentityInt4_w2_d2(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class IntState_of_Int4_w2_d2 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new IdentityInt4_w2_d2(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_interface(InterfaceSum[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_ref_of_Int(IdentityInt[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_ref_of_Int2_w2_d0(IdentityInt2_w2_d0[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_ref_of_Int2_w2_d1(IdentityInt2_w2_d1[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_ref_of_Int4_w4_d0(IdentityInt4_w4_d0[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_ref_of_Int4_w4_d1(IdentityInt4_w4_d1[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_ref_of_Int4_w2_d1(IdentityInt4_w2_d1[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_ref_of_Int4_w2_d2(IdentityInt4_w2_d2[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @Benchmark + public int sum_interface_of_Int(IntState_of_Int st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int2_w2_d0(IntState_of_Int2_w2_d0 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int2_w2_d1(IntState_of_Int2_w2_d1 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int4_w4_d0(IntState_of_Int4_w4_d0 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int4_w4_d1(IntState_of_Int4_w4_d1 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int4_w2_d1(IntState_of_Int4_w2_d1 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int4_w2_d2(IntState_of_Int4_w2_d2 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_ref_of_Int(RefState_of_Int st) { + return sum_ref_of_Int(st.arr); + } + + @Benchmark + public int sum_ref_of_Int2_w2_d0(RefState_of_Int2_w2_d0 st) { + return sum_ref_of_Int2_w2_d0(st.arr); + } + + @Benchmark + public int sum_ref_of_Int2_w2_d1(RefState_of_Int2_w2_d1 st) { + return sum_ref_of_Int2_w2_d1(st.arr); + } + + @Benchmark + public int sum_ref_of_Int4_w4_d0(RefState_of_Int4_w4_d0 st) { + return sum_ref_of_Int4_w4_d0(st.arr); + } + + @Benchmark + public int sum_ref_of_Int4_w4_d1(RefState_of_Int4_w4_d1 st) { + return sum_ref_of_Int4_w4_d1(st.arr); + } + + @Benchmark + public int sum_ref_of_Int4_w2_d1(RefState_of_Int4_w2_d1 st) { + return sum_ref_of_Int4_w2_d1(st.arr); + } + + @Benchmark + public int sum_ref_of_Int4_w2_d2(RefState_of_Int4_w2_d2 st) { + return sum_ref_of_Int4_w2_d2(st.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/sum/Primitive.java b/test/micro/org/openjdk/bench/valhalla/sum/Primitive.java new file mode 100644 index 00000000000..bc7d6105a8c --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sum/Primitive.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.sum; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Setup; + +public class Primitive extends SumBase { + + public static class PrimitiveState extends SizeState { + public int[] arr; + + @Setup + public void setup() { + arr = new int[size * 4]; + for (int i = 0; i < arr.length; i++) { + arr[i] = i; + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + private static int sum_primitive(int[] src) { + int s = 0; + for (int v : src) { + s += v; + } + return s; + } + + @Benchmark + public void sum(PrimitiveState st) { + sum_primitive(st.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/sum/SumBase.java b/test/micro/org/openjdk/bench/valhalla/sum/SumBase.java new file mode 100644 index 00000000000..79496c08244 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sum/SumBase.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.sum; + + +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@Fork(3) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +public class SumBase { + + @State(Scope.Thread) + public static abstract class SizeState { + + @Param({ + "100", // tiny size, to fit into all caches and check codegeneration quality + "1000000" // large size, to be out of all caches and memory effects + }) + public int size; + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/sum/Value.java b/test/micro/org/openjdk/bench/valhalla/sum/Value.java new file mode 100644 index 00000000000..00695288710 --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sum/Value.java @@ -0,0 +1,442 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.sum; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview"}) +public class Value extends SumBase { + + public interface InterfaceSum { + public int sum(); + } + + public static value class ValueInt implements InterfaceSum { + public final int v0; + public ValueInt(int value) { + this.v0 = value; + } + public int sum() { + return v0; + } + } + + public static value class ValueInt2_w2_d0 implements InterfaceSum { + public final int v0, v1; + + public ValueInt2_w2_d0(int v0, int v1) { + this.v0 = v0; + this.v1 = v1; + } + + public int sum() { + return v0 + v1; + } + } + + public static value class ValueInt2_w2_d1 implements InterfaceSum { + public final ValueInt v0, v1; + + public ValueInt2_w2_d1(ValueInt v0, ValueInt v1) { + this.v0 = v0; + this.v1 = v1; + } + + public ValueInt2_w2_d1(int v0, int v1) { + this(new ValueInt(v0), new ValueInt(v1)); + } + + public int sum() { + return v0.sum() + v1.sum(); + } + } + + public static value class ValueInt4_w4_d0 implements InterfaceSum { + public final int v0, v1, v2, v3; + + public ValueInt4_w4_d0(int v0, int v1, int v2, int v3) { + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + } + + public int sum() { + return v0 + v1 + v2 + v3; + } + } + + public static value class ValueInt4_w4_d1 implements InterfaceSum { + public final ValueInt v0, v1, v2, v3; + + public ValueInt4_w4_d1(ValueInt v0, ValueInt v1, ValueInt v2, ValueInt v3) { + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + } + + public ValueInt4_w4_d1(int v0, int v1, int v2, int v3) { + this(new ValueInt(v0), new ValueInt(v1), new ValueInt(v2), new ValueInt(v3)); + } + + public int sum() { + return v0.sum() + v1.sum() + v2.sum() + v3.sum(); + } + } + + public static value class ValueInt4_w2_d1 implements InterfaceSum { + public final ValueInt2_w2_d0 v0, v1; + + public ValueInt4_w2_d1(ValueInt2_w2_d0 v0, ValueInt2_w2_d0 v1) { + this.v0 = v0; + this.v1 = v1; + } + + public ValueInt4_w2_d1(int v0, int v1, int v2, int v3) { + this(new ValueInt2_w2_d0(v0, v1), new ValueInt2_w2_d0(v2, v3)); + } + + public int sum() { + return v0.sum() + v1.sum(); + } + } + + public static value class ValueInt4_w2_d2 implements InterfaceSum { + + public final ValueInt2_w2_d1 v0, v1; + + public ValueInt4_w2_d2(ValueInt2_w2_d1 v0, ValueInt2_w2_d1 v1) { + this.v0 = v0; + this.v1 = v1; + } + + public ValueInt4_w2_d2(int v0, int v1, int v2, int v3) { + this(new ValueInt2_w2_d1(v0, v1), new ValueInt2_w2_d1(v2, v3)); + } + public int sum() { + return v0.sum() + v1.sum(); + } + + } + + public static class ValState_of_Int extends SizeState { + public ValueInt[] arr; + @Setup + public void setup() { + arr = new ValueInt[size * 4]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt(i); + } + } + } + + public static class IntState_of_Int extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size * 4]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt(i); + } + } + } + + public static class ValState_of_Int2_w2_d0 extends SizeState { + public ValueInt2_w2_d0[] arr; + @Setup + public void setup() { + arr = new ValueInt2_w2_d0[size * 2]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt2_w2_d0(2 * i, 2 * i + 1); + } + } + } + + public static class IntState_of_Int2_w2_d0 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size * 2]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt2_w2_d0(2 * i, 2 * i + 1); + } + } + } + + public static class ValState_of_Int2_w2_d1 extends SizeState { + public ValueInt2_w2_d1[] arr; + @Setup + public void setup() { + arr = new ValueInt2_w2_d1[size * 2]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt2_w2_d1(2 * i, 2 * i + 1); + } + } + } + + public static class IntState_of_Int2_w2_d1 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size * 2]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt2_w2_d1(2 * i, 2 * i + 1); + } + } + } + + public static class ValState_of_Int4_w4_d0 extends SizeState { + public ValueInt4_w4_d0[] arr; + @Setup + public void setup() { + arr = new ValueInt4_w4_d0[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w4_d0(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class IntState_of_Int4_w4_d0 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w4_d0(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class ValState_of_Int4_w4_d1 extends SizeState { + public ValueInt4_w4_d1[] arr; + @Setup + public void setup() { + arr = new ValueInt4_w4_d1[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w4_d1(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class IntState_of_Int4_w4_d1 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w4_d1(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class ValState_of_Int4_w2_d1 extends SizeState { + public ValueInt4_w2_d1[] arr; + @Setup + public void setup() { + arr = new ValueInt4_w2_d1[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w2_d1(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class IntState_of_Int4_w2_d1 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w2_d1(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class ValState_of_Int4_w2_d2 extends SizeState { + public ValueInt4_w2_d2[] arr; + @Setup + public void setup() { + arr = new ValueInt4_w2_d2[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w2_d2(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class IntState_of_Int4_w2_d2 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w2_d2(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_interface(InterfaceSum[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_val_of_Int(ValueInt[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_val_of_Int2_w2_d0(ValueInt2_w2_d0[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_val_of_Int2_w2_d1(ValueInt2_w2_d1[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_val_of_Int4_w4_d0(ValueInt4_w4_d0[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_val_of_Int4_w4_d1(ValueInt4_w4_d1[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_val_of_Int4_w2_d1(ValueInt4_w2_d1[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_val_of_Int4_w2_d2(ValueInt4_w2_d2[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @Benchmark + public int sum_interface_of_Int(IntState_of_Int st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int2_w2_d0(IntState_of_Int2_w2_d0 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int2_w2_d1(IntState_of_Int2_w2_d1 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int4_w4_d0(IntState_of_Int4_w4_d0 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int4_w4_d1(IntState_of_Int4_w4_d1 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int4_w2_d1(IntState_of_Int4_w2_d1 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int4_w2_d2(IntState_of_Int4_w2_d2 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_val_of_Int(ValState_of_Int st) { + return sum_val_of_Int(st.arr); + } + + @Benchmark + public int sum_val_of_Int2_w2_d0(ValState_of_Int2_w2_d0 st) { + return sum_val_of_Int2_w2_d0(st.arr); + } + + @Benchmark + public int sum_val_of_Int2_w2_d1(ValState_of_Int2_w2_d1 st) { + return sum_val_of_Int2_w2_d1(st.arr); + } + + @Benchmark + public int sum_val_of_Int4_w4_d0(ValState_of_Int4_w4_d0 st) { + return sum_val_of_Int4_w4_d0(st.arr); + } + + @Benchmark + public int sum_val_of_Int4_w4_d1(ValState_of_Int4_w4_d1 st) { + return sum_val_of_Int4_w4_d1(st.arr); + } + + @Benchmark + public int sum_val_of_Int4_w2_d1(ValState_of_Int4_w2_d1 st) { + return sum_val_of_Int4_w2_d1(st.arr); + } + + @Benchmark + public int sum_val_of_Int4_w2_d2(ValState_of_Int4_w2_d2 st) { + return sum_val_of_Int4_w2_d2(st.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/sum/ValueNullFree.java b/test/micro/org/openjdk/bench/valhalla/sum/ValueNullFree.java new file mode 100644 index 00000000000..7b0f64f691c --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sum/ValueNullFree.java @@ -0,0 +1,452 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.sum; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class ValueNullFree extends SumBase { + + public interface InterfaceSum { + public int sum(); + } + + public static value class ValueInt implements InterfaceSum { + public final int v0; + public ValueInt(int value) { + this.v0 = value; + } + public int sum() { + return v0; + } + } + + public static value class ValueInt2_w2_d0 implements InterfaceSum { + public final int v0, v1; + + public ValueInt2_w2_d0(int v0, int v1) { + this.v0 = v0; + this.v1 = v1; + } + + public int sum() { + return v0 + v1; + } + } + + public static value class ValueInt2_w2_d1 implements InterfaceSum { + @Strict + @NullRestricted + public final ValueInt v0, v1; + + public ValueInt2_w2_d1(ValueInt v0, ValueInt v1) { + this.v0 = v0; + this.v1 = v1; + } + + public ValueInt2_w2_d1(int v0, int v1) { + this(new ValueInt(v0), new ValueInt(v1)); + } + + public int sum() { + return v0.sum() + v1.sum(); + } + } + + public static value class ValueInt4_w4_d0 implements InterfaceSum { + public final int v0, v1, v2, v3; + + public ValueInt4_w4_d0(int v0, int v1, int v2, int v3) { + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + } + + public int sum() { + return v0 + v1 + v2 + v3; + } + } + + public static value class ValueInt4_w4_d1 implements InterfaceSum { + @Strict + @NullRestricted + public final ValueInt v0, v1, v2, v3; + + public ValueInt4_w4_d1(ValueInt v0, ValueInt v1, ValueInt v2, ValueInt v3) { + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + } + + public ValueInt4_w4_d1(int v0, int v1, int v2, int v3) { + this(new ValueInt(v0), new ValueInt(v1), new ValueInt(v2), new ValueInt(v3)); + } + + public int sum() { + return v0.sum() + v1.sum() + v2.sum() + v3.sum(); + } + } + + public static value class ValueInt4_w2_d1 implements InterfaceSum { + @Strict + @NullRestricted + public final ValueInt2_w2_d0 v0, v1; + + public ValueInt4_w2_d1(ValueInt2_w2_d0 v0, ValueInt2_w2_d0 v1) { + this.v0 = v0; + this.v1 = v1; + } + + public ValueInt4_w2_d1(int v0, int v1, int v2, int v3) { + this(new ValueInt2_w2_d0(v0, v1), new ValueInt2_w2_d0(v2, v3)); + } + + public int sum() { + return v0.sum() + v1.sum(); + } + } + + public static value class ValueInt4_w2_d2 implements InterfaceSum { + @Strict + @NullRestricted + public final ValueInt2_w2_d1 v0, v1; + + public ValueInt4_w2_d2(ValueInt2_w2_d1 v0, ValueInt2_w2_d1 v1) { + this.v0 = v0; + this.v1 = v1; + } + + public ValueInt4_w2_d2(int v0, int v1, int v2, int v3) { + this(new ValueInt2_w2_d1(v0, v1), new ValueInt2_w2_d1(v2, v3)); + } + public int sum() { + return v0.sum() + v1.sum(); + } + + } + + public static class ValState_of_Int extends SizeState { + public ValueInt[] arr; + @Setup + public void setup() { + arr = (ValueInt[])ValueClass.newNullRestrictedAtomicArray(ValueInt.class, size * 4, new ValueInt(0)); + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt(i); + } + } + } + + public static class IntState_of_Int extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size * 4]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt(i); + } + } + } + + public static class ValState_of_Int2_w2_d0 extends SizeState { + public ValueInt2_w2_d0[] arr; + @Setup + public void setup() { + arr = (ValueInt2_w2_d0[])ValueClass.newNullRestrictedAtomicArray(ValueInt2_w2_d0.class, size * 2, new ValueInt2_w2_d0(0, 0)); + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt2_w2_d0(2 * i, 2 * i + 1); + } + } + } + + public static class IntState_of_Int2_w2_d0 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size * 2]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt2_w2_d0(2 * i, 2 * i + 1); + } + } + } + + public static class ValState_of_Int2_w2_d1 extends SizeState { + public ValueInt2_w2_d1[] arr; + @Setup + public void setup() { + arr = (ValueInt2_w2_d1[])ValueClass.newNullRestrictedAtomicArray(ValueInt2_w2_d1.class, size * 4, new ValueInt2_w2_d1(0, 0)); + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt2_w2_d1(2 * i, 2 * i + 1); + } + } + } + + public static class IntState_of_Int2_w2_d1 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size * 2]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt2_w2_d1(2 * i, 2 * i + 1); + } + } + } + + public static class ValState_of_Int4_w4_d0 extends SizeState { + public ValueInt4_w4_d0[] arr; + @Setup + public void setup() { + arr = (ValueInt4_w4_d0[])ValueClass.newNullRestrictedAtomicArray(ValueInt4_w4_d0.class, size, new ValueInt4_w4_d0(0, 0, 0, 0)); + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w4_d0(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class IntState_of_Int4_w4_d0 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w4_d0(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class ValState_of_Int4_w4_d1 extends SizeState { + public ValueInt4_w4_d1[] arr; + @Setup + public void setup() { + arr = (ValueInt4_w4_d1[])ValueClass.newNullRestrictedAtomicArray(ValueInt4_w4_d1.class, size, new ValueInt4_w4_d1(0, 0, 0, 0)); + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w4_d1(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class IntState_of_Int4_w4_d1 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w4_d1(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class ValState_of_Int4_w2_d1 extends SizeState { + public ValueInt4_w2_d1[] arr; + @Setup + public void setup() { + arr = (ValueInt4_w2_d1[])ValueClass.newNullRestrictedAtomicArray(ValueInt4_w2_d1.class, size, new ValueInt4_w2_d1(0, 0, 0, 0)); + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w2_d1(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class IntState_of_Int4_w2_d1 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w2_d1(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class ValState_of_Int4_w2_d2 extends SizeState { + public ValueInt4_w2_d2[] arr; + @Setup + public void setup() { + arr = (ValueInt4_w2_d2[])ValueClass.newNullRestrictedAtomicArray(ValueInt4_w2_d2.class, size, new ValueInt4_w2_d2(0, 0, 0, 0)); + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w2_d2(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class IntState_of_Int4_w2_d2 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w2_d2(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_interface(InterfaceSum[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_val_of_Int(ValueInt[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_val_of_Int2_w2_d0(ValueInt2_w2_d0[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_val_of_Int2_w2_d1(ValueInt2_w2_d1[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_val_of_Int4_w4_d0(ValueInt4_w4_d0[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_val_of_Int4_w4_d1(ValueInt4_w4_d1[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_val_of_Int4_w2_d1(ValueInt4_w2_d1[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_val_of_Int4_w2_d2(ValueInt4_w2_d2[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @Benchmark + public int sum_interface_of_Int(IntState_of_Int st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int2_w2_d0(IntState_of_Int2_w2_d0 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int2_w2_d1(IntState_of_Int2_w2_d1 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int4_w4_d0(IntState_of_Int4_w4_d0 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int4_w4_d1(IntState_of_Int4_w4_d1 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int4_w2_d1(IntState_of_Int4_w2_d1 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int4_w2_d2(IntState_of_Int4_w2_d2 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_val_of_Int(ValState_of_Int st) { + return sum_val_of_Int(st.arr); + } + + @Benchmark + public int sum_val_of_Int2_w2_d0(ValState_of_Int2_w2_d0 st) { + return sum_val_of_Int2_w2_d0(st.arr); + } + + @Benchmark + public int sum_val_of_Int2_w2_d1(ValState_of_Int2_w2_d1 st) { + return sum_val_of_Int2_w2_d1(st.arr); + } + + @Benchmark + public int sum_val_of_Int4_w4_d0(ValState_of_Int4_w4_d0 st) { + return sum_val_of_Int4_w4_d0(st.arr); + } + + @Benchmark + public int sum_val_of_Int4_w4_d1(ValState_of_Int4_w4_d1 st) { + return sum_val_of_Int4_w4_d1(st.arr); + } + + @Benchmark + public int sum_val_of_Int4_w2_d1(ValState_of_Int4_w2_d1 st) { + return sum_val_of_Int4_w2_d1(st.arr); + } + + @Benchmark + public int sum_val_of_Int4_w2_d2(ValState_of_Int4_w2_d2 st) { + return sum_val_of_Int4_w2_d2(st.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/valhalla/sum/ValueNullFreeNonAtomic.java b/test/micro/org/openjdk/bench/valhalla/sum/ValueNullFreeNonAtomic.java new file mode 100644 index 00000000000..7d70516da6a --- /dev/null +++ b/test/micro/org/openjdk/bench/valhalla/sum/ValueNullFreeNonAtomic.java @@ -0,0 +1,460 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.valhalla.sum; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.LooselyConsistentValue; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.internal.vm.annotation.Strict; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Setup; + +@Fork(value = 3, jvmArgsAppend = {"--enable-preview", "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED"}) +public class ValueNullFreeNonAtomic extends SumBase { + + public interface InterfaceSum { + public int sum(); + } + + @LooselyConsistentValue + public static value class ValueInt implements InterfaceSum { + public final int v0; + public ValueInt(int value) { + this.v0 = value; + } + public int sum() { + return v0; + } + } + + @LooselyConsistentValue + public static value class ValueInt2_w2_d0 implements InterfaceSum { + public final int v0, v1; + + public ValueInt2_w2_d0(int v0, int v1) { + this.v0 = v0; + this.v1 = v1; + } + + public int sum() { + return v0 + v1; + } + } + + @LooselyConsistentValue + public static value class ValueInt2_w2_d1 implements InterfaceSum { + @Strict + @NullRestricted + public final ValueInt v0, v1; + + public ValueInt2_w2_d1(ValueInt v0, ValueInt v1) { + this.v0 = v0; + this.v1 = v1; + } + + public ValueInt2_w2_d1(int v0, int v1) { + this(new ValueInt(v0), new ValueInt(v1)); + } + + public int sum() { + return v0.sum() + v1.sum(); + } + } + + @LooselyConsistentValue + public static value class ValueInt4_w4_d0 implements InterfaceSum { + public final int v0, v1, v2, v3; + + public ValueInt4_w4_d0(int v0, int v1, int v2, int v3) { + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + } + + public int sum() { + return v0 + v1 + v2 + v3; + } + } + + @LooselyConsistentValue + public static value class ValueInt4_w4_d1 implements InterfaceSum { + @Strict + @NullRestricted + public final ValueInt v0, v1, v2, v3; + + public ValueInt4_w4_d1(ValueInt v0, ValueInt v1, ValueInt v2, ValueInt v3) { + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + } + + public ValueInt4_w4_d1(int v0, int v1, int v2, int v3) { + this(new ValueInt(v0), new ValueInt(v1), new ValueInt(v2), new ValueInt(v3)); + } + + public int sum() { + return v0.sum() + v1.sum() + v2.sum() + v3.sum(); + } + } + + @LooselyConsistentValue + public static value class ValueInt4_w2_d1 implements InterfaceSum { + @Strict + @NullRestricted + public final ValueInt2_w2_d0 v0, v1; + + public ValueInt4_w2_d1(ValueInt2_w2_d0 v0, ValueInt2_w2_d0 v1) { + this.v0 = v0; + this.v1 = v1; + } + + public ValueInt4_w2_d1(int v0, int v1, int v2, int v3) { + this(new ValueInt2_w2_d0(v0, v1), new ValueInt2_w2_d0(v2, v3)); + } + + public int sum() { + return v0.sum() + v1.sum(); + } + } + + @LooselyConsistentValue + public static value class ValueInt4_w2_d2 implements InterfaceSum { + @Strict + @NullRestricted + public final ValueInt2_w2_d1 v0, v1; + + public ValueInt4_w2_d2(ValueInt2_w2_d1 v0, ValueInt2_w2_d1 v1) { + this.v0 = v0; + this.v1 = v1; + } + + public ValueInt4_w2_d2(int v0, int v1, int v2, int v3) { + this(new ValueInt2_w2_d1(v0, v1), new ValueInt2_w2_d1(v2, v3)); + } + public int sum() { + return v0.sum() + v1.sum(); + } + + } + + public static class ValState_of_Int extends SizeState { + public ValueInt[] arr; + @Setup + public void setup() { + arr = (ValueInt[])ValueClass.newNullRestrictedNonAtomicArray(ValueInt.class, size * 4, new ValueInt(0)); + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt(i); + } + } + } + + public static class IntState_of_Int extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size * 4]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt(i); + } + } + } + + public static class ValState_of_Int2_w2_d0 extends SizeState { + public ValueInt2_w2_d0[] arr; + @Setup + public void setup() { + arr = (ValueInt2_w2_d0[])ValueClass.newNullRestrictedNonAtomicArray(ValueInt2_w2_d0.class, size * 2, new ValueInt2_w2_d0(0, 0)); + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt2_w2_d0(2 * i, 2 * i + 1); + } + } + } + + public static class IntState_of_Int2_w2_d0 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size * 2]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt2_w2_d0(2 * i, 2 * i + 1); + } + } + } + + public static class ValState_of_Int2_w2_d1 extends SizeState { + public ValueInt2_w2_d1[] arr; + @Setup + public void setup() { + arr = (ValueInt2_w2_d1[])ValueClass.newNullRestrictedNonAtomicArray(ValueInt2_w2_d1.class, size * 4, new ValueInt2_w2_d1(0, 0)); + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt2_w2_d1(2 * i, 2 * i + 1); + } + } + } + + public static class IntState_of_Int2_w2_d1 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size * 2]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt2_w2_d1(2 * i, 2 * i + 1); + } + } + } + + public static class ValState_of_Int4_w4_d0 extends SizeState { + public ValueInt4_w4_d0[] arr; + @Setup + public void setup() { + arr = (ValueInt4_w4_d0[])ValueClass.newNullRestrictedNonAtomicArray(ValueInt4_w4_d0.class, size, new ValueInt4_w4_d0(0, 0, 0, 0)); + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w4_d0(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class IntState_of_Int4_w4_d0 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w4_d0(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class ValState_of_Int4_w4_d1 extends SizeState { + public ValueInt4_w4_d1[] arr; + @Setup + public void setup() { + arr = (ValueInt4_w4_d1[])ValueClass.newNullRestrictedNonAtomicArray(ValueInt4_w4_d1.class, size, new ValueInt4_w4_d1(0, 0, 0, 0)); + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w4_d1(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class IntState_of_Int4_w4_d1 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w4_d1(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class ValState_of_Int4_w2_d1 extends SizeState { + public ValueInt4_w2_d1[] arr; + @Setup + public void setup() { + arr = (ValueInt4_w2_d1[])ValueClass.newNullRestrictedNonAtomicArray(ValueInt4_w2_d1.class, size, new ValueInt4_w2_d1(0, 0, 0, 0)); + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w2_d1(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class IntState_of_Int4_w2_d1 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w2_d1(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class ValState_of_Int4_w2_d2 extends SizeState { + public ValueInt4_w2_d2[] arr; + @Setup + public void setup() { + arr = (ValueInt4_w2_d2[])ValueClass.newNullRestrictedNonAtomicArray(ValueInt4_w2_d2.class, size, new ValueInt4_w2_d2(0, 0, 0, 0)); + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w2_d2(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + public static class IntState_of_Int4_w2_d2 extends SizeState { + public InterfaceSum[] arr; + @Setup + public void setup() { + arr = new InterfaceSum[size]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new ValueInt4_w2_d2(4 * i, 4 * i + 1, 4 * i + 2, 4 * i + 3); + } + } + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_interface(InterfaceSum[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_val_of_Int(ValueInt[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_val_of_Int2_w2_d0(ValueInt2_w2_d0[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_val_of_Int2_w2_d1(ValueInt2_w2_d1[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_val_of_Int4_w4_d0(ValueInt4_w4_d0[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_val_of_Int4_w4_d1(ValueInt4_w4_d1[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_val_of_Int4_w2_d1(ValueInt4_w2_d1[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int sum_val_of_Int4_w2_d2(ValueInt4_w2_d2[] src) { + int s = 0; + for (var v : src) { + s += v.sum(); + } + return s; + } + + @Benchmark + public int sum_interface_of_Int(IntState_of_Int st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int2_w2_d0(IntState_of_Int2_w2_d0 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int2_w2_d1(IntState_of_Int2_w2_d1 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int4_w4_d0(IntState_of_Int4_w4_d0 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int4_w4_d1(IntState_of_Int4_w4_d1 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int4_w2_d1(IntState_of_Int4_w2_d1 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_interface_of_Int4_w2_d2(IntState_of_Int4_w2_d2 st) { + return sum_interface(st.arr); + } + + @Benchmark + public int sum_val_of_Int(ValState_of_Int st) { + return sum_val_of_Int(st.arr); + } + + @Benchmark + public int sum_val_of_Int2_w2_d0(ValState_of_Int2_w2_d0 st) { + return sum_val_of_Int2_w2_d0(st.arr); + } + + @Benchmark + public int sum_val_of_Int2_w2_d1(ValState_of_Int2_w2_d1 st) { + return sum_val_of_Int2_w2_d1(st.arr); + } + + @Benchmark + public int sum_val_of_Int4_w4_d0(ValState_of_Int4_w4_d0 st) { + return sum_val_of_Int4_w4_d0(st.arr); + } + + @Benchmark + public int sum_val_of_Int4_w4_d1(ValState_of_Int4_w4_d1 st) { + return sum_val_of_Int4_w4_d1(st.arr); + } + + @Benchmark + public int sum_val_of_Int4_w2_d1(ValState_of_Int4_w2_d1 st) { + return sum_val_of_Int4_w2_d1(st.arr); + } + + @Benchmark + public int sum_val_of_Int4_w2_d2(ValState_of_Int4_w2_d2 st) { + return sum_val_of_Int4_w2_d2(st.arr); + } + +} diff --git a/test/micro/org/openjdk/bench/vm/compiler/CodeCacheStress.java b/test/micro/org/openjdk/bench/vm/compiler/CodeCacheStress.java index 958d986e38e..61a7c19d5db 100644 --- a/test/micro/org/openjdk/bench/vm/compiler/CodeCacheStress.java +++ b/test/micro/org/openjdk/bench/vm/compiler/CodeCacheStress.java @@ -321,6 +321,7 @@ protected Class findClass(String name) throws ClassNotFoundException { } } + @SuppressWarnings("initialization") CodeCacheStress.BenchLoader loader1 = new CodeCacheStress.BenchLoader(); @Setup(Level.Trial) diff --git a/test/micro/org/openjdk/bench/vm/compiler/InterfaceCalls.java b/test/micro/org/openjdk/bench/vm/compiler/InterfaceCalls.java index d2108fb788a..18aea06790b 100644 --- a/test/micro/org/openjdk/bench/vm/compiler/InterfaceCalls.java +++ b/test/micro/org/openjdk/bench/vm/compiler/InterfaceCalls.java @@ -188,8 +188,11 @@ public int getIntFirst() { } final int asLength = 5; + @SuppressWarnings("initialization") public FirstInterface[] as = new FirstInterface[asLength]; + @SuppressWarnings("initialization") public FirstInterface[] noninlined = new FirstInterface[asLength]; + @SuppressWarnings("initialization") public FirstInterfaceExtExt[] noninlinedextext = new FirstInterfaceExtExt[asLength]; diff --git a/test/micro/org/openjdk/bench/vm/compiler/PostAllocationStores.java b/test/micro/org/openjdk/bench/vm/compiler/PostAllocationStores.java index 16f11230ce6..f22d2accb1b 100644 --- a/test/micro/org/openjdk/bench/vm/compiler/PostAllocationStores.java +++ b/test/micro/org/openjdk/bench/vm/compiler/PostAllocationStores.java @@ -95,6 +95,7 @@ static class TestWithNonNullStores { int intField2; long longField1; + @SuppressWarnings("initialization") public TestWithNonNullStores() { objectField1 = this; objectField2 = this; diff --git a/test/micro/org/openjdk/bench/vm/compiler/RBTreeSearch.java b/test/micro/org/openjdk/bench/vm/compiler/RBTreeSearch.java index 9b7430ccc13..9c2ce5386d7 100644 --- a/test/micro/org/openjdk/bench/vm/compiler/RBTreeSearch.java +++ b/test/micro/org/openjdk/bench/vm/compiler/RBTreeSearch.java @@ -388,6 +388,7 @@ private class KeyIteratorImpl implements Iterator { next = firstEntry; } + @SuppressWarnings("initialization") KeyIteratorImpl(final int k) { if ((next = locateKey(k)) != null) { if (next.key <= k) { diff --git a/test/micro/org/openjdk/bench/vm/compiler/WriteBarrier.java b/test/micro/org/openjdk/bench/vm/compiler/WriteBarrier.java index 3306974257c..a51546bd745 100644 --- a/test/micro/org/openjdk/bench/vm/compiler/WriteBarrier.java +++ b/test/micro/org/openjdk/bench/vm/compiler/WriteBarrier.java @@ -78,6 +78,7 @@ public abstract class WriteBarrier { // For field references public class Referencer { Referencer next = null; + @SuppressWarnings("initialization") Referencer() { this.next = null; } diff --git a/test/micro/org/openjdk/bench/vm/compiler/x86/RedundantLeaPeephole.java b/test/micro/org/openjdk/bench/vm/compiler/x86/RedundantLeaPeephole.java index 00a33a47617..191b837d73c 100644 --- a/test/micro/org/openjdk/bench/vm/compiler/x86/RedundantLeaPeephole.java +++ b/test/micro/org/openjdk/bench/vm/compiler/x86/RedundantLeaPeephole.java @@ -71,6 +71,7 @@ public boolean doEquals(String other) { private StoreNHelper[] arrH1 = new StoreNHelper[SIZE]; private StoreNHelper[] arrH2 = new StoreNHelper[SIZE]; + @SuppressWarnings("initialization") private StringEqualsHelper strEqHelper = new StringEqualsHelper("foo"); @Benchmark diff --git a/test/micro/org/openjdk/bench/vm/lambda/invoke/Function0.java b/test/micro/org/openjdk/bench/vm/lambda/invoke/Function0.java index 0ff26bf9d65..49ee80ac048 100644 --- a/test/micro/org/openjdk/bench/vm/lambda/invoke/Function0.java +++ b/test/micro/org/openjdk/bench/vm/lambda/invoke/Function0.java @@ -125,15 +125,19 @@ public Integer lambdaL() { } public final FunctionI mref_I2I = Function0::fooStaticI; + @SuppressWarnings("initialization") public final FunctionI mref_I2I_bound = this::fooInstanceI; public final FunctionL mref_I2L = Function0::fooStaticI; + @SuppressWarnings("initialization") public final FunctionL mref_I2L_bound = this::fooInstanceI; public final FunctionI mref_L2I = Function0::fooStaticL; + @SuppressWarnings("initialization") public final FunctionI mref_L2I_bound = this::fooInstanceL; public final FunctionL mref_L2L = Function0::fooStaticL; + @SuppressWarnings("initialization") public final FunctionL mref_L2L_bound = this::fooInstanceL; // mref naming diff --git a/test/micro/org/openjdk/bench/vm/lambda/invoke/Function1.java b/test/micro/org/openjdk/bench/vm/lambda/invoke/Function1.java index 5d96e58623c..2ed324fc49b 100644 --- a/test/micro/org/openjdk/bench/vm/lambda/invoke/Function1.java +++ b/test/micro/org/openjdk/bench/vm/lambda/invoke/Function1.java @@ -199,24 +199,33 @@ public void lambdaLL(Blackhole bh) { public final FunctionII mref_II2II = Function1::fooStaticII; + @SuppressWarnings("initialization") public final FunctionII mref_II2II_bound = this::fooInstanceII; public final FunctionIL mref_II2IL = Function1::fooStaticII; + @SuppressWarnings("initialization") public final FunctionIL mref_II2IL_bound = this::fooInstanceII; public final FunctionLL mref_II2LL = Function1::fooStaticII; + @SuppressWarnings("initialization") public final FunctionLL mref_II2LL_bound = this::fooInstanceII; public final FunctionII mref_IL2II = Function1::fooStaticIL; + @SuppressWarnings("initialization") public final FunctionII mref_IL2II_bound = this::fooInstanceIL; public final FunctionIL mref_IL2IL = Function1::fooStaticIL; + @SuppressWarnings("initialization") public final FunctionIL mref_IL2IL_bound = this::fooInstanceIL; public final FunctionLL mref_IL2LL = Function1::fooStaticIL; + @SuppressWarnings("initialization") public final FunctionLL mref_IL2LL_bound = this::fooInstanceIL; public final FunctionII mref_LL2II = Function1::fooStaticLL; + @SuppressWarnings("initialization") public final FunctionII mref_LL2II_bound = this::fooInstanceLL; public final FunctionIL mref_LL2IL = Function1::fooStaticLL; + @SuppressWarnings("initialization") public final FunctionIL mref_LL2IL_bound = this::fooInstanceLL; public final FunctionLL mref_LL2LL = Function1::fooStaticLL; + @SuppressWarnings("initialization") public final FunctionLL mref_LL2LL_bound = this::fooInstanceLL;